c++面試題


題目(一):我們可以用static修飾

個類的成員函數,也可以用const修飾類的成員函數(寫在函數的最後表示不能修改成員變量,不是指寫在前面表示返回值爲常量)。請問:能不能同時用staticconst修飾類的成員函數?

分析:答案是不可以。C++編譯器在實現const的成員函數的時候爲了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數onst this*。但當一個成員爲static的時候,該函數是沒有this指針的。也就是說此時static的用法和static是衝突的。

我們也可以這樣理解:兩者的語意是矛盾的。static的作用是表示該函數只作用在類型的靜態變量上,與類的實例沒有關係;而const的作用是確保函數不能修改類的實例的狀態,與類型的靜態變量沒有關係。因此不能同時用它們。

題目(二):運行下面的代碼,輸出是什麼?

class A

{

};

 

class B

{

public:

        B() {}

        ~B() {}

};

 

class C

{

public:

        C() {}

        virtual ~C() {}

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        printf("%d, %d, %d\n"sizeof(A), sizeof(B), sizeof(C));

        return 0;

}

分析:答案是1, 1, 4class A是一個空類型,它的實例不包含任何信息,本來求sizeof應該是0。但當我們聲明該類型的實例的時候,它必須在內存中佔有一定的空間,否則無法使用這些實例。至於佔用多少內存,由編譯器決定。Visual Studio 2008中每個空類型的實例佔用一個byte的空間。

class Bclass A的基礎上添加了構造函數和析構函數。由於構造函數和析構函數的調用與類型的實例無關(調用它們只需要知道函數地址即可),在它的實例中不需要增加任何信息。所以sizeof(B)sizeof(A)一樣,在Visual Studio 2008中都是1

class Cclass B的基礎上把析構函數標註爲虛擬函數。C++的編譯器一旦發現一個類型中有虛擬函數,就會爲該類型生成虛函數表,並在該類型的每一個實例中添加一個指向虛函數表的指針。在32位的機器上,一個指針佔4個字節的空間,因此sizeof(C)4

題目(三):運行下面中的代碼,得到的結果是什麼

class A

{

private:

        int m_value;

 

public:

        A(int value)

        {

                m_value = value;

        }

        void Print1()

        {

                printf("hello world");

        }

        void Print2()

        {

                printf("%d", m_value);

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A* pA = NULL;

        pA->Print1();

        pA->Print2();

 

        return 0;

}

分析:答案是Print1調用正常,打印出hello world,但運行至Print2時,程序崩潰。調用Print1時,並不需要pA的地址,因爲Print1的函數地址是固定的。編譯器會給Print1傳入一個this指針,該指針爲NULL,但在Print1中該this指針並沒有用到。只要程序運行時沒有訪問不該訪問的內存就不會出錯,因此運行正常。在運行print2時,需要this指針才能得到m_value的值。由於此時this指針爲NULL,因此程序崩潰了。

題目(四):運行下面中的代碼,得到的結果是什麼

class A

{

private:

        int m_value;

 

public:

        A(int value)

        {

                m_value = value;

        }

        void Print1()

        {

                printf("hello world");

        }

        virtual void Print2()

        {

                printf("hello world");

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A* pA = NULL;

        pA->Print1();

        pA->Print2();

 

        return 0;

}

分析:答案是Print1調用正常,打印出hello world,但運行至Print2時,程序崩潰。Print1的調用情況和上面的題目一樣,不在贅述。由於Print2是虛函數。C++調用虛函數的時候,要根據實例(即this指針指向的實例)中虛函數表指針得到虛函數表,再從虛函數表中找到函數的地址。由於這一步需要訪問實例的地址(即this指針),而此時this指針爲空指針,因此導致內存訪問出錯。

題目(五):靜態成員函數能不能同時也是虛函數

分析:答案是不能。調用靜態成員函數不要實例。但調用虛函數需要從一個實例中指向虛函數表的指針以得到函數的地址,因此調用虛函數需要一個實例。兩者相互矛盾。

題目(六):運行下列C++代碼,輸出什麼?

struct Point3D

{

