最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++中的多態(tài)與多重繼承實(shí)現(xiàn)與Java的區(qū)別

 更新時(shí)間:2020年03月22日 09:50:33   作者:plus2047  
這篇文章主要介紹了C++中的多態(tài)與多重繼承實(shí)現(xiàn)與Java的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

多態(tài)問(wèn)題

筆者校招面試時(shí)被問(wèn)到了著名問(wèn)題「C++ 與 Java 如何實(shí)現(xiàn)多態(tài)」,然后不幸翻車。過(guò)于著名反而沒(méi)有去準(zhǔn)備,只知道跟虛函數(shù)表有關(guān)。面試之后比較了 C++ 和 Java 多態(tài)的實(shí)現(xiàn)的異同,一并記錄在這里。

C++ 多態(tài)的虛指針實(shí)現(xiàn)

首先討論 C++. 多態(tài)也即子類對(duì)父類成員函數(shù)進(jìn)行了重寫(xiě) (Override) 后,將一個(gè)子類指針賦值給父類,再對(duì)這個(gè)父類指針調(diào)用成員函數(shù),會(huì)調(diào)用子類重寫(xiě)版本的成員函數(shù)。簡(jiǎn)單的例子:

class Parent1 {
  public:
  virtual void sayHello() { printf("Hello from parent1!\n"); }
};

class Child : public Parent1 {
  public:
  virtual void sayHello() { printf("Hello from child!\n"); }
};

int main() {
  Parent1 *p = new Child();
  p->sayHello();  // get "Hello from child!"
}

首先需要明白,對(duì)于底層實(shí)現(xiàn)而言,成員函數(shù)就是第一個(gè)參數(shù)為對(duì)象指針的函數(shù),編譯器自動(dòng)將對(duì)象指針添加到函數(shù)參數(shù)中并命名為 this 指針,除此之外與普通函數(shù)并無(wú)本質(zhì)不同。對(duì)于非多態(tài)的成員函數(shù)調(diào)用,與非成員函數(shù)調(diào)用過(guò)程基本是一致的,根據(jù)參數(shù)列表(參數(shù)列表中包含對(duì)象指針類型)和函數(shù)名在編譯時(shí)確定實(shí)際調(diào)用的函數(shù)。

為了實(shí)現(xiàn)多態(tài),不能只根據(jù)對(duì)象指針類型推斷函數(shù)簽名,也即例子中,p->sayHello() 這一行代碼在執(zhí)行時(shí)不能只根據(jù) p 的類型確認(rèn)調(diào)用的函數(shù)應(yīng)該是 Parent::sayHello 還是 Child:sayHello。在多態(tài)機(jī)制下,每個(gè)類父類和子類都需要在其數(shù)據(jù)結(jié)構(gòu)中多攜帶一個(gè)指針,這個(gè)指針指向該類的虛函數(shù)表。

類的虛函數(shù)表也即所有可能發(fā)生重寫(xiě)的函數(shù)指針表,對(duì)象創(chuàng)建時(shí)根據(jù)其實(shí)際類型決定其虛函數(shù)指針指向的虛函數(shù)列表。如在上文的例子中,Parent1 和 Child 類的虛函數(shù)列表都只有一個(gè)函數(shù),分別是 Parent1::sayHello Child::sayHello. 編譯器在編譯時(shí)將會(huì)把函數(shù)調(diào)用翻譯為「引用虛函數(shù)表中的第 N 個(gè)函數(shù)」這樣的指令,比如本例中翻譯為「引用虛函數(shù)表中第一個(gè)函數(shù)」。在運(yùn)行時(shí)讀取虛函數(shù)表中真正的函數(shù)指針。運(yùn)行時(shí) CPU 代價(jià)基本是一次指針解引用和一次下表訪問(wèn)。

Parent1 和 Child 對(duì)象都沒(méi)有自定義的數(shù)據(jù)結(jié)構(gòu)。運(yùn)行以下代碼能夠確認(rèn) Parent1 和 Child 對(duì)象的真實(shí)數(shù)據(jù)結(jié)構(gòu)大小都是 8 字節(jié),也即只有虛函數(shù)列表指針。把 Parent1 和 Child1 對(duì)象作為 64 位整數(shù)輸出,可以看到 p1, p2 的值相同,p3 與前兩者不同。這個(gè)值也即相應(yīng)類的虛函數(shù)表地址。

Parent1* p1 = new Parent1();
Parent1* p2 = new Parent1();
Parent1* p3 = new Child();
printf("sizeof Parent1: %d, sizeof Child: %d\n",
  sizeof(Parent1), sizeof(Child));
printf("val on p1: %lld\n", *(int64_t*)p1);
printf("val on p2: %lld\n", *(int64_t*)p2);
printf("val on p3: %lld\n", *(int64_t*)p3);

