天天看點

c++面試常用知識(sizeof計算類的大小,虛拟繼承,重載,隐藏,覆寫)

一. sizeof計算結構體

  注:本機機器字長為64位

1.最普通的類和普通的繼承

#include<iostream> 
using namespace std;

class Parent{
public:
    void fun(){
        cout<<"Parent fun"<<endl;
    }
}; 

class Child : public Parent{
public:
    void fun(){
        cout<<"Child fun"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}

/*
  運作結果:
   Parent size : 1, Child size : 5      
*/      

  分析:那麼為什麼類(對象)的大小為什麼會是1個位元組呢?那是被編譯器插進去的一個char ,使得這個class的不同實體(object)在記憶體中配置獨一無二的位址。也就是說這個char是用來辨別類的不同對象的。因為如果不是1,當定義這個類的對象數組時候A objects[5]; objects[0]和objects[1]就在同一個位址處,就無法區分。

2.基類中含有私有成員

#include<iostream> 
using namespace std;

class Parent{
public:
    void fun(){
        cout<<"Parent fun"<<endl;
    }
private:
    int x;    
}; 

class Child : public Parent{
public:
    void fun(){
        cout<<"Child fun"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}
/*
  執行結果:
  Parent size : 4, Child size : 12
*/      

  分析:基類裡的私有成員在派生類裡仍占有記憶體。在派生類裡,基類的int占4個位元組,char ch[5]占用5個位元組,考慮記憶體的對齊,變成4+(5+3)=12個位元組。

3.類中含有虛函數

#include<iostream> 
using namespace std;

class Parent{
public:
    virtual void fun(){
        cout<<"Parent fun"<<endl;
    }
}; 

class Child : public Parent{
public:virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}
/*
  執行結果:
  Parent size : 8, Child size : 16
*/      

  分析:有虛函數的類有個virtual table(虛函數表),裡面包含了類的所有虛函數,類中有個virtual table pointers,通常成為vptr指向這個virtual table,占用8個位元組的大小。成員類Child public繼承于Parent,類Child的虛函數表裡實際上有兩個虛函數Parent::fun()和Child::hjzgg(),類B的大小等于char ch[5]的大小加上一個指向虛函數表指針vptr的大小,考慮記憶體對齊為16。一個類裡若有虛函數,無論有多少個虛函數都隻有一個指向虛表的指針,虛表中的每一個表項儲存着一個虛函數的入口位址。當調用虛函數時,先找到虛表中它對應的表項,找到入口位址再執行。

4.多重繼承

#include<iostream> 
using namespace std;

class Parent{
public:
    virtual void fun(){
        cout<<"Parent fun"<<endl;
    }
}; 

class Father{
public:
    virtual void fun(){
        cout<<"Father fun"<<endl;
    }
};

class Child : public Parent, public Father{
public:
    virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}
/*
  執行結果:
  Parent size : 8, Child size : 24
*/      

  分析:Child中除了char ch[5]這5個位元組,Child現有一個虛函數表,裡邊有Child自身定義的虛函數以及從Parent中繼承過來的虛函數,然後又另一張虛函數表來存放Father中過來的虛函數,也就是Child對應兩個虛函數表的指針。總共記憶體空間5+8+8=21,考慮記憶體的對齊,為24位元組。

 5.虛繼承

  C++虛拟繼承

  ◇概念:

     C++使用虛拟繼承(Virtual Inheritance),解決從不同途徑繼承來的同名的資料成員在記憶體中有不同的拷貝造成資料不一緻問題,将共同基類設定為虛基類。          這時從不同的路徑繼承過來的同名資料成員在記憶體中就隻有一個拷貝,同一個函數名也隻有一個映射。

  ◇解決問題:

解決了二義性問題,也節省了記憶體,避免了資料不一緻的問題。      
◇同義詞:      
虛基類(把一個動詞當成一個名詞而已)      
當在多條繼承路徑上有一個公共的基類,在這些路徑中的某幾條彙合處,這個公共的基類就會産生多個執行個體(或多個副本),若隻想儲存這個基類的一個執行個體,可以将這個   公共基類說明為虛基類。      

  ◇執行順序

     首先執行虛基類的構造函數,多個虛基類的構造函數按照被繼承的順序構造;

   執行基類的構造函數,多個基類的構造函數按照被繼承的順序構造;

   執行成員對象的構造函數,多個成員對象的構造函數按照申明的順序構造;

