高質量C++C編程指南筆記 標籤: c++筆記 2015-11-22 20:59 179人閱讀 評論(0) 收藏

一、  在多重循環中,若是有可能,應當將最長的循環放在最內層,最短的循環放在最外層,以減小 CPU 跨切循環層的次數。ios

二、  若是循環體內存在邏輯判斷,而且循環次數很大,宜將邏輯判斷移到循環體的外面。c++

三、  即便程序真的不須要default 處理,也應該保留語句 default : break。程序員

四、  C 語言用#define 來定義常量(稱爲宏常量)。 C++ 語言除了 #define 外還能夠用 const 來定義常量(稱爲 const 常量)。數組

五、  const 與 #define 的比較安全

Ø  const 常量有數據類型,而宏常量沒有數據類型。編譯器能夠對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換可能會產生意料不到的錯誤(邊際效應)。函數

Ø  有些集成化的調試工具能夠對const 常量進行調試,可是不能對宏常量進行調試。工具

六、  須要對外公開的常量放在頭文件中,不須要對外公開的常量放在定義文件的頭部。爲便於管理,能夠把不一樣模塊的常量集中存放在一個公共的頭文件中。this

七、  const 數據成員只在某個對象生存期內是常量,而對於整個類而言倒是可變的,由於類能夠建立多個對象,不一樣的對象其 const 數據成員的值能夠不一樣。spa

八、  不能在類聲明中初始化const 數據成員。如下用法是錯誤的,由於類的對象未被建立時,編譯器不知道 SIZE 的值是什麼。指針

class A

{…

const intSIZE = 100; // 錯誤,企圖在類聲明中初始化 const 數據成員

intarray[SIZE]; // 錯誤,未知的 SIZE

};

九、  const 數據成員的初始化只能在類構造函數的初始化表中進行。

十、             枚舉常量不會佔用對象的存儲空間,它們在編譯時被所有求值。枚舉常量的缺點是:它的隱含數據類型是整數,其最大值有限,且不能表示浮點數(如 PI=3.14159)。

十一、             通常地,應將目的參數放在前面,源參數放在後面。

十二、             有時候函數本來不須要返回值,但爲了增長靈活性如支持鏈式表達,能夠附加返回值。

1三、             不少程序錯誤是由非法參數引發的,咱們應該充分理解並正確使用「斷言」( assert)來防止此類錯誤。

1四、             return 語句不可返回指向「棧內存」的「指針」或者「引用」,由於該內存在函數體結束時被自動銷燬。

1五、             斷言 assert 是僅在 Debug 版本起做用的宏,它用於檢查「不該該」發生的狀況。

1六、             引用的一些規則以下:

Ø  引用被建立的同時必須被初始化(指針則能夠在任什麼時候候被初始化)。

Ø  不能有 NULL 引用,引用必須與合法的存儲單元關聯(指針則能夠是 NULL)。

Ø  一旦引用被初始化,就不能改變引用的關係(指針則能夠隨時改變所指的對象)。

1七、             內存分配方式有三種:

Ø  從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量, static 變量。

Ø  在棧上建立。在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。

Ø  從堆上分配,亦稱動態內存分配。程序在運行的時候用 malloc 或 new 申請任意多少的內存,程序員本身負責在什麼時候用 free 或 delete 釋放內存。動態內存的生存期由咱們決定,使用很是靈活,但問題也最多。

1八、             常見的內存錯誤及其對策以下:

Ø  內存分配未成功,卻使用了它。

Ø  內存分配雖然成功,可是還沒有初始化就引用它。

Ø  內存分配成功而且已經初始化,但操做越過了內存的邊界。

Ø  忘記了釋放內存,形成內存泄露。

Ø  釋放了內存卻繼續使用它。(使用 free 或 delete 釋放了內存後,沒有將指針設置爲 NULL。致使產生「野指針」。)

1九、             數組名對應着(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容能夠改變。

20、             不能對數組名進行直接複製與比較。

2一、             用運算符 sizeof 能夠計算出數組的容量(字節數)。sizeof(p)獲得的是一個指針變量的字節數,至關於 sizeof(char*),而不是 p 所指的內存容量。 C++/C 語言沒有辦法知道指針所指的內存容量,除非在申請內存時記住它。

2二、             注意當數組做爲函數的參數進行傳遞時,該數組自動退化爲同類型的指針。

2三、             若是函數的參數是一個指針,不要期望用該指針去申請動態內存。(能夠用指針的指針)

2四、             發現指針 p 被 free 之後其地址仍然不變(非 NULL),只是該地址對應的內存是垃圾, p 成了「野指針」。若是此時不把 p 設置爲 NULL,會讓人誤覺得 p 是個合法的指針。

2五、             free()以後因爲指針所指向的內存已經被釋放,因此其它代碼有機會改寫其中的內容,至關於該指針今後指向了本身沒法控制的地方,也稱爲野指針。

2六、             咱們發現指針有一些「似是而非」的特徵:

Ø  指針消亡了,並不表示它所指的內存會被自動釋放。

Ø  內存被釋放了,並不表示指針會消亡或者成了 NULL 指針。

2七、             「野指針」的成因主要有兩種:

Ø  指針變量沒有被初始化。任何指針變量剛被建立時不會自動成爲 NULL 指針,它的缺省值是隨機的,它會亂指一氣。因此,指針變量在建立的同時應當被初始化,要麼將指針設置爲 NULL,要麼讓它指向合法的內存。

Ø  指針 p 被 free 或者 delete 以後,沒有置爲 NULL,讓人誤覺得 p 是個合法的指針。

Ø  指針操做超越了變量的做用範圍。

2八、             malloc 與 free 是 C++/C 語言的標準庫函數,new/delete 是 C++的運算符。它們均可用於申請動態內存和釋放內存。

2九、             對於非內部數據類型的對象而言,光用 maloc/free 沒法知足動態對象的要求。對象在建立的同時要自動執行構造函數,對象在消亡以前要自動執行析構函數。因爲malloc/free 是庫函數而不是運算符,不在編譯器控制權限以內,不可以把執行構造函數和析構函數的任務強加於 malloc/free。所以 C++語言須要一個能完成動態內存分配和初始化工做的運算符 new,以及一個能完成清理與釋放內存工做的運算符 delete。注意 new/delete 不是庫函數。

30、             因爲內部數據類型的「對象」沒有構造與析構的過程,對它們而言 malloc/free 和new/delete 是等價的。

3一、             若是用 free 釋放「 new 建立的動態對象」,那麼該對象因沒法執行析構函數而可能致使程序出錯。若是用 delete 釋放「 malloc 申請的動態內存」,理論上講程序不會出錯,可是該程序的可讀性不好。因此 new/delete 必須配對使用, malloc/free 也同樣。

3二、             malloc/free 的使用咱們應當把注意力集中在兩個要素上:「類型轉換」和「 sizeof」。

Ø  malloc 返回值的類型是 void *,因此在調用 malloc 時要顯式地進行類型轉換,將 void * 轉換成所須要的指針類型。

Ø  malloc 函數自己並不識別要申請的內存是什麼類型,它只關心內存的總字節數。

3三、             若是 p 是 NULL 指針,那麼 free 對 p 不管操做多少次都不會出問題。若是 p 不是 NULL 指針,那麼 free 對 p連續操做兩次就會致使程序運行錯誤。

3四、             若是用 new 建立對象數組,那麼只能使用對象的無參數構造函數。例如

Obj *objects = newObj[100]; // 建立100 個動態對象不能寫成

Obj *objects = newObj[100](1);// 建立100 個動態對象的同時賦初值 1

在用delete 釋放對象數組時,留意不要丟了符號‘ []’。例如

delete []objects; // 正確的用法

delete objects; // 錯誤的用法

後者至關於delete objects[0],漏掉了另外 99 個對象。

3五、             c++中爲何static成員函數不能聲明爲const?(注意是成員函數)

答:這是C++的規則,const修飾符用於表示函數不能修改爲員變量的值,該函數必須是含有this指針的類成員函數,函數調用方式爲thiscall,而類中的static函數本質上是全局函數,調用規約是__cdecl或__stdcall,不能用const來修飾它。

3六、             重載和內聯機制既可用於全局函數也可用於類的成員函數, const 與virtual 機制僅用於類的成員函數。

3七、             只能靠參數而不能靠返回值類型的不一樣來區分重載函數。

3八、             注意並非兩個函數的名字相同就能構成重載。全局函數和類的成員函數同名不算重載,由於函數的做用域不一樣。

3九、             小心隱式類型轉換致使重載函數產生二義性。

40、             成員函數被重載的特徵:

Ø  相同的範圍(在同一個類中);

Ø  函數名字相同;

Ø  參數不一樣;

Ø  virtual 關鍵字無關緊要。

4一、             覆蓋是指派生類函數覆蓋基類函數,特徵是:

Ø  不一樣的範圍(分別位於派生類與基類);

Ø  函數名字相同;

Ø  參數相同;

Ø  基類函數必須有virtual 關鍵字。

4二、             「隱藏」是指派生類的函數屏蔽了與其同名的基類函數,規則以下:

Ø  若是派生類的函數與基類的函數同名,可是參數不一樣。此時,不論有無 virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。【參數不一樣必定會被隱藏】

Ø  若是派生類的函數與基類的函數同名,而且參數也相同,可是基類函數沒有 virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。【沒有virtual必定會被隱藏】

4三、             參數缺省值只能出如今函數的聲明中,而不能出如今定義體中。

4四、             若是函數有多個參數,參數只能從後向前挨個兒缺省,不然將致使函數調用語句怪模怪樣。

4五、             在 C++運算符集合中,有一些運算符是不容許被重載的。這種限制是出於安全方面的考慮,可防止錯誤和混亂。

Ø  不能改變 C++內部數據類型(如int,float 等)的運算符。

Ø  不能重載‘ .’,由於‘ .’在類中對任何成員都有意義,已經成爲標準用法。

Ø  不能重載目前 C++運算符集合中沒有的符號,如#,@,$等。緣由有兩點,一是難以理解,二是難以肯定優先級。

Ø  對已經存在的運算符進行重載時,不能改變優先級規則,不然將引發混亂。

4六、             在 C 程序中,能夠用宏代碼提升執行效率。宏代碼自己不是函數,但使用起來象函數。預處理器用複製宏代碼的方式代替函數調用,省去了參數壓棧、生成彙編語言的 CALL調用、返回參數、執行 return 等過程,從而提升了速度。使用宏代碼最大的缺點是容易出錯,預處理器在複製宏代碼時經常產生意想不到的邊際效應。使用宏代碼還有另外一種缺點:沒法操做類的私有數據成員。

4七、             C++ 語言的函數內聯機制既具有宏代碼的效率,又增長了安全性,並且能夠自由操做類的數據成員。因此在 C++ 程序中,應該用內聯函數取代全部宏代碼。

4八、             關鍵字 inline 必須與函數定義體放在一塊兒才能使函數成爲內聯,僅將 inline 放在函數聲明前面不起任何做用。

4九、             定義在類聲明之中的成員函數將自動地成爲內聯函數。

50、             內聯是以代碼膨脹(複製)爲代價,僅僅省去了函數調用的開銷,從而提升函數的執行效率。

5一、             如下狀況不宜使用內聯:

Ø  若是函數體內的代碼比較長,使用內聯將致使內存消耗代價較高。

Ø  若是函數體內出現循環,那麼執行函數體內代碼的時間要比函數調用的開銷大。

5二、             每一個類只有一個析構函數和一個賦值函數,但能夠有多個構造函數(包含一個拷貝構造函數,其它的稱爲普通構造函數)。對於任意一個類 A,若是不想編寫上述函數,C++編譯器將自動爲 A 產生四個缺省的函數,如:

Ø  A(void);                                          //缺省的無參數構造函數

Ø  A(const A &a);                             //缺省的拷貝構造函數

Ø  ~A(void);                                       //缺省的析構函數

Ø  A & operate =(const A &a);     //缺省的賦值函數

5三、             構造函數與析構函數的另外一個特別之處是沒有返回值類型,這與返回值類型爲 void 的函數不一樣。

5四、             構造函數有個特殊的初始化方式叫「初始化表達式表」(簡稱初始化表)。初始化表位於函數參數表以後,卻在函數體 {} 以前。這說明該表裏的初始化工做發生在函數體內的任何代碼被執行以前。

5五、             類的 const 常量只能在初始化表裏被初始化,由於它不能在函數體內用賦值的方式來初始化。

5六、             類的數據成員的初始化能夠採用初始化表或函數體內賦值兩種方式,這兩種方的效率不徹底相同。

5七、             非內部數據類型的成員對象應當採用第一種方式初始化,以獲取更高的效率。

5八、             構造和析構的次序:構造從類層次的最根處開始,在每一層中,首先調用基類的構造函數,而後調用成員對象的構造函數。析構則嚴格按照與構造相反的次序執行,該次序是惟一的,不然編譯器將沒法自動執行析構過程。

5九、             成員對象初始化的次序徹底不受它們在初始化表中次序的影響,只由成員對象在類中聲明的次序決定。這是由於類的聲明是惟一的,而類的構造函數能夠有多個,所以會有多個不一樣次序的初始化表。若是成員對象按照初始化表的次序進行構造,這將致使析構函數沒法獲得惟一的逆序。

60、             拷貝構造函數是在對象被建立時調用的,而賦值函數只能被已經存在了的對象調用。

6一、             深度複製(針對《c++ primer plus》一書string例題的解釋):複製構造函數應當複製字符串並將副本的地址賦給str成員,而不只僅是複製字符串的地址,這樣每一個對象都有本身的字符串,而不是引用另外一個對象的字符串,調用析構函數時都將釋放不一樣的字符串,而不會試圖去釋放已經被釋放的字符串。

6二、             什麼時候調用複製構造函數:新建一個對象並將其初始化爲同類現有對象時,複製構造函數都將被調用。每當程序生成了對象副本時,編譯器都將使用複製構造函數。具體的說,當函數按值傳遞對象或函數返回對象時,都將使用複製構造函數。記住,按值傳遞意味着建立原始變量的一個副本。編譯器生成臨時對象時,也將使用複製構造函數。什麼時候生成臨時對象隨編譯器而異,但不管那種編譯器,當按值傳遞和返回對象時,都將調用複製構造函數。

6三、             String類的賦值函數要作的工做:

Ø  自我檢查賦值的狀況

Ø  釋放成員指針之前指向的內存

Ø  複製數據而不只僅是數據的地址

Ø  指向一個返回對象的引用(不要將 return *this 錯寫成 return this)

6四、             基類與派生類的析構函數應該爲虛(即加 virtual關鍵字)。

#include <iostream.h>                                                                     

classBase

{

public:

virtual ~Base() { cout<< "~Base" << endl ; }

};

classDerived : public Base

{

public:

virtual ~Derived() { cout<< "~Derived" << endl; }

};

voidmain(void)

{

Base * pB = new Derived; // upcast

delete pB;

}

輸出結果爲:

~Derived

~Base

若是析構函數不爲虛,那麼輸出結果爲

~Base

6五、             若是使用指向對象的引用或指針來調用虛方法,程序將使用爲對象類型定義的方法,而不使用爲引用或指針類型定義的方法。這稱爲動態聯編或晚期聯編。這種行爲很是重要,由於這樣基類指針或引用能夠指向派生類對象。

6六、              const 修飾函數的參數: 若是參數做輸出用,不論它是什麼數據類型,也不論它採用「指針傳遞」仍是「引用傳遞」,都不能加 const 修飾,不然該參數將失去輸出功能。const 只能修飾輸入參數。(參數作輸出用不能加constconst只能修飾輸入參數)

6七、             若是輸入參數採用「值傳遞」,因爲函數將自動產生臨時變量用於複製該參數,該輸入參數原本就無需保護,因此不要加 const 修飾。

6八、             const 成員函數的聲明看起來怪怪的: const 關鍵字只能放在函數聲明的尾部,大概是由於其它地方都已經被佔用了。

相關文章
相關標籤/搜索