1. 程式人生 > >C++面試題系列一:簡答題(1)

C++面試題系列一:簡答題(1)

1.C++支援多重繼承,請問多重繼承中的菱形繼承(diamond problem)指什麼,請舉例說明並指出一個解決方案。

菱形繼承是在繼承關係層次圖中,構成一個菱形的迴路,比如C++標準庫中形成iostream類的多重繼承:ios有兩個派生類ostream和istream,而類iostream同時繼承ostream和istream,因此iostream類中可能包含重複子物件(即ostream和istream從ios繼承的資料)。這個問題可能在iostream指標向上轉換為ios指標時出現,這時會導致歧義性,造成語法錯誤 ambiguous conversion from 'class iostream *' to 'class ios *'


這種重複子物件問題可以用virtual繼承解決。基類用virtual繼承時,派生類中只出現一個子物件,這個過程稱為虛擬基類繼承。
class ostream: virtual public ios{};
class istream: virtual public ios{};

2.遞迴函式能否宣告為inline?

inline在編譯期間會在呼叫處被展開,由於編譯器在編譯時無法確定遞迴呼叫的次數,所以無法展開inline函式。除非能夠給編譯器指定遞迴函式被調次數,否則inline函式不能被遞迴呼叫。

3.解釋關鍵字volatile和mutable。

volatile關鍵字是一種型別修飾符,用它宣告的型別變量表示可以被某些編譯器未知的因素更改,比如:作業系統、硬體或者其它執行緒等。遇到這個關鍵字宣告的變數,編譯器對訪問該變數的程式碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。簡單地說,就是防止編譯器對程式碼的優化。
mutable:const_cast運算子允許強制轉換常量性,C++提供了一個儲存類說明符(storage class specifier)mutable代替const_cast。即使在const物件和const成員函式中,mutable資料成員也是可以修改的,不需要強制轉換常量性。

4.#include<>與#include”“的區別?

優先搜尋路徑不同。引號編譯器會優先搜尋當前工作目錄下的標頭檔案,尖括號會優先搜尋標準庫標頭檔案所在目錄。

5.怎樣定義一個純虛擬函式?純虛擬函式與抽象類的關係?抽象類的特點?

將虛擬函式賦值為0即可得到一個純虛擬函式;包含純虛擬函式的類是抽象類。抽象類有以下特點:不能例項化;不能作為函式引數及函式返回型別;可以定義抽象類型別的指標。
抽象類一般作為基類使用。對於一個抽象基類,如果其派生類沒有重新定義基類的虛擬函式,則派生類也還是抽象類,只有派生類重新定義了基類的虛擬函式,派生類才不再是抽象類,才是一個可以建立物件的具體類。

6.malloc/free與new/delete的區別?

1)malloc/free是C/C++標準庫函式,而new/delete是C++中的操作符
2)malloc申請的記憶體用free函式釋放,而new生成的物件用delete操作符釋放
3)malloc申請記憶體時,需要我們指定申請的空間大小,且返回的型別為void*,需要將其強制轉換為所需型別指標;new申請記憶體時,會根據所申請的型別自動計算申請空間的大小,且可直接返回指定型別的指標
4)malloc/free申請釋放記憶體時,不需要呼叫構造/解構函式,而new/delete申請釋放記憶體時需要呼叫構造/解構函式

7.動態分配與靜態分配的區別?

動態分配是指在編譯期間無法確定需要分配的記憶體大小,在程式執行中,等記憶體大小確定以後才申請記憶體。靜態分配在編譯期間就分配好已知大小的記憶體空間。動態分配可以發生在堆或棧上,而靜態分配只能是棧。

8.建構函式的列表初始化順序?哪三種情況是必須的?

  1. const資料成員 2.引用資料成員 3. 沒有預設建構函式的成員物件

9.深拷貝與淺拷貝的區別?

淺拷貝就是兩個物件共享一塊記憶體,任何一方的變動都會影響到另一方,而且當析構一個源物件後,拷貝物件也不復存在,如果再使用它就會發生錯誤。深拷貝就是完完全全的複製出一個物件,兩者在記憶體上互相獨立。

