浅析Go语言中的Range关键字

前言

相信用过Range的朋友们都知道,Go语言中的range关键字使用起来非常的方便,它允许你遍历某个slice或者map,并通过两个参数(indexvalue),分别获取到slice或者map中某个元素所在的index以及其值。

比如像这样的用法:

?

for index, value := range mySlice {
 fmt.Println("index: " + index)
 fmt.Println("value: " + value)
}

上面的例子足够清晰的描述了range的用法,实际上在使用range关键字的时候,还有一些需要特别注意的地方,有一些新手很容易入的”坑”。

为了说明这些”坑”,我们可以从下面这个稍复杂的例子说起:

?

type Foo struct {
 bar string
}
func main() {
 list := []Foo{
 {"A"},
 {"B"},
 {"C"},
 }
 list2 := make([]*Foo, len(list))
 for i, value := range list {
 list2[i] = &value
 }
 fmt.Println(list[0], list[1], list[2])
 fmt.Println(list2[0], list2[1], list2[2])
}

在这个例子中,我们干了下面的一些事情:

1、定义了一个叫做Foo的结构,里面有一个叫bar的field。随后,我们创建了一个基于Foo结构体的slice,名字叫list

2、我们还创建了一个基于Foo结构体指针类型的slice,叫做list2

3、在一个for循环中,我们试图遍历list中的每一个元素,获取其指针地址,并赋值到list2中index与之对应的位置。

4、最后,分别输出list与list2中的每个元素

从代码来看,理所当然,我们期望得到的结果应该是这样:

?

{A} {B} {C}
&{A} &{B} &{C}

但是结果却出乎意料,程序的输出是这样的:

?

{A} {B} {C}
&{C} &{C} &{C}

从结果来看,仿佛list2中的三个元素,都指向了list中的最后一个元素。这是为什么呢?问题就出在上面那一段for…range循环中。

在Go的for…range循环中,Go始终使用值拷贝的方式代替被遍历的元素本身,简单来说,就是for…range中那个value,是一个值拷贝,而不是元素本身。这样一来,当我们期望用&获取元素的指针地址时,实际上只是取到了value这个临时变量的指针地址,而非list中真正被遍历到的某个元素的指针地址。而在整个for…range循环中,value这个临时变量会被重复使用,所以,在上面的例子中,list2被填充了三个相同的指针地址,并且这三个地址都指向value,而在最后一次循环中,value被赋与了{c}的指针地址。因此,list2输出的时候显示出了三个&{c}

同样的,下面的写法,跟for…range的例子如出一辙:

?

var value Foo
for var i := 0; i < len(list); i++ {
 value = list[i]
 list2[i] = &value
}

如果我们输出list2的三个元素,结果同样是: &{C} &{C} &{C}

那么,怎样才是正确的写法呢?我们应该用index来访问for…range中真实的元素,并获取其指针地址:

?

for i, _ := range list {
 list2[i] = &list[i]
}

这样,输出list2中的元素,就能得到我们想要的结果(&{A} &{B} &{C})了。

实验代码如下:

?

package main
 
import "fmt"
 
type Foo struct {
 bar string
}
 
func main() {
 list := []Foo{
 {"A"},
 {"B"},
 {"C"},
 }
 
 list2 := make([]*Foo, len(list))
 
 //错误的例子
 for i, value := range list {
 list2[i] = &value
 }
 
 //正确的例子
 //for i, _ := range list {
 // list2[i] = &list[i]
 //}
 
 fmt.Println(list[0], list[1], list[2])
 fmt.Println(list2[0], list2[1], list2[2])
}

了解了range的正确使用姿势,那么我们下面这个例子也能迎刃而解了:

?

package main
import "fmt"
type MyType struct {
 field string
}
func main() {
 var array [10]MyType
 for _, e := range array {
 e.field = "foo"
 }
 for _, e := range array {
 fmt.Println(e.field)
 fmt.Println("--")
 }
}

平常写代码最常见的场景,就是我们需要在一个循环中修改被遍历元素的值。比如上面这个例子,我们希望能使用for…range循环,一次性将array中每个元素的field设置为”foo”。同样,因为range值拷贝的缘故,上面的程序什么都不会输出……

而正确的做法是:

?

for i, _ := range array {
 array[i].field = "foo"
}

通过index访问每个元素,并修改其field,这样,就能输出一堆”foo”了……

实验代码如下:

?

package main
 
import "fmt"
 
type MyType struct {
 field string
}
 
func main() {
 var array [10]MyType
 
 for i, _ := range array {
 array[i].field = "foo"
 }
 
 for _, e := range array {
 fmt.Println(e.field)
 }
}

总结

以上就是关于Go语言中Range关键字的全部内容,这篇文章介绍的还是很详细的,相信本文会对大家学习Go语言具有一定的参考价值,如果有疑问大家可以留言交流,小编会尽快给大家回复的,也请大家继续支持脚本之家。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2016-09-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏函数式编程语言及工具

Scalaz(2)- 基础篇:随意多态-typeclass, ad-hoc polymorphism

  scalaz功能基本上由以下三部分组成: 1、新的数据类型,如:Validation, NonEmptyList ... 2、标准scala类型的延伸类型,...

23970
来自专栏快乐八哥

JavaScript中的函数基础

函数能让开发者把JavaScript代码变得更有效率,而且更能重复吃用。函数解决思路:把大问题变成小问题,分割的思路。 函数的语法: 函数类型1: functi...

21060
来自专栏web前端-

JavaScript基础概述

    1.常量: 常量就是在程序运行过程中,不会发生变化的量,常量通常用来表示固定不变的量,比如圆周率,万有引力常量

8120
来自专栏hbbliyong

12个JavaScript技巧

在这篇文章中将给大家分享12个有关于JavaScript的小技巧。这些小技巧可能在你的实际工作中或许能帮助你解决一些问题。 使用!!操作符转换布尔值 有时候我们...

296100
来自专栏编程

从零开始学正则

问题:我怎么才能收到你们公众号平台的推送文章呢? 正则规范 正则表达式的英文是regular expression简称regex。正则表达式就是用事先定义好的一...

19880
来自专栏Golang语言社区

Golang方法和接口

在编程语言中,方法和函数的概念需要搞清楚。函数指的是一个封装的代码块,我们可以直接调用它,并返回结果。而方法其实也是一种函数,只不过方法需要和某个对象绑定。Go...

36770
来自专栏运维小白

9.3 grep(下)

grep用法 grep 'r.o' test.txt grep 'oo*' test.txt grep '.*' test.txt grep 'o{2}' /e...

19350
来自专栏与神兽党一起成长

前序遍历树

代码来自:pickle and cPickle – Python object serialization 首先树的结构,如图

17320
来自专栏GreenLeaves

JS框架设计之对象类型判断一种子模块

Javascript有两套数据类型,一套是基础数据类型,一套是对象数据类型。基础数据类型包括5种基本数据类型,分别是null,bool,undefined,nu...

20680
来自专栏C/C++基础

C/C++文字常量与常变量

在C/C++编程时,经常遇到以下几个概念:常量、文字常量、字面常量、符号常量、字符常量、常变量、字符串常量等,网上的资料描述的不尽相同,弄得大家云里雾里。本文将...

14420

扫码关注云+社区

领取腾讯云代金券