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

本人2020屆畢業,在學校搞機械,沒有任何編程經驗,沒有項目,全靠死記用背這些C++知識點,在秋招中拿到5,6個互聯網公司的開發崗位,雖然沒有大廠,但是已經比搞機械強多了,最高offer 26W。現將自己所學的東西總結如下,希望能幫助到更多的人。

1. 引用和指針的區別

  1. 指針是一個實體,需要分配內存空間。引用只是變量的別名,不需要分配內存空間。
  2. 引用在定義的時候必須進行初始化,並且不能夠改變。指針在定義的時候不一定要初始化,並且指向的空間可變。(注:引用的值不能爲NULL)
  3. 多級指針,但是沒有多級引用,只能有一級引用。
  4. 指針和引用的自增運算結果不一樣。(指針是指向下一個空間,引用時引用的變量值加1)
  5. sizeof 引用得到的是所指向的變量(對象)的大小,而sizeof 指針得到的是指針本身的大小。
  6. 引用訪問一個變量是直接訪問,而指針訪問一個變量是間接訪問。
  7. 使用指針前最好做類型檢查,防止野指針的出現;
  8. 引用底層是通過指針實現的;
  9. 作爲參數時也不同,傳指針的實質是傳值,傳遞的值是指針的地址;傳引用的實質是傳地址,傳遞的是變量的地址。

2. C++中的值傳遞   指針參數傳遞   和   引用參數傳遞

  1. 值傳遞是最常規的C語言傳參方式。
    形參是實參的拷貝,改變形參的值並不會影響外部實參的值。
    從被調用函數的角度來說,值傳遞是單向的(實參->形參),參數的值只能傳入,不能傳出。
    當函數內部需要修改參數,並且不希望這個改變影響調用者時,採用值傳遞。
  2. 指針參數傳遞本質上是值傳遞,它所傳遞的是一個地址值。值傳遞過程中,被調函數的形式參數作爲被調函數的局部變量處理,會在棧中開闢內存空間以存放由主調函數傳遞進來的實參值,從而形成了實參的一個副本(替身)。        值傳遞的特點是,被調函數對形式參數的任何操作都是作爲局部變量進行的,不會影響主調函數的實參變量的值(形參指針變了,實參指針不會變)。
  3. 引用參數傳遞過程中,被調函數的形式參數也作爲局部變量在棧中開闢了內存空間,但是這時存放的是由主調函數放進來的實參變量的地址被調函數對形參(本體)的任何操作都被處理成間接尋址,即通過棧中存放的地址訪問主調函數中的實參變量(根據別名找到主調函數中的本體)。因此,被調函數對形參的任何操作都會影響主調函數中的實參變量。

3.  形參與實參的區別?

  1. 形參變量只有在被調用時才分配內存單元,在調用結束時, 即刻釋放所分配的內存單元。因此,形參只有在函數內部有效。 函數調用結束返回主調函數後則不能再使用該形參變量。
  2. 實參可以是常量、變量、表達式、函數等, 無論實參是何種類型的量,在進行函數調用時,它們都必須具有確定的值, 以便把這些值傳送給形參。 因此應預先用賦值,輸入等辦法使實參獲得確定值,會產生一個臨時變量
  3. 實參和形參在數量上,類型上,順序上應嚴格一致, 否則會發生“類型不匹配”的錯誤。
  4. 函數調用中發生的數據傳送是單向的。 即只能把實參的值傳送給形參,而不能把形參的值反向地傳送給實參。 因此在函數調用過程中,形參的值發生改變,而實參中的值不會變化。
  5. 當形參和實參不是指針類型時,在該函數運行時,形參和實參是不同的變量,他們在內存中位於不同的位置,形參將實參的內容複製一份,在該函數運行結束的時候形參被釋放,而實參內容不會改變。

4. static的用法和作用?

  1. 先來介紹它的第一條也是最重要的一條:隱藏。(static函數,static變量均可)當同時編譯多個文件時,所有未加static前綴的全局變量和函數都具有全局可見性
  2. static的第二個作用是保持變量內容的持久。(static變量中的記憶功能和全局生存期)存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。共有兩種變量存儲在靜態存儲區:全局變量和static變量,只不過和全局變量比起來,static可以控制變量的可見範圍,說到底static還是用來隱藏的。
  3. static的第三個作用是默認初始化爲0(static變量)。