10.請解釋靜態區域性變數(在函式中定義)與靜態資料成員(在類中定義)的不同特點。

靜態區域性變數:
1. 在程式執行到該物件的宣告處時被首次初始化,且以後不再初始化;
2. 一般在宣告處初始化,如果沒有顯式初始化,會被程式自動初始化為0;
3. 始終駐留在全域性資料區,直到程式執行結束,但作用域是區域性的,當定義它的函式或語句塊結束時,其作用域隨之結束。
靜態資料成員:
1. 只分配一次記憶體,供所有物件例項共享。但不屬於特定的類物件,在沒有產生類物件時其作用域就可見。
2. 在定義時要分配空間,所以不能在類宣告時進行初始化(const static變數除外)。靜態資料成員的初始化格式為:資料型別 類名:: 靜態資料成員名=值。

11.堆和棧的區別?

1)棧,由編譯器自動管理,無需我們手工控制;堆,申請釋放工作由程式設計師控制
2)堆的生長方向是向上的,也就是向著記憶體地址增加的方向;棧的生長方向是向下的,是向著記憶體地址減小的方向增長。 3)對於堆來講,頻繁的
new/delete 勢必會造成記憶體空間的不連續,從而造成大量的碎片,使程式效率降低。對於棧來講,則不會存在這個問題 4)一般來講在 32
位系統下,堆記憶體可以達到4G的空間,但是對於棧來講,一般都是有一定的空間大小的(2M)。

12.#define與typedef的區別?

1) #define是預處理指令,在編譯預處理時進行簡單的替換,不作正確性檢查。
2)typedef是在編譯時處理的。它在自己的作用域內給一個已經存在的型別一個別名。
typedef int * pint ;
#define PINT int *
const pint p ; //pint是一種指標型別, p不可更改,但p指向的內容可更改
const PINT ; //p可更改,但是p指向的內容不可更改

13.dynamic_cast的作用?

dynamic_cast將一個基類物件指標(或引用)(需指向派生類或為派生類的引用)cast到派生類指標。dynamic_cast會根據基類指標是否真正指向繼承類物件來做相應處理,即會作一定的判斷。對指標進行dynamic_cast,失敗返回null,成功返回正常cast後的物件指標; 對引用進行dynamic_cast,失敗丟擲一個異常,成功返回正常cast後的物件引用。

14.STL演算法中partition演算法的用法?

對指定範圍內的元素重新排序,使用輸入的函式,把結果為true的元素放在結果為false的元素之前。stable_partition版本保留原始容器中的相對順序。如使陣列中奇數位於偶數前面。

15.malloc、alloc、calloc、realloc的區別?

alloc:唯一在棧上申請記憶體的,無需釋放;
malloc:在堆上申請記憶體,最常用;
calloc:malloc+初始化為0;
realloc:將原本申請的記憶體區域擴容,引數size大小即為擴容後大小,因此此函式要求size大小必須大於ptr記憶體大小。

16.不能宣告為虛擬函式的幾種情況?

1)普通函式(非類成員函式)(不能被覆蓋)
2)友元函式(C++不支援友元函式繼承)
3)行內函數(編譯期間展開,虛擬函式是在執行期間繫結)
4)建構函式(沒有物件不能使用虛擬函式,先有建構函式後有虛擬函式,虛擬函式是對物件的動作(建構函式不能繼承))
5)靜態成員函式(只有一份大家共享)

17.memcpy的用法與strcpy之間的區別?

memcpy函式的功能是從源指標所指的記憶體地址的起始位置開始拷貝n個位元組到目標指標所指的記憶體地址的起始位置中。
void *memcpy(void *dest, const void *src, size_t n); //函式返回指向dest的指標
strcpy和memcpy主要有以下3方面的區別。
1)複製的內容不同。strcpy只能複製字串,而memcpy可以複製任意內容,例如字元陣列、整型、結構體、類等。
2)複製的方法不同。strcpy不需要指定長度,它遇到被複制字元的串結束符”\0”才結束,所以容易溢位。memcpy則是根據其第3個引數決定複製的長度。
3)用途不同。通常在複製字串時用strcpy,而需要複製其他型別資料時則一般用memcpy。

