以下代碼運行環境:windows8.1 32位 VS2015
(一)不含有虛函數的單一繼承模型:測試代碼://單一繼承,無虛函數class A{public: A(int a = 0, char c = 0) :_a(a) , _c(c) {} int GetA() { return _a; } static int staticFun() { return _val; } static int _val;PRivate: int _a; char _c;};class B :public A{public: B(char b = 0) :_b(b) {} char GetB() { return _b; }private: char _b;};int A::_val = 100;void test1(){ A a(10,'a'); B b('b');}現象以及分析:關于類的靜態成員,我們需要知道以下幾點:(1)類的靜態成員是屬于類而不屬于對象,所以他不是類的單個對象所有。(2)靜態成員只存在一個,不像普通的成員,每創建一個對象,就會創建一組普通的成員。(3)靜態成員的初始化不能在類中,肯定是不能在構造函數中,只能在類外并且在main函數之前,按照這樣的格式進行初始化:int A::_val = 100。并且是先初始化再使用。(4)在靜態成員函數中不可以使用非靜態成員。因為非靜態成員是屬于對象,而類的靜態成員函數是屬于類的,在類的對象實例化之前就已經完成了初始化。如果在靜態成員函數用引用非靜態成員,就好比是使用一個并沒有完成初始化的變量。(5)類的非靜態成員函數中可以引用類的靜態成員。反之不可以。(6)靜態成員函數沒有this指針。了解了靜態成員的這些特性(當然本文主要是來分析對象模型的),我們在分析對象模型的時候,可以順便來看看靜態成員到底存儲在哪里?首先看一下上邊代碼的內存分布:
在程序調試中,是這樣表現出來的:
這里,我們只是看到的A類的普通數據成員繼承給子類(上述圖中,子類自稱自父類的成員默認是0,是因為我們的父類的默認構造函數給出的默認值是0),下邊我們來看一下A類的靜態成員是存儲在哪里?是否會繼承給子類?
從這里我們可以看出,父類的靜態成員繼承給子類,并且兩者是存儲在一個地址上的。這里就驗證了這樣的一句話:父類中定義了靜態成員,則整個繼承體系中只有一個這樣的成員,無論派生出多少個子類。靜態成員是存儲在全局區的。(二)含有虛函數的單一繼承模型:測試代碼:class A{public: virtual void foo() { cout << "A::foo()" << endl; } virtual void funA() { cout << "A::funA()" << endl; }private: int _a; char _c;};class B :public A{public: virtual void foo() { cout << "B::foo()" << endl; } virtual void funB() { cout << "B::funB()" << endl; }private: char _b;};void test2(){ A a; B b;}內存分布:
結合程序的調試進行分析:
結合程序的調試信息進行分析:
下邊我們來寫一個函數來打印出我們的虛表,看看與我們分析的是否一樣~~void PrintTable(int* vTable){ typedef void(*pFun)(); cout << "虛表地址為:" << vTable << endl; for (int i = 0; vTable[i] != NULL; ++i) { printf("第%d個虛表地址為:%p->", i, vTable[i]); pFun f = (pFun)vTable[i]; f(); } cout << endl;}void test2(){ A a; B b; int* vTable1 = (int*)(*(int*)&a); int* vTable2 = (int*)(*(int*)&b); PrintTable(vTable1); PrintTable(vTable2);}運行結果:
【總結】:(1)一個類只要有一個虛函數,它就會被編譯器分配一個虛表指針,也就是__vfptr,用來存儲虛函數的地址;(2)子類的虛函數表是在父類的虛函數表上進行修改的,就像上邊的對象模型所示,B類的虛函數就是在A類的虛函數之后;(3)父類中的虛函數被子類改寫,也就是說,子類中含有與父類的虛函數 函數名相同,參數列表相同,返回值相同的函數(協變除外),這樣就構成了重寫。下邊再次區分幾個容易混淆的概念--重載、重寫(覆蓋)、重定義(隱藏)。重載--在同一個作用域內,函數名相同,參數列表不同,返回值可以相同可以不同的兩個函數可以構成重載,需要聲明的是,c++語言中支持函數的重載,而c語言中不支持函數的重載。原因是c語言和c++對函數的處理是不同的。具體可以點擊文末的鏈接。重寫(覆蓋)--在不同的作用域中(分別在父類和子類中),函數名相同,參數列表,返回值都相同(協變除外),并且父類的函數是虛函數,訪問限定符可同可不同的兩個函數就構成了重寫。重定義(隱藏)--在不同的作用域中(分別在父類和子類),函數名相同,只要不是構成重寫就是重定義。 協變:協變也是一種重寫,只是父類和子類中的函數的返回值不同,父類的函數返回父類的指針或者引用,子類函數返回子類的指針或者引用。(4)只有類的成員函數才可以被定義為虛函數,靜態成員函數不可以被定義為虛函數。(三)多繼承的對象模型測試代碼:class A{public: virtual void foo() { cout << "A::foo()" << endl; } virtual void funA() { cout << "A::funA()" << endl; }private: int _a;};class B{public: virtual void foo() { cout << "B::foo()" << endl; } virtual void funB() { cout << "B::funB()" << endl; }private: int _b;};class C :public A, public B{public: virtual void foo() { cout << "C::foo()" << endl; } virtual void funC() { cout << "C::funC()" << endl; }private: int _c;};void test3(){ C c;}下邊我們先通過調試信息,看看對象的內存分布:
通過這個圖,我們就可以畫出多繼承的對象的內存分布:
下邊我們仍然編寫一個函數打印出虛函數表:void PrintTable(int* vTable){ typedef void(*pFun)(); cout << "虛表地址為:" << vTable << endl; for (int i = 0; vTable[i] != NULL; ++i) { printf("第%d個虛函數地址為:0x%p->", i, vTable[i]); pFun f = (pFun)vTable[i]; f(); } cout << endl;}void test3(){ C c; int* vTable = (int*)(*(int*)&c); PrintTable(vTable); vTable = (int *)(*((int*)&c + sizeof(A) / 4)); PrintTable(vTable);}程序運行結果:
【總結】在多重繼承體系下,有n個含有虛函數的父類,派生類中就有n個虛函數表,最終子類的虛函數是在第一個父類的虛函數表中;(四)含有虛繼承的多重繼承模型(含有虛函數)測試代碼:class A{public: A() :_a(1) {} virtual void foo() { cout << "A::foo()" << endl; } virtual void funA() { cout << "A::funA()" << endl; }private: int _a;};class B : public virtual A{public: B() :_b(2) {} virtual void foo() { cout << "B::foo()" << endl; } virtual void funB() { cout << "B::funB()" << endl; }private: int _b;};class C : public virtual A{public: C() :_c(3) {} virtual void foo() { cout << "C::foo()" << endl; } virtual void funC() { cout << "C::funC()" << endl; }private: int _c;};class D :public B, public C{public: D() :_d(4) {} virtual void foo() { cout << "D::foo()" << endl; } virtual void FunD() { cout << "D::funD()" << endl; }private: int _d;};通過調試信息看對象模型:B類對象的調試信息:
C類對象的調試信息:
D類對象的調試信息:
下邊,根據調試信息畫出內存分布:
文中涉及到的內容鏈接:內存對齊重載、重寫、重定義詳解重載參考文檔:對象模型(一)對象模型(二)
新聞熱點
疑難解答
圖片精選