c++面試題1

aFakeProgramer發表於2018-07-30

 

 

一、哪些成員函式不能被繼承。

  C++中,並不是所有的成員函式都能被子類繼承,有三類成員函式不能被子類繼承,分別是:建構函式(包括拷貝構造)、解構函式、賦值運算子過載函式。

1. 建構函式

  構造方法用來初始化類的物件,與父類的其它成員不同,它不能被子類繼承(子類可以繼承父類所有的成員變數和成員方法,但不繼承父類的構造方法)。因此,在建立子類物件時,為了初始化從父類繼承來的資料成員,系統需要呼叫其父類的構造方法。 
  如果沒有顯式的建構函式,編譯器會給一個預設的建構函式,並且該預設的建構函式僅僅在沒有顯式地宣告建構函式情況下建立。

構造原則如下:

1. 如果子類沒有定義構造方法,則呼叫父類的無引數的構造方法。

2. 如果子類定義了構造方法,不論是無引數還是帶引數,在建立子類的物件的時候,首先執行父類無引數的構造方法,然後執行自己的構造方法。

3. 在建立子類物件時候,如果子類的建構函式沒有顯示呼叫父類的建構函式,則會呼叫父類的預設無參建構函式。

4. 在建立子類物件時候,如果子類的建構函式沒有顯示呼叫父類的建構函式且父類自己提供了無參建構函式,則會呼叫父類自己的無參建構函式。

5. 在建立子類物件時候,如果子類的建構函式沒有顯示呼叫父類的建構函式且父類只定義了自己的有參建構函式,則會出錯(如果父類只有有引數的構造方法,則子類必須顯示呼叫此帶參構造方法)。

6. 如果子類呼叫父類帶引數的構造方法,需要用初始化父類成員物件的方式
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2. 解構函式

  解構函式也不會被子類繼承,只是在子類的解構函式中會呼叫父類的解構函式。

3. 賦值運算子過載函式

  賦值運算子過載函式也不會被子類繼承,只是在子類的賦值運算子過載函式中會呼叫父類的賦值運算子過載函式。


二、哪些函式不能宣告成虛擬函式

  在C++,有五種函式不能被宣告成虛擬函式,分別是:非成員函式、建構函式、靜態成員函式、內聯成員函式、友元函式這五種,下面分別解釋為什麼這五種函式不能被宣告成虛擬函式。

1. 非成員函式

  非成員函式只能被過載(overload),不能被繼承(override),而虛擬函式主要的作用是在繼承中實現動態多型,非成員函式早在編譯期間就已經繫結函式了,無法實現動態多型,那宣告成虛擬函式還有什麼意義呢?

2. 建構函式

  要想呼叫虛擬函式必須要通過“虛擬函式表”來進行的,但虛擬函式表是要在物件例項化之後才能夠進行呼叫。而在建構函式執行期間,還沒有為虛擬函式表分配空間,自然就沒法呼叫虛擬函式了。

3. 靜態成員函式

  靜態成員函式對於每個類來說只有一份,所有的物件都共享這一份程式碼,它是屬於類的而不是屬於物件。虛擬函式必須根據物件型別才能知道呼叫哪一個虛擬函式,故虛擬函式是一定要在物件的基礎上才可以的,兩者一個是與例項相關,一個是與類相關。

4. 內聯成員函式

  行內函數是為了在程式碼中直接展開,減少函式呼叫花費的代價,虛擬函式是為了在繼承後物件能夠準確的執行自己的動作,並且inline函式在編譯時被展開,虛擬函式在執行時才能動態地繫結函式。

5. 友元函式

  因為C++不支援友元函式的繼承,對於沒有繼承特性的函式沒有虛擬函式的說法。友元函式不屬於類的成員函式,不能被繼承。


三、為什麼解構函式和建構函式內不能呼叫虛擬函式

  在構造派生類物件時,首先呼叫基類建構函式初始化物件的基類部分,再呼叫派生類建構函式。在執行基類建構函式時,物件的派生類部分是未初始化的。實際上,此時的物件還不是一個派生類物件(不完整)。

  析構派生類物件時,首先呼叫的是派生類解構函式,一旦開始執行派生類解構函式,物件內派生類的成員變數便呈現未定義值,此時物件便不完整。 
   
  為了適應這種不完整,編譯器將物件的型別視為在呼叫構造/解構函式時發生了變換,即:視物件的型別為當前建構函式/解構函式所在的類的型別。由此造成的結果是:在基類建構函式或者解構函式中,會將派生類物件當做基類型別物件對待。而這樣一個結果,會對建構函式、解構函式呼叫期間呼叫的虛擬函式型別的動態繫結物件產生影響,最終的結果是:如果在建構函式或者解構函式中呼叫虛擬函式,執行的都將是為建構函式或者解構函式自身類型別定義的虛擬函式版本。


