1. 程式人生 > >C++概念總結(筆試、面試題總結)

C++概念總結(筆試、面試題總結)

1. C 和 C++ 的區別

a. C 是面向過程、結構化的語言; C++ 是面向物件、模組化的語言,主要特點是封裝、繼承、多型。

b. 動態管理記憶體的方法不同,C 中用 malloc / free 分配、釋放記憶體; C++中還有 new / delete。

c. C++ 中有引用, C中沒有。

d. C++ 支援函式過載,C 中不支援。

 

2. C++ 不是型別安全的,兩個不同型別的指標之間可以強制轉換 (reinterpret cast),C#是型別安全的。

 

3. malloc / free 和 new / delete 區別

malloc / free 是標準庫函式,且malloc對開闢的記憶體空間大小嚴格要求,無法滿足動態物件的記憶體分配回收要求;

new / delete 是C++運算子,可以呼叫構造 / 解構函式,完成記憶體的動態分配和回收。

 

4. delete 和 delete[] 的區別

與 new 和 new[] 對應。

delete只調用一次解構函式(刪除一個物件指標);

detele會呼叫每一個成員的解構函式,然後呼叫operate delete釋放空間,用於刪除陣列。

 

5.建構函式和解構函式 

解構函式的呼叫次序: 先呼叫派生類的解構函式,再呼叫基類的解構函式,與建構函式剛好相反;

基類的解構函式不是虛擬函式產生的問題

a. 編譯器實施靜態繫結,在刪除指向派生類的指標時只會呼叫基類的建構函式,派生類的解構函式會無法呼叫,析構不完全導致記憶體洩漏;

建構函式不是虛擬函式原因

a. 建立一個物件的時候需要確定其型別,而虛擬函式是在執行的時候確定其型別的;而構造一個物件的時候,由於物件還沒有建立成功,編譯器無法知道物件的型別;

b. 虛擬函式的呼叫需要虛擬函式表指標,而該指標存放在物件的記憶體空間中;若建構函式申明為虛擬函式,由於物件還沒有建立,沒有為其分配記憶體空間,無法儲存虛擬函式的指標;

 

6.  “多型”

"一種介面,多個方法"。同一操作作用於不同的物件,可以有不同的解釋,產生不同的執行結果。在執行時,可以通過指向基類的指標,來呼叫實現派生類中的方法。

C++ 實現多型的方法:虛擬函式

抽象類,覆蓋,模板(過載和多型無關)。

 

7. “虛擬函式”

基類中冠以關鍵字"virtual"的成員函式,提供一種介面,允許在派生類中對基類的虛擬函式重新定義。

 

8. “純虛擬函式”

在基類中僅為其派生類保留一個函式名字,作為介面存在,不具備函式的功能,一般不能被直接呼叫。

從基類繼承來的純虛擬函式,在派生類中仍然是虛擬函式。

-“抽象類”:若一個類至少有一個純虛擬函式,則這個類就叫抽象類,抽象類用於派生其他類,不可直接建立例項。

 

9. 虛擬函式的實現

a. 每個含虛擬函式的類都有一個與之對應的虛擬函式表,其內部存放著該類所有虛擬函式對應的函式指標(地址);

b. 類的例項不包含虛擬函式表,只有虛指標;

c. 派生類會生成一個相容基類的虛擬函式表;

 

10.引用和指標的區別

引用:給變數的別名

指標:指向變數的地址

a.引用不佔用記憶體單元,也無法給其分配記憶體空間;指標佔用一定的記憶體單元;

b.不存在指向空值的引用;但存在指向空值的指標;

b.引用初始化後不能再改變;指標初始化後可以改變其指向的地址;

d.引用只有一級;指標可以有多級;

e.引用傳遞引數相當於對實參直接操作,不需要額外分配記憶體;指標傳遞引數屬於值傳遞,指標本身的值不會修改,需要從棧中分配記憶體給引數。

 

11. 陣列和指標的區別

a. 陣列只能在靜態儲存區 / 棧中被建立,而指標可以隨時指向任意型別的記憶體塊;

b. 用sizeof可以計算出陣列的容量(位元組數),卻只能計算出指標自身所佔記憶體大小;

 

12. struct /class 的區別

不申明的話struct預設的變數是public的;class預設是private的

 

13. struct / union 的區別

a. union所有成員共用一段記憶體,其大小等於其中最大元素所佔記憶體;struct每個成員各自佔用一段記憶體,其大小等於各個成員佔用記憶體大小之和;

b. 對union的成員賦值會自動覆蓋其其他元素的值;對struct成員賦值不會影響其他成員的值;

 

14. bool, int, float, 指標型別的變數分別與“零”比較

bool: if(a)  /  if(!a)

int: if (0 == a)

float: const EXPRESSION EXP = 0.000001;

         if ( a < EXP && a >- EXP )

