每一個程序都包含很多的函數(shù):函數(shù)是基本的代碼塊。
Go是編譯型語言,所以函數(shù)編寫的順序是無關(guān)緊要的;鑒于可讀性的需求,最好把 main()
函數(shù)寫在文件的前面,其他函數(shù)按照一定邏輯順序進(jìn)行編寫(例如函數(shù)被調(diào)用的順序)。
編寫多個函數(shù)的主要目的是將一個需要很多行代碼的復(fù)雜問題分解為一系列簡單的任務(wù)(那就是函數(shù))來解決。而且,同一個任務(wù)(函數(shù))可以被調(diào)用多次,有助于代碼重用。
(事實上,好的程序是非常注意DRY原則的,即不要重復(fù)你自己(Don't Repeat Yourself),意思是執(zhí)行特定任務(wù)的代碼只能在程序里面出現(xiàn)一次。)
當(dāng)函數(shù)執(zhí)行到代碼塊最后一行(}
之前)或者 return
語句的時候會退出,其中 return
語句可以帶有零個或多個參數(shù);這些參數(shù)將作為返回值(參考 第 6.2 節(jié))供調(diào)用者使用。簡單的 return
語句也可以用來結(jié)束 for 死循環(huán),或者結(jié)束一個協(xié)程(goroutine)。
Go 里面有三種類型的函數(shù):
除了main()、init()函數(shù)外,其它所有類型的函數(shù)都可以有參數(shù)與返回值。函數(shù)參數(shù)、返回值以及它們的類型被統(tǒng)稱為函數(shù)簽名。
作為提醒,提前介紹一個語法:
這樣是不正確的 Go 代碼:
func g()
{
}
它必須是這樣的:
func g() {
}
函數(shù)被調(diào)用的基本格式如下:
pack1.Function(arg1, arg2, …, argn)
Function
是 pack1
包里面的一個函數(shù),括號里的是被調(diào)用函數(shù)的實參(argument):這些值被傳遞給被調(diào)用函數(shù)的形參(parameter,參考 第 6.2 節(jié))。函數(shù)被調(diào)用的時候,這些實參將被復(fù)制(簡單而言)然后傳遞給被調(diào)用函數(shù)。函數(shù)一般是在其他函數(shù)里面被調(diào)用的,這個其他函數(shù)被稱為調(diào)用函數(shù)(calling function)。函數(shù)能多次調(diào)用其他函數(shù),這些被調(diào)用函數(shù)按順序(簡單而言)執(zhí)行,理論上,函數(shù)調(diào)用其他函數(shù)的次數(shù)是無窮的(直到函數(shù)調(diào)用棧被耗盡)。
一個簡單的函數(shù)調(diào)用其他函數(shù)的例子:
示例 6.1 greeting.go
package main
func main() {
println("In main before calling greeting")
greeting()
println("In main after calling greeting")
}
func greeting() {
println("In greeting: Hi!!!!!")
}
代碼輸出:
In main before calling greeting
In greeting: Hi!!!!!
In main after calling greeting
函數(shù)可以將其他函數(shù)調(diào)用作為它的參數(shù),只要這個被調(diào)用函數(shù)的返回值個數(shù)、返回值類型和返回值的順序與調(diào)用函數(shù)所需求的實參是一致的,例如:
假設(shè) f1 需要 3 個參數(shù) f1(a, b, c int)
,同時 f2 返回 3 個參數(shù) f2(a, b int) (int, int, int)
,就可以這樣調(diào)用 f1:f1(f2(a, b))
。
函數(shù)重載(function overloading)指的是可以編寫多個同名函數(shù),只要它們擁有不同的形參與/或者不同的返回值,在 Go 里面函數(shù)重載是不被允許的。這將導(dǎo)致一個編譯錯誤:
funcName redeclared in this book, previous declaration at lineno
Go 語言不支持這項特性的主要原因是函數(shù)重載需要進(jìn)行多余的類型匹配影響性能;沒有重載意味著只是一個簡單的函數(shù)調(diào)度。所以你需要給不同的函數(shù)使用不同的名字,我們通常會根據(jù)函數(shù)的特征對函數(shù)進(jìn)行命名(參考 第 11.12.5 節(jié))。
如果需要申明一個在外部定義的函數(shù),你只需要給出函數(shù)名與函數(shù)簽名,不需要給出函數(shù)體:
func flushICache(begin, end uintptr) // implemented externally
函數(shù)也可以以申明的方式被使用,作為一個函數(shù)類型,就像:
type binOp func(int, int) int
在這里,不需要函數(shù)體 {}
。
函數(shù)是一等值(first-class value):它們可以賦值給變量,就像 add := binOp
一樣。
這個變量知道自己指向的函數(shù)的簽名,所以給它賦一個具有不同簽名的函數(shù)值是不可能的。
函數(shù)值(functions value)之間可以相互比較:如果它們引用的是相同的函數(shù)或者都是 nil 的話,則認(rèn)為它們是相同的函數(shù)。函數(shù)不能在其它函數(shù)里面聲明(不能嵌套),不過我們可以通過使用匿名函數(shù)(參考 第 6.8 節(jié))來破除這個限制。
目前 Go 沒有泛型(generic)的概念,也就是說它不支持那種支持多種類型的函數(shù)。不過在大部分情況下可以通過接口(interface),特別是空接口與類型選擇(type switch,參考 第 11.12 節(jié))與/或者通過使用反射(reflection,參考 第 6.8 節(jié))來實現(xiàn)相似的功能。使用這些技術(shù)將導(dǎo)致代碼更為復(fù)雜、性能更為低下,所以在非常注意性能的的場合,最好是為每一個類型單獨創(chuàng)建一個函數(shù),而且代碼可讀性更強(qiáng)。