[C++面試題]之繼承與接口

[C++面試題]之繼承與接口

     整個C++程序設計全面圍繞面向對象的方式進行。類的繼承特性是C++的一個非常重要的機制。繼承特性可以使一個新類獲得其父類的操作和數據結構,程序員只需在新類中增加原有類沒有的成分。

     在面試過程中,各大企業會考量你對虛函數、純虛函數、私有繼承、多重繼承等知識點的掌握程度,因此就有了我們這一節的內容,開始吧。

1、以下代碼的輸出結果是什麼?

 
 
 
 
 
 
#include<iostream>
usingnamespacestd;
classA
{
protected:
    intm_data;
public:
    A(intdata = 0)
    {
        m_data = data;
    }
    intGetData()
    {
        returndoGetData();
    }
    virtualintdoGetData()
    {
        returnm_data;
    }
};
classB :public A
{
protected:
    intm_data;
public:
    B(intdata = 1)
    {
        m_data = data;
    }
    intdoGetData()
    {
        returnm_data;
    }
};
classC :public B
{
protected:
    intm_data;
public:
    C(intdata = 2)
    {
        m_data = data;
    }
};
int main ()
{
    C c(10);
    cout << c.GetData() <<endl;
    cout << c.A::GetData() <<endl;
    cout << c.B::GetData() <<endl;
    cout << c.C::GetData() <<endl;
    cout << c.doGetData() <<endl;
    cout << c.A::doGetData() <<endl;
    cout << c.B::doGetData() <<endl;
    cout << c.C::doGetData() <<endl;
    return0;
}

     解析:構造函數從最初始的基類開始構造的,各個類的同名變量沒有形成覆蓋,都是單獨的變量.理解這兩個重要的C++特性後解決這個問題就比較輕鬆了,下面我們看看:

     cout << c.GetData() <<endl;

     本來是要調用C類的GetData(),C中未定義,故調用B中的,但是B中也未定義,故調用A中的GetData(),因爲A中的doGetData()是虛函數,所以調用B類中的doGetData(),而B類的doGetData()返回B::m_data,故輸出 1。

     cout << c.A::GetData() <<endl;

     因爲A中的doGetData()是虛函數,所以調用B類中的doGetData(),而B類的doGetData()返回B::m_data,故輸出 1。

     cout << c.B::GetData() <<endl;

     肯定是B類的返回值 1 了。

     cout << c.C::GetData() <<endl;

     跟cout << c.GetData() <<endl;語句是一樣的。

     cout << c.doGetData() <<endl;

     B類的返回值 1 了。

     cout << c.A::doGetData() <<endl;

     因爲直接調用了A的doGetData() ,所以輸出0。

     cout << c.B::doGetData() <<endl;  
     cout << c.C::doGetData() <<endl;

     這兩個都是調用了B的doGetData(),所以輸出 1。

     這裏要注意存在一個就近調用,如果父類存在相關接口則優先調用父類接口,如果父類也不存在相關接口則調用祖父輩接口。

答案:

1 1 1 1 1 0 1 1

2、以下代碼輸出結果是什麼?

 
#include<iostream>
usingnamespacestd;
classA
{
public:
    voidvirtualf()
    {
        cout<<"A"<<endl;
    }
};
classB :public A
{
public:
    voidvirtualf()
    {
        cout<<"B"<<endl;
    }
};
int main ()
{
    A* pa=newA();
    pa->f();
    B* pb=(B*)pa;
    pb->f();
    deletepa,pb;
    pa=newB();
    pa->f();
    pb=(B*)pa;
    pb->f();
    return0;
}

        解析:這是一個虛函數覆蓋虛函數的問題。A類裏的f()函數是一個虛函數,虛函數是被子類同名函數所覆蓋的。而B類裏的f()函數也是一個虛函數,它覆蓋A類f()函數的同時,也會被它的子類覆蓋。但是在 B* pb=(B*)pa;裏面,該語句的意思是轉化pa爲B類型並新建一個指針pb,將pa複製到pb。但是這裏有一點請注意,就是pa的指針始終沒有發生變化,所以pb也指向pa的f()函數。這裏並不存在覆蓋的問題。

      delete pa,pb;刪除了pa和pb所指向的地址,但是pa、pb指針並沒有刪除,也就是我們通常說的懸浮指針,現在重新給pa指向新地址,所指向的位置是B類的,而之前pa指針類型是A類的,所以就產生了一個覆蓋。pa->f();的值是B。

      pb=(B*)pa;轉化pa爲B類指針給pb賦值,但pa所指向的f()函數是B類的f() 函數,所以pb所指向的f()函數是B類的f()函數。pb->f();的值是B。

       答案:

