1. 程式人生 > >C++程式設計師面試常見題目

C++程式設計師面試常見題目


  • 歡迎大家指正問題


 1:請用簡單的語言告訴我C++ 是什麼?

答:C++是在C語言的基礎上開發的一種面向物件程式語言,應用廣泛。C++支援多種程式設計正規化 --面向物件程式設計、泛型程式設計和過程化程式設計。 其程式設計領域眾廣,常用於系統開發,引擎開發等應用領域,是最受廣大程式設計師受用的最強大程式語言之一,支援類:類、封裝、過載等特性!

2:C和C++的區別?

C++在C的基礎上增添類

C是一個結構化語言,它的重點在於演算法和資料結構。

C程式的設計首要考慮的是如何通過一個過程,對輸入(或環境條件)進行運算處理得到輸出(或實現過程(事務)控制),而對於C++,首要考慮的是如何構造一個物件模型,讓這個模型能夠契合與之對應的問題域,這樣就可以通過獲取物件的狀態資訊得到輸出或實現過程(事務)控制。

3: C ++ 在c基礎上加了什麼?

A:包含全部的C語言部分。
B:面向物件部分,封裝,繼承,多型。
C:泛型程式設計部分,模板,方便使用。
D:STL庫。

4:什麼是面向物件(OOP)?

答:面向物件是一種對現實世界理解和抽象的方法、思想,通過將需求要素轉化為物件進行問題處理的一種思想。

5:解釋下封裝、繼承和多型?(面向物件的三個特徵)

一、封裝:

封裝是實現面向物件程式設計的第一步,封裝就是將資料或函式等集合在一個個的單元中(我們稱之為類)。也就是把客觀事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信的類或者物件操作,對不可信的進行資訊隱藏。

封裝的意義在於保護或者防止程式碼(資料)被我們無意中破壞。

二、繼承:

繼承主要實現重用程式碼,節省開發時間。

子類可以繼承父類的一些東西。

繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴充套件. 通過繼承建立的新類稱為“子類”或“派生類”。繼承的過程,就是從一般到特殊的過程。要實現繼承,可以通過“繼承”和“組合”來實現。

三、多型

多型:同一操作作用於不同的物件,可以有不同的解釋,產生不同的執行結果。在執行時,可以通過指向基類的指標,來呼叫實現派生類中的方法。

多型性是允許你將父物件設定成為和一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作。簡單的說,就是一句話:允許將子類型別的指標賦值給父類型別的指標。多型在C++中是通過虛擬函式實現的。

實現多型,有二種方式,覆蓋,過載。

覆蓋,是指子類重新定義父類的虛擬函式的做法。
過載,是指允許存在多個同名函式,而這些函式的引數表不同(或許引數個數不同,或許引數型別不同,或許兩者都不同)。

總結:作用

① 封裝可以隱藏實現細節,使得程式碼模組化
② 繼承可以擴充套件已存在的程式碼模組(類);它們的目的都是為了——程式碼重用
③ 多型則是為了實現另一個目的——介面重用!多型的作用,就是為了類在繼承和派生的時候,保證使用“家譜”中任一類的例項的某一屬性時的正確呼叫。

6 多型作用

程式碼模組化,擴充套件程式碼模組,實現程式碼重用。

7. 簡述多型的實現原理

編譯器發現一個類中有虛擬函式,便會立即為此類生成虛擬函式表vtable。虛擬函式表的各表項為指向對應虛擬函式的指標。編譯器還會在此類中隱含插入一個指標 vptr指向虛擬函式表。呼叫此類的建構函式時,在類的建構函式中,編譯器會隱含執行 vptr 與 vtable 的關聯程式碼,將 vptr 指向對應的 vtable,將類與此類的 vtable 聯絡了起來。另外在呼叫類的建構函式時,指向基礎類的指標此時已經變成指向具體的類的 this 指標,這樣依靠此 this 指標即可得到正確的 vtable。

如此才能真正與函式體進行連線,這就是動態聯編,實現多型的基本原理。

注意:一定要區分虛擬函式,純虛擬函式、虛擬繼承的關係和區別。牢記虛擬函式實現原理,因為多型C++面試的重要考點之一,而虛擬函式是實現多型的基礎。

8 什麼是“引用”?宣告和使用“引用”要注意哪些問題?

引用的特性:

   引用是目標變數的別名,對引用的操作與對變數的操作效果一樣。宣告引用的時候要必須對其初始化。引用宣告完後,相當於目標變數有兩個名稱,不能再把引用作為其他變數的別名。

   引用不是新定義一個變數,它只是表示該引用是目標變數名的一個別名,它本身不是一種資料型別,因此引用不佔用儲存單元。

   無法建立陣列的引用。因為陣列是一個由若干元素組成的集合,無法建立陣列的別名。

引用的作用:

作為函式的引數,以前用值傳遞,現在用指標或引用。

傳引用和傳指標給函式效果一樣的。

