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