7、函数与异常

一、函数的概念

  • 函数的基本形式
//函数定义。a,b是形参
func argf(a int, b int) {
    
     
	a = a + b 
}
var x, y int = 3, 6
argf(x, y) //函数调用。x,y是实参
  • 函数参数
func arg2(a, b int) {
    
     //参数类型相同时可以只写一次
	a = a + b
	//不写return时,默认执行完最后一行代码函数返回
}

func arg3(a, b *int) {
    
     //如果想通过函数修改实参,就需要指针类型
	*a = *a + *b
	*b = 888
}

func no_arg() {
    
     //函数可以没有参数,也没有返回值
	fmt.Println("欢迎开启Golang之旅")
}

func return1(a, b int) int {
    
     //函数需要返回一个int型数据
	a = a + b
	c := a //声明并初始化一个变量c
	return c
}

func return2(a, b int) (c int) {
    
     //返回变量c已经声明好了
	a = a + b
	c = a  //直接使用c
	return //由于函数要求有返回值,即使给c赋过值了,也需要显式写return
}

func return3() (int, int) {
    
     //可以没有形参,可以返回多个参数
	now := time.Now()
	return now.Hour(), now.Minute()
}
  • slice、map、channel作为函数参数
    • slice、map、channel都是引用类型
    • 它们作为函数参数时其实跟普通struct没什么区别
    • 都是对struct内部的各个字段做一次拷贝传到函数内部
func slice_arg_1(arr []int) {
    
     //slice作为参数,实际上是把slice的arrayPointer、len、cap拷贝了一份传进来
	arr[0] = 1           //修改底层数据里的首元素
	arr = append(arr, 1) //arr的len和cap发生了变化,不会影响实参
}

func main() {
    
    
	arr := []int{
    
    8}
	slice_arg_1(arr)
	fmt.Println(arr[0])   //1
	fmt.Println(len(arr)) //1
}
  • 不定长参数:不定长参数实际上是slice类型
//不定长参数
func variable_ength_arg(a int, other ...int) int {
    
     //调用该函数时,other可以对应0个参数也可以对应多个参数
	sum := a
	//不定长参数实际上是slice类型
	for _, ele := range other {
    
    
		sum += ele
	}
	if len(other) > 0 {
    
    
		fmt.Printf("first ele %d len %d cap %d\n", other[0], len(other), cap(other))
	} else {
    
    
		fmt.Printf("len %d cap %d\n", len(other), cap(other))
	}
	return sum
}

func main() {
    
    
	variable_ength_arg(1, 2, 3, 4, 5) //first ele 2 len 4 cap 4
}
  • append函数接收的就是不定长参数func append(slice []Type, elems ...Type) []Type
func main() {
    
    
	arr := []int{
    
    4, 5, 6}
	//append函数接收的就是不定长参数
	arr = append(arr, 1, 2, 3)
	arr = append(arr, 7)
	fmt.Printf("new arr %v\n", arr) //new arr [4 5 6 1 2 3 7]
}
  • **递归函数 **:函数自己调用自己
// 斐波那契数列前10个数为:0,1,1,2,3,5,8,13,21,34
func Fibonacci(n int) int {
    
    
	if n == 0 || n == 1 {
    
    
		return n //凡是递归,一定要有终止条件,否则会进入无限循环
	}
	return Fibonacci(n-1) + Fibonacci(n-2) //递归调用函数自身
}

二、匿名函数

  • 函数也是一种数据类型
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字段的类型
}

func main() {
    
    

	ch := make(chan func(string) string, 10)
	ch <- func(name string) string {
    
     //使用匿名函数
		return "hello " + name
	}
}
  • 实例1
var sum = func(a, b int) int {
    
    
	return a + b
}

type add func(a, b int) int

func op(f add, a int) int {
    
    
	b := 2 * a
	return f(a, b)
}

func main() {
    
    
	fmt.Println(sum(1, 2))  //3
	fmt.Println(op(sum, 2)) //6
}
  • 实例2
type User struct {
    
    
	Name string
	Rec  func(int, int) int
}

func add(a, b int) int {
    
    
	return a + b
}

func sub(a, b int) int {
    
    
	return a - b
}

func main() {
    
    
	user := User{
    
    Name: "hello", Rec: add}
	fmt.Println(user.Rec(1, 2)) // 3
	user.Rec = sub
	fmt.Println(user.Rec(1, 2)) // -1

}

三、闭包

  • 闭包概念
    • 闭包(Closure)是引用了自由变量的函数
    • 自由变量将和函数一同存在,即使已经离开了创造它的环境
    • 闭包复制的是原对象的指针
func sub() func() {
    
    
	i := 10
	fmt.Printf("%p\n", &i)

	b := func() {
    
    
		fmt.Printf("i addr %p i = ", &i)
		i--
		fmt.Println(i)
	}
	return b
}

