函數小結 :
函數是有名字的計算單元,對程序(就算是小程序)的結構化至關重要。函數的定義由返回類型、函數名、形參表(可能爲空)以及函數體組成。函數體是調用函數時執行的語句塊。在調用函數時,傳遞給函數的實參必須與相應的形參類型兼容。
給函數傳遞實參遵循變量初始化的規則。非引用類型的形參以相應實參的副本初始化。對(非引用)形參的任何修改僅作用於局部副本,並不影響實參本身。 複製龐大而複雜的值有昂貴的開銷。爲了避免傳遞副本的開銷,可將形參指定爲引用類型。對引用形參的任何修改會直接影響實參本身。應將不需要修改相應實參的引用形參定義爲const 引用。
在 C++ 中,函數可以重載。只要函數中形參的個數或類型不同,則同一個函數名可用於定義不同的函數。編譯器將根據函數調用時的實參確定調用哪一個函數。在重載函數集合中選擇適合的函數的過程稱爲函數匹配。
C++ 提供了兩種特殊的函數:內聯函數和成員函數。將函數指定爲內聯是建議編譯器在調用點直接把函數代碼展開。內聯函數避免了調用函數的代價。成員函數則是身爲類成員的函數。
1. 函數不能返回另一個函數或者內置數組類型,但可以返回指向函數的指針,或指向數組元素的指針的指針:
// ok: pointer tofirst element of the array
int *foo_bar() { /*... */ }
//在定義或聲明函數時,沒有顯式指定返回類型是不合法的:
// error: missingreturn type
test(double v1,double v2) { /* ... */
2.函數形參表
函數形參表可以爲空,但不能省略。沒有任何形參的函數可以用空形參表或含有單個關鍵字void 的形參表來表示。例如,下面關於process 的聲明是等價的:
void process() { /*... */ } // implicit void parameter list
void process(void){/* ... */ } // equivalent declaration
形參表由一系列用逗號分隔的參數類型和(可選的)參數名組成。如果兩個參數
具有相同的類型,則其類型必須重複聲明:
int manip(int v1, v2){ /* ... */ } // error
int manip(int v1, intv2) { /* ... */ } // ok
參數表中不能出現同名的參數。類似地,局部於函數的變量也不能使用與函數的任意參數相同的名字。參數名是可選的,但在函數定義中,通常所有參數都要命名。參數必須在命名後才能使用。
3. 參數傳遞
每次調用函數時,都會重新創建該函數所有的形參,此時所傳遞的實參將會初始化對應的形參。 形參的初始化與變量的初始化一樣:如果形參具有非引用類型,則複製實參的值,如果形參爲引用類型,則它只是實參的別名。
4. 複製實參的侷限性
複製實參並不是在所有的情況下都適合,不適宜複製實參的情況包括:
• 當需要在函數中修改實參的值時。
• 當需要以大型對象作爲實參傳遞時。對實際的應用而言,複製對象所付出的時間和存儲空間代價往往過在。
• 當沒有辦法實現對象的複製時。
5. 更靈活的指向const 的引用
如果函數具有普通的非const 引用形參,則顯然不能通過const 對象進行調用。畢竟,此時函數可以修改傳遞進來的對象,這樣就違背了實參的const 特性。但比較容易忽略的是,調用這樣的函數時,傳遞一個右值或具有需要轉換的類型的對象同樣是不允許的:
// function takes anon-const reference parameter
int incr(int &val)
{
return ++val;
}
int main()
{
short v1 = 0;
const int v2 = 42;
int v3 = incr(v1); // error: v1 is not an int
v3 = incr(v2); // error: v2 is const
v3 = incr(0); // error: literals arenot lvalues
v3 = incr(v1 + v2); // error: addition doesn't yield anlvalue
int v4 = incr(v3); // ok: v3 is a non const objecttype int
}
6. 傳遞指向指針的引用 –指針的交換
假設我們想編寫一個與前面交換兩個整數的swap 類似的函數,實現兩個指針的交換。已知需用* 定義指針,用& 定義引用。現在,問題在於如何將這兩個操作符結合起來以獲得指向指針的引用。這裏給出一個例子:
// swap values of twopointers to int
void ptrswap(int*&v1, int *&v2)
{
int *tmp = v2;
v2 = v1;
v1 = tmp;
}
形參 int *&v1的定義應從右至左理解:v1是一個引用,與指向int 型對象的指針相關聯。也就是說,v1只是傳遞進ptrswap 函數的任意指針的別名。重寫第7.2.2 節的main 函數,調用ptrswap 交換分別指向值10 和20 的指針:
int main()
{
int i = 10;
int j = 20;
int *pi = &i; //pi points to i
int *pj = &j; //pj points to j
cout <<"Before ptrswap():\t*pi: " << *pi <<"\t*pj: " << *pj << endl;
ptrswap(pi, pj); //now pi points to j; pj points to i
cout <<"After ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj<< endl;
return 0;
}
// 編譯並執行後,該程序產生如下結果:
Before ptrswap():*pi: 10 *pj: 20
After ptrswap(): *pi:20 *pj: 10
7. 千萬不要返回局部對象的引用
理解返回引用至關重要的是:千萬不能返回局部變量的引用。當函數執行完畢時,將釋放分配給局部對象的存儲空間。此時,對局部對象的引用就會指向不確定的內存。考慮下面的程序:
// Disaster: Functionreturns a reference to a local object
const string&manip(const string& s)
{
string ret = s;
// transform ret insome way
return ret; // Wrong:Returning reference to a local object!
}
這個函數會在運行時出錯,因爲它返回了局部對象的引用。當函數執行完畢,
字符串ret 佔用的儲存空間被釋放,函數返回值指向了對於這個程序來說不再有效的內存空間。
8. 千萬不要返回指向局部對象的指針
函數的返回類型可以是大多數類型。特別地,函數也可以返回指針類型。和返回局部對象的引用一樣,返回指向局部對象的指針也是錯誤的。一旦函數結束,局部對象被釋放,返回的指針就變成了指向不再存在的對象的懸垂指針.
9. 內聯函數
調用函數比求解等價表達式要慢得多。在大多數的機器上,調用函數都要做很多工作;調用前要先保存寄存器,並在返回時恢復;複製實參;程序還必須轉向一個新位置執行。
將函數指定爲inline 函數,(通常)就是將它在程序中每個調用點上“內聯地”展開。假設我們將shorterString 定義爲內聯函數,則調用:
cout <<shorterString(s1, s2) << endl;
在編譯時將展開爲:
cout <<(s1.size() < s2.size() ? s1 : s2) << endl;
從而消除了把shorterString 寫成函數的額外執行開銷。
從而消除了把shorterString 寫成函數的額外執行開銷。
// inline version:find longer of two strings inline conststring &
shorterString(conststring &s1, const string &s2)
{
return s1.size() <s2.size() ? s1 : s2;
}
inline 說明對於編譯器來說只是一個建議,編譯器可以選擇忽略這個。
一般來說,內聯機制適用於優化小的、只有幾行的而且經常被調用的函數。大多數的編譯器都不支持遞歸函數的內聯。一個1200 行的函數也不太可能在調用點內聯展開。
內聯函數應該在頭文件中定義,這一點不同於其他函數。
10. 重載與作用域
一般的作用域規則同樣適用於重載函數名。如果局部地聲明一個函數,則該函數將屏蔽而不是重載在外層作用域中聲明的同名函數。由此推論,每一個版本的重載函數都應在同一個作用域中聲明。
一般來說,局部地聲明函數是一種不明智的選擇。函數的聲明應放在頭文件中。
/* Program for illustration purposes only:
* It is bad style fora function to define a local variable
* with the same nameas a global name it wants to use
*/
string init(); // the name init hasglobal scope
void fcn()
{
int init = 0; // init is local andhides global init
string s = init(); // error: global init is hidden
}
…
void print(const string &);
void print(double); // overloads the print function
void fooBar(int ival)
{
void print(int); // new scope: hides previousinstances ofprint
print("Value:"); // error:print(const string &) is hidden
print(ival); //ok: print(int) is visible
print(3.14); //ok: calls print(int); print(double) is hidden
}
11. 候選函數
調用所考慮的重載函數集合,該集合中的函數稱爲候選函數。候選函數是與被調函數同名的函數,
可行函數
從候選函數中選擇一個或多個函數,它們能夠用該調用中指定的實參來調用。因此,選出來的函數稱爲可行函數.
12. 重載和const 形參
可基於函數的引用形參是指向const 對象還是指向非const 對象,實現函數重載。將引用形參定義爲const 來重載函數是合法的,因爲編譯器可以根據實參是否爲const 確定調用哪一個函數:
Recordlookup(Account&);
Record lookup(constAccount&); // new function
const Account a(0);
Account b;
lookup(a); // calls lookup(const Account&)
lookup(b); // calls lookup(Account&)
如果形參是普通的引用,則不能將const 對象傳遞給這個形參。如果傳遞了const 對象,則只有帶const 引用形參的版本纔是該調用的可行函數。
13. 指向函數的指針
函數指針是指指向函數而非指向對象的指針。
14.函數指針
函數指針類型相當地冗長。使用typedef 爲指針類型定義同義詞,可將函數指針的使用大大簡化:
typedef bool(*cmpFcn)(const string &, const string &);
在引用函數名但又沒有調用該函數時,函數名將被自動解釋爲指向函數的指針。假設有函數:
// compares lengths of two strings
boollengthCompare(const string &, const string &);
除了用作函數調用的左操作數以外,對lengthCompare 的任何使用都被解釋爲如下類型的指針:
bool (*)(const string&, const string &);
// 可使用函數名對函數指針做初始化或賦值:
cmpFcn pf1 = 0; // ok: unboundpointer to function
cmpFcn pf2 =lengthCompare; // ok: pointer typematches function's type
pf1 = lengthCompare; // ok: pointer type matchesfunction's type
pf2 = pf1; // ok: pointer types match
// 此時,直接引用函數名等效於在函數名上應用取地址操作符:
cmpFcn pf1 =lengthCompare;
cmpFcn pf2 =&lengthCompare;
函數指針只能通過同類型的函數或函數指針或0 值常量表達式進行初始化或賦值。將函數指針初始化爲0,表示該指針不指向任何函數。指向不同函數類型的指針之間不存在轉換:
string::size_typesumLength(const string&, const string&);
boolcstringCompare(char*, char*);
// pointer tofunction returning bool taking two const string& cmpFcn pf;
pf = sumLength; // error: returntype differs
pf = cstringCompare; // error: parameter typesdiffer
pf = lengthCompare; // ok: function and pointertypes match exactly
15. 通過指針調用函數
指向函數的指針可用於調用它所指向的函數。可以不需要使用解引用操作符,直接通過指針調用函數:
cmpFcn pf =lengthCompare;
lengthCompare("hi","bye"); // direct call
pf("hi","bye"); // equivalent call: pf1 implicitly dereferenced
(*pf)("hi","bye"); // equivalent call: pf1 explicitly dereferenced
如果指向函數的指針沒有初始化,或者具有0 值,則該指針不能在函數調用中使用。只有當指針已經初始化,或被賦值爲指向某個函數,方能安全地用來調用函數。
16. 指向重載函數的指針
C++ 語言允許使用函數指針指向重載的函數:
extern void ff(vector<double>);
extern void ff(unsigned int);
// which functiondoes pf1 refer to?
void (*pf1)(unsignedint) = &ff; // ff(unsigned)
//指針的類型必須與重載函數的一個版本精確匹配。如果沒有精確匹配的函數,則對該指針的初始化或賦值都將導致編譯錯誤:
// error: no match:invalid parameter list
void (*pf2)(int) =&ff;
// error: no match:invalid return type
double(*pf3)(vector<double>);
pf3 = &ff;