函數也是一種類型,定義變量用var,定義常量用const,定義函數用func。
匿名函數
匿名函數,沒有名字,可以在定義的時候就直接運作了。
什麼場景下會使用匿名函數呢?也就是閉包。一個最常用的場景就是程式運作出現錯誤的時候,我們要去恢複,一般是通過關鍵字defer,我們要有一個程式邏輯,在某些特定場景下運作的,但是我沒有必要為整個邏輯定義函數,通常這樣就可以使用閉包來做。
閉包
閉包(Closure)是引用了自由變量的函數,自由變量将和函數一同存在,即使已經離開了創造它的環境,閉包複制的是原對象的指針。
通常情況下一個函數的内部變量,在函數退出之後,這個函數的局部變量都會标志以廢棄,都會被垃圾回收機制給回收,但是閉包就不一樣。
package main
import "fmt"
//閉包(Closure)是引用了自由變量的函數。自由變量将和函數一同存在,即使已經離開了創造它的環境。
func sub() func() {
i := 10
fmt.Printf("%p\n", &i)
b := func() {
fmt.Printf("i addr %p\n", &i) //閉包複制的是原對象的指針
i-- //b函數内部引用了變量i
fmt.Println(i)
}
return b //傳回了b函數,變量i和b函數将一起存在,即使已經離開函數sub()
}
// 外部引用函數參數局部變量
func add(base int) func(int) int {
return func(i int) int {
fmt.Printf("base addr %p\n", &base)
base += i
return base
}
}
func main() {
b := sub()
b()
b()
fmt.Println()
tmp1 := add(10)
fmt.Println(tmp1(1), tmp1(2)) //11,13
// 此時tmp1和tmp2不是一個實體了
tmp2 := add(100)
fmt.Println(tmp2(1), tmp2(2)) //101,103
}
函數也是一種資料類型
func function_arg1(f func(a, b int) int, b int) int { //f參數是一種函數類型
a := 2 * b
return f(a, b)
}
type foo func(a, b int) int //foo是一種函數類型
func function_arg2(f foo, b int) int { //參數類型看上去簡潔多了
a := 2 * b
return f(a, b)
}
type User struct {
Name string
bye foo //bye的類型是foo,而foo代表一種函數類型
hello func(name string) string //使用匿名函數來聲明struct字段的類型
}
ch := make(chan func(string) string, 10)
ch <- func(name string) string { //使用匿名函數
return "hello " + name
}
函數類型
函數也可以指派給變量,存儲在數組、切片、映射中,也可作為參數傳遞給函數或作為函數傳回值進行傳回。
func add(a,b int) int{
return a + b
}
func main() {
c := add
fmt.Printf("%v",reflect.TypeOf(c))
}
這個c到底是什麼東西?可以列印一下c的類型。
func(int, int) int 通過這個也可以看出來函數也是一種類型即函數類型,函數類型由參數類型和數量以及傳回值數量和類型組成的。
如果我想要定義一個函數類型的變量怎麼定義呢?
var f func(int,int) int
這樣就定義了一個函數類型,變量後面就是函數類型。
var f func(int,int) int
f =add
fmt.Println(f(2,3))
func add(a,b int) int{
return a + b
}
func mul(a,b int) int{
return a - b
}
func main() {
c := add
fmt.Printf("%v",reflect.TypeOf(c))
fs := []func(int,int) int{c,mul}
fmt.Println(fs)
for _,v := range fs{
fmt.Println(v(2,1))
}
}
可以看到函數也是一種類型。函數類型是一個可以将函數類型指派給一個變量。
通過函數類型也可以定義集合類型,如切片,map。
在 Go 語言中,函數類型、map 類型自身,以及切片隻支援與 nil 的比較,而不支援同類型兩個變量的比較。
s1 := make([]int, 1)
s2 := make([]int, 2)
f1 := func() {}
f2 := func() {}
m1 := make(map[int]string)
m2 := make(map[int]string)
println(s1 == s2) // 錯誤:invalid operation: s1 == s2 (slice can only be compared to nil)
println(f1 == f2) // 錯誤:invalid operation: f1 == f2 (func can only be compared to nil)
println(m1 == m2) // 錯誤:invalid operation: m1 == m2 (map can only be compared to nil)
回調函數(Callback)
聲明&調用參數類型為函數的函數
func print1(fmt1 func(string)string,args ...string){
for i,v := range args{
fmt.Println(i,fmt1(v))
}
}
func format(params string)string{
return "*" + params + "*"
}
func table(params string)string{
return "|" + params + "|"
}
func main() {
names := []string{"jack","lucas"}
print1(format,names...)
print1(table,names...)
}
定義的函數format可以作為參數傳入到print1函數裡面去。print1函數可以接收任意函數類型為func(string)string的函數。上面就是通過函數類将一個函數傳入到另外一個函數裡面去了。
函數類型在定義的時候,一定要定義出它的參數和傳回值。
函數傳回值為函數類型
func()是一個是沒有參數,沒有傳回值的一個函數
func sayHello() {
fmt.Println("hello")
}
func sayHi() {
fmt.Println("hi")
}
func genFunc() func(){
rand.Seed(time.Now().Unix())
if rand.Int() % 2 == 0 {
return sayHello
}else {
return sayHi
}
}
func main() {
a := genFunc()
a()
}
是以函數可以放到切片裡面,map裡面,可以指派給一個變量,可以作為參數傳遞,也可以作為函數值進行傳回。
func aFileds(split rune) bool {
return split == 'a'
}
c := strings.FieldsFunc("abcd",aFileds)
fmt.Println(c)