天天看點

函數

疊代的是人,遞歸的是神。—— L.Peter Dautsch

 1、函數聲明

  在C++程式裡,完成某件工作的一種典型方式就是調用一個函數去做那件事情。定義函數是你刻畫怎樣完成某個操作的一種方式。一個函數隻有在預先聲明之後才能調用。

  在一個函數聲明中,需要給出函數的名字,這個函數傳回的值的類型(如果有的話),以及在調用這個函數時必須提供的參數的個數和類型。例如

  參數傳遞的語義等同于初始化的語義。參數的類型被逐個檢查,如果需要就會做隐式的參數類型轉換。

  在函數聲明中可以包含參數的名字。這樣做可能對讀程式的人有所幫助,但編譯器将簡單地忽略掉這樣的名字。以void作為傳回值類型表示這個函數不傳回值。

  1)函數定義

  在程式裡調用的每個函數都必須在某個地方定義(僅僅一次)。一個函數定義也就是一個給出了函數體的函數聲明。一個函數的定義和對它的所有聲明必須都描述了同樣的類型。不過,這裡并不把參數名字作為類型的一部分。

  函數可以定義為inline。

  2)靜态變量

  局部變量将在運作線程達到其定義時進行初始化。按照預設方式,這件事發生在函數的每次調用中,且函數的每個調用有自己的一份局部變量副本。如果一局部變量被聲明為static,那麼将隻有惟一的一個靜态配置設定的對象,它被用于在該函數的所有調用中表示這個變量。這個對象将隻在執行線程第一次到達它的定義時初始化。

  靜态變量在一個函數裡定義,隻能在這個函數裡通過變量名通路,但它的存在并不依賴于函數的調用。是以,這種變量可以用于儲存需要一直存在,以便在這個函數内部使用的資訊。

  靜态變量為函數提供了一種“存儲器”,使我們不必去引進可能被其他函數通路或破壞的全局變量。

2、參數傳遞

  當一個函數被調用時,将安排好其形式參數所需要的存儲,各個形式參數将用對應的實際參數進行初始化。參數傳遞的語義與初始化的語義完全相同。特别是,需要對照着每一個形式參數檢查與之對應的實際參數的類型,并執行所有标準的或者使用者定義的類型轉換。

  通過引用方式傳遞大的對象,比通過值傳遞的效率更高一些。在這種情況下,可以将有關的參數聲明為const,以指明使用這種參數僅僅是為了效率的原因,而不是想讓調用函數能夠修改對象的值。

  1)數組參數

  如果将數組作為函數的參數,傳遞的就是到數組的首元素的指針。也就是說,類型T[]作為參數傳遞時将被轉換為一個T*。這也就意味着,對數組參數的某個元素的指派,将改變實際參數數組中那個元素的值。換句話說,數組與其他類型不同,數組不會也不能按值的方式傳遞。

  多元數組的情況更加詭異,但通常可以改用指針的數組,這樣做并不需要特别的處理。

3、傳回值

  一個沒有聲明為void的函數都必須傳回一個值(main()是特殊的)。與此相反,void函數就不能傳回值。

  傳回值由傳回語句描述。一個調用自己的函數被稱為是遞歸的。函數裡可以出現多個傳回語句。

  就像參數傳遞的語義一樣,函數傳回值的語義也與初始化的語義相同,可以認為傳回語句所做的就是去初始化一個具有傳回類型的匿名變量。每當一個函數被調用時,就會建立起它的所有參數和局部變量的一套新副本。在該函數傳回後,這些存儲又會被另做他用。是以,絕不能傳回指向局部變量的指針,因為被指位置中内容的改變情況是無法預料的。

4、重載函數名

  将同一個名字用于在不同類型上操作的函數的情況稱為重載。

  重載解析與被考慮的函數聲明的順序無關。重載解析中将不考慮傳回類型。這樣規定的理由就是要保持對重載的解析隻是針對單獨的運算符或函數調用,與調用

的環境無關。

  在不同的非名字空間作用域裡聲明的函數不算是重載。

  對一個函數,聲明的重載版本過少或者過多都有可能導緻歧義性。一些C++新手會被編譯器報告出的歧義性錯誤弄得急躁起來。更有經驗的程式員則欣賞這種錯誤資訊,将它們看做是很有用的關于設計錯誤的訓示器。

5、預設參數

  預設參數的類型将在函數聲明時檢查,在調用時求值。隻可能對排列在最後的那些參數提供預設參數。例如,

6、未确定數目的參數

  對于有些參數而言,我們沒辦法确定在各個調用中所期望的所有參數的個數和類型。聲明這種函數的方式就是在參數表的最後用省略号(...)結束,省略号表示“還可能有另外一些參數”。例如

7、指向函數的指針

  對一個函數隻能做兩件事:調用它,或者取得它的位址。通過一個函數的位址而得到的指針,可以在後面用于調用這個函數。

  在指向函數的指針的聲明中也需要給出參數類型,就像函數聲明一樣。在指針指派時,完整的函數類型必須完全比對。

  指向函數的指針的數組常常很有用。例如,我的基于滑鼠的編輯器裡的菜單系統,就是利用指向函數的指針的數組實作的,這些函數表示各種各樣的操作。

  要了解指向函數的指針的表達能力,一種方式就是試着寫這種代碼而不是用函數指針——也不用它們的更具良好行為的兄弟:虛函數。通過把新函數插入運算符表等方式,就可以在運作中修改這種菜單。在運作中構造出新菜單也同樣非常容易。

8、宏

  宏在C語言裡極其重要,而在C++裡用得就少多了。關于宏的第一規則是:絕不應該去使用它,除非你不得不這樣做。

  宏名字不能重載,而且宏預處理器不能處理遞歸函數調用。

  有一種宏的使用幾乎不可能避免。指令#ifdef identifier将條件性地導緻随後的輸入被忽略,直到遇到一個#endif指令。

9、忠告

【1】質疑那些非const的引用參數;如果你想要一個函數去修改其參數,請使用指針或者傳回值;

【2】當你需要盡可能減少參數複制時,應該使用const引用參數;

【3】廣泛而一緻地使用const;

【4】避免宏;

【5】避免不确定數目的參數;

【6】不要傳回局部變量的指針或者引用;

【7】當一些函數對不同的類型執行概念上相同的工作時,請使用重載;

【8】在各種整數上重載時,通過提供函數去消除常見的歧義性;

【9】在考慮使用指向函數的指針時,請考慮虛函數或模闆是不是更好的選擇;

【10】如果你必須使用宏,請使用帶有許多大寫字母的醜陋的名字;