第五章 常量
5、1常量類型
C語言用#define啦定義常量(稱為宏常量)。C++除了#define外還可用const來定義常量(稱為const常量)。
const常量有資料類型,而宏常量沒有資料類型。編譯器可對前者進行型別安全檢查。而對後者只進行字元替換,沒有型別安全檢查。所以在C++中只使用const常量而不使用宏常量。
5、2常量定義規則
【規則5-2-1】需要對外公開的常量放在標頭檔中,不需對外公開的常量放在定義檔案的頭部。為便於管理,可把不同模組的常量集中存放在一個公用的標頭檔中。
5、3類中的常量
有時希望某些常量只能在類中有效。因#define定義的宏常量是全域的,於是想當然的認為該用const修飾資料成員來實現。但const資料成員只在某個物件存留期內是常量,而對於整個類而言卻是可變的,因類可建立多個對象,不同的對象其const資料成員的值可不同。標頭檔中不能初始化const資料成員,只能在建構函式中初始化。
怎麼才能建立在整個類中都恒定的常量呢?應該用類中的枚舉常量來實現。枚舉常量不會佔用對象的儲存空間,它們在編譯時間被全部賦值。缺點是:它的隱含資料類型是整數,其最大值有限,且不能表示浮點數。
第六章 函數設計
函數設計,函數該怎麼設計,參數、傳回值和實現。
C中函數參數和傳回值的傳遞方式:值傳遞和指標傳遞。C++中多了引用傳遞。引用傳遞的性質象指標傳遞,而方式去像值傳遞。
6、1參數的規則
【規則6-1-1】參數書寫要完整。不要唯寫參數類型忽略參數名字,若無參數用void填充。
【規則6-1-2】參數命名要恰當,順序要合理。一般,應將目的參數放在前面,源參數放在後面。
【規則6-1-3】若參數是指標,且僅作輸入用,則應在類型前加const,以防止該指標再函數體內被以外修改。
【規則6-1-4】若輸入參數以值傳遞的方式傳遞對象,則宜改用”const&”方式來傳遞,這樣可省去臨時對象的構造和析構過程,從而提高效率。
【建議6-1-1】盡量將參數個數控制在5個以內。
【建議6-1-2】盡量不要使用類型和數目不確定的參數。C標準庫函數printf是採用不確定參數的典型代表,其原型為:int
printf(const char *format[,argument]…)這種風格的函數在編譯時間喪失了嚴格的型別安全檢查。
6、2傳回值的規則
【規則6-2-1】不要省略傳回值的類型。因不加類型說明的函數,C預設返回整型;C++有嚴格的類型檢查,不會出現上述情況。C++調用C時會混淆。
【規則6-2-2】函數名字與傳回值類型在語義上不可衝突。違反這條規則的典型代表是C標準庫函數getchar。函數名字時取得char類型,但實際返回int類型。int
getchar(void)。
【規則6-2-3】不要將正常值和錯誤標識混在一起返回。正常值用輸出參數獲得,而錯誤標識用return語句返回。
C標準庫函數的設計者為什麼要將getchar聲明為令人迷惑的int型呢?正常情況,getchar的確返回單個字元。但如果getchar碰到檔案結束標識或發生讀錯誤,它必須返回一個EOF。為了區別於正常的字元,只好將EOF定義為負數(通常是-1)。因此函數getchar就成了int型。
為了避免出現上述情況,應將正常值和錯誤標識分開。getchar可改為BOOL GetChar(char *c)。
【建議6-2-1】有時候函數原本不需要傳回值,但為了增加靈活性如支援鏈式表達,可以附加傳回值。
如strcpy將strSrc拷貝到參數strDest中,同時函數傳回值又是strDest。這並非多次一舉,可獲得如下靈活性:char
str[20]; int length = strlen( strcpy(str,”Hello World”));
【建議6-2-2】若函數的傳回值是一個對象,有些場合用“引用傳遞”替換“值傳遞”可以提高些效率。而有些場合只能用“值傳遞”,否則會出錯。
6、3函數內部實現
【規則6-3-1】在函數體的“入口處”,對參數的有效性進行檢查。正確使用“斷言”(assert)來防止此類錯誤。
【規則6-3-2】在函數體的“出口處”,對return語句的正確性和效率進行檢查。注意事項如下:
Ø
return語句不可返回指向“棧記憶體”的“指標”或“引用”,因該記憶體在函數體結束時被自動銷毀。
Ø
若函數值返回是一個對象,要考慮return語句的效率。例如:return String(str1+str2);這是臨時對象的文法,表示“建立一個臨時對象並返回它”。不要以為它與“先建立一個局部對象temp並返回它的結果”是等價的。例如:String
temp(s1+s2); return temp;此代碼發生三件事。首先temp對象被建立,同時完成初始化;然後拷貝建構函式把temp拷貝到儲存傳回值的外部儲存單元中;最後,temp在函數結束時被銷毀(調用解構函式)。然而“建立一個臨時對象並返回它”的過程是不同的,編譯器直接把臨時對象建立並初始化在外部儲存單元中,省去了拷貝和析構的花費。類似,不要將return
int(x+y);寫成int temp = x+y; return temp;因內部資料類型的變數不存在構造和解構函式,顯然該“臨時變數的文法”不會提供多少效率,但程式更加簡潔易讀。
6、4其他建議
【建議6-4-1】函數功能要單一,不要設計多用途的函數。規模要小,盡量在50行內。
【建議6-4-2】盡量避免函數帶有“記憶”功能。函數的static局部變數是函數的“記憶”儲存空間。盡量少用static局部變數。
【建議6-4-3】不僅要檢查輸入參數的有效性,還要檢查通過其他途徑進入函數體內的變數的有效性,如全域變數、檔案控制代碼等。
6、5使用斷言
程式一般分Debug版本和Release版本。斷言assert是僅在Debug版本起作用的宏。為了不在程式的Debug版本和Release版本引起差別,assert不應該產生任何副作用。所以assert不是函數,而是宏。
【規則6-5-1】使用斷言捕捉不應該發生的非法情況。不要混淆非法情況與錯誤情況的區別,後者是必然存在的且一定要做出處理的。
【規則6-5-2】在函數入口處,使用斷言檢查參數的有效性。
【規則6-5-3】在編寫函數時,要進行反覆考查,並且自問“我打算做哪些假定?”一旦確定了的假定,就要用斷言對假定進行檢查。----------不懂。
6、6引用與指標的區別
引用的一些規則:
Ø
引用被建立的同時必須被初始化(指標則可在任何時候被初始化)。
Ø
不可能有NULL引用,引用必須與合法的儲存單元關聯(指標則可以是NULL)。
Ø
一旦引用被初始化,就不能改變引用的關係(指標則可以隨時改變所指的對象)。
引用的主要功能是傳遞函數的參數和傳回值。
值傳遞 指標傳遞 引用傳遞
void Func1(int x) void Func1(int *x) void Func1(int &x)
{ { {
x = x + 10; (*x) =(* x) + 10; x = x + 10;
} } }
int n = 0; int n = 0; int n = 0;
Func1(n); Func1(&n); Func1(n);
cout<<n;//n = 0; cout<<n;//n = 10; cout<<n;//n = 10;
由上可看出“引用傳遞”的性質像“指標傳遞”,而書寫方式像“值傳遞”。實際上,“引用”可做的任何事情“指標”都可做,為什麼還要“引用”呢?答案是“用適當的工具做恰如其分的工作”。指標功能強大,但非常危險。