秋招總結------C++面試題總結二

1.構造函數的執行順序?析構函數的執行順序?構造函數內部幹了啥?拷貝構造幹了啥?

  • 構造函數順序
  1. 基類構造函數。如果有多個基類,則構造函數的調用順序是某類在類派生表中出現的順序,而不是它們在成員初始化表中的順序。
  2. 成員類對象構造函數。如果有多個成員類對象則構造函數的調用順序是對象在類中被聲明的順序,而不是它們出現在成員初始化表中的順序。
  3. 派生類構造函數
  • 析構函數順序
  1. 調用派生類的析構函數
  2. 調用成員類對象的析構函數
  3. 調用基類的析構函數

2.虛析構函數的作用,父類的析構函數是否要設置爲虛函數?

  1. C++中基類採用virtual虛析構函數是爲了防止內存泄漏具體地說,如果生類中申請了內存空間,並在其析構函數中對這些內存空間進行釋放。假設基類中採用的是非虛析構函數當刪除基類指針指向的派生類對象時就不會觸發動態綁定,因而只會調用基類的析構函數,而不會調用派生類的析構函數。那麼在這種情況下,派生類中申請的空間就得不到釋放從而產生內存泄漏。所以,爲了防止這種情況的發生,C++中基類的析構函數應採用virtual虛析構函數。
  2. 純虛析構函數一定得定義,因爲每一個派生類析構函數會被編譯器加以擴張,以靜態調用的方式調用其每一個虛基類以及上一層基類的析構函數。因此,缺乏任何一個基類析構函數的定義,就會導致鏈接失敗。因此,最好不要把虛析構函數定義爲純虛析構函數。

3.構造函數和析構函數可以調用虛函數嗎?

  1. 構造函數和析構函數中最好不要調用虛函數
  2. 構造函數或者析構函數調用虛函數並不會發揮虛函數動態綁定的特性,跟普通函數沒區別;
  3. 即使構造函數或者析構函數如果能成功調用虛函數, 程序的運行結果也是不可控的。

4.構造函數析構函數可否拋出異常

  1.  C++只會析構已經完成的對象,對象只有在其構造函數執行完畢纔算是完全構造妥當。在構造函數中發生異常控制權轉出構造函數之外。因此,在對象b的構造函數中發生異常,對象b的析構函數不會被調用。因此會造成內存泄漏。
  2. 用auto_ptr對象來取代指針類成員,便對構造函數做了強化,免除了拋出異常時發生資源泄漏的危機,不再需要在析構函數中手動釋放資源;
  3. 如果控制權基於異常的因素離開析構函數,而此時正有另一個異常處於作用狀態,C++會調用terminate函數讓程序結束;
  4. 如果異常從析構函數拋出,而且沒有在當地進行捕捉,那個析構函數便是執行不全的。如果析構函數執行不全,就是沒有完成他應該執行的每一件事情。

5. 類如何實現只能靜態分配和只能動態分配

  1. 前者是把new、delete運算符重載爲private屬性。後者是把構造、析構函數設爲protected屬性,再用子類來動態創建
  • 建立類的對象有兩種方式:
  1. 靜態建立,靜態建立一個類對象,就是由編譯器爲對象在棧空間中分配內存;
  2. 動態建立,A *p = new A();動態建立一個類對象,就是使用new運算符爲對象在堆空間中分配內存。這個過程分爲兩步,第一步執行operator new()函數,在堆中搜索一塊內存並進行分配;第二步調用類構造函數構造對象;
  3. 只有使用new運算符,對象纔會被建立在堆上,因此只要限制new運算符就可以實現類對象只能建立在棧上。可以將new運算符設爲私有。

6. 如果想將某個類用作基類,爲什麼該類必須定義而非聲明?

  1. 派生類中包含並且可以使用它從基類繼承而來的成員,爲了使用這些成員,派生類必須知道他們是什麼

7. 什麼情況會自動生成默認構造函數?

  1. 帶有默認構造函數的類成員對象,如果一個類沒有任何構造函數,但它含有一個成員對象,而後者有默認構造函數,那麼編譯器就爲該類合成出一個默認構造函數。不過這個合成操作只有在構造函數真正被需要的時候纔會發生;如果一個類A含有多個成員類對象的話,那麼類A的每一個構造函數必須調用每一個成員對象的默認構造函數而且必須按照類對象在類A中的聲明順序進行;
  2. 帶有默認構造函數的基類,如果一個沒有任務構造函數的派生類派生自一個帶有默認構造函數基類,那麼該派生類會合成一個構造函數調用上一層基類的默認構造函數;
  3. 帶有一個虛函數的類
  4. 帶有一個虛基類的類
  • 合成的默認構造函數中,只有基類子對象和成員類對象會被初始化。所有其他的非靜態數據成員都不會被初始化。