18.static全域性變數與全域性變數的區別?

1)全域性變數預設是有外部連結性的,作用域是整個工程,在一個檔案內定義的全域性變數,在另一個檔案中,通過extern 全域性變數名的宣告,就可以使用全域性變數。
2)靜態全域性變數是顯式用static修飾的全域性變數,作用域是宣告此變數所在的檔案,其他的檔案即使用extern宣告也不能使用。

19.rand()函式的用法?srand函式的用法?

rand();
srand((unsigned int)(time NULL));

20.什麼是常量表達式?

常量表達式是指不會改變並且在編譯過程就能得到計算結果的表示式。
新標準規定,允許將變數宣告為constexpr型別以便由編譯器來驗證變數的值是否是一個常量表達式。宣告為constexpr的變數一定是一個常量,而且必須用常量表達式初始化。

21.友元函式及友元類?

類可以允許其他類或者函式訪問它的非公有成員,方法是令其他類或者函式成為它的友元。 友元宣告只能出現在類的內部。

22.explicit關鍵字的作用?

關鍵字explicit只能作用於只有一個實參的建構函式(需要多個實參的建構函式不能用於執行隱式轉換,所以無須將這些建構函式指定為explicit的)。只能在類內宣告建構函式時使用explicit關鍵字,在類外部定義時不應重複。
當使用explicit關鍵字宣告建構函式時,它將只能以直接初始化的形式使用。而且,編譯器將不會在自動轉換過程中使用該建構函式。

23.智慧指標有哪幾種?

shared_ptr允許多個指標指向同一個物件;unique_ptr則“獨佔”所指向的物件。標準庫還定義了一個名為weak_ptr的伴隨類,它是一種弱引用,指向shared_ptr所管理的物件。

24.什麼時候會遞增遞減shared_ptr的引用計數?

賦值,拷貝,向函式傳遞一個智慧指標,或函式返回一個智慧指標都會增加當前智慧指標的計數;向一個shared_ptr賦予一個新值或者一個shared_ptr被銷燬時,計數器就會遞減。

25.預設建構函式?

預設建構函式(default constructor)就是在沒有顯式提供初始化式時呼叫的建構函式。它由不帶引數的建構函式,或者為所有的形參提供預設實參的建構函式定義。
如果提供任何建構函式,那麼編譯器將自動生成一個預設的無參建構函式,一旦定義了建構函式,將不再生成預設的建構函式。 有多種原因,需要提供預設建構函式:
1) 當你使用靜態分配的陣列,而陣列元素型別是某個類的物件時,就要呼叫預設的建構函式。
2) 當你使用動態分配的陣列,而陣列元素型別是某個類的物件時,就要呼叫預設的建構函式,因為new操作符要呼叫Object類的無參建構函式類初始化每個陣列元素。
3) 當你使用標準庫的容器時,如果容器內的元素型別是某個類的物件時,那麼這個類就需要預設的建構函式,原因同上。
4) 一個類A以另外某個類B的物件為成員時,如果A提供了無參建構函式,而B未提供,那麼A則無法使用自己的無參建構函式。
5) 類A定義了拷貝建構函式,而沒有提供預設的建構函式,B繼承自A,所以B在初始化時要呼叫A的建構函式來初始化A,而A沒有預設的建構函式,故產生編譯錯誤。

26.STL演算法中的copy演算法

原型:copy(vec.begin(),vec.end(),dest.begin());dest容器的空間要足夠大。另有copy_n(vec.begin(),n,dest.begin());move(vec.begin(),vec.end(),dest.begin())與此類似。

27.什麼時候使用拷貝建構函式?

一個物件顯式或隱式初始化另一個同類型的物件。
函式的非引用傳參。
函式非引用返回一個物件。
初始化順序容器中的元素。
根據元素初始化列表初始化陣列元素。

28.類模板的優點?

可用來建立動態增長和減小的資料結構
型別無關,因此具有很高的可複用性
在編譯時而不是執行時檢查資料型別,保證了型別安全
平臺無關,可移植性強

