C++學習筆記3

函數小結 :

        函數是有名字的計算單元,對程序(就算是小程序)的結構化至關重要。函數的定義由返回類型、函數名、形參表(可能爲空)以及函數體組成。函數體是調用函數時執行的語句塊。在調用函數時,傳遞給函數的實參必須與相應的形參類型兼容。

        給函數傳遞實參遵循變量初始化的規則。非引用類型的形參以相應實參的副本初始化。對(非引用)形參的任何修改僅作用於局部副本,並不影響實參本身。 複製龐大而複雜的值有昂貴的開銷。爲了避免傳遞副本的開銷,可將形參指定爲引用類型。對引用形參的任何修改會直接影響實參本身。應將不需要修改相應實參的引用形參定義爲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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章