這本書是林銳博士寫於2023年7月,現在都2023年了,相隔10年多,
用了兩天時間看完這個100頁的書,裡面幾乎沒有廢話,全是精煉的總結性的東西,
思路順暢,語言流暢,看起來不費勁。
一共11章,其中1-6章的內容,和我自己的習慣是一致的,看此書之前已經是我的習慣了,
看了之後更加印證了正確性。
第7章,記憶體管理,我認為這一章是正本書中最精華的部分,是給我幫助最大的一部分。
8-11章對我的啟發介於前兩者之間。
以下是我覺得比較重要,或者說容易忘記的幾條(因為可能不常用),列出來以備以後查閱,
應該不算侵犯版權吧,呵呵。
【規則6-1-3】如果引數是指標,且僅作輸入用,則應在型別前加const,以防止該
指標在函式體內被意外修改。
例如:【規則6-1-4】如果輸入引數以值傳遞的方式傳遞物件,則宜改用「const &」方式來
傳遞,這樣可以省去臨時物件的構造和析構過程,從而提高效率。
【建議6-1-2】盡量不要使用型別和數目不確定的引數。
c 標準庫函式printf 是採用不確定引數的典型代表,其原型為:
int printf(const chat *format[, argument]…);
這種風格的函式在編譯時喪失了嚴格的型別安全檢查。
【規則6-3-2】
return 語句不可返回指向「棧記憶體」的「指標」或者「引用」,因為該內存在函式
體結束時被自動銷毀。例如
char * func(void)
【建議6-4-3】盡量避免函式帶有「記憶」功能。相同的輸入應當產生相同的輸出。
帶有「記憶」功能的函式,其行為可能是不可**的,因為它的行為可能取決於某
種「記憶狀態」。這樣的函式既不易理解又不利於測試和維護。在c/c++語言中,函式的
static 區域性變數是函式的「記憶」儲存器。建議盡量少用static 區域性變數,除非必需
【建議11-3-14】如果可能的話,使用pc-lint、logiscope 等工具進行**審查。
如果引數作輸出用,不論它是什麼資料型別,也不論它採用「指標傳遞」還是「引
用傳遞」,都不能加const 修飾,否則該引數將失去輸出功能。
const 只能修飾輸入引數:
如果輸入引數採用「值傳遞」,由於函式將自動產生臨時變數用於複製該引數,該輸
入引數本來就無需保護,所以不要加const 修飾。
記憶體分配未成功,卻使用了它。
程式設計新手常犯這種錯誤,因為他們沒有意識到記憶體分配會不成功。常用解決辦法是,
在使用記憶體之前檢查指標是否為null。如果指標p 是函式的引數,那麼在函式的入口
處用assert(p!=null)進行檢查。如果是用malloc 或new 來申請記憶體,應該用if(p==null)
或if(p!=null)進行防錯處理。
函式的return 語句寫錯了,注意不要返回指向「棧記憶體」的「指標」或者「引用」,
因為該內存在函式體結束時被自動銷毀。
// 陣列…
char a = "hello";
char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)
…// 指標…
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // 不要用 p = a;
if(strcmp(p, a) == 0) // 不要用 if (p == a)
…用運算子sizeof 可以計算出陣列的容量(位元組數)。示例7-3-3(a)中,sizeof(a)
的值是12(注意別忘了』\0』)。指標p 指向a,但是sizeof(p)的值卻是4。這是因為
sizeof(p)得到的是乙個指標變數的位元組數,相當於sizeof(char*),而不是p 所指的內
存容量。c++/c 語言沒有辦法知道指標所指的記憶體容量,除非在申請記憶體時記住它。
注意當陣列作為函式的引數進行傳遞時,該陣列自動退化為同型別的指標
7.4 指標引數是如何傳遞記憶體的?
有了malloc/free 為什麼還要new/delete ?
malloc 與free 是c++/c 語言的標準庫函式,new/delete 是c++的運算子。它們都可
用於申請動態記憶體和釋放記憶體。
對於非內部資料型別的物件而言,光用maloc/free 無法滿足動態物件的要求。物件
在建立的同時要自動執行建構函式, 物件在消亡之前要自動執行析構函式。由於
malloc/free 是庫函式而不是運算子,不在編譯器控制許可權之內,不能夠把執行建構函式
和析構函式的任務強加於malloc/free。
有乙個很重要的現象要告訴大家。對於32 位以上的應用程式而言,無論怎樣使用
malloc 與new,幾乎不可能導致「記憶體耗盡」。我在windows 98 下用visual c++編寫了
測試程式,見示例7-9。這個程式會無休止地執行下去,根本不會終止。因為32 位操作
系統支援「虛存」,記憶體用完了,自動用硬碟空間頂替。我只聽到硬碟嘎吱嘎吱地響,
window 98 已經累得對鍵盤、滑鼠毫無反應。
我可以得出這麼乙個結論:對於32 位以上的應用程式,「記憶體耗盡」錯誤處理程式
毫無用處。這下可把unix 和windows 程式設計師們樂壞了:反正錯誤處理程式不起作用,
我就不寫了,省了很多麻煩。
c++ 語言的函式內聯機制既具備巨集**的效率,又增加了安全性,而且可以自由操
作類的資料成員。所以在c++ 程式中,應該用內聯函式取代所有巨集**,「斷言assert」
恐怕是唯一的例外。assert 是僅在debug 版本起作用的巨集,它用於檢查「不應該」發生
的情況。為了不在程式的debug 版本和release 版本引起差別,assert 不應該產生任何副
作用。如果assert 是函式,由於函式呼叫會引起記憶體、**的變動,那麼將導致debug
版本與release 版本存在差異。所以assert 不是函式,而是巨集
所以說,inline 是一種「用於實現的關鍵字」,而不是一種「用於宣告的關鍵字」。
一般地,使用者可以閱讀函式的宣告,但是看不到函式的定義。儘管在大多數教科書中內
聯函式的宣告、定義體前面都加了inline 關鍵字,但我認為inline 不應該出現在函式
的宣告中。這個細節雖然不會影響函式的功能,但是體現了高質量c++/c 程式設計風格
的乙個基本原則:宣告與定義不可混為一談,使用者沒有必要、也不應該知道函式是否需
要內聯。
2)「預設的拷貝建構函式」和「預設的賦值函式」均採用「位拷貝」而非「值拷貝」
的方式來實現,倘若類中含有指標變數,這兩個函式注定將出錯。
乙個有趣的現象是,成員物件初始化的次序完全不受它們在初始化表中次序的影響,
只由成員物件在類中宣告的次序決定。這是因為類的宣告是唯一的,而類的建構函式可
以有多個,因此會有多個不同次序的初始化表。如果成員物件按照初始化表的次序進行
構造,這將導致析構函式無法得到唯一的逆序
如果不主動編寫拷貝建構函式和賦值函式,編譯器將以「位拷貝」
的方式自動生成預設的函式。倘若類中含有指標變數,那麼這兩個預設的函式就隱
含了錯誤。以類string 的兩個物件a,b 為例,假設a.m_data 的內容為「hello」,
b.m_data 的內容為「world」。
現將a 賦給b,預設賦值函式的「位拷貝」意味著執行b.m_data = a.m_data。
這將造成三個錯誤:一是b.m_data 原有的記憶體沒被釋放,造成記憶體洩露;二是
b.m_data 和a.m_data 指向同一塊記憶體,a 或b 任何一方變動都會影響另一方;三是
在物件被析構時,m_data 被釋放了兩次。
如果我們實在不想編寫拷貝建構函式和賦值函式,又不允許別人使用編譯器生成的
預設函式,怎麼辦?
偷懶的辦法是:只需將拷貝建構函式和賦值函式宣告為私有函式,不用編寫**。
例如:class a
;如果有人試圖編寫如下程式:
a b(a); // 呼叫了私有的拷貝建構函式
b = a; // 呼叫了私有的賦值函式
編譯器將指出錯誤,因為外界不可以操作a 的私有函式。
基類與派生類的析構函式應該為虛(即加virtual 關鍵字)
高質量C C程式設計指南
1.如果引數是指標,且僅作輸入用,則應在型別前加const,以防止該指標在函式體內被意外修改 2.在函式體的 入口處 對引數的有效性進行檢查 在函式體的 出口處 對return語句的正確性和效率進行檢查 3.引用的一些規則如下 1 引用被建立的同時必須被初始化 指標則可以在任何時候被初始化 2 不能...
高質量C C程式設計指南
目錄 前 言.6 第1章檔案結構 11 1.1 版權和版本的宣告 11 1.2 標頭檔案的結構 12 1.3 定義檔案的結構 13 1.4 標頭檔案的作用 13 1.5 目錄結構 14 第2章程式的版式 15 2.1 空行.15 2.2 行.16 2.3 行內的空格 17 2.4 對齊.18 2.5...
高質量C C程式設計指南 5
第5章常量 常量是一種識別符號,它的值在執行期間恆定不變。c語言用 define來定義 常量 稱 為巨集常量 c 語言除了 define外還可以用 const來定義 常量 稱 為const 常量 5.1為什麼需要常量 如果不使用常量,直接在程式中填寫數字或字串,將會有什麼麻煩?1 程式的可 讀性 可...