日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

《C++ Primer 5th》笔记(6 / 19):函数

發(fā)布時(shí)間:2023/12/13 c/c++ 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《C++ Primer 5th》笔记(6 / 19):函数 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

    • 函數(shù)基礎(chǔ)
      • 局部對(duì)象
      • 函數(shù)聲明
      • 分離式編譯
    • 參數(shù)傳遞
      • 傳遞參數(shù)
      • 傳引用參數(shù)
        • 使用引用避免拷貝
        • 使用引用形參返回額外信息
      • const形參和實(shí)參
        • 指針或引用形參與const
        • 盡量使用常量引用
      • 數(shù)組形參
        • 使用標(biāo)記指定數(shù)組長(zhǎng)度
        • 使用標(biāo)準(zhǔn)庫規(guī)范
        • 顯示傳遞一個(gè)表示數(shù)組大小的形參
        • 數(shù)組形參和const
        • 數(shù)組引用形參
        • 傳遞多維數(shù)組
      • main:處理命令行選項(xiàng)
      • 含有可變形參的函數(shù)
        • initializer_list形參
        • 省略符形參
    • 返回類型和return語句
      • 無返回值函數(shù)
      • 有返回值函數(shù)
        • 值是如何被返回的
        • 不要返回局部對(duì)象的引用或指針
        • 返回類類型的函數(shù)和調(diào)用運(yùn)算符
        • 引用返回左值
        • 列表初始化返回值
        • 主函數(shù)main的返回值
        • 遞歸
      • 返回?cái)?shù)組指針
        • 聲明一個(gè)返回?cái)?shù)組指針的函數(shù)
        • 使用尾置返回類型
        • 使用decltype
    • 函數(shù)重載
      • 定義重載函數(shù)
        • 判斷兩個(gè)形參的類型是否相異
        • 重載和const形參
        • 建議:何時(shí)不應(yīng)該重載函數(shù)
        • const_cast和重載
        • 調(diào)用重載的函數(shù)
      • 重載與作用域
    • 特殊用途語言特性
      • 默認(rèn)實(shí)參
        • 使用默認(rèn)實(shí)參調(diào)用函數(shù)
        • 默認(rèn)實(shí)參聲明
        • 默認(rèn)實(shí)參初始值
      • 內(nèi)聯(lián)函數(shù)和constexpr函數(shù)
        • 內(nèi)聯(lián)函數(shù)可避免函數(shù)調(diào)用的開銷
        • constexpr函數(shù)
        • 把內(nèi)聯(lián)函數(shù)和constexpr函數(shù)放在頭文件內(nèi)
      • 調(diào)試幫助
        • assert預(yù)處理宏
        • NDEBUG預(yù)處理變量
    • 函數(shù)匹配
      • 例子:調(diào)用應(yīng)該選用哪個(gè)重載函數(shù)
        • 確定候選函數(shù)和可行函數(shù)
        • 尋找最佳匹配(如果有的話)
        • 含有多個(gè)形參的函數(shù)匹配
      • 實(shí)參類型轉(zhuǎn)換
        • 需要類型提升和算術(shù)類型轉(zhuǎn)換的匹配
        • 函數(shù)匹配和const實(shí)參
    • 函數(shù)指針
      • 使用函數(shù)指針
        • 重載函數(shù)的指針
        • 函數(shù)指針形參
        • 返回指向函數(shù)的指針
        • 將auto和decltype用于函數(shù)指針類型

函數(shù)是一個(gè)命名了的代碼塊,我們通過調(diào)用函數(shù)執(zhí)行相應(yīng)的代碼。函數(shù)可以有0個(gè)或多個(gè)參數(shù),而且(通常)會(huì)產(chǎn)生一個(gè)結(jié)果。可以重載函數(shù),也就是說,同一個(gè)名字可以對(duì)應(yīng)幾個(gè)不同的函數(shù)。

函數(shù)基礎(chǔ)

一個(gè)典型的函數(shù)(function)定義包括以下部分:

  • 返回類型(return type)
  • 函數(shù)名字
  • 由0個(gè)或多個(gè)形參(parameter)組成的列表,形參以逗號(hào)隔開,形參的列表位于一對(duì)圓括號(hào)之內(nèi)
  • 函數(shù)體。函數(shù)執(zhí)行的操作在語句塊中說明,該語句塊稱為函數(shù)體( function body)

我們通過調(diào)用運(yùn)算符(call operator)來執(zhí)行函數(shù)。調(diào)用運(yùn)算符的形式是一對(duì)圓括號(hào),它作用于一個(gè)表達(dá)式,該表達(dá)式是函數(shù)或者指向函數(shù)的指針;

圓括號(hào)之內(nèi)是一個(gè)用逗號(hào)隔開的實(shí)參(argument)列表,我們用實(shí)參初始化函數(shù)的形參。

調(diào)用表達(dá)式的類型就是函數(shù)的返回類型。

編寫函數(shù)

舉個(gè)例子,我們準(zhǔn)備編寫一個(gè)求數(shù)的階乘的程序。n 的階乘是從1到n所有數(shù)字的乘積,例如5的階乘是120。

1 * 2 * 3 * 4 * 5 = 120

程序如下所示:

// factorial of val is val * (val - 1) * (val - 2) . . . * ((val - (val - 1)) * 1) int fact(int val) {int ret = 1;// local variable to hold the result as we calculate itwhile (val > 1)ret *= val--; // assign ret * val to ret and decrement valreturn ret; // return the result }

調(diào)用函數(shù)

要調(diào)用fact函數(shù),必須提供一個(gè)整數(shù)值,調(diào)用得到的結(jié)果也是一個(gè)整數(shù):

