您好,登錄后才能下訂單哦!
這篇文章主要介紹了C++的多態和虛函數實例分析的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇C++的多態和虛函數實例分析文章都會有所收獲,下面我們一起來看看吧。
阿里雖然是國內Java的第一大廠但是并非所有的業務都是由Java支撐,很多服務和中下層的存儲,計算,網絡服務,大規模的分布式任務都是由C++編寫。在阿里所有部門當中對C++考察最深的可能就是阿里云。
阿里對C++的常考點:
1.STL 容器相關實現
2.C++新特性的了解
3.多態和虛函數的實現
4.指針的使用
現在假設有一個編譯好的C++程序,編譯沒有錯誤,但是運行時報錯,報錯如下:你正在調用一個純虛函數(Pure virtual function call error),請問導致這個錯誤的原因可能是什么?
純虛函數調用錯誤一般由以下幾種原因導致:
從基類構造函數直接調用虛函數。(直接調用是指函數內部直接調用虛函數)
從基類析構函數直接調用虛函數。
從基類構造函數間接調用虛函數。(間接調用是指函數內部調用其他的非虛函數,其內部直接或間接地調用了虛函數)
從基類析構函數間接調用虛函數。
通過懸空指針調用虛函數。
注意:其中1,2編譯器會檢測到此類錯誤。3,4,5編譯器無法檢測出此類情況,會在運行時報錯。
編譯器在編譯時期為每個帶虛函數的類創建一份虛函數表
實例化對象時, 編譯器自動將類對象的虛表指針指向這個虛函數表
1.構造基類部分:
構造虛表指針,將實例的虛表指針指向基類的vtbl
構造基類的成員變量
執行基類的構造函數函數體
2.遞歸構造派生類部分:
將實例的虛表指針指向派生類vtbl
構造派生類的成員變量
執行派生類的構造函數體
1.遞歸析構派生類部分:
將實例的虛表指針指向派生類vtbl
執行派生類的析構函數體
析構派生類的成員變量(這里的執行函數體,析構派生類成員變量,兩者的順序和構造的步驟是相反的)
2.析構基類部分:
將實例的虛表指針指向基類的vtbl
執行基類的析構函數函數體
析構基類的成員變量
構造函數和析構函數執行函數體時,實例的虛函數表指針,指向構造函數和析構函數本身所屬的類的虛函數表,此時執行的虛函數,即調用的本身的該類本身的虛函數,下面是一個【間接調用】的栗子:基類中的析構函數中,調用純虛函數(該虛函數就在基類中定義)。
#include <iostream> using namespace std; class Parent { public: //純虛函數 virtual void virtualFunc() = 0; void helper() { virtualFunc(); } virtual ~Parent(){ helper(); } }; class Child : public Parent{ public: void virtualFunc() { cout << "Child" << endl; } virtual ~Child(){} }; int main() { Child child; //system("pause"); return 0; }
運行時報錯:libc++abi.dylib: Pure virtual function called
:
在構造實例過程當中一部分是初始化列表一部分是在函數體內,你能說一下這些的順序是什么?差別是什么和this指針構造的順序
順序:
(1)初始化列表中的先初始化。
(2)執行函數體代碼。
執行類中函數體,如執行構造函數時,所有成員已經初始化完畢了;
this
指針屬于對象,而對象還沒構造完成前,若使用this
指針,編譯器會無法識別。在初始化列表中顯然不能使用this
指針,注意:在構造函數體內部可以使用this
指針。
構造函數的執行可以分成兩個階段:
初始化階段:所有類類型的成員都會在初始化階段初始化,即使該成員沒有出現在構造函數的初始化列表中。
計算賦值階段:一般用于執行構造函數體內的賦值操作。
#include <iostream> using namespace std; class Test1 { public: Test1(){ cout << "Construct Test1" << endl; } //拷貝構造函數 Test1& operator = (const Test1& t1) { cout << "Assignment for Test1" << endl; this->a = t1.a; return *this; } int a ; }; class Test2 { public: Test1 test1; //Test2的構造函數 Test2(Test1 &t1) { cout << "構造函數體開始" << endl; test1 = t1 ; cout << "構造函數體結束" << endl; } }; int main() { Test1 t1; Test2 test(t1); system("pause"); return 0; }
分析上面的結果:
(1)第一行結果即Test t1
實例化對象時,執行Test1
的構造函數;
(2)第二行代碼,實例化Test2
對象時,在執行Test2
構造函數時,正如上面所說的,構造函數的第一步是初始化階段:所有類類型的成員都會在初始化階段初始化,即使該成員沒有出現在構造函數的初始化列表中。所以Test2在構造函數體執行之前已經使用了Test1的默認構造函數初始化好了t1。打印出Construct Test1
。
這里的拷貝構造函數中可以使用
this
指針,指向當前對象。
(3)第三四五行結果:執行Test2
的構造函數。
初始化列表的寫法和順序有沒有什么關系?
構造函數的初始化列表中的前后位置,不影響實際標量的初始化順序。成員初始化的順序和它們在類中的定義順序一致。
必須使用初始化列表的情況:數據成員是const
、引用,或者屬于某種未提供默認構造函數的類類型。
在普通的函數當中調用虛函數和在構造函數當中調用虛函數有什么區別?
普調函數當中調用虛函數是希望運行時多態。而在構造函數當中不應該去調用虛函數因為構造函數當中調用的就是本類型當中的虛函數,無法達到運行時多態的作用。
成員變量,虛函數表指針的位置是怎么排布?
如果一個類帶有虛函數,那么該類實例對象的內存布局如下:
首先是一個虛函數指針,
接下來是該類的成員變量,按照成員在類當中聲明的順序排布,整體對象的大小由于內存對齊會有空白補齊。
其次如果基類沒有虛函數但是子類含有虛函數:
此時內存子類對象的內存排布也是先虛函數表指針再各個成員。
如果將子類指針轉換成基類指針此時編譯器會根據偏移做轉換。在visual studio,x64環境下測試,下面的Parent p = Child();
是父類對象,由子類來實例化對象。
#include <iostream> using namespace std; class Parent{ public: int a; int b; }; class Child:public Parent{ public: virtual void test(){} int c; }; int main() { Child c = Child(); Parent p = Child(); cout << sizeof(c) << endl;//24 cout << sizeof(p) << endl;//8 Child* cc = new Child(); Parent* pp = cc; cout << cc << endl;//0x7fbe98402a50 cout << pp << endl;//0x7fbe98402a58 cout << endl << "子類對象abc成員地址:" << endl; cout << &(cc->a) << endl;//0x7fbe98402a58 cout << &(cc->b) << endl;//0x7fbe98402a5c cout << &(cc->c) << endl;//0x7fbe98402a60 system("pause"); return 0; }
結果如下:
24
8
0000013AC9BA4A40
0000013AC9BA4A48子類對象abc成員地址:
0000013AC9BA4A48
0000013AC9BA4A4C
0000013AC9BA4A50
請按任意鍵繼續. . .
分析上面的結果:
(1)第一行24為子類對象的大小,首先是虛函數表指針8B,然后是2個繼承父類的int
型數值,還有1個是該子類本身的int
型數值,最后的4是填充的。
(2)第二行的8為父類對象的大小,該父類對象由子類初始化,含有2個int
型成員變量。
(3)子類指針cc
指向又new
出來的子類對象(第三個),然后父類指針pp
指向這個子類對象,這兩個指針的值:
父類指針pp
值:0000013AC9BA4A48
子類指針cc
值:0000013AC9BA4A40
即發現如之前所說的:如果將子類指針轉換成基類指針此時編譯器會根據偏移做轉換。我測試環境是64位,所以指針為8個字節。轉換之后pp和cc相差一個虛表指針的偏移。
(4)&(cc->a)
的值即 0000013AC9BA4A48,和pp
值是一樣的,注意前面的 0000013AC9BA4A40到0000013AC9BA4A47其實就是子類對象的虛函數表指針了。
關于“C++的多態和虛函數實例分析”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“C++的多態和虛函數實例分析”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。