概念太多,總結一下,備忘。
1.三大特性
封裝
隱藏對象的屬性和實現細節,僅對外提供公共訪問方式。
繼承
代碼重用,子類繼承父類的函數
構造函數的覆蓋:
(1)父類沒有構造函數或只有無參構造函數。
子類無需顯示調用父類的構造函數,系統自動在調用子類構造函數前調用父類的構造函數。
(2)父類只有有參構造函數
子類必須顯示調用父類構造函數
(3)父類有無參和有參的構造函數
子類不顯示調用父類構造函數時,系統默認調用父類無參構造函數。
拷貝構造函數:用基於同一類的一個對象構造和初始化另一個對象
CExample(const CExample& C)
調用時機:
(1)一個對象以值傳遞的方式傳入函數體
(2)一個對象以值傳遞的方式從函數返回
(3)一個對象需要通過另一個對象進行初始化
Class A{
A (const A& a){//拷貝構造函數
this.x = a.x;
}
};
void fun1(A a){//(1)對象以值傳遞的方式傳入函數體
cout<<a.x<<endl;
}
A fun2(int x){//(2)對象以值傳遞的方式從函數返回
A a(x);
return a;
}
A a;
A b(a);//(3)對象需要通過另一個對象進行初始化
A b=a;//(3)對象需要通過另一個對象進行初始化
多態
多態分爲:編譯時多態和運行時多態。區別在於函數地址是編譯時綁定還是運行時綁定。
編譯時多態:
通過函數重載實現。
運行時多態:
通過虛函數實現。
核心理念就是通過基類訪問派生類定義的函數。
將子類類型的指針賦值給父類類型的指針,只能訪問子父類共有的方法,不能訪問子類特有的方法。
而且只有虛函數是調用子類的(虛函數表),其他函數依舊調用父類的(地址偏移量固定)
2.虛函數vs純虛函數
虛函數:
主要作用是“運行時多態”。virtual void fun()
使用virtual關鍵字的父類函數。子類可以重寫父類的虛函數來實現子類的特殊化。
純虛函數:
在基類中聲明的虛函數,沒有定義,virtual void fun()=0
聲明瞭純虛函數的類是一個抽象類,不能實例化,只能作爲基類。
子類中重新聲明函數,不要後面的=0
3.重載(overload)、覆蓋(override)、隱藏
重載:“讓類以統一的方式處理不同類型數據的一種手段”
同一個類中
函數名相同
參數列表不同
覆蓋:“子類在繼承父類時,重寫(重新實現)父類中的方法”
不同類中
父類中的方法有virtual關鍵字,並且非私有
函數名相同
參數列表相同
返回類型相同
作用域只能擴大(public>protected>private)
隱藏:“子類的函數屏蔽了同名的父類函數。注意只要同名函數,不管參數列表是否相同,基類函數都會被隱藏。”
不同類中
函數名相同
沒有virtual修飾
4.虛函數表
虛函數是通過虛函數表實現的。虛函數表(virtual table)實現原理參考了博客C++ 虛函數表解析
首先明確:
1)只有包含虛函數的類纔會有虛函數表
2)同屬於一個類的對象實例共享虛函數表
3)虛函數表實際上是一個指針數組,存放了虛函數的地址。
4)爲了保證效率,虛函數表指針存在對象實例的最前面的位置。
總結幾種情況:
//基類
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
基類的虛函數表
(1)一般繼承(無虛函數覆蓋)
class Derive:public Base {
public:
virtual void f1() { cout << "Derive::f1" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
};
派生類沒有重寫基類的任何虛函數,派生類的虛函數表爲:
注意:
1)虛函數按照聲明順序放在表中
2)基類虛函數在派生類虛函數前面
(2)一般繼承(有虛函數覆蓋)
class Derive:public Base {
public:
void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
};
其中派生類重寫了基類中的f()函數,派生類的虛函數表爲
注意:
1)覆蓋的派生類f()函數被放到了虛表中原來基類虛函數的位置
2)沒有被覆蓋的函數不變
(3)多重繼承(無虛函數覆蓋)
繼承關係
子類的虛函數表爲:
注意:
1) 每個基類都有自己的虛表。
2) 派生類的成員函數被放到了第一個基類的表中。(所謂的第一個父類是按照聲明順序來判斷的)
(4)多重繼承(有虛函數覆蓋)
子類中覆蓋了父類的f()函數
子類的虛函數表:
注意:
三個父類虛函數表中的f()的位置被替換成了子類的函數指針