1. 程式人生 > >2018秋招面試問題(三、C++基礎問題)

2018秋招面試問題(三、C++基礎問題)

注:面試過程中整理的學習資料,如有侵權聯絡我即刻刪除。

目錄

C++中expicit的用法

explicit用來修飾類的建構函式(用在類內部的建構函式的宣告上),被修飾的建構函式的類,不能發生隱式型別轉換,只能以顯示的方式做型別轉換。

加了explicit之後,就不能用=來賦了,只能用s = A temp(10);

new和maloc的區別?

1.new/delete是C++關鍵字,需要編譯器支援。malloc/free是庫函式,需要標頭檔案支援。

2.使用new操作符申請記憶體分配時無須指定記憶體塊的大小,編譯器會根據型別資訊自行計算。而malloc則需要顯式地指出所需記憶體的大小。

p = (int *)malloc(100 * sizeof(int));

p = new int;

3.new一個物件的時候在申請記憶體的時候會呼叫物件的建構函式,而malloc只會申請記憶體;同樣,delete在釋放記憶體之前,會呼叫解構函式,而free只是釋放記憶體。

4.new操作符記憶體分配成功時,返回的是物件型別的指標,型別嚴格與物件匹配,無須進行型別轉換,故new是符合型別安全性的操作符。而malloc記憶體分配成功則是返回void * ,需要通過強制型別轉換將void*指標轉換成我們需要的型別。

5.new記憶體分配失敗時,會丟擲bac_alloc異常。malloc分配記憶體失敗時返回null。

6.C++允許new/delete過載,不允許malloc過載。

//new操作符從自由儲存區(free store)上為物件動態分配記憶體空//間,而malloc函式從堆上動態分配記憶體。自由儲存區不等於堆。

new運算子的原理

new的內部實現分為兩步:分配記憶體和呼叫建構函式初始化

記憶體分配

呼叫相應的 operator new(size_t) 函式,動態分配記憶體。如果 operator new(size_t) 不能成功獲得記憶體,則呼叫 new_handler() 函式用於處理new失敗問題。如果沒有設定 new_handler() 函式或者 new_handler() 未能分配足夠記憶體,則丟擲 std::bad_alloc 異常

< “new運算子”所呼叫的 operator new(size_t) 函式,按照C++的名字查詢規則,首先做依賴於實參的名字查詢(即ADL規則),在要申請記憶體的資料型別T的 內部(成員函式)、資料型別T定義處的名稱空間查詢;如果沒有查詢到,則直接呼叫全域性的 ::operator new(size_t) 函式。>

建構函式:

在分配到的動態記憶體塊上初始化相應型別的物件(建構函式)並返回其首地址。如果呼叫建構函式初始化物件時丟擲異常,則自動呼叫 operator delete(void*, void*) 函式釋放已經分配到的記憶體。

delete的內部實現:

首先呼叫相應類的解構函式,然後再呼叫operator delete函式。

operator new內部是用的malloc,operator delete內部是用free。

new的過載

new過載其實過載的是內部的operator new函式以及operator delete函式。operator new的第一個引數必須是size_t,也就是無符號整型,返回值得是void*。operator delete的引數必須是void*,當然還可以有別的引數。

堆和棧的區別?為什麼棧比堆快?

(1)棧是程式編譯時系統自動分配空間,而堆是執行時程式設計師手動分配空間。

(2)堆在分配和釋放時都要呼叫函式(malloc/free),分配時會去堆空間尋找足夠大小的空間,這些都會花費一定的時間,而棧不會。

(3)訪問棧是直接訪問的,而訪問堆是間接定址的,第一次取得指標,第二次才能取出值。

棧是自頂向下的,棧頂在低地址,棧底在高地址。

函式呼叫的壓棧和出棧過程

壓棧:在函式呼叫時,第一個進棧的是函式的各個引數,然後是主函式中函式呼叫後的下一條指令(函式呼叫語句的下一條可執行語句)的地址,然後是函式中的區域性變數。注意靜態變數是不入棧的。

出棧:就是和壓棧反著的一個過程。

函式呼叫時引數的壓棧順序為什麼是從右到左?

這主要是因為有不定長引數的函式,為了支援可變長引數,比如printf(const char* format,…),實際應用中它的引數個數是由format中的%佔位符的個數來決定的,如果壓棧是從左到右的話,format先進棧,上面壓著未知個數的引數,這樣就無法知道引數的個數了,所以要從右到左進棧,讓format挨著棧頂。

陣列和vector相比,使用上如何選擇?

vector是變長陣列,有很多現成的函式可以使用,比如判空、返回長度等,還可以動態新增刪除資料,但是記憶體和計算的開銷更大些。陣列是定長的,執行效率更高一些,訪問效率很高。如果我們知道陣列的總長,並且不用隨時增加刪除元素,那就可以選擇陣列,如果不定長,且需要隨時增加刪除元素,那就選擇vector更加方便。

順序容器有哪些?

順序容器就是指各容器之間有順序關係的線性表。

deque、list、vector。其實就是線性表。每個元素都有固定位置,位置由新增進去的次序決定的。List是雙向連結串列。

vector、deque與list的區別是,vector、deque記憶體是連續的,而list是不連續的,list不能用下標訪問元素,vector、deque的查詢很方便,而list的插入和刪除很方便。vector和deque的區別是deque可以支援首元素的刪除插入(也支援尾元素),而vector只能插入刪除尾部。

