函数是组织好的、可重复使用的、用于执行指定任务的代码块。Go语言中支持函数、匿名函数和闭包。
在go中,函数也是一种特殊的数据类型func()
函数的定义方法如下:
func function_name( [parameter list] ) [return_types] {
函数体
}
这里举一个例子,交换两个变量的值:
func swap(i int, j int) (int, int) {
return j, i
}
调用函数:
func main() {
i, j := 1, 2
fmt.Println(i, j) // 1 2
i, j = swap(i, j)
fmt.Println(i, j) // 2 1
}
给函数传递的参数叫形参,形参拥有自己的内存空间,与函数外的变量的值一样,内存地址不一样,函数内外的变量是不会互相影响的。举例:
func main() {
i, j := 1, 2
fmt.Printf("init: i = %v, j = %v\r\n", i, j)
// init: i = 1, j = 2
func1(i, j)
fmt.Printf("outside func1: i = %v, j = %v\r\n", i, j)
// outside func1: i = 1, j = 2
}
func func1(i int, j int) {
i++
j++
fmt.Printf("inside func1: i = %v, j = %v\r\n", i, j)
// inside func1: i = 2, j = 3
}
如果想要在函数内改变函数外的变量,可以使用引用传递,也就是之前说过的&寻址,*找值
:
func main() {
i, j := 1, 2
fmt.Printf("init: i = %v, j = %v\r\n", i, j)
// init: i = 1, j = 2
func1(&i, &j)
fmt.Printf("outside func1: i = %v, j = %v\r\n", i, j)
// outside func1: i = 2, j = 3
}
func func1(i *int, j *int) {
*i++
*j++
fmt.Printf("inside func1: i = %v, j = %v\r\n", i, j)
// inside func1: i = 0xc00000a0a8, j = 0xc00000a0c0
fmt.Printf("inside func1: *i = %v, *j = %v\r\n", *i, *j)
// inside func1: *i = 2, *j = 3
}
对于引用传递,基于强类型语言的变量类型强一致性的优势,在引用传递上会比php严谨很多。引用传递应用还是非常广的,但是在php中因为简化了对引用传递的使用方法,操作不当会很容易导致变量污染相关问题。在强类型语言go中应该不会存在这个问题。
匿名函数指没有名字的函数;并没有牵扯到应用其他函数的变量问题。仅仅是没有名字。一般来说匿名函数会赋值给一个func()
类型的变量或者作为参数进行调用:
func main() {
i, j := 1, 2
fmt.Printf("i = %v, j = %v\r\n", i, j)
// i = 1, j = 2
swap := func(i int, j int) (int, int) {
return j, i
}
i, j = swap(i, j)
fmt.Printf("i = %v, j = %v\r\n", i, j)
// i = 2, j = 1
}
func main() {
callback(func() string {
return "call me"
})
}
func callback(f func() string) {
fmt.Println(f())
// call me
}
A函数中嵌套着B函数,B程序中有用到A的变量,当外部函数C调用函数A时,虽然A已经执行完毕,理论上函数执行完毕,它就要被弹出栈,但是由于B要用到A,所以A的变量被保存到内存中不被销毁,我们称函数B是闭包。 闭包可以使函数内的变量延迟销毁,并且可以保证函数内外的变量互相隔离:
func main() {
clock1 := clock(0)
fmt.Println(clock1())
// 1
fmt.Println(clock1())
// 2
fmt.Println(clock1())
// 3
clock2 := clock(100)
fmt.Println(clock2())
// 101
fmt.Println(clock2())
// 102
fmt.Println(clock1())
// 4
fmt.Println(clock2())
//103
}
func clock(start int) func() int {
return func() int {
start++
return start
}
}
与php,java一样,go中是支持可变参数的。当需要向函数内传递的参数数量未知的时候可以使用变量 ...类型
来传递多个参数。函数内会用一个数组接收他:
func main() {
list(1,5,2,7)
}
func list(i ...int) {
fmt.Println(reflect.TypeOf(i))
// []int
for k, v := range i {
fmt.Println(k,v)
}
// 0 1
// 1 5
// 2 2
// 3 7
}
Go语言中的defer
语句会将其后面跟随的语句进行延迟处理。在函数即将return
或结束时,将延迟处理的语句按defer
定义的逆序进行执行,也就是说,先被defer
的语句最后被执行,最后被defer
的语句,最先被执行。
func main() {
fmt.Println("main start")
defer fmt.Println("main d1")
defer fmt.Println("main d2")
defer fmt.Println("main d3")
list()
fmt.Println("main end")
}
func list() {
fmt.Println("list start")
i := 100
defer fmt.Println("d1",i)
i++
defer fmt.Println("d2",i)
i++
defer fmt.Println("d3",i)
fmt.Println("list end")
}
输出
main start
list start
list end
d3 102
d2 101
d1 100
main end
main d3
main d2
main d1
从上面的执行顺序可以看出来defer的执行顺序。另外可以看到,defer在执行之前,其值已经确定了
d3 102 d2 101 d1 100
这里举一个例子
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
fmt.Println("------")
}
输出
A 1 2 3
B 10 2 12
------
BB 10 12 22
AA 1 3 4
运行流程是这样的:
defer calc("AA", x, calc("A", x, y))
这里再注册时,需要确定 calc("A", x, y)
的值,所以会执行一次defer calc("BB", x, calc("B", x, y))
这里再注册时,需要确定 calc("B", x, y)
的值,所以会执行一次defer calc("BB", x, calc("B", x, y))
再执行 defer calc("AA", x, calc("A", x, y))
,其中defer内部注册时已经确定了 calc("B", x, y)
和 calc("A", x, y)
的值,不会重复执行在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:
我从其他地方找了一个例子:
func f1() int {
x := 5
defer func() {
x++
}()
return x // 5
}
func f2() (x int) {
defer func() {
x++
}()
return 5 // 6
}
func f3() (y int) {
x := 5
defer func() {
x++
}()
return x // 5
}
func f4() (x int) {
defer func(x int) {
x++
}(x)
return 5 // 5
}
func main() {
fmt.Println(f1()) // 5
fmt.Println(f2()) // 6
fmt.Println(f3()) // 5
fmt.Println(f4()) // 5
}
依次看一下上面的例子:
func f1() int {
x := 5 // 一个函数体内的局部变量x
fmt.Printf("%v\r\n",&x) // 0xc00000a0a8
defer func() {
x++ // 匿名函数没有传参,会使用上层f1()内的局部变量x
fmt.Printf("%v\r\n",&x) // 0xc00000a0a8
}()
return x // 看起来是x,其实是赋值给了返回的无名int类型变量,这里还是5
}
这里return x
的流程
defer
里的匿名函数,因为x没有座位参数传入,此时会向外找高层的变量x并++defer
没有对 return
产生任何影响func f2() (x int) {
defer func() {
x++ // 这里依然没有形参,会向上寻找高位的变量x,也就是返回值
}()
return 5
}
这里 return 5
的流程
x int
赋值,此时 x = 5
defer
的匿名函数,因为没有形参 x
此时会寻找高层的变量x,也就是刚才给返回值赋值的 x
并++x
也就是6func f3() (y int) {
x := 5
fmt.Printf("%v\r\n",&x) // 0xc00000a0e0
defer func() {
x++
fmt.Printf("%v\r\n",&x) // 0xc00000a0e0
}()
return x
}
这里 return x
的流程
y int
赋值,此时 y = 5
defer
的匿名函数,因为没有形参 x
此时会寻找高层的变量x,这里需要注意的是, f2()
中的 x
是返回值,而这里的 x
是和 f1()
一样的,高层的变量。y
也就是5func f4() (x int) {
defer func(x int) {
x++
}(x)
return 5
}
这里 return x
的流程
x int
赋值,此时 x = 5
defer
的匿名函数,因为有形参 x
,所以此时匿名函数内的 x
和函数外的 x
就不是一个内存地址了,相互是隔离状态的x
也就是5这里看了别人的例子,可以根据 f4()
进行进一步修改,加上引用操作来尝试一下
func f() (x int) {
defer func(y *int) {
*y++
}(&x)
return 5
}
func main() {
fmt.Println(f()) // 6
}
这里 return x
的流程
x int
赋值,此时 x = 5
defer
的匿名函数,因为有形参 y
,所以此时匿名函数内的 y
和函数外的 x
就不是一个内存地址了,相互是隔离状态的。但是!此时x传递的是返回值 x
的内存地址,也就是说,匿名函数内的 y
是一个指针,其保存的是返回值 x
的内存地址,这里使用 *y++
来通过地址寻找返回值 x
的值++x
也就是6活学活用,希望看到这里的你可以根据这些案例有自己的领悟。
入门思维导图下载(使用xmind打开):golang.xmind
本文为龚学鹏原创文章,转载无需和我联系,但请注明来自龚学鹏博客http://www.noobcoder.cn
最新评论