名企面試官精講典型程式設計題之C++篇

broadviewbj發表於2011-12-12

名企面試官精講典型程式設計題之C++

C++

國內絕大部分高校都開設C++的課程,因此絕大部分程式設計師都學過C++,於是C++成了各公司面試的首選程式語言。包括Autodesk在內的很多公司在面試的時候會有大量的C++的語法題,其他公司雖然不直接面試C++的語法,但面試題要求用C++實現演算法。因此總的說來,應聘者不管去什麼公司求職,都應該在一定程度上掌握C++

通常語言面試有3種型別。第一種型別是面試官直接詢問應聘者對C++概念的理解。這種型別的問題,面試官特別喜歡瞭解應聘者對C++關鍵字的理解程度。例如:在C++中,有哪4個與型別轉換相關的關鍵字?這些關鍵字各有什麼特點,應該在什麼場合下使用?

在這種型別的題目中,sizeof是經常被問到的一個概念。比如下面的面試片段,就反覆出現在各公司的技術面試中。

面試官:定義一個空的型別,裡面沒有任何成員變數和成員函式。對該型別求sizeof,得到的結果是多少?

應聘者:答案是1

面試官:為什麼不是0

應聘者:空型別的例項中不包含任何資訊,本來求sizeof應該是0,但是當我們宣告該型別的例項的時候,它必須在記憶體中佔有一定的空間,否則無法使用這些例項。至於佔用多少記憶體,由編譯器決定。Visual Studio中每個空型別的例項佔用1位元組的空間。

面試官:如果在該型別中新增一個建構函式和解構函式,再對該型別求sizeof,得到的結果又是多少?

應聘者:和前面一樣,還是1。呼叫建構函式和解構函式只需要知道函式的地址即可,而這些函式的地址只與型別相關,而與型別的例項無關,編譯器也不會因為這兩個函式而在例項內新增任何額外的資訊。

面試官:那如果把解構函式標記為虛擬函式呢?

應聘者:C++的編譯器一旦發現一個型別中有虛擬函式,就會為該型別生成虛擬函式表,並在該型別的每一個例項中新增一個指向虛擬函式表的指標。在32位的機器上,一個指標佔4位元組的空間,因此求sizeof得到4;如果是64位的機器,一個指標佔8位元組的空間,因此求sizeof則得到8

面試C/C++的第二種題型就是面試官拿出事先準備好的程式碼,讓應聘者分析程式碼的執行結果。這種題型選擇的程式碼通常包含比較複雜微妙的語言特性,這要求應聘者對C++考點有著透徹的理解。即使應聘者對考點有一點點模糊,那麼最終他得到的結果和實際執行的結果可能就會差距甚遠。

比如面試官遞給應聘者一張有如下程式碼的A4列印紙要求他分析編譯執行的結果,並提供3個選項:A.編譯錯誤;B.編譯成功,執行時程式崩潰;C.編譯執行正常,輸出10

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(A other)傳入的引數是A的一個例項。由於是傳值引數,我們把形參複製到實參會呼叫複製建構函式。因此如果允許複製建構函式傳值,就會在複製建構函式內呼叫複製建構函式,就會形成永無休止的遞迴呼叫從而導致棧溢位。因此C++的標準不允許複製建構函式傳值引數,在Visual StudioGCC中,都將編譯出錯。要解決這個問題,我們可以把建構函式修改為A(const A& other),也就是把傳值引數改成常量引用。

第三種題型就是要求應聘者寫程式碼定義一個型別或者實現型別中的成員函式。讓應聘者寫程式碼的難度自然比讓應聘者分析程式碼要高不少,因為能想明白的未必就能寫得清楚。很多考查C++語法的程式碼題圍繞在建構函式、解構函式及運算子過載。比如面試題1“賦值運算子函式”就是一個例子。

為了讓大家能順利地透過C++面試,更重要的是能更好地學習掌握C++這門程式語言,這裡推薦幾本C++的書,大家可以根據自己的具體情況選擇閱讀的順序:

     Effective C++》。這本書很適合在面試之前突擊C++。這本書列舉了使用C++經常出現的問題及解決這些問題的技巧。該書中提到的問題也是面試官很喜歡問的問題。

     C++ Primer》。讀完這本書,就會對C++的語法有全面的瞭解。

     Inside C++ Object Model》。這本書有助於我們深入瞭解C++物件的內部。讀懂這本書後很多C++難題,比如前面的sizeof的問題、虛擬函式的呼叫機制等,都會變得很容易。

     The C++ Programming Language》。如果是想全面深入掌握C++,沒有哪本書比這本書更適合的了。

面試題1:賦值運算子函式

題目:如下為型別CMyString的宣告,請為該型別新增賦值運算子函式。

class CMyString

{

public:

    CMyString(char* pData = NULL);

    CMyString(const CMyString& str);

    ~CMyString(void);

        

private:

    char* m_pData;

};

