• 周五. 10 月 11th, 2024

    Go语言函数

    root

    10 月 13, 2019 #Golang
    函数只能判断其是否为nil,不支持其他比较操作
    func a() {}
    func b() {}
    func main() {
        println(a == nil)
        println(b == a) //无效操作
    }
    
    从函数返回局部变量指针是安全的,编译器通过逃逸分析(escape analysis)来决定是否在堆上分配内存
    func test() *int {
        a := 0x100
        return &a
    }
    func main() {
        var a *int = test()
        println(a, *a)
    }
    

    4.2 参数

    不管是指针,引用类型,还是其他类型参数,都是值拷贝传递。区别无非是拷贝目标对象,还是拷贝指针而已。
    在函数调用前,会为形参和返回值分配内存空间。
    变参本质上就是一个切片,只能接收一到多个同类型参数。且必须放在列表尾部。
    func test(s string, a ...int) {
        fmt.Printf("%T,%v\n", a, a)
    }
    func main() {
        test("abc", 1, 2, 3, 4)
    }
    
    将切片作为变参时,须进行展开操作。
    func test(a ...int) {
        fmt.Println(a)
    }
    func main() {
        a := [3]int{10, 20, 30}
        test(a[:]...)   //转换为slice后展开
    }
    

    4.3 返回值

    有返回值的函数,必须有明确的return终止语句, 除非有panic,或者无break的死循环,则无须return终止语句

    4.4 匿名函数

    直接执行
    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))
    }
    
    普通函数和匿名函数都可作为结构体字段,或经通道传递
    func testStruct() {
        type calc struct {
            mul func(x, y int) int
        }
        x := calc{
            mul:func(x, y int) int {
                return x * y
            },
        }
        println(x.mul(2,3))
    }
    func testChannel() {
        c := make(chan func(int,int) int, 2)
        c <- func(x, y int) int {
            return x + y
        }
        println((<-c)(1,2))
    }
    
    不曾使用的匿名函数会被编译器当作错误

    4.5 延迟调用

    语句defer向当前函数注册稍后执行的函数调用。这些调用被称作延迟调用,因为它们直到当前函数执行线束才被执行。常用于资源释放,解除锁定,以及错误处理等操作
    多个延迟注册按FILO次序执行
    func main() {
        defer println("a")
        defer println("b")
    }
    /*
    b
    a
    */
    
    编译器通过插入额外的指令来实现延迟调用执行,而return和panic语句都会终止当前函数流程,引发延迟调用。
    相比直接用CALL汇编指令调用函数,延迟调用则须花费更大的代价。这其中包括注册、调用等操作。还有额外的缓存开销。

    4.6 错误处理

    官方推荐的标准做法是返回error状态
    func Scanln(a ...interface{}) (n int, err error)
    
    标准库将error定义为接口类型,以便实现自定义错误类型
    type error interface {
        Error() string
    }
    
    按惯例,error总是最后一个返回参数。
    var errDivByZero = errors.New("division by zero")
    
    func div(x, y int) (int, error) {
        if y == 0 {
            return 0, errDivByZero
        }
        return x / y, nil
    }
    func main() {
        z, err := div(5, 0)
        if err == errDivByZero {
            log.Fatalln(err)
        }
        println(z)
    }
    
    与errors.New类似的还有fmt.Errorf,它返回一个格式化内容的错误对象
    自定义错误
    type DivError struct {  //自定义错误类型
        x, y int
    }
    func (DivError) Error() string {    //实现error接口方法
        return "division by zero"
    }
    func div(x, y int) (int, error) {
        if y == 0 {
            return 0, DivError(x, y)    //返回自定义错误类型
        }
        return x / y, nil
    }
    func main() {
        z, err := div(5, 0)
        
        if err != nil {
            switch e := err.(type) {        //根据类型匹配
                case DivError:
                    fmt.Println(e, e.x, e.y)
                default:
                    fmt.Println(e)
            }
            log.Fatalln(err)
        }
        println(z)
    }
    
    panic,recover 接近try/catch结构化异常 内置函数,非语句
    func panic(v interface{})
    func recover() interface{}
    
    panic会立即中断当前函数流程,执行延迟调用。在延迟调用函数中,recover可捕获并返回panic提交的错误对象
    package main
    
    import "log"
    
    func main() {
    	defer func() {
    		if err := recover(); err != nil {	//捕获错误
    			log.Fatalln(err)
    		}
    	}()
    
    	panic("i an dead")	//引发错误
    	println("exit.")	//永不会执行
    }
    
    
    连续调用panic 仅最后一个会被recover捕获, recover必须在延迟调用函数中执行才能正常工作

    root

    发表回复