函数function
- Go函数不支持 嵌套、重载和默认参数
- 但支持以下特性:
无需声明原形、不定长变参、多返回值、命令返回值参数、匿名函数、闭包
- 定义函数使用关键字func,且大括号不能另起一行(所有有大括号的均遵循此原则)
- 函数也可以作为一种类型的使用,直接赋值给变量(匿名函数)
定义一个函数
格式:func name( 传入的变量1 类型,变量2 类型 ) [ 返回变量 类型,变量 类型 ]{ }
- 传入的变量可以没有,也可以使多个
- 当传入的变量类型相同时,可以全部省略只留最后一个
func a(a,b,c int) {}
- 返回值可以有多个,返回值类型相同,也可以只留最后一个,其中返回变量名称可以省略,省略的话,就需要每返回一个写一个变量的类型了,如果指定了返回某个局部变量,那么这个变量就已经被定义,那么在函数体内即可直接使用。
- 不指定返回变量名称,那么需要在函数尾部写入 return 变量1,变量2, 如果指定了返回的变量名,那么只需要写上return即可。
- 传入的参数个数,也可以不定(不定长变参),使用...来表示,在函数体内存储这些数据的类型为slice
func A(a ...int) -->...int必须放在最后
- 如果传入的值有1个string,有n个int,那么只能 fun A(b string, a ...int)这种形式接受
- 如果传入的参数是一个常规的int、string等类型的话,属于值传递(默认),即只是值得拷贝,而如果传递sllice,则是引用传递(其实slice也属于值拷贝,只不过,slice拷贝的是内存地址。而直接修改内存地址会影响源数据)
- 如果需要把int、string类型的值传入并修改,那么就需要把这些类型的变量的内存地址传入
package main import "fmt" func main() { a := 2 A(a) fmt.Println(a) } func A(a int) { i := 3 fmt.Println(i) } 结果: 3 2
把变量a的地址传入到函数中
package main import "fmt" func main() { a := 2 A(&a) //&a表示取a的内存地址 fmt.Println(a) } func A(a *int) { //定义指针类型,指向a的内存地址 *a = 3 //直接对内存地址进行赋值 fmt.Println(*a) } 结果: 3 3
参数传递(传值与传指针)
函数的参数传递分为两种,值传递,和引用传递,值传递指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。默认情况下,GO是值传递,在调用过程中不会影响到实际参数。
变量在内存中是存放于一定的地址上的,修改变量实际是修改变量地址处的内存。只有让局部函数知道参数的内存地址,才能修改变量的值。所以引用传递的时候需要把变量的内存地址传入到局部函数内(&a,&表示传递变量的内存地址),并将函数的参数类型调整为*int,即改为指针类型,才能在函数中修改变量的值,此时参数仍然是copy传递的,只不过copy的是一个指针。
函数作为其他变量的值
在Go语言中,一切皆类型,函数也可以被命名为变量,然后对变量进行函数的调用
package main import "fmt" func main() { a := A a() } func A() { fmt.Println("Func A") } 结果: Func A
匿名函数
在定义函数的时候不指定函数的名称,而是把函数直接赋值给某个变量的函数叫做匿名函数,调用这个函数的时候,直接使用变量的名称即可。(因为golang中的func不支持函数嵌套,使用匿名函数可以达到嵌套的效果) 匿名函数不能作为顶级函数(最外层)
package main import "fmt" func main() { a := func() { fmt.Println("Func") } a() }
闭包函数
所谓闭包函数就是将整个函数的定义一气呵成写好并赋值给一个变量。然后用这个变量名作为函数名去调用函数体。闭包函数对它外层的函数中的变量具有访问和修改的权限
package main import "fmt" func main() { f := closure(10) //调用闭包函数并传递10 fmt.Println(f(1)) //传递1给返回的函数,10+1=11 fmt.Println(f(2)) //传递2给返回的函数,10+2=12 } func closure(x int) func(int) int { //定义一个函数接收一个参数x,返回值也是一个函数接收一个变量y return func(y int) int { //返回一个int,函数接收一个参数,返回x+y的值 return x + y } } 结果: 11 12
defer
- 执行方式类似其他语言中的析构函数,在函数体执行结束后按照调用顺序的相反顺序逐个执行 (类似于栈的方式,先进后出,后进先出)
- 即使函数发生了严重错误也会执行
- 支持匿名函数的调用
- 常用语资源清理、文件关闭、解锁以及记录时间等操作
- 通过与匿名函数配合可在return之后修改函数计算结果
- 如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer时已经获得了拷贝,否则则是引用某个变量的地址
- Go没有异常机制,但有panic/recover模式来处理错误
- Panic可以再任何地方引发,但recover只有在defer调用的函数中有效
例子
package main import "fmt" func main() { fmt.Println("a") defer fmt.Println("1") defer fmt.Println("2") defer fmt.Println("3") } 结果: a 3 2 1 可以看到,在程序执行完毕后,defer是从最后一条语句开始执行的,证明了defer类似栈的运行方式
defer搭配循环的结果
package main import "fmt" func main() { for i := 0; i < 3; i++ { defer fmt.Println(i) } } 结果: 2 1 0
panic/recover实例
主要用来对程序的控制,并且仅针对函数级别的错误进行收集与回调。使程序能继续运行下去
package main import "fmt" func main() { A() B() C() } func A() { fmt.Println("A") } func B() { defer func() { //这里定义defer执行一个匿名函数,用于捕捉panic,这里如果把defer放在panic之后那么程序执行到panic后就会崩溃,那么defer就不会生效 if err := recover(); err != nil { //对引发的panic进行判断,由于手动触发了panic并发送了信息,那么用recover接收的异常返回值就要不为空,如果为nil表示没有异常,不为nil就表示异常了,这里对recover的返回值进行判断 } }() panic("this is painc")//发送异常,异常信息为”this is panic“ } func C() { fmt.Println("C") } 结果: A C 由于在函数B中定义了异常的recover机制,所以不会迫使程序退出,会继续执行
panic/recover 实例2
package main import "fmt" func main() { fmt.Println("1") fmt.Println("2") f := func() { defer func() { if err := recover(); err != nil { fmt.Println("panic") } }() panic("hello world") fmt.Println("7") } f() fmt.Println("8") } 结果: 1 2 panic //打印panic说明程序已经成功的捕捉到了异常 8
定义了匿名函数,并赋值给了变量f,匿名函数中的"7"不会打印,因为执行到panic已经崩溃了,而我们在匿名函数内定义了recover捕捉,所以匿名函数会被退出,然后继续执行其他程序
扩展:
在go语言中是没有异常捕获机制的,通过panic/recover来实现错误的捕获以及处理,利用go函数多返回值的概念,来进行check,如果err等于nil表示没有发生错误,当程序发生比较严重的错误,严重到无法弥补,比如索引越界,由于我们不能准确的判断元素的个数,所以recover也没有意义,所以说这个时候就是一个panic。如果知道可能会索引越界,并且希望程序能从错误中回复回来,那么这时候就需要用到recover,一旦调用recover,系统就会认为你需要从panic状态恢复过来,当程序进入panic状态,那么正常的程序将不会被执行,那么需要定义defer来执行recover(),defer不管在任何状态下,都会执行,只要把recover放在defer中,那么不管程序发生了怎样的错误,程序都会回复过来,需要注意的是defer类似栈的模式,后进先出。在可能发生panic的程序之前,预先定义defer,否则程序运行到painc后直接崩溃了,这个时候他只会去检查预先定义好的defer,而你放在panic之后,将会失效
例子1:
判断奇偶数
package main import "fmt" func main() { a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} fmt.Println("the slice is ", a) fmt.Println("the odd is ", odd(a)) fmt.Println("the even is ", even(a)) } func odd(num []int) []int { var result []int for _, value := range num { if value%2 == 0 { result = append(result, value) } } return result } func even(num []int) []int { var result []int for _, value := range num { if value%2 == 0 { continue } result = append(result, value) } return result }
思路:分别对切片进行过滤,偶数功能模块过滤一遍,挑出偶数,奇数功能模块过滤一遍,挑出奇数。缺点,模块复用 性差。
判断奇偶数:
package main import ( "fmt" ) type funcation func(int) bool func odd(num int) bool { if num%2 == 0 { return false } return true } func even(num int) bool { if num%2 == 0 { return true } return false } func filter(slice []int, f funcation) []int { var result []int for _, value := range slice { if f(value) { result = append(result, value) } } return result } func main() { a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println("the slice is ", a) fmt.Println("the odd is ", filter(a, odd)) fmt.Println("the even is ", filter(a, even)) }
思路:把判断奇偶的功能模块化,然后再通过一个模块调奇偶判断模块,然后再用main函数组织,(使用func类型,进行功能模块的传递),有点,结构性强,逻辑强。