Go语言中的常见陷阱

翻译 2018年01月09日 15:04:11

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

Go的50度灰:Golang新开发者要注意的陷阱和常见错误

目录 [−] 初级 开大括号不能放在单独的一行 未使用的变量 未使用的Imports 简式的变量声明仅可以在函数内部使用 使用简式声明重复声明变量 偶然的变量隐藏Ac...
  • cxlzxi
  • cxlzxi
  • 2015年12月05日 22:00
  • 1805

Go 语言中的 new() 和 make()的区别

本文是看了文章之后的心得。 在此感谢。概述Go 语言中的 new 和 make 一直是新手比较容易混淆的东西,咋一看很相似。不过解释两者之间的不同也非常容易。...
  • xiaorenwuzyh
  • xiaorenwuzyh
  • 2015年03月23日 09:36
  • 2704

Go语言学习笔记----与C语言的比较学习

最近学习了golang(go语言),其中大部分是和C相似的,记录一下不同的地方,需要注意1.go的左花括号“{”不能单独放在一行 出错代码:package main import "fmt" fun...
  • u012033124
  • u012033124
  • 2017年04月29日 21:37
  • 1064

从go语言中找&和*区别

*和&的区别 : & 是取地址符号 , 即取得某个变量的地址 , 如 ; &a *是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值...
  • sybnfkn040601
  • sybnfkn040601
  • 2017年01月19日 12:52
  • 2974

Go语言中的defer关键字

官方文档中关于defer语句的解释: defer语句延迟执行一个函数,该函数被推迟到当包含它的程序返回时(包含它的函数 执行了return语句/运行到函数结尾自动返回/对应的goroutine p...
  • qwertyupoiuytr
  • qwertyupoiuytr
  • 2017年02月15日 21:34
  • 337

Go语言学习(十二)面向对象编程-结构体

1.结构体的初始化方式例如自定义一个结构体package mainimport( "fmt" ) type Rect struct{ //type和struct为关键字 x,y fl...
  • mChenys
  • mChenys
  • 2016年05月09日 22:26
  • 999

Go语言 Select 详细解读

Go Select 详解select语句让多个channel操作等待Go的select语句让程序线程在多个channel的操作上等待,select语句在goroutine 和channel结合的操作中...
  • GreatElite
  • GreatElite
  • 2017年02月16日 12:11
  • 2841

我为什么放弃Go语言

有好几次,当我想起来的时候,总是会问自己:我为什么要放弃Go语言?这个决定是正确的吗?是明智和理性的吗?其实我一直在认真思考这个问题。 开门见山地说,我当初放弃Go语言(golang),就是因为...
  • u012860063
  • u012860063
  • 2014年04月21日 21:54
  • 1792

Go语言跳转语句

跳转语句goto,break,continue 三个语法都可以配合标签使用 标签名区分大小写,若不使用会造成编译错误 break和continue配合标签可用于多层循环的跳出 goto是调整执...
  • lengyuezuixue
  • lengyuezuixue
  • 2017年11月01日 12:07
  • 65

GO语言 代码的嵌套——各种状态的组合

原文链接地址 clipperhouse.com
  • abv123456789
  • abv123456789
  • 2014年04月18日 13:32
  • 1571
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Go语言中的常见陷阱
举报原因:
原因补充:

(最多只允许输入30个字)