5. static的第四個作用:C++中的類成員聲明static 

  1. 函數體內static變量的作用範圍爲該函數體,不同於auto變量,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值;
  2. 在模塊內的static全局變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問;
  3. 在模塊內的static函數只可被這一模塊內的其它函數調用,這個函數的使用範圍被限制在聲明它的模塊內;
  4. 在類中的static成員變量屬於整個類所擁有,對類的所有對象只有一份拷貝;
  5. 在類中的static成員函數屬於整個類所擁有,這個函數不接收this指針,因而只能訪問類的static成員變量。

    類內:

  1. static類對象必須要在類外進行初始化,static修飾的變量先於對象存在,所以static修飾的變量要在類外初始化
  2. 由於static修飾的類成員屬於類,不屬於對象,因此static類成員函數是沒有this指針的,this指針是指向本對象的指針。正因爲沒有this指針,所以static類成員函數不能訪問非static的類成員,只能訪問 static修飾的類成員;
  3. static成員函數不能被virtual修飾,static成員不屬於任何對象或實例,所以加上virtual沒有任何實際意義;靜態成員函數沒有this指針,虛函數的實現是爲每一個對象分配一個vptr指針,而vptr是通過this指針調用的,所以不能爲virtual;虛函數的調用關係,this->vptr->ctable->virtual function

6. 靜態變量什麼時候初始化

  1. 初始化只有一次,但是可以多次賦值,在主程序之前,編譯器已經爲其分配好了內存。
  2. 靜態局部變量和全局變量一樣,數據都存放在全局區域,所以在主程序之前,編譯器已經爲其分配好了內存,但在C和C++中靜態局部變量的初始化節點又有點不太一樣。在C中,初始化發生在代碼執行之前,編譯階段分配好內存之後,就會進行初始化,所以我們看到在C語言中無法使用變量對靜態局部變量進行初始化,在程序運行結束,變量所處的全局內存會被全部回收。
  3. 而在C++中,初始化時在執行相關代碼時纔會進行初始化,主要是由於C++引入對象後,要進行初始化必須執行相應構造函數和析構函數,在構造函數或析構函數中經常會需要進行某些程序中需要進行的特定操作,並非簡單地分配內存。所以C++標準定爲全局或靜態對象是有首次用到時纔會進行構造,並通過atexit()來管理。在程序結束,按照構造順序反方向進行逐個析構。所以在C++中是可以使用變量對靜態局部變量進行初始化的。

7.  const 的作用

  1. 阻止一個變量被改變,可以使用const關鍵字。在定義該const變量時,通常需要對它進行初始化,因爲以後就沒有機會再去改變它了;
  2. 指針來說,可以指定指針本身爲const,也可以指定指針所指的數據爲const,或二者同時指定爲const;
  3. 在一個函數聲明中,const可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;
  4. 對於類的成員函數,若指定其爲const類型,則表明其是一個常函數,不能修改類的成員變量,類的常對象只能訪問類的常成員函數
  5. 對於類的成員函數,有時候必須指定其返回值爲const類型,以使得其返回值不爲“左值”。
  6. const成員函數可以訪問非const對象的非const數據成員、const數據成員,也可以訪問const對象內的所有數據成員;
  7. 非const成員函數可以訪問非const對象的非const數據成員、const數據成員,但不可以訪問const對象的任意數據成員;
  8. 一個沒有明確聲明爲const的成員函數被看作是將要修改對象中數據成員的函數,而且編譯器不允許它爲一個const對象所調用。因此const對象只能調用const成員函數。
  9. const類型變量可以通過類型轉換符const_cast將const類型轉換爲非const類型;
  10. const類型變量必須定義的時候進行初始化,因此也導致如果類的成員變量有const類型的變量,那麼該變量必須在類的初始化列表中進行初始化;
  11. 對於函數值傳遞的情況,因爲參數傳遞是通過複製實參創建一個臨時變量傳遞進函數的,函數內只能改變臨時變量,但無法改變實參。則這個時候無論加不加const對實參不會產生任何影響。但是在引用或指針傳遞函數調用中,因爲傳進去的是一個引用或指針,這樣函數內部可以改變引用或指針所指向的變量,這時const 纔是實實在在地保護了實參所指向的變量。因爲在編譯階段編譯器對調用函數的選擇是根據實參進行的,所以,只有引用傳遞和指針傳遞可以用是否加const來重載。一個擁有頂層const的形參無法和另一個沒有頂層const的形參區分開來。

8.const成員函數的理解和應用

       ①const Stock & Stock::topval   (②const Stock & s)   const

  1. 處const:確保返回的Stock對象在以後的使用中不能被修改  
  2. 處const:確保此方法不修改傳遞的參數 S
  3. 處const:保證此方法不修改調用它的對象,const對象只能調用const成員函數,不能調用非const函數