四、為什麼解構函式最好宣告成虛擬函式

class A{
public:
    A(){cout << "A()" << endl;}

    ~A(){cout << "~A()" << endl;}
};

class B : public A{
public:
    B(){cout << "B()" << endl;}
    ~B(){cout << "~B()" << endl;}
};

int main()
{
    A* p = new B;
    delete p;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

  上述程式碼,最後只呼叫了父類的解構函式,沒有呼叫子類的解構函式,這在一定程度上會造成記憶體洩漏,就像你拆房子只拆了地基其他的不拆一樣。要解決這種問題可以把解構函式定義成虛擬函式。虛擬函式的呼叫會根據指標指向的型別決定,此時p指向B型別,所以會呼叫B的解構函式,這樣就不會造成記憶體洩漏。 
  所以最好把解構函式,建議是最好定義成虛擬函式。


五、實現一個不能被繼承的類

  將建構函式定義為私有的,因為子類建立物件需要先呼叫父類的建構函式,如果父類中的建構函式被定義為私有的,那麼子類就無法呼叫父類建構函式。

class A
{
private:
    A(){}
    int _a;
};

class B :public A
{
private:
    int _b;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

六、實現一個類定義出來的物件都在堆上面

class A
{
public:
    static A* fun()
    {
        return new A;
    }
private:
    A(){}
    int _a;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  上面程式碼能保證建構函式生成的物件都在堆上,但並不能保證拷貝構造出來的物件在堆上。 
  這個程式碼有缺陷,雖然能保證構造的物件都是堆上的,但是不能防止拷貝構造的物件在棧上。

A* a = A::fun(); //在堆上 
A a1(*pa);       //在棧上 
  • 1
  • 2

此時要再優化一下程式碼: 
1. 只宣告拷貝構造不實現 。 
2. 將拷貝構造宣告成私有的(防止在類外實現拷貝構造)。

class A
{
public:
    static A* fun()
    {
        return new A;
    }

private:
    A(){}
    //將拷貝建構函式宣告成私有的
    A(const A& other);
    A& operator=(const A& other);
    int _a;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

七、實現一個類定義出來的物件都在棧上面

class A
{
public:
    static A& fun()
    {
        return A();
    }

private:
    A()
    {}
    int _a;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

八、實現一個類,定義出的物件不能在堆上

  將動態分配空間的操作符宣告為私有的,即可防止構造類物件時在堆上開空間的行為。

class A
{
private:
    void* operator new(size_t size);
    void operator delete(void *p);
    int _a;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

九、用C語言實現繼承和多型

要求如下: 
C 實現一個 struct A 和 stuct B 包含一個 int 成員 _a 和 _b,要求達到B 繼承 A 的效果,也就是 B裡面包含一個 A,並且能達到多型的效果,也就是基類指標,能根據它指向的物件型別呼叫不同的函式。

//函式指標
typedef void(*FUNC) ();

struct A
{
    // 函式指標模擬實現成員函式
    FUNC _func;
    int _a;
};

struct B
{
    // 結構體巢狀定義模擬實現繼承
    struct A a;
    int _b;
};

void fa()
{
    printf("A::fa()\n");
}
void fb()
{
    printf("B::fb()\n");
}

int main()
{
    struct A a;
    struct B b;
    a._func = fa;
    b.a._func = fb;
    struct A* p = &a; 

    //指向基類物件,呼叫基類的函式
    p->_func();

    //指向派生類物件,呼叫派生類函式
    p = (struct A*)&b;
    p->_func();

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

測試結果:

[tian@localhost Test]$ ./a.out 
A::fa()
B::fb()
  • 1
  • 2
  • 3

歡迎各位大佬斧正!

轉載出處 https://blog.csdn.net/Tianzez/article/details/80057455

 

相關文章