8.什麼是類的繼承?

  • 類與類之間的關係
  1. has-A包含關係,用以描述一個類由多個部件類構成,實現has-A關係用類的成員屬性表示,即一個類的成員屬性是另一個已經定義好的類;
  2. use-A,一個類使用另一個類,通過類之間的成員函數相互聯繫,定義友元或者通過傳遞參數的方式來實現;
  3. is-A,繼承關係,關係具有傳遞性;
  • 繼承的相關概念
  1. 所謂的繼承就是一個類繼承了另一個類的屬性和方法,這個新的類包含了上一個類的屬性和方法,被稱爲子類或者派生類,被繼承的類稱爲父類或者基類;
  • 繼承的特點
  • 子類擁有父類的所有屬性和方法,子類可以擁有父類沒有的屬性和方法,子類對象可以當做父類對象使用;
  1. 繼承中的訪問控制  public、protected、private
  2. 繼承中的構造和析構函數
  3. 繼承中的兼容性原則

9. ​​​​​​​什麼是組合?

  1. 一個類裏面的數據成員另一個類的對象,即內嵌其他類的對象作爲自己的成員;創建組合類的對象:首先創建各個內嵌對象,難點在於構造函數的設計。創建對象時既要對基本類型的成員進行初始化,又要對內嵌對象進行初始化。
  2. 創建組合類對象,構造函數的執行順序:先調用內嵌對象的構造函數,然後按照內嵌對象成員在組合類中的定義順序,與組合類構造函數的初始化列表順序無關。然後執行組合類構造函數的函數體,析構函數調用順序相反。

10.​​​​​​​ 抽象基類爲什麼不能創建對象?

  • 抽象類是一種特殊的類,它是爲了抽象和設計的目的爲建立的,它處於繼承層次結構的較上層。
  • (1)抽象類的定義:
             稱
    帶有純虛函數的類爲抽象類

    (2)抽象類的作用:
             抽象類的主要作用是
    將有關的操作作爲結果接口組織在一個繼承層次結構中,由它來爲派生類提供一個公共的根,派生類將具體實現在其基類中作爲接口的操作。所以抽象類實際上刻畫了一組子類的操作接口的通用語義,這些語義也傳給子類,子類可以具體實現這些語義,也可以再將這些語義傳給自己的子類。

    (3)使用抽象類時注意:
             
    抽象類只能作爲基類來使用,其純虛函數的實現由派生類給出。如果派生類中沒有重新定義純虛函數,而只是繼承基類的純虛函數,則這個派生類仍然還是一個抽象類。如果派生類中給出了基類純虛函數的實現,則該派生類就不再是抽象類了,它是一個可以建立對象的具體的類。

    抽象類是不能定義對象的。一個純虛函數不需要(但是可以)被定義。

 

​​​​​​​11.純虛函數

      純虛函數定義:純虛函數是一種特殊的虛函數,它的一般格式如下:

  class <類名>
  {
  virtual <類型><函數名>(<參數表>)=0;
  …
  };
  在許多情況下,在基類中不能對虛函數給出有意義的實現,而把它聲明爲純虛函數,它的實現留給該基類的派生類去做。這就是純虛函數的作用。
  純虛函數可以讓類先具有一個操作名稱,而沒有操作內容,讓派生類在繼承時再去具體地給出定義。凡是含有純虛函數的類叫做抽象類。這種類不能聲明對象,只是作爲基類爲派生類服務。除非在派生類中完全實現基類中所有的的純虛函數,否則,派生類也變成了抽象類,不能實例化對象。

 

 

     純虛函數引入原因:
  1、爲了方便使用多態特性,我們常常需要在基類中定義虛擬函數。
  2、在很多情況下,基類本身生成對象是不合情理的。例如,動物作爲一個基類可以派生出老虎、孔 雀等子類,但動物本身生成對象明顯不合常理。
  爲了解決上述問題,引入了純虛函數的概念,將函數定義爲純虛函數(方法:virtual ReturnType Function()= 0;)。若要使派生類爲非抽象類,則編譯器要求在派生類中,必須對純虛函數予以重載以實現多態性。同時含有純虛函數的類稱爲抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。