傳遞引用,記憶體中沒有生成實參副本,是直接對實參操作。如果傳遞的是值型別,需要在棧上生成副本,如果是物件,還要呼叫建構函式。

指標呼叫的時候,其實也會形參分配儲存單元,且需要用“指標變數名”的形式運算,容易產生錯誤並且可讀性差;呼叫的時候,需要用變數的地址作為實參,呼叫形式不好看。引用沒有這些問題。

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

但是,引用作為返回值注意事項:

A:不能返回區域性變數的引用。

B:不能返回函式內部new的變數。因為引用僅僅是別名,無法釋放記憶體。

C: 可以返回類成員的引用,但是最好是const

D : 引用和指標一樣,可以產生多型的效果。

總結:

A: 引用的使用主要用於函式傳參,解決大塊資料或物件的問題。
B: 用引用傳遞函式引數,不產生副本,通過const,保證引用傳遞的安全性。
C:比指標的可讀性好,

9:引用和多型的區別

引用和指標可以實現多型。(說各自的含義)

10:指標和引用的區別?

1. 指標是一個變數,只不過這個變數儲存的是一個地址,指向記憶體的一個儲存單元;而引用僅是個別名;

2. 引用使用時無需解引用(*),指標需要解引用;

3. 引用只能在定義時被初始化一次,之後不可變;指標可變;

4. 引用沒有 const,指標有 const;

5. 引用不能為空NULL,指標可以為空;

6. “sizeof 引用”得到的是所指向的變數(物件)的大小,而“sizeof 指標”得到的是指標本身的大小;

7. 指標和引用的自增(++)運算意義不一樣;

8. 指標可以有多級,但是引用只能是一級(int **p;合法 而 int &&a是不合法的)

9.從記憶體分配上看:程式為指標變數分配記憶體區域,而引用不需要分配記憶體區域。

10:引用使用在原始碼級相當於普通的變數一樣使用,做函式引數時,內部傳遞的實際是變數地址

 

① 引用必須被初始化,但是不分配儲存空間。指標不必在宣告時初始化,在初始化的時候需要分配儲存空間
② 引用初始化以後不能被改變,指標可以改變所指的物件
③ 不存在指向空值的引用,但是存在指向空值的指標

11.將“引用”作為函式引數有哪些特點?

(1)傳遞引用給函式與傳遞指標的效果是一樣的。這時,被調函式的形參就成為原來主調函式中的實參變數或物件的一個別名來使用,所以在被調函式中對形參變數的操作就是對其相應的目標物件(在主調函式中)的操作。

(2)使用引用傳遞函式的引數,在記憶體中並沒有產生實參的副本,它是直接對實參操作;而使用一般變數傳遞函式的引數,當發生函式呼叫時,需要給形參分配儲存單元,形參變數是實參變數的副本;如果傳遞的是物件,還將呼叫拷貝建構函式。因此,當引數傳遞的資料較大時,用引用比用一般變數傳遞引數的效率和所佔空間都好。

(3)使用指標作為函式的引數雖然也能達到與使用引用的效果,但是,在被調函式中同樣要給形參分配儲存單元,且需要重複使用"*指標變數名"的形式進行運算,這很容易產生錯誤且程式的閱讀性較差;另一方面,在主調函式的呼叫點處,必須用變數的地址作為實參。而引用更容易使用,更清晰。

12.在什麼時候需要使用“常引用”?

如果既要利用引用提高程式的效率,又要保護傳遞給函式的資料不在函式中被改變,就應使用常引用。

常引用宣告方式:const型別識別符號&引用名=目標變數名;

例1

int a;

const int &ra = a;

ra = 1; //錯誤

a = 1; //正確

例2

string foo();

void bar(string & s);

那麼下面的表示式將是非法的:

bar(foo());

bar("hello world");

原因在於foo( )和"hello world"串都會產生一個臨時物件,而在C++中,這些臨時物件都是const型別的。因此上面的表示式就是試圖將一個const型別的物件轉換為非const型別,這是非法的。引用型引數應該在能被定義為const的情況下,儘量定義為const。

 

const int &ra = a;   // 不能通過引用對目標變數的值進行修改,從而使引用的目標成為const的,安全。

void bar(String &ra)

bar("AA") // 這個會報錯,因為 ”AA“相當於 const char[], 不能傳遞給bar函式。

可以把函式宣告為Void bar(Const String &ra), 上述語句就不會報錯。

13 常引用的作用

傳遞給函式的資料在函式中不被改變

14將“引用”作為函式返回值型別的格式、好處和需要遵守的規則?

int &fun(int a) {}

好處:不會生成副本。

規則:不能返回區域性變數的引用;不能返回函式內部new分配的記憶體引用; 如果返回成員的話,返回const

 

