
函数和闭包
1.函数执行流程
在程序编译完后,函数会以计算机指令方式存储在代码区,在定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,函数的参数以及局部变量会在调用时加载到内存中。
栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量。
栈被定义为一个特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将压入栈中的数据弹出(出栈,pop),但是栈容器必须遵循一条规则:先入栈的数据最后出栈(First In LAST
Out,FIFO).
在经典的操作系统中,栈总是向下增长的。压栈的操作使得栈顶的地址减小,弹出操作使得栈顶地址增大。
栈在程序运行中具有极其重要的地位。最重要的,栈保存一个函数调用所需要维护的信息,这通常被称为堆栈帧(Stack Frame)或者活动记录(Activate Record).一个函数调用过程所需要的信息一般包括以下几个方面:函数的返回地址;
函数的参数;
保存的上下文:包括在函数调用前后需要保持不变的寄存器;
- 函数执行流程
package main import "fmt" // 函数定义 func add(a int, b int) int { return a + b } func main() { a := 10 b := 20 //函数调用 value := add(a, b) fmt.Println(value) }
2.defer延迟调用
defer语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数。
defer作用:1.释放占用的资源
2.捕捉处理异常
3.输出日志
- 如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
package main import "fmt" func main() { //defer 延迟调用 将函数加载到内存中并没有执行 而是在函数出栈时在执行 defer fmt.Println("x86") defer fmt.Println("cloud") defer fmt.Println("com") defer fmt.Println("cn") }
3 recover错误拦截
运行时panic异常一旦被引发就会导致程序崩溃。
Go语言提供了专用于“拦截”运行时panic的内建函数“recover”。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。注意:recover只有在defer调用的函数中有效。
- 如果程序没有异常,不会打印错误信息
package main import "fmt" func Demo(i int){ //定义10个元素的数组 var arr [10]int //错误拦截要在产生错误前设置 defer func(){ //设置recover拦截错误信息 err := recover() //产生panic异常 打印错误信息 if err !=nil{ fmt.Println(err) } }() //根据函数参数为数组元素赋值 //如果i的值超过数组下标 会报错误:数组下标越界 arr[i]=10 } func main(){ Demo(10) //产生错误后继续运行 fmt.Println("程序继续运行") } #go run 14.go #runtime error: index out of range [10] with length 10 #程序继续运行
- 错误处理
编辑时异常
编译时异常
运行时异常
package main import "fmt" func demo(i int){ //recover错误拦截,出现在panic错误之前 defer func(){ err :=recover() if err != nil { fmt.Println(err) } }() //模拟数组下标越界panice错误 var arr [10]int arr[i] =123 fmt.Println(arr) } func main() { demo(10) fmt.Println("程序继续执行") } #C:\Users\vSphere\go\src\day03>go run 04.go #runtime error: index out of range [10] with length 10 #程序继续执行
- 一般性错误处理
package main import ( "errors" "fmt" ) func test(a int, b int) (v int, err error) { if b == 0 { err = errors.New("除数不能为0") fmt.Println(err) return } v = a / b fmt.Println(v) return } func main() { test(10, 0) } #C:\Users\vSphere\go\src\day03>go run 05.go #除数不能为0
4 闭包
Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
闭包是将函数内部和函数外部连接起来的桥梁
它的最大用处有两个:
1.可以读取函数内部的变量
2.变量的值始终保持在内存中以下实例中,创建了函数 getSequence() ,返回另外一个函数。该函数的目的是在闭包中递增 i变量,代码如下:
package main import "fmt" func getSequence() func() int{ i := 0 return func()int{ i += 1 return i } } func main(){ //netNumber 为一个函数,函数i为0 f1 :=getSequence() //调用f1函数 i变量自增1并返回 fmt.Println(f1()) fmt.Println(f1()) fmt.Println(f1()) //创建新的函数f2 f2 := getSequence() fmt.Println(f2()) fmt.Println(f2()) } #C:\Users\vSphere\go\src\day00>go run 15.go #1 #2 #3 #1 #2
- 注意:
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(privatevalue),这时一定要小心,不要随便改变父函数内部变量的值。
- 闭包 将匿名函数作为函数的返回值
package main import "fmt" //闭包 将匿名函数作为函数的返回值 func seq() func()int { i := 0 return func()int{ i++ return i } } func main(){ //可以读取函数内部的变量 //可以将函数始终加载到内存 f1 := seq() //进行调用 value :=f1() fmt.Println(value) value =f1() fmt.Println(value) value =f1() fmt.Println(value) f2 :=seq() value = f2() fmt.Println(value) value = f2() fmt.Println(value) } #C:\Users\vSphere\go\src\day03>go run 06.go #1 #2 #3 #1 #2
5.用闭包实现计数器
不同的闭包之间变量不会互相干扰,c1和c2两个计数器都是独立进行计数
package main import "fmt" type Tank interface { Walk() Fire() } func createCounter(initial int) func() int { if initial < 0 { initial = 0 } // 引用initial,创建一个闭包 return func() int { initial++ // 返回当前计数 return initial } } func main() { // 计数器1 c1 := createCounter(1) fmt.Println(c1()) // 2 fmt.Println(c1()) // 3 // 计数器2 c2 := createCounter(10) fmt.Println(c2()) // 11 fmt.Println(c1()) // 4 }.
6.匿名函数
package main import "fmt" func main(){ //匿名函数 func(a int,b int){ fmt.Println(a+b) }(10,20) } #C:\Users\vSphere\go\src\day03>go run 03.go #30定义函数类型变量,调用匿名函数
package main import "fmt" type FUNCTYPE func(int,int) func main(){ /* 定义函数类型变量 var f FUNCTYPE var g func(int,int) */ f := func(a int,b int){ fmt.Println(a+b) } fmt.Printf("%T\n",f) //函数在代码区的内存地址 f(10,20) fmt.Println(f) } #C:\Users\vSphere\go\src\day03>go run 03.go #func(int, int) #30 #0x49fbe0