關聯容器有哪些?

關聯容器的底層是非線性的紅黑樹結構,預設插入的時候是按鍵值key升序排列。

map、set、multi_map、multi_set。map是key-value模式,是一個key對應一個值,set是單值模式,單值其實就是set的key與值相等(set的value之間不可以相等),並且set值是不可以修改的。multi_map就是一個key可以對應多個value,multi_set就是value之間可以相等。

C++中區域性變數和全域性變數可以重名嗎?

重名是看作用域

可以。區域性宣告的作用域是從宣告的那一點開始,直到這個宣告所在的那個塊結束為止。全域性變數的作用域是從宣告的那一點開始,直到這個宣告所在的檔案的結束為止。與全域性變數重名的區域性變數在塊內可以遮蔽全域性變數,如果想在塊內使用全域性變數可以通過作用域解析符::來引用。

reease和debug的區別

一組編譯選項的不同。

Debug版: 經過編譯器編譯出的專案.exe檔案大,而且生成的二進位制命令沒有經過編譯器的優化。專案中包含著豐富的除錯資訊,供programer除錯程式。這就是為什麼,當我們在Debug程式的時候,為什麼程式就會在我們設定斷電的地方自動停下,而且彷彿時間靜止,還可以顯示此時相關變數的狀態。

Release版: 這個版本是的出發點是使用者,所以不儲存除錯資訊,編譯器在編譯的時候進行了各種優化,進而達到,程式碼檔案最小執行速度最優

優化了以下:

(1)變數,Debug版如果你不初始化變數,變數自動初始化為0xCC,刪除動態分配的記憶體時將其賦值為0xCD。Release版不會自動對變數初始化,刪除動態分配的記憶體時也不對記憶體中的資料進行處理。 (2)記憶體分配的長度,debug版以32bytes為單位分配,release版以8bytes為單位。比如你定義int a[4]; 在Debug版裡分配的記憶體長度是32byte,而在Release版裡則分配16byte。所以在debug版裡如果你定義a[4],卻在程式裡使用a[4] = xxx不會出問題,但是在release版裡就會出問題. (3)變數的型別,Release版裡對於經常使用的變數會自動使其變成暫存器變數,以加快程式執行速度。

C++中static的作用

static最重要的作用是隱藏,對於全域性變數來說,所有未加static字首的全域性變數和函式都具有全域性可見性,加extern其它的原始檔也能訪問。如果加了static,就會對別的原始檔隱藏,利用這一特性可以在不同的檔案中定義同名函式和同名變數,而不必擔心命名衝突。

對於區域性變數來說,加了static延長了生命期。如果一個函式經常被呼叫,我們希望在下一次呼叫之前可以儲存其中一個區域性變數的值,那麼就可以加static把它變為靜態區域性變數。全域性當然也可以,但是沒必要,破化了訪問範圍。

類與類的關係有哪些

縱向繼承:一個類繼承另外的一個類的功能,並可以增加它自己的新功能的能力(空心三角箭頭 子類指向父類 )實現:一個類實現一個介面(空心三角箭頭,類指向介面) 橫向:依賴 :一個類使用到另一個類。關係是臨時的偶然的,但類B的變化會影響到A(虛線箭頭)。程式碼中就是類B作為函式引數被類A在某個方法當中使用。關聯:兩個類關係是長期的,雙方平等的(實線箭頭),比如我和朋友。(可能有引用對方一個全域性變數之類的)聚合:是關聯關係的一種特例。就看它們之間是不是整體與部分,一個屬於另外一個的關係。可以分離,有各自的生命週期,部分可以屬於多個整體,也可以為多個整體共享(空心菱形加實線箭頭),比如公司和員工。組合:也是關聯關係的一種特例。也是整體與部分關係,不可分,整體的生命週期結束,部分的也結束(實心菱形加實線箭頭),比如人和人的大腦。

如何畫一個類圖

用Rational Rose工具來畫uml類圖,首先建立類,建立類的名字屬性、方法,然後再建立類與類之間的關係,關係在工具欄中有顯示。

每個類都有三個基本元素:類名、屬性、方法

根據類與類的關係來畫一個類圖。

虛線箭頭指向依賴;實線箭頭指向關聯;虛線三角指向介面;實線三角指向父類;空心菱形能分離而獨立存在,是聚合;實心菱形精密關聯不可分,是組合;

關於類成員的訪問許可權問題

< 私有成員只允許本類的成員函式和友元函式訪問,類外部的任何訪問都是非法的。Protected成員在沒有繼承的時候訪問許可權與private相同,在有繼承的時候,派生類可以訪問父類的protected成員。>

  • 派生類的sizeof大小,無論是何種方式繼承,都是基類中的成員變數之和加上虛擬函式再加上派生類中定義的變數,不管派生類中定義的是與基類同名的變數還是不同名的,都要加上這部分。派生類會隱藏基類同名的變數,但是空間卻各是各的
  • 無論何種繼承方式,派生類中無法訪問基類的private成員,卻一直都能訪問protected成員和public成員。
  • 在main函式中,一直無法訪問private成員變數,也無法訪問protected成員。只有繼承之後仍是public成員的,main函式才能訪問。