格式:型別識別符號&函式名(形參列表及型別說明){//函式體}

好處:在記憶體中不產生被返回值的副本;(注意:正是因為這點原因,所以返回一個區域性變數的引用是不可取的。因為隨著該區域性變數生存期的結束,相應的引用也會失效,產生runtime error!

注意事項:

(1)不能返回區域性變數的引用。這條可以參照Effective C++[1]的Item 31。主要原因是區域性變數會在函式返回後被銷燬,因此被返回的引用就成為了"無所指"的引用,程式會進入未知狀態。

(2)不能返回函式內部new分配的記憶體的引用。這條可以參照Effective C++[1]的Item 31。雖然不存在區域性變數的被動銷燬問題,可對於這種情況(返回函式內部new分配記憶體的引用),又面臨其它尷尬局面。例如,被函式返回的引用只是作為一個臨時變量出現,而沒有被賦予一個實際的變數,那麼這個引用所指向的空間(由new分配)就無法釋放,造成memory leak。

(3)可以返回類成員的引用,但最好是const這條原則可以參照EffectiveC++[1]的Item 30       主要原因是當物件的屬性是與某種業務規則(business rule)相關聯的時候,其賦值常常與某些其它屬性或者物件的狀態有關,因此有必要將賦值操作封裝在一個業務規則當中。如果其它物件可以獲得該屬性的非常量引用(或指標),那麼對該屬性的單純賦值就會破壞業務規則的完整性。

(4)流操作符過載返回值申明為“引用”的作用:

流操作符<<和>>,這兩個操作符常常希望被連續使用,例cout<<"hello"<<endl;因此這兩個操作符的返回值應該是一個仍然支援這兩個操作符的流引用。可選的其它方案包括:返回一個流物件和返回一個流物件指標。但是對於返回一個流物件,程式必須重新(拷貝)構造一個新的流物件,也就是說,連續的兩個<<操作符實際上是針對不同物件的!這無法讓人接受。對於返回一個流指標則不能連續使用<<操作符。因此,返回一個流物件引用是惟一選擇。這個唯一選擇很關鍵,它說明了引用的重要性以及無可替代性,也許這就是C++語言中引入引用這個概念的原因吧。

賦值操作符=。這個操作符象流操作符一樣,是可以連續使用的,例如:x=j=10;或者(x=10)=100;賦值操作符的返回值必須是一個左值,以便可以被繼續賦值。因此引用成了這個操作符的惟一返回值選擇。

#include<iostream.h>

int &put(int n);

int vals[10];

int error = -1;

void main()

{

    put(0) = 10; //以put(0)函式值作為左值,等價於vals[0]=10;

    put(9) = 20; //以put(9)函式值作為左值,等價於vals[9]=20;

    cout << vals[0];

    cout << vals[9];

}

int &put(int n)

{

    if (n >= 0 && n <= 9) return vals[n];

    else { cout << "subscript error"; return error; }

}

(5)在另外的一些操作符中,卻千萬不能返回引用:+-*/ 四則運算子。它們不能返回引用,Effective C++[1]的Item23詳細的討論了這個問題。主要原因是這四個操作符沒有side effect,因此,它們必須構造一個物件作為返回值,可選的方案包括:返回一個物件、返回一個區域性變數的引用,返回一個new分配的物件的引用、返回一個靜態物件引用。根據前面提到的引用作為返回值的三個規則,第2、3兩個方案都被否決了。靜態物件的引用又因為((a+b) == (c+d))會永遠為true而導致錯誤。所以可選的只剩下返回一個物件了。

15. 繼承方式

public    父類的訪問級別不變

protected    父類的public成員在派生類程式設計protected,其餘的不變

private        父類的所有成員變成private

16 多重繼承的二義性

多個父類中有相同名稱的變數或者函式。子類中要指明是哪個父類的。
子類中同名的函式會覆蓋父類的。

子類如果和父類函式同名但是引數不同,子類會覆蓋父類,但是用using (using Base::fun;)是可以實現父類和子類過載的

17 菱形繼承

N是基類(包含a成員和函式display),A和B分別繼承N,C繼承A和B。
A 和B 中都有a的儲存空間。可以通過A和B做限定: c.A::a 和 c.B::display();

18. 簡述C\C++程式編譯的記憶體情況分配

C、C++中記憶體分配方式可以分為三種:

(1)從靜態儲存區域分配:記憶體在程式編譯時就已經分配好,這塊記憶體在程式的整個執行期間都存在。速度快、不容易出錯,因為有系統會善後。例如全域性變數,static變數等。
(2)在棧上分配:在執行函式時,函式內區域性變數的儲存單元都在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
(3)從堆上分配:即動態記憶體分配。程式在執行的時候用 malloc 或 new 申請任意大小的記憶體,程式設計師自己負責在何時用 free 或 delete 釋放記憶體。動態記憶體的生存期由程式設計師決定,使用非常靈活。如果在堆上分配了空間,就有責任回收它,否則執行的程式會出現記憶體洩漏,另外頻繁地分配和釋放不同大小的堆空間將會產生堆內碎塊。

一個 C、C++程式編譯時記憶體分為 5 大儲存區:堆區、棧區、全域性區、文字常量區、程式程式碼區。

19記憶體分配方式

堆:有記憶體碎片的問題。一定的演算法去找合適的記憶體。效率低。OS有記錄空閒記憶體地址的連結串列

棧:專門的暫存器存放棧地址。效率高。有大小限制。

自由儲存區:用malloc /free分配釋放。 和堆類似。

全域性/靜態儲存區:全域性變數,靜態變數。

常量儲存區:放常量,不允許修改。

int a = 0;    全域性 / 靜態儲存區

char *p1;  全域性 / 靜態儲存區

int main()

{

    int b; //

    char s[] = "abc"; //

    char *p2; //

    char *p3 = "123456"; //123456在常量區,p3在棧上。

    static int c = 0//全域性(靜態)初始化區

       p1 = (char *)malloc(10); //分配得來得10和20位元組的區域就在堆區

    p2 = (char *)malloc(20);

    strcpy(p3, "123456"); //123456/0放在常量區,編譯器可能會將它與p3所指向的"123456" 優化成一個地方。

}

20:堆和棧的區別?堆和棧的生命週期?

一、堆疊空間分配區別:

1、棧(作業系統):由作業系統自動分配釋放 ,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧;

2、堆(作業系統): 一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收,分配方式倒是類似於連結串列。

二、堆疊快取方式區別:

1、棧使用的是一級快取, 他們通常都是被呼叫時處於儲存空間中,呼叫完畢立即釋放;

2、堆是存放在二級快取中,生命週期由虛擬機器的垃圾回收演算法來決定(並不是一旦成為孤兒物件就能被回收)。所以呼叫這些物件的速度要相對來得低一些。

三、堆疊資料結構區別:

堆(資料結構):堆可以被看成是一棵樹,如:堆排序;

棧(資料結構):一種先進後出的資料結構。

堆和棧的區別:

一個由c/C++編譯的程式佔用的記憶體分為以下幾個部分 
1、棧區(stack)   由編譯器自動分配釋放 ,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。 
2、堆區(heap)   一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收 。
注意它與資料結構中的堆是兩回事,分配方式倒是類似於連結串列,呵呵。 
3、全域性區(靜態區)(static),全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域, 未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。 - 程式結束後有系統釋放 
4、文字常量區  ―常量字串就是放在這裡的。 程式結束後由系統釋放 
5、程式程式碼區―存放函式體的二進位制程式碼

對記憶體的瞭解

1.棧 - 由編譯器自動分配釋放

2.堆 - 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由OS回收

3.全域性區(靜態區),全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。- 程式結束釋放

4.另外還有一個專門放常量的地方。- 程式結束釋放

5 程式程式碼區,存放2進位制程式碼。

在函式體中定義的變數通常是在棧上,用malloc, calloc, realloc等分配記憶體的函式分配得到的就是在堆上。在所有函式體外定義的是全域性量,加了static修飾符後不管在哪裡都存放在全域性區(靜態區),在所有函式體外定義的static變量表示在該檔案中有效,不能extern到別的檔案用,在函式體內定義的static表示只在該函式體內有效。另外,函式中的"adgfdf"這樣的字串存放在常量區。

21 函式引數入棧的順序?

從右端往左進入棧的。為了支援可變引數(原理要懂得)。

22:棧記憶體與文字常量區

char str1[] = "abc";

char str2[] = "abc";

const char str3[] = "abc";

const char str4[] = "abc";

const char *str5 = "abc";

const char *str6 = "abc";

char *str7 = "abc";

char *str8 = "abc";

cout << (str1 == str2) << endl;//0  分別指向各自的棧記憶體

cout << (str3 == str4) << endl;//0  分別指向各自的棧記憶體

cout << (str5 == str6) << endl;//1指向文字常量區地址相同

cout << (str7 == str8) << endl;//1指向文字常量區地址相同

結果是:0 0 1 1

解答:str1,str2,str3,str4是陣列變數,它們有各自的記憶體空間;而str5,str6,str7,str8是指標,它們指向相同的常量區域。

23:什麼是記憶體洩漏?面對記憶體洩漏和指標越界,你有哪些方法?你通常採用哪些方法來避免和減少這類錯誤?

答:用動態儲存分配函式動態開闢的空間,在使用完畢後未釋放,結果導致一直佔據該記憶體單元即為記憶體洩露。

使用的時候要記得指標的長度。

malloc的時候得確定在那裡free.

對指標賦值的時候應該注意被賦值指標需要不需要釋放.

動態分配記憶體的指標最好不要再次賦值.

24 記憶體洩漏? 指標越界和記憶體洩漏,有哪些方法?

new/delete, new[]/delete, malloc/free 配套使用
對指標賦值的時候,一定要看原來指向的東西是否需要釋放
指標指向的東西釋放後,一定要將指標設定為null。

25 棧溢位的原因:

棧大小有限制:分過多的陣列;
遞迴呼叫層太深;

26. C++中有了malloc / free , 為什麼還需要 new / delete

1、malloc與free是C++/C語言的標準庫函式,new/delete是C++的運算子。它們都可用於申請動態記憶體和釋放記憶體。

2、對於非內部資料型別的物件而言,光用maloc/free無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行解構函式。

3、由於malloc/free是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free。因此C++語言需要一個能完成動態記憶體分配和初始化工作的運算子new,以一個能完成清理與釋放記憶體工作的運算子delete。注意new/delete不是庫函式。

4、C++程式經常要呼叫C函式,而C程式只能用malloc/free管理動態記憶體。

5、new可以認為是malloc加建構函式的執行。new出來的指標是直接帶型別資訊的。而malloc返回的都是void指標。

27. 在c++程式中呼叫被C編譯器編譯後的函式,為什麼要加extern“C”

C++語言支援函式過載,C語言不支援函式過載,函式被C++編譯器編譯後在庫中的名字與C語言的不同,

假設某個函式原型為:

  1. void  foo(int x, int y);

該函式被C編譯器編譯後在庫中的名字為:  _foo

而C++編譯器則會產生像: _foo_int_int  之類的名字。為了解決此類名字匹配的問題,C++提供了C連結交換指定符號 extern "C"。

28. extern 有什麼作用

extern 標識的變數或者函式宣告其定義在別的檔案中,提示編譯器遇到此變數和函式時在其它模組中尋找其定義。

29. 標頭檔案種的ifndef/define/endif 是幹什麼用的

防止標頭檔案被重複包含

30. C庫函式實現

31、C++中空類預設產生哪些類成員函式?

對於一個空類,編譯器預設產生4個成員函式:

(1)預設建構函式

(2)解構函式

(3)拷貝建構函式

(4)賦值函式

32. c++空類的成員函式

預設的建構函式
預設的拷貝建構函式
預設的賦值運算子
預設的解構函式
預設的取址運算子
預設的取址運算子const

注意:只有當實際使用這些函式的時候,編譯器才會去定義它們。

33、解構函式可以為 virtual 型,建構函式則不能,為什麼?

虛擬函式採用一種虛呼叫的辦法。虛呼叫是一種可以在只有部分資訊的情況下工作的機制,特別允許我們呼叫一個只知道介面而不知道其準確物件型別的函式。但是如果要建立一個物件,你勢必要知道物件的準確型別,因此建構函式不能為 virtual。

34、如果虛擬函式是非常有效的,我們是否可以把每個函式都宣告為虛擬函式?

不行,這是因為虛擬函式是有代價的:由於每個虛擬函式的物件都必須維護一個 v 表,因此在使用虛擬函式的時候會產生一個系統開銷。如果僅是一個很小的類,且不行派生其他類,那麼根本沒必要使用虛擬函式。

 

35 什麼時候要用虛解構函式

通過基類的指標來刪除派生類的物件時,基類的解構函式應該是虛的。否則其刪除效果將無法實現。

一般情況下,這樣的刪除只能夠刪除基類物件,而不能刪除子類物件,形成了刪除一半形象,從而千萬記憶體洩漏。

原因:

在公有繼承中,基類對派生類及其物件的操作,只能影響到那些從基類繼承下來的成員。

如果想要用基類對非繼承成員進行操作,則要把基類的這個操作(函式)定義為虛擬函式。

那麼,解構函式自然也應該如此:如果它想析構子類中的重新定義或新的成員及物件,當然也應該宣告為虛的。

注意:

如果不需要基類對派生類及物件進行操作,則不能定義虛擬函式(包括虛解構函式),因為這樣會增加記憶體開銷。

36. c++怎樣讓返回物件的函式不呼叫拷貝建構函式

拷貝建構函式前加 “explicit” 關鍵字

37.子類析構時要呼叫父類的解構函式嗎?

解構函式呼叫的次序是先派生類的析構後基類的析構,也就是說在基類的的析構呼叫的時候,派生類的資訊已經全部銷燬了。定義一個物件時先呼叫基類的建構函式、然後呼叫派生類的建構函式;析構的時候恰好相反:先呼叫派生類的解構函式、然後呼叫基類的解構函式。

38.多型,虛擬函式,純虛擬函式

多型:是對於不同物件接收相同訊息時產生不同的動作。C++的多型性具體體現在執行和編譯兩個方面:在程式執行時的多型性通過繼承和虛擬函式來體現;

在程式編譯時多型性體現在函式和運算子的過載上;

虛擬函式:在基類中冠以關鍵字virtual的成員函式。它提供了一種介面介面。允許在派生類中對基類的虛擬函式重新定義。虛擬函式為了過載和多型,在基類中是有定義的,即便定義為空。在子類中可以重寫。多型的基礎是繼承,需要虛擬函式的支援

純虛擬函式的作用:在基類中為其派生類保留一個函式的名字,以便派生類根據需要對它進行定義。作為介面而存在純虛擬函式不具備函式的功能,一般不能直接被呼叫。純虛擬函式在基類中沒有定義,必須在子類中實現。

從基類繼承來的純虛擬函式,在派生類中仍是虛擬函式。如果一個類中至少有一個純虛擬函式,那麼這個類被稱為抽象類(abstract class)。

抽象類中不僅包括純虛擬函式,也可包括虛擬函式。抽象類必須用作派生其他類的基類,而不能用於直接建立物件例項。但仍可使用指向抽象類的指標支援執行時多型性。

39:基類的解構函式不是虛擬函式,會帶來什麼問題?

派生類的解構函式用不上,會造成資源的洩漏。

40 程式在結束的時候,系統會自動析構所有的全域性變數。

事實上,系統也會析構所有的類的靜態成員變數,就像這些靜態成員也是全域性變數一樣

41子類不能繼承父類的函式

子類繼承父類大部分的資源,不能繼承的有建構函式,解構函式,拷貝建構函式,operator=函式,友元函式。

42 為什麼基類的解構函式是虛擬函式?

動態繫結,不會造成潛在的記憶體洩漏

43 多型類中虛擬函式表是compilie-Time 還是 Run-time時建立的

虛擬函式表是在編譯時就建立了,各個虛擬函式這時被組織成了一個虛擬函式的入口地址的陣列。
而物件的隱藏成員--虛擬函式表指標是在執行期-也就是建構函式被呼叫時進行初始化的,這是實現多型的的關鍵。、

44 父類寫了一個virtual函式,如果子類覆蓋它函式不加virtual,可以多型嗎?

可以; 子類可寫,可不寫。

45 子類的空間中,有無父類的virtual函式,或者私有變數?

答:除了static的,其他的都有。

46 談談你對拷貝建構函式和賦值運算子的認識

兩個不同之處:

① 拷貝建構函式生成新的類物件,而賦值運算子不能。
② 由於拷貝建構函式是直接構造一個新的類物件,所以在初始化這個物件之前不用檢驗源物件是否和新建物件相同。而賦值運算子則需要這個操作,另外賦值運算中如果原來的物件中有記憶體分配要先把記憶體釋放掉。

注意:當有類中有指標型別的成員變數時,一定要重寫拷貝建構函式和賦值運算子,不要使用預設的。

47. 類成員的重寫、過載和隱藏的區別

重寫和過載主要有以下幾點不同

① 範圍的區別:被重寫的和重寫的函式在兩個類中,而過載和被過載的函式在同一個類中。
② 引數的區別:被重寫函式和重寫函式的引數列表一定相同,而被過載函式和過載函式的引數列表一定不同。
③ virtual 的區別:重寫的基類中被重寫的函式必須要有 virtual 修飾,而過載函式和被過載函式可以被virtual 修飾,也可以沒有。

隱藏和重寫、過載有以下幾點不同

① 與過載的範圍不同:和重寫一樣,隱藏函式和被隱藏函式不在同一個類中
② 引數的區別:隱藏函式和被隱藏的函式的引數列表可以相同,也可不同,但是函式名肯定要相同。當引數不相同時,無論基類中的引數是否被 virtual 修飾,基類的函式都是被隱藏,而不是被重寫

說明:雖然過載和覆蓋都是實現多型的基礎,但是兩者實現的技術完全不相同,達到的目的也是完全不同的,覆蓋是動態繫結的多型,而過載是靜態繫結的多型。

48:const知道嗎?解釋其作用。

1.const 修飾類的成員變數,表示成員常量,不能被修改。修飾類成員變數,成員不可以改。

2.const修飾函式承諾在本函式內部不會修改類內的資料成員,不會呼叫其它非 const 成員函式。修飾函式引數;

3.如果 const 構成函式過載,const 物件只能呼叫 const 函式,非 const 物件優先呼叫非 const 函式。

4.const 函式只能呼叫 const 函式。非 const 函式可以呼叫 const 函式。

5.類體外定義的 const 成員函式,在定義和宣告處都需要 const 修飾符。

主要有三點:

1:定義只讀變數,即常量 

2:修飾函式的引數和函式的返回值 

3: 修飾函式的定義體,這裡的函式為類的成員函式,被const修飾的成員函式代表不修改成員變數的值

49 const用法

const常量:定義的時候必須初始化。const int a 和 int const a 是一個意思。
const指標: 常量指標 和 指標常量
int a;
const int *p = &a; 常量指標,不能通P改變所值物件的值。但是可以其他方式修改。並且指標
還可以指向其他的int變數。const int *p 和 int const *p一樣。
int * const p = &i; 指標常量,p中存放的地址不可以變化,可以通過P改變變數的值,但是指標不能
再指向其他的變數。
注意const int *p 和 int const *p一樣
const引用: 可以繫結常量,也可以繫結變數。不能通過這個const引用來改變繫結物件的值。但是變數本身可以改。
非const 引用不能與const 物件繫結;但是const 引用可以繫結非const 變數。

50.請說出const與#define相比,有何優點?

const作用:定義常量、修飾函式引數、修飾函式返回值、修飾類成員函式。三個作用。被Const修飾的東西都受到強制保護,可以預防意外的變動,能提高程式的健壯性。

1)const常量有資料型別,而巨集常量沒有資料型別。編譯器可以對前者進行型別安全檢查。而對後者只進行字元替換,沒有型別安全檢查,並且在字元替換可能會產生意料不到的錯誤。

2)有些整合化的除錯工具可以對const常量進行除錯,但是不能對巨集常量進行除錯。

