C 語言是一門面向過程的編程語言,通過一個(gè)又一個(gè)函數(shù),把計(jì)算、過程控制等邏輯,包裝成一個(gè)個(gè)獨(dú)立的處理單元。
既然是函數(shù)調(diào)用,就一定會(huì)有參數(shù)和返回值的傳遞問題,因此也就產(chǎn)生了多種不同的編程范式,比如:
Posix 風(fēng)格:函數(shù)返回值只用來表示成功(0)或失敗(非0),其他的輸出結(jié)果都使用參數(shù)來傳遞。
Unix 風(fēng)格:函數(shù)返回值即包括錯(cuò)誤代碼,也包括有用的輸出結(jié)果。
GAI 風(fēng)格:與 Posix 有點(diǎn)類似,函數(shù)執(zhí)行成功時(shí)返回0,否則就返回非0。
今天,我們就來聊一聊這些函數(shù)調(diào)用范式在開發(fā)過程中的一些小思考。
假設(shè)有一個(gè)算法函數(shù),輸入兩個(gè)整型參數(shù),輸出一個(gè)整型結(jié)果,并且輸出一個(gè)錯(cuò)誤代碼。
第一種:輸入、輸出結(jié)果和錯(cuò)誤碼全部通過參數(shù)傳遞
既然所有的信息都是通過參數(shù)來傳遞的,那么函數(shù)定義就應(yīng)該是下面這樣:
?
void func1(int a, int b, int *result, int *err_code) { int c = a + b; *result = c; err_code = 0; // 沿用 Linux 中的習(xí)慣,0 表示沒有發(fā)生錯(cuò)誤。 }
?
因?yàn)椴恍枰祷厝魏螖?shù)據(jù),因此函數(shù)簽名的返回類型就是 void 。
因?yàn)檎{(diào)用者需要獲取輸出結(jié)果和錯(cuò)誤碼,因此在形參中, result和err_code需要傳遞指針類型的變量。
面對(duì)這樣的函數(shù)簽名,調(diào)用者就必須顯示的定義兩個(gè)變量result和err_code,用來接收函數(shù)的輸出。
?
// 調(diào)用者代碼 int result, err_code; func(1, 2, &result, &err_code); if (0 == err_code) printf("Success. result = %d ", result); else printf("Failed. err_code = %d ", err_code);
?
這種函數(shù)范式的優(yōu)點(diǎn)就是:在調(diào)用形式上統(tǒng)一,無論參數(shù)類型是什么(基礎(chǔ)類型、結(jié)構(gòu)體等待),都是整齊劃一的函數(shù)調(diào)用寫法。
缺點(diǎn)就是有點(diǎn)累贅。
面對(duì)任何一個(gè)函數(shù),調(diào)用者都必須定義一個(gè)err_code變量傳遞進(jìn)去。
如果一個(gè)函數(shù)是過程控制類型的,壓根就不會(huì)產(chǎn)生什么錯(cuò)誤碼,這樣的函數(shù)調(diào)用就顯得很臃腫,因?yàn)檎{(diào)用者壓根就不需要檢查錯(cuò)誤碼。
第二種:函數(shù)返回值表示錯(cuò)誤碼
也就是把第一種方式中的err_code參數(shù),通過函數(shù)返回值賦值給調(diào)用者。
這種函數(shù)編程范式還是比較常見的,返回值只表示錯(cuò)誤碼,其他的輸出結(jié)果都通過參數(shù)引用(指針)來傳遞。
?
int func2(int a, int b, int *result) { int c = a + b; *result = c; return 0; // 返回錯(cuò)誤碼 }
?
這樣的函數(shù)范式跟POSIX風(fēng)格很像了。
面對(duì)這樣的函數(shù),調(diào)用者的寫法就會(huì)變成這樣:
?
// 調(diào)用者代碼 int result, err_code; err_code = func2(1, 2, &result); if (0 == err_code) printf("Success. result = %d ", result); else printf("Failed. err_code = %d ", err_code);
?
看起來好像跟第一種方式?jīng)]有什么本質(zhì)區(qū)別,但是再看一下下面這樣的寫法呢:
?
// 調(diào)用者代碼 int result; if (0 == func2(1, 2, &result)) printf("Success. result = %d ", result); else printf("Failed. ");
?
這樣的代碼風(fēng)格,在Linux中是不是很常見?當(dāng)不需要處理錯(cuò)誤碼時(shí),這樣的編程方式會(huì)更方便一些。
第三種:函數(shù)返回值表示輸出結(jié)果
也就是把第一種方式中的result參數(shù),通過函數(shù)返回值賦值給調(diào)用者。
?
int func3(int a, int b, int *err_code) { int c = a + b; *err_code = 0; return c; }
?
這有點(diǎn)類似Unix中的風(fēng)格:
返回結(jié)果中包括了有用的數(shù)據(jù),但是它有一個(gè)局限:返回結(jié)果必須與錯(cuò)誤碼的類型一致。
另外還有一個(gè)問題:如果 int 型的返回結(jié)果也可能是負(fù)數(shù), 所以 Unix 中還必須使用另一個(gè)全局變量 errno 來單獨(dú)存儲(chǔ)錯(cuò)誤碼,存在線程安全問題(可以使用線程局部存儲(chǔ)來解決)。
面對(duì)這樣的函數(shù)簽名,調(diào)用者的調(diào)用方式如下:
?
// 調(diào)用者代碼 int result, err_code; result = func3(1, 2, &err_code)) if (0 == err_code) printf("Success. result = %d ", result); else printf("Failed. ");
?
這種方式的缺點(diǎn)與第一種一樣:必須定義一個(gè)變量 err_code,來接收錯(cuò)誤碼。
在不必要檢查錯(cuò)誤碼的場(chǎng)合中,顯得有點(diǎn)多此一舉。
小 結(jié)
以上的這三種函數(shù)調(diào)用方式,沒有好壞之分,只與每一位開發(fā)者的編碼習(xí)慣有關(guān)系。
而且在實(shí)際的項(xiàng)目代碼中,這三種方式都能看得到。
如果函數(shù)輸出結(jié)果是結(jié)構(gòu)體呢?
剛才討論的三種方式中,函數(shù)輸出結(jié)果reuslt是一個(gè)整型,如果它是一個(gè)結(jié)構(gòu)體類型的變量,那么哪一種方式相對(duì)比較好呢?
這就要注意另外兩點(diǎn)了:
結(jié)構(gòu)體的賦值是需要時(shí)間開銷的;
結(jié)構(gòu)體賦值時(shí),需要考慮深拷貝、淺拷貝的問題。
?
審核編輯:湯梓紅
評(píng)論
查看更多