9. 指針和const的用法

        當const修飾指針時,由於const的位置不同,它的修飾對象會有所不同。

  1. int *const p2中const修飾p2的值,所以理解爲p2的值不可以改變,即p2只能指向固定的一個變量地址,但可以通過*p2讀寫這個變量的值。頂層指針表示指針本身是一個常量。
  2. int const *p1或者const int *p1兩種情況中const修飾*p1,所以理解爲*p1的值不可以改變,即不可以給*p1賦值改變p1指向變量的值,但可以通過給p賦值不同的地址改變這個指針指向。底層指針表示指針所指向的變量是一個常量。
  3. int const *const p;   *p指向的值和p指向的地址都不能改變。

10. ​​​​​​​mutable

  1. 如果需要在const成員方法修改一個成員變量的值,那麼需要將這個成員變量修飾爲mutable。即用mutable修飾的成員變量不受const成員方法的限制;
  2. 可以認爲mutable的變量是類的輔助狀態,但是隻是起到類的一些方面表述的功能,修改他的內容我們可以認爲對象的狀態本身並沒有改變的。實際上由於const_cast的存在,這個概念很多時候用處不是很到了。

11. extern用法

  1. extern修飾變量的聲明:    如果文件a.c需要引用b.c中變量int v,就可以在a.c中聲明extern int v,然後就可以引用變量v。
  2. extern修飾函數的聲明:    如果文件a.c需要引用b.c中的函數,比如在b.c中原型是int fun(int mu),那麼就可以在a.c中聲明extern int fun(int mu),然後就能使用fun來做任何事情。就像變量的聲明一樣,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的範圍中。
  3. extern修飾符可用於指示C或者C++函數的調用規範。           比如在C++中調用C庫函數,就需要在C++程序中用extern “C”聲明要引用的函數。這是給鏈接器用的,告訴鏈接器在鏈接的時候用C函數規範來鏈接。主要原因是C++和C程序編譯完成後在目標代碼中命名規則不同。

12. ​​​​​​​ 深拷貝與淺拷貝

  1. 淺複製 —-只是拷貝了基本類型的數據,而引用類型數據,複製後也是會發生引用,我們把這種拷貝叫做“(淺複製)淺拷貝”,換句話說,淺複製僅僅是指向被複制的內存地址,如果原地址中對象被改變了,那麼淺複製出來的對象也會相應改變。
  2. 深複製 —-在計算機中開闢了一塊新的內存地址用於存放複製的對象。

13. ​​​​​​​C++模板是什麼,底層怎麼實現的

  1. 編譯器並不是把函數模板處理成能夠處理任意類的函數;編譯器從函數模板通過具體類型產生不同的函數;編譯器會對函數模板進行兩次編譯:在聲明的地方對模板代碼本身進行編譯,在調用的地方對參數替換後的代碼進行編譯
  2. 這是因爲函數模板被實例化後才能成爲真正的函數,在使用函數模板的源文件中包含函數模板的頭文件,如果該頭文件中只有聲明,沒有定義,那編譯器無法實例化該模板,最終導致鏈接錯誤。

14. ​​​​​​​C語言struct和C++struct區別

  1. C語言中:struct是用戶自定義數據類型(UDT);C++中struct是抽象數據類型(ADT),支持成員函數的定義,(C++中的struct能繼承,能實現多態)。
  2. C中struct是沒有權限的設置的,且struct中只能是一些變量的集合體,可以封裝數據卻不可以隱藏數據,而且成員不可以是函數。
  3. C++中,struct的成員默認訪問說明符爲public(爲了與C兼容),class中的默認訪問限定符爲private,struct增加了訪問權限,且可以和類一樣有成員函數。
  4. struct作爲類的一種特例是用來自定義數據結構的。一個結構標記聲明後,在C中必須在結構標記前加上struct,才能做結構類型名。

15. ​​​​​​​ 虛函數可以聲明爲inline嗎

  1. 虛函數用於實現運行時的多態,或者稱爲晚綁定或動態綁定。而內聯函數用於提高效率。內聯函數的原理是,在編譯期間,對調用內聯函數的地方的代碼替換成函數代碼。內聯函數對於程序中需要頻繁使用和調用的小函數非常有用。
  2. 虛函數要求在運行時進行類型確定,而內斂函數要求在編譯期完成相關的函數替換

