面試必備| 常見C++筆試面試題整理 - sa123

文章推薦指數: 80 %
投票人數:10人

編譯階段,g++會呼叫gcc,對於c++程式碼,兩者是等價的,但是因為gcc命令不能自動和C++程式使用的庫聯接,所以通常用g++來完成連結。

點選上方“計算機視覺life”,選擇“星標” 快速獲得最新幹貨 作者:阿貴 https://zhuanlan.zhihu.com/p/69999591 本文已授權,未經允許,不得二次轉載 gcc和g++的區別 簡單來說,gcc與g++都是GNU(組織)的一個編譯器。

需要注意以下幾點: gcc與g++都可以編譯c程式碼與c++程式碼。

但是:字尾為.c的,gcc把它當做C程式,而g++當做是C++程式;字尾為.cpp的,兩者都會認為是C++程式。

編譯階段,g++會呼叫gcc,對於c++程式碼,兩者是等價的,但是因為gcc命令不能自動和C++程式使用的庫聯接,所以通常用g++來完成連結。

編譯可以用gcc/g++,而連結可以用g++或者gcc-lstdc++。

因為gcc命令不能自動和C++程式使用的庫聯接(當然可以選擇手動連結,使用命令如下),所以通常使用g++來完成聯接。

但在編譯階段,g++會自動呼叫gcc,二者等價。

gccmain.cpp-lstdc++ gcc編譯的四個步驟,以最簡單的hello.c為例子 一步到位:gcchello.c這條命令隱含執行了(1)預處理(2)編譯(3)彙編(4)連結這裡未指定輸出檔案,預設輸出為a.outgcc編譯C原始碼有四個步驟:預處理—->編譯—->彙編—->連結現在我們就用gcc的命令選項來逐個剖析gcc過程。

1)預處理(Pre-processing)在該階段,編譯器將C原始碼中的包含的標頭檔案如stdio.h新增進來引數:”-E”用法:gcc-Ehello.c-ohello.i作用:將hello.c預處理輸出hello.i檔案。

2)編譯(Compiling)第二步進行的是編譯階段,在這個階段中,gcc首先要檢查程式碼的規範性、是否有語法錯誤等,以確定程式碼的實際要做的工作,在檢查無誤後,gcc把程式碼翻譯成組合語言。

引數:”-S”用法:gcc–Shello.i–ohello.s作用:將預處理輸出檔案hello.i彙編成hello.s檔案。

3)彙編(Assembling)彙編階段是把編譯階段生成的”.s”檔案轉成二進位制目的碼“.o”檔案引數:“-c”用法:gcc–chello.s–ohello.o作用:將彙編輸出檔案hello.s編譯輸出hello.o檔案。

4)連結(Link)在成功編譯之後,就進入了連結階段。

用法:gcchello.o–ohello作用:將編譯輸出檔案hello.o連結成最終可執行檔案hello。

執行該可執行檔案,出現正確的結果如下。

>>>./helloHelloWorld! C++11包含大量的新特性:包含lambda表示式,型別推導keyword:auto、decltype,和模板的大量改進。

decltype實際上有點像auto的反函式,auto能夠讓你宣告一個變數。

而decltype則能夠從一個變數或表示式中得到型別 nullptr是為了解決原來C++中NULL的二義性問題而引進的一種新的型別,由於NULL實際上代表的是0, 簡化的for迴圈,能夠用於遍歷陣列、容器、string以及由begin和end函式定義的序列(即有Iterator),for(autop:m) lambda表示式,能夠用於建立並定義匿名的函式物件,以簡化程式設計工作。

Lambda的語法例如以下:[函式物件引數](運算子過載函式引數)->返回值型別{函式體} vectoriv{5,4,3,2,1};inta=2,b=1; for_each(iv.begin(),iv.end(),[b](int&x){cout<int{returnx*(a+b);});//(3) []內的引數指的是Lambda表示式能夠取得的全域性變數。

(1)函式中的b就是指函式能夠得到在Lambda表示式外的全域性變數,假設在[]中傳入=的話,即是能夠取得全部的外部變數,如(2)和(3)Lambda表示式 ()內的引數是每次呼叫函式時傳入的引數。

->後加上的是Lambda表示式返回值的型別。

如(3)中返回了一個int型別的變數 變長引數的模板,C++11中引入了變長引數模板,所以發明了新的資料型別:tuple,tuple是一個N元組。

能夠傳入1個,2個甚至多個不同型別的資料 autot1=make_tuple(1,2.0,"C++11");autot2=make_tuple(1,2.0,"C++11",{1,0,2}); 避免了從前的pair中巢狀pair的醜陋做法。

使得程式碼更加整潔 更加優雅的初始化方法,在引入C++11之前。

僅僅有陣列能使用初始化列表,其它容器想要使用初始化列表,僅僅能用下面方法: intarr[3]={1,2,3}vectorv(arr,arr+3); 在C++11中,我們能夠使用下面語法來進行替換: intarr[3]{1,2,3};vectoriv{1,2,3};map{{1,"a"},{2,"b"}};stringstr{"HelloWorld"}; 什麼是智慧指標?智慧指標的原理 將基本型別指標封裝為類物件指標(這個類肯定是個模板,以適應不同基本型別的需求),並在析構函數里編寫delete語句刪除指標指向的記憶體空間。

智慧指標是一個類,這個類的建構函式中傳入一個普通指標,解構函式中釋放傳入的指標。

智慧指標的類都是棧上的物件,所以當函式(或程式)結束時會自動被釋放, 智慧指標就是一種棧上建立的物件,函式退出時會呼叫其解構函式,這個析構函數里面往往就是一堆計數之類的條件判斷,如果達到某個條件,就把真正指標指向的空間給釋放了。

注意事項: 不能將指標直接賦值給一個智慧指標,一個是類,一個是指標。

常用的智慧指標 智慧指標在C++11版本之後提供,包含在標頭檔案中,shared_ptr、unique_ptr、weak_ptr 1)std::auto_ptr,有很多問題。

