一門語言,包括文法、語義。通常電腦書籍上只會講文法規則。這幾年下來,發現講語義的書籍太少太少(也許是我很久不看書了^_^)。本文拋磚引玉,羅列一些與函式宣告相關的常見語義。
函數在調用時,將參數壓棧,傳回值則拷貝給調用者。如果是簡單類型,事情很簡單,但是牽扯到指標或者引用,事情就會複雜一些,因為在C/C++中,由程式員負責管理記憶體和資源,一個優秀的程式員必須清楚的瞭解自己代碼內,每一次記憶體申請及對應的釋放在什麼時候發生。
比如下面這個聲明:
struct MyDate
{
int year;
int month;
int day;
};
void GetBirthday(MyDate * pBirthday);
請問pBirthday這個指標,誰負責申請空間,誰負責釋放空間?
這個問題其實比較簡單,因為參數壓棧時將指標本身壓棧,指標所指地址並不在棧中,顯然應該由調用者負責申請記憶體,對應的,應該由調用者釋放記憶體。
如果我們把函式宣告改一下,如果返回一個指標怎麼理解?
MyDate * GetBirthday(void);
常規理解,函數負責申請記憶體,調用者負責釋放。
但是。。。但是。。。如果GetBirthday()的實現不常規呢?
static MyDate myBirthday;
...
...
MyDate * GetBirthday(void)
{
return &myBirthday;
}
如果調用者delete 返回的指標,程式行為不確定。嘿嘿,所謂行為不確定,就是在你這裡程式運行好好的,在客戶那裡一定會崩潰的行為。
可能有看官要問,為啥返回一個指標呢?為啥不直接返回MyDate對象呢?
在返回對象時,編譯器會為我們產生一個不可見的臨時對象,供調用者使用,所以如果聲明成
MyDate GetBirthday(void);
與返回指標的聲明相比,在C代碼中,會多一次結構拷貝,在C++代碼裡,會多一次拷貝構造,多一次析構。在某些追求效率的場合,這些多餘的操作很難容忍。此外,如果你的對象裡有資源、記憶體管理,在析構時很有可能將資源、記憶體意外釋放掉。
有同學說,反正是公司內部的代碼,調用者看一下實現不就結了嗎?
問題在於:
1、你要求別人看你的實現,增加別人的負擔,你是在給別人挖坑;
2、如果你的代碼是編譯成庫,供別人使用怎麼辦?你的公司不願意泄露原始碼怎麼辦?
作為折衷,可以把函式宣告成
const MyDate * GetBirthday(void);
const的語義,就是告訴調用者,這個指標歸我管,你不要亂動。
由於對象傳遞時編譯器會自動產生拷貝語句,所以往往我們都是傳遞對象指標或者對象引用,下面羅列一些常見的相關語義,供大家參考:
一個方法接受普通指標,表明這個方法可能會修改這個指標指向的資料;
一個方法接受常量指標,表明這個方法承諾不會修改這個指標所指向的資料;
一個方法返回普通指標,表明這個方法要求調用者負責釋放這個指標;
一個方法返回常量指標,表明這個方法會自己處理這個指標所指空間,調用者不需要費心;
一個方法接受普通引用,表明這個方法可能會修改這個引用的資料;
一個方法接受常量引用,表明這個方法承諾不會修改這個引用的資料;
一個方法返回普通引用,表明這個方法期望或者允許調用者修改返回的對象;
一個方法返回常量引用,表明這個方法不允許調用者修改返回的對象;
這些東西看著很瑣碎,有同學可能會說:這麼多條條框框,編碼徹底成了體力活兒。
但是沒辦法,我們要注意,代碼是寫給人看的,不是寫給編譯器看的,切記切記。