C++ 多態詳解及常見面試題


       什么是多態?

       多態就是不同對象對同一行為會有不同的狀態(舉例 : 學生和成人都去買票時,學生會打折,成人不會)

       實現多態有兩個條件: 一是虛函數重寫,重寫就是用來設置不同狀態的

                  二是對象調用虛函數時必須是指針或者引用

       ps:沒有這兩個條件無法構成多態,很多筆試題都會利用這個陷阱讓你上當!

       實際上,代碼上體現(動態)多態就是當父類指針指向子類對象,然后通過父類指針能調用子類的成員函數。

              

          什么是虛函數?什么是重寫?

       虛函數是帶有virtual關鍵字的成員函數 

       子類有個和父類完全相同(函數名,形參,返回值都相同,協變和析構函數除外)虛函數,就稱子類虛函數重寫父類虛函數 

        

       多態的原理?

       多態是用虛函數表實現的。

       有虛函數的類都會生成一個虛函數表,這個表在編譯時生成。

       虛函數表是一個存儲虛函數地址的數組,以NULL結尾。

       如果要生成子類虛表,就要經過三個步驟:第一步,將父類虛表內容拷貝到子類虛表上;

                          第二步,將子類重寫的虛函數覆蓋掉表中父類的虛函數;

                          第三步,如果子類有新增加的虛函數,按聲明次序加到最后

 

         多態如何調用?

       滿足多態的函數調用,程序運行起來后,根據對象中的虛表指針來找實際應該調用的函數; 而不滿足多態的函數在函數編譯時就確定函數地址了。

                   

 

         動態綁定與靜態綁定?

         靜態綁定是程序編譯時確定程序行為。

         動態綁定是程序運行時根據具體的對象確定程序行為。

  

        繼承中的多態:

        單繼承無虛函數覆蓋: 虛函數按聲明順序存放,父類虛函數在子類虛函數前面.

                  

       單繼承有虛函數覆蓋: 覆蓋的f()替代原有父類虛函數位置,沒覆蓋的不變

      

       多繼承無虛函數覆蓋: 每個父類都有自己的虛表,子類成員函數被放入第一個父類表中

         

       多繼承有虛函數覆蓋: 三個父類虛表中的f()都會被子類函數指針覆蓋

         

           多繼承規則: 多繼承子類未重寫的虛函數放在第一個繼承父類部分的虛函數表中,繼承的虛表都會覆蓋

      

      重復繼承: B類數據重復,具有二義性.

            

 

              

      二義性舉例: d.ib = 0;  x     d.B1::ib = 0; √

 

      菱形虛擬繼承: 解決重復繼承的數據重復,二義性問題.

          虛繼承: 繼承語法中加入virtual關鍵字.

               虛繼承子類: 加入新的虛函數,會生成一個虛函數指針(vptr)以及一張虛表,放在對象內存最前面.

               普通繼承子類: 加入新的虛函數,直接擴展父類虛表.

                  虛繼承子類會單獨保留父類的vptr和虛表,用一個四字節0分界.

               虛繼承子類有一個四字節指針偏移值.

          虛基類表:  虛繼承會生成一個隱藏的虛基類指針(vbptr),虛基類指針總是在虛表指針之后.

               vbptr指向虛基類表,虛基類表記錄了vbptr到vptr的偏移值.

               虛基類表實際上就是記錄了虛表指針的位置.

                              

        單虛繼承下,vbptr記錄了兩個vptr所在位置的偏移值.(這里[4]的地址為007C FE00,打印粗心)

                          

        菱形虛擬繼承舉例:

                     

 

       內存結構:

                         

                       

       菱形虛擬繼承內存分布總結:

      (1)基類出現順序: B1(最左父類),B2(次左父類),B(虛祖父類)

      (2)D類數據成員在B類前,並以0x00分割

      (3)D類覆蓋擴展原則與前面多繼承規則一樣

      (4)B類內容放到了最后

 

      如果出現菱形繼承,B1有一個構造函數初始化B,B2也有一個構造函數初始化B,那么D類應該按誰的構造方式初始化B呢?   

                  

 

       答案是必須讓D來初始化虛基類B.

       C++規定必須由最終的派生類D來初始化虛基類B,直接派生類 B1 和 B2 對 B 的構造函數的調用是無效的,並且虛基類始終優先調用,與聲明位置無關.

 

       

 

       虛函數和虛表在哪里?

       虛函數和普通函數一樣在代碼段,vs2013測試下,虛表在只讀常量區

                       

                        

 

       抽象類?

       有純虛函數的類。

       純虛函數就是虛函數后面再加上 = 0;

       體現了接口繼承,只聲明不實現 --- 比如,動物呼吸不能實現,但繼承它的魚和人都能呼吸並且呈現多態性

       final ---  讓父類虛函數不能被重寫  ---  體現實現繼承

               

         override ---  純虛函數 + override --- 強制重寫

           

      面試題:

       1.inline函數可以是虛函數嗎?

       不能,因為inline函數沒有地址,無法放到虛函數表中

       2.靜態成員可以是虛函數嗎?

         不能, 因為靜態成員函數沒有this指針, 因為有this指針才能訪問到虛表指針,有虛表指針才能找到虛表從而調用實際應該調用的函數。

       3.構造函數可以是虛函數嗎?/虛函數指針在什么時候生成的的?

          不能,因為對象中的虛表指針是在構造函數初始化列表階段才初始化的

       4.析構函數可以是虛函數嗎?什么場景下析構函數是虛函數?

         可以,並且最好把基類的析構函數定義成虛函數

         當父類指針指向子類對象時,如果析構函數不是虛函數,析構就只會釋放父類對象,造成內存泄漏。(因為析構重名,只能調用一個,調用默認的父類析構函數)

         定義成虛函數后,調用析構時就會取出虛表指針找到實際應該調用的函數(指針雖然都是父類類型,但是指針內取出的虛表是不一樣的,所以析構能調用子類析構)

       5.對象訪問普通函數快還是虛函數更快?

       首先如果是普通對象,是一樣快的,如果是指針對象或者是引用對象,則調用的普通函數快,因為普通對象在編譯時就確定地址了,虛函數構成多態,運行時調用虛函數需要到虛函數表中去查找 

        6.下面輸出是?

