[C++]C++面試知識總結


1.程序運行知識

1.1 內存布局和分配方式

C程序的內存布局如下:

 

  • 靜態存儲區:存儲全局變量和static變量,通常在程序編譯期間已經分配好了。
    • BSS段:存放未初始化的static變量和全局變量
    • Data段:存放初始化過的static變量和全局變量
    • Text段:存儲程序的二進制代碼,程序代碼區。  
  • 堆:程序運行時通過malloc申請的內存區存放在堆中,需要使用free來釋放該內存空間,生存期在malloc和free之間。
  • 棧:執行函數時,函數的局部變量存儲在棧中,執行結束后自動釋放該內存區域,棧內存分配運算內置與處理器指令集中。

C++程序的內存布局與C程序布局類似,區別是C++不再區分全局變量和靜態變量是否已經初始化,全部存儲在靜態存儲區;另外堆中存放new/delete申請釋放的資源,而malloc和free申請的資源存放在自由存儲區。

1.2 內存溢出原因

  • 棧溢出:越界訪問造成,例如局部變量數組越界訪問或者函數內局部變量使用過多,超出了操作系統為該進程分配的棧的大小,還有遞歸函數層次過多超過了棧大小。
  • 堆溢出:程序申請了資源但忘記釋放該資源,造成內存泄露,累積泄露內存過多會造成內存溢出。

1.3 內存泄露和檢測

  • C++內存泄漏檢測內存泄露是指程序中動態分配了內存,但是在程序結束時沒有釋放這部分內存,從而造成那一部分內存不可用的情況。

  •  動態內存泄露檢測:檢查new/delete的資源是否正確釋放,檢查程序運行期間內存是否一直在增長,使用內存檢測工具來檢測泄露情況。

1.4  程序生成過程

  • 預處理階段:根據文件中的預處理指令來修改源文件的內容。如#include指令,作用是把頭文件的內容添加到.cpp文件中。
  • 編譯階段:將其翻譯成等價的中間代碼或匯編代碼。
  • 匯編階段:把匯編語言翻譯成目標機器指令。
  • 鏈接階段:例如,某個源文件中的函數可能引用了另一個源文件中定義的某個函數;在程序中可能調用了某個庫文件中的函數。

1.5 預編譯

  • 定義:預編譯又稱為預處理 , 是做些代碼文本的替換工作。處理 # 開頭的指令 , 比如拷貝 #include 包含的文件代碼, #define 宏定義的替換 , 條件編譯等。
  • 功能:宏定義,文件包含,條件編譯。

1.6 頭文件的作用

  • 保存程序的聲明。
  • 通過頭文件可以來調用庫函數。因為有些代碼不能向用戶公布,只要向用戶提供頭文件和二進制的庫即可。用戶只需要按照頭文件中的接口聲明來調用庫功能,編譯器會從庫中提取相應的代碼。
  • 如果某個接口被實現或被使用時,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤,這一簡單的規則能大大減輕程序員調試、改錯的負擔。

2. 基本數據類型和用法知識

2.1 struct與class的區別

  • 默認情況下,struct的成員變量是public的,而class是private的。
  • struct保證成員按照聲明順序在內存中存儲,而class不能保證。
  • 默認情況下,struct是public繼承,而class是private繼承。

2.2 struct與union的區別

  • struct中各個成員變量是獨立的,union中的成員變量共享同一片內存區域,內存區域長度由成員變量中長度最大的一個決定。
  • struct不能保證分配的是連續內存,但union分配的是連續內存。

2.3 const和define的用途以及區別

  • const用途:用來定義常量、修飾函數參數、修飾函數返回值,可以避免被修改,提高程序的健壯性。
  • define用途:是宏定義,在編譯的時候會進行替換,這樣做的話可以避免沒有意義的數字或字符串,便於程序的閱讀。
  • 區別:const定義的數據有數據類型,而宏常量沒有數據類型。編譯器可以對const常量進行類型檢查。而對宏定義只進行字符替換,沒有類型安全檢查,所以字符替換時可能出錯。

