• go基础语法10问


    1.recover的执行时机

    无,recover 必须在 defer 函数中运行。recover 捕获的是祖父级调用时的异常,直接调用时无效。

    func main() {
        recover()
        panic(1)
    }
    
    • 1
    • 2
    • 3
    • 4

    直接 defer 调用也是无效。

    func main() {
        defer recover()
        panic(1)
    }
    
    • 1
    • 2
    • 3
    • 4

    defer 调用时多层嵌套依然无效。

    func main() {
        defer func() {
            func() { recover() }()
        }()
        panic(1)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    必须在 defer 函数中直接调用才有效。

    func main() {
        defer func() {
            recover()
        }()
        panic(1)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.闭包错误引用同一个变量问题怎么处理

    在每轮迭代中生成一个局在这里插入代码片部变量 i 。如果没有 i := i 这行,将会打印同一个变量。

    func main() {
        for i := 0; i < 5; i++ {
            i := i
            defer func() {
                println(i)
            }()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    或者是通过函数参数传入 i 。

    func main() {
        for i := 0; i < 5; i++ {
            defer func(i int) {
                println(i)
            }(i)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.在循环内部执行defer语句会发生啥

    defer 在函数退出时才能执行,在 for 执行 defer 会导致资源延迟释放。

    func main() {
        for i := 0; i < 5; i++ {
            func() {
                f, err := os.Open("/path/to/file")
                if err != nil {
                    log.Fatal(err)
                }
                defer f.Close()
            }()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    func 是一个局部函数,在局部函数里面执行 defer 将不会有问题。

    4.说出一个避免Goroutine泄露的措施

    可以通过 context 包来避免内存泄漏

    func main() {
        ctx, cancel := context.WithCancel(context.Background())
    
        ch := func(ctx context.Context) <-chan int {
            ch := make(chan int)
            go func() {
                for i := 0; ; i++ {
                    select {
                    case <- ctx.Done():
                        return
                    case ch <- i:
                    }
                }
            } ()
            return ch
        }(ctx)
    
        for v := range ch {
            fmt.Println(v)
            if v == 5 {
                cancel()
                break
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    下面的 for 循环停止取数据时,就用 cancel 函数,让另一个协程停止写数据。如果下面 for 已停止读取数据,上面 for 循环还在写入,就会造成内存泄漏。

    5.如何跳出for select 循环

    通常在for循环中,使用break可以跳出循环,但是注意在go语言中,for select配合时,break 并不能跳出循环。

    func testSelectFor2(chExit chan bool){
     EXIT:
        for  {
            select {
            case v, ok := <-chExit:
                if !ok {
                    fmt.Println("close channel 2", v)
                    break EXIT//goto EXIT2
                }
    
                fmt.Println("ch2 val =", v)
            }
        }
    
        //EXIT2:
        fmt.Println("exit testSelectFor2")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    6.如何在切片中查找

    go中使用 sort.searchXXX 方法,在排序好的切片中查找指定的方法,但是其返回是对应的查找元素不存在时,待插入的位置下标(元素插入在返回下标前)。

    可以通过封装如下函数,达到目的。

    func IsExist(s []string, t string) (int, bool) {
        iIndex := sort.SearchStrings(s, t)
        bExist := iIndex!=len(s) && s[iIndex]==t
    
        return iIndex, bExist
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    7.如何初始化带嵌套结构的结构体

    go 的哲学是组合优于继承,使用 struct 嵌套即可完成组合,内嵌的结构体属性就像外层结构的属性即可,可以直接调用。
    注意初始化外层结构体时,必须指定内嵌结构体名称的结构体初始化,如下看到 s1方式报错,s2 方式正确。

    type stPeople struct {
        Gender bool
        Name string
    }
    
    type stStudent struct {
        stPeople
        Class int
    }
    
    //尝试4 嵌套结构的初始化表达式
    //var s1 = stStudent{false, "JimWen", 3}
    var s2 = stStudent{stPeople{false, "JimWen"}, 3}
    fmt.Println(s2.Gender, s2.Name, s2.Class)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    8.切片和数组的区别

    数组是具有固定长度,且拥有零个或者多个,相同数据类型元素的序列。数组的长度是数组类型的一部分,所以[3]int 和 [4]int 是两种不同的数组类型。数组需要指定大小,不指定也会根据初始化的自动推算出大小,不可改变;数组是值传递。数组是内置类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度。
    当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数len(array)获取其长度。数组定义:

    var array [10]int
    
    var array =[5]int{1,2,3,4,5}
    
    • 1
    • 2
    • 3

    切片表示一个拥有相同类型元素的可变长度的序列。切片是一种轻量级的数据结构,它有三个属性:指针、长度和容量。切片不需要指定大小;切片是地址传递;切片可以通过数组来初始化,也可以通过内置函数make()初始化 。初始化时len=cap,在追加元素时如果容量cap不足时将按len的2倍扩容。切片定义:

    var slice []type = make([]type, len)
    
    • 1

    9.new和make的区别

    new 的作用是初始化一个指向类型的指针 (*T) 。new 函数是内建函数,函数定义:func new(Type) Type。使用 new 函数来分配空间。传递给 new 函数的是一个类型,不是一个值。返回值是指向这个新分配的零值的指针。
    make 的作用是为 slice,map 或 chan 初始化并返回引用 (T)。make 函数是内建函数,函数定义:func make(Type, size IntegerType) Type;第一个参数是一个类型,第二个参数是长度;返回值是一个类型。
    make(T, args) 函数的目的与 new(T) 不同。它仅仅用于创建 Slice, Map 和 Channel,并且返回类型是 T(不是T
    )的一个初始化的(不是零值)的实例。

    10.Printf()、Sprintf()、Fprintf()函数的区别用法是什么

    都是把格式好的字符串输出,只是输出的目标不一样。
    Printf(),是把格式字符串输出到标准输出(一般是屏幕,可以重定向)。Printf() 是和标准输出文件 (stdout) 关联的,Fprintf 则没有这个限制。
    Sprintf(),是把格式字符串输出到指定字符串中,所以参数比printf多一个char*。那就是目标字符串地址。
    Fprintf(),是把格式字符串输出到指定文件设备中,所以参数比 printf 多一个文件指针 FILE*。主要用于文件操作。Fprintf() 是格式化输出到一个stream,通常是到文件。

  • 相关阅读:
    c++ 智能指针 (std::weak_ptr)(一)
    Python3 JSON 数据解析
    macOS - 使用 chromedriver
    el-tree自定义节点内容
    基于瞬时频率的语言信号清/浊音判决和高音检测(MATLAB R2021)
    你写过的最蠢的代码是?
    乘性散斑噪声理论及其仿真实例分析-Matlab源代码
    程序员的浪漫七夕
    JVM阶段(6)-方法区回收
    异步编程解决方案 Generator生成器函数、iterator迭代器、async/await、Promise
  • 原文地址:https://blog.csdn.net/m0_73728511/article/details/133471634