pointer:  if ( a == NULL) / if ( a != NULL)

 

15. 常引用: const 型別識別符號 &引用名 = 目標變數名;

既利用引用提高了效率,又保護了傳遞給函式的資料不被函式改變。

int a = 5;

const int &ra = a;

a = 3;  // right

ra = 3;  // wrong,不可以給常引用賦值

 

16. 引用作為物件的返回值好處注意事項

好處:在記憶體中不產生被返回值的副本

注意:

a. 不可以返回區域性變數的引用(生存週期和區域性變數一致,區域性變數被回收後其引用也不存在了)

b. 不可以返回函式內部new分配的記憶體的引用(若返回值僅作為臨時變量出現,則new分配的記憶體無法回收)

c. 可以返回類成員的引用,但最好加上const

 

17. overload / override 的區別

overload: 過載,允許存在多個同名函式,其各自的引數列表不同(引數個數或型別);

                編譯器根據函式不同的引數表對同名函式的名稱做修飾,就成了不同的函式。

override: 覆蓋,子類重新定義父類虛擬函式的方法;

               當子類重新定義了父類的虛擬函式後,父類指標根據賦給他的不同的子類指標,動態呼叫屬於子類的虛擬函式。

過載屬於“早繫結”,編譯期間就已經確定函式的地址了。

覆蓋輸入“晚繫結”,編譯期間無法確定呼叫子類的虛擬函式的地址。

 

18. override 和 overwrite 的區別

override:覆蓋,派生類覆蓋基類的虛擬函式,實現介面重用;

                 特徵是基類和派生類中的函式名、引數相同,基類必須有virtual關鍵字;

overwrite:重寫,派生類遮蔽了與其同名的基類函式;

                  特徵是基類和派生類中的函式名相同,引數不同或者基類中沒有virtual關鍵字;

 

19. 只能使用引數初始化列表的情況:當類中含有const、reference成員變數時,以及基類的建構函式。

 

20. main函式執行前會執行的程式碼:全域性物件的建構函式;

 

21.記憶體分配的幾種方式和區別

a. 靜態儲存區分配:全域性變數、static靜態變數

    記憶體在編譯期間就已經分配好的,並且在整個程式執行期間都存在;

b. 從棧上建立:函式執行時,其值傳遞的引數和區域性變數從棧上建立,函式執行後被自動釋放(編譯器自動執行),棧記憶體分配      運算內置於處理器指令集

c. 從堆上分配:又叫動態記憶體分配,程式執行時用malloc / new 申請記憶體,使用free / delete釋放記憶體,動態記憶體的生命週期由程      序員決定;頻繁分配釋放會使記憶體不連續、產生碎片;

棧的生長空間向下,地址越來越小;堆的生長空間向上,地址越來越大;

 

22. const 和 #define 的區別

const作用:定義變數、修飾函式引數、修飾函式返回值;被修飾的物件都被強制保護,防止意外更改,提升程式魯棒性;

const修飾變數(成員變數),則為常量不可以修改;const修飾成員函式,則該函式不可以修改類中資料成員,不會呼叫其他非const成員函式;

a. const常量的有資料型別,#define沒有;編譯器會對const修飾的常量進行型別安全檢查,對巨集常量只進行字元替換;

b. #define 可以定義簡單的函式,const不可以;const常量可以被除錯,巨集常量不可以;

 

23. int(*s[10])(int) 表示函式指標陣列,每個指標指向一個int func(int params) 的函式。

 

24. char str1[] = "abc";   char str2[] = "abc";

      str1 == str2 : false  ==> 分別指向各自的記憶體;

      char *str3 = "abc";   char str4* = "abc";

      str3 == str4 : true    ==> 指標變數,指向相同的常量區;

 

25. 將程式跳轉至指定的記憶體執行:

(void(*)())0x100000: 將0x100000強制轉換為函式指標;

*((void(*)())0x100000)(): 取其值;

typedef void(*)() voidFuncPtr;

*((voidFuncPtr)0x100000);

 

26. 全域性變數、區域性變數的區別及如何實現:

a. 生命週期不同:全域性變數的生命週期和主程式一致,區域性變數在區域性函式內部、甚至再區域性迴圈體內部,推出迴圈就不存在了;

b. 使用方式不同:通過申明後,全域性變數可以再程式的各個部分被使用;區域性變數只能被區域性使用;

c. 分配記憶體的位置不同:全域性變數分配在靜態儲存區的全域性資料段上;區域性變數分配在堆疊中;作業系統和編譯器通過檢查記憶體分配的位置區分全域性變數和區域性變數;

 

27. STL: Standard Template Library 包括容器和演算法