func main() {
    
    
	//对于sub来讲,i是局部变量,正常情况下离开了sub就被释放了
	//但是因为闭包函数b的存在,接下来的2个f()调用仍然是对原局部变量i的地址进行操作
	f := sub() //0xc000122058
	f()        //i addr 0xc0000180a8 i = 9
	f()        //i addr 0xc0000180a8 i = 8

}
  • 闭包案例
    • tmp1与传入base=10一同存在
    • tmp2(这里base被初始化为10)与传入base=10一同存在
    • 可以看到tmp1和tmp2的函数地址是不一样的
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() {
    
    
	tmp1 := add(10)
	fmt.Println(tmp1(1), tmp1(2))

	// base addr 0xc0000a6058
	// base addr 0xc0000a6058
	// 11 13

	tmp2 := add(10)
	fmt.Println(tmp2(1), tmp2(2))

	// base addr 0xc0000a6090
	// base addr 0xc0000a6090
	// 11 13
}

四、defer

  • defer概念

    • defer用于注册一个延迟调用(在函数返回之前调用)
    • defer典型的应用场景是释放资源,比如关闭文件句柄,释放数据库连接等
  • 如果同一个函数有多个defer,则后注册的先执行

func basic() {
    
    
	fmt.Println("A")
	defer fmt.Println("B")
	//如果同一个函数有多个defer,则后注册的先执行
	defer fmt.Println("C")
	fmt.Println("D")
}

func main() {
    
    
	basic() // A D C B
}
  • defer后接func和接表达式
    • defer后接func的时候,不会拷贝变量
    • defer后面不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算
func defer_exe_time() (i int) {
    
    
	i = 9
	defer func() {
    
    
		fmt.Printf("defer_B i=%d\n", i) //打印5而非9
	}()
	defer func(i int) {
    
    
		fmt.Printf("defer_C i=%d\n", i)
	}(i) //i=9,在注册的时候这里i已经拷贝了9
	defer fmt.Printf("defer_A i=%d\n", i) //变量在注册defer时被拷贝或计算
	return 5
}

func main() {
    
    
	defer_exe_time()
	// defer_A i=9
	// defer_C i=9
	// defer_B i=5
}
  • defer-panic:func内部如果发生panic,会把panic暂时搁置,当把其他defer执行完之后再来执行这个panic
func defer_panic() {
    
    
	defer fmt.Println("11111111")
	n := 0
	defer func() {
    
    
		fmt.Println(2 / n)
		defer fmt.Println("2222222")
	}()
	defer fmt.Println("33333333")
}

func main() {
    
    
	defer_panic()
}

//33333333
//11111111
//发生异常: panic
//"runtime error: integer divide by zero"
//Stack:
//	3  0x0000000000448726 in main.defer_panic.func1
//	    at c:/develop_project/go_project/proj1/main.go:9
//	5  0x0000000000448465 in main.defer_panic
//	    at c:/develop_project/go_project/proj1/main.go:13
//	6  0x00000000004488d7 in main.main
//	    at c:/develop_project/go_project/proj1/main.go:16

五、异常机制

  • error:go语言没有try catch,它提倡返回error
func divide(a, b int) (int, error) {
    
    
	if b == 0 {
    
    
		return -1, errors.New("divide by zero")
	}
	return a / b, nil
}

func main() {
    
    
	if res, err := divide(3, 0); err != nil {
    
    
		fmt.Println(err.Error()) //divide by zero
	} else {
    
    
		fmt.Println(res)
	}
}
  • 自定义error
type PathError struct {
    
    
	path       string
	op         string
	createTime string
	message    string
}

func NewPathError(path, op, message string) PathError {
    
    
	return PathError{
    
    
		path:       path,
		op:         op,
		createTime: time.Now().Format("2006-01-02"), //yyyy-MM-dd
		message:    message,
	}
}

func (e PathError) Error() string {
    
    
	return e.createTime + ":" + e.op + " " + e.path + " " + e.message
}

func deletePath(path string) error {
    
    
	return NewPathError(path, "delete", "path not exits")
}
  • 何时会发生panic
    • 运行时错误会导致panic,比如数组越界、除0
    • 程序主动调用panic(error)
  • panic会执行什么
    • 逆序执行当前goroutine的defer链(recover从这里介入)
    • 打印错误信息和调用堆栈
    • 调用exit(2)结束整个进程
  • recover:recover会阻断panic的执行
func soo() {
    
    
	defer func() {
    
    
		if err := recover(); err != nil {
    
    
			fmt.Println("发生了panic,不让程序退出")
		}
	}()
	panic(errors.New("这是错误信息"))
}

func main() {
    
    
	soo()
	fmt.Println("333333333")
}

猜你喜欢

转载自blog.csdn.net/qq23001186/article/details/129103019