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

1. 說說你對c和c++的看法,c和c++的區別?

  1. 第一點就應該想到C是面向過程的語言,而C++是面向對象的語言,一般簡歷上第一條都是熟悉C/C++基本語法,瞭解C++面向對象思想,那麼,請問什麼是面向對象
  2. C和C++動態管理內存的方法不一樣,C是使用malloc/free函數,而C++除此之外還有new/delete關鍵字;(關於malooc/free與new/delete的不同又可以說一大堆,最後的擴展_1部分列出十大區別);
  3. 接下來就不得不談到C中的struct和C++的類,C++的類是C所沒有的,但是C中的struct是可以在C++中正常使用的,並且C++對struct進行了進一步的擴展,使struct在C++中可以和class一樣當做類使用,而唯一和class不同的地方在於struct的成員默認訪問修飾符是public,而class默認的是private;
  4. C++支持函數重載,而C不支持函數重載,而C++支持重載的依仗就在於C++的名字修飾與C不同,例如在C++中函數int fun(int ,int)經過名字修飾之後變爲 _fun_int_int ,而C是 
    _fun,一般是這樣的,所以C++纔會支持不同的參數調用不同的函數;
  5. C++中有引用,而C沒有;這樣就不得不提一下引用和指針的區別(文後擴展_2);
  6. 當然還有C++全部變量的默認鏈接屬性是外鏈接,而C是內連接;
  7. C 中用const修飾的變量不可以用在定義數組時的大小,但是C++用const修飾的變量可以(如果不進行&,解引用的操作的話,是存放在符號表的,不開闢內存);

2. 堆與棧的區別?

  1. 管理方式對於棧來講,是由編譯器自動管理,無需我們手工控制;對於堆來說,釋放工作由程序員控制,容易產生memory leak。 
  2. 空間大小一般來講在32位系統下,堆內存可以達到4G的空間,從這個角度來看堆內存幾乎是沒有什麼限制的。但是對於棧來講,一般都是有一定的空間大小的,例如,在VC6下面,默認的棧空間大小是1M(好像是,記不清楚了)。當然,我們可以修改: 打開工程,依次操作菜單如下:Project->Setting->Link,在Category 中選中Output,然後在Reserve中設定堆棧的最大值和commit。 注意:reserve最小值爲4Byte;commit是保留在虛擬內存的頁文件裏面,它設置的較大會使棧開闢較大的值,可能增加內存的開銷和啓動時間。 
  3. 碎片問題對於堆來講,頻繁的new/delete勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。對於棧來講,則不會存在這個問題,因爲棧是先進後出的隊列,他們是如此的一一對應,以至於永遠都不可能有一個內存塊從棧中間彈出,在他彈出之前,在他上面的後進的棧內容已經被彈出,詳細的可以參考數據結構,這裏我們就不再一一討論了。 
  4. 生長方向對於堆來講,生長方向是向上的,也就是向着內存地址增加的方向;對於棧來講,它的生長方向是向下的,是向着內存地址減小的方向增長。 
  5. 分配方式堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由alloca函數進行分配,但是棧的動態分配和堆是不同的,它的動態分配是由編譯器進行釋放,無需我們手工實現。 
  6. 分配效率棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很複雜的,例如爲了分配一塊內存,庫函數會按照一定的算法(具體的算法可以參考數據結構/操作系統)在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由於內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然後進行返回。顯然,堆的效率比棧要低得多。

3. 野指針是什麼?如何檢測內存泄漏?

  1. 野指針:指向內存被釋放的內存或者沒有訪問權限的內存的指針。
  • “野指針”的成因主要有3種:
  1. 指針變量沒有被初始化。任何指針變量剛被創建時不會自動成爲NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要麼將指針設置爲NULL,要麼讓它指向合法的內存。例如
    char *p = NULL;
    char *str = new char(100);
  2. 指針p被free或者delete之後,沒有置爲NULL
  3. 指針操作超越了變量的作用範圍
  • 如何避免野指針:
  1. 對指針進行初始化

          ①將指針初始化爲NULL。  char *   p  = NULL;

          ②用malloc分配內存。char * p = (char * )malloc(sizeof(char));

          ③用已有合法的可訪問的內存地址對指針初始化。char num[ 30] = {0}; char *p = num;

     2.指針用完後釋放內存,將指針賦NULL。

                delete(p);   p = NULL;

