這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
函數有以下幾個優點:可以把一系列語句打包成一個程式單元;可以把大的工作分解為小的任務,這些任務可以讓不同程式員在不同時間、不同地點獨立完成;一個函數可以對使用者隱藏實現細節。這些優點,讓函數變成了程式不可或缺的最重要的部分之一。
在此章之前,我們已經見過很多函數了,現在是時候徹底討論函數的特性了。本章用的例子是一個網路爬蟲,也是web搜尋引擎中負責抓取網頁的組件,他們會根據抓取到的網頁中的連結繼續去抓取該連結指向的頁面,這個例子可以讓我們學習遞迴函式、匿名函數、錯誤處理及函數的很多其它特性。
函式宣告包含了函數名,可省略的參數列表、傳回值列表以及函數體:
func name(parameter-list) (result-list) { body}
參數列表描述了參數名和參數類型,這些參數是函數範圍的局部變數,值由調用者傳入。傳回值列表描述了函數傳回值的名字和類型,如果函數只返回一個不具名變數或者乾脆沒有傳回值,那傳回值列表的括弧是可以省略的。如果一個函式宣告不包含傳回值列表,那麼函數體執行完畢後,不會返回任何值:
func hypot(x, y float64) float64 { return math.Sqrt(x*x + y*y)}fmt.Println(hypot(3,4)) // "5"
x和y是參數名,3和4是調用時傳入的參數值,函數返回了一個float64類型的值。
傳回值也可以像參數一樣被命名,在這種情況下,傳回值被聲明成函數範圍的局部變數,並會初始化為對應類型的零值。
如果函數在聲明時有傳回值列表,那該函數必須要執行到return語句,除非函數無法執行到結尾處:例如發生了panic或者存在沒有break的無限迴圈。
正如hypot函數一樣,如果多個參數或者傳回值有相同的類型,我們可以一起集中聲明,下面兩個聲明是等價的:
func f(i, j, k int, s, t string) { /* ... */ }func f(i int, j int, k int, s string, t string) { /* ... */ }
下面的4個函數,每個函數都有2個int參數和一個int傳回值,但是我們用4種不同的方法進行了聲明。這裡要注意底線(空白操作符)的使用:
func add(x int, y int) int {return x + y}func sub(x, y int) (z int) { z = x - y; return}func first(x int, _ int) int { return x }func zero(int, int) int { return 0 }fmt.Printf("%T\n", add) // "func(int, int) int"fmt.Printf("%T\n", sub) // "func(int, int) int"fmt.Printf("%T\n", first) // "func(int, int) int"fmt.Printf("%T\n", zero) // "func(int, int) int"
函數類型就是函數簽名,如果兩個函數的參數列表和傳回值列表的變數類型能一一對應,那麼這兩個函數就有相同的簽名。參數和傳回值的變數名以及是否集中聲明並不會影響函數簽名。
函數調用的傳參必須按照參數聲明的順序。Go語言是沒有預設參數值的說法,也沒有任何方法通過參數名指定傳入參數的值,因此除了文檔外,參數和傳回值的變數名對於函數調用者是沒有任何意義的。
在函數體中,參數是局部變數,被初始化為調用者傳的值。函數的參數和具名傳回值是函數最外層的局部變數,它們的詞法塊就是整個函數。
參數是通過值來傳遞的,因此傳遞過去的都是原來變數的拷貝,對參數的修改不會影響到原來的變數。但是,如果參數是參考型別,例如指標、slice、map、function、channel等,那對參數的修改可能會影響原來的變數。
你可能會在標準庫中看到沒有函數體的函式宣告,這表示該函數不是用Go實現的:
package mathfunc Sin(x float64) float //該函數用彙編實現