2.4 枚舉和define的區別

  • #define 是在預編譯階段進行簡單替換。枚舉常量則是在編譯的時候確定其值。
  • 一般在編譯器里,可以調試枚舉常量,但是不能調試宏常量。
  • 枚舉可以一次定義大量相關的常量,而#define 宏一次只能定義一個。

2.5 內聯函數和宏的區別

  • 內聯函數在編譯時展開,宏在預編譯時展開。
  • 在編譯的時候內聯函數可以直接被嵌入到目標代碼中,而宏只是一個簡單的文本替換,內聯函數可以完成諸如類型檢測、語句是否正確等編譯功能,宏就不具備這樣的功能。inline函數是函數,宏不是函數。   

2.6 new/delete和malloc/free的區別

  • new/delete用調用構造函數來實例化對象和調用析構函數釋放對象申請的資源。
  • malloc/free用來申請內存和釋放內存,但是申請和釋放的對象只能是內部數據類型。
  • malloc與free是C++/C語言的標准庫函數,new/delete是C++的運算符。

2.7 delete和delete[]的區別

  • delete只會調用一次析構函數,delete[]會調用每一個成員的析構函數。
  • delete與new配套,delete []與new []配套,用new分配的內存用delete刪除用new[]分配的內存用delete[]刪除。

2.8 指針和引用的概念和區別

  • 指針指向一塊內存,指針保存的是內存的地址;引用是變量的別名,本質是引用該變量的地址。解引用是取指針指向的地址的內容,取地址是獲得變量在內存中的地址。

  • 引用在創建的同時必須初始化,保證引用的對象是有效的,所以不存在NULL引用。
  • 指針在定義的時候不必初始化,所以,指針則可以是NULL,可以在定義后面的任何地方重新賦值。
  • 引用一旦被初始化為指向一個對象,它就不能被改變為另一個對象的引用。
  • 指針在任何時候都可以改變為指向另一個對象。
  • 引用的創建和銷毀並不會調用類的拷貝構造函數。
  • 因為不存在空引用,並且引用一旦被初始化為指向一個對象,它就不能被改變為另一個對象的引用,所以比指針安全。由於const 指針仍然存在空指針,並且有可能產生野指針,所以還是不安全。
  • 程序會給指針變量分配內存區域,而引用不需要分配內存區域。
  • 返回引用時,在內存中不產生被返回值的副本。

2.9 memset,memcpy和strcpy的區別

  • memset用來對一段內存空間全部設置為某個字符。
  • memcpy是內存拷貝函數,可以拷貝任何數據類型的對象。
  • strcpy只能拷貝字符串,遇到’\0′結束拷貝。

2.10 指針在16位機,32位機,64位機中分別占多大內存

  • 16位機:2字節。
  • 32位機:4字節。
  • 64位機:8字節。

2.11 字符指針,浮點數指針和函數指針哪個占用內存更大

  • 一樣大,指針的占用內存大小只和機器相關。

2.12 如何引用一個全局變量

  • 在同一文件中:直接引用。
  • 咋不同文件中:直接引用頭文件;使用extern聲明變量。

2.13 變量聲明和定義的區別

  • 變量聲明:告訴編譯器有某個類型的變量,但不會為其分配內存。
  • 變量定義:位該類型的變量分配內存。

2.14 野指針,未初始化指針和空指針的區別

  • 野指針:指向一個已刪除的對象或無意義地址的指針。
    • 原因:指針變量沒有被初始化,或者指針p被free或者delete之后,沒有置為NULL。  
  • 空指針:空指針表示“未分配” 或者“尚未指向任何地方” 的指針。
  • 區別:空指針可以確保不指向任何對象或函數; 而未野指針或初始化指針則可能指向任何地方。

2.15 常量指針和指針常量的區別

  • 常量指針:是一個指向常量的指針。可以防止對指針誤操作而修改該常量。
  • 指針常量:是一個常量,且是一個指針。指針常量不能修改指針所指向的地址,一旦初始化,地址就固定了,不能對它進行移動操作。但是指針常量的內容是可以改變。