a. 容器:存放資料的地方,分為序列式容器和關聯式容器;

    序列式容器:其內部元素不一定有序,但可以被排序;e.g   vector / list / queue / heap / priority-queue;

    關聯式容器:內部為平衡二叉樹,每個元素都有鍵值和值(key/value) e.g   map / set / hashtable / hash_set;

b. 演算法:排序 / 複製 及各個容器特定的演算法;

c. STL的迭代器提供了一種方法,使其可以按順序訪問容器元素又不會暴露其內部的結構;

 

28. static作用 / 與const的區別

a. 函式體內:static修飾的區域性變數的作用範圍為該函式體,其記憶體只被分配一次,因此其值在下次呼叫的時候維持了上一次的值;

b. 模組內:static修飾全域性變數 / 全域性函式,可以被模組內所有函式訪問,而不可以被模組外的其他函式訪問,適用範圍限制在起申明的模組內部;

c. 類內:修飾成員變數,表明該變數為整個類所有,對類的所有物件只有一份拷貝;

d. 類內:修飾成員函式,表明該函式為整個類所有,不接受this指標,只能訪問類中的static成員變數;

const 強調值不可修改;static強調對所有類的

 

29. STL map / set 原理

底層通過紅黑樹實現,是一種特殊的二叉查詢樹,接近平衡;

a. 每個節點是黑色或者紅色的;

b. 根節點為黑色,葉子節點為黑色(NULL);

c. 若一個節點為紅色,則其全部子節點為黑色;

d. 從一個節點到該節點的子孫節點的所有路徑包含數目相同的黑色節點; 

 

30. “記憶體洩漏”, 面對記憶體洩漏和指標越界的措施方法:

動態分配所開闢的空間,在使用完畢後未手動釋放,導致一直佔用該記憶體,即為記憶體洩露;

原因:

a. 記憶體的建構函式和解構函式中的new和delete沒有配套;

b. 釋放物件陣列時沒有使用delete[], 而是使用了delete;

方法:malloc / free 要配套,對指標賦值的時候應該注意指標是否需要釋放;使用的時候記得指標的長度,防止越界;

 

31. 定義和申明的區別

申明是告訴編譯器變數的型別和名稱,不會為變數分配記憶體空間;

定義需要分配記憶體空間,同一個變數可以被申明多次,但是隻能被定義一次;

 

32. C++檔案編譯與執行的四個階段:

a. 預處理:根據檔案中的預處理指令來修改原始檔中的內容;

b. 編譯:編譯成彙編程式碼;

c. 彙編:把彙編程式碼翻譯成目標機器指令;

d. 連結:連線目的碼生成可執行程式;

 

33. STL中unordered map 和 map 的區別:

map 是STL的一個關聯容器,提供鍵值對的資料管理,底層通過紅黑樹實現,實際上是二叉排序樹和非嚴格的二叉平衡樹,所以map內部是有序的;

unordered map 和 map 類似,也是儲存鍵值對,可以通過key快速索引到value,但unordered map不會根據key進行排序;unordered map 是一個冗餘的雜湊表,儲存時根據key的hash值判斷元素是否相同;

 

34. C++ 記憶體管理

C++記憶體被分為五個區:棧、堆、自由儲存區、全域性 / 靜態儲存區、常量區;

棧:存放函式引數和區域性變數,編譯器自動分配和回收;

堆:new關鍵字動態分配的記憶體,有程式設計師手動進行釋放,否則程式結束後系統自動釋放;

自由儲存區:有malloc分配的記憶體,和堆相似,由對應的free釋放;

全域性/靜態儲存區:存放全域性變數和靜態變數;

常量區:存放常量,不允許被修改;

 

35. 靜態繫結和動態繫結

是C++多型的一種特性

靜態型別:物件在申明時採用的資料型別,在編譯時確定;

動態型別:當前物件所指向的型別,在執行期間決定,物件的動態型別可變,靜態型別不可變;

靜態繫結:繫結的物件是靜態物件,函式依賴於物件的靜態型別,在編譯時確定;

動態繫結:繫結的物件是動態物件,函式依賴於物件的動態型別,在執行時確定;

虛擬函式使用動態繫結;

 

36. 引用是否能實現動態繫結?

可以。因為引用(或指標)既可以指向基類物件又可以指向派生類物件;用引用呼叫的虛擬函式在執行時確定,是引用(或指標)所指的物件的實際型別所定義的;

 

37. 深拷貝和淺拷貝

如果一個類擁有資源,當其發生複製過程時,如果資源重新分配了就是深拷貝;反之就是淺拷貝;

 

38. 呼叫拷貝建構函式的情況(三種)

a. 用類的一個物件去初始化類的另一個物件時;

b. 當函式的引數是類的物件的時候,即值傳遞的時候;如果是引用傳遞則不會呼叫;

c. 當函式的返回值是類的物件或引用的時候;

系統自動生成建構函式的情況:未定義類的普通建構函式; 拷貝建構函式;

 

