浅析Go语言中的Range关键字

作者:daisy 时间:2024-02-10 10:57:42 

前言

相信用过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
0
投稿

猜你喜欢

  • VBScript运行出错信息涵义列表

    2007-08-12 18:03:00
  • fgetcvs在linux的问题

    2024-06-05 09:37:56
  • SQLServer 2008 新增T-SQL 简写语法

    2024-01-28 23:49:25
  • 详解Python进阶之切片的误区与高级用法

    2022-09-18 04:03:12
  • Django之Mode的外键自关联和引用未定义的Model方法

    2023-09-04 13:34:40
  • python 从远程服务器下载东西的代码

    2023-01-23 11:00:55
  • 如何将txt文本中的数据轻松导入MySQL表中

    2009-03-06 17:35:00
  • Asp中Scripting.TextStream 对象介绍

    2007-11-02 12:19:00
  • Tensorflow卷积实现原理+手写python代码实现卷积教程

    2021-12-21 20:12:52
  • 用XMLHTTP很好的一个例子

    2008-04-25 10:25:00
  • Mysql如何通过binlog日志恢复数据详解

    2024-01-16 16:53:57
  • python palywright库基本使用

    2021-03-30 17:34:42
  • Flask接口签名sign原理与实例代码浅析

    2023-06-21 23:18:13
  • Python图像处理之简单画板实现方法示例

    2022-08-28 07:36:17
  • Python和Sublime整合过程图示

    2023-04-11 15:23:33
  • JavaScript实现简单省市联动

    2024-04-16 09:53:44
  • vue转react useEffect的全过程

    2024-05-02 16:32:22
  • PHP和JavaScrip分别获取关联数组的键值示例代码

    2023-06-16 05:30:51
  • mssql存储过程表名和字段名为变量的实现方法

    2011-12-01 07:59:13
  • php基于curl主动推送最新内容给百度收录的方法

    2023-11-22 04:46:44
  • asp之家 网络编程 m.aspxhome.com