2.16 指針函數和函數指針的區別

  • 指針函數:返回值是指針的函數。
  • 函數指針:一個指向函數的指針。函數名被括號括起來,並且加有指針符號。

2.17 const char*, char const*, char* const的區別

  • char * const cp;//cp是常指針,指向char類型的數據
  • const char * cp;//cp是char類型的指針,指向const char
  • char const * p;//C++里面沒有const*的運算符,所以const屬於前面的類型。

2.18 static全局變量與普通的全局變量的區別

  • 全局變量在整個工程文件內都有效。
  • 靜態全局變量只在定義它的文件內有效。
  • 全局變量和靜態變量如果沒有手工初始化,則由編譯器初始化為0。

2.19 static局部變量和普通局部變量的區別

  • 靜態局部變量只在定義它的函數內有效,只是程序僅分配一次內存,函數返回后,該變量不會消失,直到程序運行結束后才釋放。
  • 普通局部變量在定義它的函數內有效,這個函數返回會后失效。
  • static局部變量會自動初始化,而局部變量不會。

2.20 sizeof用在不同對象上的區別

  • sizeof是C語言的一種單目操作符,並不是函數。sizeof以字節的形式返回操作數的大小。
  • 若操作數具有類型char、unsigned char或signed char,其結果等於1。
  • 當操作數是指針時,sizeof依賴於系統的位數。
  • 當操作數具有數組類型時,其結果是數組的總字節數。
  • 聯合類型操作數的sizeof是其最大字節成員的字節數。
  • 結構類型操作數的sizeof是這種類型對象的總字節數。
  • 如果操作數是函數中的數組形參或函數類型的形參,sizeof給出其指針的大小。

2.21 sizeof與strlen的區別

  • sizeof是運算符,計算數據所占的內存空間;strlen()是一個函數,計算字符數組的字符數。
  • sizeof可以用類型作參數;strlen()只能用char*作參數,必須是以‘/0’結束。
  • 數組做sizeof的參數不退化,傳遞給strlen就退化為指針了。
  • sizeof操作符的結果類型是size_t,它在頭文件中typedef為unsigned int類型。該類型保證能容納實現建立的最大對象的字節大小。

2.22 空指針指向了內存的什么地方

  • 標准並沒有對空指針指向內存中的什么地方這一個問題作出規定,一般取決於系統的實現。我們常見的空指針一般指向 0 地址,即空指針的內部用全 0 來表示。空指針的“邏輯地址”一定是0,對於空指針的地址,操作系統是特殊處理的。並非空指針指向一個0地址的物理地址。在實際編程中不需要了解在我們的系統上空指針到底是一個 0指針還是非0地址,我們只需要了解一個指針是否是空指針就可以了——編譯器會自動實現其中的轉換,為我們屏蔽其中的實現細節。

2.23 有一個char * 型指針剛好指向一些int 型變量, 我想跳過它們。 為什么((int *)p)++; 不行?

  • 類型轉換的實質“把這些二進制位看作另一種類型, 並作相應的對待”。
  • ((int *)p)++是一個轉換操作符, 根據定義它只能生成一個右值(rvalue)。
  • 而右值既不能賦值, 也不能用++ 自增。
  • 正確的做法:p = (char *)((int *)p + 1);。

3. 面向對象知識

3.1 面向對象三個基本特點

  • 封裝:將客觀事物抽象成類,每個類對自身的數據和方法。封裝可以使得代碼模塊化,目的是為了代碼重用。
  • 繼承:子類繼承父類的方法和屬性,繼承可以擴展已存在的代碼,目的是為了代碼重用。
  • 多態:通過繼承同一個基類,產生了相關的不同的派生類,與基類中同名的成員函數在不同的派生類中會有不同的實現,也就是說:一個接口、多種方法。

3.2 多態的作用

  • 可以隱藏實現的細節,使得代碼模塊化;方便擴展代碼;
  • 可以實現接口重用。

3.3 空類默認的成員函數

  • 默認構造函數
  • 析構函數
  • 復制構造函數
  • 賦值運算符

3.4 類的成員函數重載、覆蓋和隱藏的概念和區別

  • 重載是指再同一個作用域內,有幾個同名的函數,但是參數列表的個數和類型不同。
  • 函數覆蓋是指派生類函數覆蓋基類函數,函數名、參數類型、返回值類型一模一樣。派生類的對象會調用子類中的覆蓋版本,覆蓋父類中的函數版本。
  • 隱藏”是指派生類的函數屏蔽了與其同名的基類函數。
  • 覆蓋和隱藏的區別:
    • 派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏。
    • 派生類的函數與基類的函數同名,參數也相同。基類函數有virtual,是覆蓋,沒有virtual就是隱藏。

3.5 基類和子類的構造、析構順序

  • 定義一個對象時先調用基類的構造函數、然后調用派生類的構造函數
  • 先派生類的析構后基類的析構,也就是說在基類的的析構調用的時候,派生類的信息已經全部銷毀了

3.6 深拷貝與淺拷貝的區別

  • 深拷貝意味着拷貝了資源和指針

  • 淺拷貝只是拷貝了指針,沒有拷貝資源

3.7 構造函數的特點

  • 構造函數只在建立對象的時候自動被調用一次

  • 構造函數必須是公共的,否則無法生成對象

  • 構造函數只負責為自己的類構造對象 

3.8 析構函數的特點

  • 函數名稱固定:~類名( )
  • 沒有返回類型,沒有參數
  • 不可以重載,一般由系統自動的調用

3.8 公有繼承、私有繼承、受保護的繼承

  • 公有繼承時,派生類對象可以訪問基類中的公有成員,派生類的成員函數可以訪問基類中的公有和受保護成員;公有繼承時基類受保護的成員,可以通過派生類對象訪問但不能修改。
  • 私有繼承時,基類的成員只能被直接派生類的成員訪問,無法再往下繼承。
  • 受保護繼承時,基類的成員也只被直接派生類的成員訪問,無法再往下繼承。

3.9 類成員中只能使用構造函數的初始化列表而不能賦值的有哪些

  • const成員
  • 引用成員

3.10 函數模板與類模板的區別

  • 函數模板是模板的一種,可以生成各種類型的函數實例,函數模板的實例化是由編譯程序在處理函數調用時自動完成的
  • 類模板的實例化必須由程序員在程序中顯式地指定。

3.11 引用與多態的關系

  • 引用就是對象的別名。
  • 引用主要用作函數的形參。
  • 引用必須用與該引用同類型的對象初始化: 引用是除指針外另一個可以產生多態效果的手段。
  • 一個基類的引用可以指向它的派生類實例。

3.12 static成員變量和static成員函數

  • static數據成員獨立於該類的任意對象而存在。
  • tatic數據成員(const static數據成員除外)在類定義體內聲明,必須在類外進行初始化。
  • static數據成員定義放在cpp文件中,不能放在初始化列表中。
  • static成員函數在類的外部定義。
  • Static成員函數沒有this形參。
  • 可以直接訪問所屬類的static成員,不能直接使用非static成員。
  • 因為static成員不是任何對象的組成部分,所以static成員函數不能被聲明為const。
  • static成員函數也不能被聲明為虛函數。

3.13 static總結

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

3.13 const總結

  • 欲阻止一個變量被改變,可以使用const關鍵字。在定義該const變量時,通常需要對它進行初始化,因為以后就沒有機會再去改變它了。
  • 對指針來說,可以指定指針本身為const,也可以指定指針所指的數據為const,或二者同時指定為const。
  • 在一個函數聲明中,const可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值。
  • 對於類的成員函數,若指定其為const類型,則表明其是一個常函數,不能修改類的成員變量。
  • 對於類的成員函數,有時候必須指定其返回值為const類型,以使得其返回值不為“左值”。

4. STL標准庫

4.1 STL

  • 容器:主要的七種容器 vector,list,deque,map,multimap,set,multiset。
  • 算法
  • 迭代器

參考文獻

[1] c++面試知識,知乎。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM