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
}
}
Go 语言从新手到大神:每个人都会踩的五十个坑(转)的更多相关文章
- C语言是菜鸟和大神的分水岭
作为一门古老的编程语言,C语言已经坚挺了好几十年了,初学者从C语言入门,大学将C语言视为基础课程.不管别人如何抨击,如何唱衰,C语言就是屹立不倒:Java.C#.Python.PHP.Perl 等都有 ...
- 新手求大神,有其他swit-case的思路写这个程序么?
两个程序: switch-case与if-else if的区别相同点:可以实现多分支结构;不同点:switch:一般只能用于等值比较.(可以进行范围运算???---学会用switch计算范围出炉的思路 ...
- jquery-图片轮播(新手请大神指教一下)
这是我刚学jquery写的,感觉效果不是很好. #scrollPics{ height: 330px; width: 980px; margin-bottom: 10px; overflow: hid ...
- C语言简单实现链栈基本几个功能(适合新手看,大神可指正)
接着上一次的顺序栈,今天我记一下链栈,因为我也是刚学不久,有些地方也稍稍理解不了,所以,一起共勉.我会用我自己结合教材上画的图,争取跟代码一起结合,用文字和图最大化的解释代码,这样的话 ...
- 7天教你精通变大神,学CAD关键还要掌握方法,纯干货新手要看
接触CAD初期是“痛苦”的,“煎熬”的,也是充满“成就”的. 痛苦是初学者怎么都不懂,需要学习的东西很多,整个过程是有些痛苦的. 煎熬也是每个求学阶段都会遇到的状态,眼睛会了,手不会,这个状态很难受. ...
- kaggle新手如何在平台学习大神的代码
原创:数据臭皮匠 [导读]Kaggle ,作为听说它很牛X但从未接触过的同学,可能仅仅了解这是一个参加数据挖掘比赛的网站,殊不知Kaggle也会有赛题相关的数据集, 比如我们熟知的房价预测.泰坦尼克 ...
- [OC笔记]@property之个人理解,大神轻拍
/** * 一个简单的对象 * * @author suzhen * */ public class SimpleObjcet { /** * 声明一个age字段 */ private Object ...
- 听justjavac大神live前端的入门与进阶小笔记
代码规范 代码强壮,调试代码 少用变量,多用常量 少用for循环,why循环,多用函数式, 不要直接去使用框架 刷题 提高编程思维 用js去做c语音的问题 阅读别人代码,去看别人的代码 a+b> ...
- 前端自学vs跟大神系统学?你看着办
前端自学vs跟大神系统学?你看着办 一名广告专业学生,在大三的时候对于广告行业的前景不是很看好,转而自学web前端,刚开始接触的前端语言是html(html应该不算编程语言),上手很容易,在w3csh ...
随机推荐
- js5:框架的使用,使框架之间无痕连接
原文发布时间为:2008-11-08 -- 来源于本人的百度文章 [由搬家工具导入] <html> <head> <base target="js4" ...
- 大话tomcat之tomcat连接mysql
研究主题:tomcat连接mysql 一.tomcat连接mysql的两种连接方式:简单连接(不使用连接池)使用tomcat连接池 二.简单分析:(简介部分摘自一篇博客,觉得写得非常赞,读了非常有快感 ...
- hdu 2680(最短路)
Choose the best route Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Ot ...
- webview reload 错误 Error Domain=WebKitErrorDomain Code=102 "Frame load interrupted"
在某个特定的场合先需要对WKWebView进行一次reload,但是直接回走到失败的代理方法中并报如下的错误 Error Domain=WebKitErrorDomain Code=102 " ...
- 牛客网 牛客小白月赛1 D.多项式乘法
D.多项式乘法 链接:https://www.nowcoder.com/acm/contest/85/D来源:牛客网 这个题想一下就能想出来了. 代码: 1 #include<iostrea ...
- 双端队列-deque【集vector与list于一身的牺牲内存换功能完善】
看到双端队列(deque)了,书上是这样说的:除了栈和队列外还有一种限定性数据结构是双端队列:双端队列是限定插入和删除操作在表的两端进行的线性表:尽管双端队列看起来似乎比栈和队列更灵活,但实际上在应用 ...
- Jenkins+ProGet+Windows Batch搭建全自动的内部包(NuGet)打包和推送及管理平台
这一篇文章是继http://www.cnblogs.com/EasonJim/p/5954155.html的升级版,由于CCNET已经过时,所以我把打包过程的CCNET工具换成Jenkins去实现,批 ...
- setTimeout改变this指向(****************************************)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- python __new__和__init__的区别
http://www.cnblogs.com/tuzkee/p/3540293.html 继承自object的新式类才有__new__ __new__至少要有一个参数cls,代表要实例化的类,此参数在 ...
- FTP经典常用命令
FTP命令是Internet用户使用最频繁的命令之一,不论是在DOS还是UNIX操作系统下使用FTP,都会遇到大量的FTP内部命令. 熟悉并灵活应用FTP的内部命令,可以大大方便使用者,并收到事半功倍 ...