C++ 多態(tài)與多重繼承

有一個(gè)非常有意思的問(wèn)題:C++ 發(fā)生多重繼承時(shí),如何支持多態(tài)。剛剛提到,多態(tài)的原理是編譯器將成員函數(shù)調(diào)用編譯為「引用虛函數(shù)表中第 N 個(gè)函數(shù)」,虛函數(shù)表在對(duì)象數(shù)據(jù)結(jié)構(gòu)中的位置和要調(diào)用虛函數(shù)列表中的第幾個(gè)函數(shù)在編譯時(shí)都是需要確定的。多重繼承對(duì)象如果只有一個(gè)虛函數(shù)列表,那不同父類的虛函數(shù)列表中的位置就要發(fā)生沖突。如果有多個(gè)虛函數(shù)列表,編譯時(shí)就難以確定虛函數(shù)列表指針在數(shù)據(jù)結(jié)構(gòu)中的位置。C++ 采取了非常精妙的做法:將所有父類的數(shù)據(jù)結(jié)構(gòu)(包括虛指針列表)在該對(duì)象的數(shù)據(jù)結(jié)構(gòu)上依次排列,該對(duì)象的指針正常指向數(shù)據(jù)結(jié)構(gòu)起始位置。當(dāng)指針發(fā)生類型轉(zhuǎn)換時(shí),C++ 編譯器會(huì)對(duì)指針的值盡可能的進(jìn)行調(diào)整,使其指向該指針類型應(yīng)該對(duì)應(yīng)的位置。指針的值在這個(gè)過(guò)程中發(fā)生了變化。

比如,Child 類繼承了 Parent1, Parent2 兩個(gè)類,則在 Child 指針轉(zhuǎn)換為 Parent1 指針時(shí),不對(duì)指針的值進(jìn)行調(diào)整,因?yàn)?Parent1 是 Child 的第一個(gè)父類。但將 Child 轉(zhuǎn)換為 Parent2 時(shí),需要將指針指增加 Parent1 數(shù)據(jù)結(jié)構(gòu)長(zhǎng)度的值,使指針指向?qū)?yīng) Parent2 數(shù)據(jù)結(jié)構(gòu)開(kāi)始位置。在本例子中,Parent1 數(shù)據(jù)結(jié)構(gòu)只有虛函數(shù)列表指針,在 64 位機(jī)器上長(zhǎng)度為 8. 因此,在 Child 指針轉(zhuǎn)換為 Parent2 指針時(shí),其值增加了 8.

class Parent1 {
  public:
  virtual void sayHello() { printf("Hello from parent1!\n"); }
};

class Parent2 {
  public:
  virtual void sayHi() { printf("Hi from Parent2!\n"); }
};

class Child : public Parent1, public Parent2 {
  public:
  virtual void sayHello() { printf("Hello from child!\n"); }
  virtual void sayHi() { printf("Hi from child!\n"); }
};

int main() {
  Child *p = new Child();
  printf("size of Child: %d", sizeof(Child));
  printf("pointer val as Child*: %lld\n", int64_t(p));
  printf("pointer val as Parent1*: %lld\n", int64_t((Parent1*)p));
  printf("pointer val as Parent2*: %lld\n", int64_t((Parent2*)p));
}

運(yùn)行這段代碼,會(huì)發(fā)現(xiàn) Child 數(shù)據(jù)結(jié)構(gòu)大小增長(zhǎng)到 16,也即兩個(gè)指針。并且指針的值在后兩次類型轉(zhuǎn)換時(shí)是不同的,在 64 位機(jī)器上相差 8 個(gè)字節(jié),也即 Parent1 的數(shù)據(jù)結(jié)構(gòu)大小。另外如果將 p 轉(zhuǎn)換成 Void 指針再轉(zhuǎn)換為 Parent 指針,此時(shí)編譯器就不能正確推斷這個(gè)偏移量,此時(shí)就會(huì)發(fā)生未定義行為。

