除了上一篇文章介紹的運(yùn)算符操作,函數(shù)操作是另一種在編程中常用的操作。 函數(shù)操作常被稱為函數(shù)調(diào)用。此篇文章將介紹如何在Go中聲明和調(diào)用函數(shù)。
讓我們來(lái)看一個(gè)函數(shù)聲明:
func SquaresOfSumAndDiff(a int64, b int64) (s int64, d int64) {
x, y := a + b, a - b
s = x * x
d = y * y
return // <=> return s, d
}
從上面的例子中,我們可以發(fā)現(xiàn)一個(gè)函數(shù)聲明從左到右由以下部分組成:
func
關(guān)鍵字。
SquareOfSumAndDiff
。
return
關(guān)鍵字可以用來(lái)結(jié)束此函數(shù)的正常向前執(zhí)行流程并進(jìn)入此函數(shù)的退出階段(詳見(jiàn)下下節(jié)中的解釋)。
在上面的例子中,每個(gè)函數(shù)參數(shù)和結(jié)果聲明都由一個(gè)名字和一個(gè)類型組成(變量名字在前,類型在后)。 我們可以把一個(gè)參數(shù)和結(jié)果聲明看作是一個(gè)省略了var
關(guān)鍵字的標(biāo)準(zhǔn)變量聲明。 上面這個(gè)函數(shù)有兩個(gè)輸入?yún)?shù)(a
和b
)以及兩個(gè)輸出結(jié)果(x
和y
)。 它們的類型都是int64
。
輸出結(jié)果聲明列表中的所有聲明中的結(jié)果名稱可以(而且必須)同時(shí)出現(xiàn)或者同時(shí)省略。 這兩種方式在實(shí)踐中都使用得很廣泛。 如果一個(gè)返回結(jié)果聲明中的結(jié)果名稱沒(méi)有省略,則這個(gè)返回結(jié)果稱為具名返回結(jié)果。否則稱為匿名返回結(jié)果。
如果一個(gè)函數(shù)聲明的所有返回結(jié)果均為匿名的,則在此函數(shù)體內(nèi)的返回語(yǔ)句return
關(guān)鍵字后必須跟隨一系列返回值,這些返回值和此函數(shù)的各個(gè)返回結(jié)果聲明一一對(duì)應(yīng)。比如,下面這個(gè)函數(shù)聲明和上例中的函數(shù)聲明是等價(jià)的。
func SquaresOfSumAndDiff(a int64, b int64) (int64, int64) {
return (a+b) * (a+b), (a-b) * (a-b)
}
事實(shí)上,如果一個(gè)函數(shù)聲明中的所有輸入?yún)?shù)在此函數(shù)體內(nèi)都沒(méi)有被使用過(guò),則它們也可以都同時(shí)是匿名的。 不過(guò)這種情形在實(shí)際編程中很少見(jiàn)。
盡管一個(gè)函數(shù)聲明中的輸入?yún)?shù)和返回結(jié)果看上去是聲明在這個(gè)函數(shù)體的外部,但是在此函數(shù)體內(nèi),這些輸入?yún)?shù)和輸出結(jié)果被當(dāng)作局部變量來(lái)使用。 但輸入?yún)?shù)和輸出結(jié)果和普通局部變量還是有一點(diǎn)區(qū)別的:目前的主流Go編譯器不允許一個(gè)名稱不為_
的普通局部變量被聲明而不有效使用。
Go不支持輸入?yún)?shù)默認(rèn)值。每個(gè)返回結(jié)果的默認(rèn)值是它的類型的零值。 比如,下面的函數(shù)在被調(diào)用時(shí)將打印出(和返回)0 false
。
func f() (x int, y bool) {
println(x, y) // 0 false
return
}
和普通的變量聲明一樣,如果若干連續(xù)的輸入?yún)?shù)或者返回結(jié)果的類型相同,則在它們的聲明中可以共用一個(gè)類型。 比如,上面的兩個(gè)SquaresOfSumAndDiff
函數(shù)聲明和下面這個(gè)是完全等價(jià)的。
func SquaresOfSumAndDiff(a, b int64) (s, d int64) {
return (a+b) * (a+b), (a-b) * (a-b)
// 上面這行等價(jià)于下面這行:
// s = (a+b) * (a+b); d = (a-b) * (a-b); return
}
注意,盡管在上面這個(gè)函數(shù)聲明的返回結(jié)果都是具名的,函數(shù)體內(nèi)的return
關(guān)鍵字后仍然可以跟返回值。
如果一個(gè)函數(shù)聲明只包含一個(gè)返回結(jié)果,并且此返回結(jié)果是匿名的,則此函數(shù)聲明中的返回結(jié)果部分不必用小括號(hào)括起來(lái)。 如果一個(gè)函數(shù)聲明的返回結(jié)果列表為空,則此函數(shù)聲明中的返回結(jié)果部分可以完全被省略掉。 一個(gè)函數(shù)聲明的輸入?yún)?shù)列表部分總不能省略掉,即使此函數(shù)聲明的輸入?yún)?shù)列表為空。
下面是更多函數(shù)聲明的例子:
func CompareLower4bits(m, n uint32) (r bool) {
// 下面這兩行等價(jià)于:return m&0xFF > n&0xff
r = m&0xF > n&0xf
return
}
// 此函數(shù)沒(méi)有輸入?yún)?shù)。它的結(jié)果聲明列表只包含一個(gè)
// 匿名結(jié)果聲明,因此它不必用()括起來(lái)。
func VersionString() string {
return "go1.0"
}
// 此函數(shù)沒(méi)有返回結(jié)果。它的所有輸入?yún)?shù)都是匿名的。
// 它的結(jié)果聲明列表為空,因此可以被省略掉。
func doNothing(string, int) {
}
在前面的《Go語(yǔ)言101》文章中,我們已經(jīng)知道一個(gè)程序的main
入口函數(shù)必須不帶任何輸入?yún)?shù)和返回結(jié)果。
注意,在Go中,所有函數(shù)都必須直接聲明在包級(jí)代碼塊中。 或者說(shuō),任何一個(gè)函數(shù)都不能被聲明在另一個(gè)函數(shù)體內(nèi)。 雖然匿名函數(shù)(將在下面的某節(jié)中介紹)可以定義在函數(shù)體內(nèi),但匿名函數(shù)定義不屬于函數(shù)聲明。
一個(gè)聲明的函數(shù)可以通過(guò)它的名稱和一個(gè)實(shí)參列表來(lái)調(diào)用之。 一個(gè)實(shí)參列表必須用小括號(hào)括起來(lái)。 實(shí)參列表中的每一個(gè)單值實(shí)參對(duì)應(yīng)著(或稱被傳遞給了)一個(gè)形參。
注意:函數(shù)傳參也屬于賦值操作。在傳參中,各個(gè)實(shí)參被賦值給各個(gè)對(duì)應(yīng)形參。
一個(gè)實(shí)參值的類型不必一定要和其對(duì)應(yīng)的形參聲明的類型一樣。 但如果一個(gè)實(shí)參值的類型和其對(duì)應(yīng)的形參聲明的類型不一致,則此實(shí)參必須能夠隱式轉(zhuǎn)換到其對(duì)應(yīng)的形參的類型。
如果一個(gè)函數(shù)帶有返回值,則它的一個(gè)調(diào)用被視為一個(gè)表達(dá)式。如果此函數(shù)返回多個(gè)結(jié)果,則它的每個(gè)調(diào)用被視為一個(gè)多值表達(dá)式。 一個(gè)多值表達(dá)式可以被同時(shí)賦值給多個(gè)目標(biāo)值(數(shù)量必須匹配,各個(gè)輸出結(jié)果被賦值給相對(duì)應(yīng)的目標(biāo)值)。
下面這個(gè)例子完整地展示了如何調(diào)用幾個(gè)已經(jīng)聲明了的函數(shù)。
package main
func SquaresOfSumAndDiff(a int64, b int64) (int64, int64) {
return (a+b) * (a+b), (a-b) * (a-b)
}
func CompareLower4bits(m, n uint32) (r bool) {
r = m&0xF > n&0xf
return
}
// 使用一個(gè)函數(shù)調(diào)用的返回結(jié)果來(lái)初始化一個(gè)包級(jí)變量。
var v = VersionString()
func main() {
println(v) // v1.0
x, y := SquaresOfSumAndDiff(3, 6)
println(x, y) // 81 9
b := CompareLower4bits(uint32(x), uint32(y))
println(b) // false
// "Go"的類型被推斷為string;1的類型被推斷為int32。
doNothing("Go", 1)
}
func VersionString() string {
return "v1.0"
}
func doNothing(string, int32) {
}
從上例可以看出,一個(gè)函數(shù)的聲明可以出現(xiàn)在它的調(diào)用之前,也可以出現(xiàn)在它的調(diào)用之后。
一個(gè)函數(shù)調(diào)用可以被延遲執(zhí)行或者在另一個(gè)協(xié)程(goroutine,或稱綠色線程)中執(zhí)行。 后面的一文將對(duì)這兩個(gè)特性進(jìn)行詳解。
在Go中,當(dāng)一個(gè)函數(shù)調(diào)用返回后(比如執(zhí)行了一個(gè)return
語(yǔ)句或者函數(shù)中的最后一條語(yǔ)句執(zhí)行完畢), 此調(diào)用可能并未立即退出。一個(gè)函數(shù)調(diào)用從返回開(kāi)始到最終退出的階段稱為此函數(shù)調(diào)用的退出階段(exiting phase)。 函數(shù)調(diào)用的退出階段的意義將在講解延遲函數(shù)的時(shí)候體現(xiàn)出來(lái)。
函數(shù)調(diào)用的退出階段將在后面的一篇文章中詳細(xì)解釋。
Go支持匿名函數(shù)。定義一個(gè)匿名函數(shù)和聲明一個(gè)函數(shù)類似,但是一個(gè)匿名函數(shù)的定義中不包含函數(shù)名稱部分。 注意匿名函數(shù)定義不是一個(gè)函數(shù)聲明。
一個(gè)匿名函數(shù)在定義后可以被立即調(diào)用,比如:
package main
func main() {
// 這個(gè)匿名函數(shù)沒(méi)有輸入?yún)?shù),但有兩個(gè)返回結(jié)果。
x, y := func() (int, int) {
println("This function has no parameters.")
return 3, 4
}() // 一對(duì)小括號(hào)表示立即調(diào)用此函數(shù)。不需傳遞實(shí)參。
// 下面這些匿名函數(shù)沒(méi)有返回結(jié)果。
func(a, b int) {
println("a*a + b*b =", a*a + b*b) // a*a + b*b = 25
}(x, y) // 立即調(diào)用并傳遞兩個(gè)實(shí)參。
func(x int) {
// 形參x遮擋了外層聲明的變量x。
println("x*x + y*y =", x*x + y*y) // x*x + y*y = 32
}(y) // 將實(shí)參y傳遞給形參x。
func() {
println("x*x + y*y =", x*x + y*y) // x*x + y*y = 25
}() // 不需傳遞實(shí)參。
}
注意,上例中的最后一個(gè)匿名函數(shù)處于變量x
和y
的作用域內(nèi),所以在它的函數(shù)體內(nèi)可以直接使用這兩個(gè)變量。 這樣的函數(shù)稱為閉包(closure)。事實(shí)上,Go中的所有的自定義函數(shù)(包括聲明的函數(shù)和匿名函數(shù))都可以被視為閉包。 這就是為什么Go中的函數(shù)使用起來(lái)和動(dòng)態(tài)語(yǔ)言中的函數(shù)一樣靈活。
在后面的文章中,我們將了解到一個(gè)匿名函數(shù)可以被賦值給某個(gè)函數(shù)類型的值,從而我們不必在定義完此匿名函數(shù)后立即調(diào)用它,而是可以在以后合適的時(shí)候再調(diào)用它。
Go支持一些內(nèi)置函數(shù),比如前面的例子中已經(jīng)用到過(guò)多次的println
和print
函數(shù)。 我們可以不引入任何庫(kù)包(見(jiàn)下一篇文章)而調(diào)用一個(gè)內(nèi)置函數(shù)。
我們可以使用內(nèi)置函數(shù)real
和imag
來(lái)得到一個(gè)復(fù)數(shù)的實(shí)部和虛部(均為浮點(diǎn)數(shù)類型)。 注意,如果這兩個(gè)函數(shù)的任何一個(gè)調(diào)用的實(shí)參是一個(gè)常量,則此調(diào)用將在編譯時(shí)刻被估值,其返回結(jié)果也是一個(gè)常量。 此調(diào)用將被視為一個(gè)常量表達(dá)式。特別地,如果此實(shí)參是一個(gè)類型不確定值,則返回結(jié)果也是一個(gè)類型不確定值。
一個(gè)例子:
// c是一個(gè)類型不確定復(fù)數(shù)常量。
const c = complex(1.6, 3.3)
// 函數(shù)調(diào)用real(c)和imag(c)的結(jié)果都是類型
// 不確定浮點(diǎn)數(shù)值。在下面這句賦值中,它們都
// 被推斷為float32類型的值。
var a, b float32 = real(c), imag(c)
// 變量d的類型被推斷為內(nèi)置類型complex64。
// 函數(shù)調(diào)用real(d)和imag(d)的結(jié)果都是
// 類型為float32的類型確定值。
var d = complex(a, b)
// 變量e的類型被推斷為內(nèi)置類型complex128。
// 函數(shù)調(diào)用real(e)和imag(e)的結(jié)果都是
// 類型為float64的類型確定值。
var e = c
更多內(nèi)置類型將在很多后面其它文章中介紹。
本文是一篇Go函數(shù)入門的文章,很多其它函數(shù)相關(guān)的概念并未在此文中解釋。 今后,我們可以從函數(shù)類型和函數(shù)值一文中了解到和函數(shù)相關(guān)的其它概念。
更多建議: