這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
函數
本Go語言基礎入門內容均來自於《Go語言編程》,個人記錄,加深理解。
函式宣告
函數的基本組成為:關鍵字func、函數名、參數列表、傳回值、函數體和返回語句。
一般的: func 函數名 (傳入參數) (返回參數) {函數體}
如:
package mymathimport "errors"func Add(a int, b int) (ret int, err error) { if a < 0 || b < 0 { // 假設這個函數只支援兩個非負數位加法 err= errors.New("Should be non-negative numbers!") return } return a + b, nil // 支援多重傳回值}
如果參數列表中若干個相鄰的參數類型相同,可以省略前面變數的型別宣告,如:
func Add(a, b int</span>)(ret int, err error) { // ...}如果傳回值列表中多個傳回值的類型相同,也可以用同樣的方式合并。
如果函數只有一個傳回值,也可以這麼寫:
func Add(a, b int) int { // ...}
函數調用
1、匯入該函數所在包
2、如下調用
import "mymath"// 假設Add被放在一個叫 mymath的包中 // ...c := mymath.Add(1, 2)
值得注意的是:如果想在外部調用該包函數,所調用的函數名必須大寫。
Go語言有著這樣的規定:小寫字母開頭的函數只在本包內可見,大寫字母開頭的函數才能被其他包使用。
這個規則也適用於類型和變數的可見度。
不定參數
不定參數是指函數傳入的參數個數為不定數量。為了做到這點,首先需要將函數定義為接受不定參數類型:
func myfunc(args ... int) {//參數名 ...type for _, arg := range args { fmt.Println(arg) }}該函數可接收不定數量參數,如:
myfunc(2, 3, 4)myfunc(1, 3, 7, 13)
從內部實現機理上來說,類型...type本質上是一個數組切片,也就是[]type,這也是為什麼上面的參數args可以用for迴圈來獲得每個傳入的參數。
假如沒有...type這樣的文法糖,開發人員將不得不這麼寫:
func myfunc2(args [] int) { for _, arg := range args { fmt.Println(arg) }}從函數的實現角度來看,這沒有任何影響,該怎麼寫就怎麼寫。但從調用方來說,情形則完全不同:
myfunc2([] int{1, 3, 7, 13})你會發現,我們不得不加上[]int{} 來構造一個數組切片執行個體。
不定參數的傳遞
func myfunc(args ... int) {// 按原樣傳遞myfunc3(args...)// 傳遞片段,實際上任意的int slice都可以傳進去myfunc3(args[1:]...)}
任意類型的不定參數
之前的例子中將不定參數類型約束為 int ,如果你希望傳任意類型,可以指定類型為interface{} 。
下面是Go語言中fmt,Printf()函數原型:
func Printf(format string, args ... interface{}) { // ...}下面代碼可以讓我們清晰的知道傳入參數的類型:
package mainimport "fmt"func MyPrintf(args ... interface{}) { for _, arg := range args { switch arg. (type) { case int: fmt.Println(arg, "is an int value.") case string: fmt.Println(arg, "is a string value.") case int64: fmt.Println(arg, "is an int64 value.") default: fmt.Println(arg, "is an unknown type.") } }}func main() { var v1 int = 1 var v2 int64 = 234 var v3 string = "hello" var v4 float32 = 1.234 MyPrintf(v1, v2, v3, v4)}
該程式的輸出結果為:
1 is an int value.234 is an int64 value.hello is a string value.1.234 is an unknown type.
匿名函數和閉包
在Go裡面,函數可以像普通變數一樣被傳遞或使用,這與C語言的回呼函數比較類似。不同
的是, Go語言支援隨時在代碼裡定義匿名函數。
匿名函數由一個不帶函數名的函式宣告和函數體組成,如下所示:
func(a, b int, z float64) bool { return a*b <int(z)}
匿名函數可以直接賦值給一個變數或者直接執行:
f := func(x, y int) int { return x + y}func(ch chan int) { ch <- ACK} (reply_chan) // 花括弧後直接跟參數列表表示函數調用
閉包
閉包是可以包含自由(未綁定到特定對象)變數的代碼塊,這些變數不在這個代碼塊內或者
任何全域上下文中定義,而是在定義代碼塊的環境中定義。要執行的代碼塊(由於自由變數包含
在代碼塊中,所以這些自由變數以及它們引用的對象沒有被釋放)為自由變數提供綁定的計算環
境(範圍)。
這是官方的說法,我的理解是:簡單說就一個封閉的func,它可以訪問它外部聲明的變數,但外部存取不了它內部聲明的變數,除非它願意提供一個控制代碼
Go語言中的閉包同樣也會引用到函數外的變數。閉包的實現確保只要閉包還被使用,那麼
被閉包引用的變數會一直存在, 如:
package mainimport ( "fmt")func main() { var j int = 5 a := func()( func()) { var i int = 10 return func() { fmt.Printf("i, j: %d, %d\n", i, j) } }() a() j *= 2 a()}
上述例子的執行結果是:
i, j: 10, 5i, j: 10, 10
在上面的例子中,變數a指向的閉包函數引用了局部變數i和j , i的值被隔離,在閉包外不
能被修改,改變j 的值以後,再次調用a,發現結果是修改過的值。
在變數a指向的閉包函數中,只有內部的匿名函數才能訪問變數i,而無法通過其他途徑訪問
到,因此保證了i的安全性。