不支援複製(複製建構函式)和賦值(operator=),但複製或賦值的時候不會提示出錯。

所以可能會造成程式崩潰,比如 auto_ptrp1(newstring("auto");//#1auto_ptrp2;//#2p2=p1;//#3 在語句#3中,p2接管string物件的所有權後,p1的所有權將被剝奪。

前面說過,這是好事,可防止p1和p2的解構函式試圖刪同—個物件;但如果程式隨後試圖使用p1,這將是件壞事,因為p1不再指向有效的資料。

如果再訪問p1指向的內容則會導致程式崩潰。

auto_ptr是C++98提供的解決方案,C+11已將將其摒棄,摒棄auto_ptr的原因,一句話總結就是:避免潛在的記憶體崩潰問題。

2)C++11引入的unique_ptr,也不支援複製和賦值,但比auto_ptr好,直接賦值會編譯出錯。

實在想賦值的話,需要使用:std::move。

例如: std::unique_ptrp1(newint(5))//#4std::unique_ptrp2=p1;//編譯會出錯//#5std::unique_ptrp3=std::move(p1);//轉移所有權,現在那塊記憶體歸p3所有,p1成為無效的指標.//#6 編譯器認為語句#5非法,因此,unique_ptr比auto_ptr更安全。

但unique_ptr還有更聰明的地方。

有時候,會將一個智慧指標賦給另一個並不會留下危險的懸掛指標。

當程式試圖將一個unique_ptr賦值給另一個時,如果源unique_ptr是個臨時右值,編譯器允許這麼做;如果源unique_ptr將存在一段時間,編譯器將禁止這麼做 unique_ptrpu1(newstring("helloworld"));unique_ptrpu2;pu2=pu1;//#1notallowedunique_ptrpu3;pu3=unique_ptr(newstring("You"));//#2allowed 其中#1留下懸掛的unique_ptr(pu1),這可能導致危害。

而#2不會留下懸掛的unique_ptr,因為它呼叫unique_ptr的建構函式,該建構函式建立的臨時物件在其所有權讓給pu3後就會被銷燬。

這種隨情況而已的行為表明,unique_ptr優於允許兩種賦值的auto_ptr。

3)C++11或boost的shared_ptr,基於引用計數的智慧指標。

可隨意賦值,直到記憶體的引用計數為0的時候這個記憶體會被釋放。

4)C++11或boost的weak_ptr,弱引用。

引用計數有一個問題就是互相引用形成環,這樣兩個指標指向的記憶體都無法釋放。

需要手動打破迴圈引用或使用weak_ptr。

顧名思義,weak_ptr是一個弱引用,只引用,不計數。

如果一塊記憶體被shared_ptr和weak_ptr同時引用,當所有shared_ptr析構了之後,不管還有沒有weak_ptr引用該記憶體,記憶體也會被釋放。

所以weak_ptr不保證它指向的記憶體一定是有效的,在使用之前需要檢查weak_ptr是否為空指標。

智慧指標的作用 C++程式設計中使用堆記憶體是非常頻繁的操作,堆記憶體的申請和釋放都由程式設計師自己管理。

程式設計師自己管理堆記憶體可以提高了程式的效率,但是整體來說堆記憶體的管理是麻煩的,C++11中引入了智慧指標的概念,方便管理堆記憶體。

使用普通指標,容易造成堆記憶體洩露(忘記釋放),二次釋放,野指標,程式發生異常時記憶體洩露等問題等,使用智慧指標能更好的管理堆記憶體。

1、C和C++的區別 1)C是面向過程的語言,是一個結構化的語言,考慮如何透過一個過程對輸入進行處理得到輸出;C++是面向物件的語言,主要特徵是“封裝、繼承和多型”。

封裝隱藏了實現細節,使得程式碼模組化;派生類可以繼承父類的資料和方法,擴充套件了已經存在的模組,實現了程式碼重用;多型則是“一個介面,多種實現”,透過派生類重寫父類的虛擬函式,實現了介面的重用。

2)C和C++動態管理記憶體的方法不一樣,C是使用malloc/free,而C++除此之外還有new/delete關鍵字。

