1. 程式人生 > >C++ —— 經典面試題

C++ —— 經典面試題

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

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

1. 建構函式

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

構造原則如下:

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

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

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

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

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

6. 如果子類呼叫父類帶引數的構造方法,需要用初始化父類成員物件的方式

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;
}

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

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

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

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

class B :public A
{
private:
    int _b;
};

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

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

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

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

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

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

private:
    A(){}
    //將拷貝建構函式宣告成私有的
    A(const A& other);
    A& operator=(const A& other);
    int _a;
};

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

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

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

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

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

class A
{
private:
    void* operator new(size_t size);
    void operator delete(void *p);
    int _a;
};

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

要求如下:
C 實現一個 struct Astuct 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;
}

測試結果:

[[email protected] Test]$ ./a.out 
A::fa()
B::fb()

歡迎各位大佬斧正!

相關推薦

C++ 經典試題

sel 備份 函數地址 開發 對象 const對象 代碼 結構 函數類型 1,關於動態申請內存 答:內存分配方式三種: (1)從靜態存儲區域分配:內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。 全局變量,static變量。 (2)在棧上創建:在執行

C#經典試題及答案

list add 字段 有一個 副本 udp 隔離性 垃圾回收 readonly 字節 1.請你說說.net 中類和結構的區別? 答:結構和類具有大體的語法,但是結構受到的限制比類多。結構不能聲明默認的的構造函數,為結構的副本是編譯器創建和銷毀的,所以不需要默認的構造函數和

C++經典試題匯總

代碼 get child pre delet 函數調用 列表 code pub 1. 下面代碼輸出什麽?為什麽?(初始化列表) #include<iostream> using namespace std; class Test { int m_i

C/C++知識點之C/C++經典試題

本文主要向大家介紹了 C/C++知識點之C/C++經典面試題一,通過具體的內容向大家展示,希望對大家學習C/C++知識點有所幫助。 1.變數的宣告和定義有什麼區別? 常量:在程式執行過程中,不會發生改變的量,不能被改變的量 變數:在程式執行過程中,可以被改變的量 定義變數的方式:資料型別

C++經典試題

1 new/delete 與 malloc/free的區別     運算子是語言自身的特性,有固定的語義,編譯器知道意味著什麼,由編譯器解釋語義,生成相應的程式碼。     庫函式是依賴於庫的,一定程度上獨立於語言的。編譯器不關心庫函式的作用,只保證編譯,呼叫函式引數和返

C++經典試題--單例模式

轉載請註明出處 單例模式是面試到設計模式幾乎一定會問道的一個設計模式,另外的一個設計模式MVC也是很常見,後續博文會繼續分析MVC模式 就是一個類只返回一個物件的實體。 物件只是在第一次初始化的時候執行建構函式,進行建立。 比如:一家公司只會有一個CEO實體,任何事情都得回

第一章:10道C/C++經典試題

面試題 1:變數的宣告和定義有什麼區別 為變數分配地址和儲存空間的稱為定義,不分配地址的稱為宣告。 一個變數可以在多個地方宣告,但是隻在一個地方定義。 加入 extern 修飾的是變數的宣告,說明此變數將在檔案以外或在檔案後面部分定義。 說明:很多時候一個變數,只是宣告不分配記憶體空間,直到具體使用時

C++經典試題(三)

21.用C++寫個程式,如何判斷一個作業系統是16位還是32位的? 【標準答案】定義一個指標p,打印出sizeof(p),如果節果是4,則表示該作業系統是32位,列印結果是2,表示是16位。 22

Linux C經典試題

本篇文章整理了幾道Linux下C語言的經典面試題,相信對大家更好的理解Linux下的C語言會有很大的幫助,歡迎大家探討指正。 1、如果在Linux下使用GCC編譯器執行下列程式,輸出結果是什麼? 123456 #include<stdio.h>int

C++ —— 經典試題

一、哪些成員函式不能被繼承。   C++中,並不是所有的成員函式都能被子類繼承,有三類成員函式不能被子類繼承,分別是:建構函式(包括拷貝構造)、解構函式、賦值運算子過載函式。 1. 建構函式   構造方法用來初始化類的物件,與父類的其它成

c/c++經典試題

面試題9:簡述C、C++程式編譯的記憶體分配情況 C、C++中記憶體分配方式可以分為三種: (1)從靜態儲存區域分配: 記憶體在程式編譯時就已經分配好,這塊記憶體在程式的整個執行期間都存在。速度快、不容易出錯,因為有系統會善後。例如全域性變數,static變數等。 (2)在棧上分配: 在執行函式時,函式內區

linux C經典試題

華為筆試題 1.寫出判斷ABCD四個表示式的是否正確, 若正確, 寫出經過表示式中 a的值(3分) int a = 4; (A)a += (a++); (B) a += (++a) ;(C) (a++) += a;(D) (++a) += (a++); a = ? 答:C

第三章:10道C/C++經典試題

面試題 21:談談你對程式設計規範的理解或認識 程式設計規範可總結為:程式的可行性,可讀性、可移植性以及可測試性。 說明:這是程式設計規範的總綱目,面試者不一定要去背誦上面給出的那幾個例子,應該去理解這幾個例子說明的問題,想一想,自己如何解決可行性、可讀性、可移植性以及

C#經典試題100道

1. .NET和C#有什麼區別 答:.NET一般指 .NET FrameWork框架,它是一種平臺,一種技術。 C#是一種程式

Linux下C語言的幾道經典試題

ref 使用 linu 學習資源 chan ima 什麽 img c語言 本篇文章整理了幾道Linux下C語言的經典面試題,相信對大家更好的理解Linux下的C語言會有很大的幫助,歡迎大家探討指正。 1、如果在Linux下使用GCC編譯器執行下列程序,輸出結果是什麽? 答

C語言程式猿必會的記憶體四區及經典試題解析

前言:     為啥叫C語言程式猿必會呢?因為特別重要,學習C語言不知道記憶體分割槽,對很多問題你很難解釋,如經典的:傳值傳地址,前者不能改變實參,後者可以,知道為什麼?還有經典面試題如下:  #include <stdio.h> #include <stdlib.h>#in

C語言經典試題C語言面試寶典

第一部分:基本概念及其它問答題 1、關鍵字static的作用是什麼? 這個簡單的問題很少有人能回答完全。在C語言中,關鍵字static有三個明顯的作用: 1). 在函式體,一個被宣告為靜態的變數在這一函式被呼叫過程中維持其值不變。 2). 在模組內(但在函式體外),一個被宣告為

linux C程式設計師 經典試題(非常經典)

linux C程式設計師 經典面試題一 想成為嵌入式程式設計師應知道的0x10個基本問題: 前處理器(Preprocessor) 1 . 用預處理指令#define 宣告一個常數,用以表明1年中有多少秒(忽略閏年問題)          #define SECONDS_P

經典C語言試題4:位元組對齊的作用

   一、什麼是位元組對齊?     在現代計算機中,記憶體空間都是按照字節(byte)劃分的。從理論上講對任何型別的變數的訪問可以從任何地址開始,但實際情況是,訪問特定型別的變數的時候經常在特定的記憶體地址訪問,這就需要各種型別的資料按照一定規則在空間上排列,而不是順序地一

C++類和繼承中的部分經典試題

1.如何實現一個無法繼承的類? 思路: 私有繼承不可見,建構函式是合成的 class A { public: static A* GetObj1() //靜態成員函式 { return new A; //new 物件 } static A Ge