目錄
01.概念
02.虛擬繼承
原理
03.繼承和組合
01.概念
單繼承:
一個子類只有一個父類時,稱這種繼承關系為單繼承。
多繼承:
一個子類同時有兩個及以上的父類時,稱這種繼承關系為多繼承。
菱形繼承:
菱形繼承是多繼承的一種特殊形式。
#include<iostream>
using namespace std;
class Person
{
public:string _name; // 姓名
};
class Student : public Person
{
protected:int _num; //學號
};
class Teacher : public Person
{
protected:int _id; // 職工編號
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修課程
};
void Test()
{// 這樣會有二義性無法明確知道訪問的是哪一個Assistant a;a._name = "peter";// 需要顯示指定訪問哪個父類的成員可以解決二義性問題,但是數據冗余問題無法解決a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}
在上述結構中,類 Assistant
?通過類 teacher
和類 student
兩次繼承了類 person
,導致 Assistant
具有兩份 person
的副本。這會帶來以下問題:
- 內存浪費:
Assistant
有兩個person
的副本。 - 二義性:訪問
person
中的成員變量或方法時,編譯器無法確定是通過teacher
?還是student
?繼承的person
。
?
02.虛擬繼承
虛擬繼承可以解決菱形繼承的二義性和數據冗余問題,如上面的繼承關系,在student和teacher的繼承person時使用虛擬繼承,即可解決問題。
class Person
{
public:string _name; // 姓名
};
class Student : virtual public Person
{
protected:int _num; //學號
};
class Teacher : virtual public Person
{
protected:int _id; // 職工編號
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修課程
};
void Test()
{Assistant a;a._name = "peter";
}
此時就不存在了對_name訪問不明確的問題,因為虛擬繼承保證在整個繼承層次中只存在一份基類的實例。
原理
我們用一個簡化的菱形繼承體系,再借助內存窗口觀察對象成員的模型。
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;
}
下面是菱形繼承的內存對象成員模型:這里可以看到數據冗余:
下面是菱形虛擬繼承的內存對象成員模型:
這里可以分析出D對象中將A放到了最下面,這個A同時屬于B和C,那么B和C如何去找到公共的A呢?這里是通過了B和C的兩個指針,指向的一張表。這兩個指針叫虛基表指針,這兩個表叫虛基表。虛基表中存的偏移量。通過偏移量可以找到下面的A。?
03.繼承和組合
繼承是一種“is-a”關系,表示一個類是另一個類的特殊化。通過繼承,子類可以獲得父類的屬性和方法。
特點
- 代碼復用:子類繼承了父類的屬性和方法,減少了代碼重復。
- 層次結構:形成類的層次結構,表示通用和特定的關系。
- 多態性:通過繼承,可以實現多態,即使用父類引用指向子類對象。
組合是一種“has-a”關系,表示一個類包含另一個類作為其成員。組合通常用于表示類之間的部分-整體關系。
特點
- 靈活性:組合比繼承更加靈活,可以動態地改變組合對象的行為。
- 低耦合度:類之間的耦合度較低,有助于維護和擴展代碼。
- 封裝性:通過組合,可以將類的實現細節封裝起來,隱藏復雜性。
class Engine {
public:void start() {cout << "Engine started" << endl;}
};class Car {
private:Engine engine; // Car 包含一個 Engine 對象public:void start() {engine.start(); // 使用 Engine 的方法cout << "Car started" << endl;}
};int main() {Car myCar;myCar.start(); // 組合了 Engine 對象return 0;
}
在這個例子中,Car
包含一個 Engine
對象,因此 Car
“擁有” 一個 Engine
。
繼承 vs 組合
-
繼承:
- 優點:代碼復用、層次結構、多態性。
- 缺點:強耦合,子類依賴于父類的實現,修改父類可能會影響到所有子類。
-
組合:
- 優點:靈活性高,低耦合度,易于維護和擴展。
- 缺點:可能需要編寫更多的代碼來包裝組合對象的功能。
實際盡量多去用組合。組合的耦合度低,代碼維護性好。不過繼承也有用武之地的,有 些關系就適合繼承那就用繼承,另外要實現多態,也必須要繼承。類之間的關系可以用 繼承,可以用組合,就用組合。
以上就是菱形繼承相關知識的整理了,歡迎在評論區留言,覺得這篇博客對你有幫助的,可以點贊收藏關注支持一波~😉