天天看點

兩道騰訊的面試真題,考驗C++對象模型的了解

1、原題如下:

#include <iostream>
 
using namespace std;
 
class Test
{
public:
    static int getA();
    int getB();
    virtual int getC();
    int a;
    static int c;
};
 
int Test::getA()
{
    return 0;
}
 
int Test::getB()
{
    return 0;
}
 
int Test::getC()
{
    return 0;
}
 
int main()
{
    Test *ptr = (Test*)malloc(sizeof(Test));
 
    //下面的幾個調用,哪些會出現問題?
    ptr->getA();
    ptr->getB();
    ptr->getC();
    ptr->a;
    ptr->c;
 
    return 0;
}

      

答案:所有語句都能通過編譯,但是運作時,ptr->getC();這條語句會出錯,crash。

分析:考察的地方有malloc和虛函數。

虛函數調用會crash,其他正常。因為其他在編譯期間可以确定位址。

malloc調用,不會像new一樣調用構造函數(free不會調用析構函數),所有虛函數指針都不會配置設定。

參考文獻:《深度探索C++對象模型》

2、原題如下:

以下代碼是否完全正确,執行可能得到的結果是____。

A.程式正常運作

B.程式編譯錯誤

C.程式崩潰

D.程式死循環

#include <iostream>
 
using namespace std;
 
class A
{
    int i;
};
 
class B
{
    A *p;
public:
    B(){p = new A;}
    ~B(){delete p;}
};
 
void sayHello(B b)
{
}
 
int main()
{
    B b;
    sayHello(b);
}

      

答案:C.首先程式中沒有編譯錯誤,也沒有死循環(程式中沒有循環哪裡來的死循環)。有些初學者可能會說main函數沒有寫return 0,main函數不顯性的寫return,編譯器也會幫你做的。當類中存在指針類型的成員變量時指派和析構要格外注意,這道題的問題就出在類B對象b中的指針p被析構了兩次。

分析:當執行完成B b這句話後,在b中就構造了一個類A的指針對象p,當調用sayHello(b)函數時系統将會調用類B的指派構造函數構造一個類B的執行個體bStep(為了友善下面的叙述随便起了一個名字)傳入到sayHello函數中(問題就出在bStep這個執行個體中),這裡當sayHello執行完成後,之前構造的執行個體bStep将被析構(執行delete p)。然後程式繼續開心的執行,直到執行完main函數後系統将會析構b,當b被析構時将再次執行delete p。這樣p就被析構了兩遍導緻程式崩潰。

我們把代碼增加一些輸出資訊後大家就更容易看了:

#include <iostream>
using namespace std;
class A
{
    int i;
};
class B
{
    A *p;
public:
    B(){ printf("構造\n"); p = new A; }
    ~B(){ printf("析構\n"); delete p; }
    B(const B &b){ printf("拷貝構造\n"); }
};
void sayHello(B b)
{
}
int main()
{
    B b;
    sayHello(b);
}      

控制台輸出如下(2次構造函數,2次析構函數):

構造

拷貝構造

析構

正确的方法應該把sayHello函數寫成這樣:

void sayHello(const B& b)

{

}

控制台輸出如下(1次構造函數,1次析構函數):

使用引用的方法效率高,沒有拷貝構造函數被調用,因為沒有新對象被建立。const目的是避免對象被改變。

參考文獻:《Effective C++ 第三版》條款20:甯以pass-by-reference-to-const替換pass-by-value

繼續閱讀