一、繼承與靜態成員
基類定義了static 靜態成員,則整個繼承體系里面只有一個這樣的成員。無論派生出多少個子
類,都只有一個 static 成員實例 。
class person
{
public:person(const char* name = "lisi"):_name(name){}
public:string _name;// 靜態成員屬性static int count;
};
class worker : public person
{
public:protected:string _job_number = "111";
};
// 靜態成員屬性需要在類外定義
int person::count = 0;void Test2(void)
{person pobj;worker wobj;pobj._name = "wangwu";cout << pobj._name << endl;cout << wobj._name << endl;cout << "wobj.count: " << wobj.count << endl;// 基類更改這個靜態成員pobj.count = 5;cout << "wobj.count: " << wobj.count << endl;cout << "&pobj.count = " << &pobj.count << endl;cout << "&wobj.count = " << &wobj.count << endl;
}
靜態成員屬性需要在類外定義;
上面的_name,基類對象和派生類對象各自私有一份,而對于靜態成員變量?count,派生類繼承的 count 和基類里面的 count 是同一份。
即基類里面的靜態成員,無論有多少個派生類,它們都共享同一個靜態成員。
二、單繼承
單繼承:一個派生類只有一個直接基類,我們稱這個繼承關系為單繼承。
class Person{};
class Worker : public Person{};
class Teacher : public Worker{};
三、多繼承
多繼承:一個派生類有兩個或以上直接基類時稱這個繼承關系為多繼承。
四、菱形繼承
類似以上繼承為菱形繼承。
菱形繼承會產生數據冗余和二義性的問題。在Assistant的對象中Person成員會有兩份。
二義性無法明確知道訪問的是哪一個,需要顯示指定訪問哪個父類的成員可以解決二義性問題,但是數據冗余問題無法解決。
那么該怎么解決呢?
虛擬繼承可以解決菱形繼承的二義性和數據冗余的問題。
五、虛擬繼承
為解決菱形繼承的二義性和數據冗余,我們采用菱形繼承,我們看以下代碼:
class A
{
public:int _a;
};
// class B : public A
class B : virtual public A
{
public:int _b;
};
// class C : public A
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
六、總結
1.很多人說C++語法復雜,其實多繼承就是一個體現。有了多繼承,就存在菱形繼承,有了菱形繼承就有菱形虛擬繼承,底層實現就很復雜。所以一般不建議設計出多繼承,一定不要設計出菱形繼承。否則在復雜度及性能上都有問題。
2.多繼承可以認為是C++的缺陷之一,很多后來的語言都沒有多繼承,如Java
3.繼承和組合的區別?什么時候用繼承?什么時候用組合?
繼承和組合是面向對象編程中兩種常見的代碼重用機制,它們有不同的使用場景和特點。
繼承:
- 繼承是一種“is-a”關系,它描述了兩個類之間的一種層次結構,子類繼承了父類的屬性和方法,并且可以添加自己的新屬性和方法。
- 適合于在現有類的基礎上進行擴展,通過重用現有類的代碼來實現新類的功能。
- 通常用于描述一種分類或分類的關系,子類是父類的特例,具有更具體的特征或行為。
- 適用于需要實現代碼的重用和擴展的情況。
組合:
- 組合是一種“has-a”關系,它描述了兩個類之間的一種包含關系,一個類包含另一個類的實例作為其成員變量。
- 適合于描述一種包含關系,其中一個類包含另一個類的實例,并且通過這種組合來實現更復雜的功能。
- 通常用于描述一種組合或擁有關系,其中一個類包含了另一個類的實例作為其一部分。
- 適用于需要將不同的類組合在一起實現某個功能的情況,而不是通過繼承來實現。
在未來代碼設計中,遵循的設計原則是:低耦合,高內聚。
在選擇繼承還是組合時,可以考慮以下幾點:
- 代碼重用性:如果需要重用現有類的代碼并擴展其功能,則可以選擇繼承。如果只是需要利用現有類的功能而不需要擴展其功能,則可以選擇組合。
- 耦合性:繼承會增加類之間的耦合性,子類與父類之間存在較強的依賴關系,而組合可以降低耦合性,類之間的關系更靈活。
- 設計靈活性:組合比繼承更靈活,因為可以隨時更改組合關系,而不會影響類的結構。繼承則更加靜態,子類的結構受限于父類的定義。
4.如何定義一個無法被繼承的類?
第一種方式C++98,將基類的構造私有化,派生類繼承這個基類,在實例化對象時,需要調用基類的構造,但由于基類的構造已經私有化,故會編譯報錯。
class A
{
public://將基類的構造函數私有化
private: A(int a = int()):_a(a){cout << "A()" << endl;};
protected:int _a;
};class B : public A
{
protected:int _b;
};
對于 C++11 的做法是:通過關鍵字 final,被final修飾的類無法被繼承,編譯器會強制檢查。
// 用 final 修飾 A類, 此時A類無法被繼承
class A final
{
public:A(int a = int()) :_a(a){cout << "A()" << endl;}
protected:int _a;
};class B : public A
{
protected:int _b;
};