本文將通過三個經典示例程序,帶你徹底理解:
- 多層繼承的訪問規則
- 多源繼承的構造與析構順序
- 菱形繼承中的二義性問題
一,多層繼承
概念
多層繼承指 一個類繼承另一個派生類,形成繼承鏈,例如:
A → B → C
類 C 間接繼承了 A 的成員。
示例代碼:
#include <iostream>
using namespace std;
class A
{
public:
A() { cout << "A" << endl; }
~A() { cout << "~A" << endl; }
public:
void fun_A_public() { cout << "fun_A_public" << endl; }
protected:
void fun_A_protected() { cout << "fun_A_protected" << endl; }
private:
void fun_A_private() { cout << "fun_A_private" << endl; }
};
class B : public A
{
public:
B() { cout << "B" << endl; }
~B() { cout << "~B" << endl; }
public:
void fun_B_public() { cout << "fun_B_public" << endl; }
protected:
void fun_B_protected() { cout << "fun_B_protected" << endl; }
private:
void fun_B_private() { cout << "fun_B_private" << endl; }
};
class C : public B
{
public:
C() { cout << "C" << endl; }
~C() { cout << "~C" << endl; }
public:
void fun_C_public() { cout << "fun_C_public" << endl; }
protected:
void fun_C_protected() { cout << "fun_C_protected" << endl; }
private:
void fun_C_private() { cout << "fun_C_private" << endl; }
};
int main()
{
// 1. 在類外部通過類對象只能訪問public成員
// 2. 對基類成員的訪問由繼承方式和原訪問權限共同決定
// (1)只有基類的public成員,以public形式繼承,那麼派生來的對象在類外部才可以訪問
// (2)除此之外的情況,派生來的對象在類外部都無法訪問
C c;
c.fun_A_public();
c.fun_B_public();
c.fun_C_public();
return 0;
}
- 在類的內部,只能訪問基類的public、protected成員,private無法訪問
- 如果是多層繼承的的話,那麼子類對基類成員的訪問,只看其直接父類中成員訪問權限
- 父類成員在子類中存在的的訪問權限,是由繼承方式和原成員訪問權限共同決定
理解方法:
- 如果看子類內部是否能訪問,那麼將繼承關係的代碼改為單繼承來看。
- 如果看子類外部是否能訪問,那麼將繼承關係的代碼改為單個類來看
- 在改代碼時,將父類中的代碼複製到子類中,然後按照繼承方式修改父類成員訪問權限
運行結果:
結果分析
- 構造順序:
A → B → C - 析構順序:
~C → ~B → ~A - 訪問權限:
- 類外部只能訪問最終類對象中 public繼承下來的public成員。
- 類內部可訪問基類的
public與protected,不能訪問private。
二,多源繼承
概念
多源繼承(或稱“多重繼承”)是指一個派生類從多個基類繼承:
class C : public A, public B
這種設計可以讓子類同時擁有多個基類的功能,但也可能帶來複雜性。
示例代碼:
#include <iostream>
using namespace std;
class A
{
public:
A(int n) { cout << "A" << endl; }
~A() { cout << "~A" << endl; }
};
class B
{
public:
B(int n) { cout << "B" << endl; }
~B() { cout << "~B" << endl; }
};
// 多源繼承:構造順序由繼承列表順序決定,而不是初始化列表順序
class C : public A, public B
{
public:
C(int n) : B(n), A(n)
{
cout << "C" << endl;
}
~C() { cout << "~C" << endl; }
};
int main()
{
C c(5);
return 0;
}
輸出結果:
結果分析
- 構造順序
由繼承列表順序決定:A → B → C即使初始化列表寫成B(n), A(n),也無效。 - 析構順序
與構造相反:~C → ~B → ~A - 注意點
- 構造順序只與 類定義時的繼承順序 有關;
- 初始化列表順序不會改變這一點;
- 若多個基類含有同名成員,會產生訪問二義性。
三,菱形繼承
概念
菱形繼承是一種特殊的多重繼承結構:
類 D 同時繼承 B 和 C,而 B、C 又繼承自 A。
這會導致 D 中出現兩個 A 子對象,從而引發 二義性問題。
示例代碼:
#include <iostream>
using namespace std;
class A
{
public:
A() { cout << "A" << endl; }
~A() { cout << "~A" << endl; }
void fun_A() { cout << "fun_A" << endl; }
};
class B : public A
{
public:
B() { cout << "B" << endl; }
~B() { cout << "~B" << endl; }
void fun() { cout << "fun_B" << endl; }
};
class C : public A
{
public:
C() { cout << "C" << endl; }
~C() { cout << "~C" << endl; }
void fun() { cout << "fun_C" << endl; }
};
class D : public B, public C
{
public:
D() { cout << "D" << endl; }
~D() { cout << "~D" << endl; }
};
int main()
{
D d;
d.B::fun_A(); // 明確指定作用域
// d.fun_A(); // 錯誤:二義性,D 中存在兩個 A 子對象
cout << sizeof(D) << endl;
return 0;
}
運行結果:
結果分析
- 類
D擁有 兩個獨立的 A 子對象(來自 B 和 C)。 - 調用
fun_A()時會出現二義性錯誤,需指定作用域:d.B::fun_A()。 - 內存中
D的大小包含了兩個A子對象的空間。
解決辦法:虛繼承
通過在 B 和 C 中使用 虛繼承,可以讓 D 只保留一個共享的 A 子對象:
class B : virtual public A { ... };
class C : virtual public A { ... };
此時 D 中只有一個 A 實例,二義性問題消失,內存佔用也更小。
四、總結對比表