欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++的多態(tài)和虛函數(shù)你真的了解嗎

 更新時間:2022年02月14日 15:49:25   作者:山頂夕景  
這篇文章主要為大家詳細介紹了C++的多態(tài)和虛函數(shù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

一、C++的面試??键c

阿里雖然是國內(nèi)Java的第一大廠但是并非所有的業(yè)務都是由Java支撐,很多服務和中下層的存儲,計算,網(wǎng)絡(luò)服務,大規(guī)模的分布式任務都是由C++編寫。在阿里所有部門當中對C++考察最深的可能就是阿里云。

阿里對C++的常考點:

1.STL 容器相關(guān)實現(xiàn)

2.C++新特性的了解

3.多態(tài)和虛函數(shù)的實現(xiàn)

4.指針的使用

二、阿里真題

2.1 真題一

現(xiàn)在假設(shè)有一個編譯好的C++程序,編譯沒有錯誤,但是運行時報錯,報錯如下:你正在調(diào)用一個純虛函數(shù)(Pure virtual function call error),請問導致這個錯誤的原因可能是什么?

純虛函數(shù)調(diào)用錯誤一般由以下幾種原因?qū)е拢?/strong>

  • 從基類構(gòu)造函數(shù)直接調(diào)用虛函數(shù)。(直接調(diào)用是指函數(shù)內(nèi)部直接調(diào)用虛函數(shù))
  • 從基類析構(gòu)函數(shù)直接調(diào)用虛函數(shù)。
  • 從基類構(gòu)造函數(shù)間接調(diào)用虛函數(shù)。(間接調(diào)用是指函數(shù)內(nèi)部調(diào)用其他的非虛函數(shù),其內(nèi)部直接或間接地調(diào)用了虛函數(shù))
  • 從基類析構(gòu)函數(shù)間接調(diào)用虛函數(shù)。
  • 通過懸空指針調(diào)用虛函數(shù)。

注意:其中1,2編譯器會檢測到此類錯誤。3,4,5編譯器無法檢測出此類情況,會在運行時報錯。

(1)虛函數(shù)表vtbl

編譯器在編譯時期為每個帶虛函數(shù)的類創(chuàng)建一份虛函數(shù)表

實例化對象時, 編譯器自動將類對象的虛表指針指向這個虛函數(shù)表

(2)構(gòu)造一個派生類對象的過程

1.構(gòu)造基類部分:

  • 構(gòu)造虛表指針,將實例的虛表指針指向基類的vtbl
  • 構(gòu)造基類的成員變量
  • 執(zhí)行基類的構(gòu)造函數(shù)函數(shù)體

2.遞歸構(gòu)造派生類部分:

  • 將實例的虛表指針指向派生類vtbl
  • 構(gòu)造派生類的成員變量
  • 執(zhí)行派生類的構(gòu)造函數(shù)體

(3)析構(gòu)一個派生類對象的過程

1.遞歸析構(gòu)派生類部分:

  • 將實例的虛表指針指向派生類vtbl
  • 執(zhí)行派生類的析構(gòu)函數(shù)體
  • 析構(gòu)派生類的成員變量(這里的執(zhí)行函數(shù)體,析構(gòu)派生類成員變量,兩者的順序和構(gòu)造的步驟是相反的)

2.析構(gòu)基類部分:

  • 將實例的虛表指針指向基類的vtbl
  • 執(zhí)行基類的析構(gòu)函數(shù)函數(shù)體
  • 析構(gòu)基類的成員變量

構(gòu)造函數(shù)和析構(gòu)函數(shù)執(zhí)行函數(shù)體時,實例的虛函數(shù)表指針,指向構(gòu)造函數(shù)和析構(gòu)函數(shù)本身所屬的類的虛函數(shù)表,此時執(zhí)行的虛函數(shù),即調(diào)用的本身的該類本身的虛函數(shù),下面是一個【間接調(diào)用】的栗子:基類中的析構(gòu)函數(shù)中,調(diào)用純虛函數(shù)(該虛函數(shù)就在基類中定義)。