4. ​​​​​​​懸空指針和野指針有什麼區別?

  1. 野指針:野指針指,訪問一個已刪除或訪問受限的內存區域的指針,野指針不能判斷是否爲NULL來避免。指針沒有初始化,釋放後沒有置空,越界
  2. 懸空指針:一個指針的指向對象已被刪除,那麼就成了懸空指針野指針是那些未初始化的指針。

5. ​​​​​​​內存泄漏

  1. 內存泄漏:內存泄漏是指由於疏忽錯誤造成了程序未能釋放掉不再使用內存的情況。內存泄漏並非指內存在物理上消失,而是應用程序分配某段內存後,由於設計錯誤,失去了對該段內存的控制;
  2. 後果:只發生一次小的內存泄漏可能不被注意,但泄漏大量內存的程序將會出現各種證照:性能下降內存逐漸用完,導致另一個程序失敗
  3. 如何排除​​​​​​​:
  • ​​​​​​​使用工具軟件BoundsChecker,BoundsChecker是一個運行時錯誤檢測工具,它主要定位程序運行時期發生的各種錯誤;調試運行DEBUG版程序,運用以下技術:CRT(C run-time libraries)、運行時函數調用堆棧、內存泄漏時提示的內存分配序號(集成開發環境OUTPUT窗口),綜合分析內存泄漏的原因,排除內存泄漏。

​​​​​​​      解決辦法:

  1. 智能指針。

     檢查、定位內存泄漏

  1. 檢查方法:在main函數最後面一行,加上一句_CrtDumpMemoryLeaks()。調試程序,自然關閉程序讓其退出,查看輸出:

    輸出這樣的格式{453}normal block at 0x02432CA8,868 bytes long

    被{}包圍的453就是我們需要的內存泄漏定位值,868 bytes long就是說這個地方有868比特內存沒有釋放。

    定位代碼位置

    在main函數第一行加上_CrtSetBreakAlloc(453);意思就是在申請453這塊內存的位置中斷。然後調試程序,程序中斷了,查看調用堆棧。加上頭文件#include <crtdbg.h>

6.​​​​​​​​​​​​​​ new和malloc的區別?

7. ​​​​​​​​​​​​​​delete p;與delete[]p,allocator

  1. 動態數組管理new一個數組時,[]中必須是一個整數,但是不一定是常量整數,普通數組必須是一個常量整數;
  2. new動態數組返回的並不是數組類型,而是一個元素類型的指針
  3. delete[]時,數組中的元素按逆序的順序進行銷燬;
  4. new在內存分配上面有一些侷限性,new的機制是將內存分配和對象構造組合在一起,同樣的,delete也是將對象析構和內存釋放組合在一起的。allocator將這兩部分分開進行,allocator申請一部分內存,不進行初始化對象,只有當需要的時候才進行初始化操作。

8. ​​​​​​​​​​​​​​malloc申請的存儲空間能用delete釋放嗎

  1. 不能,malloc /free主要爲了兼容C,new和delete 完全可以取代malloc /free的。malloc /free的操作對象都是必須明確大小的。而且不能用在動態類上。new 和delete會自動進行類型檢查和大小malloc/free不能執行構造函數與析構函數,所以動態對象它是不行的。當然從理論上說使用malloc申請的內存是可以通過delete釋放的。不過一般不這樣寫的。而且也不能保證每個C++的運行時都能正常。

