原文:Common Gotchas in Go
作者:Mike JS. Choi
翻译:雁惊寒
摘要:本文介绍了Go初学者很可能会遇到的三个常见陷阱。以下是译文。
我最近开发了我的第一个真正的Go程序。它叫“Fix All Conflicts(译者注:修复所有的冲突)”,或简称为fac。这是一个简单易用的控制台程序,用于解决git合并冲突。我之所以开发这么个工具,是因为我一直都没有找到一个好用的合并工具。
开发的过程非常有意思,我在这个过程中学到了很多东西。所以,我决定记录下初学者很可能会遇到的一些常见“陷阱”!
有时候,地鼠很可能相当有侵略性。
1. Range
range
函数是Go中最常用的函数之一。下面是range
函数的使用示例。请注意,基于一些疯狂的原因,我们决定让动物园里所有的动物都拥有999
条腿。
type Animal struct {
name string
legs int
}
func main() {
zoo := []Animal{ Animal{ "Dog", 4 },
Animal{ "Chicken", 2 },
Animal{ "Snail", 0 },
}
fmt.Printf("-> Before update %v\n", zoo)
for _, animal := range zoo {
// Oppps! `animal` is a copy of an element
animal.legs = 999
}
fmt.Printf("\n-> After update %v\n", zoo)
}
上面的代码看起来没什么问题。但是,你可能会惊讶地发现两个fmt.Printf()
语句打印出来的结果是相同的。
-> Before update [{Dog 4} {Chicken 2} {Snail 0}]
-> After update [{Dog 4} {Chicken 2} {Snail 0}]
教训
range
的value属性(这里是animal
)是zoo
的值的一个副本,而不是指向zoo
中的值的指针。
修复
要修改数组中元素的值,我们必须通过它的指针来修改。
for idx, _ := range zoo {
zoo[idx].legs = 999
}
这个看起来可能很平常,但你可能会惊讶地发现这是最常见的错误之一。
2. The … thingy
你可能会在C语言中使用…
关键字来创建变长参数函数, 变长参数函数接受数量或类型可变的参数。
在C语言中,你必须调用va_arg
宏来访问可选参数。如果用其他方式来使用可变参数,编译器就会报错。
int add_em_up (int count,...) {
...
va_start (ap, count); /* Initialize the argument list */
for (i = 0; i < count; i++)
sum += va_arg(ap, int); /* Get the next argument value */
va_end (ap); /* Clean up */
return sum
}
然而,在Go中,情况有点相似,但又有很大的不同。下面是Go中的一个可变参数函数myFprint
。请注意它是如何使用可变参数a
的。
func myFprint(format string, a ...interface{}) {
if len(a) == 0 {
fmt.Printf(format)
} else {
// ⚠️ `a` should be `a...`
fmt.Printf(format, a)
// ✅
fmt.Printf(format, a...)
}
}
func main() {
myFprint("%s : line %d\n", "file.txt", 49)
}
[file.txt %!s(int=49)] : line %!d(MISSING)
file.txt : line 49
你可能会认为编译器会因为我们错误地使用了可变参数a
而报错。但是,请注意,fmt.Sprintf
只是用了a
中的第一个参数。
教训
在Go中,可变参数函数会被编译器转换成slices
这意味着可变参数a
实际上只是一个slice。正因为如此,下面的代码是完全正确的。
// `a` is just a slice!
for _, elem := range a {
fmt.Println(elem)
}
修复
记住,在使用可变参数的地方,请输入三个点(…)!
3. Slicing 切片
如果你了解Python中的slicing的话,你应该会知道Python中的slicing其实是给了你一个新的列表,该列表中的元素是对复制过去的元素的引用。因此,Python的代码是这样的。
a = [1, 2, 3]
b = a[:2] # �� 完全是一个新的list
b[0] = 999
>>> a
[1, 2, 3]
>>> b
[999, 2]
但是,如果你在Go中编写同样的代码的话,就会遇到其他问题。
func main() {
data := []int{1,2,3}
slice := data[:2]
slice[0] = 999
fmt.Println(data)
fmt.Println(slice)
}
教训
在Go中,切片与原始片共享相同的数组空间及其容量。因此,如果更改切片中的元素,也会改变原始数组中的内容。
修复
如果你想得到一个单独的切片,有两个选择。
// Option #1
// appending elements to a nil slice
// `...` changes slice to arguments for the variadic function `append`
a := append([]int{}, data[:2]...)
// Option #1
// Create slice with length of 2
// copy(dest, src)
a := make([]int, 2)
copy(a, data[:2]
根据StackOverflow中的一篇文章所述,append
的速度比make. + copy
更快一些。
1月13日,SDCC 2017之数据库线上峰会即将强势来袭,秉承干货实料(案例)的内容原则,邀请了来自阿里巴巴、腾讯、微博、网易等多家企业的数据库专家及高校研究学者,围绕Oracle、MySQL、PostgreSQL、Redis等热点数据库技术展开,从核心技术的深挖到高可用实践的剖析,打造精华压缩式分享,举一反三,思辨互搏,报名及更多详情可点击此处查看。
有疑问加站长微信联系(非本文作者)