        int x;

        int y;

        int z;

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        Point3D* pPoint = NULL;

        int offset = (int)(&(pPoint)->z);

 

        printf("%d", offset);

        return 0;

}

答案:輸出8。由於在pPoint->z的前面加上了取地址符號,運行到此時的時候,會在pPoint的指針地址上加z在類型Point3D中的偏移量8。由於pPoint的地址是0,因此最終offset的值是8

&(pPoint->z)的語意是求pPoint中變量z的地址(pPoint的地址0z的偏移量8),並不需要訪問pPoint指向的內存。只要不訪問非法的內存,程序就不會出錯。

題目(七):運行下列C++代碼,輸出什麼?

class A

{

public:

        A()

        {

                Print();

        }

        virtual void Print()

        {

                printf("A is constructed.\n");

        }

};

 

class B: public A

{

public:

        B()

        {

                Print();

        }

 

        virtual void Print()

        {

                printf("B is constructed.\n");

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A* pA = new B();

        delete pA;

 

        return 0;

}

答案:先後打印出兩行:A is constructed. B is constructed. 調用B的構造函數時,先會調用B的基類及A的構造函數。然後在A的構造函數裏調用Print。由於此時實例的類型B的部分還沒有構造好,本質上它只是A的一個實例,他的虛函數表指針指向的是類型A的虛函數表。因此此時調用的PrintA::Print,而不是B::Print。接着調用類型B的構造函數,並調用Print。此時已經開始構造B,因此此時調用的PrintB::Print

同樣是調用虛擬函數Print,我們發現在類型A的構造函數中,調用的是A::Print,在B的構造函數中,調用的是B::Print。因此虛函數在構造函數中,已經失去了虛函數的動態綁定特性。

題目 10

int SizeOf(char pString[])

{

        return sizeof(pString);

}

 

int _tmain(int argc, _TCHAR* argv[])

{

        char* pString1 = "google";

        int size1 = sizeof(pString1);

        int size2 = sizeof(*pString1);

 

        char pString2[100] = "google";

        int size3 = sizeof(pString2);

        int size4 = SizeOf(pString2);

 

        printf("%d, %d, %d, %d", size1, size2, size3, size4);

 

        return 0;

}

答案:4, 1, 100, 4。pString1是一個指針。在32位機器上,任意指針都佔4個字節的空間。*pString1是字符串pString1的第一個字符。一個字符佔一個字節。pString2是一個數組,sizeof(pString2)是求數組的大小。這個數組包含100個字符,因此大小是100個字節。而在函數SizeOf中,雖然傳入的參數是一個字符數組,當數組作爲函數的參數進行傳遞時,數組就自動退化爲同類型的指針。因此size4也是一個指針的大小,爲4.

題目(九):在C++和C#中,struct和class有什麼不同?

答案:在C++中,如果沒有標明函數或者變量是的訪問權限級別,在struct中,是public的;而在class中,是private的。



題目(12):運行下圖中的C++代碼,輸出是什麼?

#include <iostream>

 

class A

{

private:

        int n1;

        int n2;

public:

        A(): n2(0), n1(n2 + 2)

        {

        }

 

        void Print()

        {

                std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl;

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A a;

        a.Print();

 

        return 0;

}

答案:輸出n1是一個隨機的數字,n2爲0。在C++中,成員變量的初始化順序與變量在類型中的申明順序相同,而與它們在構造函數的初始化列表中的順序無關。因此在這道題中,會首先初始化n1,而初始n1的參數n2還沒有初始化,是一個隨機值,因此n1就是一個隨機值。初始化n2時,根據參數0對其初始化,故n2=0。



題目(13):編譯運行下圖中的C++代碼,結果是什麼?(A)編譯錯誤;(B)編譯成功,運行時程序崩潰;(C)編譯運行正常,輸出10。請選擇正確答案並分析原因。

#include <iostream>

 

class A

{

private:

