C++之菱形继承与虚继承(含虚函数)
生活随笔
收集整理的這篇文章主要介紹了
C++之菱形继承与虚继承(含虚函数)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
面向對象的三大特征:封裝,多態,繼承
前面我們已經講了繼承的一些知識點,在這基礎上,我們講的時候再涉獵一些多態的只是。
下面我們先接著上次講有虛函數的菱形虛繼承 首先什么是虛函數。? 虛函數:在類里面,函數前面有virtual關鍵字的成員函數就是虛函數。
代碼塊: class base { public:base(){cout << "base()" << endl;}~base(){cout << "~base()" << endl;}virtual void dis()//加了關鍵字virtual,dis就成了虛函數{cout << "pub=" << pub;cout << " pro=" << pro;cout << " pri=" << pri << endl;}int pub; protected:int pro; private:int pri; };
我們先講幾個知識點:
1.虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱為V-Table。
2.在一個類中,成員函數有虛函數的話,那么,這個對象的前四個字節一定是存放一個指向這個虛函數表(簡稱虛表) ?? 的指針。虛表里面放的是虛函數的地址
3.對于菱形繼承這樣,有多個基類的類對象,則會有多個虛表,每一個基類對應一個虛表,同時,虛表的順序和繼承時的順序相同。
4.即使是存在虛基類指針,虛表指針也是在虛基類指針的上方,這是為了保證正確取到虛函數的偏移量。 舉個簡單的例子
代碼塊: #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<stdio.h> using namespace std; class base { public:base(int a=1,int b=2,int c=3): pub(a), pro(b), pri(c){cout << "base()" << endl << endl;}~base(){cout << "~base()" << endl << endl;}virtual void dis()//加了關鍵字virtual,dis就成了虛函數{cout << "dis()" << endl<<endl;cout << "pub=" << pub;cout << " pro=" << pro;cout << " pri=" << pri << endl << endl;}virtual void func()//同上,虛函數{pub *= 2;pro *= 2;pri *= 2;}int pub; protected:int pro; private:int pri; }; void Test()//析構函數只在函數體結束時候調用,所以在main函數里聲明不好看到析構函數 {base b;cout << "sizeof(b)=" << sizeof(b) << endl << endl; } int main() {Test();system("pause");return 0; }
運行結果: 分析:
在成員函數dis(),func()沒有加上virtual之前,b對象的大小等于12
加上virtual之后輸出size=16,說明多出了四個字節。
在調用監視和內存查看,在對象最開始的四個字節確實是多了一個_vfptr指針
我們又在內存窗口輸入_vfptr的地址0x00F9DC74,發現里面的兩個地址確實和dis(),func()一一對應,也印證了我們上面的觀點。
上一篇文章我們講了沒有虛函數的菱形繼承和虛繼承。
下面我們在觀察一下帶虛函數的菱形虛繼承的對象模型是怎樣的
這里有兩種情況:
1.ABCD四個類的虛函數都是virtual void dis(),誰也不多誰也不少。
代碼塊: #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<iostream> using namespace std; typedef void(*FUNC) ();//函數指針 class A { public:A(int x = 1):a(x){cout << "A類構造函數----A()" << endl << endl;}~A(){cout << "A類析造函數----~A()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "a=" << a << endl;}int a;//把a放在公有位置,便于類外訪問 }; class B :virtual public A//加上了關鍵字virtual,變成了虛繼承 { public:B(int y = 2):b(y){cout << "B類構造函數----B()" << endl << endl;}~B(){cout << "B類析構函數----~B()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "b=" << b << endl;} private:int b; };class C :virtual public A//加上了關鍵字virtual,變成了虛繼承 { public:C(int z = 3):c(z){cout << "C類構造函數----C()" << endl << endl;}~C(){cout << "C類析構函數----~C()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "c=" << c << endl;} private:int c; }; class D :public B, public C//分別公有繼承B,C { public:D(int z1 = 4):d(z1){cout << "D類構造函數------D()" << endl << endl;}~D(){cout << "D類析構函數------~D()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "d=" << d << endl;} private:int d; };void Test() {D obj; } int main() {Test();system("pause");return 0; }
分析: 它有一個虛基類A,所以存在虛基表指針; 它每個類還有虛函數,所以存在,虛表指針; 虛表指針位于虛基表指針上方(這個第二種情況解釋) ; 但是這里比較特殊的就是每個類中的虛函數都是一樣的,所以就構成了這種特殊的對象模型。 在這里我們通過調用監視和查看內存來觀察: 截圖: 通過上圖我們知道,要是每個類都有一樣的虛函數,則虛表指針就不會存在與每一個對象中,而是只在虛基類的前四個字節,然后這個指針就指向了存放著統一的虛函數的虛表。
2.這種情況就是每個類的虛函數存在著不同。 代碼塊: #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<iostream> using namespace std; typedef void(*FUNC) ();//函數指針 class A { public:A(int x = 1):a(x){cout << "A類構造函數----A()" << endl << endl;}~A(){cout << "A類析造函數----~A()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "A::dis()" << endl;}int a;//把a放在公有位置,便于類外訪問 }; class B :virtual public A//加上了關鍵字virtual,變成了虛繼承 { public:B(int y = 2):b(y){cout << "B類構造函數----B()" << endl << endl;}~B(){cout << "B類析構函數----~B()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "B::dis()" << endl;}virtual void func()//和D類相同的虛函數func(){cout << "B::func()" << endl;}virtual void func2()//B類自己的虛函數{cout << "B::func2()" << endl;}int b; }; class C :virtual public A//加上了關鍵字virtual,變成了虛繼承 { public:C(int z = 3):c(z){cout << "C類構造函數----C()" << endl << endl;}~C(){cout << "C類析構函數----~C()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "C::dis()" << endl;}virtual void func3()//C類自己的虛函數{cout << "C::func3()" << endl;}int c; }; class D :public B, public C//分別公有繼承B,C { public:D(int z1 = 4):d(z1){cout << "D類構造函數------D()" << endl << endl;}~D(){cout << "D類析構函數------~D()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "d::dis()" << endl;}virtual void func()//和B類相同的虛函數func(){cout << "D::func()" << endl;}virtual void func4()//D類自己的虛函數{cout << "D::func4()" << endl;}int d; }; void PrintVTable(int* VTable) {cout << " 虛表地址-->" << VTable << endl;for (int i = 0; VTable[i] != 0; ++i){printf(" 第%d個虛函數地址 :0X%x,-->", i, VTable[i]);FUNC f = (FUNC)VTable[i];//函數指針f();//執行這個函數}cout << endl; } int main() {D obj;A obj_a;B obj_b;C obj_c;int* VTable1 = (int*)(*(int*)&obj);//obj的地址,通過一系列的類型轉換,所以指針這塊要熟PrintVTable(VTable1);int* VTable2 = (int*)(*(int*)&obj+32);//obj_a的地址,我是知道對象模型然后把代碼寫死了,你們可以嘗試其它方法PrintVTable(VTable2);int* VTable3 = (int*)(*(int*)&obj_b);//obj_b的地址PrintVTable(VTable3);int* VTable4 = (int*)(*(int*)&obj_c);//obj_c的地址PrintVTable(VTable4);cout << "sizeof (obj)=" << sizeof(obj) << endl;//obj對象的大小,一開始以為是36,但看內存發現多了一串0占了四個字節cout << "sizeof (obj_a)=" << sizeof(obj_a) << endl;cout << "sizeof (obj_b)=" << sizeof(obj_b) << endl;cout << "sizeof (obj_c)=" << sizeof(obj_c) << endl;system("pause");return 0; }
運行結果加后期 截圖1:
截圖2:
模型草圖: 分析:
先了解一下多態:
概念:多態就是多種形態,C++的多態分為靜態多態和動態多態。 1. 靜態多態:主要通過函數的重載和模板來實現,是在編譯時決定,也叫編譯時多態。
2. 動態多態:就是通過繼承重寫基類的虛函數實現的多態,是在運行時決定,也叫運行時多態。
結合上一個例子模型的分析,這里的不同就是每個類里面的虛函數不一樣了,所以它的對象模型也發生了改變,通過內存窗口相信大家也大概知道了是怎樣的一個布局。 很多東西在截圖上面都畫出來了,所以我也不知道該怎么再分析了
相信大家通過自己動手去實現一遍,會有更加深刻的了解。。
前面我們已經講了繼承的一些知識點,在這基礎上,我們講的時候再涉獵一些多態的只是。
下面我們先接著上次講有虛函數的菱形虛繼承 首先什么是虛函數。? 虛函數:在類里面,函數前面有virtual關鍵字的成員函數就是虛函數。
代碼塊: class base { public:base(){cout << "base()" << endl;}~base(){cout << "~base()" << endl;}virtual void dis()//加了關鍵字virtual,dis就成了虛函數{cout << "pub=" << pub;cout << " pro=" << pro;cout << " pri=" << pri << endl;}int pub; protected:int pro; private:int pri; };
我們先講幾個知識點:
1.虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱為V-Table。
2.在一個類中,成員函數有虛函數的話,那么,這個對象的前四個字節一定是存放一個指向這個虛函數表(簡稱虛表) ?? 的指針。虛表里面放的是虛函數的地址
3.對于菱形繼承這樣,有多個基類的類對象,則會有多個虛表,每一個基類對應一個虛表,同時,虛表的順序和繼承時的順序相同。
4.即使是存在虛基類指針,虛表指針也是在虛基類指針的上方,這是為了保證正確取到虛函數的偏移量。 舉個簡單的例子
代碼塊: #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<stdio.h> using namespace std; class base { public:base(int a=1,int b=2,int c=3): pub(a), pro(b), pri(c){cout << "base()" << endl << endl;}~base(){cout << "~base()" << endl << endl;}virtual void dis()//加了關鍵字virtual,dis就成了虛函數{cout << "dis()" << endl<<endl;cout << "pub=" << pub;cout << " pro=" << pro;cout << " pri=" << pri << endl << endl;}virtual void func()//同上,虛函數{pub *= 2;pro *= 2;pri *= 2;}int pub; protected:int pro; private:int pri; }; void Test()//析構函數只在函數體結束時候調用,所以在main函數里聲明不好看到析構函數 {base b;cout << "sizeof(b)=" << sizeof(b) << endl << endl; } int main() {Test();system("pause");return 0; }
運行結果: 分析:
在成員函數dis(),func()沒有加上virtual之前,b對象的大小等于12
加上virtual之后輸出size=16,說明多出了四個字節。
在調用監視和內存查看,在對象最開始的四個字節確實是多了一個_vfptr指針
我們又在內存窗口輸入_vfptr的地址0x00F9DC74,發現里面的兩個地址確實和dis(),func()一一對應,也印證了我們上面的觀點。
上一篇文章我們講了沒有虛函數的菱形繼承和虛繼承。
下面我們在觀察一下帶虛函數的菱形虛繼承的對象模型是怎樣的
這里有兩種情況:
1.ABCD四個類的虛函數都是virtual void dis(),誰也不多誰也不少。
代碼塊: #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<iostream> using namespace std; typedef void(*FUNC) ();//函數指針 class A { public:A(int x = 1):a(x){cout << "A類構造函數----A()" << endl << endl;}~A(){cout << "A類析造函數----~A()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "a=" << a << endl;}int a;//把a放在公有位置,便于類外訪問 }; class B :virtual public A//加上了關鍵字virtual,變成了虛繼承 { public:B(int y = 2):b(y){cout << "B類構造函數----B()" << endl << endl;}~B(){cout << "B類析構函數----~B()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "b=" << b << endl;} private:int b; };class C :virtual public A//加上了關鍵字virtual,變成了虛繼承 { public:C(int z = 3):c(z){cout << "C類構造函數----C()" << endl << endl;}~C(){cout << "C類析構函數----~C()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "c=" << c << endl;} private:int c; }; class D :public B, public C//分別公有繼承B,C { public:D(int z1 = 4):d(z1){cout << "D類構造函數------D()" << endl << endl;}~D(){cout << "D類析構函數------~D()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "d=" << d << endl;} private:int d; };void Test() {D obj; } int main() {Test();system("pause");return 0; }
分析: 它有一個虛基類A,所以存在虛基表指針; 它每個類還有虛函數,所以存在,虛表指針; 虛表指針位于虛基表指針上方(這個第二種情況解釋) ; 但是這里比較特殊的就是每個類中的虛函數都是一樣的,所以就構成了這種特殊的對象模型。 在這里我們通過調用監視和查看內存來觀察: 截圖: 通過上圖我們知道,要是每個類都有一樣的虛函數,則虛表指針就不會存在與每一個對象中,而是只在虛基類的前四個字節,然后這個指針就指向了存放著統一的虛函數的虛表。
2.這種情況就是每個類的虛函數存在著不同。 代碼塊: #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<iostream> using namespace std; typedef void(*FUNC) ();//函數指針 class A { public:A(int x = 1):a(x){cout << "A類構造函數----A()" << endl << endl;}~A(){cout << "A類析造函數----~A()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "A::dis()" << endl;}int a;//把a放在公有位置,便于類外訪問 }; class B :virtual public A//加上了關鍵字virtual,變成了虛繼承 { public:B(int y = 2):b(y){cout << "B類構造函數----B()" << endl << endl;}~B(){cout << "B類析構函數----~B()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "B::dis()" << endl;}virtual void func()//和D類相同的虛函數func(){cout << "B::func()" << endl;}virtual void func2()//B類自己的虛函數{cout << "B::func2()" << endl;}int b; }; class C :virtual public A//加上了關鍵字virtual,變成了虛繼承 { public:C(int z = 3):c(z){cout << "C類構造函數----C()" << endl << endl;}~C(){cout << "C類析構函數----~C()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "C::dis()" << endl;}virtual void func3()//C類自己的虛函數{cout << "C::func3()" << endl;}int c; }; class D :public B, public C//分別公有繼承B,C { public:D(int z1 = 4):d(z1){cout << "D類構造函數------D()" << endl << endl;}~D(){cout << "D類析構函數------~D()" << endl << endl;}virtual void dis()//記住這個dis(),每個類都有這個虛函數{cout << "d::dis()" << endl;}virtual void func()//和B類相同的虛函數func(){cout << "D::func()" << endl;}virtual void func4()//D類自己的虛函數{cout << "D::func4()" << endl;}int d; }; void PrintVTable(int* VTable) {cout << " 虛表地址-->" << VTable << endl;for (int i = 0; VTable[i] != 0; ++i){printf(" 第%d個虛函數地址 :0X%x,-->", i, VTable[i]);FUNC f = (FUNC)VTable[i];//函數指針f();//執行這個函數}cout << endl; } int main() {D obj;A obj_a;B obj_b;C obj_c;int* VTable1 = (int*)(*(int*)&obj);//obj的地址,通過一系列的類型轉換,所以指針這塊要熟PrintVTable(VTable1);int* VTable2 = (int*)(*(int*)&obj+32);//obj_a的地址,我是知道對象模型然后把代碼寫死了,你們可以嘗試其它方法PrintVTable(VTable2);int* VTable3 = (int*)(*(int*)&obj_b);//obj_b的地址PrintVTable(VTable3);int* VTable4 = (int*)(*(int*)&obj_c);//obj_c的地址PrintVTable(VTable4);cout << "sizeof (obj)=" << sizeof(obj) << endl;//obj對象的大小,一開始以為是36,但看內存發現多了一串0占了四個字節cout << "sizeof (obj_a)=" << sizeof(obj_a) << endl;cout << "sizeof (obj_b)=" << sizeof(obj_b) << endl;cout << "sizeof (obj_c)=" << sizeof(obj_c) << endl;system("pause");return 0; }
運行結果加后期 截圖1:
截圖2:
模型草圖: 分析:
先了解一下多態:
概念:多態就是多種形態,C++的多態分為靜態多態和動態多態。 1. 靜態多態:主要通過函數的重載和模板來實現,是在編譯時決定,也叫編譯時多態。
2. 動態多態:就是通過繼承重寫基類的虛函數實現的多態,是在運行時決定,也叫運行時多態。
結合上一個例子模型的分析,這里的不同就是每個類里面的虛函數不一樣了,所以它的對象模型也發生了改變,通過內存窗口相信大家也大概知道了是怎樣的一個布局。 很多東西在截圖上面都畫出來了,所以我也不知道該怎么再分析了
相信大家通過自己動手去實現一遍,會有更加深刻的了解。。
總結
以上是生活随笔為你收集整理的C++之菱形继承与虚继承(含虚函数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MyBatis配置文件,节点顺序
- 下一篇: C++多态的原理(虚函数指针和虚函数表)