Go語言 函數(shù)聲明和調(diào)用

2023-02-16 17:36 更新

除了上一篇文章介紹的運算符操作,函數(shù)操作是另一種在編程中常用的操作。 函數(shù)操作常被稱為函數(shù)調(diào)用。此篇文章將介紹如何在Go中聲明和調(diào)用函數(shù)。

函數(shù)聲明

讓我們來看一個函數(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)一個函數(shù)聲明從左到右由以下部分組成:

  1. 第一部分是func關鍵字。
  2. 第二部分是函數(shù)名稱。函數(shù)名稱必須是一個標識符。 這里的函數(shù)名稱是SquareOfSumAndDiff。
  3. 第三部分是輸入?yún)?shù)列表。輸入?yún)?shù)聲明列表必須用一對小括號括起來。 輸入?yún)?shù)聲明有時也稱為形參聲明(對應后面將介紹的函數(shù)調(diào)用中的實參)。
  4. 第四部分是輸出結果聲明列表。在Go中,一個函數(shù)可以有多個返回值。 比如上面這個例子就有兩個返回值。 當一個函數(shù)的輸出結果聲明列表為空或者只包含一個匿名結果聲明時,此列表可以不用一對小括號括起來(見下面的示例);否則,小括號是必需的。
  5. 最后一部分是函數(shù)體。函數(shù)體必須用一對大括號括起來。 一對大括號和它其間的代碼形成了一個顯式代碼塊。 在一個函數(shù)體內(nèi),return關鍵字可以用來結束此函數(shù)的正常向前執(zhí)行流程并進入此函數(shù)的退出階段(詳見下下節(jié)中的解釋)。

在上面的例子中,每個函數(shù)參數(shù)和結果聲明都由一個名字和一個類型組成(變量名字在前,類型在后)。 我們可以把一個參數(shù)和結果聲明看作是一個省略了var關鍵字的標準變量聲明。 上面這個函數(shù)有兩個輸入?yún)?shù)(ab)以及兩個輸出結果(xy)。 它們的類型都是int64。

輸出結果聲明列表中的所有聲明中的結果名稱可以(而且必須)同時出現(xiàn)或者同時省略。 這兩種方式在實踐中都使用得很廣泛。 如果一個返回結果聲明中的結果名稱沒有省略,則這個返回結果稱為具名返回結果。否則稱為匿名返回結果。

如果一個函數(shù)聲明的所有返回結果均為匿名的,則在此函數(shù)體內(nèi)的返回語句return關鍵字后必須跟隨一系列返回值,這些返回值和此函數(shù)的各個返回結果聲明一一對應。比如,下面這個函數(shù)聲明和上例中的函數(shù)聲明是等價的。

func SquaresOfSumAndDiff(a int64, b int64) (int64, int64) {
	return (a+b) * (a+b), (a-b) * (a-b)
}

事實上,如果一個函數(shù)聲明中的所有輸入?yún)?shù)在此函數(shù)體內(nèi)都沒有被使用過,則它們也可以都同時是匿名的。 不過這種情形在實際編程中很少見。

盡管一個函數(shù)聲明中的輸入?yún)?shù)和返回結果看上去是聲明在這個函數(shù)體的外部,但是在此函數(shù)體內(nèi),這些輸入?yún)?shù)和輸出結果被當作局部變量來使用。 但輸入?yún)?shù)和輸出結果和普通局部變量還是有一點區(qū)別的:目前的主流Go編譯器不允許一個名稱不為_的普通局部變量被聲明而不有效使用。

Go不支持輸入?yún)?shù)默認值。每個返回結果的默認值是它的類型的零值。 比如,下面的函數(shù)在被調(diào)用時將打印出(和返回)0 false。

func f() (x int, y bool) {
	println(x, y) // 0 false
	return
}

和普通的變量聲明一樣,如果若干連續(xù)的輸入?yún)?shù)或者返回結果的類型相同,則在它們的聲明中可以共用一個類型。 比如,上面的兩個SquaresOfSumAndDiff函數(shù)聲明和下面這個是完全等價的。

