C++相關面試常見題型

變量默認初始化的幾種不一樣狀況

#include <iostream>
#include <string>

string global_str;
int global_int;
static int sglobal_x;

int main()
{
    static int slocal_x;
    int local_int;
    string local_str;
}
  • 對於string類型的變量來講,由於string類型自己接收無參數的初始化方式,因此不論變量定義在函數內仍是函數外部都默認初始化爲空串。
  • 對內置類型int來講,變量 global_int 定義在全部函數體以外,根據C++的規定,global_int默認初始化爲0;而變量 local_int 定義在main函數內部,將不被初始化,若是程序員試圖拷貝或輸出爲初始化的變量,將遇到一個未定義的值。
  • 對於靜態局部變量全局靜態變量來講都會被初始化爲0;

大端與小端的概念?各自的優點是什麼?

  • 大端與小端是用來描述多字節數據在內存中的存放順序,即字節序。大端(Big Endian)是指低地址端存放高位字節,小端(Little Endian)是指低地址端存放低位字節。
  • Big Endian:符號位的斷定固定爲第一個字節,容易判斷正負。
  • Little Endian:長度爲1,2,4字節的數,排列方式都是同樣的,數據類型轉換很是方便。
  • 須要記住計算機是以字節爲存儲單位

舉一個例子,好比數字0x12 34 56 78在內存中的表示形式爲:ios

  • 1)大端模式:

低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78程序員

  • 2)小端模式:

低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12編程

short int x;
char x0,x1;
x=0x1122;
x0=((char*)&x)[0]; //低地址單元
x1=((char*)&x)[1]; //高地址單元

new 和 malloc 的區別

  • new是運算符,malloc()是一個庫函數
  • new會調用構造函數,malloc不會;
  • new返回指定類型指針,malloc返回void*指針;
  • new會自動計算需分配的空間,malloc不行;
  • new能夠被重載,malloc不能。

指針和引用的區別

  • 指針是一個實體,而引用僅是個別名
  • 引用使用時無需解引用(*),指針須要解引用;
  • 引用只能在定義時被初始化一次,以後不可變,而指針可變;
  • 引用沒有const,指針有const;
  • 引用不能爲空,指針能夠爲空;
  • 從內存分配上看,指針變量需分配內存,引用則不須要;
  • sizeof(引用)獲得所指對象的大小,sizeof(指針)獲得指針自己的大小;
  • 指針和引用的自增(++)運算意義不同。

static關鍵字的做用

在C語言中:數組

  • 加了 static 的全局變量和函數,對其餘源文件隱藏(不能跨文件了)。
  • static修飾的函數內的局部變量,生存期爲整個源程序運行期間,但做用域仍爲函數內。
  • static變量和所有變量同樣,存在靜態存儲區,會默認初始化爲0.

在C++語言中,仍然有上面的做用,但多了下面兩個:安全

  • 聲明靜態成員變量,須要在類體外使用做用域運算符進行初始化。
  • 聲明靜態成員函數,在函數中不能訪問非靜態成員變量和函數。

C++的內存分區

  • 棧區(stack):主要存放函數參數以及局部變量,由系統自動分配釋放。
  • 堆區(heap):由用戶經過 malloc/new 手動申請,手動釋放。
  • 全局/靜態區:存放全局變量、靜態變量;程序結束後由系統釋放。
  • 字符串常量區:字符串常量就放在這裏,程序結束後由系統釋放。
  • 代碼區:存放程序的二進制代碼。

clipboard.png

堆和棧的區別

  • 棧由編譯器自動分配釋放,存放函數參數、局部變量等。而堆由程序員手動分配和釋放;
  • 棧是向低地址擴展的數據結構,是一塊連續的內存的區域。而堆是向高地址擴展的數據結構,是不連續的內存區域;
  • 棧的默認大小爲1M左右,而堆的大小能夠達到幾G,僅受限於計算機系統中有效的虛擬內存

clipboard.png
(小端)數據結構

vector、map、multimap、unordered_map、unordered_multimap的底層數據結構,以及幾種map容器如何選擇?

底層數據結構socket

  • vector基於數組,map、multimap基於紅黑樹,unordered_map、unordered_multimap基於哈希表

根據應用場景進行選擇:ide

  • map/unordered_map 不容許重複元素
  • multimap/unordered_multimap 容許重複元素
  • map/multimap 底層基於紅黑樹,元素自動有序,且插入、刪除效率高
  • unordered_map/unordered_multimap 底層基於哈希表,故元素無序,查找效率高。

內存泄漏怎麼產生的?如何避免?

  • 內存泄漏通常是指堆內存的泄漏,也就是程序在運行過程當中動態申請的內存空間再也不使用後沒有及時釋放,致使那塊內存不能被再次使用。
  • 更廣義的內存泄漏還包括未對系統資源的及時釋放,好比句柄、socket等沒有使用相應的函數釋放掉,致使系統資源的浪費。

解決方法:函數

  • 養成良好的編碼習慣和規範,記得及時釋放掉內存或系統資源。
  • 重載new和delete,以鏈表的形式自動管理分配的內存。
  • 使用智能指針,share_ptr、auto_ptr、weak_ptr。

說幾個C++11的新特性

  • auto類型推導
  • 範圍for循環
  • lambda函數
  • override 和 final 關鍵字
  • 空指針常量nullptr
  • 線程支持、智能指針等

static_cast 和 dynamic_cast 的區別

  • cast發生的時間不一樣,一個是static編譯時,一個是runtime運行時
  • static_cast是至關於C的強制類型轉換,用起來可能有一點危險,不提供運行時的檢查來確保轉換的安全性。
  • dynamic_cast用於轉換指針和和引用不能用來轉換對象 ——主要用於類層次間的上行轉換和下行轉換,還能夠用於類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是同樣的;在進行下行轉換時,dynamic_cast具備類型檢查的功能,比static_cast更安全。在多態類型之間的轉換主要使用dynamic_cast,由於類型提供了運行時信息

const限定符

  • 在定義常變量時必須同時對它初始化,此後它的值不能再改變。常變量不能出如今賦值號的左邊(不爲可賦值的「左值」);
  • 對指針來講,能夠指定指針自己爲const,也能夠指定指針所指的數據爲const,或兩者同時指定爲const;
  • 在一個函數聲明中,const能夠修飾形參,代表它是一個輸入參數,在函數內部不能改變其值;
  • 對於類的成員函數,若指定其爲const類型,則代表其是一個常函數,不能修改類的成員變量,若是要改變某個變量,記得將變量聲明爲mutable
  • 對於類的成員函數,有時候必須指定其返回值爲const類型,以使得其返回值不能被賦值。
//operator*的返回結果必須是一個const對象,不然下列代碼編譯出錯
const classA operator*(const classA& a1,const classA& a2);  
classA a, b, c;
(a*b) = c;  
//對a*b的結果賦值。操做(a*b) = c顯然不符合編程者的初衷,也沒有任何意義
//所以將返回值聲明爲const,直接杜絕這種行爲。

const與#define的區別

  • const常量有數據類型,能夠尋址;而宏常量沒有數據類型,不能尋址。編譯器能夠對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換可能會產生意料不到的錯誤(邊際效應)。
  • 有些集成化的調試工具能夠對const常量進行調試,可是不能對宏常量進行調試。
  • 在C++程序中只使用const常量而不使用宏常量,即const常量徹底取代宏常量。

sizeof運算符

  • sizeof是C語言的一種單目操做符,它並非函數。操做數能夠是一個表達式或類型名。數據類型必須用括號括住,sizeof(int);變量名能夠不用括號括住。
int a[50];  //sizeof(a)=200
int *a=new int[50];  //sizeof(a)=4;
Class Test{int a; static double c};  //sizeof(Test)=4
Test *s;  //sizeof(s)=4
Class Test{ };  //sizeof(Test)=1
int func(char s[5]);  //sizeof(s)=4;
  • 數組類型,其結果是數組的總字節數;指向數組的指針,其結果是該指針的字節數。
  • 函數中的數組形參或函數類型的形參,其結果是指針的字節數。數組做爲參數傳給函數時傳的是指針而不是數組,傳遞的是數組的首地址.
  • 聯合類型,其結果採用成員最大長度對齊。
  • 結構類型或類類型,其結果是這種類型對象的總字節數,包括任何填充在內。
  • 類中的靜態成員不對結果產生影響,由於靜態變量的存儲位置與結構或者類的實例地址無關;
  • 沒有成員變量的類的大小爲1,由於必須保證類的每個實例在內存中都有惟一的地址;
  • 有虛函數的類都會創建一張虛函數表,表中存放的是虛函數的函數指針,這個表的地址存放在類中,因此無論有幾個虛函數,都只佔據一個指針大小

strlen 和 sizeof 的區別

    • sizeof(...)是運算符,在頭文件中typedef爲unsigned int,其值在編譯時即計算好了,參數能夠是數組、指針、類型、對象、函數等。
    • 它的功能是:得到保證能容納實現所創建的最大對象的字節大小。
    • 因爲在編譯時計算,所以sizeof不能用來返回動態分配的內存空間的大小。實際上,用sizeof來返回類型以及靜態分配的對象、結構或數組所佔的空間,返回值跟對象、結構、數組所存儲的內容沒有關係。
    • strlen(...)是函數,要在運行時才能計算。參數必須是字符型指針(char*)。當數組名做爲參數傳入時,實際上數組就退化成指針了。
    • 它的功能是:返回字符串的長度。該字符串多是本身定義的,也多是內存中隨機的,該函數實際完成的功能是從表明該字符串的第一個地址開始遍歷,直到遇到結束符/0。返回的長度大小不包括/0。
    char arr[10] = "What?";
    int len_one = strlen(arr);
    int len_two = sizeof(arr); 
    cout << len_one << " and " << len_two << endl;
     
    輸出結果爲:5 and 10
    sizeof返回定義arr數組時編譯器爲其分配的數組空間大小,不關內心面實際存儲了多少數據。
    strlen只關心存儲的數據內容,不關心空間的大小和類型。
    char  *parr = new char[10];
    int len_one = strlen(parr);
    int len_two = sizeof(parr);
    int len_three = sizeof(*parr);
    cout << len_one << " and " << len_two << " and " << len_three << endl;
    輸出結果:24 and 4 and 1
    第一個輸出結果24實際上每次運行可能不同,這取決於parr裏面存了什麼(從parr[0]開始知道遇到第一個NULL結束);
    第二個結果實際上本意是想計算parr所指向的動態內存空間的大小,可是事與願違,sizeof認爲parr是個字符指針,所以返回的是該指針所佔的空間(指針的存儲用的是長整型,因此爲4);
    第三個結果,因爲*parr所表明的是parr所指的地址空間存放的字符,因此長度爲1。

    結構體的內存對齊

    • 每一個成員相對於這個結構體變量地址的偏移量正好是該成員類型所佔字節的整數倍。爲了對齊數據,可能必須在上一個數據結束和下一個數據開始的地方插入一些沒有用處字節。
    • 最終佔用字節數爲成員類型中最大佔用字節數的整數倍
    • 通常的結構體成員按照默認對齊字節數遞增或是遞減的順序排放,會使總的填充字節數最少。
    struct AlignData1
    {
        char c;
        short b;
        int i;
        char d;
    }Node;
    
    這個結構體在編譯之後,爲了字節對齊,會被整理成這個樣子:
    struct AlignData1
    {
        char c;
        char padding[1];
        short b;
        int i;
        char d;
        char padding[3];
    }Node;

    虛函數的實現原理

    • 編譯器會爲每一個有虛函數的類建立一個虛函數表,該虛函數表將被該類的全部對象共享。類的虛函數表是一塊連續的內存,每一個內存單元中記錄一個JMP指令的地址。類的每一個虛函數佔據虛函數表中的一塊,若是類中有N個虛函數,那麼其虛函數表將有4N字節的大小
    • 編譯器在有虛函數的類的實例中建立了一個指向這個表的指針,該指針一般存在於對象實例中最前面的位置(這是爲了保證取到虛函數表的有最高的性能)。這意味着能夠經過對象實例的地址獲得這張虛函數表,而後就能夠遍歷其中函數指針,並調用相應的函數。
    • 有虛函數或虛繼承的類實例化後的對象大小至少爲4字節(確切的說是一個指針的字節數;說至少是由於還要加上其餘非靜態數據成員,還要考慮對齊問題);沒有虛函數和虛繼承的類實例化後的對象大小至少爲1字節(沒有非靜態數據成員的狀況下也要有1個字節來記錄它的地址)。

    哪些函數適合聲明爲虛函數,哪些不能?工具

    • 當存在類繼承而且析構函數中有必需要進行的操做時(如須要釋放某些資源,或執行特定的函數)析構函數須要是虛函數,不然若使用父類指針指向子類對象,在delete時只會調用父類的析構函數,而不能調用子類的析構函數,從而形成內存泄露或達不到預期結果;
    • 內聯函數不能爲虛函數:內聯函數須要在編譯階段展開,而虛函數是運行時動態綁定的,編譯時沒法展開
    • 構造函數不能爲虛函數:構造函數在進行調用時還不存在父類和子類的概念,父類只會調用父類的構造函數,子類調用子類的,所以不存在動態綁定的概念;可是構造函數中能夠調用虛函數,不過並無動態效果,只會調用本類中的對應函數;
    • 靜態成員函數不能爲虛函數:靜態成員函數是以類爲單位的函數,與具體對象無關,虛函數是與對象動態綁定的

    程序加載時的內存分佈

    • 在多任務操做系統中,每一個進程都運行在一個屬於本身的虛擬內存中,而虛擬內存被分爲許多頁,並映射到物理內存中,被加載到物理內存中的文件纔可以被執行。這裏咱們主要關注程序被裝載後的內存佈局,其可執行文件包含了代碼段,數據段,BSS段,堆,棧等部分,其分佈以下圖所示。

    clipboard.png

    • 代碼段(.text):用來存放可執行文件的機器指令。存放在只讀區域,以防止被修改。
    • 只讀數據段(.rodata):用來存放常量存放在只讀區域,如字符串常量、全局const變量等。
    • 可讀寫數據段(.data):用來存放可執行文件中已初始化全局變量,即靜態分配的變量和全局變量。
    • BSS段(.bss):未初始化的全局變量和局部靜態變量通常放在.bss的段裏,以節省內存空間。
    • :用來容納應用程序動態分配的內存區域。當程序使用malloc或new分配內存時,獲得的內存來自堆。堆一般位於棧的下方。
    • :用於維護函數調用的上下文。棧一般分配在用戶空間的最高地址處分配。
    • 動態連接庫映射區:若是程序調用了動態連接庫,則會有這一部分。該區域是用於映射裝載的動態連接庫。
    • 保留區:內存中受到保護而禁止訪問的內存區域。

    智能指針

    • 智能指針是在 <memory> 頭文件中的std命名空間中定義的,該指針用於確保程序不存在內存和資源泄漏且是異常安全的。它們對RAII「獲取資源即初始化」編程相當重要,RAII的主要原則是爲將任何堆分配資源(如動態分配內存或系統對象句柄)的全部權提供給其析構函數包含用於刪除或釋放資源的代碼以及任何相關清理代碼的堆棧分配對象。大多數狀況下,當初始化原始指針或資源句柄以指向實際資源時,會當即將指針傳遞給智能指針。
    • 智能指針的設計思想:將基本類型指針封裝爲類對象指針(這個類確定是個模板,以適應不一樣基本類型的需求),並在析構函數裏編寫delete語句刪除指針指向的內存空間。
    • unique_ptr只容許基礎指針的一個全部者。unique_ptr小巧高效;大小等同於一個指針且支持右值引用,從而可實現快速插入和對STL集合的檢索。
    • shared_ptr採用引用計數的智能指針,主要用於要將一個原始指針分配給多個全部者(例如,從容器返回了指針副本又想保留原始指針時)的狀況。當全部的shared_ptr全部者超出了範圍或放棄全部權,纔會刪除原始指針。大小爲兩個指針;一個用於對象,另外一個用於包含引用計數的共享控制塊。最安全的分配和使用動態內存的方法是調用make_shared標準庫函數,此函數在動態分配內存中分配一個對象並初始化它,返回對象的shared_ptr。

    智能指針支持的操做

    • 使用重載的->和*運算符訪問對象。
    • 使用get成員函數獲取原始指針,提供對原始指針的直接訪問。你可使用智能指針管理你本身的代碼中的內存,還能將原始指針傳遞給不支持智能指針的代碼。
    • 使用刪除器定義本身的釋放操做。
    • 使用release成員函數的做用是放棄智能指針對指針的控制權,將智能指針置空,並返回原始指針。(只支持unique_ptr)
    • 使用reset釋放智能指針對對象的全部權。
    #include <iostream>
    #include <string>
    #include <memory>
    using namespace std;
    
    class base
    {
    public:
        base(int _a): a(_a)    {cout<<"構造函數"<<endl;}
        ~base()    {cout<<"析構函數"<<endl;}
        int a;
    };
    
    int main()
    {
        unique_ptr<base> up1(new base(2));
        // unique_ptr<base> up2 = up1;   //編譯器提示未定義
        unique_ptr<base> up2 = move(up1);  //轉移對象的全部權 
        // cout<<up1->a<<endl; //運行時錯誤 
        cout<<up2->a<<endl; //經過解引用運算符獲取封裝的原始指針 
        up2.reset(); // 顯式釋放內存 
    
        shared_ptr<base> sp1(new base(3));
        shared_ptr<base> sp2 = sp1;  //增長引用計數 
        cout<<"共享智能指針的數量:"<<sp2.use_count()<<endl;  //2
        sp1.reset();  //
        cout<<"共享智能指針的數量:"<<sp2.use_count()<<endl;  //1
        cout<<sp2->a<<endl; 
        auto sp3 = make_shared<base>(4);//利用make_shared函數動態分配內存 
    }
    相關文章
    相關標籤/搜索