3)C++支援函式過載,C不支援函式過載 4)C++中有引用,C中不存在引用的概念 2、C++中指標和引用的區別 1)指標是一個新的變數,儲存了另一個變數的地址,我們可以透過訪問這個地址來修改另一個變數; 引用只是一個別名,還是變數本身,對引用的任何操作就是對變數本身進行操作,以達到修改變數的目的 2)引用只有一級,而指標可以有多級 3)指標傳參的時候,還是值傳遞,指標本身的值不可以修改,需要透過解引用才能對指向的物件進行操作 引用傳參的時候,傳進來的就是變數本身,因此變數可以被修改 3、結構體struct和共同體union(聯合)的區別 結構體:將不同型別的資料組合成一個整體,是自定義型別 共同體:不同型別的幾個變數共同佔用一段記憶體 1)結構體中的每個成員都有自己獨立的地址,它們是同時存在的; 共同體中的所有成員佔用同一段記憶體,它們不能同時存在; 2)sizeof(struct)是記憶體對齊後所有成員長度的總和,sizeof(union)是記憶體對齊後最長資料成員的長度、 結構體為什麼要記憶體對齊呢? 1.平臺原因(移植原因):不是所有的硬體平臺都能訪問任意地址上的任意資料,某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常 2.硬體原因:經過記憶體對齊之後,CPU的記憶體訪問速度大大提升。

4、#define和const的區別 1)#define定義的常量沒有型別,所給出的是一個立即數;const定義的常量有型別名字,存放在靜態區域 2)處理階段不同,#define定義的宏變數在預處理時進行替換,可能有多個複製,const所定義的變數在編譯時確定其值,只有一個複製。

3)#define定義的常量是不可以用指標去指向,const定義的常量可以用指標去指向該常量的地址 4)#define可以定義簡單的函式,const不可以定義函式 5、過載overload,覆蓋(重寫)override,隱藏(重定義)overwrite,這三者之間的區別 1)overload,將語義相近的幾個函式用同一個名字表示,但是引數列表(引數的型別,個數,順序不同)不同,這就是函式過載,返回值型別可以不同 特徵:相同範圍(同一個類中)、函式名字相同、引數不同、virtual關鍵字可有可無 2)override,派生類覆蓋基類的虛擬函式,實現介面的重用,返回值型別必須相同 特徵:不同範圍(基類和派生類)、函式名字相同、引數相同、基類中必須有virtual關鍵字(必須是虛擬函式) 3)overwrite,派生類遮蔽了其同名的基類函式,返回值型別可以不同 特徵:不同範圍(基類和派生類)、函式名字相同、引數不同或者引數相同且無virtual關鍵字 6、new、delete、malloc、free之間的關係 new/delete,malloc/free都是動態分配記憶體的方式 1)malloc對開闢的空間大小嚴格指定,而new只需要物件名 2)new為物件分配空間時,呼叫物件的建構函式,delete呼叫物件的解構函式 既然有了malloc/free,C++中為什麼還需要new/delete呢? 運算子是語言自身的特性,有固定的語義,編譯器知道意味著什麼,由編譯器解釋語義,生成相應的程式碼。

庫函式是依賴於庫的,一定程度上獨立於語言的。

編譯器不關心庫函式的作用,只保證編譯,呼叫函式引數和返回值符合語法,生成call函式的程式碼。

malloc/free是庫函式,new/delete是C++運算子。

對於非內部資料型別而言,光用malloc/free無法滿足動態物件都要求。

new/delete是運算子,編譯器保證呼叫構造和解構函式對物件進行初始化/析構。

但是庫函式malloc/free是庫函式,不會執行構造/析構。

7、delete和delete[]的區別 delete只會呼叫一次解構函式,而delete[]會呼叫每個成員的解構函式 用new分配的記憶體用delete釋放,用new[]分配的記憶體用delete[]釋放 多型,虛擬函式,純虛擬函式 多型:不同物件接收相同的訊息產生不同的動作。

多型包括編譯時多型和執行時多型 執行時多型是:透過繼承和虛擬函式來體現的。

編譯時多型:運算子過載上。

封裝可以隱藏實現細節,使得程式碼模組化;繼承可以擴充套件已存在的程式碼模組(類);它們的目的都是為了——程式碼重用。

多型也有程式碼重用的功能,還有解決專案中緊耦合的問題,提高程式的可擴充套件性。

C++實現多型的機制很簡單,在繼承體系下,將父類的某個函式給成虛擬函式(即加上virtual關鍵字),在派生類中對這個虛擬函式進行重寫,利用父類的指標或引用呼叫虛擬函式。

透過指向派生類的基類指標或引用,訪問派生類中同名覆蓋成員函式。

對於虛擬函式呼叫來說,每一個物件內部都有一個虛表指標,在構造子類物件時,執行建構函式中進行虛表的建立和虛表指標的初始化,該虛表指標被初始化為本類的虛表。

所以在程式中,不管你的物件型別如何轉換,但該物件內部的虛表指標是固定的,所以呢,才能實現動態的物件函式呼叫,這就是C++多型性實現的原理。

需要注意的幾點總結(基類有虛擬函式):1、每一個類都有虛表,單繼承的子類擁有一張虛表,子類物件擁有一個虛表指標;若子類是多重繼承(同時繼承多個基類),則子類維護多張虛擬函式表(針對不同基類構建不同虛表),該子類的物件也將包含多個虛表指標。