例如,繪畫程序中,shape作爲一個基類可以派生出圓形、矩形、正方形、梯形等, 如果我要求面積總和的話,那麼會可以使用一個 shape * 的數組,只要依次調用派生類的area()函數了。如果不用接口就沒法定義成數組,因爲既可以是circle ,也可以是square ,而且以後還可能加上rectangle,等等.

三、相似概念
1、多態性

指相同對象收到不同消息或不同對象收到相同消息時產生不同的實現動作。C++支持兩種多態性:編譯時多態性,運行時多態性。
  a.編譯時多態性:通過重載函數實現
  b.運行時多態性:通過虛函數實現。
2、虛函數
  虛函數是在基類中被聲明爲virtual,並在派生類中重新定義的成員函數,可實現成員函數的動態重載。
3、抽象類
  包含純虛函數的類稱爲抽象類。由於抽象類包含了沒有定義的純虛函數,所以不能定義抽象類的對象。

12.​​​​​​​ 類什麼時候會析構?

  1. 對象生命週期結束,被銷燬時;
  2. delete指向對象的指針時,或delete指向對象的基類類型指針,而其基類虛構函數是虛函數時;
  3. 對象i是對象o的成員,o的析構函數被調用時,對象i的析構函數也被調用。

13.​​​​​​​ 爲什麼友元函數必須在類內部聲明?

  1. 因爲編譯器必須能夠讀取這個結構的聲明以理解這個數據類型的大、行爲等方面的所有規則。有一條規則在任何關係中都很重要,那就是誰可以訪問我的私有部分

14. ​​​​​​​介紹一下C++裏面的多態?

  1. 靜態多態(重載,模板)

              是在編譯的時候,就確定調用函數的類型

    2.動態多態(覆蓋,虛函數實現)

             在運行的時候,才確定調用的是哪個函數,動態綁定。運行基類指針指向派生類的對象,並調用派生類的函數。

虛函數實現原理:虛函數表和虛函數指針。

純虛函數: virtual int fun() = 0;

函數的運行版本由實參決定,在運行時選擇函數的版本,所以動態綁定又稱爲運行時綁定。

當編譯器遇到一個模板定義時,它並不生成代碼。只有當實例化出模板的一個特定版本時,編譯器纔會生成代碼。

15. ​​​​​​​繼承機制中對象之間如何轉換?指針和引用之間如何轉換?

  1. 向上類型轉換:將派生類指針或引用轉換爲基類的指針或引用被稱爲向上類型轉換,向上類型轉換會自動進行,而且向上類型轉換是安全的。
  2. 向下類型轉換:基類指針或引用轉換爲派生類指針或引用被稱爲向下類型轉換,向下類型轉換不會自動進行,因爲一個基類對應幾個派生類,所以向下類型轉換時不知道對應哪個派生類,所以在向下類型轉換時必須加動態類型識別技術。RTTI技術,用dynamic_cast  進行向下類型轉換。

16. ​​​​​​ 組合與繼承優缺點?

一:繼承

繼承是Is a 的關係,比如說Student繼承Person,則說明Student is a Person。繼承的優點是子類可以重寫父類的方法來方便地實現對父類的擴展。

繼承的缺點有以下幾點:

父類的內部細節對子類是可見的。

:子類從父類繼承的方法在編譯時就確定下來了,所以無法在運行期間改變從父類繼承的方法的行爲。

:如果對父類的方法做了修改的話(比如增加了一個參數),則子類的方法必須做出相應的修改。所以說子類與父類是一種高耦合,違背了面向對象思想。

二:組合

組合也就是設計類的時候把要組合的類的對象加入到該類中作爲自己的成員變量

組合指的是,在一個類中以另外一個類的對象(也就是實例)作爲數據屬性,稱爲類的組合也就是說:一個類的屬性是另一個類的對象,就是組合。

組合的優點:

:當前對象只能通過所包含的那個對象去調用其方法,所以所包含的對象的內部細節對當前對象時不可見的。

:當前對象與包含的對象是一個低耦合關係,如果修改包含對象的類中代碼不需要修改當前對象類的代碼。

:當前對象可以在運行時動態的綁定所包含的對象。可以通過set方法給所包含對象賦值。

