對於相加函數,應當用“值傳遞”的方式返回String對象。如果改用“引用傳遞”,那麼函數返回值是一個指向局部對象temp的“引用”。由於temp在函數結束時被自動銷毀,將導致返回的“引用”無效。例如:
c = a + b;
此時 a + b 並不返回期望值,c什麼也得不到,流下了隱患。
6.3 函數內部實現的規則
不同功能的函數其內部實現各不相同,看起來似乎無法就“內部實現”達成一致的觀點。但根據經驗,我們可以在函數體的“入口處”和“出口處”從嚴把關,從而提高函數的質量。
l 【規則6-3-1】在函數體的“入口處”,對參數的有效性進行檢查。
很多程序錯誤是由非法參數引起的,我們應該充分理解並正確使用“斷言”(assert)來防止此類錯誤。詳見6.5節“使用斷言”。
l 【規則6-3-2】在函數體的“出口處”,對return語句的正確性和效率進行檢查。
如果函數有返回值,那麼函數的“出口處”是return語句。我們不要輕視return語句。如果return語句寫得不好,函數要麼出錯,要麼效率低下。
注意事項如下:
(1)return語句不可返回指向“棧內存”的“指針”或者“引用”,因為該內存在函數體結束時被自動銷毀。例如
char * Func(void)
{
char str[] = “hello world”; // str的內存位於棧上
…
return str; // 將導致錯誤
}
(2)要搞清楚返回的究竟是“值”、“指針”還是“引用”。
(3)如果函數返回值是一個對象,要考慮return語句的效率。例如
return String(s1 + s2);
這是臨時對象的語法,表示“創建一個臨時對象並返回它”。不要以為它與“先創建一個局部對象temp並返回它的結果”是等價的,如
String temp(s1 + s2);
return temp;
實質不然,上述代碼將發生三件事。首先,temp對象被創建,同時完成初始化;然後拷貝構造函數把temp拷貝到保存返回值的外部存儲單元中;最後,temp在函數結束時被銷毀(調用析構函數)。然而“創建一個臨時對象並返回它”的過程是不同的,編譯器直接把臨時對象創建並初始化在外部存儲單元中,省去了拷貝和析構的化費,提高了效率。
類似地,我們不要將
return int(x + y); // 創建一個臨時變量並返回它
寫成
int temp = x + y;
return temp;
由於內部數據類型如int,float,double的變量不存在構造函數與析構函數,雖然該“臨時變量的語法”不會提高多少效率,但是程序更加簡潔易讀。
6.4 其它建議
2 【建議6-4-1】函數的功能要單一,不要設計多用途的函數。
2 【建議6-4-2】函數體的規模要小,盡量控制在50行代碼之內。
2 【建議6-4-3】盡量避免函數帶有“記憶”功能。相同的輸入應當產生相同的輸出。
帶有“記憶”功能的函數,其行為可能是不可預測的,因為它的行為可能取決於某種“記憶狀態”。這樣的函數既不易理解又不利於測試和維護。在C/C++語言中,函數的static局部變量是函數的“記憶”存儲器。建議盡量少用static局部變量,除非必需。
2 【建議6-4-4】不僅要檢查輸入參數的有效性,還要檢查通過其它途徑進入函數體內的變量的有效性,例如全局變量、文件句柄等。
2 【建議6-4-5】用於出錯處理的返回值一定要清楚,讓使用者不容易忽視或誤解錯誤情況。
6.5 使用斷言
程序一般分為Debug版本和Release版本,Debug版本用於內部調試,Release版本發行給用戶使用。
斷言assert是僅在Debug版本起作用的宏,它用於檢查“不應該”發生的情況。示例6-5是一個內存復制函數。在運行過程中,如果assert的參數為假,那麼程序就會中止(一般地還會出現提示對話,說明在什麼地方引發了assert)。
void *memcpy(void *pvTo, const void *pvFrom, size_t size)
{
assert((pvTo != NULL) && (pvFrom != NULL)); // 使用斷言
byte *pbTo = (byte *) pvTo; // 防止改變pvTo的地址
byte *pbFrom = (byte *) pvFrom; // 防止改變pvFrom的地址
while(size -- > 0 )
*pbTo ++ = *pbFrom ++ ;
return pvTo;
}
示例6-5 復制不重疊的內存塊
assert不是一個倉促拼湊起來的宏。為了不在程序的Debug版本和Release版本引起差別,assert不應該產生任何副作用。所以assert不是函數,而是宏。程序員可以把assert看成一個在任何系統狀態下都可以安全使用的無害測試手段。如果程序在assert處終止了,並不是說含有該assert的函數有錯誤,而是調用者出了差錯,assert可以幫助我們找到發生錯誤的原因。
很少有比跟蹤到程序的斷言,卻不知道該斷言的作用更讓人沮喪的事了。你化了很多時間,不是為了排除錯誤,而只是為了弄清楚這個錯誤到底是什麼。有的時候,程序員偶爾還會設計出有錯誤的斷言。所以如果搞不清楚斷言檢查的是什麼,就很難判斷錯誤是出現在程序中,還是出現在斷言中。幸運的是這個問題很好解決,只要加上清晰的注釋即可。這本是顯而易見的.