func SquaresOfSumAndDiff(a, b int64) (s, d int64) {
	return (a+b) * (a+b), (a-b) * (a-b)
	// 上面這行等價于下面這行:
	// s = (a+b) * (a+b); d = (a-b) * (a-b); return
}

注意,盡管在上面這個函數(shù)聲明的返回結果都是具名的,函數(shù)體內(nèi)的return關鍵字后仍然可以跟返回值。

如果一個函數(shù)聲明只包含一個返回結果,并且此返回結果是匿名的,則此函數(shù)聲明中的返回結果部分不必用小括號括起來。 如果一個函數(shù)聲明的返回結果列表為空,則此函數(shù)聲明中的返回結果部分可以完全被省略掉。 一個函數(shù)聲明的輸入?yún)?shù)列表部分總不能省略掉,即使此函數(shù)聲明的輸入?yún)?shù)列表為空。

下面是更多函數(shù)聲明的例子:

func CompareLower4bits(m, n uint32) (r bool) {
	// 下面這兩行等價于:return m&0xFF > n&0xff
	r = m&0xF > n&0xf
	return
}

// 此函數(shù)沒有輸入?yún)?shù)。它的結果聲明列表只包含一個
// 匿名結果聲明,因此它不必用()括起來。
func VersionString() string {
	return "go1.0"
}

// 此函數(shù)沒有返回結果。它的所有輸入?yún)?shù)都是匿名的。
// 它的結果聲明列表為空,因此可以被省略掉。
func doNothing(string, int) {
}

在前面的《Go語言101》文章中,我們已經(jīng)知道一個程序的main入口函數(shù)必須不帶任何輸入?yún)?shù)和返回結果。

注意,在Go中,所有函數(shù)都必須直接聲明在包級代碼塊中。 或者說,任何一個函數(shù)都不能被聲明在另一個函數(shù)體內(nèi)。 雖然匿名函數(shù)(將在下面的某節(jié)中介紹)可以定義在函數(shù)體內(nèi),但匿名函數(shù)定義不屬于函數(shù)聲明。

函數(shù)調(diào)用

一個聲明的函數(shù)可以通過它的名稱和一個實參列表來調(diào)用之。 一個實參列表必須用小括號括起來。 實參列表中的每一個單值實參對應著(或稱被傳遞給了)一個形參。

注意:函數(shù)傳參也屬于賦值操作。在傳參中,各個實參被賦值給各個對應形參。

一個實參值的類型不必一定要和其對應的形參聲明的類型一樣。 但如果一個實參值的類型和其對應的形參聲明的類型不一致,則此實參必須能夠隱式轉換到其對應的形參的類型。

如果一個函數(shù)帶有返回值,則它的一個調(diào)用被視為一個表達式。如果此函數(shù)返回多個結果,則它的每個調(diào)用被視為一個多值表達式。 一個多值表達式可以被同時賦值給多個目標值(數(shù)量必須匹配,各個輸出結果被賦值給相對應的目標值)。

下面這個例子完整地展示了如何調(diào)用幾個已經(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
}

// 使用一個函數(shù)調(diào)用的返回結果來初始化一個包級變量。
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) {
}

從上例可以看出,一個函數(shù)的聲明可以出現(xiàn)在它的調(diào)用之前,也可以出現(xiàn)在它的調(diào)用之后。

一個函數(shù)調(diào)用可以被延遲執(zhí)行或者在另一個協(xié)程(goroutine,或稱綠色線程)中執(zhí)行。 后面的一文將對這兩個特性進行詳解。

函數(shù)調(diào)用的退出階段

在Go中,當一個函數(shù)調(diào)用返回后(比如執(zhí)行了一個return語句或者函數(shù)中的最后一條語句執(zhí)行完畢), 此調(diào)用可能并未立即退出。一個函數(shù)調(diào)用從返回開始到最終退出的階段稱為此函數(shù)調(diào)用的退出階段(exiting phase)。 函數(shù)調(diào)用的退出階段的意義將在講解延遲函數(shù)的時候體現(xiàn)出來。