9. ​​​​​​​ malloc、realloc、calloc的區別

  1. malloc函數
  • void* malloc(unsigned int num_size);
  • int *p = malloc(20*sizeof(int));申請20個int類型的空間;

     2. calloc函數

  • void* calloc(size_t n,size_t size);

    int *p = calloc(20, sizeof(int));

    省去了人爲空間計算;malloc申請的空間的值是隨機初始化的,calloc申請的空間的值是初始化爲0的;

    3.realloc函數

  • void realloc(void *p, size_t new_size); 給動態分配的空間分配額外的空間,用於擴充容量。

10. ​​​​​​​ 使用智能指針管理內存資源,RAII

  1. RAII全稱是“Resource Acquisition is Initialization”,直譯過來是“資源獲取即初始化,也就是說在構造函數中申請分配資源,在析構函數中釋放資源。因爲C++的語言機制保證了,當一個對象創建的時候,自動調用構造函數,當對象超出作用域的時候會自動調用析構函數。所以,在RAII的指導下,我們應該使用類來管理資源,將資源和對象的生命週期綁定。
  2. 智能指針(std::shared_ptr和std::unique_ptr)即RAII最具代表的實現,使用智能指針,可以實現自動的內存管理,再也不需要擔心忘記delete造成的內存泄漏。毫不誇張的來講,有了智能指針,代碼中幾乎不需要再出現delete了。

11.​​​​​​​ 手寫實現智能指針類

  1. 智能指針是一個數據類型,一般用模板實現,模擬指針行爲的同時還提供自動垃圾回收機制。它會自動記錄SmartPointer<T*>對象的引用計數,一旦T類型對象的引用計數爲0,就釋放該對象。除了指針對象外,我們還需要一個引用計數的指針設定對象的值,並將引用計數計爲1,需要一個構造函數。新增對象還需要一個構造函數,析構函數負責引用計數減少和釋放內存。通過覆寫賦值運算符,才能將一箇舊的智能指針賦值給另一個指針,同時舊的引用計數減1,新的引用計數加1
  2. 一個構造函數、拷貝構造函數、複製構造函數、析構函數、移走函數;

12. ​​​​​​​ 內存對齊?位域?

  1. 分配內存的順序是按照聲明的順序
  2.  每個變量相對於起始位置的偏移量必須是該變量類型大小的整數倍,不是整數倍空出內存,直到偏移量是整數倍爲止
  3.  最後整個結構體的大小必須是裏面變量類型最大值的整數倍

     添加了#pragma pack(n)後規則就變成了下面這樣:

  1. 偏移量要是n和當前變量大小中較小值的整數倍
  2. 整體大小要是n和最大變量大小中較小值的整數倍
  3. n值必須爲1,2,4,8…,爲其他值時就按照默認的分配規則

13. ​​​​​​​結構體變量比較是否相等

  1. 重載了 “==” 操作符
  2. struct foo {

        int a;

        int b;

        bool operator==(const foo& rhs) // 操作運算符重載

        {

            return( a == rhs.a) && (b == rhs.b);

     

        }

    };

  3. 元素的話,一個個比;
  4. 指針直接比較,如果保存的是同一個實例地址,則(p1==p2)爲真;

14. ​​​​​​​爲什麼內存對齊

  1. 平臺原因(移植原因)
  • 不是所有的硬件平臺都能訪問任意地址上的任意數據的;
  • 某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異

    2. ​​​​​​​性能原因

  • 數據結構(尤其是棧)應該儘可能地在自然邊界上對齊。
  • 原因在於,爲了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。

15. ​​​​​​​怎樣判斷兩個浮點數是否相等?

  1. 對兩個浮點數判斷大小和是否相等不能直接用==來判斷,會出錯!明明相等的兩個數比較反而是不相等!
  2. 對於兩個浮點數比較只能通過相減並與預先設定的精度比較,記得要取絕對值!浮點數與0的比較也應該注意。與浮點數的表示方式有關。

