? ? ? ? 本篇來繼續說說繼承。上篇可移步至【C++】詳細講解繼承(上)?
1.繼承與友元
友元關系不能繼承 ,也就是說基類友元不能訪問派?類私有和保護成員。
class Student;//前置聲明class Same //基類
{
public:friend void Fun(const Same& p, const Student& s);//友元聲明
protected:string _name;
};class Student : public Same //派生類
{
protected:int _stuid;
};
void Fun(const Same& p, const Student& s)
{cout << p._name << endl;cout << s._stuid << endl;
}
像上面的代碼,Fun函數只能訪問Same基類的成員變量_name,_stuid是訪問不到的。
?解決方法就是在派生類Student里面也加上友元聲明,就可以了。
class Student : public Same //派生類
{friend void Fun(const Same& p, const Student& s);//友元聲明
protected:int _stuid;
};
2.繼承與靜態成員
基類定義了static靜態成員,則整個繼承體系?? 只有?個這樣的成員 。?論派?出多少個派?類,都只有?個static成員實例。
class Same //基類
{
public:string _name;static int _count;//靜態成員變量
};int Same::_count = 0;//靜態成員變量初始化class Student : public Same //派生類
{
protected:int _stuNum;
};
?我們觀察一下_name的地址和_count的地址。
int main()
{Same p;Student s;cout << &p._name << endl;cout << &s._name << endl;cout << &p._count << endl;cout << &s._count << endl;return 0;
}
這?的運?結果可以看到:?靜態成員_name的地址是不?樣的,說明派?類繼承下來了,基類和派?類對象各有?份;靜態成員_count的地址是?樣的,說明派?類和基類共?同?份靜態成員。
公有情況下,基類和派生類指定類域都可以訪問靜態成員變量。
cout << Same::_count << endl;
cout << Student::_count << endl;
也可以通過對象訪問。
?
?改變其中一個,另一個也改變,因為這就是同一個。
cout << Same::_count << endl;
cout << Student::_count << endl;
Same::_count++; //改變_count
cout << p._count << endl;
cout << s._count << endl;
3.多繼承以及菱形繼承
3.1 繼承模型
- 單繼承:?個派?類只有?個直接基類時稱這個繼承關系為單繼承
- 多繼承:?個派?類有兩個或以上直接基類時稱這個繼承關系為多繼承,多繼承對象在內存中的模型是,先繼承的基類在前?,后?繼承的基類在后?,派?類成員在放到最后?。
- 菱形繼承:菱形繼承是多繼承的?種特殊情況,如下。
菱形繼承有數據冗余和?義性(存在歧義)的問題,在Assistant的對象中Person成員會有兩份。所以在實踐中并不提倡設計出菱形繼承的模型。
?二義性問題可以通過指定訪問哪個基類的成員來解決,但是數據冗余問題是不能得到解決的。
如果在特定場景下,一定需要設計菱形繼承,怎么辦?虛繼承就出場了。
3.2 虛繼承
新增了一個關鍵字virtual。放在會造成數據冗余和二義性的那些類上。
這里就是在Student和Teacher 加上virtual,都要加,只加一個都沒用。
class Person
{
public:string _name; // 姓名};
// 使?虛繼承Person類
class Student : virtual public Person
{
protected:int _num; //學號
};
// 使?虛繼承Person類
class Teacher : virtual public Person
{
protected:int _id; // 職?編號
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修課程
};
?加了virtual后person在Assistant里繼承的數據就只有一份了,數據冗余和二義性就得到了解決。
3.3 相關小測試
假如現在有一個菱形繼承關系如下,?virtual應該加在哪里?
加在B類和C類上。因為E里面會有數據冗余和二義性,而這些冗余的數據是因為A有兩份,導致繼承A的是B和C,所以要加在B和C上,不是D和C。
?最后,除非萬不得已,不要設計出菱形繼承。菱形繼承以及virtual的底層是特別復雜的。
3.4 多繼承中指針偏移問題
先看題。
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main()
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}
說法正確的是?
答案: ?C :p1 == p3 != p2?先繼承的在前面 ,先聲明的在前面,所以Base1和Base2的底層位置如下。
p3是Derive的指針,指向開頭。
p1是Base1的指針,Base1是基類,p1指向的范圍是派生類Derive切出來的Base1的那部分,最開始指向開頭,和p3一個位置。
p2與p1同理,指向的范圍是Derive切出來的Base2的那部分,最開始指向Base2開頭。
所以答案是 p1 == p3 != p2?
4.繼承和組合
- public繼承是?種is-a的關系。也就是說每個派?類對象都是?個基類對象。
- 組合是?種has-a的關系。假設B組合了A,每個B對象中都有?個A對象。
- 繼承允許你根據基類的實現來定義派?類的實現。這種通過?成派?類的復?通常被稱為?箱復?(white-box reuse)。在繼承?式中,基類的內部細節對派?類可? 。繼承?定程度破壞了基類的封裝,基類的改變,對派?類有很?的影響。派?類和基類間的依賴關系很強,耦合度?。
- 對象組合是類繼承之外的另?種復?選擇。新的更復雜的功能可以通過組裝或組合對象來獲得。對象組合要求被組合的對象具有良好定義的接?。這種復??格被稱為?箱復?(black-box reuse),因為對象的內部細節是不可?的。對象只以“?箱”的形式出現。 組合類之間沒有很強的依賴關系,耦合度低。
- 優先使?組合,?不是繼承。實際盡量多去?組合,組合的耦合度低,代碼維護性好,但這也不是絕對的,要根據實際選擇使用。
比如下面的代碼,就能很好解釋繼承和組合。
class Tire //輪胎
{
protected:string _brand; // 品牌size_t _size; // 尺?
};
class Car //車
{
protected:string _colour; // 顏?string _num; // ?牌號Tire _t1; // 輪胎Tire _t2; // 輪胎Tire _t3; // 輪胎Tire _t4; // 輪胎
};
輪胎和車就比較符合 has-a 的關系,車有輪胎,用的組合。
class BMW : public Car //繼承
{
public:void Drive() { cout << "BMW" << endl; }
};class Benz : public Car //繼承
{
public:void Drive() { cout << "Benz" << endl; }
};
上面的代碼是繼承,BMW 和?Benz 是品牌,比較符合 is-a 的關系,BMW和Benz是車。
本次分享就到這里了,我們下篇見~