C++ Primer 筆記-第6章 函數(shù)

6.1 函數(shù)基礎(chǔ)
函數(shù)的本質(zhì)是一個(gè)命名了的代碼塊。
函數(shù)的返回值不能是數(shù)組類型或函數(shù)類型,但可以是指向數(shù)組的或函數(shù)的指針。

6.1.1 局部對(duì)象
我們把只存在于塊執(zhí)行期間的對(duì)象成為?自動(dòng)對(duì)象。
局部靜態(tài)對(duì)象 local static object?在程序的執(zhí)行路徑第一次經(jīng)過(guò)對(duì)象定義語(yǔ)句時(shí)初始化,并且直到程序終止才被銷毀,在此期間即使對(duì)象所在的函數(shù)結(jié)束執(zhí)行也不會(huì)對(duì)它有影響。
6.1.2 函數(shù)聲明
函數(shù)只能定義一次,但是可以聲明多次。

6.2 參數(shù)傳遞
6.2.1 傳值參數(shù)
熟悉 C 的程序員常常使用指針類型的形參訪問(wèn)函數(shù)外部對(duì)象。在 C++ 語(yǔ)言中,建議使用引用類型的形參替代指針。
6.2.3?const
?形參和實(shí)參
和其他初始化過(guò)程一樣,當(dāng)用實(shí)參初始化形參時(shí)會(huì)忽略掉頂層?
const
。因?yàn)轫攲?
const
?被忽略掉,所以傳入兩個(gè)?fcn()
?的參數(shù)可以完全一樣,造成函數(shù)重載錯(cuò)誤:

void fcn(const int i){}
void fcn(int i){} ? ? ? ? ?// 錯(cuò)誤:重復(fù)定義

6.2.4 數(shù)組形參
管理指針形參有三種常用的技術(shù):
(1) 使用編輯指定數(shù)組長(zhǎng)度:要求數(shù)組本身包含一個(gè)結(jié)束標(biāo)記。
(2) 使用標(biāo)準(zhǔn)庫(kù)規(guī)范:傳遞指向數(shù)組首元素和尾后元素的指針。
(3) 顯示傳遞一個(gè)表示數(shù)組大小的形參。
形參可以是數(shù)組的引用,且?
&arr
?兩端的括號(hào)不能少:

void f(int (&arr)[10]){} ? ?// 正確:arr是具有10個(gè)整數(shù)的整型數(shù)組的引用
void f(int &arr[10]){} ? ? ?// 錯(cuò)誤:將arr聲明成了引用的數(shù)組

傳遞多維數(shù)組:

// matrix 指向數(shù)組的首元素,該數(shù)組的元素是由10個(gè)整數(shù)構(gòu)成的數(shù)組
void f(int (*matrix)[10], int rowsize){}
// 再次強(qiáng)調(diào),*matrix兩端的括號(hào)必不可少:
int *matrix[10]; ? ? ? ? ? // 10個(gè)指針構(gòu)成的數(shù)組
int (*matrix)[10]; ? ? ? ? // 指向含有10個(gè)整數(shù)的數(shù)組的指針

6.2.5?main
: 處理命令行選項(xiàng)
第二個(gè)形參?
argv
?是一個(gè)數(shù)組,它的元素是指向 C 風(fēng)格的字符串的指針。

// 兩個(gè)表達(dá)式等價(jià)
int main(int argc, char *argv[]){}
int main(int argc, char **argc){}

假定?
main
?函數(shù)位于執(zhí)行文件 prog 之內(nèi),可以向程序傳遞選項(xiàng):prog -d -o ofile data0
?,此時(shí)?argc
?等于5,argv
?應(yīng)該包含如下的C風(fēng)格字符串:

argc[0] = "prog"; ? ? ? ? ?// 或者 argv[0] 也可以指向一個(gè)空字符串
argc[1] = "-d";
argc[2] = "-o";
argc[3] = "ofile";
argc[4] = "data0";
argc[5] = 0;