當面試官要求應聘者定義一個賦值運算子函式時,他會在檢查應聘者寫出的程式碼時關注如下幾點:

     是否把返回值的型別宣告為該型別的引用,並在函式結束前返回例項自身的引用(即*this)。只有返回一個引用,才可以允許連續賦值。否則如果函式的返回值是void,應用該賦值運算子將不能做連續賦值。假設有3CMyString的物件:str1str2str3,在程式中語句str1=str2=str3將不能透過編譯。

     是否把傳入的引數的型別宣告為常量引用。如果傳入的引數不是引用而是例項,那麼從形參到實參會呼叫一次複製建構函式。把引數宣告為引用可以避免這樣的無謂消耗,能提高程式碼的效率。同時,我們在賦值運算子函式內不會改變傳入的例項的狀態,因此應該為傳入的引用引數加上const關鍵字。

     是否釋放例項自身已有的記憶體。如果我們忘記在分配新記憶體之前釋放自身已有的空間,程式將出現記憶體洩露。

     是否判斷傳入的引數和當前的例項(*this)是不是同一個例項。如果是同一個,則不進行賦值操作,直接返回。如果事先不判斷就進行賦值,那麼在釋放例項自身的記憶體的時候就會導致嚴重的問題:當*this和傳入的引數是同一個例項時,那麼一旦釋放了自身的記憶體,傳入的引數的記憶體也同時被釋放了,因此再也找不到需要賦值的內容了。

經典的解法,適用於初級程式設計師

當我們完整地考慮了上述4個方面之後,我們可以寫出如下的程式碼:

CMyString& CMyString::operator =(const CMyString &str)

{

    if(this == &str)

        return *this;

 

    delete []m_pData;

    m_pData = NULL;

 

    m_pData = new char[strlen(str.m_pData) + 1];

    strcpy(m_pData, str.m_pData);

 

    return *this;

}

這是一般C++教材上提供的參考程式碼。如果接受面試的是應屆畢業生或者C++初級程式設計師,能全面地考慮到前面四點並完整地寫出程式碼,面試官可能會讓他透過這輪面試。但如果面試的是C++高階程式設計師,面試官可能會提出更高的要求。

考慮異常安全性的解法,高階程式設計師必備

在前面的函式中,我們在分配記憶體之前先用delete釋放了例項m_pData的記憶體。如果此時記憶體不足導致new char丟擲異常,m_pData將是一個空指標,這樣非常容易導致程式崩潰。也就是說一旦在賦值運算子函式內部丟擲一個異常,CMyString的例項不再保持有效的狀態,這就違背了異常安全性(Exception Safety)原則。

要想在賦值運算子函式中實現異常安全性,我們有兩種方法。一個簡單的辦法是我們先用new分配新內容再用delete釋放已有的內容。這樣只在分配內容成功之後再釋放原來的內容,也就是當分配記憶體失敗時我們能確保CMyString的例項不會被修改。我們還有一個更好的辦法是先建立一個臨時例項,再交換臨時例項和原來的例項。下面是這種思路的參考程式碼:

CMyString& CMyString::operator =(const CMyString &str)

{

    if(this != &str)

    {

        CMyString strTemp(str);

 

        char* pTemp = strTemp.m_pData;

        strTemp.m_pData = m_pData;

        m_pData = pTemp;

    }

 

    return *this;

}

在這個函式中,我們先建立一個臨時例項strTemp,接著把strTemp.m_pData和例項自身的m_pData做交換。由於strTemp是一個區域性變數,但程式執行到if的外面時也就出了該變數的作用域,就會自動呼叫strTemp的解構函式,把strTemp.m_pData所指向的記憶體釋放掉。由於strTemp.m_pData指向的記憶體就是例項之前m_pData的記憶體,這就相當於自動呼叫解構函式釋放例項的記憶體。

在新的程式碼中,我們在CMyString的建構函式里用new分配記憶體。如果由於記憶體不足丟擲諸如bad_alloc等異常,我們還沒有修改原來例項的狀態,因此例項的狀態還是有效的,這也就保證了異常安全性。

如果應聘者在面試的時候能夠考慮到這個層面,面試官就會覺得他對程式碼的異常安全性有很深的理解,那麼他自然也就能透過這輪面試了。

  原始碼:

本題完整的原始碼詳見01_AssignmentOperator專案。

  測試用例:

     把一個CMyString的例項賦值給另外一個例項。

     把一個CMyString的例項賦值給它自己。

     連續賦值。

  本題考點:

     考查對C++的基礎語法的理解,如運算子函式、常量引用等。

     考查對記憶體洩露的理解。

     對高階C++程式設計師,面試官還將考查應聘者對程式碼異常安全性的理解。

 

 名企面試官精講典型程式設計題之C++篇

 

本文選自《劍指Offer——名企面試官精講典型程式設計題》一書

圖書詳細資訊:http://space.itpub.net/?uid-13164110-action-viewspace-itemid-712760

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/13164110/viewspace-713192/,如需轉載,請註明出處,否則將追究法律責任。

相關文章