16. ​​​​​​​類成員初始化方構造函數的執行順序 ?爲什麼用成員初始化列表會快一些?

  1. 賦值初始化,通過在函數體內進行賦值初始化;列表初始化,在冒號後使用初始化列表進行初始化。                                           這兩種方式的主要區別在於:                                                                                                                                                    函數體中初始化:是在所有的數據成員被分配內存空間後才進行的。                                                                                        列表初始化:是給數據成員分配內存空間時就進行初始化,就是說分配一個數據成員只要冒號後有此數據成員的賦值表達式(此表達式必須是括號賦值表達式),那麼分配了內存空間後在進入函數體之前給數據成員賦值,就是說初始化這個數據成員此時函數體還未執行。
  2. 一個派生類構造函數的執行順序如下:                                                                                                                                           虛擬基類的構造函數(多個虛擬基類則按照繼承的順序執行構造函數)。                                                                                 基類的構造函數(多個普通基類也按照繼承的順序執行構造函數)。                                                                                       類類型的成員對象的構造函數(按照初始化順序)                                                                                                                     派生類自己的構造函數
  3. 方法一是在構造函數當中做賦值的操作,而方法二是做純粹的初始化操作。我們都知道,C++的賦值操作是會產生臨時對象的。臨時對象的出現會降低程序的效率。

17. 成員列表初始化

  1. 必須使用成員初始化的四種情況:                                                                                                                                                      當初始化一個引用成員時;                                                                                                                                                        當初始化一個常量成員時;                                                                                                                                                        當調用一個基類的構造函數,而它擁有一組參數時;                                                                                                                當調用一個成員類的構造函數,而它擁有一組參數時;
  2. 成員初始化列表做了什麼:                                                                                                                                                               編譯器會一一操作初始化列表,以適當的順序在構造函數之內安插初始化操作,並且在任何顯示用戶代碼之前;                   list中的項目順序是由類中的成員聲明順序決定的,不是由初始化列表的順序決定的;

18. ​​​​​​​​​​​​​​ 構造函數爲什麼不能爲虛函數?析構函數爲什麼要虛函數?

  1. 構造函數本來就是爲了明確初始化對象成員才產生的,然而virtual function主要是爲了再不完全瞭解細節的情況下也能正確處理對象。另外,virtual函數是在不同類型的對象產生不同的動作,現在對象還沒有產生,如何使用virtual函數來完成你想完成的動作。
  2. 直接的講,C++中基類採用virtual虛析構函數是爲了防止內存泄漏。具體地說,如果派生類中申請了內存空間,並在其析構函數中對這些內存空間進行釋放。假設基類中採用的是非虛析構函數,當刪除基類指針指向的派生類對象就不會觸發動態綁定,因而只會調用基類的析構函數,而不會調用派生類的析構函數。那麼在這種情況下,派生類中申請的空間就得不到釋放從而產生內存泄漏。所以,爲了防止這種情況的發生,C++中基類的析構函數應採用virtual虛析構函數。

19. 析構函數的作用,如何起作用?

  1. 構造函數只是起初始化值的作用,但實例化一個對象的時候,可以通過實例去傳遞參數,從主函數傳遞到其他的函數裏面,這樣就使其他的函數裏面有值了。規則,只要你一實例化對象,系統自動回調用一個構造函數,就是你不寫,編譯器也自動調用一次。
  2. 析構函數與構造函數的作用相反,用於撤銷對象的一些特殊任務處理,可以是釋放對象分配的內存空間;特點:析構函數與構造函數同名,但該函數前面加~。 析構函數沒有參數,也沒有返回值,而且不能重載,在一個類中只能有一個析構函數。 當撤銷對象時,編譯器也會自動調用析構函數。 每一個類必須有一個析構函數,用戶可以自定義析構函數,也可以是編譯器自動生成默認的析構函數。一般析構函數定義爲類的公有成員。

20. ​​​​​​​構造函數和析構函數可以調用虛函數嗎,爲什麼

  1. 在C++中,提倡不在構造函數和析構函數中調用虛函數
  2. 構造函數和析構函數調用虛函數時都不使用動態聯編,如果在構造函數或析構函數中調用虛函數,則運行的是爲構造函數或析構函數自身類型定義的版本;
  3. 因爲父類對象會在子類之前進行構造,此時子類部分的數據成員還未初始化,因此調用子類的虛函數時不安全的,故而C++不會進行動態聯編;
  4. 析構函數是用來銷燬一個對象的,在銷燬一個對象時,先調用子類的析構函數,然後再調用基類的析構函數。所以在調用基類的析構函數時,派生類對象的數據成員已經銷燬,這個時候再調用子類的虛函數沒有任何意義。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章