        int value;

 

public:

        A(int n)

        {

                value = n;

        }

 

        A(A other)

        {

                value = other.value;

        }

 

        void Print()

        {

                std::cout << value << std::endl;

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A a = 10;

        A b = a;

        b.Print();

 

        return 0;

}

答案:編譯錯誤。在複製構造函數中傳入的參數是A的一個實例。由於是傳值,把形參拷貝到實參會調用複製構造函數。因此如果允許複製構造函數傳值,那麼會形成永無休止的遞歸併造成棧溢出。因此C++的標準不允許複製構造函數傳值參數,而必須是傳引用或者常量引用。在Visual Studio和GCC中,都將編譯出錯。


題目(15):運行下圖中代碼,輸出的結果是什麼?這段代碼有什麼問題?

#include <iostream>

 

class A

{

public:

        A()

        {

                std::cout << "A is created." << std::endl;

        }

 

        ~A()

        {

                std::cout << "A is deleted." << std::endl;

        }

};

 

class B : public A

{

public:

        B()

        {

                std::cout << "B is created." << std::endl;

        }

 

        ~B()

        {

                std::cout << "B is deleted." << std::endl;

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A* pA = new B();

        delete pA;

 

        return 0;

}

答案:輸出三行,分別是:A is created. B is created. A is deleted。用new創建B時,回調用B的構造函數。在調用B的構造函數的時候,會先調用A的構造函數。因此先輸出A is created. B is created.

接下來運行delete語句時,會調用析構函數。由於pA被聲明成類型A的指針,同時基類A的析構函數沒有標上virtual,因此只有A的析構函數被調用到,而不會調用B的析構函數。

由於pA實際上是指向一個B的實例的指針,但在析構的時候只調用了基類A的析構函數,卻沒有調用B的析構函數。這就是一個問題。如果在類型B中創建了一些資源,比如文件句柄、內存等,在這種情況下都得不到釋放,從而導致資源泄漏。

問題(16):運行如下的C++代碼,輸出是什麼?

class A

{

public:

    virtual void Fun(int number = 10)

    {

        std::cout << "A::Fun with number " << number;

    }

};

 

class B: public A

{

public:

    virtual void Fun(int number = 20)

    {

        std::cout << "B::Fun with number " << number;

    }

};

 

int main()

{

    B b;

    A &a = b;

    a.Fun();

}

答案:輸出B::Fun with number 10。由於a是一個指向B實例的引用,因此在運行的時候會調用B::Fun。但缺省參數是在編譯期決定的。在編譯的時候,編譯器只知道a是一個類型a的引用,具體指向什麼類型在編譯期是不能確定的,因此會按照A::Fun的聲明把缺省參數number設爲10。