當(dāng)使用?
argv
?中的實(shí)參時(shí),一定要記得可選的實(shí)參從?argv[1]
?開(kāi)始;argv[0]
?保存程序的名字,而非用戶輸入。
6.2.6 含有可變形參的函數(shù)
為了編寫(xiě)處理不同數(shù)量實(shí)參的函數(shù),c++11新標(biāo)準(zhǔn)提供了兩種主要的辦法:如果所有的實(shí)參函數(shù)類型相同,可以傳遞一個(gè)名為?
initializer_list
?的標(biāo)準(zhǔn)庫(kù)函數(shù);如果實(shí)參的類型不同,我們可以使用可變參數(shù)模板。拷貝或賦值一個(gè)?
initializer_list
?對(duì)象不會(huì)拷貝列表中的元素,拷貝后原始列表和副本共享元素。和?
vector
?不同,initializer_list
?對(duì)象中的元素永遠(yuǎn)是常量值,我們無(wú)法改變?initializer_list
?對(duì)象中元素的值。省略符形參是為了便于 C++ 程序訪問(wèn)某些特殊的 C 代碼而設(shè)置的,這些代碼用到了名為?
varargs
?的 C 標(biāo)準(zhǔn)庫(kù)功能。省略符形參僅僅用于 C 和 C++ 通用的類型,大多數(shù)類類型的對(duì)象在傳遞給省略符形參時(shí)無(wú)法正確拷貝。省略符形參只能出現(xiàn)列表的最后一個(gè)位置,省略符對(duì)應(yīng)的實(shí)參無(wú)需類型檢查:

void foo(parm_list, ...);
void foo(...);

6.3 返回類型和?return
?語(yǔ)句
6.3.2 有返回值函數(shù)
函數(shù)的返回類型決定函數(shù)調(diào)用是否是左值,調(diào)用一個(gè)返回引用的函數(shù)得到左值,其他返回類型得到右值。
允許?
main
?函數(shù)沒(méi)有?return
?語(yǔ)句直接結(jié)束。編譯器會(huì)隱式地插入一條返回 0 的?return
?語(yǔ)句。為了使?
main
?函數(shù)返回的值與機(jī)器無(wú)關(guān),cstdlib
?頭文件定義了兩個(gè)預(yù)處理變量:EXIT_FAILURS
?和?EXIT_SUCCESS
。main
?函數(shù)不能遞歸調(diào)用自身
6.3.3 返回?cái)?shù)組指針
因?yàn)閿?shù)組不能被拷貝,所以函數(shù)不能返回?cái)?shù)組。不過(guò),函數(shù)可以返回?cái)?shù)組的指針或引用:

// 使用類型別名
typedef int arrT[10]; ?// arrT是一個(gè)類型別名,它表示的類型是含有10個(gè)整數(shù)的數(shù)組
using arrT = int[10]; ?// arrT的等價(jià)聲明
arrT* func(int i); ? ? // func返回一個(gè)指向含有10個(gè)整數(shù)的數(shù)組的指針
// 不使用類型別名
Type (*function(parameter_list))[dimension];// 數(shù)組的維度必須跟在函數(shù)名字之后
//例如:
int (*func(int i))[10];
// 使用尾置返回類型
auto func(int i) -> int(*)[10];
// 如果知道函數(shù)返回的指針將指向哪個(gè)數(shù)組,可以使用 decltype 關(guān)鍵字聲明返回類型。
int odd[] = {1, 2, 3};
decltype(odd) *arrPtr(int i)
{ ?
? ?return &odd;
}


6.4 函數(shù)重載
重載的前提是要在同一個(gè)作用域下。
main
?函數(shù)不能重載。

// 一個(gè)擁有頂層 `const` 的形參無(wú)法和另一個(gè)沒(méi)有頂層 `const` 的形參區(qū)分開(kāi)來(lái):
int func(double* a);
int func(double* const a);
// 底層const形參可以區(qū)分
int func(double* b);
int func(const double* b);

調(diào)用重載函數(shù)時(shí),如有多于一個(gè)函數(shù)可以匹配,但是每一個(gè)都不是明顯的最佳選擇。此時(shí)也將發(fā)生錯(cuò)誤,稱為?二義性調(diào)用 ambiguous call。
6.4.1 重載與作用域
如果我們?cè)趦?nèi)層作用域中聲明了名字,它將隱藏外層作用域中聲明的同名實(shí)體。在不同的作用域中無(wú)法重載函數(shù)名。