組合的缺點:①:容易產生過多的對象。②:爲了能組合多個對象,必須仔細對接口進行定義。

17. ​​​​​​​移動構造函數

  1. 我們用對象a初始化對象b,後對象a我們就不在使用了,但是對象a的空間還在呀(在析構之前),既然拷貝構造函數,實際上就是把a對象的內容複製一份到b中,那麼爲什麼我們不能直接使用a的空間呢?這樣就避免了新的空間的分配,大大降低了構造的成本。這就是移動構造函數設計的初衷;
  2. 拷貝構造函數中,對於指針,我們一定要採用深層複製,而移動構造函數中,對於指針,我們採用淺層複製。淺層複製之所以危險,是因爲兩個指針共同指向一片內存空間,若第一個指針將其釋放,另一個指針的指向就不合法了。所以我們只要避免第一個指針釋放空間就可以了。避免的方法就是將第一個指針(比如a->value)置爲NULL,這樣在調用析構函數的時候,由於有判斷是否爲NULL的語句,所以析構a的時候並不會回收a->value指向的空間;
  3. 移動構造函數的參數和拷貝構造函數不同,拷貝構造函數的參數是一個左值引用,但是移動構造函數的初值是一個右值引用。意味着,移動構造函數的參數是一個右值或者將亡值的引用。也就是說,只用用一個右值,或者將亡值初始化另一個對象的時候,纔會調用移動構造函數。而那個move語句,就是將一個左值變成一個將亡值。

18. ​​​​​​​C語言的編譯鏈接過程?

      源代碼-->預處理-->編譯-->優化-->彙編-->鏈接-->可執行文件

  1. 預處理讀取c源程序,對其中的僞指令(以#開頭的指令)和特殊符號進行處理。包括宏定義替換、條件編譯指令、頭文件包含指令、特殊符號。 預編譯程序所完成的基本上是對源程序的“替代”工作。經過此種替代,生成一個沒有宏定義、沒有條件編譯指令、沒有特殊符號的輸出文件。i預處理後的c文件,.ii預處理後的C++文件。
  2. 編譯階段編譯程序所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中間代碼表示或彙編代碼。.s文件
  3. 彙編過程彙編過程實際上指把彙編語言代碼翻譯成目標機器指令的過程。對於被翻譯系統處理的每一個C語言源程序,都將最終經過這一處理而得到相應的目標文件。目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。.o目標文件
  4. 鏈接階段鏈接程序的主要工作就是將有關的目標文件彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標文件成爲一個能夠誒操作系統裝入執行的統一整體。

19. ​​​​​​​函數指針?

  1. 什麼是函數指針? 
  • 函數指針指向的是特殊的數據類型,函數的類型是由其返回的數據類型和其參數列表共同決定的,而函數的名稱則不是其類型的一部分。
  • 一個具體函數的名字,如果後面不跟調用符號(即括號),則該名字就是該函數的指針(注意:大部分情況下,可以這麼認爲,但這種說法並不很嚴格)。

    2.函數指針的聲明方法

int (*pf)(const int&, const int&); (1)

上面的pf就是一個函數指針,指向所有返回類型爲int,並帶有兩個const int&參數的函數。注意*pf兩邊的括號是必須的,否則上面的定義就變成了:

int *pf(const int&, const int&); (2)

而這聲明瞭一個函數pf,其返回類型爲int *, 帶有兩個const int&參數。

     3.爲什麼有函數指針

函數與數據項相似,函數也有地址。我們希望在同一個函數中通過使用相同的形參在不同的時間使用產生不同的效果。

     4.一個函數名就是一個指針,它指向函數的代碼。一個函數地址是該函數的進入點,也就是調用函數的地址。函數的調用可以通過函數名,也可以通過指向函數的指針來調用。函數指針還允許將函數作爲變元傳遞給其他函數;

     5.兩種方法賦值:指針名 = 函數名;  指針名 = &函數名

20. ​​​​​​​c/c++的內存分配,詳細說一下棧、堆、靜態存儲區?

  1. 棧區(stack)由編譯器自動分配釋放,存放函數的參數值局部變量的值等其操作方式類似於數據結構中的棧。
  2.   堆區(heap) —  一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS(操作系統)回收。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表。  
  3. 全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束後由系統釋放。  
  4. 文字常量區  常量字符串就是放在這裏的。程序結束後由系統釋放。
  5. ​​​​​​​程序代碼區    —存放函數體的二進制代碼。

 

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