命令记不住

登陆

函数和闭包和 recover 错误拦截

函数和闭包

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
推荐阅读