虛函數:只有類的成員函數才能定義為虛函數
虛函數 在類的成員函數前面加上一個 virtual 關鍵字, 此時這個成員函數就叫做虛函數
虛函數 當在子類中定義了一個與父類完全相同的虛函數的時候,此時就叫做子類的虛函數重寫了父類的虛函數
構成多態的條件 派生類重寫基類的虛函數實現多態, 必須要求函數名相同參數列表以及返回值相同(協變除外)
協變 派生類重寫基類的虛函數的時候函數名相同, 參數列表相同, 但是返回值可以不同(返回值是父子關系)
靜態成員函數不能定義為虛函數
靜態成員函數是屬于這個類的, 它不屬于某個對象, 而虛函數必須有一張虛表, 但是虛表存在于對象中,靜態成員函數既然不屬于對象,那么何來的虛表呢, 沒有虛表又何談虛函數呢
類外定義虛函數 只能在聲明虛函數的時候在虛函數前面加上一個 virtual 關鍵字, 當在類外定義的時候, 此時就不能在其前面加上 virtual 關鍵字
構造函數 構造函數不能為虛函數.
原因是虛函數必須有一張虛表, 但是虛表又存在于對象中, 那么問題來了, 構造函數是為了構造出對象的, 現在對象都沒有, 拿來的虛表, 沒有虛表拿來的虛函數
父類是虛函數,子類會繼續保持,反之不成立
不要在析構函數或者構造函數中調用虛函數
因為在構造函數和析構函數中對象會不完整, 而虛函數的前提是存在于虛表中, 虛表又存在于對象中, 對象不完整,如何找到虛函數呢
最好把基類的析構函數聲明為虛函數
class Person
{...
};class Student:public Person
{...
};int main()
{Person* p = new Student;delete p;return 0;
}
看到上面的場景中, 我們第一反應就是當我們一次New, 一次delete沒有任何問題, 但是問題來了, 如果Student中除了普通成員函數外, Student 對象在析構的時候還需要我們自己去清理, 此時delete的只是父類的對象, 那student中需要清理的內存沒有得到清理,此時不就造成內存泄露了嗎?
同時注意, 子類和父類的析構函數在編譯的時候編譯器會將兩者處理為相同函數名, 因此當定義為虛函數的時候,此時關注的就是對象了, 在釋放的時候會看一下p是一個student的對象,因此會清理student中的東西,此時就不會造成內存泄露了
將析構函數定義為虛函數的目的就是為了在析構的時候當對象是子類的時候清理子類,當對象是父類的時候清理父類,已達到資源正確釋放
多態:虛函數指針或引用+父類指針或引用
多態條件
當使用基類指針或者引用調用重寫的虛函數的時候,此時指向父類調用的就是父類的虛函數,指向子類調用的就是子類的虛函數
為什么指針和引用可以實現多態而對象不能實現多態
class A
{
public:virtual void Debug(){}
};class B:public A
{
public:virtual void Debug(){}
};void main()
{B b;A a = b;A * point_A = &b;a.Debug();point_A->Debug();
}
——對于程序A a = b而言,b內存布局在賦值的時候已經從B轉換到A了,多于的數據都被丟棄,因此其就是一個A類型變量,那么a.Debug這里的a就是一個A類型的變量。
——對于point_A來說,它是一個指針,其類型雖然是A,但指向的區域的內容卻是一個B類型的內存結構,雖然內存結果的布局與A兼容,但其虛函數表中的Debug卻是B的實現。
內聯函數不能定義為虛函數
虛函數必須有虛表,虛表中存放的是地址,但是內聯在編譯的過程中會被展開,它沒有地址何來的虛表,沒有虛表何來的虛函數
調用普通函數比調用虛函數快
因為普通函數就是直接call到一個地址,而調用虛函數得先找到虛表, 然后拿著虛表中的地址再找到虛函數, 因此虛函數是間接尋址, 多了訪存次數, 當然也就比普通函數慢了
重寫重載重定義的區別
重載 重載函數必須在同一作用域內,滿足函數名相同, 參數不同的函數就叫做函數之間構成了重載
重寫(覆蓋)不同作用域內(基類和派生類)函數名相同, 參數相同, 返回值相同(協變除外), 同時函數前必須有 virtual 修飾
重定義(隱藏) 不同作用域內(基類和派生類)函數名相同, 只要不是重寫就是重定義
繼承與靜態函數
基類中定義了static成員, 則整個繼承體系中只有一個static成員, 無論派生出多少個子類, 都只有一個static成員實例
純虛函數
成員函數形參后面加上 = 0, 此時的成員函數就會變成純虛函數. 同時包含純虛函數的類就叫做抽象類(接口類)
純虛函數只有在派生類中重新定義后才能實例化出對象
抽象類不能實例化出對象