39. C++四種強制轉換

型別轉換機制分為隱式型別轉換和顯示型別轉換(強制型別轉換);

a. static_cast:編譯時期的靜態型別檢查 static_cast<type>(expression)

b. dynamic_cast:基類指標/引用安全向下轉換到派生類指標/引用,提供執行時的型別檢查;不能轉換則返回NULL,基類中必須有虛擬函式,保證多型; dynamic_cast<source>(expression)

c. const_cast:去除const屬性,轉換為volatile屬性’

d. reinterpret_cast:為了將一種資料型別轉換為另一種資料型別;

 

40. 除錯程式的方法

Windows下使用VS的debug功能,Linux下使用gdb,設定斷點和監視器;

安裝一些除錯外掛,例如VS裡的ImageWatch;

 

41. extern "C" 作用:為了能夠正確實現C++呼叫C語言程式碼;

 

42. typedef 和 #define 區別

#define 是預處理命令,預處理時進行簡單的替換,不做型別檢查;

typedef是編譯時處理的,在自己的作用域內給已經存在的型別一個別名;

 

43. volatile的作用:解決變數在“共享”環境下容易出現讀取錯誤的問題;

 

44. 野指標及其成因

野指標不是NULL指標,是未初始化或未清零的指標,它指向的記憶體地址不是程式設計師所期望的,可能指向了受限制的記憶體;

成因:

a. 指標變數未被初始化;

b. 指標指向的記憶體被釋放了,但是指標沒有置為NULL;

c. 指標超過了變數的作用範圍;

 

45. 執行緒安全和執行緒不安全

執行緒安全:多執行緒訪問時,採用了加鎖機制,當一個執行緒訪問該類的某個資料時進行保護,其他執行緒不能進行訪問知道該程序訪問結束;

執行緒不安全:不提供資料訪問保護,有可能多個執行緒先後更改資料所得到的資料就是髒資料;

 

46. 棧溢位的原因和方法

原因:

a. 函式呼叫層次過深,每呼叫一次函式引數、區域性變數資訊時就壓一次棧;

b. 區域性變數體積過大;

解決方法:

a. 增加棧記憶體的數目;

b. 使用堆記憶體;如把陣列定義改為指標,動態申請記憶體;也可以把區域性變數變成全域性變數或靜態變數(statci)

 

47. C++11 新特性

auto:自動型別推導;

decltype:從一個變數或表示式中得到型別;

nullptr:解決原來C++中NULL二義性問題,因為NULL實際代表0;

lambda表示式:定義建立匿名函式;

擴充套件了STL,併入了TR1(Technical Report 1)程式庫;

 

48. lambda表示式

 sort(v.begin(), v.end(), [](int a, int b) -> bool { return a < b; }); 
[capture list] (params list) mutable exception-> return type { function body }
  1. capture list:捕獲外部變數列表
  2. params list:形參列表
  3. mutable指示符:用來說用是否可以修改捕獲的變數
  4. exception:異常設定
  5. return type:返回型別
  6. function body:函式體

 

49. vector 和 list  區別

vector和陣列都擁有連續記憶體空間,vector 記憶體不夠時會以2倍的的記憶體大小重新申請一塊更大的記憶體,將原資料拷貝過去並釋放舊空間;很好的支援隨機存取,vector<int>::iterator 支援 + / += / < 等操作符;

list 是由雙向連結串列實現,記憶體空間不連續,只能通過指標訪問,隨機存取效率低,但插入刪除效率高;

 

50. C語言函式呼叫過程

a. 從棧空間分配儲存空間;

b. 從實參的儲存空間複製值到形參棧空間;

c. 運算;

 

51. 友元類和友元函式 friend

友元提供了不同類的成員函式、類的成員函式和一般函式之間的資料共享機制;

通過友元一個不同的函式或另一個不同類的成員函式可以訪問類中私有和保護成員;

提高了程式執行效率,同時也破化了類的封裝性和資料的隱藏性,導致程式的可維護性變差;

友元函式:可以訪問類的私有成員的非成員函式,是定義在類外的普通函式,不屬於任何類,但需要在類的定義中申明;

友元類:友元類的所有成員函式都是另一個類的友元函式,都可以訪問另一個類的私有成員和保護成員;

注意:

a. 友元關係不可以被繼承;

b. 友元關係是單向的;

c. 友元關係不具有傳遞性;

 

52. C++執行緒的幾種鎖機制

a. 互斥鎖:用於控制多個執行緒互斥訪問共享資源;

b. 條件鎖:條件變數,執行緒因為某個條件滿足時可以使用條件變數使該程式處於阻塞狀態;

c. 自旋鎖:發生阻塞時,讓CPU不斷迴圈請求這個鎖;

d. 讀寫鎖:“讀者/寫者”問題的拓展;

e. 遞迴鎖