二十万字C/C++、嵌入式软开面试题全集宝典八
目錄
141、 迭代器++it,it++哪個好,為什么
142、 C++如何處理多個異常的?
143、 模板和實現可不可以不寫在一個文件里面?為什么?
144、 在成員函數中調用delete this會出現什么問題?對象還可以使用嗎?
145、 三個智能指針
146、 智能指針怎么用?智能指針出現循環引用怎么解決?
147、 智能指針的作用
148、 auto_ptr作用
149、 class、union、struct的區別
150、 動態聯編與靜態聯編
151、 動態編譯與靜態編譯
152、 動態鏈接和靜態鏈接區別
153、 在不使用額外空間的情況下,交換兩個數?
154、 strcpy和memcpy的區別
155、 簡述strcpy、sprintf與memcpy的區別
156、 strcpy函數和strncpy函數的區別?哪個函數更安全?
157、 執行int main(int argc, char *argv[])時的內存結構
158、 volatile關鍵字的作用?
159、 講講大端小端,如何檢測(三種方法)
160、 查看內存的方法
?
141、 迭代器++it,it++哪個好,為什么
1.前置返回一個引用,后置返回一個對象
// ++i實現代碼為: int& operator++() { *this += 1; return *this; }
2.前置不會產生臨時對象,后置必須產生臨時對象,臨時對象會導致效率降低
?
142、 C++如何處理多個異常的?
1.C++中的異常情況:
○1語法錯誤(編譯錯誤):比如變量未定義、括號不匹配、關鍵字拼寫錯誤等等編譯器在編譯時能發現的錯誤,這類錯誤可以及時被編譯器發現,而且可以及時知道出錯的位置及原因,方便改正。
○2運行時錯誤:比如數組下標越界、系統內存不足等等。這類錯誤不易被程序員發現,它能通過編譯且能進入運行,但運行時會出錯,導致程序崩潰。為了有效處理程序運行時錯誤,C++中引入異常處理機制來解決此問題。
2.C++異常處理機制:
異常處理基本思想:執行一個函數的過程中發現異常,可以不用在本函數內立即進行處理,而是拋出該異常,讓函數的調用者直接或間接處理這個問題。
C++異常處理機制由3個模塊組成:try(檢查)、throw(拋出)、catch(捕獲)
拋出異常的語句格式為:throw 表達式;如果try塊中程序段發現了異常則拋出異常。
try{可能拋出異常的語句;(檢查)}
catch(類型名[形參名])//捕獲特定類型的異常
{//處理1; }
catch(類型名[形參名])//捕獲特定類型的異常
{//處理2; }
catch(…)//捕獲所有類型的異常{}
143、 模板和實現可不可以不寫在一個文件里面?為什么?
不可以!因為在編譯時模板并不能生成真正的二進制代碼,而是在編譯調用模板類或函數的CPP文件時才會去找對應的模板聲明和實現,在這種情況下編譯器是不知道實現模板類或函數的CPP文件的存在,所以它只能找到模板類或函數的聲明而找不到實現,而只好創建一個符號寄希望于鏈接程序找地址。但模板類或函數的實現并不能被編譯成二進制代碼,結果鏈接程序找不到地址只好報錯了。
《C++編程思想》第15章(第300頁)說明了原因:模板定義很特殊。由template<…>處理的任何東西都意味著編譯器在當時不為它分配存儲空間,它一直處于等待狀態直到被一個模板實例告知。在編譯器和連接器的某一處,有一機制能去掉指定模板的多重定義。所以為了容易使用,幾乎總是在頭文件中放置全部的模板聲明和定義。
144、 在成員函數中調用delete this會出現什么問題?對象還可以使用嗎?
1.類對象空間
在類對象的內存空間中,只有數據成員和虛函數表指針,并不包含代碼內容,類的成員函數單獨放在代碼段中。在調用成員函數時,隱含傳遞一個this指針,讓成員函數知道當前是哪個對象在調用它。當調用delete this時,類對象的內存空間被釋放。在delete this之后進行的其他任何函數調用,只要不涉及到this指針的內容,都能夠正常運行。一旦涉及到this指針,如操作數據成員,調用虛函數等,就會出現不可預期的問題。
2.為什么是不可預期的問題?
delete this之后不是釋放了類對象的內存空間了么,那么這段內存應該已經還給系統,不再屬于這個進程。照這個邏輯來看,應該發生指針錯誤,無訪問權限之類的令系統崩潰的問題才對啊?這個問題牽涉到操作系統的內存管理策略。delete this釋放了類對象的內存空間,但是內存空間卻并不是馬上被回收到系統中,可能是緩沖或者其他什么原因,導致這段內存空間暫時并沒有被系統收回。此時這段內存是可以訪問的,你可以加上100,加上200,但是其中的值卻是不確定的。當你獲取數據成員,可能得到的是一串很長的未初始化的隨機數;訪問虛函數表,指針無效的可能性非常高,造成系統崩潰。
3.如果在類的析構函數中調用delete this,會發生什么?
會導致堆棧溢出。原因很簡單,delete的本質是“為將被釋放的內存調用一個或多個析構函數,然后,釋放內存”。顯然,delete this會去調用本對象的析構函數,而析構函數中又調用delete this,形成無限遞歸,造成堆棧溢出,系統崩潰。(值傳遞的拷貝構造函數也是無限遞歸)
145、 三個智能指針
1.shared_ptr共享的智能指針:
shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。在最后一個shared_ptr析構的時候,內存才會被釋放。
注意事項:
○1不要用一個原始指針初始化多個shared_ptr。
○2不要再函數實參中創建shared_ptr,在調用函數之前先定義以及初始化它。
○3不要將this指針作為shared_ptr返回出來。
○4要避免循環引用。(兩個類,互相包含對方。使用weak_ptr解決)
2.unique_ptr獨占的智能指針:
<1>unique_ptr是一個獨占的智能指針,他不允許其他的智能指針共享其內部的指針,不允許通過賦值將一個unique_ptr賦值給另外一個unique_ptr。
<2>unique_ptr不允許復制,但可以通過函數返回給其他的unique_ptr,還可以通過std::move來轉移到其他的unique_ptr,這樣它本身就不再擁有原來指針的所有權了。
<3>如果希望只有一個智能指針管理資源或管理數組就用unique_ptr,如果希望多個智能指針管理同一個資源就用shared_ptr。
3.weak_ptr弱引用的智能指針:
弱引用的智能指針weak_ptr是用來監視shared_ptr的,不會使引用計數加一,它不管理shared_ptr內部的指針,主要是為了監視shared_ptr的生命周期,更像是shared_ptr的一個助手。weak_ptr沒有重載運算符*和->,因為它不共享指針,不能操作資源,主要是為了通過shared_ptr獲得資源的監測權,它的構造不會增加引用計數,它的析構不會減少引用計數,純粹只是作為一個旁觀者來監視shared_ptr中關連的資源是否存在。 weak_ptr還可以用來返回this指針和解決循環引用的問題。
146、 智能指針怎么用?智能指針出現循環引用怎么解決?
1.shared_ptr
調用一個名為make_shared的標準庫函數,
shared_ptr<int> p = make_shared<int>(42);
通常用auto更方便,auto p = …;shared_ptr<int> p2(new int(2));
每個shared_ptr都有一個關聯的計數器,通常稱為引用計數,一旦一個shared_ptr的計數器變為0,它就會自動釋放自己所管理的對象;shared_ptr的析構函數就會遞減它所指的對象的引用計數。如果引用計數變為0,shared_ptr的析構函數就會銷毀對象,并釋放它占用的內存。
2.unique_ptr
一個unique_ptr擁有它所指向的對象。某個時刻只能有一個unique_ptr指向一個給定對象。當unique_ptr被銷毀時,它所指向的對象也被銷毀。
3.weak_ptr
weak_ptr是一種不控制所指向對象生存期的智能指針,它指向由一個shared_ptr管理的對象,將一個weak_ptr綁定到一個shared_ptr不會改變引用計數,一旦最后一個指向對象的shared_ptr被銷毀,對象就會被釋放,即使有weak_ptr指向對象,對象還是會被釋放。
弱指針用于專門解決shared_ptr循環引用的問題,weak_ptr不會修改引用計數,即其存在與否并不影響對象的引用計數器。循環引用就是:兩個對象互相使用一個shared_ptr成員變量指向對方。弱引用并不對對象的內存進行管理,在功能上類似于普通指針,然而一個比較大的區別是,弱引用能檢測到所管理的對象是否已經被釋放,從而避免訪問非法內存。
5.循環引用問題
雖然智能指針會減少內存泄漏的可能性,但是如果使用智能指針的方式不對,一樣會造成內存泄漏。比較典型的情況是循環引用問題,比如這段代碼:
這個程序中智能指針的引用情況如下圖
上圖中,class A和class B的對象各自被兩個智能指針管理,也就是A object和B object引用計數都為2,為什么是2?
分析class A對象的引用情況,該對象被main函數中的pa和class B對象中的ptr管理,因此A object引用計數是2,B object同理。
在這種情況下,在main函數中一個while循環結束的時候,pa和pb的析構函數被調用,但是class A對象和class B對象仍然被一個智能指針管理,A object和B object引用計數變成1,于是這兩個對象的內存無法被釋放,造成內存泄漏,如下圖所示
解決方法
解決方法很簡單,把class A或者class B中的shared_ptr改成weak_ptr即可,由于weak_ptr不會增加shared_ptr的引用計數,所以A object和B object中有一個的引用計數為1,在pa和pb析構時,會正確地釋放掉內存。
147、 智能指針的作用
1.C++11中引入了智能指針的概念,方便管理堆內存。使用普通指針,容易造成堆內存泄露(忘記釋放),二次釋放,程序發生異常時內存泄露等問題等,使用智能指針能更好的管理堆內存。
2.智能指針在C++11版本之后提供,包含在頭文件<memory>中,shared_ptr、unique_ptr、weak_ptr。shared_ptr多個指針指向相同的對象。shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減為0時,自動刪除所指向的堆內存。shared_ptr內部的引用計數是線程安全的,但是對象的讀取需要加鎖。
3.初始化。智能指針是個模板類,可以指定類型,傳入指針通過構造函數初始化。也可以使用make_shared函數初始化。不能將指針直接賦值給一個智能指針,一個是類,一個是指針。例如std::shared_ptr<int> p4 = new int(1);的寫法是錯誤的
4.拷貝和賦值。拷貝使得對象的引用計數增加1,賦值使得原對象引用計數減1,當計數為0時,自動釋放內存。后來指向的對象引用計數加1,指向后來的對象
5.unique_ptr“唯一”擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(通過禁止拷貝語義、只有移動語義來實現)。相比與原始指針unique_ptr用于其RAII的特性,使得在出現異常的情況下,動態資源能得到釋放。unique_ptr指針本身的生命周期:從unique_ptr指針創建時開始,直到離開作用域。離開作用域時,若其指向對象,則將其所指對象銷毀(默認使用delete操作符,用戶可指定其他操作)。unique_ptr指針與其所指對象的關系:在智能指針生命周期內,可以改變智能指針所指對象,如創建智能指針時通過構造函數指定、通過reset方法重新指定、通過release方法釋放所有權、通過移動語義轉移所有權。
6.智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。每次創建類的新對象時,初始化指針并將引用計數置為1;當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針并增加與之相應的引用計數;對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數為減至0,則刪除對象),并增加右操作數所指對象的引用計數;調用析構函數時,構造函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。
7.weak_ptr 是一種不控制對象生命周期的智能指針, 它指向一個 shared_ptr 管理的對象. 進行該對象的內存管理的是那個強引用的 shared_ptr. weak_ptr只是提供了對管理對象的一個訪問手段。weak_ptr 設計的目的是為配合 shared_ptr 而引入的一種智能指針來協助 shared_ptr 工作, 它只可以從一個 shared_ptr 或另一個 weak_ptr 對象構造, 它的構造和析構不會引起引用記數的增加或減少。
148、 auto_ptr作用
1.auto_ptr的出現,主要是為了解決“有異常拋出時發生內存泄漏”的問題;拋出異常,將導致指針p所指向的空間得不到釋放而導致內存泄漏;
2.auto_ptr構造時取得某個對象的控制權,在析構時釋放該對象。我們實際上是創建一個auto_ptr<Type>類型的局部對象,該局部對象析構時,會將自身所擁有的指針空間釋放,所以不會有內存泄漏;
3.auto_ptr的構造函數是explicit,阻止了一般指針隱式轉換為 auto_ptr的構造,所以不能直接將一般類型的指針賦值給auto_ptr類型的對象,必須用auto_ptr的構造函數創建對象;
4.由于auto_ptr對象析構時會刪除它所擁有的指針,所以使用時避免多個auto_ptr對象管理同一個指針;
5.Auto_ptr內部實現,析構函數中刪除對象用的是delete而不是delete[],所以auto_ptr不能管理數組;
6.auto_ptr支持所擁有的指針類型之間的隱式類型轉換。
7.可以通過*和->運算符對auto_ptr所有用的指針進行提領操作;
8.T* get(),獲得auto_ptr所擁有的指針;T* release(),釋放auto_ptr的所有權,并將所有用的指針返回。
149、 class、union、struct的區別
1.C語言中,struct只是一個聚合數據類型,沒有權限設置,無法添加成員函數,無法實現面向對象編程,且如果沒有typedef結構名,聲明結構變量必須添加關鍵字struct。
2.C++中,struct功能大大擴展,可以有權限設置(默認權限為public),可以像class一樣有成員函數,繼承(默認public繼承),可以實現面對對象編程,允許在聲明結構變量時省略關鍵字struct。
3.C與C++中的union:一種數據格式,能夠存儲不同的數據類型,但只能同時存儲其中的一種類型。C++ union結構式一種特殊的類。它能夠包含訪問權限、成員變量、成員函數(可以包含構造函數和析構函數)。它不能包含虛函數和靜態數據變量。它也不能被用作其他類的基類,它本身也不能有從某個基類派生而來。Union中得默認訪問權限是public。union類型是共享內存的,以size最大的結構作為自己的大小。每個數據成員在內存中的起始地址是相同的。
4.在C/C++程序的編寫中,當多個基本數據類型或復合數據結構要占用同一片內存時,我們要使用聯合體;當多種類型,多個對象,多個事物只取其一時(我們姑且通俗地稱其為“n 選1”),我們也可以使用聯合體來發揮其長處。在某一時刻,一個union中只能有一個值是有效的。union的一個用法就是可以用來測試CPU是大端模式還是小端模式。
150、 動態聯編與靜態聯編
1.在C++中,聯編是指一個計算機程序的不同部分彼此關聯的過程。按照聯編所進行的階段不同,可以分為靜態聯編和動態聯編;
2.靜態聯編是指聯編工作在編譯階段完成的,這種聯編過程是在程序運行之前完成的,又稱為早期聯編。要實現靜態聯編,在編譯階段就必須確定程序中的操作調用(如函數調用)與執行該操作代碼間的關系,確定這種關系稱為束定,在編譯時的束定稱為靜態束定。靜態聯編對函數的選擇是基于指向對象的指針或者引用的類型。其優點是效率高,但靈活性差。
3.動態聯編是指聯編在程序運行時動態地進行,根據當時的情況來確定調用哪個同名函數,實際上是在運行時虛函數的實現。這種聯編又稱為晚期聯編,或動態束定。動態聯編對成員函數的選擇是基于對象的類型,針對不同的對象類型將做出不同的編譯結果。C++中一般情況下的聯編是靜態聯編,但是當涉及到多態性和虛函數時應該使用動態聯編。動態聯編的優點是靈活性強,但效率低。動態聯編規定,只能通過指向基類的指針或基類對象的引用來調用虛函數,其格式為:指向基類的指針變量名->虛函數名(實參表)或基類對象的引用名.虛函數名(實參表)
4.實現動態聯編三個條件:
必須把動態聯編的行為定義為類的虛函數;
類之間應滿足子類型關系,通常表現為一個類從另一個類公有派生而來;
必須先使用基類指針指向子類型的對象,然后直接或間接使用基類指針調用虛函數;
151、 動態編譯與靜態編譯
1.靜態編譯,編譯器在編譯可執行文件時,把需要用到的對應動態鏈接庫中的部分提取出來,連接到可執行文件中去,使可執行文件在運行時不需要依賴于動態鏈接庫;
2.動態編譯的可執行文件需要附帶一個動態鏈接庫,在執行時,需要調用其對應動態鏈接庫的命令。所以其優點一方面是縮小了執行文件本身的體積,另一方面是加快了編譯速度,節省了系統資源。缺點是哪怕是很簡單的程序,只用到了鏈接庫的一兩條命令,也需要附帶一個相對龐大的鏈接庫;二是如果其他計算機上沒有安裝對應的運行庫,則用動態編譯的可執行文件就不能運行。
152、 動態鏈接和靜態鏈接區別
1.靜態連接庫就是把(lib)文件中用到的函數代碼直接鏈接進目標程序,程序運行的時候不再需要其它的庫文件;動態鏈接就是把調用的函數所在文件模塊(DLL)和調用函數在文件中的位置等信息鏈接進目標程序,程序運行的時候再從DLL中尋找相應函數代碼,因此需要相應DLL文件的支持。
2.靜態鏈接庫與動態鏈接庫都是共享代碼的方式,如果采用靜態鏈接庫,則無論你愿不愿意,lib 中的指令都全部被直接包含在最終生成的 EXE 文件中了。但是若使用 DLL,該 DLL 不必被包含在最終 EXE 文件中,EXE 文件執行時可以“動態”地引用和卸載這個與 EXE 獨立的 DLL 文件。靜態鏈接庫和動態鏈接庫的另外一個區別在于靜態鏈接庫中不能再包含其他的動態鏈接庫或者靜態庫,而在動態鏈接庫中還可以再包含其他的動態或靜態鏈接庫。
3.動態庫就是在需要調用其中的函數時,根據函數映射表找到該函數然后調入堆棧執行。如果在當前工程中有多處對dll文件中同一個函數的調用,那么執行時,這個函數只會留下一份拷貝。但是如果有多處對lib文件中同一個函數的調用,那么執行時,該函數將在當前程序的執行空間里留下多份拷貝,而且是一處調用就產生一份拷貝。
153、 在不使用額外空間的情況下,交換兩個數?
1.算術
x = x + y; y = x - y; x = x - y;2.異或
x = x^y;// 只能對int,char.. y = x^y; x = x^y; x ^= y ^= x;154、 strcpy和memcpy的區別
1、復制的內容不同。strcpy只能復制字符串,而memcpy可以復制任意內容,例如字符數組、整型、結構體、類等。
2、復制的方法不同。strcpy不需要指定長度,它遇到被復制字符的串結束符"\0"才結束,所以容易溢出。memcpy則是根據其第3個參數決定復制的長度。
3、用途不同。通常在復制字符串時用strcpy,而需要復制其他類型數據時則一般用memcpy。
155、 簡述strcpy、sprintf與memcpy的區別
1.操作對象不同
○1strcpy的兩個操作對象均為字符串
○2sprintf的操作源對象可以是多種數據類型,目的操作對象是字符串
○3memcpy的兩個對象就是兩個任意可操作的內存地址,并不限于何種數據類型。
2.執行效率不同
memcpy最高,strcpy次之,sprintf的效率最低。
3.實現功能不同
○1strcpy主要實現字符串變量間的拷貝
○2sprintf主要實現其他數據類型格式到字符串的轉化
○3memcpy主要是內存塊間的拷貝。
156、 strcpy函數和strncpy函數的區別?哪個函數更安全?
1.函數原型
char* strcpy(char* strDest, const char* strSrc) char* strncpy(char* strDest, const char* strSrc, int pos)2.strcpy函數: 如果參數 dest 所指的內存空間不夠大,可能會造成緩沖溢出(buffer Overflow)的錯誤情況,在編寫程序時請特別留意,或者用strncpy()來取代。
strncpy函數:用來復制源字符串的前n個字符,src 和 dest 所指的內存區域不能重疊,且 dest 必須有足夠的空間放置n個字符。
3.如果目標長>指定長>源長,則將源長全部拷貝到目標長,自動加上’\0’如果指定長<源長,則將源長中按指定長度拷貝到目標字符串,不包括’\0’如果指定長>目標長,運行時錯誤;
157、 執行int main(int argc, char *argv[])時的內存結構
參數的含義是程序在命令行下運行的時候,需要輸入argc 個參數,每個參數是以char 類型輸入的,依次存在數組里面,數組是 argv[],所有的參數在指針
char * 指向的內存中,數組的中元素的個數為 argc 個,第一個參數為程序的名稱(gcc時能夠體現)。
158、 volatile關鍵字的作用?
volatile關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。聲明時語法:int volatile vInt;當要求使用 volatile 聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。而且讀取的數據立刻被保存。
1、性質:
易變性:在匯編層?反映出來,就是兩條語句,下?條語句不會直接使?上?條語句對應的volatile 變量的寄存器內容,?是重新從內存中讀取。
不可優化性: volatile 告訴編譯器,不要對我這個變量進?各種激進的優化,甚?將變量直接消除,保證程序員寫在代碼中的指令,?定會被執?
順序性:能夠保證 volatile 變量之間的順序性,編譯器不會進?亂序優化。
2、應用場景:
1) 中斷服務程序中修改的供其它程序檢測的變量需要加volatile;
2) 多任務環境下各任務間共享的標志應該加volatile;
3) 存儲器映射的硬件寄存器通常也要加volatile說明,因為每次對它的讀寫都可能由不同意義;
159、 講講大端小端,如何檢測(三種方法)
大端模式:是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址端。
小端模式,是指數據的高字節保存在內存的高地址中,低位字節保存在在內存的低地址端。
1.直接讀取存放在內存中的十六進制數值,取低位進行值判斷
2.用共同體來進行判斷
union共同體所有數據成員是共享一段內存的,后寫入的成員數據將覆蓋之前的成員數據,成員數據都有相同的首地址。Union的大小為最大數據成員的大小。
union的成員數據共用內存,并且首地址都是低地址首字節。int i= 1時:大端存儲1放在最高位,小端存儲1放在最低位。當讀取char ch時,是最低地址首字節,大小端會顯示不同的值。
160、 查看內存的方法
1.首先打開vs編譯器,創建好項目,并且將代碼寫進去,這里就不貼代碼了,你可以隨便的寫個做個測試;
2.調試的時候做好相應的斷點,然后點擊開始調試;
2.1程序調試之后會在你設置斷點的地方暫停,然后選擇調試->窗口->內存,就打開了內存數據查看的窗口了。
總結
以上是生活随笔為你收集整理的二十万字C/C++、嵌入式软开面试题全集宝典八的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 双目估计方法_教你提高双目立体视觉系统的
- 下一篇: linux中printf命令,Linux