美文网首页
go 语法常犯错误

go 语法常犯错误

作者: Jancd | 来源:发表于2018-07-16 23:59 被阅读41次

    不小心覆盖了变量

    对从动态语言转过来的开发者来说,简短声明很好用,这可能会让人误会 := 是一个赋值操作符。

    如果你在新的代码块中像下边这样误用了 :=,编译不会报错,但是变量不会按你的预期工作:

    func main() {
        x := 1
        println(x)        // 1
        {
            println(x)    // 1
            x := 2
            println(x)    // 2    // 新的 x 变量的作用域只在代码块内部
        }
        println(x)        // 1
    }
    

    这是 Go 开发者常犯的错,而且不易被发现。

    这是 Go 开发者常犯的错,而且不易被发现。

    可使用 vet 工具来诊断这种变量覆盖,Go 默认不做覆盖检查,添加 -shadow 选项来启用:

    > go tool vet -shadow main.go
    main.go:9: declaration of "x" shadows declaration at main.go:5
    

    注意 vet 不会报告全部被覆盖的变量,可以使用 go-nyet 来做进一步的检测:

    > $GOPATH/bin/go-nyet main.go
    main.go:10:3:Shadowing variable `x`
    

    map 容量

    在创建 map 类型的变量时可以指定容量,但不能像 slice 一样使用 cap() 来检测分配空间的大小:

    // 错误示例
    func main() {
        m := make(map[string]int, 99)
        println(cap(m))     // error: invalid argument m1 (type map[string]int) for cap  
    }    
    

    类型声明与方法

    从一个现有的非 interface 类型创建新类型时,并不会继承原有的方法:

    // 定义 Mutex 的自定义类型
    type myMutex sync.Mutex
    
    func main() {
        var mtx myMutex
        mtx.Lock()
        mtx.UnLock()
    }
    

    mtx.Lock undefined (type myMutex has no field or method Lock)...

    如果你需要使用原类型的方法,可将原类型以匿名字段的形式嵌到你定义的新 struct 中:

    // 类型以字段形式直接嵌入
    type myLocker struct {
        sync.Mutex
    }
    
    func main() {
        var locker myLocker
        locker.Lock()
        locker.Unlock()
    }
    

    range 遍历 slice 和 array 时混淆了返回值

    与其他编程语言中的 for-in 、foreach 遍历语句不同,Go 中的 range 在遍历时会生成 2 个值,第一个是元素索引,第二个是元素的值:

    // 错误示例
    func main() {
        x := []string{"a", "b", "c"}
        for v := range x {
            fmt.Println(v)    // 1 2 3
        }
    }
    
    // 正确示例
    func main() {
        x := []string{"a", "b", "c"}
        for _, v := range x {    // 使用 _ 丢弃索引
            fmt.Println(v)
        }
    }
    

    访问 map 中不存在的 key

    和其他编程语言类似,如果访问了 map 中不存在的 key 则希望能返回 nil.

    Go 则会返回元素对应数据类型的零值,比如 nil、'' 、false 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中。

    检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:

    // 错误的 key 检测方式
    func main() {
        x := map[string]string{"one": "2", "two": "", "three": "3"}
        if v := x["two"]; v == "" {
            fmt.Println("key two is no entry")    // 键 two 存不存在都会返回的空字符串
        }
    }
    
    // 正确示例
    func main() {
        x := map[string]string{"one": "2", "two": "", "three": "3"}
        if _, ok := x["two"]; !ok {
            fmt.Println("key two is no entry")
        }
    }
    

    defer 函数的参数值

    对 defer 延迟执行的函数,它的参数会在声明时候就会求出具体值,而不是在执行时才求值:

    // 在 defer 函数中参数会提前求值
    func main() {
        var i = 1
        defer fmt.Println("result: ", func() int { return i * 2 }())
        i++
    }
    

    result: 2

    defer 函数的执行时机

    对 defer 延迟执行的函数,会在调用它的函数结束时执行,而不是在调用它的语句块结束时执行,注意区分开。

    比如在一个长时间执行的函数里,内部 for 循环中使用 defer 来清理每次迭代产生的资源调用,就会出现问题.

    package main
    
    import (
        "os"
        "path/filepath"
        "fmt"
    )
    
    // 命令行参数指定目录名
    // 遍历读取目录下的文件
    func main() {
        if len(os.Args) != 2 {
            os.Exit(1)
        }
    
        dir := os.Args[1]
        start,err := os.Stat(dir)
        if err != nil && !start.IsDir() {
            os.Exit(2)
        }
    
        var targets []string
        filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
            if err != nil{
                return err
            }
    
            if !info.Mode().IsRegular() {
                return nil
            }
    
            targets = append(targets,path)
    
            return nil
        })
    
        for _,target := range targets{
            func(){
                f,err := os.Open(target)
                if err != nil {
                    fmt.Println("bad target:", target, "error:", err)
                    return    // 在匿名函数内使用 return 代替 break 即可
                }
                fmt.Println("ok target:", target)
                defer f.Close()
            }()
        }
    }
    

    for 语句中的迭代变量与闭包函数

    type field struct {
        name string
    }
    
    func (p *field) print() {
        fmt.Println(p.name)
    }
    
    // 错误示例
    func main() {
        data := []field{{"one"}, {"two"}, {"three"}}
        for _, v := range data {
            go v.print()
        }
        time.Sleep(3 * time.Second)
        // 输出 three three three 
    }
    
    
    // 正确示例
    func main() {
        data := []field{{"one"}, {"two"}, {"three"}}
        for _, v := range data {
            v := v
            go v.print()
        }
        time.Sleep(3 * time.Second)
        // 输出 one two three
    }
    
    // 正确示例
    func main() {
        data := []*field{{"one"}, {"two"}, {"three"}}
        for _, v := range data {    // 此时迭代值 v 是三个元素值的地址,每次 v 指向的值不同
            go v.print()
        }
        time.Sleep(3 * time.Second)
        // 输出 one two three
    }
    

    类型声明与方法

    从一个现有的非 interface 类型创建新类型时,并不会继承原有的方法:

    // 定义 Mutex 的自定义类型
    type myMutex sync.Mutex
    
    func main() {
        var mtx myMutex
        mtx.Lock()
        mtx.UnLock()
    }
    
    > mtx.Lock undefined (type myMutex has no field or method Lock)...
    

    如果你需要使用原类型的方法,可将原类型以匿名字段的形式嵌到你定义的新 struct 中:

    // 类型以字段形式直接嵌入
    type myLocker struct {
        sync.Mutex
    }
    
    func main() {
        var locker myLocker
        locker.Lock()
        locker.Unlock()
    }
    
    interface 类型声明也保留它的方法集:
    
    type myLocker sync.Locker
    
    func main() {
        var locker myLocker
        locker.Lock()
        locker.Unlock()
    }
    

    使用指针作为方法的 receiver

    只要值是可寻址的,就可以在值上直接调用指针方法。即是对一个方法,它的 receiver 是指针就足矣。

    但不是所有值都是可寻址的,比如 map 类型的元素、通过 interface 引用的变量:

    type data struct {
        name string
    }
    
    type printer interface {
        print()
    }
    
    func (p *data) print() {
        fmt.Println("name: ", p.name)
    }
    
    func main() {
        d1 := data{"one"}
        d1.print()    // d1 变量可寻址,可直接调用指针 receiver 的方法
    
        var in printer = data{"two"}
        in.print()    // 类型不匹配
    
        m := map[string]data{
            "x": data{"three"},
        }
        m["x"].print()    // m["x"] 是不可寻址的    // 变动频繁
    }
    

    相关文章

      网友评论

          本文标题:go 语法常犯错误

          本文链接:https://www.haomeiwen.com/subject/njycpftx.html