51: 函式的const引數構成過載函式

如果是值傳遞的函式,值的const 和非const 不構成過載。
如果是引用和指標的可以構成過載。
非const 可以呼叫const。
const 不可呼叫非const的。
呼叫過載函式的時候 會選擇最匹配的那個

52 C中static有什麼作用

① 隱藏。當我們同時編譯多個檔案時,所有未加static字首的全域性變數和函式都具有全域性可見性,故使用static在不同的檔案中定義同名函式和同名變數,而不必擔心命名衝突。
② 保持變數內容的持久。儲存在靜態資料區的變數會在程式剛開始執行時就完成初始化,也是唯一的一次初始化。共有兩種變數儲存在靜態儲存區:全域性變數和static變數。
③ 預設初始化為0.其實全域性變數也具備這一屬性,因為全域性變數也儲存在靜態資料區。在靜態資料區,記憶體中所有的位元組預設值都是0×00,某些時候這一特點可以減少程式設計師的工作量。

53:類的static變數在什麼時候初始化?函式的static變數在什麼時候初始化?

答:類的靜態成員變數在類例項化之前就已經存在了,並且分配了記憶體。函式的static變數在執行此函式時進行初始化。

類的static變數的定義和初始化

static int Sum;//在標頭檔案中宣告靜態資料成員
int Myclass::Sum=0;//定義並初始化靜態資料成員,在類的外部。

關鍵字static的作用:

1、函式體內 static 變數的作用範圍為該函式體,不同於 auto 變數, 該變數的記憶體只被分配一次,因此其值在下次呼叫時仍維持上次的值

2、在模組內的 static 全域性變數可以被模組內所有函式訪問,但不能被模組外其他函式訪問

3、在模組內的static 函式只可被這一模組內的其他函式呼叫,這個函式的使用範圍被限制在宣告它的模組內

4、 在類的static 成員變數屬於整個類所擁有,對類的所以物件只有一份拷貝

5、 在類中的 static 成員函式屬於整個類所擁有,這個函式不接收 this 指標,因而只能訪問類的 static 成員變數

介紹它最重要的一條:隱藏。(static函式,static變數均可) --> 對應上面的2、3項      當同時編譯多個檔案時,所有未加static字首的全域性變數和函式都具有全域性可見性。
static:
  區域性static變數:區域性靜態變數,處於記憶體中的靜態儲存區;只能初始化一次;作用域是區域性。
  全域性static變數:全域性靜態變數,靜態儲存區;全域性靜態變數的作用局是宣告它的檔案,在檔案之外是不可見的。其實是從
  定義的地方到檔案結尾。
  類的static成員:類的全域性變數,被類的所有獨享共享,包括派生類的物件。按照這種方式int base::var = 10;進行初始化,不能在建構函式內初始化,但是可以用const修飾的static資料成員在類內初始化。