16. ​​​​​​ define、const、typedef、inline使用方法?

  • ​​​​​​​const與#define的區別:
  1. const定義的常量是變量帶類型,而#define定義的只是個常數不帶類型
  2. define只在預處理階段起作用,簡單的文本替換,而const在編譯、鏈接過程中起作用;
  3. define只是簡單的字符串替換沒有類型檢查。而const是有數據類型的,是要進行判斷的,可以避免一些低級錯誤;
  4. define預處理後,佔用代碼段空間,const佔用數據段空間
  5. const不能重定義,而define可以通過#undef取消某個符號的定義,進行重定義;
  6. define獨特功能,比如可以用來防止文件重複引用。
  • #define和別名typedef的區別
  1. ​​​​​​​執行時間不同,typedef在編譯階段有效,typedef有類型檢查的功能;#define是宏定義,發生在預處理階段,不進行類型檢查;
  2. 功能差異,typedef用來定義類型的別名,定義與平臺無關的數據類型,與struct的結合使用等。#define不只是可以爲類型取別名,還可以定義常量、變量、編譯開關等。
  3. 作用域不同,#define沒有作用域的限制,只要是之前預定義過的宏,在以後的程序中都可以使用。而typedef有自己的作用域。
  • defineinline的區別
  1. #define關鍵字inline是函數
  2. 宏定義在預處理階段進行文本替換,inline函數在編譯階段進行替換;
  3. inline函數有類型檢查,相比宏定義比較安全;

17. ​​​​​​​#include 的順序以及尖叫括號和雙引號的區別

  1. 表示編譯器只在系統默認目錄或尖括號內的工作目錄下搜索頭文件,並不去用戶的工作目錄下尋找,所以一般尖括號用於包含標準庫文件;

  2. 表示編譯器先在用戶的工作目錄下搜索頭文件,如果搜索不到則到系統默認目錄下去尋找,所以雙引號一般用於包含用戶自己編寫的頭文件。

18. lambda 函數

 

[capture] (parameters) mutable ->return-type {statement};

  1. 利用lambda表達式可以編寫內嵌的匿名函數,用以替換獨立函數或者函數對象;

  2. 每當你定義一個lambda表達式後,編譯器會自動生成一個匿名類(這個類當然重載了()運算符),我們稱爲閉包類型(closure type)。那麼在運行時,這個lambda表達式就會返回一個匿名的閉包實例,其實一個右值。所以,我們上面的lambda表達式的結果就是一個個閉包。閉包的一個強大之處是其可以通過傳值或者引用的方式捕捉其封裝作用域內的變量,前面的方括號就是用來定義捕捉模式以及變量,我們又將其稱爲lambda捕捉塊。
  3. lambda表達式的語法定義如下:
  4. lambda必須使用尾置返回來指定返回類型,可以忽略參數列表和返回值,但必須永遠包含捕獲列表和函數體

19. ​​​​​​​模板類和模板函數的區別是什麼?

  1. 函數模板實例化是由編譯程序在處理函數調用時自動完成的,而類模板的實例化必須由程序員在程序中顯式地指定。即函數模板允許隱式調用和顯式調用而類模板只能顯示調用。在使用類模板必須加<T>,而函數模板不必

20. ​​​​​​​爲什麼模板類一般都是放在一個h文件中

  1. 模板定義很特殊。由template<…>處理的任何東西都意味着編譯器在當時不爲它分配存儲空間,它一直處於等待狀態直到被一個模板實例告知。在編譯器和連接器的某一處,有一機制能去掉指定模板的多重定義。所以爲了容易使用,幾乎總是在頭文件中放置全部的模板聲明和定義。
  2. 分離式編譯的環境下,編譯器編譯某一個.cpp文件時並不知道另一個.cpp文件的存在,也不會去查找(當遇到未決符號時它會寄希望於連接器)。這種模式在沒有模板的情況下運行良好,但遇到模板時就傻眼了,因爲模板僅在需要的時候纔會實例化出來,所以,當編譯器只看到模板的聲明時,它不能實例化該模板,只能創建一個具有外部連接的符號並期待連接器能夠將符號的地址決議出來。然而當實現該模板的.cpp文件中沒有用到模板的實例時,編譯器懶得去實例化,所以,整個工程的.obj中就找不到一行模板實例的二進制代碼,於是連接器也黔驢技窮了。

 

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