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
}
}

Go 语言从新手到大神:每个人都会踩的五十个坑(转)的更多相关文章

  1. C语言是菜鸟和大神的分水岭

    作为一门古老的编程语言,C语言已经坚挺了好几十年了,初学者从C语言入门,大学将C语言视为基础课程.不管别人如何抨击,如何唱衰,C语言就是屹立不倒:Java.C#.Python.PHP.Perl 等都有 ...

  2. 新手求大神,有其他swit-case的思路写这个程序么?

    两个程序: switch-case与if-else if的区别相同点:可以实现多分支结构;不同点:switch:一般只能用于等值比较.(可以进行范围运算???---学会用switch计算范围出炉的思路 ...

  3. jquery-图片轮播(新手请大神指教一下)

    这是我刚学jquery写的,感觉效果不是很好. #scrollPics{ height: 330px; width: 980px; margin-bottom: 10px; overflow: hid ...

  4. C语言简单实现链栈基本几个功能(适合新手看,大神可指正)

            接着上一次的顺序栈,今天我记一下链栈,因为我也是刚学不久,有些地方也稍稍理解不了,所以,一起共勉.我会用我自己结合教材上画的图,争取跟代码一起结合,用文字和图最大化的解释代码,这样的话 ...

  5. 7天教你精通变大神,学CAD关键还要掌握方法,纯干货新手要看

    接触CAD初期是“痛苦”的,“煎熬”的,也是充满“成就”的. 痛苦是初学者怎么都不懂,需要学习的东西很多,整个过程是有些痛苦的. 煎熬也是每个求学阶段都会遇到的状态,眼睛会了,手不会,这个状态很难受. ...

  6. kaggle新手如何在平台学习大神的代码

    原创:数据臭皮匠  [导读]Kaggle ,作为听说它很牛X但从未接触过的同学,可能仅仅了解这是一个参加数据挖掘比赛的网站,殊不知Kaggle也会有赛题相关的数据集, 比如我们熟知的房价预测.泰坦尼克 ...

  7. [OC笔记]@property之个人理解,大神轻拍

    /** * 一个简单的对象 * * @author suzhen * */ public class SimpleObjcet { /** * 声明一个age字段 */ private Object ...

  8. 听justjavac大神live前端的入门与进阶小笔记

    代码规范 代码强壮,调试代码 少用变量,多用常量 少用for循环,why循环,多用函数式, 不要直接去使用框架 刷题 提高编程思维 用js去做c语音的问题 阅读别人代码,去看别人的代码 a+b> ...

  9. 前端自学vs跟大神系统学?你看着办

    前端自学vs跟大神系统学?你看着办 一名广告专业学生,在大三的时候对于广告行业的前景不是很看好,转而自学web前端,刚开始接触的前端语言是html(html应该不算编程语言),上手很容易,在w3csh ...

随机推荐

  1. C#去除字符串的最后一个字符 与 JavaScript去除最后一个字符

    原文发布时间为:2009-04-29 -- 来源于本人的百度文章 [由搬家工具导入] 例子: C#.NET中去掉字符串的最后一个逗号 字符串为 (a,b,c,)最后一个逗号即c后面的逗号 答: &qu ...

  2. 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---22

    以下为阅读<Linux命令行与shell脚本编程大全 第3版>的读书笔记,为了方便记录,特地与书的内容保持同步,特意做成一节一次随笔,特记录如下:

  3. C#生成高清缩略图的方法

    /// <summary> /// 为图片生成缩略图 /// </summary> /// <param name="phyPath">原图片的 ...

  4. MPchartAndroid-柱状图

    mChart = (LineChart) findViewById(R.id.chart1); mChart.setDescription("");    //设置图表描述信息 m ...

  5. Codeforces Gym100971 K.Palindromization-回文串 (IX Samara Regional Intercollegiate Programming Contest Russia, Samara, March 13)

    这个题就是从字符串中删除一个字符,然后剩下的是回文串. 我写的代码虽然长得好看,但是循环里面的比较条件容易想错,太智障了... 一开始写的是计数比较,但是有的时候下标相同的也比较了,为了简单一些,直接 ...

  6. n!在k进制下的后缀0

    问n! 转化成k进制后的位数和尾数的0的个数.[UVA 10061 How many zeros and how many digits?] Given a decimal integer numbe ...

  7. 深入V8引擎-Time核心方法之win篇(1)

    上一篇的源码看得十分无趣,官方文档跟黑心棉一样渣. 这一篇讲讲windows操作系统上的时间戳实现,由于类的声明,方法解释上一篇都贴过了,所以这次直接上对应版本的代码. windows与mac很不一样 ...

  8. SecureCRT发送键盘按键对应表(转义字符)

    \r 发送回车(CR) \n 发送换行符(LF) \b 发送退格 \e 发送一个转义 \t 发送一个标签 \\ 发送一个反斜杠字符 \v 将剪贴板的内容粘贴到活动状态会话窗口 \p 暂停一秒钟

  9. mysql之日期函数

    写在前面 mysql的学习,断断续续,今天就接着学习mysql的日期操作吧. 系列文章 mysql之创建数据库,创建数据表 mysql之select,insert,delete,update mysq ...

  10. 14.【nuxt起步】-Pm2 和nuxt服务运行

    1.安装pm2 npm install pm2 -gd 2.启动 Pm2 start ./bin/www 3. pm2 save 4.Pm2 startup 5.Pm2 save修改 package. ...