函數(shù)調(diào)用的退出階段將在后面的一篇文章中詳細解釋。

匿名函數(shù)

Go支持匿名函數(shù)。定義一個匿名函數(shù)和聲明一個函數(shù)類似,但是一個匿名函數(shù)的定義中不包含函數(shù)名稱部分。 注意匿名函數(shù)定義不是一個函數(shù)聲明。

一個匿名函數(shù)在定義后可以被立即調(diào)用,比如:

package main

func main() {
	// 這個匿名函數(shù)沒有輸入?yún)?shù),但有兩個返回結果。
	x, y := func() (int, int) {
		println("This function has no parameters.")
		return 3, 4
	}() // 一對小括號表示立即調(diào)用此函數(shù)。不需傳遞實參。

	// 下面這些匿名函數(shù)沒有返回結果。

	func(a, b int) {
		println("a*a + b*b =", a*a + b*b) // a*a + b*b = 25
	}(x, y) // 立即調(diào)用并傳遞兩個實參。

	func(x int) {
		// 形參x遮擋了外層聲明的變量x。
		println("x*x + y*y =", x*x + y*y) // x*x + y*y = 32
	}(y) // 將實參y傳遞給形參x。

	func() {
		println("x*x + y*y =", x*x + y*y) // x*x + y*y = 25
	}() // 不需傳遞實參。
}

注意,上例中的最后一個匿名函數(shù)處于變量xy的作用域內(nèi),所以在它的函數(shù)體內(nèi)可以直接使用這兩個變量。 這樣的函數(shù)稱為閉包(closure)。事實上,Go中的所有的自定義函數(shù)(包括聲明的函數(shù)和匿名函數(shù))都可以被視為閉包。 這就是為什么Go中的函數(shù)使用起來和動態(tài)語言中的函數(shù)一樣靈活。

在后面的文章中,我們將了解到一個匿名函數(shù)可以被賦值給某個函數(shù)類型的值,從而我們不必在定義完此匿名函數(shù)后立即調(diào)用它,而是可以在以后合適的時候再調(diào)用它。

內(nèi)置函數(shù)

Go支持一些內(nèi)置函數(shù),比如前面的例子中已經(jīng)用到過多次的printlnprint函數(shù)。 我們可以不引入任何庫包(見下一篇文章)而調(diào)用一個內(nèi)置函數(shù)。

我們可以使用內(nèi)置函數(shù)realimag來得到一個復數(shù)的實部和虛部(均為浮點數(shù)類型)。 注意,如果這兩個函數(shù)的任何一個調(diào)用的實參是一個常量,則此調(diào)用將在編譯時刻被估值,其返回結果也是一個常量。 此調(diào)用將被視為一個常量表達式。特別地,如果此實參是一個類型不確定值,則返回結果也是一個類型不確定值。

一個例子:

// c是一個類型不確定復數(shù)常量。
const c = complex(1.6, 3.3)

// 函數(shù)調(diào)用real(c)和imag(c)的結果都是類型
// 不確定浮點數(shù)值。在下面這句賦值中,它們都
// 被推斷為float32類型的值。
var a, b float32 = real(c), imag(c)

// 變量d的類型被推斷為內(nèi)置類型complex64。
// 函數(shù)調(diào)用real(d)和imag(d)的結果都是
// 類型為float32的類型確定值。
var d = complex(a, b)

// 變量e的類型被推斷為內(nèi)置類型complex128。
// 函數(shù)調(diào)用real(e)和imag(e)的結果都是
// 類型為float64的類型確定值。
var e = c

更多內(nèi)置類型將在很多后面其它文章中介紹。

更多函數(shù)相關的概念

本文是一篇Go函數(shù)入門的文章,很多其它函數(shù)相關的概念并未在此文中解釋。 今后,我們可以從函數(shù)類型和函數(shù)值一文中了解到和函數(shù)相關的其它概念。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號