C++ 須要注意的一些問題

1. 常量必須在構造函數的初始化列表裏面初始化。c++

class A { const int size = 0; }; 是錯誤的。 須要改爲 class A{ A(){ const int size = 10; } };程序員

或者改爲 class A { static const int size = 10; };算法

2. 構造函數的初始化列表初始化變量的順序是根據成員變量的聲明順序來執行的。編程

好比:windows

 1 class base
 2 
 3 {
 4  private:
 5 
 6   int m_i;
 7 
 8   int m_j;
 9 
10 public:
11 
12   base(int i): m_j(i), m_i(m_j) {}

就有錯誤,這裏的m_i將會被初始化成一個隨機值。數組

3. 基類的析構函數必須是虛函數。由於這樣處理後,全部子類的析構函數都將會被自動變爲virtual類型,這就保證了在任何狀況下,不會出現因爲析構函數未被調用而致使的內存泄露。安全

 

4. 爲何不能將構造函數定義爲虛函數?數據結構

虛擬函數是採用一種虛調用的辦法。虛調用是一種能夠在只有部分信息的狀況下工做的機制,特別容許咱們調用一個只知道接口而不知道其準確對象類型的函數。可是若是建立一個對象,你應該知道對象的準確對象,所以構造函數不能爲虛函數。函數

 

5. 爲何不能把每一個函數都聲明爲虛函數?優化

虛函數是由代價的:因爲每一個虛函數都維護一個v表,所以在使用虛函數的時候會產生一個系統開銷。若是僅僅是一個很小的類,且不想派生其餘類,那麼根本就沒有必要使用虛函數。

6. 析構函數能夠是內聯函數嗎?

答案是能夠的。

7. 單參數的構造函數若是不添加explicit關鍵字,會定義一個隱含的類型轉換,添加explicit關鍵字會消除這種類型轉換。

8. 若是類中包含指針的話,通常須要編寫拷貝構造函數和拷貝賦值運算符,以免淺拷貝。

9. 爲何須要友元?

類具備封裝和信息隱藏的特性。只有類的成員函數才能訪問類的私有成員,程序中的其餘函數是沒法訪問私有成員的。非成員函數能夠訪問

類的公有成員,可是若是將數據成員都定義成公有的,這又破壞了隱藏的特性。另外,應該看到在某些狀況下,特別是在對某些成員函數屢次調

用時,因爲參數傳遞,類型檢查和安全性檢查都須要時間開銷,而影響程序的運行效率。

爲了解決這個問題,提出了一種使用友元的方案。友元是一種定義在類外部的普通函數。但它須要在類體內進行說明,爲了與該類的成員函數加

以區別,在說明時前面加以關鍵字friend。友元不是成員函數,可是能夠訪問類的私有成員。做用在於提升程序的運行效率,可是,它破壞了類

的封裝性和隱藏性,使得非成員函數能夠訪問類的私有成員。

10. C++中如何阻止一個類被實例化?

使用抽象類,或者構造函數被聲明爲private

11. 何時編譯器會生成copy constructor呢?

只要本身沒有編寫,程序中須要,都會生成

12. C++引入的開銷體如今如下兩個方面:

1.編譯時的開銷

模板,類層次結構,強類型檢查等新特性,以及大量使用了這些新特性的C++模板,算法庫都明顯地增長了C++編譯器的負擔。可是應當看到,這些新技能在不增長程序執行效率的前提下,明顯下降了廣大C++程序員的工做量。

2.運行時的開銷

相對於傳統的C程序而言,C++中可能引入額外運行時的開銷特性包括:

虛基類,虛函數,RTTI(dynamic_cast, typeid), 異常以及對象的構造和析構

虛基類,從直接虛繼承的子類中訪問虛基類的數據成員或其虛函數時,將增長兩次指針引用(大部分狀況下能夠優化爲一次)和一次整型加法的時間開銷。定義一個虛基類表,定義若干虛基類表指針的空間開銷。

虛函數的運行開銷有進行整形加法和指針引用的時間開銷。定義一個虛表,定義若干個虛表指針的空間開銷。

RTTI的運行開銷主要有進行整形比較和取址操做(可能還會有一兩次整形加法)所增長的時間開銷。定義一個type_info對象(包括類型ID和類名稱)的空間開銷。"dynamic_cast"用於在類層次結構中漫遊,對指針或者引用進行自由地向上,向下或者交叉轉化。"typeid"則用於獲取一個對象或者引用的確切類型。通常地講,能用虛函數解決的問題就不要用"dynamic_cast",可以用"dynamic_cast"解決的就不用"typeid".

關於異常,對於幾乎全部的編譯器來講,在正常狀況下,try塊中的代碼執行效率和普通代碼同樣高,並且因爲不須要使用傳統的經過返回值函數調用來判斷錯誤的方式,代碼的執行效率還會進一步提升。拋出和捕捉異常的開銷也只是在某些狀況下會高於函數返回和函數調用的開銷。

關於構造和析構,開銷也不老是存在的。對於不須要初始化/銷燬的類型,並無構造和析構的開銷,相反對於那些須要初始化/銷燬的類型來講,即便使用傳統的C方式來實現,也至少須要與之至關的開銷。

固然,RTTI是有用的。但由於一些理論上以及方法論上的緣由,它破壞了面向對象的純潔性。

首先,它破壞了抽象,使得一些原本不該該被使用的方法和屬性被不正確地使用。其次,由於運行時類型的不肯定性,它把程序變得更脆弱。第三點,也是最重要的一點,它使得程序缺少擴展性。當加入一個新的特性時候,你也許須要仔細閱讀你的dynamic_cast或者instanceof的代碼,必要時改動它們,以保證這個新的類型加入不會致使問題。而這個過程當中,編譯器將不會給你任何幫助。

13. 請描述heap與stack的區別

在進行c/c++編程時,須要程序員對內存的瞭解比較精準。常常須要操做的內存能夠分爲:

棧區:由編譯器自動分配和釋放,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧

堆區:通常由程序員分配和釋放,若程序員不釋放,程序結束時可能由操做系統回收。注意它與數據結構中的堆是兩回事,分配方式卻是相似鏈表。

全局區(靜態區): 全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未釋放的靜態變量在相鄰的另外一塊區域。程序結束後由系統釋放。

文字常量區:常量字符串就是放這裏的,程序結束後由系統釋放。

程序代碼區:存放函數體的二進制代碼。

 1 int a = 0; // 全局初始化區
 2 char *p1; // 全局未初始化
 3 main()
 4 {
 5     int b; //
 6     char s[] = "abc"; //
 7     char *p2;  //
 8     char *p3 = "123456"; // "123456"在常量區,p3在棧上
 9     static int c = 0; // 全局(靜態)初始化區
10     p1 = (char *)malloc(10);
11     p2 = (char *)malloc(20); 
12     // 分配得來的10和20字節的區域就在堆區
13     strcpy(p1, "123456");
14     //"123456"放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方   
15 }

堆和棧的理論知識:

1. 申請方式

棧:由系統自動分配。好比,聲明在函數中的一個局部變量int b,系統自動在棧中爲b開闢空間。

堆:須要程序員本身申請,並指明大小,在C中使用malloc函數。

好比 p1 = (char *)malloc(10);

在C++中使用new運算符 ,好比:

p2 = new char;

可是,注意p1,p2自身在棧中的。

2. 申請後的系統的響應

棧:只要棧的剩餘空間大於所申請的空間,系統將爲程序提供內存,不然將報以長提示棧溢出。

堆:首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷鏈表,尋找第一個空間大於所申請空間的堆節

點,而後將該節點從空閒節點鏈表中刪除,並將該節點的空間分配給程序。對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的

大小,這樣,代碼的delete語句才能正確地釋放本內存空間。另外,因爲找到的堆節點的大小不必定正好等於申請的大小,系統會自動將多餘的

那部分從新加入空餘鏈表中。

3. 申請大小的限制

棧:在windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在windows下,棧的大小是2MB,也有的說是1MB,總之是一個編譯時候就肯定的常數。若是申請的空間超過棧的剩餘空間,將提示overflow。所以,能從棧得到的空間較小。

堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是鏈表存儲空閒內存地址的,天然是不連續的。而鏈表的遍歷方向是由低地址向高地址,堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。

申請效率的比較:

棧:由系統自動分配,速度較快。但程序員沒法控制。

堆:使用malloc/new分配的,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便。另外,在windows下,最好的方式是用virtualAlloc分配內存。不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊內存,雖然用起來最不方便,可是速度最快,也最靈活。

堆和棧中的存儲內容

棧:在函數調用時候,第一個進棧的是主函數中的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數。在大多數的

C編譯器中,參數是由右往左入棧的,而後是函數的局部變量。注意靜態變量是不入棧的。

當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運

行。

堆:通常是在堆得頭部用一個字節表示存放堆的大小。堆中的具體內容由程序員安排。

存取效率的比較

char s1[] = "aaaaaa";

char *s2 = "bbbbb";

"aaaaaa"是在運行時刻賦值的,而"bbbbb"是在編譯時刻就肯定的。可是,在之後的存取中,在棧上的數組比指針所指向的字符串快。

相關文章
相關標籤/搜索