static修飾成員函式,類只有一份,不含this指標。
static成員變數定義放在cpp檔案中。 const static 可以就地初始化。

54 static 檔案作用域的問題

當同時編譯多個檔案時,所有未加static的全域性變數和函式都是全域性可見的(其他檔案加上的extern就行)。
用static修飾全域性變數,可見性就是本檔案,這樣可以在不同的原始檔中定義同名的函式和變數,不擔心衝突。
static函式: 主要就是為了隱藏(只在本檔案中可以看到)。
static變數: 一是隱藏; 另外是保持變數內容的持久。儲存在靜態區域的變數會在程式剛剛執行時就完成初始化,
也是唯一的一次初始化(初始化只是一次,但是可以改變值)
static 還有一個作用:預設初始化為0,其實全域性變數也是這樣的。

55. c/c++中static

要理解static,就必須要先理解另一個與之相對的關鍵字auto,其實我們通常宣告的不用static修飾的變數,都是auto的,因為它是預設的。auto的含義是由程式自動控制變數的生存週期,通常指的就是變數在進入其作用域的時候被分配,離開其作用域的時候被釋放;而static就是不auto,變數在程式初始化時被分配,直到程式退出前才被釋放;也就是static是按照程式的生命週期來分配釋放變數的,而不是變數自己的生命週期;所以,像這樣的例子:

1

2

3

4

5

void func()

{

int a;

  static int b;

}

每一次呼叫該函式,變數a都是新的,因為它是在進入函式體的時候被分配,退出函式體的時候被釋放,所以多個執行緒呼叫該函式,都會擁有各自獨立的變數a,因為它總是要被重新分配的;而變數b不管你是否使用該函式,在程式初始化時就被分配的了,或者在第一次執行到它的宣告的時候分配(不同的編譯器可能不同),所以多個執行緒呼叫該函式的時候,總是訪問同一個變數b,這也是在多執行緒程式設計中必須注意的!

static的全部用法:

1.類的靜態成員:

1

2

3

4

5

class A

{

  private:

    static int s_value;

};

在cpp中必須對它進行初始化:

1

int A::s_value = 0; // 注意,這裡沒有static的修飾!

類的靜態成員是該類所有例項的共用成員,也就是在該類的範疇內是個全域性變數,也可以理解為是一個名為A::s_value的全域性變數,只不過它是帶有類安全屬性的;道理很簡單,因為它是在程式初始化的時候分配的,所以只分配一次,所以就是共用的;

類的靜態成員必須初始化,道理也是一樣的,因為它是在程式初始化的時候分配的,所以必須有初始化,類中只是宣告,在cpp中才是初始化,可以在初始化的程式碼上放個斷點,在程式執行main的第一條語句之前就會先走到那;如果你的靜態成員是個類,那麼就會呼叫到它的建構函式;

2.類的靜態函式:

1

2

3

4

5

class A

{

  private:

  static void func(int value);

};

實現的時候也不需要static的修飾,因為static是宣告性關鍵字;類的靜態函式是在該類的範疇內的全域性函式,不能訪問類的私有成員,只能訪問類的靜態成員,不需要類的例項即可呼叫;實際上,它就是增加了類的訪問許可權的全域性函式:void A::fun(int);

靜態成員函式可以繼承和覆蓋,但無法是虛擬函式;

3.只在cpp內有效的全域性變數:

在cpp檔案的全域性範圍內宣告:

1

static int g_value = 0;

這個變數的含義是在該cpp內有效,但是其他的cpp檔案不能訪問這個變數;如果有兩個cpp檔案聲明瞭同名的全域性靜態變數,那麼他們實際上是獨立的兩個變數;

如果不使用static宣告全域性變數:

1

int g_value = 0;

那麼將無法保證這個變數不被別的cpp共享,也無法保證一定能被別的cpp共享,因為要讓多個cpp共享一個全域性變數,應將它宣告為extern(外部)的;也有可能編譯會報告變數被重複定義;總之不建議這樣的寫法,不明確這個全域性變數的用法;

如果在一個頭檔案中宣告:

1

static int g_vaule = 0;

那麼會為每個包含該標頭檔案的cpp都建立一個全域性變數,但他們都是獨立的;所以也不建議這樣的寫法,一樣不明確需要怎樣使用這個變數,因為只是建立了一組同名而不同作用域的變數;

這裡順便說一下如何宣告所有cpp可共享的全域性變數,在標頭檔案裡宣告為extern的:

1

extern int g_value; // 注意,不要初始化值!

然後在其中任何一個包含該標頭檔案的cpp中初始化(一次)就好:

1

int g_value = 0; // 初始化一樣不要extern修飾,因為extern也是宣告性關鍵字;

然後所有包含該標頭檔案的cpp檔案都可以用g_value這個名字訪問相同的一個變數;

56:解釋C++中靜態函式和靜態變數?

 (1)類靜態資料成員在編譯時建立並初始化:在該類的任何物件建立之前就存在,不屬於任何物件,而非靜態類成員變數則是屬於物件所有的。類靜態資料成員只有一個拷貝,為所有此類的物件所共享。

(2)類靜態成員函式屬於整個類,不屬於某個物件,由該類所有物件共享。

1、static 成員變數實現了同類物件間資訊共享。

2、static 成員類外儲存,求類大小,並不包含在內。

3、static 成員是名稱空間屬於類的全域性變數,儲存在 data 區的rw段。

4、static 成員只能類外初始化。

5、可以通過類名訪問(無物件生成時亦可),也可以通過物件訪問。

1、靜態成員函式的意義,不在於資訊共享,資料溝通,而在於管理靜態資料成員,完成對靜態資料成員的封裝。

2、靜態成員函式只能訪問靜態資料成員。原因:非靜態成員函式,在呼叫時 this指標時被當作引數傳進。而靜態成員函式屬於類,而不屬於物件,沒有 this 指標。

57、哪一種成員變數可以在同一類的例項間共享?

必須使用靜態成員變數在一個類的所有例項間共享資料。如果想限制對靜態成員變數的訪問,必須把它們宣告為保護型或私有型。不允許用靜態成員去存放某一個物件的資料。靜態成員函式是在這個類的所有物件間共享的。如果靜態成員資料設為私有的,可以通過共有靜態和產能原函式訪問。

58、過載與覆蓋有什麼不同?

虛擬函式總是在派生類中被改寫,這種改寫被稱為“override”(覆蓋)。

override 是指派生類重寫基類的虛擬函式,重寫的函式必須有一致的引數表和返回值。Override這個單詞好像一直沒什麼合適的中文詞彙來對應。有些人譯為“覆蓋”,還貼切一些。

overload約定成俗地被翻譯為“過載”,