#include <iostream>
using namespace std;

class Parent {
public:
	//純虛函數(shù)
    virtual void virtualFunc() = 0;
    void helper() {
        virtualFunc();
    }
    virtual ~Parent(){
        helper();
    }
};

class Child : public Parent{
    public:
    void virtualFunc() {
        cout << "Child" << endl;
    }
    virtual ~Child(){}
};


int main() {
    Child child;
	//system("pause");
    return 0;
}

運行時報錯libc++abi.dylib: Pure virtual function called

在這里插入圖片描述

2.2 真題二

在構(gòu)造實例過程當中一部分是初始化列表一部分是在函數(shù)體內(nèi),你能說一下這些的順序是什么?差別是什么和this指針構(gòu)造的順序

順序:

(1)初始化列表中的先初始化。

(2)執(zhí)行函數(shù)體代碼。

  • 執(zhí)行類中函數(shù)體,如執(zhí)行構(gòu)造函數(shù)時,所有成員已經(jīng)初始化完畢了;
  • this指針屬于對象,而對象還沒構(gòu)造完成前,若使用this指針,編譯器會無法識別。在初始化列表中顯然不能使用this指針,注意:在構(gòu)造函數(shù)體內(nèi)部可以使用this指針。

構(gòu)造函數(shù)的執(zhí)行可以分成兩個階段:

  • 初始化階段:所有類類型的成員都會在初始化階段初始化,即使該成員沒有出現(xiàn)在構(gòu)造函數(shù)的初始化列表中。
  • 計算賦值階段:一般用于執(zhí)行構(gòu)造函數(shù)體內(nèi)的賦值操作。
#include <iostream>
using namespace std;

class Test1 {
public:
    Test1(){
    	cout << "Construct Test1" << endl;
    }
	//拷貝構(gòu)造函數(shù)
    Test1& operator = (const Test1& t1) {
    	cout << "Assignment for Test1" << endl;
        this->a = t1.a;
        return *this;
    }
    int a ;
};

class Test2 {
public:
    Test1 test1;
	//Test2的構(gòu)造函數(shù)
    Test2(Test1 &t1) {
    	cout << "構(gòu)造函數(shù)體開始" << endl;
        test1 = t1 ;
        cout << "構(gòu)造函數(shù)體結(jié)束" << endl;
    }
};

int main() {
    Test1 t1;
    Test2 test(t1);
	system("pause");
    return 0;
}

在這里插入圖片描述

分析上面的結(jié)果:

(1)第一行結(jié)果即Test t1實例化對象時,執(zhí)行Test1的構(gòu)造函數(shù);

(2)第二行代碼,實例化Test2對象時,在執(zhí)行Test2構(gòu)造函數(shù)時,正如上面所說的,構(gòu)造函數(shù)的第一步是初始化階段:所有類類型的成員都會在初始化階段初始化,即使該成員沒有出現(xiàn)在構(gòu)造函數(shù)的初始化列表中。所以Test2在構(gòu)造函數(shù)體執(zhí)行之前已經(jīng)使用了Test1的默認構(gòu)造函數(shù)初始化好了t1。打印出Construct Test1。

這里的拷貝構(gòu)造函數(shù)中可以使用this指針,指向當前對象。

(3)第三四五行結(jié)果:執(zhí)行Test2的構(gòu)造函數(shù)。

2.3 真題三

初始化列表的寫法和順序有沒有什么關(guān)系?

構(gòu)造函數(shù)的初始化列表中的前后位置,不影響實際標量的初始化順序。成員初始化的順序和它們在類中的定義順序一致。

必須使用初始化列表的情況:數(shù)據(jù)成員是const、引用,或者屬于某種未提供默認構(gòu)造函數(shù)的類類型。

2.4 真題四

在普通的函數(shù)當中調(diào)用虛函數(shù)和在構(gòu)造函數(shù)當中調(diào)用虛函數(shù)有什么區(qū)別?