int main() {int j = fact(5); // j equals 120, i.e., the result of fact(5)cout << "5! is " << j << endl;return 0; }

函數(shù)的調(diào)用完成兩項(xiàng)工作:

  • 用實(shí)參初始化函數(shù)對(duì)應(yīng)的形參,
  • 將控制權(quán)轉(zhuǎn)移給被調(diào)用函數(shù)。此時(shí),主調(diào)函數(shù)(calling function)的執(zhí)行被暫時(shí)中斷,被調(diào)函數(shù)(called function)開始執(zhí)行。
  • 執(zhí)行函數(shù)的第一步是(隱式地)定義并初始化它的形參。因此,當(dāng)調(diào)用fact函數(shù)時(shí),首先創(chuàng)建一個(gè)名為val的int變量,然后將它初始化為調(diào)用時(shí)所用的實(shí)參5。

    當(dāng)遇到一條return語句時(shí)函數(shù)結(jié)束執(zhí)行過程。和函數(shù)調(diào)用一樣,return語句也完成兩項(xiàng)工作:

  • 返回return 語句中的值(如果有的話),
  • 將控制權(quán)從被調(diào)函數(shù)轉(zhuǎn)移回主調(diào)函數(shù)。
  • 函數(shù)的返回值用于初始化調(diào)用表達(dá)式的結(jié)果,之后繼續(xù)完成調(diào)用所在的表達(dá)式的剩余部分。因此,我們對(duì)fact函數(shù)的調(diào)用等價(jià)于如下形式:

    int val = 5; // initialize val from the literal 5 int ret = 1; // code from the body of fact while (val > 1)ret *= val--; int j = ret; // initialize j as a copy of ret

    形參和實(shí)參

    實(shí)參是形參的初始值。第一個(gè)實(shí)參初始化第一個(gè)形參,第二個(gè)實(shí)參初始化第二個(gè)形參,以此類推。盡管實(shí)參與形參存在對(duì)應(yīng)關(guān)系,但是并沒有規(guī)定實(shí)參的求值順序(參見4.1.3節(jié),第123頁)。編譯器能以任意可行的順序?qū)?shí)參求值。

    實(shí)參的類型必須與對(duì)應(yīng)的形參類型匹配,這一點(diǎn)與之前的規(guī)則是一致的,我們知道在初始化過程中初始值的類型也必須與初始化對(duì)象的類型匹配。函數(shù)有幾個(gè)形參,我們就必須提供相同數(shù)量的實(shí)參。因?yàn)楹瘮?shù)的調(diào)用規(guī)定實(shí)參數(shù)量應(yīng)與形參數(shù)量一致,所以形參一定會(huì)被初始化。

    在上面的例子中,fact函數(shù)只有一個(gè)int類型的形參,所以每次我們調(diào)用它的時(shí)候,都必須提供一個(gè)能轉(zhuǎn)換成int的實(shí)參:

    fact( "hello" ); //錯(cuò)誤:實(shí)參類型不正確 fact(); //錯(cuò)誤:實(shí)參數(shù)量不足 fact(42, 10, 0); //錯(cuò)誤:實(shí)參數(shù)量過多 fact(3.14); //正確:該實(shí)參能轉(zhuǎn)換成int類型
    • 因?yàn)椴荒軐onst char*轉(zhuǎn)換成int,所以第一個(gè)調(diào)用失敗。
    • 第二個(gè)和第三個(gè)調(diào)用也會(huì)失敗,不過錯(cuò)誤的原因與第一個(gè)不同,它們是因?yàn)閭魅氲膶?shí)參數(shù)量不對(duì)。要想調(diào)用fact函數(shù)只能使用一個(gè)實(shí)參,只要實(shí)參數(shù)量不是一個(gè),調(diào)用都將失敗。
    • 最后一個(gè)調(diào)用是合法的,因?yàn)?double可以轉(zhuǎn)換成int。執(zhí)行調(diào)用時(shí),實(shí)參隱式地轉(zhuǎn)換成int類型(截去小數(shù)部分),調(diào)用等價(jià)于
    fact (3);

    函數(shù)的形參列表

    函數(shù)的形參列表可以為空,但是不能省略。要想定義一個(gè)不帶形參的函數(shù),最常用的辦法是書寫一個(gè)空的形參列表。不過為了與C語言兼容,也可以使用關(guān)鍵字void表示函數(shù)沒有形參:

    void f1(){/* ...*/} //隱式地定義空形參列表 void f2(void){/* ...*/} //顯式地定義空形參列表*

    形參列表中的形參通常用逗號(hào)隔開,其中每個(gè)形參都是含有一個(gè)聲明符的聲明。即使兩個(gè)形參的類型一樣,也必須把兩個(gè)類型都寫出來:

    int f3(int v1, v2){/* ...*/ }//錯(cuò)誤 int f4(int v1, int v2){/* ...*/}//正確

    任意兩個(gè)形參都不能同名,而且函數(shù)最外層作用域中的局部變量也不能使用與函數(shù)形參一樣的名字。
    形參名是可選的,但是由于我們無法使用未命名的形參,所以形參一般都應(yīng)該有個(gè)名字。

    偶爾,函數(shù)確實(shí)有個(gè)別形參不會(huì)被用到,則此類形參通常不命名以表示在函數(shù)體內(nèi)不會(huì)使用它。不管怎樣,是否設(shè)置未命名的形參并不影響調(diào)用時(shí)提供的實(shí)參數(shù)量。即使某個(gè)形參不被函數(shù)使用,也必須為它提供一個(gè)實(shí)參。

    函數(shù)返回類型

    大多數(shù)類型都能用作函數(shù)的返回類型。一種特殊的返回類型是void,它表示函數(shù)不返回任何值。函數(shù)的返回類型不能是數(shù)組類型或函數(shù)類型,但可以是指向數(shù)組或函數(shù)的指針,在本章,會(huì)陸續(xù)介紹。

    局部對(duì)象

    在C++語言中,名字有作用域,對(duì)象有生命周期(lifetime)。理解這兩個(gè)概念非常重要。

    • 名字的作用域是程序文本的一部分,名字在其中可見。

    • 對(duì)象的生命周期是程序執(zhí)行過程中該對(duì)象存在的一段時(shí)間。

    如我們所知,函數(shù)體是一個(gè)語句塊。塊構(gòu)成一個(gè)新的作用域,我們可以在其中定義變量。形參和函數(shù)體內(nèi)部定義的變量統(tǒng)稱為局部變量(local variable)。它們對(duì)函數(shù)而言是“局部”的,僅在函數(shù)的作用域內(nèi)可見,同時(shí)局部變量還會(huì)隱藏(hide)在外層作用域中同名的其他所有聲明中。

    在所有函數(shù)體之外定義的對(duì)象存在于程序的整個(gè)執(zhí)行過程中。此類對(duì)象在程序啟動(dòng)時(shí)被創(chuàng)建,直到程序結(jié)束才會(huì)銷毀。局部變量的生命周期依賴于定義的方式。

    自動(dòng)對(duì)象

    對(duì)于普通局部變量對(duì)應(yīng)的對(duì)象來說,當(dāng)函數(shù)的控制路徑經(jīng)過變量定義語句時(shí)創(chuàng)建該對(duì)象,當(dāng)?shù)竭_(dá)定義所在的塊末尾時(shí)銷毀它。我們把只存在于塊執(zhí)行期間的對(duì)象稱為自動(dòng)對(duì)象(automatic object)。當(dāng)塊的執(zhí)行結(jié)束后,塊中創(chuàng)建的自動(dòng)對(duì)象的值就變成未定義的了。(呼之即來,揮之即去)

    形參是一種自動(dòng)對(duì)象。函數(shù)開始時(shí)為形參申請(qǐng)存儲(chǔ)空間,因?yàn)樾螀⒍x在函數(shù)體作用域之內(nèi),所以一旦函數(shù)終止,形參也就被銷毀。

    我們用傳遞給函數(shù)的實(shí)參初始化形參對(duì)應(yīng)的自動(dòng)對(duì)象。對(duì)于局部變量對(duì)應(yīng)的自動(dòng)對(duì)象來說,則分為兩種情況:

    • 如果變量定義本身含有初始值,就用這個(gè)初始值進(jìn)行初始化;
    • 否則,如果變量定義本身不含初始值,執(zhí)行默認(rèn)初始化。這意味著內(nèi)置類型的未初始化局部變量將產(chǎn)生未定義的值。

    局部靜態(tài)對(duì)象

    某些時(shí)候,有必要令局部變量的生命周期貫穿函數(shù)調(diào)用及之后的時(shí)間。可以將局部變量定義成static類型從而獲得這樣的對(duì)象。局部靜態(tài)對(duì)象(local static object)在程序的執(zhí)行路徑第一次經(jīng)過對(duì)象定義語句時(shí)初始化,并且直到程序終止才被銷毀,在此期間即使對(duì)象所在的函數(shù)結(jié)束執(zhí)行也不會(huì)對(duì)它有影響。

    (MyNote:局部靜態(tài)對(duì)象具有全局變量長(zhǎng)命期與局部變量的私有性。)

    舉個(gè)例子,下面的函數(shù)統(tǒng)計(jì)它自己被調(diào)用了多少次,這樣的函數(shù)也許沒什么實(shí)際意義,但是足夠說明問題:

    size_t count_calls() {static size_t ctr = 0; // value will persist across callsreturn ++ctr; }int main() {for (size_t i = 0; i != 10; ++i)cout << count_calls() << endl;return 0; }

    這段程序?qū)⑤敵鰪?到10(包括10在內(nèi))的數(shù)字。

    如果局部靜態(tài)變量沒有顯式的初始值,它將執(zhí)行值初始化,內(nèi)置類型的局部靜態(tài)變量初始化為0。

    函數(shù)聲明

    和其他名字一樣,函數(shù)的名字也必須在使用之前聲明。類似于變量,函數(shù)只能定義一次,但可以聲明多次。唯一的例外是如第15章將要介紹的,如果一個(gè)函數(shù)永遠(yuǎn)也不會(huì)被我們用到,那么它可以只有聲明沒有定義。

    函數(shù)的聲明和函數(shù)的定義非常類似,唯一的區(qū)別是函數(shù)聲明無須函數(shù)體,用一個(gè)分號(hào)替代即可。

    因?yàn)楹瘮?shù)的聲明不包含函數(shù)體,所以也就無須形參的名字。事實(shí)上,在函數(shù)的聲明中經(jīng)常省略形參的名字。盡管如此,寫上形參的名字還是有用處的,它可以幫助使用者更好地理解函數(shù)的功能:

    //我們選擇beg和end作為形參的名字以表示這兩個(gè)迭代器劃定了輸出值的范圍 void print (vector<int>::const_iterator beg, vector<int>:: const_iterator end) ;

    函數(shù)的三要素(返回類型、函數(shù)名、形參類型)描述了函數(shù)的接口,說明了調(diào)用該函數(shù)所需的全部信息。函數(shù)聲明也稱作函數(shù)原型(function prototype)。

    在頭文件中進(jìn)行函數(shù)聲明

    回憶之前所學(xué)的知識(shí),我們建議變量在頭文件中聲明,在源文件中定義。與之類似,函數(shù)也應(yīng)該在頭文件中聲明而在源文件中定義

    看起來把函數(shù)的聲明直接放在使用該函數(shù)的源文件中是合法的,也比較容易被人接受;但是這么做可能會(huì)很煩瑣而且容易出錯(cuò)。相反,如果把函數(shù)聲明放在頭文件中,就能確保同一函數(shù)的所有聲明保持一致。而且一旦我們想改變函數(shù)的接口,只需改變一條聲明即可。

    定義函數(shù)的源文件應(yīng)該把含有函數(shù)聲明的頭文件包含進(jìn)來,編譯器負(fù)責(zé)驗(yàn)證函數(shù)的定義和聲明是否匹配。

    Best Practices:含有函數(shù)聲明的頭文件應(yīng)該被包含到定義函數(shù)的源文件中。

    (MyNote:函數(shù)聲明在頭文件,定義在源文件。)

    分離式編譯

    隨著程序越來越復(fù)雜,我們希望把程序的各個(gè)部分分別存儲(chǔ)在不同文件中。例如,函數(shù)存在一個(gè)文件里,把使用這些函數(shù)的代碼存在其他源文件中。為了允許編寫程序時(shí)按照邏輯關(guān)系將其劃分開來,C++語言支持所謂的分離式編譯(separate compilation)。分離式編譯允許我們把程序分割到幾個(gè)文件中去,每個(gè)文件獨(dú)立編譯。

    編譯和鏈接多個(gè)源文件

    舉個(gè)例子,假設(shè)fact函數(shù)的定義位于一個(gè)名為fact.cc的文件中,它的聲明位于名為Chapter6.h的頭文件中。顯然與其他所有用到fact函數(shù)的文件一樣,fact.cc應(yīng)該包含chapter6.h頭文件(#include “Chapter6.h”)。

    另外,我們?cè)诿麨閒actMain.cc 的文件中創(chuàng)建main函數(shù),main函數(shù)將調(diào)用fact函數(shù)。要生成可執(zhí)行文件(executable file),必須告訴編譯器我們用到的代碼在哪里。

    對(duì)于上述幾個(gè)文件來說,編譯的過程如下所示:

    $ cc factMain.cc fact.cc # generates factMain.exe or a.out $ cc factMain.cc fact.cc -o main # generates main or main.exe

    其中,cc是編譯器的名字,$是系統(tǒng)提示符,#后面是命令行下的注釋語句。接下來如果運(yùn)行可執(zhí)行文件,就會(huì)執(zhí)行我們定義的main函數(shù)。

    如果我們修改了其中一個(gè)源文件,那么只需重新編譯那個(gè)改動(dòng)了的文件。大多數(shù)編譯器提供了分離式編譯每個(gè)文件的機(jī)制,這一過程通常會(huì)產(chǎn)生一個(gè)后綴名是.obj(Windows)或.o(UNIX)的文件,后綴名的含義是該文件包含對(duì)象代碼(object code)。

    接下來編譯器負(fù)責(zé)把對(duì)象文件鏈接在一起形成可執(zhí)行文件。在我們的系統(tǒng)中,編譯的過程如下所示:

    $ cc -c factMain.cc # generates factMain.o $ cc -c fact.cc # generates fact.o $ cc factMain.o fact.o # generates factMain.exe or a.out $ cc factMain.o fact.o -o main # generates main or main.exe

    你可以仔細(xì)閱讀編譯器的用戶手冊(cè),弄清楚由多個(gè)文件組成的程序是如何編譯并執(zhí)行的。

    (MyNote:這就是頭文件與源文件如何聯(lián)系在一起。)

    參數(shù)傳遞

    如前所述,每次調(diào)用函數(shù)時(shí)都會(huì)重新創(chuàng)建它的形參,并用傳入的實(shí)參對(duì)形參進(jìn)行初始化。

    Note:形參初始化的機(jī)理與變量初始化一樣。

    和其他變量一樣,形參的類型決定了形參和實(shí)參交互的方式。如果形參是引用類型,它將綁定到對(duì)應(yīng)的實(shí)參上;否則,將實(shí)參的值拷貝后賦給形參

    • 當(dāng)形參是引用類型時(shí),我們說它對(duì)應(yīng)的實(shí)參被引用傳遞(passed by reference)或者函數(shù)被傳引用調(diào)用(called by reference)。和其他引用一樣,引用形參也是它綁定的對(duì)象的別名;也就是說,引用形參是它對(duì)應(yīng)的實(shí)參的別名。

    • 當(dāng)實(shí)參的值被拷貝給形參時(shí),形參和實(shí)參是兩個(gè)相互獨(dú)立的對(duì)象。我們說這樣的實(shí)參被值傳遞(passed by value)或者函數(shù)被傳值調(diào)用(called by value)。

    傳遞參數(shù)

    當(dāng)初始化一個(gè)非引用類型的變量時(shí),初始值被拷貝給變量。此時(shí),對(duì)變量的改動(dòng)不會(huì)影響初始值:

    int n = 0; //int類型的初始變量 int i = n; // i是n的值的副本 i = 42; // i的值改變;n的值不變

    傳值參數(shù)的機(jī)理完全一樣,函數(shù)對(duì)形參做的所有操作都不會(huì)影響實(shí)參。

    例如,在fact函數(shù)

    int fact(int val) {int ret = 1;// local variable to hold the result as we calculate itwhile (val > 1)ret *= val--; // assign ret * val to ret and decrement valreturn ret; // return the result }

    內(nèi)對(duì)變量val執(zhí)行遞減操作:

    ret *= val--;//將val的值減1

    盡管fact函數(shù)改變了val的值,但是這個(gè)改動(dòng)不會(huì)影響傳入fact的實(shí)參。調(diào)用fact(i)不會(huì)改變i的值。

    指針形參

    指針的行為和其他非引用類型一樣。當(dāng)執(zhí)行指針拷貝操作時(shí),拷貝的是指針的值(MyNote:指針,即地址值)。拷貝之后,兩個(gè)指針是不同的指針。因?yàn)橹羔樖刮覀兛梢蚤g接地訪問它所指的對(duì)象,所以通過指針可以修改它所指對(duì)象的值:

    int n = 0, i = 42; int *p = &n, *q = &i; // p points to n; q points to i *p = 42; // value in n is changed; p is unchanged p = q; // p now points to i; values in i and n are unchanged

    指針形參的行為與之類似:

    // function that takes a pointer and sets the pointed-to value to zero void reset(int *ip) {*ip = 0; // changes the value of the object to which ip pointsip = 0; // changes only the local copy of ip; the argument is unchanged }

    調(diào)用reset函數(shù)之后,實(shí)參所指的對(duì)象被置為o,但是實(shí)參本身并沒有改變:

    int i = 42; reset(&i); // changes i but not the address of i cout << "i = " << i << endl; // prints i = 0

    Best Practices:熟悉C的程序員常常使用指針類型的形參訪問函數(shù)外部的對(duì)象。在C++語言中,建議使用引用類型的形參替代指針。

    (MyNote:引用,我個(gè)人理解為特殊的指針。)

    傳引用參數(shù)

    回憶過去所學(xué)的知識(shí),我們知道對(duì)于引用的操作實(shí)際上是作用在引用所引的對(duì)象上。

    int n = 0, i = 42; int &r = n; // r is bound to n (i.e., r is another name for n) r = 42; // n is now 42 r = i; // n now has the same value as i i = r; // i has the same value as n

    引用形參的行為與之類似。通過使用引用形參,允許函數(shù)改變一個(gè)或多個(gè)實(shí)參的值。

    舉個(gè)例子,我們可以改寫上一小節(jié)的reset程序,使其接受的參數(shù)是引用類型而非指針:

    // function that takes a reference to an int and sets the given object to zero void reset(int &i) // i is just another name for the object passed to reset {i = 0; // changes the value of the object to which i refers }

    和其他引用一樣,引用形參綁定初始化它的對(duì)象。當(dāng)調(diào)用這一版本的 reset 函數(shù)時(shí),i綁定我們傳給函數(shù)的int對(duì)象,此時(shí)改變i也就是改變i所引對(duì)象的值。此例中,被改變的對(duì)象是傳入reset的實(shí)參。
    調(diào)用這一版本的reset函數(shù)時(shí),我們直接傳入對(duì)象而無須傳遞對(duì)象的地址:

    int j = 42; reset(j); // j is passed by reference; the value in j is changed cout << "j = " << j << endl; // prints j = 0

    在上述調(diào)用過程中,形參i僅僅是j的又一個(gè)名字。在reset內(nèi)部對(duì)i的使用即是對(duì)j的使用。


    MyNote:傳引用與傳指針相比,傳引用使用時(shí),無需像傳指針那樣要用一個(gè)解引用,這樣簡(jiǎn)潔些。

    // function that takes a pointer and sets the pointed-to value to zero void reset(int *ip) {*ip = 0; // changes the value of the object to which ip pointsip = 0; // changes only the local copy of ip; the argument is unchanged }

    使用引用避免拷貝

    拷貝大的類類型對(duì)象或者容器對(duì)象比較低效,甚至有的類類型(包括IO類型在內(nèi))根本就不支持拷貝操作。當(dāng)某種類型不支持拷貝操作時(shí),函數(shù)只能通過引用形參訪問該類型的對(duì)象

    舉個(gè)例子,我們準(zhǔn)備編寫一個(gè)函數(shù)比較兩個(gè)string 對(duì)象的長(zhǎng)度。因?yàn)閟tring對(duì)象可能會(huì)非常長(zhǎng),所以應(yīng)該盡量避免直接拷貝它們,這時(shí)使用引用形參是比較明智的選擇。又因?yàn)楸容^長(zhǎng)度無須改變string 對(duì)象的內(nèi)容,所以把形參定義成對(duì)常量的引用:

    // compare the length of two strings bool isShorter(const string &s1, const string &s2) {return s1.size() < s2.size(); }

    如將要介紹的,當(dāng)函數(shù)無須修改引用形參的值時(shí)最好使用常量引用。(只讀屬性)

    Best Practices:如果函數(shù)無須改變引用形參的值,最好將其聲明為常量引用。

    使用引用形參返回額外信息

    一個(gè)函數(shù)只能返回一個(gè)值,然而有時(shí)函數(shù)需要同時(shí)返回多個(gè)值,引用形參為我們一次返回多個(gè)結(jié)果提供了有效的途徑

    舉個(gè)例子,我們定義一個(gè)名為find_char的函數(shù),它返回在string對(duì)象中某個(gè)指定字符第一次出現(xiàn)的位置。同時(shí),我們也希望函數(shù)能返回該字符出現(xiàn)的總次數(shù)。

    該如何定義函數(shù)使得它能夠既返回位置也返回出現(xiàn)次數(shù)呢?

  • 一種方法是定義一個(gè)新的數(shù)據(jù)類型,讓它包含位置和數(shù)量?jī)蓚€(gè)成員。
  • 還有另一種更簡(jiǎn)單的方法,我們可以給函數(shù)傳入一個(gè)額外的引用實(shí)參,令其保存字符出現(xiàn)的次數(shù):
  • // returns the index of the first occurrence of c in s // the reference parameter occurs counts how often c occurs string::size_type find_char(const string &s, char c, string::size_type &occurs) {auto ret = s.size(); // position of the first occurrence, if anyoccurs = 0; // set the occurrence count parameterfor (decltype(ret) i = 0; i != s.size(); ++i) {if (s[i] == c) {if (ret == s.size())ret = i; // remember the first occurrence of c++occurs; // increment the occurrence count}}return ret; // count is returned implicitly in occurs }

    當(dāng)我們調(diào)用find_char函數(shù)時(shí),必須傳入三個(gè)實(shí)參:作為查找范圍的一個(gè)string對(duì)象、要找的字符以及一個(gè)用于保存字符出現(xiàn)次數(shù)的size_type對(duì)象。假設(shè)s是一個(gè)string對(duì)象,ctr是一個(gè)size_type對(duì)象,則我們通過如下形式調(diào)用find_char函數(shù):

    auto index = find_char(s, 'o', ctr);

    調(diào)用完成后,如果string對(duì)象中確實(shí)存在o,那么ctr的值就是。出現(xiàn)的次數(shù),index指向o第一次出現(xiàn)的位置;否則如果string對(duì)象中沒有o, index等于 s.size()而ctr等于0。

    const形參和實(shí)參

    當(dāng)形參是const時(shí),必須要注意第2章關(guān)于頂層const的內(nèi)容。如前所述,頂層const作用于對(duì)象本身:

    const int ci = 42; // we cannot change ci; const is top-level int i = ci; // ok: when we copy ci, its top-level const is ignored int * const p = &i; // const is top-level; we can't assign to p,注意,初始化與賦值在C++中是兩碼事 *p = 0; // ok: changes through p are allowed; i is now 0

    和其他初始化過程一樣,當(dāng)用實(shí)參初始化形參時(shí)會(huì)忽略掉頂層const。換句話說,形參的頂層const被忽略掉了。當(dāng)形參有頂層const時(shí),傳給它常量對(duì)象或者非常量對(duì)象都是可以的:

    void fcn(const int i) { /* fcn can read but not write to i */ }

    調(diào)用fcn函數(shù)時(shí),既可以傳入const int也可以傳入int。忽略掉形參的頂層const可能產(chǎn)生意想不到的結(jié)果:

    void fcn(const int i) { /* fcn can read but not write to i */ } void fcn(int i) { /* . . . */ } // error: redefines fcn(int)

    在C++語言中,允許我們定義若干具有相同名字的函數(shù),不過前提是不同函數(shù)的形參列表應(yīng)該有明顯的區(qū)別。因?yàn)轫攲觕onst被忽略掉了,所以在上面的代碼中傳入兩個(gè)fcn函數(shù)的參數(shù)可以完全一樣。因此第二 fcn是錯(cuò)誤的,盡管形式上有差異,但實(shí)際上它的形參和第一個(gè)fcn的形參沒什么不同。

    指針或引用形參與const

    形參的初始化方式和變量的初始化方式是一樣的,所以回顧通用的初始化規(guī)則有助于理解本節(jié)知識(shí)。

    我們可以使用非常量初始化一個(gè)底層const對(duì)象,但是反過來不行;同時(shí)一個(gè)普通的引用必須用同類型的對(duì)象初始化。

    int i = 42; const int *cp = &i; // ok: but cp can't change i const int &r = i; // ok: but r can't change i const int &r2 = 42; // ok://我們可以使用非常量初始化一個(gè)底層const對(duì)象,但是反過來不行; int *p = cp; // error: types of p and cp don't match int &r3 = r; // error: types of r3 and r don't match int &r4 = 42; // error: can't initialize a plain reference from a literal//普通引用變量綁定一個(gè)變量

    將同樣的初始化規(guī)則應(yīng)用到參數(shù)傳遞上可得如下形式:

    // function that takes a reference to an int and sets the given object to zero void reset(int &i) // i is just another name for the object passed to reset {i = 0; // changes the value of the object to which i refers } int i = 0; const int ci = i; string::size_type ctr = 0; reset(&i); // calls the version of reset that has an int* parameter reset(&ci); // error: can't initialize an int* from a pointer to a const int object reset(i); // calls the version of reset that has an int& parameterreset(ci); // error: can't bind a plain reference to the const object ci reset(42); // error: can't bind a plain reference to a literal reset(ctr); // error: types don't match; ctr has an unsigned type// ok: find_char's first parameter is a reference to const find_char("Hello World!", 'o', ctr);

    要想調(diào)用引用版本的reset,只能使用int類型的對(duì)象,而不能使用字面值、求值結(jié)果為int的表達(dá)式、需要轉(zhuǎn)換的對(duì)象或者const int類型的對(duì)象。類似的,要想調(diào)用指針版本的reset只能使用int*。

    另一方面,我們能傳遞一個(gè)字符串字面值作為find_char的第一個(gè)實(shí)參,這是因?yàn)樵摵瘮?shù)的引用形參是常量引用,而C++允許我們用字面值初始化常量引用。

    盡量使用常量引用

    把函數(shù)不會(huì)改變的形參定義成(普通的)引用是一種比較常見的錯(cuò)誤,這么做帶給函數(shù)的調(diào)用者一種誤導(dǎo),即函數(shù)可以修改它的實(shí)參的值。此外,使用引用而非常量引用也會(huì)極大地限制函數(shù)所能接受的實(shí)參類型。就像剛剛看到的,我們不能把const對(duì)象、字面值或者需要類型轉(zhuǎn)換的對(duì)象傳遞給普通的引用形參。

    這種錯(cuò)誤絕不像看起來那么簡(jiǎn)單,它可能造成出人意料的后果。以上文的find_char函數(shù)為例,那個(gè)函數(shù)(正確地)將它的string類型的形參定義成常量引用。假如我們把它定義成普通的string&:

    // bad design: the first parameter should be a const string& string::size_type find_char(string &s, char c,string::size_type &occurs);

    則只能將find_char函數(shù)作用于string對(duì)象。類似下面這樣的調(diào)用

    find_char("Hello World", 'o', ctr);//string &s = "Hello World";不行,const string &s = "Hello World";行

    將在編譯時(shí)發(fā)生錯(cuò)誤。

    還有一個(gè)更難察覺的問題,假如其他函數(shù)(正確地)將它們的形參定義成常量引用,那么第二個(gè)版本的find_char無法在此類函數(shù)中正常使用。舉個(gè)例子,我們希望在一個(gè)判斷string對(duì)象是否是句子的函數(shù)中使用find_char:

    bool is_sentence(const string &s) {// if there's a single period at the end of s, then s is a sentencestring::size_type ctr = 0;return find_char(s, '.', ctr) == s.size() - 1 && ctr == 1; }

    如果find_char的第一個(gè)形參類型是string&,那么上面這條調(diào)用find_char的語句將在編譯時(shí)發(fā)生錯(cuò)誤。原因在于s是常量引用,但find_char被(不正確地)定義成只能接受普通引用。

    解決該問題的一種思路是修改is_sentence的形參類型,但是這么做只不過轉(zhuǎn)移了錯(cuò)誤而已,結(jié)果是is_sentence函數(shù)的調(diào)用者只能接受非常量string對(duì)象了。

    正確的修改思路是改正find_char函數(shù)的形參。如果實(shí)在不能修改find_char,就在is _sentence內(nèi)部定義一個(gè)string類型的變量,令其為s的副本,然后把這個(gè)string對(duì)象傳遞給find_char。

    數(shù)組形參

    數(shù)組(第3章內(nèi)容)的兩個(gè)特殊性質(zhì)對(duì)我們定義和使用作用在數(shù)組上的函數(shù)有影響,這兩個(gè)性質(zhì)分別是:

  • 不允許拷貝數(shù)組
  • 使用數(shù)組時(shí)(通常)會(huì)將其轉(zhuǎn)換成指針。
  • 因?yàn)椴荒芸截悢?shù)組,所以我們無法以值傳遞的方式使用數(shù)組參數(shù)。因?yàn)閿?shù)組會(huì)被轉(zhuǎn)換成指針,所以當(dāng)我們?yōu)楹瘮?shù)傳遞一個(gè)數(shù)組時(shí),實(shí)際上傳遞的是指向數(shù)組首元素的指針。

    盡管不能以值傳遞的方式傳遞數(shù)組,但是我們可以把形參寫成類似數(shù)組的形式:

    // despite appearances, these three declarations of print are equivalent // each function has a single parameter of type const int* void print(const int*); void print(const int[]); // shows the intent that the function takes an array void print(const int[10]); // dimension for documentation purposes (at best)

    盡管表現(xiàn)形式不同,但上面的三個(gè)函數(shù)是等價(jià)的:每個(gè)函數(shù)的唯一形參都是const int *類型的。當(dāng)編譯器處理對(duì)print函數(shù)的調(diào)用時(shí),只檢查傳入的參數(shù)是否是const int *類型:

    int i = 0, j[2] = {0, 1}; print(&i); // ok: &i is int* print(j); // ok: j is converted to an int* that points to j[0]

    如果我們傳給 print 函數(shù)的是一個(gè)數(shù)組,則實(shí)參自動(dòng)地轉(zhuǎn)換成指向數(shù)組首元素的指針,數(shù)組的大小對(duì)函數(shù)的調(diào)用沒有影響。

    WARNING:和其他使用數(shù)組的代碼一樣,以數(shù)組作為形參的函數(shù)也必須確保使用數(shù)組時(shí)不會(huì)越界。

    因?yàn)閿?shù)組是以指針的形式傳遞給函數(shù)的,所以一開始函數(shù)并不知道數(shù)組的確切尺寸,調(diào)用者應(yīng)該為此提供一些額外的信息。管理指針形參有三種常用的技術(shù)。

    使用標(biāo)記指定數(shù)組長(zhǎng)度

    管理數(shù)組實(shí)參的第一種方法是要求數(shù)組本身包含一個(gè)結(jié)束標(biāo)記,使用這種方法的典型示例是C風(fēng)格字符串。C風(fēng)格字符串存儲(chǔ)在字符數(shù)組中,并且在最后一個(gè)字符后面跟著一個(gè)空字符。函數(shù)在處理C風(fēng)格字符串時(shí)遇到空字符停止:

    void print(const char *cp) {if (cp) // if cp is not a null pointerwhile (*cp) // so long as the character it points to is not a null charactercout << *cp++; // print the character and advance the pointer }

    這種方法適用于那些有明顯結(jié)束標(biāo)記且該標(biāo)記不會(huì)與普通數(shù)據(jù)混淆的情況,但是對(duì)于像int這樣所有取值都是合法值的數(shù)據(jù)就不太有效了。

    使用標(biāo)準(zhǔn)庫規(guī)范

    管理數(shù)組實(shí)參的第二種技術(shù)是傳遞指向數(shù)組首元素和尾后元素的指針,這種方法受到了標(biāo)準(zhǔn)庫技術(shù)的啟發(fā),使用該方法,我們可以按照如下形式輸出元素內(nèi)容:

    void print(const int *beg, const int *end) {// print every element starting at beg up to but not including endwhile (beg != end)cout << *beg++ << endl; // print the current element// and advance the pointer }

    while循環(huán)使用解引用運(yùn)算符和后置遞減運(yùn)算符輸出當(dāng)前元素并在數(shù)組內(nèi)將beg向前移動(dòng)一個(gè)元素,當(dāng)beg和end相等時(shí)結(jié)束循環(huán)。

    為了調(diào)用這個(gè)函數(shù),我們需要傳入兩個(gè)指針:一個(gè)指向要輸出的首元素,另一個(gè)指向尾元素的下一位置:

    int j[2] = {0, 1}; // j is converted to a pointer to the first element in j // the second argument is a pointer to one past the end of j print(begin(j), end(j)); // begin and end functions

    只要調(diào)用者能正確地計(jì)算指針?biāo)傅奈恢?#xff0c;那么上述代碼就是安全的。在這里,我們使用標(biāo)準(zhǔn)庫begin和end函數(shù)提供所需的指針。

    顯示傳遞一個(gè)表示數(shù)組大小的形參

    第三種管理數(shù)組實(shí)參的方法是專門定義一個(gè)表示數(shù)組大小的形參,在C程序和過去的C++程序中常常使用這種方法。使用該方法,可以將print函數(shù)重寫成如下形式:

    // const int ia[] is equivalent to const int* ia // size is passed explicitly and used to control access to elements of ia void print(const int ia[], size_t size) {for (size_t i = 0; i != size; ++i) {cout << ia[i] << endl;} }

    這個(gè)版本的程序通過形參size的值確定要輸出多少個(gè)元素,調(diào)用print函數(shù)時(shí)必須傳入這個(gè)表示數(shù)組大小的值:

    int j[] = { 0, 1 }; // int array of size 2 print(j, end(j) - begin(j));

    只要傳遞給函數(shù)的size值不超過數(shù)組實(shí)際的大小,函數(shù)就是安全的。

    數(shù)組形參和const

    我們的三個(gè)print函數(shù)都把數(shù)組形參定義成了指向const的指針,本章關(guān)于引用的討論同樣適用于指針。

    • 當(dāng)函數(shù)不需要對(duì)數(shù)組元素執(zhí)行寫操作的時(shí)候,數(shù)組形參應(yīng)該是指向const的指針。
    • 只有當(dāng)函數(shù)確實(shí)要改變?cè)刂档臅r(shí)候,才把形參定義成指向非常量的指針。

    (MyNote:只讀時(shí)用const。)

    數(shù)組引用形參

    C++語言允許將變量定義成數(shù)組的引用,基于同樣的道理,形參也可以是數(shù)組的引用。此時(shí),引用形參綁定到對(duì)應(yīng)的實(shí)參上,也就是綁定到數(shù)組上:

    // ok: parameter is a reference to an array; the dimension is part of the type void print(int (&arr)[10]) {for (auto elem : arr)cout << elem << endl; }

    Note:&arr兩端的括號(hào)必不可少:

    f(int &arr[10]) // error: declares arr as an array of references f(int (&arr)[10]) // ok: arr is a reference to an array of ten ints

    因?yàn)閿?shù)組的大小是構(gòu)成數(shù)組類型的一部分,所以只要不超過維度,在函數(shù)體內(nèi)就可以放心地使用數(shù)組。但是,這一用法也無形中限制了print函數(shù)的可用性,我們只能將函數(shù)作用于大小為10的數(shù)組:

    int i = 0, j[2] = {0, 1}; int k[10] = {0,1,2,3,4,5,6,7,8,9}; print(&i); // error: argument is not an array of ten ints print(j); // error: argument is not an array of ten ints print(k); // ok: argument is an array of ten ints

    第16章將要介紹我們應(yīng)該如何編寫這個(gè)函數(shù),使其可以給引用類型的形參傳遞任意大小的數(shù)組。

    傳遞多維數(shù)組

    我們?cè)?jīng)介紹過,在C++語言中實(shí)際上沒有真正的多維數(shù)組,所謂多維數(shù)組其實(shí)是數(shù)組的數(shù)組。

    和所有數(shù)組一樣,當(dāng)將多維數(shù)組傳遞給函數(shù)時(shí),真正傳遞的是指向數(shù)組首元素的指針。因?yàn)槲覀兲幚淼氖菙?shù)組的數(shù)組,所以首元素本身就是一個(gè)數(shù)組,指針就是一個(gè)指向數(shù)組的指針。數(shù)組第二維(以及后面所有維度〉的大小都是數(shù)組類型的一部分,不能省略:

    // matrix points to the first element in an array whose elements are arrays of ten ints void print(int (*matrix)[10], int rowSize) { /* . . . */ }

    上述語句將matrix聲明成指向含有10個(gè)整數(shù)的數(shù)組的指針。

    Note:再一次強(qiáng)調(diào),*matrix兩端的括號(hào)必不可少:

    int *matrix[10]; // array of ten pointers int (*matrix)[10]; // pointer to an array of ten ints

    我們也可以使用數(shù)組的語法定義函數(shù),此時(shí)編譯器會(huì)一如既往地忽略掉第一個(gè)維度,所以最好不要把它包括在形參列表內(nèi):

    // equivalent definition void print(int matrix[][10], int rowSize) { /* . . . */ }

    matrix的聲明看起來是一個(gè)二維數(shù)組,實(shí)際上形參是指向含有10個(gè)整數(shù)的數(shù)組的指針。

    main:處理命令行選項(xiàng)

    main函數(shù)是演示C++程序如何向函數(shù)傳遞數(shù)組的好例子。到目前為止,我們定義的main函數(shù)都只有空形參列表:

    int main () { ... }

    然而,有時(shí)我們確實(shí)需要給main傳遞實(shí)參,一種常見的情況是用戶通過設(shè)置一組選項(xiàng)來確定函數(shù)所要執(zhí)行的操作。例如,假定main函數(shù)位于可執(zhí)行文件prog之內(nèi),我們可以向程序傳遞下面的選項(xiàng):

    prog -d -o ofile data0

    這些命令行選項(xiàng)通過兩個(gè)(可選的)形參傳遞給main函數(shù):

    int main (int argc, char *argv[]){ ... }

    第二個(gè)形參argv是一個(gè)數(shù)組,它的元素是指向C風(fēng)格字符串的指針;第一個(gè)形參argc表示數(shù)組中字符串的數(shù)量。

    因?yàn)榈诙€(gè)形參是數(shù)組,所以main函數(shù)也可以定義成:

    int main (int argc, char **argv){ ... }

    其中argv指向char*。

    當(dāng)實(shí)參傳給main函數(shù)之后,argv的第一個(gè)元素指向程序的名字或者一個(gè)空字符串,接下來的元素依次傳遞命令行提供的實(shí)參。最后一個(gè)指針之后的元素值保證為0。

    以上面提供的命令行為例,argc應(yīng)該等于5,argv應(yīng)該包含如下的C風(fēng)格字符串:

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

    WARNING:當(dāng)使用argv中的實(shí)參時(shí),一定要記得可選的實(shí)參從argv[1]開始,argv[0]保存程序的名字,而非用戶輸入。

    含有可變形參的函數(shù)

    有時(shí)我們無法提前預(yù)知應(yīng)該向函數(shù)傳遞幾個(gè)實(shí)參。例如,我們想要編寫代碼輸出程序產(chǎn)生的錯(cuò)誤信息,此時(shí)最好用同一個(gè)函數(shù)實(shí)現(xiàn)該項(xiàng)功能,以便對(duì)所有錯(cuò)誤的處理能夠整齊劃一。然而,錯(cuò)誤信息的種類不同,所以調(diào)用錯(cuò)誤輸出函數(shù)時(shí)傳遞的實(shí)參也各不相同。

    為了編寫能處理不同數(shù)量實(shí)參的函數(shù),C++11新標(biāo)準(zhǔn)提供了兩種主要的方法:

  • 如果所有的實(shí)參類型相同,可以傳遞一個(gè)名為initializer_list的標(biāo)準(zhǔn)庫類型;
  • 如果實(shí)參的類型不同,我們可以編寫一種特殊的函數(shù),也就是所謂的可變參數(shù)模板,關(guān)于它的細(xì)節(jié)將在第16章介紹。
  • C++還有一種特殊的形參類型(即省略符),可以用它傳遞可變數(shù)量的實(shí)參。本節(jié)將簡(jiǎn)要介紹省略符形參,不過需要注意的是,這種功能一般只用于與C函數(shù)交互的接口程序。

    initializer_list形參

    如果函數(shù)的實(shí)參數(shù)量未知但是全部實(shí)參的類型都相同,我們可以使用initializer_list類型的形參。initializer_list是一種標(biāo)準(zhǔn)庫類型,用于表示某種特定類型的值的數(shù)組。initializer_list類型定義在同名的頭文件中,它提供的操作如下表所示。

    ..
    initializer_list lst;默認(rèn)初始化;T類型元素的空列表
    initializer_list lst{a, b, c…};lst的元素?cái)?shù)量和初始值一樣多;lst的元素是對(duì)應(yīng)初始值的副本;列表中的元素是const
    lst2(lst)拷貝或賦值一個(gè)initializer_list對(duì)象不會(huì)拷貝列表中的元素;拷貝后,原始列表和副本共享元素
    lst2 = lst同上條
    lst.size()列表中的元素?cái)?shù)量
    lst.begin()返回指向lst中首元素的指針
    lst.end()返回指向lst中尾元素下一位置的指針

    和vector一樣,initializer_list也是一種模板類型。定義initializer_list對(duì)象時(shí),必須說明列表中所含元素的類型:

    initializer_list<string> ls; // initializer_list of strings initializer_list<int> li; // initializer_list of ints

    和 vector不一樣的是,initializer_list對(duì)象中的元素永遠(yuǎn)是常量值,我們無法改變initializer_list對(duì)象中元素的值

    我們使用如下的形式編寫輸出錯(cuò)誤信息的函數(shù),使其可以作用于可變數(shù)量的實(shí)參:

    void error_msg(initializer_list<string> il) {for (auto beg = il.begin(); beg != il.end(); ++beg)cout << *beg << " " ;cout << endl; }

    作用于initializer_list對(duì)象的begin和end操作類似于vector對(duì)應(yīng)的成員。

    begin()成員提供一個(gè)指向列表首元素的指針,end()成員提供一個(gè)指向列表尾后元素的指針。我們的函數(shù)首先初始化 beg令其表示首元素,然后依次遍歷列表中的每個(gè)元素。在循環(huán)體中,解引用beg 以訪問當(dāng)前元素并輸出它的值。

    如果想向initializer_list形參中傳遞一個(gè)值的序列,則必須把序列放在一對(duì)花括號(hào)內(nèi):

    // expected, actual are strings if (expected != actual)error_msg({"functionX", expected, actual}); elseerror_msg({"functionX", "okay"});

    在上面的代碼中我們調(diào)用了同一個(gè)函數(shù)error_msg,但是兩次調(diào)用傳遞的參數(shù)數(shù)量不同:第一次調(diào)用傳入了三個(gè)值,第二次調(diào)用只傳入了兩個(gè)。

    含有initializer_list形參的函數(shù)也可以同時(shí)擁有其他形參。例如,調(diào)試系統(tǒng)可能有個(gè)名為ErrCode的類用來表示不同類型的錯(cuò)誤,因此我們可以改寫之前的程序,使其包含一個(gè)initializer_list形參和一個(gè)ErrCode形參:

    void error_msg(ErrCode e, initializer_list<string> il) {cout << e.msg() << ": ";for (const auto &elem : il)cout << elem << " " ;cout << endl; }

    因?yàn)閕nitializer_list包含begin和end成員,所以我們可以使用范圍for循環(huán)處理其中的元素。和之前的版本類似,這段程序遍歷傳給il形參的列表值,每次迭代時(shí)訪問一個(gè)元素。

    為了調(diào)用這個(gè)版本的error_msg函數(shù),需要額外傳遞一個(gè)ErrCode實(shí)參:

    if (expected != actual)error_msg(ErrCode(42), {"functionX", expected, actual}); elseerror_msg(ErrCode(0), {"functionX", "okay"});

    省略符形參

    省略符形參是為了便于C++程序訪問某些特殊的C代碼而設(shè)置的,這些代碼使用了名為varargs的C標(biāo)準(zhǔn)庫功能。通常,省略符形參不應(yīng)用于其他目的。你的C編譯器文檔會(huì)描述如何使用varargs。

    WARNING:省略符形參應(yīng)該僅僅用于C和C++通用的類型。特別應(yīng)該注意的是,大多數(shù)類類型的對(duì)象在傳遞給省略符形參時(shí)都無法正確拷貝。

    省略符形參只能出現(xiàn)在形參列表的最后一個(gè)位置,它的形式無外乎以下兩種:

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

    第一種形式指定了foo 函數(shù)的部分形參的類型,對(duì)應(yīng)于這些形參的實(shí)參將會(huì)執(zhí)行正常的類型檢查。省略符形參所對(duì)應(yīng)的實(shí)參無須類型檢查。在第一種形式中,形參聲明后面的逗號(hào)是可選的。

    返回類型和return語句

    return語句終止當(dāng)前正在執(zhí)行的函數(shù)并將控制權(quán)返回到調(diào)用該函數(shù)的地方。return語句有兩種形式:

    return; return expression;

    無返回值函數(shù)

    沒有返回值的return 語句只能用在返回類型是void 的函數(shù)中。返回void的函數(shù)不要求非得有return語句,因?yàn)樵谶@類函數(shù)的最后一句后面會(huì)隱式地執(zhí)行return。

    通常情況下,void函數(shù)如果想在它的中間位置提前退出,可以使用return語句。return的這種用法有點(diǎn)類似于我們用break語句退出循環(huán)。例如,可以編寫一個(gè)swap函數(shù),使其在參與交換的值相等時(shí)什么也不做直接退出:

    void swap(int &v1, int &v2) {// if the values are already the same, no need to swap, just returnif (v1 == v2)return;// if we're here, there's work to doint tmp = v2;v2 = v1;v1 = tmp;// no explicit return necessary }

    這個(gè)函數(shù)首先檢查值是否相等,如果相等直接退出函數(shù),如果不相等才交換它們的值。在最后一條賦值語句后面隱式地執(zhí)行return。

    一個(gè)返回類型是void的函數(shù)也能使用return語句的第二種形式,不過此時(shí)return語句的expression必須是另一個(gè)返回void的函數(shù)。強(qiáng)行令void函數(shù)返回其他類型的表達(dá)式將產(chǎn)生編譯錯(cuò)誤。

    有返回值函數(shù)

    return語句的第二種形式提供了函數(shù)的結(jié)果。只要函數(shù)的返回類型不是 void,則該函數(shù)內(nèi)的每條return語句必須返回一個(gè)值。return語句返回值的類型必須與函數(shù)的返回類型相同,或者能隱式地轉(zhuǎn)換成函數(shù)的返回類型。

    盡管C++無法確保結(jié)果的正確性,但是可以保證每個(gè)return語句的結(jié)果類型正確。也許無法顧及所有情況,但是編譯器仍然盡量確保具有返回值的函數(shù)只能通過一條有效的return語句退出。例如:

    // incorrect return values, this code will not compile bool str_subrange(const string &str1, const string &str2) {// same sizes: return normal equality testif (str1.size() == str2.size())return str1 == str2;// ok: == returns bool// find the size of the smaller string; conditional operatorauto size = (str1.size() < str2.size()) ? str1.size() : str2.size();// look at each element up to the size of the smaller stringfor (decltype(size) i = 0; i != size; ++i) {if (str1[i] != str2[i])return; // error #1: no return value; compiler should detect this error}// error #2: control might flow off the end of the function without a return// the compiler might not detect this error }
  • 第一個(gè)錯(cuò)誤是for循環(huán)內(nèi)的return語句是錯(cuò)誤的,因?yàn)樗鼪]有返回值,編譯器能檢測(cè)到這個(gè)錯(cuò)誤。
  • 第二個(gè)錯(cuò)誤是函數(shù)在for循環(huán)之后沒有提供 return語句。
  • 在上面的程序中,如果一個(gè)string對(duì)象是另一個(gè)的子集,則函數(shù)在執(zhí)行完for循環(huán)后還將繼續(xù)其執(zhí)行過程,顯然應(yīng)該有一條return 語句專門處理這種情況。編譯器也許能檢測(cè)到這個(gè)錯(cuò)誤,也許不能。如果編譯器沒有發(fā)現(xiàn)這個(gè)錯(cuò)誤,則運(yùn)行時(shí)的行為將是未定義的。

    WARNING:在含有return語句的循環(huán)后面應(yīng)該也有一條return語句,如果沒有的話該程序就是錯(cuò)誤的。很多編譯器都無法發(fā)現(xiàn)此類錯(cuò)誤。

    值是如何被返回的

    返回一個(gè)值的方式和初始化一個(gè)變量或形參的方式完全一樣:返回的值用于初始化調(diào)用點(diǎn)的一個(gè)臨時(shí)量,該臨時(shí)量就是函數(shù)調(diào)用的結(jié)果。

    必須注意當(dāng)函數(shù)返回局部變量時(shí)的初始化規(guī)則。例如我們書寫一個(gè)函數(shù),給定計(jì)數(shù)值、單詞和結(jié)束符之后,判斷計(jì)數(shù)值是否大于1。如果是,返回單詞的復(fù)數(shù)形式。如果不是,返回單詞原形:

    //如果ctr的值大于1,返回word的復(fù)數(shù)形式 string make_plural(size_t ctr, const string &word, const string &ending) {return (ctr > 1) ? word + ending : word; }

    該函數(shù)的返回類型是string,意味著返回值將被拷貝到調(diào)用點(diǎn)。因此,該函數(shù)將返回word的副本或者一個(gè)未命名的臨時(shí)string對(duì)象,該對(duì)象的內(nèi)容是word和ending的和。

    同其他引用類型一樣,如果函數(shù)返回引用,則該引用僅是它所引對(duì)象的一個(gè)別名。舉個(gè)例子來說明,假定某函數(shù)挑出兩個(gè)string形參中較短的那個(gè)并返回其引用:

    //挑出兩個(gè)string對(duì)象中較短的那個(gè),返回其引用 const string &shorterstring(const string &sl,const string &s2){return s1.size() <= s2.size() ? s1 : s2 ; }

    其中形參和返回類型都是const string 的引用,不管是調(diào)用函數(shù)還是返回結(jié)果都不會(huì)真正拷貝string對(duì)象。

    不要返回局部對(duì)象的引用或指針

    函數(shù)完成后,它所占用的存儲(chǔ)空間也隨之被釋放掉。因此,函數(shù)終止意味著局部變量的引用將指向不再有效的內(nèi)存區(qū)域:

    // disaster: this function returns a reference to a local object const string &manip() {string ret; // transform ret in some wayif (!ret.empty())return ret; // WRONG: returning a reference to a local object! elsereturn "Empty"; // WRONG: "Empty" is a local temporary string }

    上面的兩條 return語句都將返回未定義的值,也就是說,試圖使用manip函數(shù)的返回值將引發(fā)未定義的行為。

    • 對(duì)于第一條return語句來說,顯然它返回的是局部對(duì)象的引用。
    • 在第二條return語句中,字符串字面值轉(zhuǎn)換成一個(gè)局部臨時(shí)string對(duì)象,對(duì)于manip來說,該對(duì)象和 ret一樣都是局部的。

    當(dāng)函數(shù)結(jié)束時(shí)臨時(shí)對(duì)象占用的空間也就隨之釋放掉了,所以兩條return語句都指向了不再可用的內(nèi)存空間。

    Tip:要想確保返回值安全,我們不妨提問:引用所引的是在函數(shù)之前已經(jīng)存在的哪個(gè)對(duì)象?

    如前所述,返回局部對(duì)象的引用是錯(cuò)誤的;同樣,返回局部對(duì)象的指針也是錯(cuò)誤的。一旦函數(shù)完成,局部對(duì)象被釋放,指針將指向一個(gè)不存在的對(duì)象。

    返回類類型的函數(shù)和調(diào)用運(yùn)算符

    和其他運(yùn)算符一樣,調(diào)用運(yùn)算符也有優(yōu)先級(jí)和結(jié)合律。調(diào)用運(yùn)算符的優(yōu)先級(jí)與點(diǎn)運(yùn)算符和箭頭運(yùn)算符相同,并且也符合左結(jié)合律。因此,如果函數(shù)返回指針、引用或類的對(duì)象,我們就能使用函數(shù)調(diào)用的結(jié)果訪問結(jié)果對(duì)象的成員

    例如,我們可以通過如下形式得到較短string對(duì)象的長(zhǎng)度:

    //調(diào)用string對(duì)象的size成員,該string對(duì)象是由shorterstring函數(shù)返回的 auto sz = shorterString(s1,s2).size();

    因?yàn)樯厦嫣岬降倪\(yùn)算符都滿足左結(jié)合律,所以 shorterString 的結(jié)果是點(diǎn)運(yùn)算符的左側(cè)運(yùn)算對(duì)象,點(diǎn)運(yùn)算符可以得到該string對(duì)象的size成員,size又是第二個(gè)調(diào)用運(yùn)算符的左側(cè)運(yùn)算對(duì)象。

    引用返回左值

    函數(shù)的返回類型決定函數(shù)調(diào)用是否是左值。調(diào)用一個(gè)返回引用的函數(shù)得到左值,其他返回類型得到右值。可以像使用其他左值那樣來使用返回引用的函數(shù)的調(diào)用,特別是,我們能為返回類型是非常量引用的函數(shù)的結(jié)果賦值:

    char &get_val(string &str, string::size_type ix) {return str[ix]; // get_val assumes the given index is valid } int main() {string s("a value");cout << s << endl; // prints a valueget_val(s, 0) = 'A'; // changes s[0] to A這里函數(shù)調(diào)用是左值,雖然有點(diǎn)怪,但是語法正確的cout << s << endl;// prints A valuereturn 0; }

    把函數(shù)調(diào)用放在賦值語句的左側(cè)可能看起來有點(diǎn)奇怪,但其實(shí)這沒什么特別的。返回值是引用,因此調(diào)用是個(gè)左值,和其他左值一樣它也能出現(xiàn)在賦值運(yùn)算符的左側(cè)。

    如果返回類型是常量引用,我們不能給調(diào)用的結(jié)果賦值,這一點(diǎn)和我們熟悉的情況是一樣的:

    shorterString ( "hi" , "bye" ) = "X";//錯(cuò)誤:返回值是個(gè)常量

    列表初始化返回值

    C++11新標(biāo)準(zhǔn)規(guī)定,函數(shù)可以返回花括號(hào)包圍的值的列表。類似于其他返回結(jié)果,此處的列表也用來對(duì)表示函數(shù)返回的臨時(shí)量進(jìn)行初始化。如果列表為空,臨時(shí)量執(zhí)行值初始化,否則,返回的值由函數(shù)的返回類型決定。

    舉個(gè)例子,回憶前文的error_msg函數(shù),該函數(shù)的輸入是一組可變數(shù)量的string 實(shí)參,輸出由這些string對(duì)象組成的錯(cuò)誤信息。在下面的函數(shù)中,我們返回一個(gè)vector對(duì)象,用它存放表示錯(cuò)誤信息的string對(duì)象:

    vector<string> process() {// . . .// expected and actual are stringsif (expected.empty())return {}; // return an empty vector else if (expected == actual)return {"functionX", "okay"}; // return list-initialized vector else return {"functionX", expected, actual}; }

    第一條return語句返回一個(gè)空列表,此時(shí),process 函數(shù)返回的vector對(duì)象是空的。如果expected不為空,根據(jù)expected和actual是否相等,函數(shù)返回的vector對(duì)象分別用兩個(gè)或三個(gè)元素初始化。

    如果函數(shù)返回的是內(nèi)置類型,則花括號(hào)包圍的列表最多包含一個(gè)值,而且該值所占空間不應(yīng)該大于目標(biāo)類型的空間。如果函數(shù)返回的是類類型,由類本身定義初始值如何使用。

    主函數(shù)main的返回值

    之前介紹過,如果函數(shù)的返回類型不是void,那么它必須返回一個(gè)值。

    但是這條規(guī)則有個(gè)例外:我們?cè)试Smain函數(shù)沒有return語句直接結(jié)束。如果控制到達(dá)了main函數(shù)的結(jié)尾處而且沒有return語句,編譯器將隱式地插入一條返回0的return語句。

    第1章介紹的,main函數(shù)的返回值可以看做是狀態(tài)指示器。返回0表示執(zhí)行成功,返回其他值表示執(zhí)行失敗,其中非0值的具體含義依機(jī)器而定。為了使返回值與機(jī)器無關(guān),cstdlib頭文件定義了兩個(gè)預(yù)處理變量,我們可以使用這兩個(gè)變量分別表示成功與失敗:

    int main() {if (some_failure)return EXIT_FAILURE; // defined in cstdlib elsereturn EXIT_SUCCESS; // defined in cstdlib }

    因?yàn)樗鼈兪穷A(yù)處理變量,所以既不能在前面加上std::,也不能在using聲明中出現(xiàn)。

    遞歸

    如果一個(gè)函數(shù)調(diào)用了它自身,不管這種調(diào)用是直接的還是間接的,都稱該函數(shù)為遞歸函數(shù)(recursive function)。舉個(gè)例子,我們可以使用遞歸函數(shù)重新實(shí)現(xiàn)求階乘的功能:

    //計(jì)算val的階乘,即1 * 2* 3 ...* val int factorial (int val) {if (val > 1)return factorial (val-1)* val;return 1; }

    在上面的代碼中,我們遞歸地調(diào)用factorial 函數(shù)以求得從val中減去1后新數(shù)字的階乘。當(dāng)val遞減到1時(shí),遞歸終止,返回1。

    在遞歸函數(shù)中,一定有某條路徑是不包含遞歸調(diào)用的,否則,函數(shù)將“永遠(yuǎn)”遞歸下去,換句話說,函數(shù)將不斷地調(diào)用它自身直到程序棧空間耗盡為止。我們有時(shí)候會(huì)說這種函數(shù)含有遞歸循環(huán)(recursion loop)。在factorial函數(shù)中,遞歸終止的條件是val等于1。

    下面的表格顯示了當(dāng)給factorial函數(shù)傳入?yún)?shù)5時(shí),函數(shù)的執(zhí)行軌跡。

    調(diào)用返回值
    factorial(5)factorial(4) * 5120
    factorial(4)factorial(3) * 424
    factorial(3)factorial(2) * 36
    factorial(2)factorial(1) * 22
    factorial(1)11

    Note:main函數(shù)不能調(diào)用它自己。

    返回?cái)?shù)組指針

    因?yàn)閿?shù)組不能被拷貝,所以函數(shù)不能返回?cái)?shù)組。不過,函數(shù)可以返回?cái)?shù)組的指針或引用。雖然從語法上來說,要想定義一個(gè)返回?cái)?shù)組的指針或引用的函數(shù)比較煩瑣,但是有一些方法可以簡(jiǎn)化這一任務(wù),其中最直接的方法是使用類型別名:

    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ù)組的指針

    其中 arrT是含有10個(gè)整數(shù)的數(shù)組的別名。因?yàn)槲覀儫o法返回?cái)?shù)組,所以將返回類型定義成數(shù)組的指針。因此,func函數(shù)接受一個(gè)int實(shí)參,返回一個(gè)指向包含10個(gè)整數(shù)的數(shù)組的指針。

    聲明一個(gè)返回?cái)?shù)組指針的函數(shù)

    要想在聲明func時(shí)不使用類型別名,我們必須牢記被定義的名字后面數(shù)組的維度:

    int arr[10] ;// arr是一個(gè)含有10個(gè)整數(shù)的數(shù)組 int *p1[10] ;//p1是一個(gè)含有10個(gè)指針的數(shù)組 int (*p2)[10] = &arr;// p2是一個(gè)指針,它指向含有10個(gè)整數(shù)的數(shù)組

    和這些聲明一樣,如果我們想定義一個(gè)返回?cái)?shù)組指針的函數(shù),則數(shù)組的維度必須跟在函數(shù)名字之后。然而,函數(shù)的形參列表也跟在函數(shù)名字后面且形參列表應(yīng)該先于數(shù)組的維度。因此,返回?cái)?shù)組指針的函數(shù)形式如下所示:

    Type (*function(parameter_list))[dimension]

    類似于其他數(shù)組的聲明,Type表示元素的類型,dimension表示數(shù)組的大小。(*function(parameter_list))兩端的括號(hào)必須存在,就像我們定義p2時(shí)兩端必須有括號(hào)一樣。如果沒有這對(duì)括號(hào),函數(shù)的返回類型將是指針的數(shù)組。

    舉個(gè)具體點(diǎn)的例子,下面這個(gè)func函數(shù)的聲明沒有使用類型別名:

    int (*func(int i))[10];

    可以按照以下的順序來逐層理解該聲明的含義:

    • func(int i)表示調(diào)用func函數(shù)時(shí)需要一個(gè)int類型的實(shí)參。

    • (*func(int i))意味著我們可以對(duì)函數(shù)調(diào)用的結(jié)果執(zhí)行解引用操作。(*不是指針聲明符)

    • (*func(int i))[10]表示解引用func的調(diào)用將得到一個(gè)大小是10的數(shù)組。

    • int (*func (int i))[10]表示數(shù)組中的元素是int類型。

    使用尾置返回類型

    在C++11新標(biāo)準(zhǔn)中還有一種可以簡(jiǎn)化上述func聲明的方法,就是使用尾置返回類型( trailing return type)。任何函數(shù)的定義都能使用尾置返回,但是這種形式對(duì)于返回類型比較復(fù)雜的函數(shù)最有效,比如返回類型是數(shù)組的指針或者數(shù)組的引用。尾置返回類型跟在形參列表后面并以一個(gè)->符號(hào)開頭。為了表示函數(shù)真正的返回類型跟在形參列表之后,我們?cè)诒緫?yīng)該出現(xiàn)返回類型的地方放置一個(gè)auto:

    //func接受一個(gè)int類型的實(shí)參,返回一個(gè)指針,該指針指向含有10個(gè)整數(shù)的數(shù)組 auto func(int i) -> int (*)[10];

    因?yàn)槲覀儼押瘮?shù)的返回類型放在了形參列表之后,所以可以清楚地看到func函數(shù)返回的是一個(gè)指針,并且該指針指向了含有10個(gè)整數(shù)的數(shù)組。

    使用decltype

    還有一種情況,如果我們知道函數(shù)返回的指針將指向哪個(gè)數(shù)組,就可以使用decltype關(guān)鍵字聲明返回類型。例如,下面的函數(shù)返回一個(gè)指針,該指針根據(jù)參數(shù)i的不同指向兩個(gè)已知數(shù)組中的某一個(gè):

    int odd[] = {1,3,5,7,9}; int even[] = {0,2,4,6,8}; //返回一個(gè)指針,該指針指向含有5個(gè)整數(shù)的數(shù)組 decltype(odd) *arrPtr(int i){return (i % 2) ? &odd : &even;//返回一個(gè)指向數(shù)組的指針 }

    arrPtr使用關(guān)鍵字 decltype表示它的返回類型是個(gè)指針,并且該指針?biāo)傅膶?duì)象與odd 的類型一致。因?yàn)?odd是數(shù)組,所以arrPtr返回一個(gè)指向含有5個(gè)整數(shù)的數(shù)組的指針。

    有一個(gè)地方需要注意:decltype并不負(fù)責(zé)把數(shù)組類型轉(zhuǎn)換成對(duì)應(yīng)的指針,所以decltype的結(jié)果是個(gè)數(shù)組,要想表示arrPtr返回指針還必須在函數(shù)聲明時(shí)加一個(gè)*符號(hào)。

    函數(shù)重載

    如果同一作用域內(nèi)的幾個(gè)函數(shù)名字相同但形參列表不同,我們稱之為重載(overloaded)函數(shù)。例如,上文中我們定義了幾個(gè)名為print的函數(shù):

    void print(const char *cp); void print(const int *beg, const int *end); void print(const int ia[], size_t size);

    這些函數(shù)接受的形參類型不一樣,但是執(zhí)行的操作非常類似。當(dāng)調(diào)用這些函數(shù)時(shí),編譯器會(huì)根據(jù)傳遞的實(shí)參類型推斷想要的是哪個(gè)函數(shù):

    int j[2] = {0,1}; print("Hello World"); // calls print(const char*) print(j, end(j) - begin(j)); // calls print(const int*, size_t) print(begin(j), end(j)); // calls print(const int*, const int*)

    函數(shù)的名字僅僅是讓編譯器知道它調(diào)用的是哪個(gè)函數(shù),而函數(shù)重載可以在一定程度上減輕程序員起名字、記名字的負(fù)擔(dān)。

    Note:main函數(shù)不能重載。

    定義重載函數(shù)

    有一種典型的數(shù)據(jù)庫應(yīng)用,需要?jiǎng)?chuàng)建幾個(gè)不同的函數(shù)分別根據(jù)名字、電話、賬戶號(hào)碼等信息查找記錄。函數(shù)重載使得我們可以定義一組函數(shù),它們的名字都是lookup,但是查找的依據(jù)不同。我們能通過以下形式中的任意一種調(diào)用lookup函數(shù):

    Record lookup(const Account&); // find by Account Record lookup(const Phone&); // find by Phone Record lookup(const Name&); // find by Name Account acct; Phone phone; Record r1 = lookup(acct); // call version that takes an Account Record r2 = lookup(phone); // call version that takes a Phone

    其中,雖然我們定義的三個(gè)函數(shù)各不相同,但它們都有同一個(gè)名字。編譯器根據(jù)實(shí)參的類型確定應(yīng)該調(diào)用哪一個(gè)函數(shù)。

    對(duì)于重載的函數(shù)來說,它們應(yīng)該在形參數(shù)量或形參類型上有所不同。在上面的代碼中,雖然每個(gè)函數(shù)都只接受一個(gè)參數(shù),但是參數(shù)的類型不同。

    不允許兩個(gè)函數(shù)除了返回類型外其他所有的要素都相同。假設(shè)有兩個(gè)函數(shù),它們的形參列表一樣但是返回類型不同,則第二個(gè)函數(shù)的聲明是錯(cuò)誤的:

    Record lookup(const Account&); bool lookup(const Account&); // error: only the return type is different

    判斷兩個(gè)形參的類型是否相異

    有時(shí)候兩個(gè)形參列表看起來不一樣,但實(shí)際上是相同的:(似非而是)

    // each pair declares the same function Record lookup(const Account &acct); Record lookup(const Account&); // parameter names are ignoredtypedef Phone Telno; Record lookup(const Phone&); Record lookup(const Telno&); // Telno and Phone are the same type
  • 第一對(duì)聲明中,第一個(gè)函數(shù)給它的形參起了名字,第二個(gè)函數(shù)沒有。形參的名字僅僅起到幫助記憶的作用,有沒有它并不影響形參列表的內(nèi)容。

  • 第二對(duì)聲明看起來類型不同,但事實(shí)上Telno不是一種新類型,它只是 Phone的別名而已。類型別名為已存在的類型提供另外一個(gè)名字,它并不是創(chuàng)建新類型。因此,第二對(duì)中兩個(gè)形參的區(qū)別僅在于一個(gè)使用類型原來的名字,另一個(gè)使用它的別名,從本質(zhì)上來說它們沒什么不同。

  • 重載和const形參

    前文介紹,頂層const(第2章)不影響傳入函數(shù)的對(duì)象。一個(gè)擁有頂層const的形參無法和另一個(gè)沒有頂層const的形參區(qū)分開來:

    Record lookup(Phone); Record lookup(const Phone); // redeclares Record lookup(Phone) Record lookup(Phone*); Record lookup(Phone* const); // redeclares Record lookup(Phone*)

    在這兩組函數(shù)聲明中,每一組的第二個(gè)聲明和第-一個(gè)聲明是等價(jià)的。

    另一方面,如果形參是某種類型的指針或引用,則通過區(qū)分其指向的是常量對(duì)象還是非常量對(duì)象可以實(shí)現(xiàn)函數(shù)重載,此時(shí)的const是底層的

    // functions taking const and nonconst references or pointers have different parameters // declarations for four independent, overloaded functions Record lookup(Account&); // function that takes a reference to Account Record lookup(const Account&); // new function that takes a const reference Record lookup(Account*); // new function, takes a pointer to Account Record lookup(const Account*); // new function, takes a pointer to const

    在上面的例子中,編譯器可以通過實(shí)參是否是常量來推斷應(yīng)該調(diào)用哪個(gè)函數(shù)。

    因?yàn)閏onst不能轉(zhuǎn)換成其他類型(第4章“其他隱式類型轉(zhuǎn)換”節(jié)內(nèi)容),所以我們只能把const對(duì)象(或指向const的指針)傳遞給const形參。

    相反的,因?yàn)榉浅A靠梢赞D(zhuǎn)換成const,所以上面的4個(gè)函數(shù)都能作用于非常量對(duì)象或者指向非常量對(duì)象的指針。不過,接下來將要介紹的,當(dāng)我們傳遞一個(gè)非常量對(duì)象或者指向非常量對(duì)象的指針時(shí),編譯器會(huì)優(yōu)先選用非常量版本的函數(shù)。

    建議:何時(shí)不應(yīng)該重載函數(shù)

    盡管函數(shù)重載能在一定程度上減輕我們?yōu)楹瘮?shù)起名字、記名字的負(fù)擔(dān),但是最好只重載那些確實(shí)非常相似的操作。有些情況下,給函數(shù)起不同的名字能使得程序更易理解。舉個(gè)例子,下面是幾個(gè)負(fù)責(zé)移動(dòng)屏幕光標(biāo)的函數(shù):

    Screen& moveHome(); screen& moveAbs(int, int); Screen& moveRel(int, int, string direction);

    乍看上去,似平可以把這組函數(shù)統(tǒng)一命名為move,從而實(shí)現(xiàn)函數(shù)的重載:

    Screen& move(); Screen& move(int, int); Screen& move(int, int, string direction);

    其實(shí)不然,重載之后這些函數(shù)失去了名字中本來擁有的信息。盡管這些函數(shù)確實(shí)都是在移動(dòng)光標(biāo),但是具體移動(dòng)的方式卻各不相同。以moveHome為例,它表示的是移動(dòng)光標(biāo)的一種特殊實(shí)例。

    一般來說,是否重載函數(shù)要看哪個(gè)更容易理解:

    //哪種形式更容易理解呢? myscreen.moveHome();//我們認(rèn)為應(yīng)該是這一個(gè)! myscreen.move();

    const_cast和重載

    const_cast為第4章內(nèi)容。

    const_cast只能改變運(yùn)算對(duì)象的底層const。

    const char *pc; char *p = const_cast<char*>(pc);// 正確:但是通過p 寫值是未定義的行為

    對(duì)于將常量對(duì)象轉(zhuǎn)換成非常量對(duì)象的行為,我們一般稱其為“去掉const性質(zhì)(cast away the const)”。一旦我們?nèi)サ袅四硞€(gè)對(duì)象的const性質(zhì),編譯器就不再阻止我們對(duì)該對(duì)象進(jìn)行寫操作了。

    回憶上文的shorterString函數(shù):

    // return a reference to the shorter of two strings const string &shorterString(const string &s1, const string &s2) {return s1.size() <= s2.size() ? s1 : s2; }

    這個(gè)函數(shù)的參數(shù)和返回類型都是 const string 的引用。我們可以對(duì)兩個(gè)非常量的string實(shí)參調(diào)用這個(gè)函數(shù),但返回的結(jié)果仍然是const string 的引用。

    因此我們需要一種新的 shorterString函數(shù),當(dāng)它的實(shí)參不是常量時(shí),得到的結(jié)果是一個(gè)普通的引用,使用const_cast可以做到這一點(diǎn):

    string &shorterString(string &s1, string &s2) {auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));return const_cast<string&>(r); }

    在這個(gè)版本的函數(shù)中,首先將它的實(shí)參強(qiáng)制轉(zhuǎn)換成對(duì)const 的引用,然后調(diào)用了shorterString函數(shù)的const版本。const版本返回對(duì)const string的引用,這個(gè)引用事實(shí)上綁定在了某個(gè)初始的非常量實(shí)參上。因此,我們可以再將其轉(zhuǎn)換回一個(gè)普通的string&,這顯然是安全的。

    (MyNote:因?yàn)閏onst不能轉(zhuǎn)換成其他類型(第4章“其他隱式類型轉(zhuǎn)換”節(jié)內(nèi)容),如果想修改函數(shù)返回的const字符串對(duì)象,顯然是不行的。)

    調(diào)用重載的函數(shù)

    定義了一組重載函數(shù)后,我們需要以合理的實(shí)參調(diào)用它們。函數(shù)匹配( function matching)是指一個(gè)過程,在這個(gè)過程中我們把函數(shù)調(diào)用與一組重載函數(shù)中的某一個(gè)關(guān)聯(lián)起來,函數(shù)匹配也叫做重載確定(overload resolution)。編譯器首先將調(diào)用的實(shí)參與重載集合中每一個(gè)函數(shù)的形參進(jìn)行比較,然后根據(jù)比較的結(jié)果決定到底調(diào)用哪個(gè)函數(shù)。

    在很多(可能是大多數(shù))情況下,程序員很容易判斷某次調(diào)用是否合法,以及當(dāng)調(diào)用合法時(shí)應(yīng)該調(diào)用哪個(gè)函數(shù)。通常,重載集中的函數(shù)區(qū)別明顯,它們要不然是參數(shù)的數(shù)量不同,要不就是參數(shù)類型毫無關(guān)系。

    此時(shí),確定調(diào)用哪個(gè)函數(shù)比較容易。但是在另外一些情況下要想選擇函數(shù)就比較困難了,比如當(dāng)兩個(gè)重載函數(shù)參數(shù)數(shù)量相同且參數(shù)類型可以相互轉(zhuǎn)換時(shí)(第4章“類型轉(zhuǎn)換”)。我們將在本章“函數(shù)匹配”節(jié)介紹當(dāng)函數(shù)調(diào)用存在類型轉(zhuǎn)換時(shí)編譯器處理的方法。

    現(xiàn)在我們需要掌握的是,當(dāng)調(diào)用重載函數(shù)時(shí)有三種可能的結(jié)果:

    • 編譯器找到一個(gè)與實(shí)參最佳匹配(best match)的函數(shù),并生成調(diào)用該函數(shù)的代碼。
    • 找不到任何一個(gè)函數(shù)與調(diào)用的實(shí)參匹配,此時(shí)編譯器發(fā)出無匹配(no match)的錯(cuò)誤信息。
    • 有多于一個(gè)函數(shù)可以匹配,但是每一個(gè)都不是明顯的最佳選擇。此時(shí)也將發(fā)生錯(cuò)誤,稱為二義性調(diào)用(ambiguous call)。

    重載與作用域

    WARNING:一般來說,將函數(shù)聲明置于局部作用域內(nèi)不是一個(gè)明智的選擇。但是為了說明作用域和重載的相互關(guān)系,我們將暫時(shí)違反這一原則而使用局部函數(shù)聲明。

    對(duì)于剛接觸C++的程序員來說,不太容易理清作用域和重載的關(guān)系。其實(shí),重載對(duì)作用域的一般性質(zhì)并沒有什么改變:如果我們?cè)趦?nèi)層作用域中聲明名字,它將隱藏外層作用域中聲明的同名實(shí)體。在不同的作用域中無法重載函數(shù)名

    string read(); void print(const string &); void print(double); // overloads the print function void fooBar(int ival) {bool read = false; // new scope: hides the outer declaration of readstring s = read(); // error: read is a bool variable, not a function// bad practice: usually it's a bad idea to declare functions at local scopevoid print(int); // new scope: hides previous instances of printprint("Value: "); // error: print(const string &) is hiddenprint(ival); // ok: print(int) is visibleprint(3.14); // ok: calls print(int); print(double) is hidden }

    大多數(shù)讀者都能理解調(diào)用read函數(shù)會(huì)引發(fā)錯(cuò)誤。因?yàn)楫?dāng)編譯器處理調(diào)用read的請(qǐng)求時(shí),找到的是定義在局部作用域中的read。這個(gè)名字是個(gè)布爾變量,而我們顯然無法調(diào)用一個(gè)布爾值,因此該語句非法。

    調(diào)用print函數(shù)的過程非常相似。在fooBar內(nèi)聲明的print(int)隱藏了之前兩個(gè)print函數(shù),因此只有一個(gè)print函數(shù)是可用的:該函數(shù)以int值作為參數(shù)。

    當(dāng)我們調(diào)用print函數(shù)時(shí),編譯器首先尋找對(duì)該函數(shù)名的聲明,找到的是接受 int值的那個(gè)局部聲明。一旦在當(dāng)前作用域中找到了所需的名字,編譯器就會(huì)忽略掉外層作用域中的同名實(shí)體。剩下的工作就是檢查函數(shù)調(diào)用是否有效了。

    Note:在C++語言中,名字查找發(fā)生在類型檢查之前。

    第一個(gè)調(diào)用傳入一個(gè)字符串字面值,但是當(dāng)前作用域內(nèi)print 函數(shù)唯一的聲明要求參數(shù)是int類型。字符串字面值無法轉(zhuǎn)換成int類型,所以這個(gè)調(diào)用是錯(cuò)誤的。在外層作用域中的print (const string&)函數(shù)雖然與本次調(diào)用匹配,但是它已經(jīng)被隱藏掉了,根本不會(huì)被考慮

    當(dāng)我們?yōu)閜rint函數(shù)傳入一個(gè)double類型的值時(shí),重復(fù)上述過程。編譯器在當(dāng)前作用域內(nèi)發(fā)現(xiàn)了print(int)函數(shù),double類型的實(shí)參轉(zhuǎn)換成int類型,因此調(diào)用是合法的。

    假設(shè)我們把print(int)和其他print函數(shù)聲明放在同一個(gè)作用域中,則它將成為另一種重載形式。此時(shí),因?yàn)榫幾g器能看到所有三個(gè)函數(shù),上述調(diào)用的處理結(jié)果將完全不同:

    void print(const string &); void print(double); // overloads the print function void print(int); // another overloaded instance void fooBar2(int ival) {print("Value: "); // calls print(const string &)print(ival); // calls print(int)print(3.14); // calls print(double) }

    特殊用途語言特性

    默認(rèn)實(shí)參

    某些函數(shù)有這樣一種形參,在函數(shù)的很多次調(diào)用中它們都被賦予一個(gè)相同的值,此時(shí),我們把這個(gè)反復(fù)出現(xiàn)的值稱為函數(shù)的默認(rèn)實(shí)參(default argument)。調(diào)用含有默認(rèn)實(shí)參的函數(shù)時(shí),可以包含該實(shí)參,也可以省略該實(shí)參。

    例如,我們使用string對(duì)象表示窗口的內(nèi)容。一般情況下,我們希望該窗口的高、寬和背景字符都使用默認(rèn)值。但是同時(shí)我們也應(yīng)該允許用戶為這幾個(gè)參數(shù)自由指定與默認(rèn)值不同的數(shù)值。為了使得窗口函數(shù)既能接納默認(rèn)值,也能接受用戶指定的值,我們把它定義成如下的形式:

    typedef string::size_type sz; string screen(sz ht = 24, sz wid = 80, char backgrnd = '');

    其中我們?yōu)槊恳粋€(gè)形參都提供了默認(rèn)實(shí)參,默認(rèn)實(shí)參作為形參的初始值出現(xiàn)在形參列表中。我們可以為一個(gè)或多個(gè)形參定義默認(rèn)值,不過需要注意的是,一旦某個(gè)形參被賦予了默認(rèn)值,它后面的所有形參都必須有默認(rèn)值。

    使用默認(rèn)實(shí)參調(diào)用函數(shù)

    如果我們想使用默認(rèn)實(shí)參,只要在調(diào)用函數(shù)的時(shí)候省略該實(shí)參就可以了。例如,screen 函數(shù)為它的所有形參都提供了默認(rèn)實(shí)參,所以我們可以使用0、1、2或3個(gè)實(shí)參調(diào)用該函數(shù):

    string window; window = screen(); // equivalent to screen(24,80,' ') window = screen(66);// equivalent to screen(66,80,' ') window = screen(66, 256); // screen(66,256,' ') window = screen(66, 256, '#'); // screen(66,256,'#')

    函數(shù)調(diào)用時(shí)實(shí)參按其位置解析,默認(rèn)實(shí)參負(fù)責(zé)填補(bǔ)函數(shù)調(diào)用缺少的尾部實(shí)參(靠右側(cè)位置)。例如,要想覆蓋backgrnd的默認(rèn)值,必須為ht和wid提供實(shí)參:

    window = screen(, , '?'); // error: can omit only trailing arguments window = screen('?'); // calls screen('?',80,' ')

    需要注意,第二個(gè)調(diào)用傳遞一個(gè)字符值,是合法的調(diào)用。然而盡管如此,它的實(shí)際效果卻與書寫的意圖不符。

    該調(diào)用之所以合法是因?yàn)椤?‘是個(gè)char,而函數(shù)最左側(cè)形參的類型string::size_type是一種無符號(hào)整數(shù)類型,所以char類型可以轉(zhuǎn)換成函數(shù)最左側(cè)形參的類型。當(dāng)該調(diào)用發(fā)生時(shí),char類型的實(shí)參隱式地轉(zhuǎn)換成string::size_type,然后作為height的值傳遞給函數(shù)。在我們的機(jī)器上,’?'對(duì)應(yīng)的十六進(jìn)制數(shù)是0x3F,也就是十進(jìn)制數(shù)的63,所以該調(diào)用把值63傳給了形參height。

    當(dāng)設(shè)計(jì)含有默認(rèn)實(shí)參的函數(shù)時(shí),其中一項(xiàng)任務(wù)是合理設(shè)置形參的順序,盡量讓不怎么使用默認(rèn)值的形參出現(xiàn)在前面,而讓那些經(jīng)常使用默認(rèn)值的形參出現(xiàn)在后面

    默認(rèn)實(shí)參聲明

    對(duì)于函數(shù)的聲明來說,通常的習(xí)慣是將其放在頭文件中,并且一個(gè)函數(shù)只聲明一次,但是多次聲明同一個(gè)函數(shù)也是合法的。不過有一點(diǎn)需要注意,在給定的作用域中一個(gè)形參只能被賦予一次默認(rèn)實(shí)參。換句話說,函數(shù)的后續(xù)聲明只能為之前那些沒有默認(rèn)值的形參添加默認(rèn)實(shí)參,而且該形參右側(cè)的所有形參必須都有默認(rèn)值。假如給定

    //表示高度和寬度的形參沒有默認(rèn)值 string screen(sz, sz, char = ' ');

    我們不能修改一個(gè)已經(jīng)存在的默認(rèn)值:

    string screen(sz, sz, char = '*"); //錯(cuò)誤:重復(fù)聲明

    但是可以按照如下形式添加默認(rèn)實(shí)參:

    string screen(sz = 24, sz = 80, char); //正確:添加默認(rèn)實(shí)參

    Best Practices:通常,應(yīng)該在函數(shù)聲明中指定默認(rèn)實(shí)參,并將該聲明放在合適的頭文件中。

    默認(rèn)實(shí)參初始值

    局部變量不能作為默認(rèn)實(shí)參。除此之外,只要表達(dá)式的類型能轉(zhuǎn)換成形參所需的類型,該表達(dá)式就能作為默認(rèn)實(shí)參:

    // the declarations of wd, def, and ht must appear outside a function sz wd = 80; char def = ' '; sz ht(); string screen(sz = ht(), sz = wd, char = def); string window = screen(); // calls screen(ht(), 80, ' ')

    用作默認(rèn)實(shí)參的名字在函數(shù)聲明所在的作用域內(nèi)解析,而這些名字的求值過程發(fā)生在函數(shù)調(diào)用時(shí):

    void f2() {def = '*'; // changes the value of a default argumentsz wd = 100; // hides the outer definition of wd but does not change the default//這里wd是局部變量,上面的wd是全部變量。window = screen(); // calls screen(ht(), 80, '*') }

    我們?cè)诤瘮?shù)f2內(nèi)部改變了def 的值,所以對(duì)screen的調(diào)用將會(huì)傳遞這個(gè)更新過的值。另一方面,雖然我們的函數(shù)還聲明了一個(gè)局部變量用于隱藏外層的 wd,但是該局部變量與傳遞給screen的默認(rèn)實(shí)參沒有任何關(guān)系。

    內(nèi)聯(lián)函數(shù)和constexpr函數(shù)

    上文我們編寫了一個(gè)小函數(shù)shorterString,它的功能是比較兩個(gè)string 形參的長(zhǎng)度并返回長(zhǎng)度較小的string的引用。把這種規(guī)模較小的操作定義成函數(shù)有很多好處,主要包括:

    • 閱讀和理解shorterString函數(shù)的調(diào)用要比讀懂等價(jià)的條件表達(dá)式容易得多。
    • 使用函數(shù)可以確保行為的統(tǒng)一,每次相關(guān)操作都能保證按照同樣的方式進(jìn)行。
    • 如果我們需要修改計(jì)算過程,顯然修改函數(shù)要比先找到等價(jià)表達(dá)式所有出現(xiàn)的地方再逐一修改更容易。
    • 函數(shù)可以被其他應(yīng)用重復(fù)利用,省去了程序員重新編寫的代價(jià)。

    然而,使用shorterstring 函數(shù)也存在一個(gè)潛在的缺點(diǎn):調(diào)用函數(shù)一般比求等價(jià)表達(dá)式的值要慢一些。

    在大多數(shù)機(jī)器上,一次函數(shù)調(diào)用其實(shí)包含著一系列工作:

    • 調(diào)用前要先保存寄存器,并在返回時(shí)恢復(fù);
    • 可能需要拷貝實(shí)參;
    • 程序轉(zhuǎn)向一個(gè)新的位置繼續(xù)執(zhí)行。

    內(nèi)聯(lián)函數(shù)可避免函數(shù)調(diào)用的開銷

    將函數(shù)指定為內(nèi)聯(lián)函數(shù)(inline),通常就是將它在每個(gè)調(diào)用點(diǎn)上“內(nèi)聯(lián)地”展開。

    假設(shè)我們把shorterString函數(shù)定義成內(nèi)聯(lián)函數(shù),則如下調(diào)用

    cout<< shorterstring (s1, s2) <<endl;

    將在編譯過程中展開成類似于下面的形式

    cout << (s1.size() < s2.size() ? s1 : s2) << endl;

    從而消除了shorterString函數(shù)的運(yùn)行時(shí)開銷。

    在shorterString函數(shù)的返回類型前面加上關(guān)鍵字inline,這樣就可以將它聲明成內(nèi)聯(lián)函數(shù)了:

    //內(nèi)聯(lián)版本:尋找兩個(gè)string對(duì)象中較短的那個(gè) inline const string &shorterstring(const string &s1,const string &s2){return s1.size() <= s2.size() ? s1 : s2; }

    Note:內(nèi)聯(lián)說明只是向編譯器發(fā)出的一個(gè)請(qǐng)求,編譯器可以選擇忽略這個(gè)請(qǐng)求。

    一般來說,內(nèi)聯(lián)機(jī)制用于優(yōu)化規(guī)模較小、流程直接、頻繁調(diào)用的函數(shù)。很多編譯器都不支持內(nèi)聯(lián)遞歸函數(shù),而且一個(gè)75行的函數(shù)也不大可能在調(diào)用點(diǎn)內(nèi)聯(lián)地展開。

    constexpr函數(shù)

    constexpr函數(shù)(constexpr function)是指能用于常量表達(dá)式(第2章內(nèi)容)的函數(shù)。定義 constexpr函數(shù)的方法與其他函數(shù)類似,不過要遵循幾項(xiàng)約定:函數(shù)的返回類型及所有形參的類型都得是字面值類型(第2章內(nèi)容),而且函數(shù)體中必須有且只有一條return語句:

    constexpr int new_sz() { return 42;} constexpr int foo = new_sz();//正確: foo是一個(gè)常量表達(dá)式

    我們把new_sz定義成無參數(shù)的constexpr函數(shù)。因?yàn)榫幾g器能在程序編譯時(shí)驗(yàn)證new_sz函數(shù)返回的是常量表達(dá)式,所以可以用new_sz函數(shù)初始化constexpr類型的變量foo。

    執(zhí)行該初始化任務(wù)時(shí),編譯器把對(duì)constexpr函數(shù)的調(diào)用替換成其結(jié)果值。為了能在編譯過程中隨時(shí)展開,constexpr函數(shù)被隱式地指定為內(nèi)聯(lián)函數(shù)

    constexpr函數(shù)體內(nèi)也可以包含其他語句,只要這些語句在運(yùn)行時(shí)不執(zhí)行任何操作就行。例如,constexpr函數(shù)中可以有空語句、類型別名以及using聲明。

    我們?cè)试Sconstexpr函數(shù)的返回值并非一個(gè)常量:

    //如果arg是常量表達(dá)式,則scale(arg)也是常量表達(dá)式 constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }

    當(dāng)scale的實(shí)參是常量表達(dá)式時(shí),它的返回值也是常量表達(dá)式;反之則不然:

    int arr[scale(2)]; //正確: scale(2)是常量表達(dá)式 int i = 2;//i不是常量表達(dá)式 ,const int i = 2;才是 int a2[scale(i)] ;//錯(cuò)誤:scale (i)不是常量表達(dá)式

    如上例所示,當(dāng)我們給scale函數(shù)傳入一個(gè)形如字面值2的常量表達(dá)式時(shí),它的返回類型也是常量表達(dá)式。此時(shí),編譯器用相應(yīng)的結(jié)果值替換對(duì)scale函數(shù)的調(diào)用。

    如果我們用一個(gè)非常量表達(dá)式調(diào)用scale函數(shù),比如int類型的對(duì)象i,則返回值是一個(gè)非常量表達(dá)式。當(dāng)把 scale函數(shù)用在需要常量表達(dá)式的上下文中時(shí),由編譯器負(fù)責(zé)檢查函數(shù)的結(jié)果是否符合要求。如果結(jié)果恰好不是常量表達(dá)式,編譯器將發(fā)出錯(cuò)誤信息。

    Note:constexpr函數(shù)不一定返回常量表達(dá)式。

    把內(nèi)聯(lián)函數(shù)和constexpr函數(shù)放在頭文件內(nèi)

    和其他函數(shù)不一樣,內(nèi)聯(lián)函數(shù)和 constexpr函數(shù)可以在程序中多次定義。畢竟,編譯器要想展開函數(shù)僅有函數(shù)聲明是不夠的,還需要函數(shù)的定義。不過,對(duì)于某個(gè)給定的內(nèi)聯(lián)函數(shù)或者constexpr函數(shù)來說,它的多個(gè)定義必須完全一致。基于這個(gè)原因,內(nèi)聯(lián)函數(shù)和constexpr函數(shù)通常定義在頭文件中。

    調(diào)試幫助

    C++程序員有時(shí)會(huì)用到一種類似于頭文件保護(hù)(第2章有相關(guān)論述)的技術(shù),以便有選擇地執(zhí)行調(diào)試代碼。基本思想是,程序可以包含一些用于調(diào)試的代碼,但是這些代碼只在開發(fā)程序時(shí)使用。當(dāng)應(yīng)用程序編寫完成準(zhǔn)備發(fā)布時(shí),要先屏蔽掉調(diào)試代碼。這種方法用到兩項(xiàng)預(yù)處理功能:assert和 NDEBUG。

    assert預(yù)處理宏

    assert是一種預(yù)處理宏(preprocessor marco)。所謂預(yù)處理宏其實(shí)是一個(gè)預(yù)處理變量,它的行為有點(diǎn)類似于內(nèi)聯(lián)函數(shù)。assert宏使用一個(gè)表達(dá)式作為它的條件:

    assert (expr) ;

    首先對(duì)expr求值,如果表達(dá)式為假(即 0),assert輸出信息并終止程序的執(zhí)行。如果表達(dá)式為真(即非0),assert什么也不做。

    assert宏定義在cassert頭文件中。如我們所知,預(yù)處理名字由預(yù)處理器而非編譯器管理,因此我們可以直接使用預(yù)處理名字而無須提供using聲明。也就是說,我們應(yīng)該使用assert而不是std::assert,也不需要為assert提供using聲明。

    和預(yù)處理變量一樣,宏名字在程序內(nèi)必須唯一。含有cassert頭文件的程序不能再定義名為assert 的變量、函數(shù)或者其他實(shí)體。在實(shí)際編程過程中,即使我們沒有包含cassert頭文件,也最好不要為了其他目的使用assert。很多頭文件都包含了cassert,這就意味著即使你沒有直接包含 cassert,它也很有可能通過其他途徑包含在你的程序中。

    assert 宏常用于檢查“不能發(fā)生”的條件。例如,一個(gè)對(duì)輸入文本進(jìn)行操作的程序可能要求所有給定單詞的長(zhǎng)度都大于某個(gè)閾值。此時(shí),程序可以包含一條如下所示的語句:

    assert(word.size() > threshold);

    NDEBUG預(yù)處理變量

    assert的行為依賴于一個(gè)名為NDEBUG 的預(yù)處理變量的狀態(tài)。如果定義了NDEBUG,則assert 什么也不做。默認(rèn)狀態(tài)下沒有定義NDEBUG,此時(shí) assert將執(zhí)行運(yùn)行時(shí)檢查。

    我們可以使用一個(gè)#define語句定義NDEBUG,從而關(guān)閉調(diào)試狀態(tài)

    同時(shí),很多編譯器都提供了一個(gè)命令行選項(xiàng)使我們可以定義預(yù)處理變量:

    $ cc -D NDEBUG main.c # use /D with the Microsoft compiler

    這條命令的作用等價(jià)于在main.c文件的一開始寫#define NDEBUG。

    定義NDEBUG能避免檢查各種條件所需的運(yùn)行時(shí)開銷,當(dāng)然此時(shí)根本就不會(huì)執(zhí)行運(yùn)行時(shí)檢查。因此,assert應(yīng)該僅用于驗(yàn)證那些確實(shí)不可能發(fā)生的事情。我們可以把a(bǔ)ssert當(dāng)成調(diào)試程序的一種輔助手段,但是不能用它替代真正的運(yùn)行時(shí)邏輯檢查,也不能替代程序本身應(yīng)該包含的錯(cuò)誤檢查

    除了用于assert外,也可以使用NDEBUG編寫自己的條件調(diào)試代碼。如果NDEBUG未定義,將執(zhí)行#ifndef和#endif之間的代碼;如果定義了NDEBUG,這些代碼將被忽略掉:

    void print(const int ia[], size_t size) { #ifndef NDEBUG// _ _func_ _ is a local static defined by the compiler that holds the function's namecerr << __func__ << ": array size is " << size << endl; #endif // ...

    在這段代碼中,我們使用變量__func__輸出當(dāng)前調(diào)試的函數(shù)的名字。編譯器為每個(gè)函數(shù)都定義了__func__,它是const char的一個(gè)靜態(tài)數(shù)組,用于存放函數(shù)的名字。

    除了C++編譯器定義的__func__之外,預(yù)處理器還定義了另外4個(gè)對(duì)于程序調(diào)試很有用的名字:

    • __FILE__存放文件名的字符串字面值。
    • __LINE__存放當(dāng)前行號(hào)的整型字面值。
    • __TIMEB__存放文件編譯時(shí)間的字符串字面值。
    • __DATE__存放文件編譯日期的字符串字面值。

    可以使用這些常量在錯(cuò)誤消息中提供更多信息:

    if (word.size() < threshold)cerr << "Error: " << _ _FILE_ _<< " : in function " << _ _func_ _<< " at line " << _ _LINE_ _ << endl<< " Compiled on " << _ _DATE_ _<< " at " << _ _TIME_ _ << endl<< " Word read was \"" << word<< "\": Length too short" << endl;

    如果我們給程序提供了一個(gè)長(zhǎng)度小于threshold的string對(duì)象,將得到下面的錯(cuò)誤消息:

    Error : wdebug.cc : in function main at line 27Compiled on Jul 11 2012 at 20:50:03Word read was "foo" : Length too short

    函數(shù)匹配

    例子:調(diào)用應(yīng)該選用哪個(gè)重載函數(shù)

    在大多數(shù)情況下,我們?nèi)菀状_定某次調(diào)用應(yīng)該選用哪個(gè)重載函數(shù)。然而,當(dāng)幾個(gè)重載函數(shù)的形參數(shù)量相等以及某些形參的類型可以由其他類型轉(zhuǎn)換得來時(shí),這項(xiàng)工作就不那么容易了。以下面這組函數(shù)及其調(diào)用為例:

    void f(); void f (int) ; void f (int, int) ; void f (double, double = 3.14); f (5.6);//調(diào)用void f (double, double)

    確定候選函數(shù)和可行函數(shù)

    函數(shù)匹配的第一步是選定本次調(diào)用對(duì)應(yīng)的重載函數(shù)集,集合中的函數(shù)稱為候選函數(shù)(candidate function)。候選函數(shù)具備兩個(gè)特征:

  • 與被調(diào)用的函數(shù)同名,
  • 其聲明在調(diào)用點(diǎn)可見。
  • 在這個(gè)例子中,有4個(gè)名為f的候選函數(shù)。

    第二步考察本次調(diào)用提供的實(shí)參,然后從候選函數(shù)中選出能被這組實(shí)參調(diào)用的函數(shù),這些新選出的函數(shù)稱為可行函數(shù)(viable function)。可行函數(shù)也有兩個(gè)特征:

  • 其形參數(shù)量與本次調(diào)用提供的實(shí)參數(shù)量相等,
  • 每個(gè)實(shí)參的類型與對(duì)應(yīng)的形參類型相同,或者能轉(zhuǎn)換成形參的類型。
  • 我們能根據(jù)實(shí)參的數(shù)量從候選函數(shù)中排除掉兩個(gè)。不使用形參的函數(shù)和使用兩個(gè)int形參的函數(shù)顯然都不適合本次調(diào)用,這是因?yàn)槲覀兊恼{(diào)用只提供了一個(gè)實(shí)參,而它們分別有0個(gè)和兩個(gè)形參。

    使用一個(gè) int形參的函數(shù)和使用兩個(gè)double形參的函數(shù)是可行的,它們都能用一個(gè)實(shí)參調(diào)用。其中最后那個(gè)函數(shù)本應(yīng)該接受兩個(gè)double值,但是因?yàn)樗幸粋€(gè)默認(rèn)實(shí)參,所以只用一個(gè)實(shí)參也能調(diào)用它。

    Note:如果函數(shù)含有默認(rèn)實(shí)參,則我們?cè)谡{(diào)用該函數(shù)時(shí)傳入的實(shí)參數(shù)量可能少于它實(shí)際使用的實(shí)參數(shù)量。

    在使用實(shí)參數(shù)量初步判別了候選函數(shù)后,接下來考察實(shí)參的類型是否與形參匹配。和一般的函數(shù)調(diào)用類似,實(shí)參與形參匹配的含義可能是它們具有相同的類型,也可能是實(shí)參類型和形參類型滿足轉(zhuǎn)換規(guī)則。在上面的例子中,剩下的兩個(gè)函數(shù)都是可行的:

    • f(int)是可行的,因?yàn)閷?shí)參類型double 能轉(zhuǎn)換成形參類型int。

    • f(double,double)是可行的,因?yàn)樗牡诙€(gè)形參提供了默認(rèn)值,而第一個(gè)形參的類型正好是 double,與函數(shù)使用的實(shí)參類型完全一致。

    Note:如果沒找到可行函數(shù),編譯器將報(bào)告無匹配函數(shù)的錯(cuò)誤。

    尋找最佳匹配(如果有的話)

    函數(shù)匹配的第三步是從可行函數(shù)中選擇與本次調(diào)用最匹配的函數(shù)。在這一過程中,逐一檢查函數(shù)調(diào)用提供的實(shí)參,尋找形參類型與實(shí)參類型最匹配的那個(gè)可行函數(shù)。下一節(jié)將介紹“最匹配”的細(xì)節(jié),它的基本思想是,實(shí)參類型與形參類型越接近,它們匹配得越好

    在我們的例子中,調(diào)用只提供了一個(gè)(顯式的)實(shí)參,它的類型是double。如果調(diào)用f(int) ,實(shí)參將不得不從double轉(zhuǎn)換成int。另一個(gè)可行函數(shù)f(double,double)則與實(shí)參精確匹配。精確匹配比需要類型轉(zhuǎn)換的匹配更好,因此,編譯器把f(5.6)解析成對(duì)含有兩個(gè)double形參的函數(shù)的調(diào)用,并使用默認(rèn)值填補(bǔ)我們未提供的第二個(gè)實(shí)參。

    含有多個(gè)形參的函數(shù)匹配

    當(dāng)實(shí)參的數(shù)量有兩個(gè)或更多時(shí),函數(shù)匹配就比較復(fù)雜了。對(duì)于前面那些名為f的函數(shù),我們來分析如下的調(diào)用會(huì)發(fā)生什么情況:

    (42,2.56);

    選擇可行函數(shù)的方法和只有一個(gè)實(shí)參時(shí)一樣,編譯器選擇那些形參數(shù)量滿足要求且實(shí)參類型和形參類型能夠匹配的函數(shù)。此例中,可行函數(shù)包括 f(int,int)和 f(double,double)。

    接下來,編譯器依次檢查每個(gè)實(shí)參以確定哪個(gè)函數(shù)是最佳匹配。如果有且只有一個(gè)函數(shù)滿足下列條件,則匹配成功:

    • 該函數(shù)每個(gè)實(shí)參的匹配都不劣于其他可行函數(shù)需要的匹配。The match for each argument is no worse than the match required by any other viable function

    • 至少有一個(gè)實(shí)參的匹配優(yōu)于其他可行函數(shù)提供的匹配。There is at least one argument for which the match is better than the match provided by any other viable function

    如果在檢查了所有實(shí)參之后沒有任何一個(gè)函數(shù)脫穎而出,則該調(diào)用是錯(cuò)誤的。編譯器將報(bào)告二義性調(diào)用的信息。

    在上面的調(diào)用中,只考慮第一個(gè)實(shí)參時(shí)我們發(fā)現(xiàn)函數(shù)f(int,int)能精確匹配;要想匹配第二個(gè)函數(shù),int類型的實(shí)參必須轉(zhuǎn)換成double類型。顯然需要內(nèi)置類型轉(zhuǎn)換的匹配劣于精確匹配,因此僅就第一個(gè)實(shí)參來說,f(int, int)比 f(double,double)更好。

    接著考慮第二個(gè)實(shí)參2.56,此時(shí)f(double,double)是精確匹配;要想調(diào)用f(int,int)必須將2.56從double類型轉(zhuǎn)換成int類型。因此僅就第二個(gè)實(shí)參來說,f(double,double)更好。

    (MyNote:公說公有理婆說婆有理。)

    編譯器最終將因?yàn)檫@個(gè)調(diào)用具有二義性而拒絕其請(qǐng)求:因?yàn)槊總€(gè)可行函數(shù)各自在一個(gè)實(shí)參上實(shí)現(xiàn)了更好的匹配,從整體上無法判斷孰優(yōu)孰劣。看起來我們似乎可以通過強(qiáng)制類型轉(zhuǎn)換其中的一個(gè)實(shí)參來實(shí)現(xiàn)函數(shù)的匹配,但是在設(shè)計(jì)良好的系統(tǒng)中,不應(yīng)該對(duì)實(shí)參進(jìn)行強(qiáng)制類型轉(zhuǎn)換。

    Best Practices:調(diào)用重載函數(shù)時(shí)應(yīng)盡量避免強(qiáng)制類型轉(zhuǎn)換。如果在實(shí)際應(yīng)用中確實(shí)需要強(qiáng)制類型轉(zhuǎn)換,則說明我們?cè)O(shè)計(jì)的形參集合不合理。

    實(shí)參類型轉(zhuǎn)換

    為了確定最佳匹配,編譯器將實(shí)參類型到形參類型的轉(zhuǎn)換劃分成幾個(gè)等級(jí),具體排序如下所示:

  • 精確匹配,包括以下情況:
    • 實(shí)參類型和形參類型相同。
    • 實(shí)參從數(shù)組類型或函數(shù)類型轉(zhuǎn)換成對(duì)應(yīng)的指針類型。
    • 向?qū)崊⑻砑禹攲觕onst或者從實(shí)參中刪除頂層const。
  • 通過const轉(zhuǎn)換實(shí)現(xiàn)的匹配。
  • 通過類型提升實(shí)現(xiàn)的匹配。
  • 通過算術(shù)類型轉(zhuǎn)換或指針轉(zhuǎn)換實(shí)現(xiàn)的匹配。
  • 通過類類型轉(zhuǎn)換實(shí)現(xiàn)的匹配(第14章內(nèi)容)。
  • (2~4項(xiàng)為第4章“類型轉(zhuǎn)換”內(nèi)容)

    需要類型提升和算術(shù)類型轉(zhuǎn)換的匹配

    WARNING:內(nèi)置類型的提升和轉(zhuǎn)換可能在函數(shù)匹配時(shí)產(chǎn)生意想不到的結(jié)果,但幸運(yùn)的是,在設(shè)計(jì)良好的系統(tǒng)中函數(shù)很少會(huì)含有與下面例子類似的形參。

    分析函數(shù)調(diào)用前,我們應(yīng)該知道小整型一般都會(huì)提升到int類型或更大的整數(shù)類型。

    假設(shè)有兩個(gè)函數(shù),一個(gè)接受int、另一個(gè)接受short,則只有當(dāng)調(diào)用提供的是short類型的值時(shí)才會(huì)選擇short版本的函數(shù)。有時(shí)候,即使實(shí)參是一個(gè)很小的整數(shù)值,也會(huì)直接將它提升成int類型;此時(shí)使用short版本反而會(huì)導(dǎo)致類型轉(zhuǎn)換:

    void ff(int); void ff(short); ff('a');// char提升成int;調(diào)用f(int)

    所有算術(shù)類型轉(zhuǎn)換的級(jí)別都一樣。例如,從int向unsigned int 的轉(zhuǎn)換并不比從int向double的轉(zhuǎn)換級(jí)別高。舉個(gè)具體點(diǎn)的例子,考慮

    void manip(long); void manip(float); manip(3.14);//錯(cuò)誤:二義性調(diào)用

    字面值3.14的類型是double,它既能轉(zhuǎn)換成long也能轉(zhuǎn)換成float。因?yàn)榇嬖趦煞N可能的算數(shù)類型轉(zhuǎn)換,所以該調(diào)用具有二義性。

    函數(shù)匹配和const實(shí)參

    如果重載函數(shù)的區(qū)別在于它們的引用類型的形參是否引用了const,或者指針類型的形參是否指向const,則當(dāng)調(diào)用發(fā)生時(shí)編譯器通過實(shí)參是否是常量來決定選擇哪個(gè)函數(shù):

    Record lookup (Account&); //函數(shù)的參數(shù)是Account的引用 Record lookup(const Account& ); //函數(shù)的參數(shù)是一個(gè)常量引用 const Account a; Account b; lookup(a); //調(diào)用lookup (const Account&) lookup(b); //調(diào)用lookup(Account&)

    在第一個(gè)調(diào)用中,我們傳入的是const對(duì)象a。因?yàn)椴荒馨哑胀ㄒ媒壎ǖ絚onst對(duì)象上,所以此例中唯一可行的函數(shù)是以常量引用作為形參的那個(gè)函數(shù),并且調(diào)用該函數(shù)與實(shí)參a精確匹配。

    在第二個(gè)調(diào)用中,我們傳入的是非常量對(duì)象b。對(duì)于這個(gè)調(diào)用來說,兩個(gè)函數(shù)都是可行的,因?yàn)槲覀兗瓤梢允褂胋初始化常量引用也可以用它初始化非常量引用。然而,用非常量對(duì)象初始化常量引用需要類型轉(zhuǎn)換,接受非常量形參的版本則與b精確匹配。因此,應(yīng)該選用非常量版本的函數(shù)。

    指針類型的形參也類似。如果兩個(gè)函數(shù)的唯一區(qū)別是它的指針形參指向常量或非常量,則編譯器能通過實(shí)參是否是常量決定選用哪個(gè)函數(shù):

    • 如果實(shí)參是指向常量的指針,調(diào)用形參是const*的函數(shù);
    • 如果實(shí)參是指向非常量的指針,調(diào)用形參是普通指針的函數(shù)。

    函數(shù)指針

    函數(shù)指針指向的是函數(shù)而非對(duì)象。和其他指針一樣,函數(shù)指針指向某種特定類型。函數(shù)的類型由它的返回類型和形參類型共同決定,與函數(shù)名無關(guān)。例如:

    //比較兩個(gè)string對(duì)象的長(zhǎng)度 bool lengthCompare (const string &, const string &);

    該函數(shù)的類型是bool (const string&,const string&)。要想聲明一個(gè)可以指向該函數(shù)的指針,只需要用指針替換函數(shù)名即可:

    //pf指向一個(gè)函數(shù),該函數(shù)的參數(shù)是兩個(gè)const string的引用,返回值是bool類型 bool (*pf)(const string &, const string &);//未初始化
    • 從我們聲明的名字開始觀察,pf前面有個(gè)*,因此pf是指針;
    • 右側(cè)是形參列表,表示pf指向的是函數(shù);
    • 再觀察左側(cè),發(fā)現(xiàn)函數(shù)的返回類型是布爾值。

    因此,pf 就是一個(gè)指向函數(shù)的指針,其中該函數(shù)的參數(shù)是兩個(gè)const string 的引用,返回值是bool類型。

    Note:*pf兩端的括號(hào)必不可少。如果不寫這對(duì)括號(hào),則pf是一個(gè)返回值為bool指針的函數(shù):

    //聲明一個(gè)名為 pf的函數(shù),該函數(shù)返回bool* bool *pf (const string &, const string &);

    使用函數(shù)指針

    當(dāng)我們把函數(shù)名作為一個(gè)值使用時(shí),該函數(shù)自動(dòng)地轉(zhuǎn)換成指針。例如,按照如下形式我們可以將lengthCompare的地址賦給pf:

    pf = lengthCompare;//pf指向名為lengthcompare的函數(shù) pf = &lengthCompare;//等價(jià)的賦值語句:取地址符是可選的

    此外,我們還能直接使用指向函數(shù)的指針調(diào)用該函數(shù),無須提前解引用指針:

    bool b1 = pf("hello","goodbye" ) ; //調(diào)用lengthcompare函數(shù) bool b2 = (*pf)("hello","goodbye" );//一個(gè)等價(jià)的調(diào)用 bool b3 = lengthCompare("hello","goodbye");//另一個(gè)等價(jià)的調(diào)用

    在指向不同函數(shù)類型的指針間不存在轉(zhuǎn)換規(guī)則。但是和往常一樣,我們可以為函數(shù)指針賦一個(gè)nullptr或者值為0的整型常量表達(dá)式,表示該指針沒有指向任何一個(gè)函數(shù):

    string::size_type sumLength(const string&, const string&); bool cstringCompare(const char*, const char* ) ; pf = 0 ; //正確:pf不指向任何函數(shù) pf = sumLength; //錯(cuò)誤:返回類型不匹配 pf = cstringCompare; //錯(cuò)誤:形參類型不匹配 pf = lengthCompare; //正確:函數(shù)和指針的類型精確匹配

    重載函數(shù)的指針

    當(dāng)我們使用重載函數(shù)時(shí),上下文必須清晰地界定到底應(yīng)該選用哪個(gè)函數(shù)。如果定義了指向重載函數(shù)的指針

    void ff(int*); void ff(unsigned int) ; void (*pf1)(unsigned int) = ff; // pf1指向ff(unsigned)

    編譯器通過指針類型決定選用哪個(gè)函數(shù),指針類型必須與重載函數(shù)中的某一個(gè)精確匹配

    void (*pf2)(int) = ff;//錯(cuò)誤:沒有任何一個(gè)ff與該形參列表匹配* double (*pf3)(int*) = ff;//錯(cuò)誤:ff和pf3的返回類型不匹配

    函數(shù)指針形參

    和數(shù)組類似,雖然不能定義函數(shù)類型的形參,但是形參可以是指向函數(shù)的指針。此時(shí),形參看起來是函數(shù)類型,實(shí)際上卻是當(dāng)成指針使用:

    //第三個(gè)形參是函數(shù)類型,它會(huì)自動(dòng)地轉(zhuǎn)換成指向函數(shù)的指針 void useBigger (const string &s1,const string &s2,bool pf (const string &, const string &)); //等價(jià)的聲明:顯式地將形參定義成指向函數(shù)的指針 void useBigger(const string &s1,const string &s2,bool (*pf)(const string &, const string &)) ;

    我們可以直接把函數(shù)作為實(shí)參使用,此時(shí)它會(huì)自動(dòng)轉(zhuǎn)換成指針:

    //自動(dòng)將函數(shù)lengthcompare轉(zhuǎn)換成指向該函數(shù)的指針 useBigger(s1, s2, lengthCompare);

    正如useBigger的聲明語句所示,直接使用函數(shù)指針類型顯得冗長(zhǎng)而煩瑣。類型別名和 decltype(第2章內(nèi)容)能讓我們簡(jiǎn)化使用了函數(shù)指針的代碼:

    // Func和Func2是函數(shù)類型 typedef bool Func(const string&, const string&); typedef decltype(lengthCompare) Func2; //等價(jià)的類型// FuncP和FuncP2是指向函數(shù)的指針 typedef bool (*FuncP)(const string&, const string&) ; typedef decltype(lengthCompare) *FuncP2;//等價(jià)的類型

    我們使用typedef定義自己的類型。Func和Func2是函數(shù)類型,而FuncP和 FuncP2是指針類型。需要注意的是,decltype返回函數(shù)類型,此時(shí)不會(huì)將函數(shù)類型自動(dòng)轉(zhuǎn)換成指針類型。因?yàn)閐ecltype的結(jié)果是函數(shù)類型,所以只有在結(jié)果前面加上*才能得到指針。可以使用如下的形式重新聲明useBigger:

    // useBigger的等價(jià)聲明,其中使用了類型別名 void useBigger(const string&, const string&, Func); void useBigger(const string&, const string&, FuncP2);

    這兩個(gè)聲明語句聲明的是同一個(gè)函數(shù),在第一條語句中,編譯器自動(dòng)地將Func表示的函數(shù)類型轉(zhuǎn)換成指針。

    返回指向函數(shù)的指針

    和數(shù)組類似,雖然不能返回一個(gè)函數(shù),但是能返回指向函數(shù)類型的指針。然而,我們必須把返回類型寫成指針形式,編譯器不會(huì)自動(dòng)地將函數(shù)返回類型當(dāng)成對(duì)應(yīng)的指針類型處理。與往常一樣,要想聲明一個(gè)返回函數(shù)指針的函數(shù),最簡(jiǎn)單的辦法是使用類型別名:

    using F = int(int*, int); //F是函數(shù)類型,不是指針 using PF = int(*)(int*, int); //PF是指針類型

    其中我們使用類型別名將F定義成函數(shù)類型,將PF定義成指向函數(shù)類型的指針。必須時(shí)刻注意的是,和函數(shù)類型的形參不一樣,返回類型不會(huì)自動(dòng)地轉(zhuǎn)換成指針。我們必須顯式地將返回類型指定為指針:

    PF f1(int); //正確:PF是指向函數(shù)的指針,f1返回指向函數(shù)的指針 F f1(int); //錯(cuò)誤:F是函數(shù)類型,f1不能返回一個(gè)函數(shù) F *f1(int); //正確:顯式地指定返回類型是指向函數(shù)的指針

    當(dāng)然,我們也能用下面的形式直接聲明f1:

    int (*f1(int))(int*, int) ;

    按照由內(nèi)向外的順序閱讀這條聲明語句:

  • 我們看到f1有形參列表,所以f1是個(gè)函數(shù);
  • f1前面有*,所以f1返回一個(gè)指針;
  • 進(jìn)一步觀察發(fā)現(xiàn),指針的類型本身也包含形參列表,因此指針指向函數(shù),該函數(shù)的返回類型是int。
  • 出于完整性的考慮,有必要提醒讀者我們還可以使用尾置返回類型的方式聲明一個(gè)返回函數(shù)指針的函數(shù):

    auto f1(int) -> int(*) (int* , int) ;

    將auto和decltype用于函數(shù)指針類型

    如果我們明確知道返回的函數(shù)是哪一個(gè),就能使用decltype簡(jiǎn)化書寫函數(shù)指針返回類型的過程。

    例如假定有兩個(gè)函數(shù),它們的返回類型都是string::size_type,并且各有兩個(gè)const strings類型的形參,此時(shí)我們可以編寫第三個(gè)函數(shù),它接受一個(gè)string類型的參數(shù),返回一個(gè)指針,該指針指向前兩個(gè)函數(shù)中的一個(gè):

    string::size_type sumLength(const string&, const string&); string::size_type largerLength(const string&, const string&);//根據(jù)其形參的取值,getFcn函數(shù)返回指向sumLength或者largerLength的指針 decltype(sumLength) *getFcn (const string &);

    聲明getFcn唯一需要注意的地方是,牢記當(dāng)我們將decltype作用于某個(gè)函數(shù)時(shí),它返回函數(shù)類型而非指針類型。因此,我們顯式地加上*以表明我們需要返回指針,而非函數(shù)本身。

    總結(jié)

    以上是生活随笔為你收集整理的《C++ Primer 5th》笔记(6 / 19):函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。