分享
  1. 首页
  2. 文章

Go语言中的常见陷阱

dev_csdn · · 1655 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

原文: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等热点数据库技术展开,从核心技术的深挖到高可用实践的剖析,打造精华压缩式分享,举一反三,思辨互搏,报名及更多详情可点击此处查看
这里写图片描述


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:dev_csdn

查看原文:Go语言中的常见陷阱

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1655 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