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

C语言中经常遇到的陷阱!好东西

本文来自:http://blog.csdn.net/zhzht19861011/article/details/17117819 0.前言           设备的可靠性涉及多个方面:稳定的...
  • dddd0216
  • dddd0216
  • 2016年05月17日 18:09
  • 3665

C语言常见陷阱之“纠缠不清的位域”

一.表达式求值--整数类型提升 问题: #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { char c; ...
  • f2016913
  • f2016913
  • 2016年12月01日 09:34
  • 1509

一起来谈谈,面试中的那些“陷阱问题”

1. 前言众所周知,我们在面试的时候,HR 会通过各种各样的问题来“刁难”你。但是面对这些问题,每个人的反应会各不相同,HR 也就能以此来推断出你平时大概是个什么样子的人,并考虑你是否合适这个职位。可...
  • MR_LP
  • MR_LP
  • 2016年12月21日 11:46
  • 2701

c语言编程中常见的陷阱和漏洞

  • 2011年05月19日 15:22
  • 4.68MB
  • 下载

C语言常见陷阱之“纠缠不清的位域”

一.表达式求值--整数类型提升 问题: #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { char c; ...
  • f2016913
  • f2016913
  • 2016年12月01日 09:34
  • 1509

Go语言 常见数据结构性能测试

在开发过程中,我们总是在设计不同的数据结构和不同的算法对数据进行组织和存取,有些场合我们可以用硬编码或者代码生成器来生成一组函数按条件返回数据,另外一些场合我们可以把数据存储在slice当中,需要查找...
  • abv123456789
  • abv123456789
  • 2014年04月20日 21:07
  • 1622

如何避开 Go 中的各种陷阱

Go 语言有一些我们常说的「坑」,有不少优秀的文章讨论过这些「坑」。这些文章所讨论的东西非常重要,尤其对 Go 的初学者来说,一不小心就掉进了这些「坑」里。...
  • sqn614
  • sqn614
  • 2017年05月03日 14:03
  • 376

Go for的三重陷阱

说明这篇for的三重陷阱取自我的另一篇博文Go学习笔记。为了方便快速的查找,将其复制了过来。for陷阱之循环变量 在使用匿名函数时,须特别注意在for词块中引入的循环变量。如果在匿名函数里保存了这个变...
  • u011304970
  • u011304970
  • 2017年05月24日 11:31
  • 688

NoSQL误用和常见陷阱分析

  • 2016年12月26日 14:52
  • 1MB
  • 下载

spring中@Transactional的各个参数的意思和常见的事务陷阱

Spring事务的传播行为 在service类前加上@Transactional,声明这个service所有方法需要事务管理。每一个业务方法开始时都会打开一个事务。 Spring默认情...
  • zxl0016
  • zxl0016
  • 2012年06月29日 09:55
  • 9960
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Go语言中的常见陷阱
举报原因:
原因补充:

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