2、虛表可以繼承,如果子類沒有重寫虛擬函式,那麼子類虛表中仍然會有該函式的地址,只不過這個地址指向的是基類的虛擬函式實現。

如果基類3個虛擬函式,那麼基類的虛表中就有三項(虛擬函式地址),派生類也會有虛表,至少有三項,如果重寫了相應的虛擬函式,那麼虛表中的地址就會改變,指向自身的虛擬函式實現。

如果派生類有自己的虛擬函式,那麼虛表中就會新增該項。

3、派生類的虛表中虛擬函式地址的排列順序和基類的虛表中虛擬函式地址排列順序相同。

第一:編譯器在發現Father類中有虛擬函式時,會自動為每個含有虛擬函式的類生成一份虛擬函式表,也叫做虛表,該表是一個一維陣列,虛表裡儲存了虛擬函式的入口地址。

第二:編譯器會在每個物件的前四個位元組中儲存一個虛表指標,即(vptr),指向物件所屬類的虛表。

在程式執行時的合適時機,根據物件的型別去初始化vptr,從而讓vptr指向正確的虛表,從而在呼叫虛擬函式時,能找到正確的函式。

第三:所謂的合適時機,在派生類定義物件時,程式執行會自動呼叫建構函式,在建構函式中建立虛表並對虛表初始化。

在構造子類物件時,會先呼叫父類的建構函式,此時,編譯器只“看到了”父類,併為父類物件初始化虛表指標,令它指向父類的虛表;當呼叫子類的建構函式時,為子類物件初始化虛表指標,令它指向子類的虛表。

虛擬函式:在基類中用virtual的成員函式。

允許在派生類中對基類的虛擬函式重新定義。

基類的虛擬函式可以有函式體,基類也可以例項化。

虛擬函式要有函式體,否則編譯過不去。

虛擬函式在子類中可以不覆蓋。

建構函式不能是虛擬函式。

純虛擬函式:基類中為其派生類保留一個名字,以便派生類根據需要進行定義。

包含一個純虛擬函式的類是抽象類。

純虛擬函式後面有=0;抽象類不可以例項化。

但可以定義指標。

如果派生類如果不是先基類的純虛擬函式,則仍然是抽象類。

抽象類可以包含虛擬函式。

8、STL庫用過嗎?常見的STL容器有哪些?演算法用過幾個? STL包括兩部分內容:容器和演算法 容器即存放資料的地方,比如array,vector,分為兩類,序列式容器和關聯式容器 序列式容器,其中的元素不一定有序,但是都可以被排序,比如vector,list,queue,stack,heap,priority-queue,slist 關聯式容器,內部結構是一個平衡二叉樹,每個元素都有一個鍵值和一個實值,比如map,set,hashtable,hash_set 演算法有排序,複製等,以及各個容器特定的演算法 迭代器是STL的精髓,迭代器提供了一種方法,使得它能夠按照順序訪問某個容器所含的各個元素,但無需暴露該容器的內部結構,它將容器和演算法分開,讓二者獨立設計。

9、const知道嗎?解釋一下其作用 const修飾類的成員變數,表示常量不可能被修改 const修飾類的成員函式,表示該函式不會修改類中的資料成員,不會呼叫其他非const的成員函式 const函式只能呼叫const函式,非const函式可以呼叫const函式 10、虛擬函式是怎麼實現的 每一個含有虛擬函式的類都至少有有一個與之對應的虛擬函式表,其中存放著該類所有虛擬函式對應的函式指標(地址), 類的示例物件不包含虛擬函式表,只有虛指標; 派生類會生成一個相容基類的虛擬函式表。

11、堆和棧的區別 1)棧stack存放函式的引數值、區域性變數,由編譯器自動分配釋放 堆heap,是由new分配的記憶體塊,由應用程式控制,需要程式設計師手動利用delete釋放,如果沒有,程式結束後,作業系統自動回收 2)因為堆的分配需要使用頻繁的new/delete,造成記憶體空間的不連續,會有大量的碎片 3)堆的生長空間向上,地址越大,棧的生長空間向下,地址越小 12、關鍵字static的作用 1)函式體內:static修飾的區域性變數作用範圍為該函式體,不同於auto變數,其記憶體只被分配一次,因此其值在下次呼叫的時候維持了上次的值 2)模組內:static修飾全域性變數或全域性函式,可以被模組內的所有函式訪問,但是不能被模組外的其他函式訪問,使用範圍限制在宣告它的模組內 3)類中:修飾成員變數,表示該變數屬於整個類所有,對類的所有物件只有一份複製 4)類中:修飾成員函式,表示該函式屬於整個類所有,不接受this指標,只能訪問類中的static成員變數 注意和const的區別!!!const強調值不能被修改,而static強調唯一的複製,對所有類的物件 13、STL中map和set的原理(關聯式容器) map和set的底層實現主要透過紅黑樹來實現 紅黑樹是一種特殊的二叉查詢樹 1)每個節點或者是黑色,或者是紅色 2)根節點是黑色 3)每個葉子節點(NIL)是黑色。

[注意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!] 4)如果一個節點是紅色的,則它的子節點必須是黑色的 5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。

特性4)5)決定了沒有一條路徑會比其他路徑長出2倍,因此紅黑樹是接近平衡的二叉樹。

14、#include#include“file.h”的區別 前者是從標準庫路徑尋找 後者是從當前工作路徑 15、什麼是記憶體洩漏?面對記憶體洩漏和指標越界,你有哪些方法? 動態分配記憶體所開闢的空間,在使用完畢後未手動釋放,導致一直佔據該記憶體,即為記憶體洩漏。

方法:malloc/free要配套,對指標賦值的時候應該注意被賦值的指標是否需要釋放;使用的時候記得指標的長度,防止越界 16、定義和宣告的區別 宣告是告訴編譯器變數的型別和名字,不會為變數分配空間 定義需要分配空間,同一個變數可以被宣告多次,但是隻能被定義一次 17、C++檔案編譯與執行的四個階段 1)預處理:根據檔案中的預處理指令來修改原始檔的內容 2)編譯:編譯成彙編程式碼 3)彙編:把彙編程式碼翻譯成目標機器指令 4)連結:連結目的碼生成可執行程式 18、STL中的vector的實現,是怎麼擴容的? vector使用的注意點及其原因,頻繁對vector呼叫push_back()對效能的影響和原因。

vector就是一個動態增長的陣列,裡面有一個指標指向一片連續的空間,當空間裝不下的時候,會申請一片更大的空間,將原來的資料複製過去,並釋放原來的舊空間。

當刪除的時候空間並不會被釋放,只是清空了裡面的資料。

對比array是靜態空間一旦配置了就不能改變大小。

vector的動態增加大小的時候,並不是在原有的空間上持續新的空間(無法保證原空間的後面還有可供配置的空間),而是以原大小的兩倍另外配置一塊較大的空間,然後將原內容複製過來,並釋放原空間。

在VS下是1.5倍擴容,在GCC下是2倍擴容。

在原來空間不夠儲存新值時,每次呼叫push_back方法都會重新分配新的空間以滿足新資料的新增操作。

如果在程式中頻繁進行這種操作,還是比較消耗效能的。

19、STL中unordered_map和map的區別 map是STL中的一個關聯容器,提供鍵值對的資料管理。

底層透過紅黑樹來實現,實際上是二叉排序樹和非嚴格意義上的二叉平衡樹。

所以在map內部所有的資料都是有序的,且map的查詢、插入、刪除操作的時間複雜度都是O(logN)。

unordered_map和map類似,都是儲存key-value對,可以透過key快速索引到value,不同的是unordered_map不會根據key進行排序。

unordered_map底層是一個防冗餘的雜湊表,儲存時根據key的hash值判斷元素是否相同,即unoredered_map內部是無序的。

20、C++的記憶體管理 在C++中,記憶體被分成五個區:棧、堆、自由儲存區、靜態儲存區、常量區 棧:存放函式的引數和區域性變數,編譯器自動分配和釋放 堆:new關鍵字動態分配的記憶體,由程式設計師手動進行釋放,否則程式結束後,由作業系統自動進行回收 自由儲存區:由malloc分配的記憶體,和堆十分相似,由對應的free進行釋放 全域性/靜態儲存區:存放全域性變數和靜態變數 常量區:存放常量,不允許被修改 21、建構函式為什麼一般不定義為虛擬函式?而解構函式一般寫成虛擬函式的原因? 1、建構函式不能宣告為虛擬函式 1)因為建立一個物件時需要確定物件的型別,而虛擬函式是在執行時確定其型別的。

而在構造一個物件時,由於物件還未建立成功,編譯器無法知道物件的實際型別,是類本身還是類的派生類等等 2)虛擬函式的呼叫需要虛擬函式表指標,而該指標存放在物件的記憶體空間中;若建構函式宣告為虛擬函式,那麼由於物件還未建立,還沒有記憶體空間,更沒有虛擬函式表地址用來呼叫虛擬函式即構造函數了 2、解構函式最好宣告為虛擬函式 首先解構函式可以為虛擬函式,當析構一個指向派生類的基類指標時,最好將基類的解構函式宣告為虛擬函式,否則可以存在記憶體洩露的問題。

如果解構函式不被宣告成虛擬函式,則編譯器實施靜態繫結,在刪除指向派生類的基類指標時,只會呼叫基類的解構函式而不呼叫派生類解構函式,這樣就會造成派生類物件析構不完全。

子類析構時,要呼叫父類的解構函式嗎? 解構函式呼叫的次序時先派生類後基類的。

和建構函式的執行順序相反。

並且解構函式要是virtual的,否則如果用父類的指標指向子類物件的時候,解構函式靜態繫結,不會呼叫子類的析構。

不用顯式呼叫,會自動呼叫 22、靜態繫結和動態繫結的介紹 靜態繫結和動態繫結是C++多型性的一種特性 1)物件的靜態型別和動態型別 靜態型別:物件在宣告時採用的型別,在編譯時確定 動態型別:當前物件所指的型別,在執行期決定,物件的動態型別可變,靜態型別無法更改 2)靜態繫結和動態繫結 靜態繫結:繫結的是物件的靜態型別,函式依賴於物件的靜態型別,在編譯期確定 動態繫結:繫結的是物件的動態型別,函式依賴於物件的動態型別,在執行期確定 只有虛擬函式才使用的是動態繫結,其他的全部是靜態繫結 23、引用是否能實現動態繫結,為什麼引用可以實現 可以。

因為引用(或指標)既可以指向基類物件也可以指向派生類物件,這一事實是動態繫結的關鍵。

用引用(或指標)呼叫的虛擬函式在執行時確定,被呼叫的函式是引用(或指標)所指的物件的實際型別所定義的。

24、深複製和淺複製的區別 深複製和淺複製可以簡單的理解為:如果一個類擁有資源,當這個類的物件發生複製過程的時候,如果資源重新分配了就是深複製;反之沒有重新分配資源,就是淺複製。

25、什麼情況下會呼叫複製建構函式(三種情況) 系統自動生成的建構函式:普通建構函式和複製建構函式(在沒有定義對應的建構函式的時候) 生成一個例項化的物件會呼叫一次普通建構函式,而用一個物件去例項化一個新的物件所呼叫的就是複製建構函式 呼叫複製建構函式的情形: 1)用類的一個物件去初始化另一個物件的時候 2)當函式的引數是類的物件時,就是值傳遞的時候,如果是引用傳遞則不會呼叫 3)當函式的返回值是類的物件或者引用的時候 舉例: #include#includeusingnamespacestd;classA{private:intdata;public:A(inti){data=i;} //自定義的建構函式A(A&&a); //複製建構函式intgetdata(){returndata;}};//複製建構函式A::A(A&&a){data=a.data;cout<(expression) 該運算子把expression轉換成type-id型別,在編譯時使用型別資訊執行轉換,在轉換時執行必要的檢測(指標越界、型別檢查),其運算元相對是安全的 2)dynamic_cast:執行時的檢查 用於在整合體系中進行安全的向下轉換downcast,即基類指標/引用->派生類指標/引用 dynamic_cast是4個轉換中唯一的RTTI運算子,提供執行時型別檢查。

dynamic_cast如果不能轉換返回NULL dynamic_cast轉為引用型別的時候轉型失敗會拋bad_cast 源類中必須要有虛擬函式,保證多型,才能使用dynamic_cast(expression) 3)const_cast 去除const常量屬性,使其可以修改;volatile屬性的轉換 4)reinterpret_cast 通常為了將一種資料型別轉換成另一種資料型別 27、除錯程式的方法 windows下直接使用vs的debug功能 linux下直接使用gdb,我們可以在其過程中給程式新增斷點,監視等輔助手段,監控其行為是否與我們設計相符 28、extern“C”作用 extern“C”的主要作用就是為了能夠正確實現C++程式碼呼叫其他C語言程式碼。

加上extern“C”後,會指示編譯器這部分程式碼按C語言的進行編譯,而不是C++的。

29、typdef和define區別 #define是預處理命令,在預處理是執行簡單的替換,不做正確性的檢查 typedef是在編譯時處理的,它是在自己的作用域內給已經存在的型別一個別名 typedef(int*)pINT; #definepINT2int* 效果相同?實則不同!實踐中見差別:pINTa,b;的效果同int*a;int*b;表示定義了兩個整型指標變數。

而pINT2a,b;的效果同int*a,b;表示定義了一個整型指標變數a和整型變數b。

30、volatile關鍵字在程式設計中有什麼作用 volatile是“易變的”、“不穩定”的意思。

volatile是C的一個較為少用的關鍵字,它用來解決變數在“共享”環境下容易出現讀取錯誤的問題。

變數如果加了voletile修飾,則會從記憶體中重新裝載內容,而不是直接從暫存器中複製內容。

在本次執行緒內,當讀取一個變數時,為了提高讀取速度,編譯器進行最佳化時有時會先把變數讀取到一個暫存器中;以後,再讀取變數值時,就直接從暫存器中讀取;當變數值在本執行緒裡改變時,會同時把變數的新值copy到該暫存器中,以保持一致。

當變數因別的執行緒值發生改變,上面暫存器的值不會相應改變,從而造成應用程式讀取的值和實際的變數值不一致。

volatile可以避免最佳化、強制記憶體讀取的順序,但是volatile並沒有執行緒同步的語義,C++標準並不能保證它在多執行緒情況的正確性。

C++11開始有一個很好用的庫,那就是atomic類模板,在標頭檔案中,多個執行緒對atomic物件進行訪問是安全的,並且提供不同種類的執行緒同步。

它預設使用的是最強的同步,所以我們就使用預設的就好。

31、引用作為函式引數以及返回值的好處 對比值傳遞,引用傳參的好處: 1)在函式內部可以對此引數進行修改 2)提高函式呼叫和執行的效率(所以沒有了傳值和生成副本的時間和空間消耗) 如果函式的引數實質就是形參,不過這個形參的作用域只是在函式體內部,也就是說實參和形參是兩個不同的東西,要想形參代替實參,肯定有一個值的傳遞。

函式呼叫時,值的傳遞機制是透過“形參=實參”來對形參賦值達到傳值目的,產生了一個實參的副本。

即使函式內部有對引數的修改,也只是針對形參,也就是那個副本,實參不會有任何更改。

函式一旦結束,形參生命也宣告終結,做出的修改一樣沒對任何變數產生影響。

用引用作為返回值最大的好處就是在記憶體中不產生被返回值的副本。

但是有以下的限制: 1)不能返回區域性變數的引用。

因為函式返回以後區域性變數就會被銷燬 2)不能返回函式內部new分配的記憶體的引用。

雖然不存在區域性變數的被動銷燬問題,可對於這種情況(返回函式內部new分配記憶體的引用),又面臨其它尷尬局面。

例如,被函式返回的引用只是作為一個臨時變量出現,而沒有被賦予一個實際的變數,那麼這個引用所指向的空間(由new分配)就無法釋放,造成memoryleak 3)可以返回類成員的引用,但是最好是const。

因為如果其他物件可以獲得該屬性的非常量的引用,那麼對該屬性的單純賦值就會破壞業務規則的完整性。

32、純虛擬函式 純虛擬函式是隻有宣告沒有實現的虛擬函式,是對子類的約束,是介面繼承 包含純虛擬函式的類是抽象類,它不能被例項化,只有實現了這個純虛擬函式的子類才能生成物件 普通函式是靜態編譯的,沒有執行時多型 33、什麼是野指標 野指標不是NULL指標,是未初始化或者未清零的指標,它指向的記憶體地址不是程式設計師所期望的,可能指向了受限的記憶體 成因: 1)指標變數沒有被初始化 2)指標指向的記憶體被釋放了,但是指標沒有置NULL 3)指標超過了變量了的作用範圍,比如b[10],指標b+11 33、執行緒安全和執行緒不安全 執行緒安全就是多執行緒訪問時,採用了加鎖機制,當一個執行緒訪問該類的某個資料時,進行保護,其他執行緒不能進行訪問直到該執行緒讀取完,其他執行緒才可以使用,不會出現資料不一致或者資料汙染。

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

34、C++中記憶體洩漏的幾種情況 記憶體洩漏是指己動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。

1)類的建構函式和解構函式中new和delete沒有配套 2)在釋放物件陣列時沒有使用delete[],使用了delete 3)沒有將基類的解構函式定義為虛擬函式,當基類指標指向子類物件時,如果基類的解構函式不是virtual,那麼子類的解構函式將不會被呼叫,子類的資源沒有正確釋放,因此造成記憶體洩露 4)沒有正確的清楚巢狀的物件指標 35、棧溢位的原因以及解決方法 棧溢位是指函式中的區域性變數造成的溢位(注:函式中形參和函式中的區域性變數存放在棧上) 棧的大小通常是1M-2M,所以棧溢位包含兩種情況,一是分配的的大小超過棧的最大值,二是分配的大小沒有超過最大值,但是接收的buf比原buf小。

1)函式呼叫層次過深,每呼叫一次,函式的引數、區域性變數等資訊就壓一次棧 2)區域性變數體積太大。

解決辦法大致說來也有兩種: 1>增加棧記憶體的數目;如果是不超過棧大小但是分配值小的,就增大分配的大小 2>使用堆記憶體;具體實現由很多種方法可以直接把陣列定義改成指標,然後動態申請記憶體;也可以把區域性變數變成全域性變數,一個偷懶的辦法是直接在定義前邊加個static,呵呵,直接變成靜態變數(實質就是全域性變數) 36、C++標準庫vector以及迭代器 每種容器型別都定義了自己的迭代器型別,每種容器都定義了一隊命名為begin和end的函式,用於返回迭代器。

迭代器是容器的精髓,它提供了一種方法使得它能夠按照順序訪問某個容器所含的各個元素,但無需暴露該容器的內部結構,它將容器和演算法分開,讓二者獨立設計。

38、C++中vector和list的區別 vector和陣列類似,擁有一段連續的記憶體空間。

vector申請的是一段連續的記憶體,當插入新的元素記憶體不夠時,通常以2倍重新申請更大的一塊記憶體,將原來的元素複製過去,釋放舊空間。

因為記憶體空間是連續的,所以在進行插入和刪除操作時,會造成記憶體塊的複製,時間複雜度為o(n)。

list是由雙向連結串列實現的,因此記憶體空間是不連續的。

只能透過指標訪問資料,所以list的隨機存取非常沒有效率,時間複雜度為o(n);但由於連結串列的特點,能高效地進行插入和刪除。

vector擁有一段連續的記憶體空間,能很好的支援隨機存取,因此vector::iterator支援“+”,“+=”,“::iterator則不支援“+”、“+=”、“::iterator和list::iterator都過載了“++”運算子。

總之,如果需要高效的隨機存取,而不在乎插入和刪除的效率,使用vector; 如果需要大量的插入和刪除,而不關心隨機存取,則應使用list。

39、C語言的函式呼叫過程 函式的呼叫過程: 1)從棧空間分配儲存空間 2)從實參的儲存空間複製值到形參棧空間 3)進行運算 形參在函式未呼叫之前都是沒有分配儲存空間的,在函式呼叫結束之後,形參彈出棧空間,清除形參空間。

陣列作為引數的函式呼叫方式是地址傳遞,形參和實參都指向相同的記憶體空間,呼叫完成後,形參指標被銷燬,但是所指向的記憶體空間依然存在,不能也不會被銷燬。

當函式有多個返回值的時候,不能用普通的return的方式實現,需要透過傳回地址的形式進行,即地址/指標傳遞。

傳值:傳值,實際是把實參的值賦值給行參,相當於copy。

那麼對行參的修改,不會影響實參的值。

傳址:實際是傳值的一種特殊方式,只是他傳遞的是地址,不是普通的賦值,那麼傳地址以後,實參和行參都指向同一個物件,因此對形參的修改會影響到實參。

40、C++中的基本資料型別及派生型別 1)整型int 2)浮點型單精度float,雙精度double 3)字元型char 4)邏輯型bool 5)控制型void 基本型別的字長及其取值範圍可以放大和縮小,改變後的型別就叫做基本型別的派生型別。

派生型別宣告符由基本型別關鍵字char、int、float、double前面加上型別修飾符組成。

型別修飾符包括: >short短型別,縮短字長 >long長型別,加長字長 >signed有符號型別,取值範圍包括正負值 >unsigned無符號型別,取值範圍只包括正值 41、友元函式和友元類 友元提供了不同類的成員函式之間、類的成員函式和一般函式之間進行資料共享的機制。

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

友元的正確使用能提高程式的執行效率,但同時也破壞了類的封裝性和資料的隱藏性,導致程式可維護性變差。

1)友元函式 有元函式是可以訪問類的私有成員的非成員函式。

它是定義在類外的普通函式,不屬於任何類,但是需要在類的定義中加以宣告。

friend型別函式名(形式引數); 一個函式可以是多個類的友元函式,只需要在各個類中分別宣告。

2)友元類 友元類的所有成員函式都是另一個類的友元函式,都可以訪問另一個類中的隱藏資訊(包括私有成員和保護成員)。

friendclass類名; 使用友元類時注意: (1)友元關係不能被繼承。

(2)友元關係是單向的,不具有交換性。

若類B是類A的友元,類A不一定是類B的友元,要看在類中是否有相應的宣告。

(3)友元關係不具有傳遞性。

若類B是類A的友元,類C是B的友元,類C不一定是類A的友元,同樣要看類中是否有相應的申明 c++函式庫中一些實用的函式 1.__gcd(x,y) 求兩個數的最大公約數,如__gcd(6,8)就返回2。

2.reverse(a+1,a+n+1) 將陣列中的元素反轉。

a是陣列名,n是長度,跟sort的用法一樣。

值得一提的是,對於字元型陣列也同樣適用。

3.unique(a+1,a+n+1) 去重函式。

跟sort的用法一樣。

不過他返回的值是最後一個數的地址,所以要得到新的陣列長度應該這麼寫:_n=unique(a+1,a+n+1)–a–1. 4.lower_bound(a+1,a+n+1,x);upper_bound(a+1,a+n+1,x) lower_bound是查詢陣列中第一個大於等於x的數,返回該地址,同理也是pos=lower_bound(a+1,a+n+1,x)–a upper_bound是查詢第一個大於x的數,用法和lower_bound一樣 複雜度是二分的複雜度,O(logn)。

(其實就是代替了手寫二分) 5.fill(a+1,a+n+1,x) 例如 int陣列:fill(arr,arr+n,要填入的內容); vector也可以:fill(v.begin(),v.end(),要填入的內容); fill(vector.begin(),cnt,val);//從當前起始點開始,將之後的cnt個元素賦值為val。

memset(arr,val,cnt);//在標頭檔案裡。

將陣列a中的每一個元素都賦成x,跟memset的區別是,memset函式按照位元組填充,所以一般memset只能用來填充char型陣列,(因為只有char型佔一個位元組)如果填充int型陣列,除了0和-1,其他的不能。

參考: https://www.cnblogs.com/liufei1983/p/7099401.html https://blog.csdn.net/ljh0302/article/details/81098764 交流群 歡迎加入公眾號讀者群一起和同行交流,目前有SLAM、演算法競賽、影象檢測分割、人臉人體、醫學影像、自動駕駛、綜合等微信群(以後會逐漸細分),請掃描下面微訊號加群,備註:”暱稱+學校/公司+研究方向“,例如:”張三+上海交大+視覺SLAM“。

請按照格式備註,否則不予透過。

新增成功後會根據研究方向邀請進入相關微信群。

請勿在群內傳送廣告,否則會請出群,謝謝理解~ 如有AI領域實習、求職、招聘、專案合作、諮詢服務等需求,快來加入我們吧,期待和你建立連線,找人找技術不再難! 推薦閱讀 乾貨總結|SLAM面試常見問題及參考解答 2019暑期計算機視覺實習應聘總結 2018年SLAM、三維視覺方向求職經驗分享 經驗分享|SLAM、3Dvision筆試面試問題 經驗分享|2018夏威夷水面無人艇全球競賽 最新AI乾貨,我在看 相關文章 搜尋: Copyright©2022|Poweredbysa123.cc



請為這篇文章評分?