普調(diào)函數(shù)當中調(diào)用虛函數(shù)是希望運行時多態(tài)。而在構(gòu)造函數(shù)當中不應該去調(diào)用虛函數(shù)因為構(gòu)造函數(shù)當中調(diào)用的就是本類型當中的虛函數(shù),無法達到運行時多態(tài)的作用。

2.5 真題五

成員變量,虛函數(shù)表指針的位置是怎么排布?

如果一個類帶有虛函數(shù),那么該類實例對象的內(nèi)存布局如下:

  • 首先是一個虛函數(shù)指針,
  • 接下來是該類的成員變量,按照成員在類當中聲明的順序排布,整體對象的大小由于內(nèi)存對齊會有空白補齊。
  • 其次如果基類沒有虛函數(shù)但是子類含有虛函數(shù):
    • 此時內(nèi)存子類對象的內(nèi)存排布也是先虛函數(shù)表指針再各個成員。

如果將子類指針轉(zhuǎn)換成基類指針此時編譯器會根據(jù)偏移做轉(zhuǎn)換。在visual studio,x64環(huán)境下測試,下面的Parent p = Child();是父類對象,由子類來實例化對象。

#include <iostream>
using namespace std;

class Parent{
public:
    int a;
    int b;
};

class Child:public Parent{
public:
    virtual void test(){}
    int c;
};

int main() {
    Child c = Child();
    Parent p = Child();
    cout << sizeof(c) << endl;//24
    cout << sizeof(p) << endl;//8

    Child* cc = new Child();
    Parent* pp = cc;
    cout << cc << endl;//0x7fbe98402a50
    cout << pp << endl;//0x7fbe98402a58
	cout << endl << "子類對象abc成員地址:" << endl;
    cout << &(cc->a) << endl;//0x7fbe98402a58
    cout << &(cc->b) << endl;//0x7fbe98402a5c
    cout << &(cc->c) << endl;//0x7fbe98402a60
	system("pause");
    return 0;
}

結(jié)果如下:

24
8
0000013AC9BA4A40
0000013AC9BA4A48

子類對象abc成員地址:
0000013AC9BA4A48
0000013AC9BA4A4C
0000013AC9BA4A50
請按任意鍵繼續(xù). . .

分析上面的結(jié)果:

(1)第一行24為子類對象的大小,首先是虛函數(shù)表指針8B,然后是2個繼承父類的int型數(shù)值,還有1個是該子類本身的int型數(shù)值,最后的4是填充的。

(2)第二行的8為父類對象的大小,該父類對象由子類初始化,含有2個int型成員變量。

(3)子類指針cc指向又new出來的子類對象(第三個),然后父類指針pp指向這個子類對象,這兩個指針的值:

  • 父類指針pp值:0000013AC9BA4A48
  • 子類指針cc值:0000013AC9BA4A40

即發(fā)現(xiàn)如之前所說的:如果將子類指針轉(zhuǎn)換成基類指針此時編譯器會根據(jù)偏移做轉(zhuǎn)換。我測試環(huán)境是64位,所以指針為8個字節(jié)。轉(zhuǎn)換之后pp和cc相差一個虛表指針的偏移。

(4)&(cc->a)的值即 0000013AC9BA4A48,和pp值是一樣的,注意前面的 0000013AC9BA4A40到0000013AC9BA4A47其實就是子類對象的虛函數(shù)表指針了。

三、小結(jié)

阿里??嫉腃++的問題集中在以下幾點:

  • 虛函數(shù)的實現(xiàn)
  • 虛函數(shù)使用出現(xiàn)的問題原因
  • 帶有虛函數(shù)的類對象的構(gòu)造和析構(gòu)過程
  • 對象的內(nèi)存布局
  • 虛函數(shù)的缺點:相比普通函數(shù),虛函數(shù)調(diào)用需要2次跳轉(zhuǎn)(即需要先找到對象的虛函數(shù)表,再查找該表項,即虛函數(shù)指針,即真正的虛函數(shù)地址),會降低CPU緩存的命中率。運行時綁定,編譯器不好優(yōu)化。

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!    

相關(guān)文章

最新評論