A A B B

3、派生類的3種繼承方式?

       答案:

    (1)公有繼承方式:

     基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見。這裏保護成員與私有成員相同。

     基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員可見,基類的公有成員和保護成員作爲派生類的成員時,它們都保持原有的狀態;基類的私有成員不可見,基類的私有成員仍然是私有的,派生類不可訪問基類中的私有成員。

     基類成員對派生類對象的可見性對派生類對象來說,基類的公有成員是可見的,其他成員是不可見的。

    (2)私有繼承方式:

     基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見。

     基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員可見,基類的公有成員和保護成員都作爲派生類的私有成員,並且不能被這個派生類的子類所訪問;基類的私有成員不可見,派生類不可訪問基類中的私有成員。

     基類成員對派生類對象的可見性對派生類對象來說,基類的所以成員都是不可見的。

     所以說,在私有繼承時,基類的成員只能由直接派生類訪問,而無法再往下繼承。

    (3)保護繼承方式:

     這種繼承方式與私有繼承方式的情況相同,兩者的區別僅在於對派生類的成員而言,基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見。

     基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員可見,基類的公有成員和保護成員都作爲派生類的保護成員,並且不能被這個派生類的子類所訪問;基類的私有成員不可見,派生類不可訪問基類中的私有成員。

     基類成員對派生類對象的可見性對派生類對象來說,基類的所以成員都是不可見的。

     所以說,在私有繼承時,基類的成員只能由直接派生類訪問,而無法再往下繼承。

4、下面程序運行結果是什麼?

 
#include<iostream>
usingnamespacestd;
classA
{
    chark[3];
public:
    virtualvoidaa(){};
};
classB :public virtual A
{
    charj[3];
public:
    virtualvoidbb(){};
};
classC :public virtual B
{
    chari[3];
public:
    virtualvoidcc(){};
};
int main ()
{
    cout <<"sizeof(A):"<< sizeof(A) << endl;
    cout <<"sizeof(B):"<< sizeof(B) << endl;
    cout <<"sizeof(C):"<< sizeof(C) << endl;
    return0;
}

      解析:(1)對於A類,由於有一個虛函數,那麼必須有一個對應的虛函數表來記錄對應的函數入口地址。每個地址需標有一個虛指針,指針的大小爲4。類中還有一個char k[3],每一個char值所佔空間是1,所以char k[3]所佔大小是3。做一個數據對齊後變爲4。所以,sizeof(A)的結果就是char k[3]所佔大小4和虛指針所佔大小4之和等於8。

    (2)對於B類,由於B繼承了A,同時還擁有自己的虛函數,那麼B中首先擁有一個vfptr_B,指向自己的虛函數表。還有char j[3],大小爲4,可虛繼承該如何實現呢?首先要通過加入一個虛類指針(記vbptr_B_A)來指向其父類,然後還要包含父類的所有內容,所以sizeof(B)的大小是:A類所佔大小8,char j[3]所佔大小4,vfptr_B所佔大小4,vbptr_B_A所佔大小4,它們之和等於20。

    (3)對於C類和B類差不多,結果是32。

     答案:

sizeof(A):8

sizeof(B):20

sizeof(C):32

5、什麼是虛繼承?它與一般的繼承有什麼不同?它有什麼用?寫出一段虛繼承的C++代碼。

    答案:

虛擬繼承是多重繼承中特有的概念。虛擬基類是爲了解決多重繼承而出現的,請看下圖:

QQ截圖20111106094148

在圖 1中,類D接觸自類B和類C,而類B和類C都繼承自類A,因此出現了圖 2所示的情況。

在圖 2中,類D中會出現兩次A。爲了節省內存空間,可以將B、C對A的繼承定義爲虛擬繼承,而A成了虛擬基類。最後形成了圖 3。

代碼如下:

1
2
3
4
classA;
classB :public virtual A;
classC :public virtual A;
classD :public B,publicC;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章