虛繼承
父類中只有一份子類的資料, 父類資料並不直接放在虛繼承的子類中,子類中會定義一個虛基表指標,指向父類.虛基表中存在偏移量,這個量就是表地址到父類地址的距離.位置大概是:
虛繼承只有在多繼承關係(隔代繼承)的時候才有比較有效果, 單繼承虛基類效果不顯著. 虛基表中的資料屬於類的公共部分.虛基類和普通類中如果含有一個同名函式(變數), 優先顯示普通類的函式(變數)
注意帶有虛繼承的建構函式與解構函式的呼叫順序:
-
先呼叫虛基類的建構函式. 並按照它們被繼承的順序構造.(注:即使不是直接虛繼承, 是父類虛繼承,也是先呼叫父類虛基類的建構函式)
-
再呼叫非虛基類的建構函式,並按照它們被繼承的順序構造.
-
呼叫類中成員的建構函式,並按照宣告呼叫.
-
再呼叫類本身的建構函式
-
析構的時候先呼叫派生類本身的解構函式, 再呼叫物件成員的解構函式, 最後呼叫基類解構函式. 基類解構函式先呼叫普通類解構函式, 再呼叫虛基類建構函式
來看這個程式:
#include<iostream>
using namespace std;
class A{
public:
A(int a):x(a){ cout<<"A constructor..."<<x<<endl; }
int f(){return ++x;}
~A(){cout<<"destructor A..."<<endl;}
private:
int x;
};
class B:public virtual A{
private:
int y;
A Aobj;
public:
B(int a,int b,int c):A(a),y(c),Aobj(c){ cout<<"B constructor..."<<y<<endl;}
int f(){
A::f();
Aobj.f();
return ++y;
}
void display(){ cout<<A::f()<<"\t"<<Aobj.f()<<"\t"<<f()<<endl; }
~B(){cout<<"destructor B..."<<endl;}
};
class C:public B{
public:
C(int a,int b,int c):B(a,b,c),A(0){ cout<<"C constructor..."<<endl;}
};
class D:public C,public virtual A{
public:
D(int a,int b,int c):C(a,b,c),A(c){ cout<<"D constructor..."<<endl;}
~D(){cout<<"destructor D...."<<endl;}
};
int main()
{
D d(7,8,9);
d.f();
d.display();
return 0;
}
output:
A constructor...9
A constructor...9
B constructor...9
C constructor...
D constructor...
11 11 11
destructor D....
destructor B...
destructor A...
destructor A...
- 先呼叫虛基類的建構函式, a(9)
- 注意第二個建構函式的呼叫是因為虛基類的建構函式只呼叫一次, 所以開始呼叫 Aobj 類成員變數的建構函式 Aobj(9)
- 類成員變數建構函式全部呼叫完後開始呼叫類建構函式 B(a,b,c)
- 父類建構函式呼叫完後依次呼叫子類建構函式 C(), D()
- 虛基類和普通類中如果含有一個同名函式(變數), 優先顯示普通類的函式(變數), 故呼叫 B::func() , 虛基類 a 中的x = 10, Aobj.x = 10, B.y = 10 , 然後呼叫 B.display(), 呼叫虛基類 A.f() , 返回 ++x = 11 , 再呼叫 AObj.f() , 返回 ++Aobj.x = 11, 再呼叫B.f() , 返回 ++ y = 11
- 然後開始析構, 先析構派生類建構函式, 再呼叫父類解構函式; 先呼叫普通類解構函式, 再呼叫虛基類建構函式;故先析構派生類 D, 再析構 C(無輸出), 析構 B的時候,先呼叫 B的解構函式, 再呼叫成員 Aobj 的解構函式, 最後呼叫虛基類 A 的解構函式
虛擬函式與多型
在父類含有虛擬函式,子類重寫父類虛擬函式的情況下, 使用父類指標或者父類引用繫結子類並呼叫虛擬函式, 即為多型.
虛擬函式表
類成員函式並不佔用類物件的記憶體空間. 當物件引入虛擬函式之後,類內會插入一個虛擬函式指標,該指標佔用物件一個指標大小的記憶體空間(4位或者8位)
當存在至少一個虛擬函式時, 編譯期間會為類生成一個虛擬函式表.
當帶有虛擬函式的類生成的時候, 編譯器會為類的建構函式中增加一個為對虛擬函式指標賦值的操作
- 把子類直接複製給子類, 只複製子類中父類的部分,子類會重新建立虛擬函式表,所以呼叫的仍為最初的虛擬函式,使用的不是子類的虛擬函式表.
- base & b = a;或者 base* b = &a; a 為派生類, 引用賦值實際呼叫的還是派生類, 但是它會把派生類的成員隱藏起來無法呼叫, 但是可以正常呼叫父類成員, 同時如果父類中存在虛擬函式, 子類重寫虛擬函式, 那麼透過 虛擬函式指標呼叫子類重寫的虛擬函式(因為虛擬函式表還是沒改變).如果是虛繼承的父類, 也會直接把虛基類的部分直接繼承, 同理虛擬函式指標也會繼承, 所以會呼叫子類重寫的虛擬函式.
- 在父類中再呼叫父類中的虛擬函式, 即使被子類重寫,也會加上base:: 作用域而呼叫父類虛擬函式
c++ 函式過載細節:
很久以前(八十年代),沒有辦法區分++和--運算子的字首與字尾呼叫。這個問題遭到程式設計師的報怨,於是C++語言得到了擴充套件,允許過載increment 和 decrement運算子的兩種形式。
然而有一個句法上的問題,過載函式間的區別決定於它們的引數型別上的差異,但是不論是increment或decrement的字首還是字尾都只有一個引數。為了解決這個語言問題,C++規定字尾形式有一個int型別引數,當函式被呼叫時,編譯器傳遞一個0做為int引數的值給該函式:
// 過載前置自增運算子
Counter& operator++() {
}
// 過載後置自增運算子
Counter& operator++(int) { // 要加入 int 的佔位引數
}
使用引用就可以實現鏈式法則 , 即a++++, 但是注意這裡並無法對前置自增的返回值也進行處理