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