6.5 特殊用途語(yǔ)言特性
6.5.1 默認(rèn)實(shí)參
要注意由于隱式類型轉(zhuǎn)換而出現(xiàn)的與預(yù)期不符合的函數(shù)調(diào)用情況。
在給定的作用域中一個(gè)形參只能被賦予一次默認(rèn)實(shí)參,函數(shù)的后續(xù)聲明只能為之前那些沒(méi)有默認(rèn)值的形參添加默認(rèn)實(shí)參,而且該形參右側(cè)的所有形參必須都有默認(rèn)值。
6.5.2 內(nèi)聯(lián)函數(shù)和?constexpr
?函數(shù)
一次函數(shù)的調(diào)用包含的工作:調(diào)用前要先保存寄存器,并在返回時(shí)恢復(fù);可能需要拷貝實(shí)參;程序轉(zhuǎn)向一個(gè)新的位置繼續(xù)執(zhí)行。
內(nèi)聯(lián)函數(shù)可以避免函數(shù)調(diào)用的開(kāi)銷,在調(diào)用點(diǎn)上 “內(nèi)聯(lián)地” 展開(kāi)。
constexpr
?函數(shù)是指能用于常量表達(dá)式的函數(shù)。函數(shù)的返回類型及所有形參的類型都得是字面值類型,而且函數(shù)體中必須有且只有一條?return
?語(yǔ)句。編譯器會(huì)把對(duì)?constexpr
?函數(shù)的調(diào)用替換成其結(jié)果值。為了能在編譯過(guò)程中隨視展開(kāi),constexpr
?函數(shù)被隱式地指定為內(nèi)聯(lián)函數(shù)。內(nèi)聯(lián)函數(shù)和?
constexpr
函數(shù)通常定義在頭文件中。
6.5.3 調(diào)試幫助
assert
是一種預(yù)處理宏,常用于檢查“不能發(fā)生”的條件。assert
?的行為依賴于一個(gè)名為?NDEBUG
?的預(yù)處理變量的狀態(tài)。如果定義了?NDEBUG
,則?assert
?什么也不做。應(yīng)該把?
assert
?當(dāng)作調(diào)試程序的一種輔助手段,但是不能用它替代真正的運(yùn)行時(shí)邏輯檢查,也不能替代程序本身應(yīng)該包含的錯(cuò)誤檢查。編譯器定義了5個(gè)對(duì)程序調(diào)試很有用的局部靜態(tài)變量:

_ _func_ _ 存放函數(shù)名的字符串字面值
_ _FILE_ _ 存放文件名的字符串字面值
_ _LINE_ _ 存放當(dāng)前行號(hào)的整型字面值
_ _TIME_ _ 存放文件編譯時(shí)間的字符串字面值
_ _DATE_ _ 存放文件編譯日期的字符串字面值


6.6 函數(shù)匹配
函數(shù)匹配的第一步是選定本次調(diào)用對(duì)應(yīng)的重載函數(shù)集,集合中的函數(shù)稱為?候選函數(shù) candidate function。
函數(shù)匹配的第二步考察本次調(diào)用提供的實(shí)參,然后從候選函數(shù)中選出能被這組實(shí)參調(diào)用的函數(shù),這些新選出的函數(shù)稱為?可行函數(shù) viable fuction。
函數(shù)匹配的第三步是從可行函數(shù)中選擇與本次調(diào)用最匹配的函數(shù)。
含有多個(gè)形參的函數(shù)匹配規(guī)則:
·該函數(shù)每個(gè)實(shí)參的匹配都不劣與其他可行函數(shù)需要的匹配
·至少有一個(gè)實(shí)參的匹配優(yōu)于其他可行函數(shù)提供的匹配。
·如果在檢查了所有實(shí)參之后沒(méi)有任何一個(gè)函數(shù)脫穎而出,則該調(diào)用是錯(cuò)誤的。編譯器將報(bào)告二義性調(diào)用的信息。
調(diào)用重載函數(shù)時(shí)應(yīng)盡量避免強(qiáng)制類型轉(zhuǎn)換。如果在實(shí)際應(yīng)用中確實(shí)需要強(qiáng)制類型轉(zhuǎn)換,則說(shuō)明我們?cè)O(shè)計(jì)的形參集合不合理。
6.6.1 實(shí)參類型轉(zhuǎn)換
為了確定最佳匹配,編譯器將實(shí)參類型到形參類型的轉(zhuǎn)換劃分了成了幾個(gè)等級(jí):
(1) 精確匹配:
·實(shí)參類型?和?形參類型?相同。
·實(shí)參從?數(shù)組類型?或?函數(shù)類型?轉(zhuǎn)換成對(duì)應(yīng)的指針類型。
· 向?qū)崊⑻砑禹攲?
const
?或者從實(shí)參中刪除頂層?const
。(2) 通過(guò)?
const
?轉(zhuǎn)換?實(shí)現(xiàn)的匹配。(3) 通過(guò)?類型提升?實(shí)現(xiàn)的匹配。
(4) 通過(guò)?算術(shù)類型轉(zhuǎn)換?或?指針轉(zhuǎn)換?實(shí)現(xiàn)的匹配。
(5) 通過(guò)?類類型轉(zhuǎn)換?實(shí)現(xiàn)的匹配。

