zoukankan      html  css  js  c++  java
  • golang易错题收集

    1. 变量作用域

    var p *int
    
    func foo() (*int, error) {
        var i int = 5
        return &i, nil
    }
    
    func bar() {
        //use p
        fmt.Println(*p)
    }
    
    func main() {
        p, err := foo()
        if err != nil {
            fmt.Println(err)
            return
        }
        bar()
        fmt.Println(*p)
    }

     runtime error

    问题出在操作符:=,对于使用:=定义的变量,如果新变量与同名已定义的变量不在同一个作用域中,那么 Go 会新定义这个变量。对于本例来说,main() 函数里的 p 是新定义的变量,会遮住全局变量 p,导致执行到bar()时程序,全局变量 p 依然还是 nil,程序随即 Crash。

    2. 循环次数在循环开始前就已经确定

    func main() {
        v := []int{1, 2, 3}
        for i := range v {
            v = append(v, i)
        }
    }

    循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数。

    [1 2 3 0]
    [1 2 3 0 1]
    [1 2 3 0 1 2]

    func main() {
        var a = [5]int{1, 2, 3, 4, 5}
        var r [5]int
    
        for i, v := range a {
            if i == 0 {
                a[1] = 12
                a[2] = 13
            }
            r[i] = v
        }
        fmt.Println("r = ", r)
        fmt.Println("a = ", a)
    }

    range 表达式是副本参与循环,就是说例子中参与循环的是 a 的副本,而不是真正的 a。

    r =  [1 2 3 4 5]
    a =  [1 12 13 4 5]

    3. goroutine访问外部变量

    func main() {
    
        var m = [...]int{1, 2, 3}
    
        for i, v := range m {
            go func() {
                fmt.Println(i, v)
            }()
        }
    
        time.Sleep(time.Second * 3)
    }

    for range 使用短变量声明(:=)的形式迭代变量,需要注意的是,变量 i、v 在每次循环体中都会被重用,而不是重新声明。

    各个 goroutine 中输出的 i、v 值都是 for range 循环结束后的 i、v 最终值(goroutines启动时for早已执行完),而不是各个goroutine启动时的i, v值。

    可以理解为闭包引用,使用的是上下文环境的值。
    两种可行的 fix 方法:

    for i, v := range m {
        go func(i,v int) {
            fmt.Println(i, v)
        }(i,v)
    }
    // 临时变量保存当前值
    for i, v := range m {
        i := i           // 这里的 := 会重新声明变量,而不是重用
        v := v
        go func() {
            fmt.Println(i, v)
        }()
    }

    注意:函数的参数或函数的接收者是及时计算值并压栈的(如9 GMP模型)。

    一个是函数的参数或接收者(立即求值),一个是函数的执行体(执行体不会立即执行),defer同样。

    package main
    
    import (
        "fmt"
        "runtime"
        "time"
    )
    
    func main() {
        runtime.GOMAXPROCS(1)
    
        var a = []int{1, 2, 3, 4}
        for _, v := range a {
            go fmt.Println(v)
        }
    
        time.Sleep(3 * time.Second)
    }
    4
    1
    2
    3

    4. defer

    func f(n int) (r int) {
        defer func() {
            r += n
            recover()
        }()
    
        var f func()
    
        defer f()
        f = func() {
            r += 2
        }
        return n + 1
    }
    
    func main() {
        fmt.Println(f(3))
    }

    7

    第一步执行r = n +1,接着执行第二个 defer,由于此时 f() 未定义,引发异常,随即执行第一个 defer,异常被 recover(),程序正常执行,最后 return。

    参考:5 年 Gopher 都不知道的 defer 细节,你别再掉进坑里!

    5. slice

    func change(s ...int) {
        s = append(s,3)
    }
    
    func main() {
        slice := make([]int,5,5)
        slice[0] = 1
        slice[1] = 2
        change(slice...)
        fmt.Println(slice)
        change(slice[0:2]...)
        fmt.Println(slice)
    }

    [1 2 0 0 0]

    [1 2 3 0 0]

    知识点:可变函数、append()操作。Go 提供的语法糖...,可以将 slice 传进可变函数,不会创建新的切片。第一次调用 change() 时,append() 操作使切片底层数组发生了扩容,原 slice 的底层数组不会改变;第二次调用change() 函数时,使用了操作符[i,j]获得一个新的切片,假定为 slice1,它的底层数组和原切片底层数组是重合的,不过 slice1 的长度、容量分别是 2、5,所以在 change() 函数中对 slice1 底层数组的修改会影响到原切片。

    6. interface比较

    type T interface{}
    type X string
    type Y = string
    
    func main() {
        var t T = "abc"
        var x X = "abc"
        var y Y = "abc"
    
        fmt.Println(t == x)
        fmt.Println(t == string(x))
    
        fmt.Println(t == y)
        fmt.Println(t == string(y))
    }
    //
    false
    true
    true
    true

    接口值(t)与非接口值(x)也是可以比较的, Go语言规范里(https://golang.org/ref/spec#Comparison_operators):
    A value x of non-interface type X and a value t of interface type T are comparable when values of type X are comparable and X implements T.

    They are equal if t's dynamic type is identical to X and t's dynamic value is equal to x.
    意思是如果非接口值x实现了T接口, 而且接口值的动态类型与X类型相同, 动态值与x相等, 那比较结果就相等.
    而这一行中, 接口值t的动态类型为string, 动态值为"abc"(第12行), 而非接口值x的类型为X, 类型都不相同, 动态值就不用比较了

    把==号后面的值转换为string类型, 再与接口值t的动态类型和动态值比较, 类型相同, 值相等, 返回true

    接口值t的动态类型和非接口值y的类型比较, 相同, 动态值与非接口值y的值比较, 相等, 返回true

    Y是string类型的别名, 所以string(y)和第19行的y一样, 都是string类型, 此行中的string()没有必要

    7. 多重赋值

    func main() {
        i := 1
        s := []string{"A", "B", "C"}
        i, s[i-1] = 2, "Z"
        fmt.Printf("s: %v 
    ", s)  // s: [Z B C]
    }

    多重赋值分为两个步骤,有先后顺序:

    计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式;

    赋值;

    8. 指针接收直接引用,值接收拷贝

    type People struct {
        Value int
        name  string
    }
    
    func (s *People) valueAdd1() {
        s.Value += 1
        fmt.Println("in *People: ", s.Value, ", addr:", &s.Value)
    }
    
    func (s People) valueAdd2() {
        s.Value += 1
        fmt.Println("in People: ", s.Value, ", addr:", &s.Value)
    }
    
    func (s People) valueAdd3() People {
        s.Value += 1
        fmt.Println("in People return: ", s.Value, ", addr:", &s.Value)
        return s
    }
    func main() {
        fmt.Printf("%T
    ", &People{})
    
        people := new(People)
        fmt.Println("addr:", &people.Value)
        people.valueAdd1()
        fmt.Println(people.Value)
    
        people.valueAdd2()
        fmt.Println(people.Value)
    
        people2 := people.valueAdd3()
        fmt.Println(people2.Value)
    }
    ////
    *main.People
    addr: 0xc0000044e0
    in *People:  1 , addr: 0xc0000044e0
    1
    in People:  2 , addr: 0xc000004500
    1
    in People return:  2 , addr: 0xc000004520
    2

    9. GMP模型

    参考:http://topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/GMP%E5%8E%9F%E7%90%86%E4%B8%8E%E8%B0%83%E5%BA%A6.html

    package main
    
    import (
        "fmt"
        "runtime"
        "time"
    )
    
    type field struct {
        name string
    }
    
    func (f field) print() {
        fmt.Println(f.name)
    }
    
    func main() {
        runtime.GOMAXPROCS(1)
        data := []*field{{"one"}, {"two"}, {"three"}}
    
        for _, v := range data {
            go v.print()
        }
    
        time.Sleep(3 * time.Second)
    }

    当设置GOMAXPROCS为1时,仅提供一个P(逻辑处理器核心数),所有的goroutine都在这一个P上执行。

    three
    one
    two

    goroutine会先填充本地执行队列(然后填充全局执行队列),所以按顺序执行。

    之所以首先输出three,是因为go运行时调度时会会把最后一个goroutines直接关联P执行。

    参考:

    1. http://mian.topgoer.com/

  • 相关阅读:
    JS动态计算rem
    Vue数据双向绑定原理
    NOI2019 退役记
    友情链接
    算法博客总结
    总结各类错误(always online)
    学习笔记:powerful number求积性函数前缀和
    LOJ#2409. 「THUPC 2017」小 L 的计算题 / Sum(生成函数)
    多项式简单操作
    LOJ #3103. 「JSOI2019」节日庆典
  • 原文地址:https://www.cnblogs.com/embedded-linux/p/13426657.html
Copyright © 2011-2022 走看看