Go 语言重新手到大神:每一个人都会踩的五十个坑 (1-12)

本文翻译自最近各类 Go 语言社区分享的不少的英文文档 50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs,小编第一眼觉得 50 Shades of Grey……html

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

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

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

“{”不能单独放在一行

级别:新手入门级app

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

补充: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 inforeach 熟悉的话,那么 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
    }
}

( ﹁ ﹁ ) 怎么有“大神”这个标签…… 本文续集会在今晚八点前发布,敬请期待。

相关文章
相关标签/搜索