zoukankan      html  css  js  c++  java
  • Go 语言从新手到大神:每个人都会踩的五十个坑(转)

    Go语言是一个简单却蕴含深意的语言。但是,即便号称是最简单的C语言,都能总结出一本《C陷阱与缺陷》,更何况Go语言呢。Go语言中的许多坑其实并不是因为Go自身的问题。一些错误你再别的语言中也会犯,例如作用域,一些错误就是对因为 Go 语言的特性不了解而导致的,例如 range。

    其实如果你在学习Go语言的时候去认真地阅读官方文档,百科,邮件列表或者其他的类似 Rob Pike 的名人博客,报告,那么本文中提到的许多坑都可以避免。但是不是每个人都会从基础学起,例如译者就喜欢简单粗暴地直接用Go语言写程序。如果你也像译者一样,那么你应该读一下这篇文章:这样可以避免在调试程序时浪费过多时间。

    本文将50个坑按照使用使用范围和难易程度分为以下三个级别:“新手入门级”,“新手深入级”,“新手进阶级”。

    “{”不能单独放在一行

    级别:新手入门级

    Go语言设计者肯定和C语言设计者(K&R)有种不明不白的关系,因为C语言中的K&R格式在Go语言中得到发扬光大。大多数语言中,大括号中的左括号是可以随便放在哪里的:C语言中必须要按照K&R格式对代码进行格式化之后,左括号才会被放在前一行中的最后。但是Go语言中,左括号必须强制不能单独放在一行。这个规则得益于“自动分号注射”(automatic semicolon injection)。

    补充:go提供了专门用于格式化代码的gofmt工具。

    出错代码:

    package main
    
    import "fmt"
    
    func main()  
    { //error, can't have the opening brace on a separate line
        fmt.Println("hello there!")
    }
    

    错误信息:

    /tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {
    

    修正代码:

    package main
    
    import "fmt"
    
    func main() {  
        fmt.Println("works!")
    }
    

    未使用已定义的变量

    级别:新手入门级

    如果代码中有未使用的变量,那个代码编译的时候就会报错。Go要求在代码中所有声明的变量都需要被用到,当然,全局变量除外。函数的参数也可以只被声明,不被使用。

    对于未声明变量的调用同样会导致编译失败。和C语言一样,Go编译器也是个女人,他说什么你都要尽力满足。

    出错代码:

    package main
    
    var gvar int //not an error
    
    func main() {  
        var one int   //error, unused variable
        two := 2      //error, unused variable
        var three int //error, even though it's assigned 3 on the next line
        three = 3
    
        func(unused string) {
            fmt.Println("Unused arg. No compile error")
        }("what?")
    }
    

    错误信息:

    /tmp/sandbox473116179/main.go:6: one declared and not used /tmp/sandbox473116179/main.go:7: two declared and not used /tmp/sandbox473116179/main.go:8: three declared and not used
    

    修正代码:

    package main
    
    import "fmt"
    
    func main() {  
        var one int
        _ = one
    
        two := 2 
        fmt.Println(two)
    
        var three int 
        three = 3
        one = three
    
        var four int
        four = four
    }
    

    当然,你也可以考虑删除那些没有使用的变量。

    未使用的包

    级别:新手入门级

    当import一个包之后,如果不使用这个包,或者这个包中的函数/接口/数据结构/变量,那么将会编译失败。

    如果真的确认要引入变量但是不使用的话,我们可以用“”标识符坐标记,避免编译失败。“”标识符表示为了得到这些包的副作用而引入这些包。

    出错代码:

    package main
    
    import (  
        "fmt"
        "log"
        "time"
    )
    
    func main() {  
    }
    

    错误信息:

    /tmp/sandbox627475386/main.go:4: imported and not used: "fmt" 
    /tmp/sandbox627475386/main.go:5: imported and not used: "log" 
    /tmp/sandbox627475386/main.go:6: imported and not used: "time"
    

    修正代码

    package main
    
    import (  
        _ "fmt"
        "log"
        "time"
    )
    
    var _ = log.Println
    
    func main() {  
        _ = time.Now
    }
    
    

    只能在函数内部使用简短的变量声明

    级别:新手入门级 出错代码:

    package main
    
    myvar := 1 //error
    
    func main() {  
    }
    

    错误信息:

    /tmp/sandbox265716165/main.go:3: non-declaration statement outside function body
    
    

    修正代码:

    package main
    
    var myvar = 1
    
    func main() {  
    }
    

    无法使用精简的赋值语句对变量重新赋值

    级别:新手入门级

    不能使用精简的赋值语句重新赋值单个变量,但是可以使用精简的赋值语句同时赋值多个变量。

    并且,重定义的变量必须写在同一个代码块。

    错误信息:

    package main
    
    func main() {  
        one := 0
        one := 1 //error
    }
    

    错误信息:

    /tmp/sandbox706333626/main.go:5: no new variables on left side of :=
    

    修正代码:

    package main
    
    func main() {  
        one := 0
        one, two := 1,2
    
        one,two = two,one
    }
    

    隐式变量(作用域)

    级别:新手入门级

    和 C 语言一样,Go 语言也有作用于,一个变量的作用范围仅仅是一个代码块。虽然精简的赋值语句很简单,但是注意作用域。

    package main
    
    import "fmt"
    
    func main() {  
        x := 1
        fmt.Println(x)     //打印 1
        {
            fmt.Println(x) //打印 1
            x := 2
            fmt.Println(x) //打印 2
        }
        fmt.Println(x)     //打印 1 ( 不是 2)
    }
    

    甚至对于有经验的开发者来说,这也是个不注意就会掉进去的深坑。

    除非特别指定,否则无法使用 nil 对变量赋值

    级别:新手入门级

    nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。

    错误信息:

    package main
    
    func main() {  
        var x = nil //error
    
        _ = x
    }
    

    错误信息:

    /tmp/sandbox188239583/main.go:4: use of untyped nil
    

    修正代码:

    package main
    
    func main() {  
        var x interface{} = nil
    
        _ = x
    }
    

    Slice 和 Map 的 nil 值

    级别:新手入门级

    初始值为 nil 的 Slice 是可以进行“添加”操作的,但是对于 Map 的“添加”操作会导致运行时恐慌。( ﹁ ﹁ ) 恐慌。

    修正代码:

    package main
    
    func main() {  
        var s []int
        s = append(s,1)
    }
    
    

    错误信息:

    package main
    
    func main() {  
        var m map[string]int
        m["one"] = 1 //error
    
    }
    

    Map 定长

    级别:新手入门级

    创建 Map 的时候可以指定 Map 的长度,但是在运行时是无法使用 cap() 功能重新指定 Map 的大小,Map 是定长的。

    错误信息:

    package main
    
    func main() {  
        m := make(map[string]int,99)
        cap(m) //error
    }
    

    错误信息:

    /tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap
    

    字符串无法为 nil

    级别:新手入门级

    所有的开发者都可能踩的坑,在 C 语言中是可以 char *String=NULL,但是 Go 语言中就无法赋值为 nil。

    错误信息:

    package main
    
    func main() {  
        var x string = nil //error
    
        if x == nil { //error
            x = "default"
        }
    }
    

    Compile Errors:

    /tmp/sandbox630560459/main.go:4: cannot use nil as type string in assignment /tmp/sandbox630560459/main.go:6: invalid operation: x == nil (mismatched types string and nil)
    

    修正代码:

    package main
    
    func main() {  
        var x string //defaults to "" (zero value)
    
        if x == "" {
            x = "default"
        }
    }
    

    参数中的数组

    Array Function Arguments

    级别:新手入门级 对于 C 和 C++ 开发者来说,数组就是指针。给函数传递数组就是传递内存地址,对数组的修改就是对原地址数据的修改。但是 Go 语言中,传递的数组不是内存地址,而是原数组的拷贝,所以是无法通过传递数组的方法去修改原地址的数据的。

    package main
    
    import "fmt"
    
    func main() {  
        x := [3]int{1,2,3}
    
        func(arr [3]int) {
            arr[0] = 7
            fmt.Println(arr) //prints [7 2 3]
        }(x)
    
        fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])
    }
    

    如果需要修改原数组的数据,需要使用数组指针(array pointer)。

    package main
    
    import "fmt"
    
    func main() {  
        x := [3]int{1,2,3}
    
        func(arr *[3]int) {
            (*arr)[0] = 7
            fmt.Println(arr) //prints &[7 2 3]
        }(&x)
    
        fmt.Println(x) //prints [7 2 3]
    }
    

    或者可以使用 Slice,

    package main
    
    import "fmt"
    
    func main() {  
        x := []int{1,2,3}
    
        func(arr []int) {
            arr[0] = 7
            fmt.Println(arr) //prints [7 2 3]
        }(x)
    
        fmt.Println(x) //prints [7 2 3]
    }
    

    使用 Slice 和 Array 的 range 会导致预料外的结果

    级别:新手入门级

    如果你对别的语言中的 for in 和 foreach 熟悉的话,那么 Go 中的 range 使用方法完全不一样。因为每次的 range 都会返回两个值,第一个值是在 Slice 和 Array 中的编号,第二个是对应的数据。

    出错代码:

    package main
    
    import "fmt"
    
    func main() {  
        x := []string{"a","b","c"}
    
        for v := range x {
            fmt.Println(v) //prints 0, 1, 2
        }
    }
    

    修正代码:

    package main
    
    import "fmt"
    
    func main() {  
        x := []string{"a","b","c"}
    
        for _, v := range x {
            fmt.Println(v) //prints a, b, c
        }
    }
  • 相关阅读:
    groovy-搭建环境
    isAssignableFrom
    H5调用摄像头
    php生成唯一id
    剑指Offer刷题日常
    ASCII码对照表
    用redis stream作队列的一些心得
    在 CAP 中使用 AOP ( Castle.DynamicProxy )
    office2019下载
    JVM调优浅谈
  • 原文地址:https://www.cnblogs.com/unqiang/p/6599198.html
Copyright © 2011-2022 走看看