對Go中的函數特性作一個總結。懂則看,不懂則算。編程
func myfunc(int,int)
。type myfunc func(int,int) int
。
func(int,int) int
。因此,當函數ab()賦值給一個變量ref_ab
時ref_ab := ab
,不能再將其它函數類型的函數cd()賦值給變量ref_ab
。函數能夠有0或多個參數,0或多個返回值,參數和返回值都須要指定數據類型,返回值經過return關鍵字來指定。數據結構
return能夠有參數,也能夠沒有參數,這些返回值能夠有名稱,也能夠沒有名稱。Go中的函數能夠有多個返回值。閉包
return a+b
是正確的,但return c=a+b
是錯誤的例如:app
// 單個返回值 func func_a() int{ return a } // 只要命名了返回值,必須括號包圍 func func_b() (a int){ // 變量a int已存在,無需再次聲明 a = 10 return // 等價於:return a } // 多個返回值,且在return中指定返回的內容 func func_c() (int,int){ return a,b } // 多個返回值 func func_d() (a,b int){ return // 等價於:return a,b } // return覆蓋命名返回值 func func_e() (a,b int){ return x,y }
Go中常常會使用其中一個返回值做爲函數是否執行成功、是否有錯誤信息的判斷條件。例如return value,exists
、return value,ok
、return value,err
等。編程語言
當函數的返回值過多時,例若有4個以上的返回值,應該將這些返回值收集到容器中,而後以返回容器的方式去返回。例如,同類型的返回值能夠放進slice中,不一樣類型的返回值能夠放進map中。ide
但函數有多個返回值時,若是其中某個或某幾個返回值不想使用,能夠經過下劃線_
這個blank identifier來丟棄這些返回值。例以下面的func_a
函數兩個返回值,調用該函數時,丟棄了第二個返回值b,只保留了第一個返回值a賦值給了變量a
。函數
func func_a() (a,b int){ return } func main() { a,_ := func_a() }
Go中是經過傳值的方式傳參的,意味着傳遞給函數的是拷貝後的副本,因此函數內部訪問、修改的也是這個副本。性能
例如:ui
a,b := 10,20 min(a,b) func min(x,y int) int{}
上面調用min()時,是將a和b的值拷貝一份,而後將拷貝的副本賦值給變量x,y的,因此min()函數內部,訪問、修改的一直是a、b的副本,和原始的數據對象a、b沒有任何關係。lua
若是想要修改外部數據(即上面的a、b),須要傳遞指針。
例如,下面兩個函數,func_value()
是傳值函數,func_ptr()
是傳指針函數,它們都修改同一個變量的值。
package main import "fmt" func main() { a := 10 func_value(a) fmt.Println(a) // 輸出的值仍然是10 b := &a func_ptr(b) fmt.Println(*b) // 輸出修改後的值:11 } func func_value(x int) int{ x = x + 1 return x } func func_ptr(x *int) int{ *x = *x + 1 return *x }
map、slice、interface、channel這些數據類型自己就是指針類型的,因此就算是拷貝傳值也是拷貝的指針,拷貝後的參數仍然指向底層數據結構,因此修改它們可能會影響外部數據結構的值。
另外注意,賦值操做b = a+1
這種類型的賦值也是拷貝賦值。換句話說,如今底層已經有兩個數據對象,一個是a,一個是b。但a = a+1
這種類型的賦值雖然本質上是拷貝賦值,但由於a的指針指向特性,使得結果上看是原地修改數據對象而非生成新數據對象。
有時候參數過多,或者想要讓函數處理任意多個的參數,能夠在函數定義語句的參數部分使用ARGS...TYPE
的方式。這時會將...
表明的參數所有保存到一個名爲ARGS的slice中,注意這些參數的數據類型都是TYPE。
...
在Go中稱爲variadic,在使用...
的時候(如傳遞、賦值),能夠將它看做是一個slice,下面的幾個例子能夠說明它的用法。
例如:func myfunc(a,b int,args...int) int {}
。除了前兩個參數a和b外,其它的參數全都保存到名爲args的slice中,且這些參數全都是int類型。因此,在函數內部就已經有了一個args = []int{....}
的數據結構。
例如,下面的例子中,min()函數要從全部參數中找出最小的值。爲了實驗效果,特意將前兩個參數a和b獨立到slice的外面。min()函數內部同時會輸出保存到args中的參數值。
package main import "fmt" func main() { a,b,c,d,e,f := 10,20,30,40,50,60 fmt.Println(min(a,b,c,d,e,f)) } func min(a,b int,args...int) int{ // 輸出args中保存的參數 // 等價於 args := []int{30,40,50,60} for index,value := range args { fmt.Printf("%s%d%s %d\n","args[",index,"]:",value) } // 取出a、b中較小者 min_value := a if a>b { min_value = b } // 取出全部參數中最小值 for _,value := range args{ if min_value > value { min_value = value } } return min_value }
但上面代碼中調用函數時傳遞參數的方式顯然比較笨重。若是要傳遞的參數過多(要比較的值不少),能夠先將這些參數保存到一個slice中,再傳遞slice給min()函數。傳遞slice給函數的時候,使用SLICE...
的方式便可。
func main() { s1 := []int{30,40,50,60,70} fmt.Println(min(10,20,s1...)) }
上面的賦值方式已經能說明能使用slice來理解...
的行爲。另外,下面的例子也能很好的解釋:
// 聲明f1() func f1(s...string){ // 調用f2()和f3() f2(s...) f3(s) } // 聲明f2()和f3() func f2(s...string){} func f3(s []string){}
若是各參數的類型不一樣,又想定義成變長參數,該如何?第一種方式,可使用struct,第二種方式可使用接口。接口暫且不說,若是使用struct,大概以下:
type args struct { arg1 string arg2 int arg3 type3 }
而後能夠將args傳遞給函數:f(a,b int,args{})
,若是args結構中須要初始化,則f(a,b int,args{arg1:"hello",arg2:22})
。
在builtin包中有一些內置函數,這些內置函數額外的導入包就能使用。
有如下內置函數:
$ go doc builtin | grep func func close(c chan<- Type) func delete(m map[Type]Type1, key Type) func panic(v interface{}) func print(args ...Type) func println(args ...Type) func recover() interface{} func complex(r, i FloatType) ComplexType func imag(c ComplexType) FloatType func real(c ComplexType) FloatType func append(slice []Type, elems ...Type) []Type func make(t Type, size ...IntegerType) Type func new(Type) *Type func cap(v Type) int func copy(dst, src []Type) int func len(v Type) int
close
用於關閉channeldelete
用於刪除map中的元素copy
用於拷貝sliceappend
用於追加slicecap
用於獲取slice的容量len
用於獲取
print
和println
:底層的輸出函數,用來調試用。在實際程序中,應該使用fmt中的print類函數complex
、imag
、real
:操做複數(虛數)panic
和recover
:處理錯誤new
和make
:分配內存並初始化
v := new(int)
注意,地址和指針是不一樣的。地址就是數據對象在內存中的地址,指針則是佔用一個機器字長(32位機器是4字節,64位機器是8字節)的數據,這個數據中存儲的是它所指向數據對象的地址。
a -> AAAA b -> Pointer -> BBBB
new()和make()構造數據對象賦值給變量的都是指向數據對象的指針。
函數內部調用函數自身的函數稱爲遞歸函數。
使用遞歸函數最重要的三點:
例如,遞歸最多見的示例,求一個給定整數的階乘。由於階乘的公式爲n*(n-1)*...*3*2*1
,它在參數爲1的時候退出函數,也就是說它的遞歸基點是1,因此對是否爲基點進行判斷,而後再寫遞歸表達式。
package main import "fmt" func main() { fmt.Println(a(5)) } func a(n int) int{ // 判斷退出點 if n == 1 { return 1 } // 遞歸表達式 return n * a(n-1) }
它的調用過程大概是這樣的:
再好比斐波那契數列,它的計算公式爲f(n)=f(n-1)+f(n-2)
且f(2)=f(1)=1
。它在參數爲1和2的時候退出函數,因此它的退出點爲1和2。
package main import "fmt" func main() { fmt.Println(f(3)) } func f(n int) int{ // 退出點判斷 if n == 1 || n == 2 { return 1 } // 遞歸表達式 return f(n-1)+f(n-2) }
如何遞歸一個目錄?它的遞歸基點是文件,只要是文件就返回,只要是目錄就進入。因此,僞代碼以下:
func recur(dir FILE) FILE{ // 退出點判斷 if (dir is a file){ return dir } // 當前目錄的文件列表 file_slice := filelist() // 遍歷全部文件 for _,file := range file_slice { return recur(file) } }
匿名函數是沒有名稱的函數。通常匿名函數嵌套在函數內部,或者賦值給一個變量,或者做爲一個表達式。
定義的方式:
// 聲明匿名函數 func(args){ ...CODE... } // 聲明匿名函數並直接執行 func(args){ ...CODE... }(parameters)
下面的示例中,先定義了匿名函數,將其賦值給了一個變量,而後在須要的地方再去調用執行它。
package main import "fmt" func main() { // 匿名函數賦值給變量 a := func() { fmt.Println("hello world") } // 調用匿名函數 a() fmt.Printf("%T\n", a) // a的type類型:func() fmt.Println(a) // 函數的地址 }
若是給匿名函數的定義語句後面加上()
,表示聲明這個匿名函數的同時並執行:
func main() { msg := "Hello World" func(m string) { fmt.Println(m) }(msg) }
其中func(c string)
表示匿名函數的參數,func(m string){}(msg)
的msg
表示傳遞msg變量給匿名函數,並執行。
能夠將func做爲一種type,之後能夠直接使用這個type來定義函數。
package main import "fmt" type add func(a,b int) int func main() { var a add = func(a,b int) int{ return a+b } s := a(3,5) fmt.Println(s) }