29.如何用位方法求x%y?

通用公式為x&(y-1)

30.假定CSomething是一個類,執行下面這些語句之後,記憶體裡建立了__個CSomething物件?

CSomething a();// 沒有建立物件,這裡不是使用預設建構函式,而是定義了一個函式
CSomething b(2);//使用一個引數的建構函式,建立了一個物件。
CSomething c[3];//使用無參建構函式,建立了3個物件。
CSomething &ra=b;//ra引用b,沒有建立新物件。
CSomething d=b;//使用拷貝建構函式,建立了一個新的物件d。
CSomething *pA = c;//建立指標,指向物件c,沒有構造新物件。
CSomething *p = new CSomething(4);//新建一個物件。

31.void *memset(void *s, int ch, size_t n)的作用?

將s中前n個位元組用ch替換並返回s。 作用是在一段記憶體塊中填充某個給定的值,它是對較大的結構體或陣列進行清零操作的一種最快方法,通常為新申請的記憶體做初始化工作。

32.為什麼拷貝建構函式的類物件的形參必須是引用型別?

若不是引用型別:為了呼叫拷貝建構函式,必須拷貝它的實參,為了拷貝它的實參,我們又需要呼叫拷貝建構函式,如此無限迴圈。則我們的呼叫永遠不會成功。

33.合成的拷貝建構函式?

沒有定義拷貝建構函式,編譯器就會為我們合成一個。與合成的預設建構函式不同,即使我們定義了其他建構函式,也會合成拷貝建構函式。合成拷貝建構函式的行為是,執行逐個成員初始化,將新物件初始化為原物件的副本(淺拷貝)。編譯器將現在物件的每個非static成員,依次複製到正建立的物件。合成複製建構函式直接複製內建型別成員的值,類型別成員使用該類的複製建構函式進行復制。 由此可見,當使用合成的拷貝建構函式時,由於淺複製的問題,容易出現錯誤。

34.編寫拷貝賦值運算子時應該注意的問題?

1:形參列表應為const &型別:常量確保在函式內不改變傳入例項的狀態,引用避免呼叫拷貝建構函式,以提高效率
2:返回應該為引用型別,以方便連續賦值
3:判斷傳入的引數和當前例項是否是同一個例項,以避免出現刪除自身記憶體的情況
4:刪除例項自身已有的記憶體,避免出現記憶體洩露
更高階的方法是:建立一個臨時例項,作為傳入例項的副本,然後將當前例項資料成員和臨時例項資料成員進行交換。當程式執行出臨時例項的建立範圍後,程式會自動呼叫解構函式析構臨時例項。

35.過載遞增++遞減–運算子。

前置版本返回引用,後置版本返回值。為解決前置和後置運算子無法區分的問題,後置版本接收一個額外的(不被使用)int型別的形參。當我們使用後置運算子時,編譯器為這個形參提供一個值為0的實參。這個形參的唯一作用就是區分前置版本和後置版本的函式,而不是真的要在實現後置版本時參與運算。

//前置運算子,返回引用
A &operator++(){v++;return *this}
A &operator--(){v--;return *this}
//後置運算子,返回值
A operator++ (int x){int temp=v;v++;return tmp;}
A operator-- (int x){int temp=v;v--;return tmp;}

36.int i=-2147483648;則~i,-i,1-i,-1-i分別為什麼?

計算機中以補碼錶示資料和運算:
-2147483648(-2^31)的二進位制表示為:1000 0000 0000 0000 0000 0000 0000 0000
則~i=2^31-1=2147483647
對一個數求負,相當於對其求補運算,即仍為1000 0000 0000 0000 0000 0000 0000 0000,也可以這樣理解(-2^31+2^32=2^31,2^31不能由正數表示,還需用負數表示,為-2^31)
1-i相當與-i+1,即拿-i的補碼和1相加,為-2147483647
-1-i即拿-1的補碼和-i的補碼相加:-1的補碼為1111 1111 1111 1111 1111 1111 1111 1111,相加得0111 1111 1111 1111 1111 1111 1111 1111為2^31-1=2147483647

(整理自網路)