   執行派生類自己的構造函數;

     析構以與構造相反的順序執行;

  注:

    從虛基類直接或間接派生的派生類中的構造函數的成員初始化清單中都要列出對虛基類構造函數的調用。但隻有用于建立對象的最派生類的構造函數調用虛基類           的構造函數,而該派生類的所有基類中列出的對虛基類的構造函數的調用在執行中被忽略,進而保證對虛基類子對象隻初始化一次。

    在一個成員初始化清單中同時出現對虛基類和非虛基類構造函數的調用時,虛基類的構造函數先于非虛基類的構造函數執行。

 5.1 沒有用虛拟繼承

#include<iostream> 
using namespace std;

class Parent{
public:
    virtual void fun(){
        cout<<"Parent fun"<<endl;
    }
}; 

class Father{
public:
    virtual void fun(){
        cout<<"Father fun"<<endl;
    }
};

class Child : public Parent, public Father{
public:
    virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    ch.fun();      

    ch.Parent::fun();//這樣調用是對的

    ch.Father::fun();

cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}
/*
  執行結果:
   [Error] request for member 'fun' is ambiguous    
*/      

    分析:因為派生類中的虛函數表會繼承來自各個基類的虛函數。是以Child對應的虛函數表中會有Parent 和 Father各自的fun()函數,是以在調用的時候就會出現歧義,不知道應該調用哪個!

  同樣,和下面一樣的寫法也是錯誤的,增加一個Super類。

#include<iostream> 
using namespace std;

class Super{
public:
    virtual void fun(){
        cout<<"Super fun"<<endl;
    }
};

class Parent :  public Super{
public:
     
}; 

class Father : public Super{
public:
      
};

class Child : public Parent, public Father{
public:
    virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    ch.fun();
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}      
/*
  執行結果:
   [Error] request for member 'fun' is ambiguous    
*/      

 5.2 使用虛拟繼承#include<iostream> 

using namespace std;

class Super{
public:
    Super(){
        cout<<"Super construction"<<endl;
    }
    virtual void fun(){
        cout<<"Super fun"<<endl;
    }
};

class Parent : virtual public Super{
public:
     Parent(){
         cout<<"Parent construction"<<endl;
     }
}; 

class Father : virtual public Super{
public:
      Father(){
          cout<<"Father construction"<<endl;
      }
};

class Child : public Parent, public Father{
public:
    virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Child ch;
    ch.fun();
    cout<<"Parent size : "<<sizeof(Parent)<<", Child size : "<<sizeof(Child) <<endl;
    return 0;
}
/*
  執行結果:      

   Super construction

   Parent construction

   Father construction

   Super fun

   Parent size : 8, Child size : 24

*/      

  分析:

1.在多繼承情況下,虛基類關鍵字的作用範圍和繼承方式關鍵字相同,隻對緊跟其後的基類起作用。      
2.聲明了虛基類之後,虛基類在進一步派生過程中始終和派生類一起,維護同一個基類子對象的拷貝。(Super的構造函數隻執行了一次,如果不是有虛基類,那麼Super的構造函數     将會執行兩次。)      
3.觀察類構造函數的構造順序,拷貝也隻有一份。      

二. c++重載、覆寫、隐藏的差別和執行方式

  1.成員函數被重載的特征

  (1)相同的範圍(在同一個類中); 

  (2)函數名字相同; 

  (3)參數不同; 

  (4)virtual 關鍵字可有可無。 

  2.“覆寫”是指派生類函數覆寫基類函數,特征是:

  (1)不同的範圍(分别位于派生類與基類); 

  (3)參數相同; 

  (4)基類函數必須有virtual 關鍵字。 

  3.“隐藏”是指派生類的函數屏蔽了與其同名的基類函數,特征是:

  (1)不同的範圍(分别位于派生類與基類);

  (2)如果派生類的函數與基類的函數同名,但是參數不同,此時,不論有無virtual關鍵字,基類的函數将被隐藏(注意别與重載混淆)。 

  (3)如果派生類的函數與基類的函數同名,且參數相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隐藏(注意别與覆寫混淆)。

  小結:說白了就是如果派生類和基類的函數名和參數都相同,屬于覆寫,這是可以了解的吧,完全一樣當然要覆寫了;如果隻是函數名相同,參數并不相同,則屬 于隐藏。

作者:胡峻峥

繼續閱讀