go 函数 —— 摘自go语言学习笔记

  1. 函数是代码复用和测试的基本单元
  2. 函数属于第一类对象,具备相同签名(参数及返回值列表)的视作同一类型;第一类对象指可在运行期创建,可用作函数参数或返回值,可存入变量的实体。最常见的用法就是匿名函数
  3. 函数只能判断其是否为nil,不支持其他比较操作
  4. 从函数返回局部变量指针是安全的,编译器会通过逃逸分析来决定是否在堆上分配内存
  5. 定义函数类型
    type FormatFunc func(string, ...interface{}) (string, error)
    // 如不使用命名类型,这个参数签名会长到没法看
    func format(f FormatFunc, s string, a ...interface{}) (string, error) {
    	return f(s, a...)
    }
    
  6. 不管是指针、引用类型,还是其他类型参数,都是值拷贝传递,区别无非是拷贝目标对象,还是拷贝指针而已。在函数调用前,会为形参和返回值分配内存空间,并将实参拷贝到形参内存
  7. 表面上看,指针参数的性能要更好一些,但实际上得具体分析。被复制的指针会延长目标对象生命周期,还可能会导致它被分配到堆上,那么其性能消耗就得加上堆内存分配和垃圾回收的成本。其实在栈上复制小对象只需很少的指令即可完成,远比运行时进行堆内存分配要快得多。另外,并发编程也提倡尽可能使用不可变对象(只读或复制),这可消除数据同步等麻烦。当然,如果复制成本很高,或需要修改原对象状态,自然使用指针更好
  8. 将过多的参数独立成option struct,既便于扩展参数集,也方便通过newOption函数设置默认配置。这也是代码复用的一种方式,避免多处调用时繁琐的参数配置
  9. 变参本质上就是一个切片。只能接收一到多个同类型参数,且必须放在列表尾部。不能有多个变参,如 i …int, s …string
    func test(s string, a ...int) {
    	fmt.Printf("%T, %v \n", a, a)
    }
    func main() {
    	test("abc", 1, 2, 3, 4)      // 输出: []int, [1 2 3 4]
    }
    }
    
  10. 将切片作为变参时,须进行展开操作。如果是数组,先将其转换为切片。既然变参是切片,如果参数复制的仅是切片本身,并不包括底层数组,也因此可修改原数据。如果需要,可用内置函数copy复制底层数据
    func test(a ...int) {
    	fmt.Println(a)
    }
    func main() {
    	a := [3]int{10, 20, 30}
    	test(a[:]...)        // 先转换为slice后展开
    }
    }
    
  11. 有返回值的函数,必须有明确的return终止语句;多返回值可用作其他函数调用实参,或当作结果直接返回;如果返回值类型能明确表明其含义,就尽量不要对其命名
  12. 匿名函数和普通函数的最大区别是我们可在函数内部定义匿名函数,形成类似嵌套效果。匿名函数可以直接调用,保存到变量,作为参数或返回值
    // 直接执行
    func main() {
    	func(s string) {
    		println(s)
    	}("hello,world")
    }
    // 赋值给变量
    func main() {
    	add := func(x ,y int) int {
    		return x + y
    	}
    	println(add(1,2))
    }
    // 作为参数
    func test(f func()) {
    	f()
    }
    func main() {
    	test(func() {
    		println(hello,world")
    	})
    }
    // 作为返回值
    func test() func(int, int) int {
    	return func(x, y int) int {
    		return x + y
    	}
    }
    func main() {
    	add := test()
    	println(add(1, 2))
    }
    
  13. 普通函数和匿名函数都可作为结构体字段,或经通道传递
  14. 不曾使用的匿名函数会被编译器当作错误
  15. 除闭包外,匿名函数也是一种常见重构手段。可将大函数分解成多个相对独立的匿名函数块,然后用相对简洁的调用完成逻辑流程,以实现框架和细节分离
  16. 闭包
    16.1 闭包是在其词法上下文中引用了自由变量的函数,或者说是函数和其引用的环境的组合体
    16.2 闭包直接引用了原环境变量,返回的不仅是匿名函数,还包括所引用的环境变量指针。所以闭包是函数和引用环境的组合体,即闭包是返回的匿名函数和函数使用到的变量组合成的整体
    16.3 test返回的匿名函数会引用上下文环境变量x
    func test(x int) func() {
    	return func() {
    		println(x)
    	}
    }
    func main() {
    	f := test(123)
    	f()
    }
    // 输出:123
    
    16.4
    func add() func(int) int {
       n := 10
       return func(x int) int {
       	n += x
       	return n
       }
    }
    func main() {
       f := add()
       println(f(1))
       println(f(2))
       println(f(3))
    }
    // 输出:11 13 16
    // 原因是匿名函数和n形成一个整体,每次调用n都在改变
    
    16.5 for循环复用局部变量i,那么每次添加的匿名函数引用的自然是同一变量。添加操作仅仅是将匿名函数放入列表,并未执行。因此,当main执行这些函数时,它们读取的是环境变量 i 最后一次循环时的值。
    解决方法是每次用不同的环境变量或传参复制,让各自闭包环境各不相同
    func test() []func() {
    	var s []func()
    
    	for i := 0; i < 2; i++ {
    		s = append(s,func() {   // 将多个匿名函数添加到列表
    			println(&i, i)
    		})
    	}
    
    	return s    // 返回匿名函数列表
    }
    func main() {
    	for _, f := range test() {     // 迭代执行所有匿名函数
    		f()
    	}
    }
    // 输出:0xc82000a0078 2
    //      0xc82000a0078 2
    
    func test() []func() {
    	var s []func()
    
    	for i := 0; i < 2; i++ {
    		x := i
    		s = append(s,func() {   // 将多个匿名函数添加到列表
    			println(&x, x)
    		})
    	}
    
    	return s    // 返回匿名函数列表
    }
    func main() {
    	for _, f := range test() {     // 迭代执行所有匿名函数
    		f()
    	}
    }
    // 输出:0xc82006e000 0
    //      0xc82006e008 1
    
  17. 多个延迟注册按FILO次序进行
  18. return 和 panic语句都会终止当前函数流程,引发延迟调用。另外,return语句不是ret汇编指令,它会先更新返回值
    func test() (z int) {
    	defer func() {
    		println("defer:",z)
    		z+=100    // 修改命名返回值
    	}()
    	return 100
    }
    func main() {
    	println("test:",test())
    }
    // 输出:defer:100
    //      test:200
    
  19. panic会立即中断当前函数流程,执行延迟调用,而在延迟调用函数中,recover可捕获并返回panic提交的错误对象
    func main() {
    	defer func() {
    		if err := recover(); err != nil {
    			log.Fatalln(err)
    		}
    	}()
    	panic("i am dead")
    	println("exit.")   //永远不会执行
    }
    
  20. 无论是否执行recover,所有延迟调用都会被执行。但中断性错误会沿调用堆栈向外传递,要么被外层捕获,要么导致进程崩溃
    func test() (z int) {
    	defer println("test.1")
    	defer println("test.2")
    
    	panic("i am dead")
    }
    func main() {
    	defer func() {
    		log.Println(recover())
    	}()
    
    	test()
    }
    // 输出:test.2
    //      test.1
    //      i am dead
    
  21. 连续调用panic,仅最后一个会被recover捕获
    func main() {
    	defer func() {
    		for {
    			if err := recover(); err != nil {
    				log.Println(err)
    			} else {
    				log.Fatalln("fatal")
    			}
    		}
    	}()
    	
    	defer func() {
    		panic("you are dead")
    	}()
    	
    	panic("i am dead")
    }
    // 输出:you are dead
    //      fatal
    
  22. 另外,recover必须在延迟调用函数中执行才能正常工作
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值