class B{};
class B1 :public virtual  B{};
class B2 :public virtual  B{};
class D : public B1, public B2{};

int main()
{
    B b;
    B1 b1;
    B2 b2;
    D d;
    cout << "sizeof(b)=" << sizeof(b)<<endl;           //1,空類用了一個占位符
    cout << "sizeof(b1)=" << sizeof(b1) << endl;       //4,有虛基類表指針(32位系統)
    cout << "sizeof(b2)=" << sizeof(b2) << endl;       //4,同上
    cout << "sizeof(d)=" << sizeof(d) << endl;         //8,有b1和b2兩個虛基類表指針
    return 0;
}

        7.設計一個不能被繼承的類

//解法:私有一個輔助類,子類虛繼承輔助類,並是他的友元.
//       友元能夠調用輔助類的私有函數
//       因為有虛繼承,所以Try類必須自己構造虛基類,但是虛基類私有了構造和析構,所以調用不了
class MakeSealed{
    friend SealedClass;
 private:
     MakeSealed();
     ~MakeSealed();
};

class SealedClass : virtual public MakeSealed;
{
 public:
     SealedClass();
     ~SealedClass();
};

class Try : public MakeSealed;
{             
};

  

     c++的多態總結:

     在父類的函數前加上virtual關鍵字,在子類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數,如果對象類型是子類,就調用子類的函數,如果對象類型是父類,就調用父類的函數。

    •   虛函數機制(virtual function) , 用以支持執行期綁定,實現多態。
    •   虛基類 (virtual base class) ,虛繼承關系產生虛基類,用於在多重繼承下保證基類在子類中擁有唯一實例。

       

         

       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM