四 函式設計

2021-06-03 20:54:41 字數 3843 閱讀 7308

函式介面的兩個要素是引數和返回值。c

語言中,函式的引數和返回值的傳遞方式有兩種:值傳遞(

pass by value

)和指標傳遞(

pass by pointer

)。c++ 

語言中多了引用傳遞(

pass by reference

)。由於引用傳遞的性質象指標傳遞,而使用方式卻象值傳遞。

引數的規則

【規則6-1-3】如果引數是指標,且僅作輸入用,則應在型別前加const,以防止該指標在函式體內被意外修改。

例如:void stringcopy(char *strdestination,

const char *strsource);

【規則6-1-4】如果輸入引數以值傳遞的方式傳遞物件,則宜改用「const &」方式來傳遞,這樣可以省去臨時物件的構造和析構過程,從而提高效率。

返回值的規則

【規則6-2-3】不要將正常值和錯誤標誌混在一起返回。正常值用輸出引數獲得,而錯誤標誌用return語句返回。

在正常情況下,getchar

的確返回單個字元。但如果

getchar

碰到檔案結束標誌或發生讀錯誤,它必須返回乙個標誌

eof。為了區別於正常的字元,只好將

eof定義為負數(通常為負

1)。因此函式

getchar

就成了int

型別。

我們在實際工作中,經常會碰到上述令人為難的問題。為了避免出現誤解,我們應該將正常值和錯誤標誌分開。即:正常值用輸出引數獲得,而錯誤標誌用return

語句返回。

函式getchar

可以改寫成 

bool getchar(char *c);

雖然gechar

比getchar

靈活,例如 

putchar(getchar()); 

但是如果

getchar

用錯了,它的靈活性又有什麼用呢?

【建議6-2-1】有時候函式原本不需要返回值,但為了增加靈活性如支援鏈式表達,可以附加返回值。

例如字串拷貝函式strcpy

的原型:

char *strcpy(char *strdest,

const char *strsrc);

strcpy函式將

strsrc

拷貝至輸出引數

strdest

中,同時函式的返回值又是

strdest

。這樣做並非多此一舉,可以獲得如下靈活性:

char str[20];

int  length = strlen( strcpy(str, 「hello world」) ); 

【建議6-2-2】如果函式的返回值是乙個物件,有些場合用「引用傳遞」替換「值傳遞」可以提高效率。而有些場合只能用「值傳遞」而不能用「引用傳遞」,否則會出錯。

例如:

class string

string的賦值函式

operate = 

的實現如下:

string & string::operate=(const string &other)

對於賦值函式,應當用「引用傳遞」的方式返回string

物件。如果用「值傳遞」的方式,雖然功能仍然正確,但由於

return

語句要把 

*this

拷貝到儲存返回值的外部儲存單元之中,增加了不必要的開銷,降低了賦值函式的效率。例如:

string a,b,c;

a = b;  // 如果用「值傳遞」,將產生一次 

*this 拷貝

a = b = c;  // 如果用「值傳遞」,將產生兩次 

*this 拷貝

string的相加函式

operate + 

的實現如下:

string  operate+(const string &s1, const string &s2)  

對於相加函式,應當用「值傳遞」的方式返回string

物件。如果改用「引用傳遞」,那麼函式返回值是乙個指向區域性物件

temp

的「引用」。由於

temp

在函式結束時被自動銷毀,將導致返回的「引用」無效。例如:

c = a + b; 

此時 a + b 

並不返回期望值,

c什麼也得不到,留下了隱患。

函式內部實現的規則

不同功能的函式其內部實現各不相同,看起來似乎無法就「內部實現」達成一致的觀點。但根據經驗,我們可以在函式體的「入口處」和「出口處」從嚴把關,從而提高函式的質量。

【規則6-3-1】在函式體的「入口處」,對引數的有效性進行檢查。

很多程式錯誤是由非法引數引起的,我們應該充分理解並正確使用「斷言」(assert

)來防止此類錯誤。

【規則6-3-2】在函式體的「出口處」,對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

的變數不存在建構函式與析構函式,雖然該「臨時變數的語法」不會提高多少效率,但是程式更加簡潔易讀。

使用斷言

斷言assert

是僅在debug

版本起作用的巨集,它用於檢查「不應該」發生的情況。

在執行過程中,如果

assert

的引數為假,那麼程式就會中止(一般地還會出現提示對話,說明在什麼地方引發了

assert)。

assert不是函式,而是巨集。程式設計師可以把

assert

看成乙個在任何系統狀態下都可以安全使用的無害測試手段。如果程式在

assert

處終止了,並不是說含有該

assert

的函式有錯誤,而是呼叫者出了差錯,

assert

可以幫助我們找到發生錯誤的原因。

動手學CV Pytorch(四) 損失函式設計

放在最前 本文參考datawhale教程 動手學cv pytorch 超好的學習教程!我們分配了許多prior bboxes,我們要想讓其 類別和目標框資訊,我們先要知道每個prior bbox和哪個目標對應,從而才能判斷 的是否準確,從而將訓練進行下去。不同方法 ground truth boxe...

函式(四) 函式過載

基於 c primer p206 1.定義過載函式 1 對於過載的函式,應該在形參數量或形參型別上有所不同。形參名是否相同,同一型別的不同別名,返回值型別是否相同不能用於區別過載函式。2 特別的,頂層 const 不影響傳入函式的物件,乙個擁有頂層 const 的形參無法和另乙個沒有頂層 const...

函式基礎(四) 遞迴函式

學過數學的可能多多少少聽過 遞迴 這個詞 那麼遞迴函式到底是怎麼個函式呢?老樣子,從需求找方法!我想要 100 2 結果繼續除2,直到結果為零,然後列印每一步的結果 怎麼寫呢?可以用迴圈!對!要的就是這種結果!但是,總有艮的 就想用函式來解決這個問題 甚至更過分的!還不想用迴圈!能做嗎?於是就有大傻...