日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++继承详解三 ----菱形继承、虚继承

發布時間:2023/11/30 c/c++ 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++继承详解三 ----菱形继承、虚继承 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載:http://blog.csdn.net/pg_dog/article/details/70175488

今天呢,我們來講講菱形繼承虛繼承。這兩者的講解是分不開的,要想深入了解菱形繼承,你是繞不開虛繼承這一點的。它倆有著什么關系呢?值得我們來剖析。?
菱形繼承也叫鉆石繼承,它是多繼承的一種特殊實例吧,它的基本架構如下圖:?

在我們的設想中,D所對應的對象模型應該如下圖所示:

?
下面我們來用一段代碼驗證一下:

class A { public:A(){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}char a; };class B :public A { public:B(){cout << "B()" << endl;}~B(){cout << "~B()" << endl;}char b; };class C :public A { public:C(){cout << "C()" << endl;}~C(){cout << "~C()" << endl;}int c; }; class D :public B, public C { public:D(){cout << "D()" << endl;}~D(){cout << "~D()" << endl;}int d;};int main() {cout << sizeof(A)<< endl; //1cout << sizeof(B)<< endl; //2cout << sizeof(C)<< endl; //8cout << sizeof(D)<< endl; //16system("pause");return 0; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

上面顯示的大小似乎證實了我們的猜想,但實際上對象模型不是這樣的,如下圖所示?
?
但是你會發現,這里面存在一個問題,對象D中有兩個‘a’,存在數據冗余的問題,如果對象B,C中有兩個同名的函數或同名成員變量(本例中的變量‘a’),那么對象D在調用該函數或該成員變量時,該選擇調用哪個呢?這也就可以看出還存有二義性問題。那么該如何處理呢??
解決二義性問題很簡單,你在調用函數時加上作用域運算符(::),但是數據冗余問題還是沒有解決。那么編譯器是如何處理這兩個問題的呢??
為了解決二義性問題和數據冗余問題,C++引入了虛繼承這一概念。下面重點來看虛繼承。

虛繼承?
虛繼承又稱共享繼承,是面向對象編程的一種技術,是指一個指定的基類,在繼承體系結構中,將其成員數據實例共享給也從這個基類直接或間接派生的其他類。虛擬繼承是多重繼承中特有的概念,虛擬繼承就是為了解決多重繼承而出現的。?
這里我想引入《C++ Primer》這本書中對虛繼承的有關描述。

在C++語言中我們通過虛繼承的機制來解決共享問題。虛繼承的目的是令某個類作出聲明,承諾共享它的基類。其中,共享的基類子對象稱其為虛基類。在這種機制下,不論虛基類在繼承體系中出現了多少次,在派生類中都只含有唯一一個共享的虛基類子對象。

這里還有一個概念,虛基類。虛基類是通過virtual繼承而來的派生類的基類。例如:B虛繼承了A,所以A是B的虛基類,而不是說A是虛基類。?
看下圖了解普通基類與虛基類的區別:

按照上面的說法,在對象D中應該只含有一個共享的虛基類子對象,也就是例子中的_a。確實,這樣就解決了數據冗余與二義性問題。我們來驗證上面的的說法。(為了計算簡單,我將上例中每個類成員變量變為整形int)

下面我們來看一段代碼:

class A { public:A(){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}void print(){printf("A");}int _a; };class B :virtual public A //B虛繼承A { public:B(){cout << "B()" << endl;}~B(){cout << "~B()" << endl;}int _b; };class C :virtual public A //C虛繼承A { public:C(){cout << "C()" << endl;}~C(){cout << "~C()" << endl;}int _c; }; class D :public B, public C { public:D(){cout << "D()" << endl;}~D(){cout << "~D()" << endl;}int _d;};int main() {cout << sizeof(A)<< endl;cout << sizeof(B)<< endl;cout << sizeof(C)<< endl;cout << sizeof(D)<< endl;B bb;C cc;D dd;dd.B::_a = 1;dd.C::_a = 2;dd._b = 3;dd._c = 4;dd._d = 5;system("pause");return 0; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

B和C都是虛擬繼承,?
按照我們之前的推理,對象D的結構應該如圖所示:?
?
我們來通過vs2013調試中的內存窗口來驗證一下:?
?
看到這個結果是不是嚇壞寶寶了?和我們預測的完全不一樣,對象A和B中的_a跑到了最底部,這種結構明顯沒有了數據冗余和二義性問題了,這是怎么實現的呢?這就要引入一新的概念——虛基類表。?
虛基類表:又稱虛基表,編譯器會給虛繼承而來的派生類生成一個指針vbptr指向一個虛基表,而虛基表中存放的是偏移量。?
我們來看對象D中的對象B,它的第一部分(第一行)就是虛基類表指針vbptr,它存的是虛基表的地址,虛基表中存的是共享基類成員變量_a的相對此位置的偏移量,我們來看看,“01259b60”是個地址,利用內存窗口我們可以發現里面存著兩部分第一行“00 00 00 00”和第二行“00 00 00 14”,虛基表中分兩部分:第一部分存儲的是對象相對于存放vptr指針的偏移量(在這就是“00 00 00 00”,偏移量為0),第二部分存儲的是對象中基類對象部分相對于存放vbptr指針的地址的偏移量(在這就是“00 00 00 14”),?
即20(十六進制下14就是十進制的20),也就是說偏移量是20個字節,你可以用他們的地址相減驗證一番。你可以看圖數一下,而對象D中的C的第一部分也是一樣,是個虛基表,存的一樣也是偏移量,它存的地址“00369b68”,里面是“00 00 00 0c”即十進制的12,即偏移量為12字節??梢钥聪聢D:

下面我再講一個概念——虛函數,這會在下篇文章多態中重點講解,但是這里有必要了解一下。?
虛函數——類的成員函數前面加上virtual關鍵字,則這個函數被稱為虛函數。

虛函數:用于定義類型特定行為的成員函數。通過引用和指針對虛函數的調用直到運行時才被解析,依據是引用或指針所綁定對象的類型。(《C++ Primer》中定義)

虛函數重寫(覆蓋):當在子類定義了一個和父類完全相同的虛函數時,則稱這個這個子類的函數重寫了(覆蓋了)父類的虛函數。?
既然說到這,就有必要區分一下幾個概念:?
重載:在同一作用域內,函數名相同,參數不同,返回值可不同的一對函數被稱為重載。?
隱藏(重定義):在不同作用域(一般指基類和派生類),函數名相同,參數列表也相同,但不需要virtual關鍵字的一組函數稱為隱藏。?
覆蓋:不在同一作用域(一般指派生類和基類),完全相同(協變除外)基類中函數必須有virtual關鍵字的一對函數被稱為重定義。

注:?
1,基類中定義了虛函數,在派生類中該函數始終保持虛函數的特性。?
2,只有類的成員函數才能定義為虛函數。?
3,靜態成員函數不能定義為虛函數。?
4,如果在類外定義虛函數,只能在聲明處加virtual關鍵字,類外定義函數時不能加virtual關鍵字。?
5,構造函數不能為虛函數。?
6,最好不要將賦值運算符重載定義為虛函數,因為使用容易混淆。?
7,不要在構造函數和析構函數調用虛函數,在構造函數和析構函數中對象是不完整的,可能會發生未定義的行為。?
8,最好將基類的析構函數定義為虛函數。(注:雖然基類的析構函數和派生類的析構函數名稱不一樣,但構成覆蓋,因為編譯器做了特殊處理)?
9,虛繼承只對虛繼承子類后面派生出的子類有影響,對虛繼承自雷本身沒有影響。

純虛函數?
純虛函數——在成員函數的后面加上=0,則成員函數為純虛函數。一個純虛函數無需定義,但也可以定義,但是必須在類外,也就是說我們不能在類內部為一個帶有=0的函數提供函數體。包含純虛函數的類被稱為抽象類,也叫接口類。抽象類不能實例化出對象。他只是作為基類服務于派生類,如果派生類不對基類的虛函數進行覆蓋,那他仍將是抽象基類。

class Father //抽象類(接口類) { public:virtual void fun() = 0; //定義純虛函數 protected:int _a; };class Child { public:virtual void fun() = 0; //覆蓋,否則Child也是抽象類(接口類) };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

繼承和友元?
友元關系不能繼承,也就是說基類友元不能訪問子類私有和保護成員。

繼承和靜態成員?
基類中定義了靜態成員,則整個繼承體系中只有一個這樣的成員。無論派生出多少的子類,都只有一個靜態成員實例。


創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的C++继承详解三 ----菱形继承、虚继承的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。