這個(gè)特性其實(shí)說(shuō)明了一個(gè)非常有意思的事實(shí):C++ 編譯器在編譯時(shí)能夠推斷指針的偏移量,那么編譯器也應(yīng)該可以推斷該指針指向?qū)ο蟮恼鎸?shí)類型。那么,既然可以編譯時(shí)推斷對(duì)象真實(shí)類型,那要虛函數(shù)表又有何用?直接推斷正確的函數(shù)調(diào)用不就可以了嗎?問(wèn)題在于,如果真的在編譯時(shí)推斷多態(tài)函數(shù)調(diào)用,就意味著要為不同類型的對(duì)象生成不一樣的二進(jìn)制代碼。同一行代碼,根據(jù)指針值的不同,產(chǎn)生的函數(shù)調(diào)用不同。這樣一來(lái)也意味第三方庫(kù)需要提供源代碼,來(lái)進(jìn)行相關(guān)的推斷,類似于模板庫(kù)。這都是不可接受的,因此虛函數(shù)列表仍然有必要。借助虛函數(shù)列表,使用指針的代碼能夠生成一致的機(jī)器碼。
從另一個(gè)角度理解,編譯器在編譯一個(gè)完整的 App 時(shí)確實(shí)能夠推斷所有變量的真實(shí)類型,但這需要聯(lián)系過(guò)多上下文。編譯一段代碼卻需要這段代碼輸入?yún)?shù)的除類型之外的上下文信息,并根據(jù)上下文信息生成不同的二進(jìn)制文件,這是不可接受的。

Java 多態(tài)比較

由于 Java 的多態(tài)機(jī)制比 C++ 簡(jiǎn)單,理論上可以使用 C++ 的機(jī)制實(shí)現(xiàn) Java 多態(tài)。但 C++ 跟 Java 有一點(diǎn)決定性的不同:C++ 要求父類成員方法必須有 Virtual 關(guān)鍵字修飾時(shí)才能被重寫(xiě)。這就意味著編譯器在編譯父類時(shí)就能確認(rèn)那些函數(shù)可能被重寫(xiě),于是可以對(duì)不可能重寫(xiě)的函數(shù)直接在編譯時(shí)決定調(diào)用的具體函數(shù),而對(duì)可能重寫(xiě)的函數(shù)使用虛指針表處理。而 Java 的方法默認(rèn)都是可以重寫(xiě)的,因此可以認(rèn)為 Java 方法調(diào)用都需要經(jīng)過(guò)查詢虛函數(shù)列表的過(guò)程,會(huì)比 C++ 不重寫(xiě)函數(shù)多一點(diǎn)開(kāi)銷。

Java 不支持多重繼承,但 Java 支持接口 Interface, 接口跟多重繼承有相似之處,不能簡(jiǎn)單的使用一個(gè)虛函數(shù)表查找。類需要為其實(shí)現(xiàn)的每個(gè) Interface 生成一個(gè)虛函數(shù)列表,跟 C++ 的情況類似。OpenJDK 文檔指出,在類定義中找到 Interface 的虛函數(shù)列表的辦法是很粗暴的:在類實(shí)現(xiàn)的所有 Interface 列表中遍歷查找。文檔中指出,真正的多重繼承是罕見(jiàn)的,通??梢詺w結(jié)為單繼承。對(duì)此遍歷過(guò)程可能有各種優(yōu)化,筆者沒(méi)有深入了解。

思考 Java 和 C++ 的一點(diǎn)不同:C++ 沒(méi)有運(yùn)行時(shí)類型,由編譯器在編譯時(shí)盡力保證指針指向的位置有對(duì)象正確的數(shù)據(jù)結(jié)構(gòu)。將子類指針賦值給父類指針變量時(shí),編譯器盡力對(duì)其進(jìn)行調(diào)整,但如果發(fā)生了 Void 指針賦值等,則編譯器無(wú)法保證指針指向的位置有正確的對(duì)象數(shù)據(jù)結(jié)構(gòu)。這一步只要語(yǔ)法上沒(méi)有錯(cuò)誤,就不會(huì)立即報(bào)錯(cuò),編譯器也無(wú)法確認(rèn)是否會(huì)發(fā)生問(wèn)題,一定要等到該指針實(shí)際進(jìn)行解引用等發(fā)生異常才會(huì)報(bào)錯(cuò)。Java 有運(yùn)行時(shí)類型,在將對(duì)象賦值給不同的類型的變量時(shí),會(huì)在運(yùn)行時(shí)進(jìn)行類型檢查,如果沒(méi)有正確的類型繼承關(guān)系,會(huì)在賦值時(shí)報(bào)錯(cuò)。

另外,對(duì)比 Java 的 Interface 和 C++ 的多重繼承,會(huì)發(fā)現(xiàn) Interface 的運(yùn)行時(shí)時(shí)間開(kāi)銷要比 C++ 多重繼承大得多。但是 C++ 多重繼承需要為每個(gè)父類附加一個(gè)指針,并且編譯器在編譯時(shí)需要完成更多的工作。Java 相對(duì)于 C++ 是更加「強(qiáng)類型」的語(yǔ)言。

