天天看點

C/C++面試題的知識點(13)

(1)

一個對象通路普通成員函數和虛函數哪個更快?

答:通路普通成員函數較快。原因是普通成員函數的位址,在編譯階段已經确定,通路時直接尋址;而虛函數在調用時,首先需要找到虛函數表指針,然後在虛函數表中尋找虛函數位址,這個過程花費時間導緻通路比普通函數速度慢一些。

(2)

在什麼情況下,析構函數需要是虛函數?

當我們delete一個動态配置設定的對象的指針時,将執行析構函數。如果該指針指向繼承體系中的某個類型,則有可能出現指針的靜态類型與被删除對象的動态類型不符的情況。例如:

class Base{
    ...
    virtual ~Base() = default;
};
class Derived : public Base{
    ... 
};
Base *p = new Derived;
delete p;
           

這樣的話,編譯器必須清楚應該執行子類的析構函數。通過将父類的析構函數定義為虛函數就可以保證執行正确的析構函數版本。

繼承關系對基類拷貝控制的最直接的影響是基類通常應該定義一個虛析構函數,這樣就可以動态配置設定繼承體系中的對象了。

(3)

内聯函數、構造函數、靜态成員函數可以是虛函數嗎?

虛函數在運作時确定綁定的對象的動态類型。

内聯函數在編譯階段展開,為了減少函數調用時的代價。inline關鍵字作為提示符告訴編譯器此函數作為内聯函數希望在編譯階段展開,但是,編譯器并不一定要展開。是以可以聲明為虛函數。親測可以。

class Base {
public:
    ...
    virtual inline void fun() { cout << "base::hello" << endl; }
};

class Derived :public Base {
public:
    ...
    void fun() { cout << "derived::hello" << endl; }
};

Derived d;
d.fun();
           

構造函數不能是虛函數,因為調用虛函數需要虛函數表指針,執行構造函數之前是沒有虛函數表指針的。

靜态成員函數以類為機關,與具體對象無關,無法有自己的虛函數表指針。虛函數是面向對象的動态綁定的。

(4)

構造函數中可以調用虛函數嗎?

文法上可以,但沒有實際意義。不能起到多态效果,失去了虛函數的意義。

生成一個子類對象,會先調用父類的構造函數,然後調用子類的構造函數。和普通函數沒差別。

(5)

C++中虛繼承的作用及底層原理?
class A {
public:
    int a;
};

class B :public A {

};

class C :public A {

};

class D :public B, public C {

};

D d;
d.a;    //error, 不明确
           

D類對象中有倆a副本,調用時編譯器有些迷糊。“菱形繼承”問題:從不同路徑繼承來的同名的資料成員,在記憶體中有不同的拷貝,造成資料不一緻問題。

解決:虛繼承,将共同基類設定為虛基類。

class A {
public:
    int a;
};

class B :public virtual A {

};

class C :public virtual A {

};

class D :public B, public C {

};

ok!
           

底層實作與編譯器有關。通常可使用虛基類指針實作,各對象中僅儲存一份父類的對象,多繼承時通過虛基類指針引用該公共對象,避免菱形繼承問題中的二義性問題。

虛繼承:在繼承定義中包含了virtual關鍵字的繼承關系;

虛基類:在虛繼承體系中通過virtual繼承而來的基類,我們說A稱之為B和C的虛基類,不說A是個虛基類。

虛拟繼承很少用到,主要是C++中多重繼承不被推薦,也并不常用。一旦離開多重繼承,虛拟繼承失去存在必要。