6.7 函數(shù)指針
函數(shù)指針指向的是?函數(shù)?而非?對(duì)象?。函數(shù)指針指向某種特定的類型。函數(shù)的類型由它的返回類型和形參類型共同決定,與函數(shù)名無(wú)關(guān)。

// 函數(shù)聲明:
bool func(int a);
/ 函數(shù)指針:
bool (*pf)(int); ? ? ? ? ?//未初始化
// *pf兩端的括號(hào)必不可少
bool *pf(int); ? ? ? ? ? ?// 聲明了一個(gè)名為pf的函數(shù),該函數(shù)返回bool*

當(dāng)我們把函數(shù)名作為值使用時(shí),該函數(shù)自動(dòng)地轉(zhuǎn)換成指針。

pf = func; pf = &func; ? ?// 等價(jià)

直接使用指向函數(shù)的指針調(diào)用該函數(shù),無(wú)需提前解引用指針:

bool b1 = pf(1); ? ? ? ? ?// 調(diào)用func()
bool b2 = (*pf)(1); ? ? ? // 等價(jià)調(diào)用
bool b3 = func(1); ? ? ? ?// 等價(jià)調(diào)用

在指向不同函數(shù)類型的指針間不存在轉(zhuǎn)換規(guī)則。可以給函數(shù)值賦值一個(gè)?
nullptr
?或值為 0 的整型常量表達(dá)式,表示該指針沒(méi)有指向任何一個(gè)函數(shù)。如果定義了指向重載函數(shù)的指針,編譯器通過(guò)指針類型決定選用哪個(gè)函數(shù),指針類型必須與重載函數(shù)中的某一個(gè)精確匹配。
和數(shù)組類似,不能定義函數(shù)類型的形參,但是形參可以是指向函數(shù)的指針。

void use(bool pf(int)); ? // 形參是函數(shù)類型,會(huì)自動(dòng)轉(zhuǎn)換成指向函數(shù)的指針void use(bool (*pf)(int)); ? ? // 等價(jià)的聲明,顯示地將形參定義成指向函數(shù)的指針

直接使用函數(shù)指針類型顯得冗長(zhǎng)而煩瑣。類型別名?
typedef
?和?decltype
?能簡(jiǎn)化使用函數(shù)指針的代碼。下面的四個(gè)聲明語(yǔ)句聲明的是同一個(gè)函數(shù),編譯器會(huì)自動(dòng)將函數(shù)類型轉(zhuǎn)換成指針。

bool func(int a);
// Func 和 Func2是函數(shù)類型
typedef bool Func(int);
typedef decltype(func) Func2; ? ?// 等價(jià)
void use(Func); ? ? ? ? ? ? ? ? ?// 聲明1
void use(Func2); ? ? ? ? ? ? ? ? // 聲明2
// FuncP 和 FuncP2 是指向函數(shù)的指針
typedef bool (*Funcp)(int);
typedef decltype(func) *Funcp2; ?// 等價(jià)
void use(Funcp); ? ? ? ? ? ? ? ? // 聲明3v
oid use(Funcp2); ? ? ? ? ? ? ? ? // 聲明4

與函數(shù)不能直接返回?數(shù)組?但能返回?指向數(shù)組的指針?類似,函數(shù)不能直接返回?函數(shù)類型?但能返回?指向函數(shù)的指針。
必須把返回類型寫(xiě)成?指針?形式,編譯器?不會(huì)自動(dòng)地?將函數(shù)返回類型當(dāng)成對(duì)應(yīng)的指針類型處理。

// 使用類型別名聲明f1:
using F = int(int*, int); ? ? ? ?// F是函數(shù)類型,不是指針
using PF = int(*)(int*, int); ? ?// PF是指針類型
PF f1(int); ? ? ? ? ? ? ? ? ?// 正確:PF是指向函數(shù)的指針,f1返回指向函數(shù)的指針
F *f1(int); ? ? ? ? ? ? ? ? ?// 正確:顯示地指定返回類型是指向函數(shù)的指針F
f1(int); ? ? ? ? ? ? ? ? ? ? // 錯(cuò)誤:F是函數(shù)類型,f1不能返回一個(gè)函數(shù)
// 直接聲明f1:
int (*f1(int))(int*, int);
// 使用尾后返回類型聲明f1:
auto f1(int) -> int(*)(int*, int);
// 如果明確知道返回函數(shù)是哪一個(gè),就能使用 decltype 簡(jiǎn)化書(shū)寫(xiě)
// decltype返回的是函數(shù)類型而非指針,所以需要顯式地加上*以表明需要返回的是指針而非函數(shù)本身
bool func(int);
decltype(func) *getFcn(const string&);