            這一題的關鍵在於理解確定缺省參數的值是在編譯的時候,但確定引用、指針的虛函數調用哪個類型的函數是在運行的時候。




問題(17):運行如下的C代碼,輸出是什麼?

char* GetString1()

{

    char p[] = "Hello World";

    return p;

}

 

char* GetString2()

{

    char *p = "Hello World";

    return p;

}

 

 

int _tmain(int argc, _TCHAR* argv[])

{

    printf("GetString1 returns: %s. \n", GetString1());

    printf("GetString2 returns: %s. \n", GetString2());

 

    return 0;

}

答案:輸出兩行,第一行GetString1 returns: 後面跟的是一串隨機的內容,而第二行GetString2 returns: Hello World. 兩個函數的區別在於GetString1中是一個數組,而GetString2中是一個指針。

當運行到GetString1時,p是一個數組,會開闢一塊內存,並拷貝"Hello World"初始化該數組。接着返回數組的首地址並退出該函數。由於p是GetString1內的一個局部變量,當運行到這個函數外面的時候,這個數組的內存會被釋放掉。因此在_tmain函數裏再去訪問這個數組的內容時,結果是隨機的。

當運行到GetString2時,p是一個指針,它指向的是字符串常量區的一個常量字符串。該常量字符串是一個全局的,並不會因爲退出函數GetString2而被釋放掉。因此在_tmain中仍然根據GetString2返回的地址得到字符串"Hello World"。




問題(19):運行下圖中C代碼,輸出的結果是什麼?

int _tmain(int argc, _TCHAR* argv[])

{

    char str1[] = "hello world";

    char str2[] = "hello world";

 

    char* str3 = "hello world";

    char* str4 = "hello world";

 

    if(str1 == str2)

        printf("str1 and str2 are same.\n");

    else

        printf("str1 and str2 are not same.\n");

 

    if(str3 == str4)

        printf("str3 and str4 are same.\n");

    else

        printf("str3 and str4 are not same.\n");

 

    return 0;

}

答案:輸出兩行。第一行是str1 and str2 are not same,第二行是str3 and str4 are same。

str1和str2是兩個字符串數組。我們會爲它們分配兩個長度爲12個字節的空間,並把"hello world"的內容分別拷貝到數組中去。這是兩個初始地址不同的數組,因此比較str1和str2的值,會不相同。str3和str4是兩個指針,我們無需爲它們分配內存以存儲字符串的內容,而只需要把它們指向"hello world“在內存中的地址就可以了。由於"hello world”是常量字符串,它在內存中只有一個拷貝,因此str3和str4指向的是同一個地址。因此比較str3和str4的值,會是相同的。

問題(23):運行下圖中的C++代碼,打印出的結果是什麼?

bool Fun1(char* str)

{

    printf("%s\n", str);

    return false;

}

 

bool Fun2(char* str)

{

    printf("%s\n", str);

    return true;

}

 

int _tmain(int argc, _TCHAR* argv[])

{

    bool res1, res2;

    res1 = (Fun1("a") && Fun2("b")) || (Fun1("c") || Fun2("d"));

    res2 = (Fun1("a") && Fun2("b")) && (Fun1("c") || Fun2("d"));

 

    return res1 || res2;

}

答案:打印出4行,分別是a、c、d、a。

在C/C++中,與、或運算是從左到右的順序執行的。在計算rest1時,先計算Fun1(“a”) && Func2(“b”)。首先Func1(“a”)打印出內容爲a的一行。由於Fun1(“a”)返回的是false, 無論Func2(“b”)的返回值是true還是false,Fun1(“a”) && Func2(“b”)的結果都是false。由於Func2(“b”)的結果無關重要,因此Func2(“b”)會略去而不做計算。接下來計算Fun1(“c”) || Func2(“d”),分別打印出內容c和d的兩行。

                在計算rest2時,首先Func1(“a”)打印出內容爲a的一行。由於Func1(“a”)返回false,和前面一樣的道理,Func2(“b”)會略去不做計算。由於Fun1(“a”) && Func2(“b”)的結果是false,不管Fun1(“c”) && Func2(“d”)的結果是什麼,整個表達式得到的結果都是false,因此Fun1(“c”) && Func2(“d”)都將被忽略。

問題(25):運行下面的C++代碼,打印的結果是什麼?

class Base

{

public:

    void print() { doPrint();}

 

private:

    virtual void doPrint() {cout << "Base::doPrint" << endl;}

};

 

class Derived : public Base

{

private:

    virtual void doPrint() {cout << "Derived::doPrint" << endl;}

};

 

int _tmain(int argc, _TCHAR* argv[])

{

    Base b;

    b.print();

 

    Derived d;

    d.print();

 

       return 0;

}

答案:輸出兩行,分別是Base::doPrint和Derived::doPrint。在print中調用doPrint時,doPrint()的寫法和this->doPrint()是等價的,因此將根據實際的類型調用對應的doPrint。所以結果是分別調用的是Base::doPrint和Derived::doPrint2。如果感興趣,可以查看一下彙編代碼,就能看出來調用doPrint是從虛函數表中得到函數地址的。


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