## 一.繼承中的同名覆蓋
1. **重載** 局限于函數,而且需要通過參數和返回值來區分。
2. **同名覆蓋** 相同的名字出現在不同作用域中。對于函數,則是參數和返回值完全一樣。在繼承的情況下,派生類聲明的同名成員覆蓋基類聲明的同名成員。
>注意,這只是名字的覆蓋,在對象的內存空間中,派生類聲明的同名成員與基類的同名成員仍會占據不同的空間。
3. **同名覆蓋下訪問基類的成員** 可以通過在成員名前加上基類作用域來訪問隱藏的成員。
>[warning] 下面案例可運行在任意編譯器上。
```c++
#include <iostream>
using namespace std;
struct Base
{
int x_;
Base(int x) :x_(x) {}
};
struct Derived : public Base {
int x_;
Derived(int x1, int x2) :Base(x1), x_(x2) {}
};
int main()
{
Derived d(2,5);
cout<<d.x_<<endl; //派生類同名成員覆蓋基類同名成員
cout<<d.Base::x_<<endl; //訪問基類同名成員
return 0;
}
```
>[test]
>5
>2
## 二.虛基類
### 1.虛基類的功能
>繼承帶來的一系列沖突和冗余問題:
>+ 如果一個派生類有多個直接基類,而這些直接基類又有一個共同的基類,則在最終的派生類中會保留該間接共同基類數據成員的多份同名成員。
>+ 在引用這些同名的成員時,必須在成員前增加基類作用域。
虛基類使得在繼承間接共同基類時只保留一份成員,解決數據冗余的問題。
### 2.虛基類的聲明
在派生類聲明時,為繼承方式中添加 `virtual` 關鍵字。
### 3.最派生類
含有虛基類的多重繼承體系中,負責對虛基類進行初始化的類。(一般情況下,用 `A` 類創建對象, `A` 就是最派生類。)
當創建一個類類型對象時,編譯器會從這個對象開始,向上查找其繼承關系。如果有虛基類,則非最派生類在構造時,不會對基類進行構造,直到最派生類構造。
如果虛基類沒有無參構造函數,則 **所有直接和間接派生類的構造函數需要在成員初始化器列表中加上虛基類的構造函數** 。但只有最派生類會執行虛基類的構造函數。
### 4.虛基類的淺層原理
微軟的編譯器(vc6和vs)引入了虛基類表指針。每個對象如果有一個或多個虛基類,就會由編譯器安插一個指針,指向虛基類表。這個虛基類類表是一個數組,它有 `虛基類數目+1` 個元素。第一個元素為虛基類表指針在該派生類的偏移量,后面的元素則是虛基類相對于虛基類表指針位置的偏移量。
其他的編譯器,則可能采用在虛函數表中放入虛基類偏移量的方法。
#### 例1
現在有下面5個類:
```c++
struct A
{
int x_;
A(int x) :x_(x) {}
};
struct B1 :virtual public A {
int y1_;
B1(int x, int y) :A(x), y1_(y) {}
};
struct B2 :virtual public A {
int y2_;
B2() :A(0), y2_(8) {}
};
struct C : public B1,public B2 {
int z_;
C(int x, int y,int z) :A(x),B1(x,y),B2(), z_(z) {}
};
struct D : public C {
int w_;
D(int x, int y,int z,int w_) :A(x),C(x,y,z), w_(z) {}
};
```
它們的繼承關系樹像這樣:

當我們構造一個C類的對象,則C類就是最派生類。此時C類對象需要承擔虛基類A的構造。
當我們構造一個D類的對象,則D類就是最派生類。此時D類對象需要承擔虛基類A的構造。
當我們構造一個B1類的對象,則B1類就是最派生類。此時B1類對象需要承擔虛基類A的構造。
當我們構造一個B2類的對象,則B2類就是最派生類。此時B2類對象需要承擔虛基類A的構造。
#### 例2
```C++
struct A1
{
int a;
A1(int x=0) : a(x) {}
};
struct A2
{
int b;
A2(int x = 0) : b(x) {}
};
struct A3
{
int c;
A3(int x = 0) : c(x) {}
};
struct A4
{
int d;
A4(int x = 0) : d(x) {}
};
struct B :public A2, virtual public A4, public A3, virtual public A1
{
int y2_;
B(int x = 0) : y2_(x){}
};
int main()
{
return 0;
}
```
使用 `Visual Studio` 打開上述代碼,并在命令行選項中添加 `/d1 reportSingleClassLayoutB `,這樣,在編譯時,通過輸出窗口可以看到 類B的內存布局:
```c
class B size(24):
+---
0 | +--- (base class A2)
0 | | b
| +---
4 | +--- (base class A3)
4 | | c
| +---
8 | {vbptr}
12 | y2_
+---
+--- (virtual base A4)
16 | d
+---
+--- (virtual base A1)
20 | a
+---
B::$vbtable@:
0 | -8
1 | 8 (Bd(B+8)A4)
2 | 12 (Bd(B+8)A1)
vbi: class offset o.vbptr o.vbte fVtorDisp
A4 16 8 4 0
A1 20 8 8 0
```
上面的信息展示出了B的內存布局、虛基類表 `B::$vbtable@` 以及虛基類表的信息 `vbi` 。
在相對位置為 `8` 的位置有一個 `vbptr` ,它是虛基類表指針。再通過看虛基類表中,我們可以看到,第0個元素是虛基類相對于該派生類的偏移量,第1-2個元素則是相應虛基類的偏移量。
- 閱讀說明
- 1.1 概述
- C++基礎
- 1.2 變量與常量
- 1.2.1 變量
- 1.2.2 字面值常量
- 字符型常量
- 數值型常量
- 1.2.3 cv限定符
- 1.3 作用域
- 1.3.1 標識符
- 1.3.2 *命名空間
- 1.3.3 作用域
- 1.3.4 可見性
- 1.4 數據類型
- 1.4.1 概述
- 1.4.2 處理類型
- 類型別名
- * auto說明符
- * decltype說明符
- 1.4.3 數組
- 1.4.4 指針
- 1.4.5 引用
- 1.5 表達式
- 1.5.1 概述
- 1.5.2 值的類別
- 1.5.3 *初始化
- 1.5.4 運算符
- 算術運算符
- 邏輯和關系運算符
- 賦值運算符
- 遞增遞減運算符
- 成員訪問運算符
- 位運算符
- 其他運算符
- 1.5.5 *常量表達式
- 1.5.6 類型轉換
- 第2章 面向過程編程
- 2.1 流程語句
- 2.1.1 條件語句
- 2.1.2 循環語句
- 2.1.3 跳轉語句
- 2.1.4 *異常處理
- 2.2 函數
- 2.2.1 概述
- 2.2.2 函數參數
- 2.2.3 內置函數
- 2.2.4 函數重載
- 2.2.5 * 匿名函數
- 2.3 存儲和生命期
- 2.3.1 生命周期與存儲區域
- 2.3.2 動態內存
- 2.4 *預處理命令
- 第3章 面向對象編程
- 3.1 概述
- 3.2 類和對象
- 3.3 成員
- 3.3.1 訪問限制
- 3.3.2 常成員
- 3.3.3 靜態成員
- 3.3.4 成員指針
- 3.3.5 this指針
- 3.4 特殊的成員函數
- 3.4.1 概述
- 3.4.2 構造函數
- 3.4.3 析構函數
- 3.4.4 拷貝語義
- 3.4.5 * 移動語義
- 3.5 友元
- 3.6 運算符重載與類型轉換
- 3.6.1 概述
- 3.6.2 重載方法
- 3.6.3 類型轉換
- 3.7 繼承與多態性
- 3.7.1 概述
- 3.7.2 派生類
- 3.7.3 子類型
- 3.7.4 虛基類
- 3.7.5 虛函數
- 3.7.6 抽象類
- 3.8 模板與泛型
- 3.8.1 概述
- 3.8.2 模板類型
- 3.8.3 *模板參數
- 3.8.4 *模板編譯
- 3.8.5 *模板推斷
- 3.8.6 *實例化與特例化
- 第4章 C++標準庫
- 4.1 概述
- 4.2 輸入輸出流
- 4.2.1 概述
- 4.2.2 *流的狀態
- 4.2.3 *常用流
- 4.2.4 *格式化I/O
- 4.2.5 *低級I/O
- 4.2.6 *隨機訪問
- 4.3 *C輸入輸出
- 4.3.1 *字符輸入輸出
- 4.3.2 *格式化輸入輸出
- 4.4 * 容器
- 4.4.1 * 概述
- 4.4.2 * 基本操作
- 4.4.3 * 順序容器
- 4.4.4 * 迭代器
- 4.4.5 * 容器適配器
- 4.5 * 泛型算法
- 4.6 * 內存管理
- 4.6.1 * 自動指針
- 4.7 * 其他設施