到此這篇關(guān)于C++中的多態(tài)與多重繼承實(shí)現(xiàn)與Java的區(qū)別的文章就介紹到這了,更多相關(guān)C++ 多態(tài)與多重繼承內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Sublime Text 3 實(shí)現(xiàn)C++代碼的編譯和運(yùn)行示例

    Sublime Text 3 實(shí)現(xiàn)C++代碼的編譯和運(yùn)行示例

    下面小編就為大家?guī)?lái)一篇Sublime Text 3 實(shí)現(xiàn)C++代碼的編譯和運(yùn)行示例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • C++實(shí)現(xiàn)重載矩陣的部分運(yùn)算符

    C++實(shí)現(xiàn)重載矩陣的部分運(yùn)算符

    這篇文章主要為大家詳細(xì)介紹了如何利用C++實(shí)現(xiàn)重載矩陣的部分運(yùn)算符,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C++有一定幫助,需要的可以參考一下
    2022-10-10
  • C語(yǔ)言實(shí)現(xiàn)順序表的全操作詳解

    C語(yǔ)言實(shí)現(xiàn)順序表的全操作詳解

    順序表,全名順序存儲(chǔ)結(jié)構(gòu),是線性表的一種,線性表用于存儲(chǔ)邏輯關(guān)系為“一對(duì)一”的數(shù)據(jù),順序表自然也不例外,不僅如此,順序表對(duì)數(shù)據(jù)的物理存儲(chǔ)結(jié)構(gòu)也有要求,跟隨下文來(lái)具體了解吧
    2022-04-04
  • C語(yǔ)言以數(shù)據(jù)塊的形式讀寫(xiě)文件實(shí)例代碼

    C語(yǔ)言以數(shù)據(jù)塊的形式讀寫(xiě)文件實(shí)例代碼

    本文主要介紹C語(yǔ)言中以數(shù)據(jù)塊的形式讀寫(xiě)文件,這里提供了實(shí)例代碼舉例說(shuō)明,有需要的小伙伴可以參考下
    2016-07-07
  • VS2022配置編譯使用boost庫(kù)的實(shí)現(xiàn)

    VS2022配置編譯使用boost庫(kù)的實(shí)現(xiàn)

    本文介紹了如何在VS2022中配置和編譯使用Boost庫(kù)的步驟,包括下載Boost、解壓、配置環(huán)境變量和編譯等過(guò)程,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-12-12
  • C++超詳細(xì)講解析構(gòu)函數(shù)

    C++超詳細(xì)講解析構(gòu)函數(shù)

    既然在創(chuàng)建對(duì)象時(shí)有構(gòu)造函數(shù)(給成員初始化),那么在銷毀對(duì)象時(shí)應(yīng)該還有一個(gè)清除成員變量數(shù)據(jù)的操作咯,析構(gòu)函數(shù)與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完成對(duì)象的銷毀,局部對(duì)象銷毀工作是由編譯器完成的。而對(duì)象在銷毀時(shí)會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),完成類的一些資源清理工作
    2022-06-06
  • VScode+cuda編程常見(jiàn)環(huán)境問(wèn)題的解決

    VScode+cuda編程常見(jiàn)環(huán)境問(wèn)題的解決

    本文主要介紹了VScode+cuda編程常見(jiàn)環(huán)境問(wèn)題的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • C++字符數(shù)組的輸入輸出和字符串結(jié)束標(biāo)志使用講解

    C++字符數(shù)組的輸入輸出和字符串結(jié)束標(biāo)志使用講解

    這篇文章主要介紹了C++字符數(shù)組的輸入輸出和符串結(jié)束標(biāo)志使用講解,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-09-09
  • C++實(shí)現(xiàn)LeetCode(7.翻轉(zhuǎn)整數(shù))

    C++實(shí)現(xiàn)LeetCode(7.翻轉(zhuǎn)整數(shù))

    這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(7.翻轉(zhuǎn)整數(shù)),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • C標(biāo)準(zhǔn)庫(kù)<assert.h>的實(shí)現(xiàn)詳解

    C標(biāo)準(zhǔn)庫(kù)<assert.h>的實(shí)現(xiàn)詳解

    這篇文章主要介紹了C標(biāo)準(zhǔn)庫(kù)<assert.h>的實(shí)現(xiàn),主要包括了<assert.h>的基本概念、實(shí)現(xiàn)及用法等,需要的朋友可以參考下
    2014-09-09

最新評(píng)論

高雄市| 巴中市| 泸州市| 辉县市| 邵东县| 江西省| 祁阳县| 南安市| 义马市| 广宗县| 岐山县| 武安市| 巫山县| 沐川县| 巨鹿县| 襄汾县| 灵石县| 怀仁县| 讷河市| 南汇区| 米脂县| 开远市| 金溪县| 澄江县| 那曲县| 宜春市| 大名县| 福清市| 成安县| 和林格尔县| 盘山县| 成都市| 泽普县| 喀喇沁旗| 望都县| 新河县| 桦甸市| 深圳市| 民权县| 玉屏| 安仁县|