• Go 函数详解


    一、函数基础

    • 函数由函数声明关键字 func、函数名、参数列表、返回列表、函数体组成
    • 函数是一种类型。函数类型变量可以像其他类型变量一样使用,可以作为其他函数的参数或返回值,也可以直接调用执行
    • 函数名首字母大小写决定了其包可见性
    • 参数和返回值需用()包裹,如果返回值是一个非命名的参数,则可省略。函数体使用{}包裹,且{必须位于同行行尾

    1. 基本使用

    // 1. 可以没有输入参数,也可以没有返回值(默认返回 0)
    func A() {
        ...
    }
    // 2. 多个相邻的相同类型参数可以使用简写模式
    func B(a, b int) int {
        return a + b
    }
    // 3. 支持有名的返回值
    func C(a, b int) (sum int) {
        // sum 相当于函数体内的局部变量,初始化为零值
        sum = a + b
        return  // 可以不带 sum
    }
    // 4. 不支持默认值参数
    // 5. 不支持函数重载
    // 6. 不支持函数嵌套定义,但支持嵌套匿名函数
    func D(a, b int) (sum int) {
        E := func(x, y int) int {
            return x + y
        }
        return E(a, b)
    }
    // 7. 支持多值返回(一般将错误类型作为最后一个返回值)
    func F(a, b int) (int, int) {
        return b, a
    }
    // 8. 函数实参到形参的传递永远是**值拷贝**
    func G(a *int) {  // a 是实参指针变量的副本,和实参指向同一个地址
        *a += 1
    }
    

    2. 不定参数

    // 1. 不定参数类型相同
    // 2. 不定参数必须是函数的最后一个参数
    // 3. 不定参数在函数体内相当于切片
    func sum(arr ...int) (sum int) {
        for _, v := range arr {  // arr 相当于切片,可使用 range访问
            sum += v
        }
        return
    }
    // 4. 可以将切片传递给不定参数
    array := [...]int{1, 2, 3, 4}  // 不能将数组传递给不定参数
    slice := []int{1, 2, 3, 4}
    sum(slice...)  // 切片名后要加 ...
    // 5. 形参为不定参数的函数和形参为切片的函数类型不同
    func suma(arr ...int) (sum int) {
        for v := range arr {
            sum += v
        }
        return
    }
    func sumb(arr []int) (sum int) {
        for v := range arr {
            sum += v
        }
        return
    }
    fmt.Printf("%T
    ", suma)  // func(...int) int
    fmt.Printf("%T", sumb)  // func([]int) int
    

    3. 函数类型

    函数类型又叫函数签名:函数定义行去掉函数名、参数名和 {

    func add(a, b int) int { return a + b }
    func sub(x int, y int) (c int) { c = x - y; return }
    fmt.Printf("%T", add)  // func(int, int) int
    fmt.Printf("%T", sub)  // func(int, int) int
    

    可以使用 type 定义函数类型。函数类型变量和函数名都可以看做指针变量,该指针指向函数代码的开始位置

    func add(a, b int) int { return a + b }
    func sub(a, b int) int { return a - b }
    type Op func(int, int) int  // 定义一个函数类型:输入两个 int,返回一个 int
    func do(f Op, a, b int) int {
        t := f
        return t(a, b)
    }
    fmt.Println(do(add, 1, 2))  // 3
    fmt.Println(do(sub, 1, 2))  // -1
    

    4. 匿名函数

    // 1. 直接赋值给函数变量
    var sum = func(a, b int) int {
        return a + b
    }
    func do(f func(int, int) int, a, b int) int {
        return f(a, b)
    }
    // 2. 作为返回值
    func getAdd() func(int, int) int {
        return func(a, b int) int {
            return a + b
        }
    }
    func main() {
        // 3. 直接被调用
        defer func() {
            if err:= recover(); err != nil {
                fmt.Println(err)
            }
        }()
        sum(1, 2)
        getAdd()(1 , 2)
        // 4. 作为实参
        do(func(x, y int) int { return x + y }, 1, 2)
    }
    

    二、函数高级

    1. defer

    可注册多个延迟调用函数,以先进后出的顺序执行。常用于保证资源最终得到回收释放

    func main() {
        // defer 后跟函数或方法调用,不能是语句
        defer func() {
            println("first")
        }()
        defer func() {
            println("second")
        }()
        println("main")
    }
    // main
    // second
    // first
    

    defer 函数的实参在注册时传递,后续变更无影响

    func f() int {
        a := 1
        defer func(i int) {
            println("defer i =", i)
        }(a)
        a++
        return a
    }
    print(f())
    // defer i = 1
    // 2
    

    defer 若位于 return 后,则不会执行

    func main() {
        println("main")
        return
        defer func() {
            println("first")
        }()
    }
    // main
    

    若主动调用os.Exit(int)退出进程,则不会执行 defer

    func main() {
        defer func() {
            println("first")
        }()
        println("main")
        os.Exit(1)
    }
    // main
    

    关闭资源例子

    func CopyFile(dst, src string) (w int64, err error) {
        srcFile, err := os.Open(src)
        if err != nil {
            return
        }
        // defer 一般放在错误检查语句后面。若位置不当可能造成 panic
        defer srcFile.Close()
        
        dstFile, err := os.Create(dst)
        if err != nil {
            return
        }
        defer dstFile.Close()
        
        w, err = io.Copy(dstFile, srcFile)
        return
    }
    

    defer 使用注意事项:

    • defer 会延迟资源的释放
    • 尽量不要放在循环语句中
    • defer 相对于普通函数调用需要间接的数据结构支持,有一定性能损耗
    • defer 中最好不要对有名返回值进行操作

    2. 闭包

    • 闭包是由函数及其相关引用环境组合成的实体。一般通过在匿名函数中引用外部函数的局部变量或包全局变量构成
    • 闭包对闭包外的环境引入是直接引用:编译器检测到闭包,会将闭包引用的外部变量分配到堆上
    • 闭包是为了减少全局变量,在函数调用的过程中隐式地传递共享变量。但不够清晰,一般不建议用
    • 对象是附有行为的数据,而闭包是附有数据的行为。类在定义时已经显式地集中定义了行为,但闭包中的数据没有显式地集中声明的地方
    // fa 返回的是一个闭包:形参a + 匿名函数
    func fa(a int) func(i int) int {
        return func(i int) int {
            println(&a, a)
            a = a + i
            return a
        }
    }
    func main() {
        f := fa(1)  // f 使用的 a 是 0xc0000200f0
        g := fa(1)  // g 使用的 a 是 0xc0000200f8
        // f、g 引用的闭包环境中的 a 是函数调用产生的副本:每次调用都会为局部变量分配内存
        println(f(1))
        println(f(1))  // 闭包共享外部引用,因此修改的是同一个副本
        println(g(1))
        println(g(1))
    }
    // 0xc0000200f0 1
    // 2
    // 0xc0000200f0 2
    // 3
    // 0xc0000200f8 1
    // 2
    // 0xc0000200f8 2
    // 3
    

    闭包引用全局变量(不推荐)

    var a = 0
    // fa 返回的是一个闭包:全局变量a + 匿名函数
    func fa() func(i int) int {
        return func(i int) int {
            println(&a, a)
            a = a + i
            return a
        }
    }
    func main() {
        f := fa()
        g := fa()
        // f、g 引用的闭包环境中的 a 是同一个
        println(f(1))
        println(g(1))
        println(f(1))
        println(g(1))
    }
    // 0x511020 0
    // 1
    // 0x511020 1
    // 2
    // 0x511020 2
    // 3
    // 0x511020 3
    // 4
    

    同一个函数返回的多个闭包共享该函数的局部变量

    func fa(a int) (func(int) int, func(int) int) {
        println(&a, a)
        add := func(i int) int {
            a += i
            println(&a, a)
            return a
        }
        sub := func(i int) int {
            a -= i
            println(&a, a)
            return a
        }
        return add, sub
    }
    func main() {
        f, g := fa(0)  // f、g 使用的 a 都是 0xc0000200f0
        s, k := fa(0)  // s、k 使用的 a 都是 0xc0000200f8
        println(f(1), g(2))
        println(s(1), k(2))
    }
    // 0xc0000200f0 0
    // 0xc0000200f8 0
    // 0xc0000200f0 1
    // 0xc0000200f0 -1
    // 1 -1
    // 0xc0000200f8 1
    // 0xc0000200f8 -1
    // 1 -1
    

    三、错误处理

    1. 错误和异常

    • 广义的错误:发生非期望的行为
    • 狭义的错误:发生非期望的己知行为
      • 这里的己知是指错误类型是预料并定义好的
    • 异常:发生非期待的未知行为,又被称为未捕获的错误
      • 这里的未知是指错误的类型不在预先定义的范围内
      • 程序在执行时发生未预先定义的错误,程序编译器和运行时都没有及时将其捕获处理,而是由操作系统进行异常处理。如 C 语言的 Segmentation Fault

    错误分类

    Go 不会出现 untrapped error,只需处理 runtime errors 和程序逻辑错误

    Go 提供两种错误处理机制

    • 通过 panic 打印程序调用栈,终止程序来处理错误
    • 通过函数返回错误类型的值来处理错误

    Go 是静态强类型语言,程序的大部分错误是可以在编译器检测到的,但有些错误行为需要在运行期才能检测出来,此种错误行为将导致程序异常退出。建议:

    • 若程序发生的错误导致程序不能继续执行,此时程序应该主动调用 panic
    • 若程序发生的错误能够容错继续执行,此时应该使用 error 返回值的方式处理,或在非关键分支上使用 recover 捕获 panic

    2. panic 和 recover

    panic(i interface{})  // 主动抛出错误
    recover() interface{}  // 捕获抛出的错误
    
    • 引发panic情况:①主动调用panic;②程序运行时检测抛出运行时错误
    • panic 后,程序会从当前位置返回,逐层向上执行 defer 语句,逐层打印函数调用栈,直到被 recover 捕获或运行到最外层函数退出
    • 参数为空接口类型,可以传递任意类型变量
    • defer 中也可以 panic,能被后续 defer 捕获
    • recover 只有在 defer 函数体内被调用才能捕获 panic,否则返回 nil
    // 以下场景捕获失败
    defer recover()
    defer fmt.Println(recover())
    defer func() {
        func() {  // 两层嵌套
            println("defer inner")
            recover()
        }()
    }()
    // 以下场景捕获成功
    defer func() {
        println("defer inner")
        recover()
    }()
    func except() {
        recover()
    }
    func test() {
        defer except()
        painc("test panic")
    }
    

    可以同时有多个 panic(只会出现在 defer 里),但只有最后一次 panic 能被捕获

    func main() {
        defer func() {
            if err := recover(); err != nil {
                fmt.Println(err)
            }
            fmt.Println(recover())
        }()
        defer func() {
            panic("first defer panic")
        }()
        defer func() {
            panic("second defer panic")
        }()
        panic("main panic")
    }
    // first defer panic
    // <nil>
    

    包中 init 函数引发的 panic 只能在 init 函数中捕获(init 先于 main 执行)

    函数不能捕获内部新启动的 goroutine 抛出的 panic

    func do() {
        // 不能捕获 da 中的 panic
        defer func() {
            if err := recover(); err != nil {
                fmt.Println(err)
            }
        }()
        go da()
        time.Sleep(3 * time.Second)
    }
    func da() {
        panic("panic da")
    }
    

    3. error

    Go 内置错误接口类型 error。任何类型只要实现Error() string方法,都可以传递 error 接口类型变量???

    type error interface {
        Error() string
    }
    

    使用 error:

    • 在多个返回值的函数中,error 作为函数最后一个返回值
    • 若函数返回 error 类型变量,先处理error != nil的异常场景,再处理其他流程
    • defer 放在 error 判断的后面

    四、底层实现

    TODO

  • 相关阅读:
    Web开发需要掌握的
    使用this关键字,构造函数的相互调用
    FCKEditor.Net在Asp.Net MVC中的配置
    技术收集
    System.Collections命名空间
    C#中的托管堆,托管
    C#中的数据类型
    sql server部分主要代码
    Visual C#常用函数和方法集汇总
    C#委托和事件
  • 原文地址:https://www.cnblogs.com/lb477/p/14790947.html
Copyright © 2020-2023  润新知