C++知識點隨筆(五):虛繼承
虛繼承的出現就是為了解決多繼承中訪問不明確的問題。
首先讓我們先看一下虛繼承的程式碼:
#include<iostream>
using namespace std;
class AA
{
public:
int m_a;
AA()
{
m_a = 100;
}
};
class BB : virtual public AA
{
public:
int m_b;
BB()
{
m_a = 200;
}
};
class CC : virtual public AA
{
public:
int m_c;
CC()
{
m_a = 300;
}
};
class DD : public BB, public CC
{
public:
int m_d;
DD()
{
m_a = 400;
}
};
int main()
{
DD dd;
cout << sizeof(dd) << endl;
cout << &dd << endl;
cout << &dd.m_a << endl;
cout << &dd.m_b << endl;
cout << &dd.m_c << endl;
cout << &dd.m_d << endl;
cout << dd.AA::m_a << endl;
cout << dd.BB::m_a << endl;
cout << dd.CC::m_a << endl;
cout << dd.m_a << endl;
system("pause");
return 0;
}
輸出結果:
dd物件在記憶體中的結構如下:
我們看到虛繼承中,記憶體的分配是與多繼承不同的,我們不再是賦值兩份AA物件,而是將虛基類放在了最後面,然後原來的BB和CC所繼承的AA的地址存放的是指向AA物件的指標,這樣做的另一個好處是還可以節約記憶體空間,因為如果AA物件的大小是100個位元組,那我們也只需要一個4位元組的指標指向它就可以了,而不用賦值一份兒佔用空間。
注意:虛基類只有一份,並且所有繼承虛基類的子類都要虛繼承,否則如果有一個不是虛繼承的那也會再複製一份AA,記憶體中同樣存在了兩份AA,那麼訪問不明確的問題就還是存在。
虛基類為什麼要放在最後?
因為我們在使用這個虛基類的時候首先要知道是哪一個物件(就是指向虛基類的指標)呼叫的它,我們把虛基類放在最後就是要保證:前面的所有的物件使用虛基類的時候都是通過指向虛基類的指標找到的。如果我們不放在後面而是放在前面,那麼找到虛基類就有了兩種方式:直接使用物件的地址 or 指向虛基類的指標,這樣我們在使用虛基類的時候還要加一個判斷,來判斷到底是誰呼叫的呢?這樣顯然更麻煩了。所以我們要把虛基類放在後面,讓所有子類都是通過指向虛基類的指標來找到它。
含有虛擬函式的虛繼承
我們首先看一道題:下列程式的結果是什麼?
#include <iostream>
#include <memory.h>
#include <assert.h>
using namespace std;
class A
{
char k[3];
public:
virtual void aa(){};
};
class B : public virtual A
{
char j[3];
public:
virtual void bb(){};
};
class C : public virtual B
{
char i[3];
public:
virtual void cc(){};
};
int main(int arge,char *argv[])
{
cout<<"sizeof(A):"<<sizeof(A)<<endl;
cout<<"sizeof(B):"<<sizeof(B)<<endl;
cout<<"sizeof(C):"<<sizeof(C)<<endl;
system("pause");
return 0;
}
輸出結果:
我們來看一下c物件在記憶體中的結構:
從圖中我們可以看出:每個類最初始的4個位元組都是指向虛擬函式列表的指標,由於A是虛基類,沒有父類,所以A只有8個位元組大小;而B虛繼承了A,所以B要有一個指向A類地址的指標,所以B類是12+8(A的大小)=20;同樣,C類虛繼承了B,所以C要有一個指向B類地址的指標,所以C類是12+20(B的大小)=32。
什麼情況下會產生新的虛擬函式列表呢?
為了便於理解,我們首先來看一下上一個例子中,改為普通繼承情況下的輸出:
我們現在再看一下c物件在記憶體中的結構:
那麼我們是通過什麼方式來確定他們的結構的呢?就是分別用不同型別的指標去指向c這個物件。如下:
C c;
A* a = &c;
B* b = &c;
當我們使用普通繼承的時候a、b、c所返回的地址都是相同的,即 0C 地址。所以在實現多型的時候,父類將虛擬函式加到了這個虛表中,而他的子類在重寫虛擬函式的時候也是首先複製了父類的虛擬函式列表,然後用自己重寫的虛擬函式的指標覆蓋父類所存入的指標。這樣我們在父類呼叫多型的時候就可以直接從虛表裡找到新的虛擬函式的地址了。
相關文章
- C++知識點隨筆(四):耦合問題、new和malloc、虛析構、多繼承C++繼承
- C++知識點隨筆(六):模板C++
- C++虛繼承的概念C++繼承
- c++ 虛繼承詳解C++繼承
- JS基礎-完美掌握繼承知識點JS繼承
- 知識盲點 隨筆筆記筆記
- 【odoo】【知識點】檢視的繼承邏輯Odoo繼承
- 菱形繼承,虛繼承繼承
- Python小知識點隨筆Python
- 虛繼承繼承
- C++知識點隨筆(三):static、const、friend、inlineC++inline
- C/C++【知識點筆記】C++筆記
- C++學習筆記——C++ 繼承C++筆記繼承
- C++ 虛擬函式和虛繼承淺析C++函式繼承
- corejava基礎知識(1)-繼承Java繼承
- Objective c 知識總結 繼承Object繼承
- C++繼承二之虛擬函式C++繼承函式
- C++ 繼承、多型、虛擬函式C++繼承多型函式
- C++中的虛繼承的構造C++繼承
- C++繼承一之公有繼承C++繼承
- Java零散知識點整理(二)(構造方法、繼承)Java構造方法繼承
- C++繼承C++繼承
- JavaSE基礎知識學習-----繼承Java繼承
- C++ 虛繼承 物件記憶體佈局C++繼承物件記憶體
- C++虛繼承原理與類佈局分析C++繼承
- C++知識點49——類繼承與類的構造、拷貝、operator=和解構函式C++繼承函式
- C++繼承詳解:共有(public)繼承,私有(private)繼承,保護(protected)繼承C++繼承
- mysql--平日點滴知識隨筆(待續)MySql
- c++知識點C++
- C++,繼承,基類和派生類指標間賦值等知識C++繼承指標賦值
- C++ | 類繼承C++繼承
- C++菱形繼承C++繼承
- C++中公有繼承、保護繼承、私有繼承的區別C++繼承
- 繼承筆記繼承筆記
- C++基本知識點C++
- C++單繼承、多繼承情況下的虛擬函式表分析C++繼承函式
- C++繼承體系C++繼承
- C++中的繼承C++繼承