浅析Go语言中的Range关键字
作者:daisy 发布时间:2024-02-10 10:57:42
前言
相信用过Range的朋友们都知道,Go语言中的range关键字使用起来非常的方便,它允许你遍历某个slice或者map,并通过两个参数(index
和value
),分别获取到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)
}
}
总结
猜你喜欢
- 一、固定费用问题案例解析1.1、固定费用问题(Fixed cost problem)固定费用问题,是指求解生产成本最小问题时,总成本包括固定
- 如何做一个分页程序? 这在ASP中确实容易实现,但需要技巧,看看下面的分页代码和说明: <angu
- 本文实例为大家分享了python批量转换图片为黑白的具体代码,供大家参考,具体内容如下用到的库:OpenCV、osimport cv2imp
- 使用PDB的方式有两种:1. 单步执行代码,通过命令 python -m pdb xxx.py 启动脚本,进入单步执行模式 pdb
- 前言在AI领域,来快速实现一个idea:前后端开发+部署+展现,如果走传统的前后端分离开发+服务器docker部署等方式,会很重且入门成本很
- 这篇文章主要介绍了python线程join方法原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 导入pandas模块:import pandas as pd使用import读入pandas模块,并且为了方便使用其缩写pd指代。读入待处理
- 一.入参解析库 argparse有时候写Python脚本,需要处理入参[-h][-v][-F]...等情况,如果自己来解析的话,会花费很多时
- AJAX:Asynchronous JavaScript And XML--------异步JavaScript和XML
- 对于注入而言,错误提示是极其重要。所谓错误提示是指和正确页面不同的结果反馈,高手是很重视这个一点的,这对于注入点的精准判断至关重要。本问讨论
- 什么是内存管理机制python中创建的对象的时候,首先会去申请内存地址,然后对对象进行初始化,所有对象都会维护在一个叫做refchain的双
- 原作者:Jason MannInternet Magazine showed that people do not read on the
- RPCHandler 和 R * roxy 的基本思路是很比较简单的。 如果一个客户端想要调用一个远程函数,比如 foo(1, 2, z=3
- 一、项目视图分析通过上图,我们可以看到,一个完整的项目,基本包括三个部分:用户视图层、接口层、数据处理层,其中,用户视图层是用来接收用户的数
- 前言很多时候我们要用到图片上传功能,如果图片一直用放在别的网站上,通过加载网址的方式来显示的话其实也挺麻烦的,我们通过使用 django-f
- 基本思路:首先用开发者工具找到需要提取数据的标签列利用xpath定位需要提取数据的列表然后再逐个提取相应的数据:保存数据到csv:利用开发者
- 说明1、方差检验是用来比较两个或多个变量数据的样本,以确定它们之间的差异是简单随机的,或者是由于过程之间的显著统计差异造成的。2、自变量X是
- 英文文档:setattr(object, name, value)This is the counterpart of getattr().
- webargs是一个用于解析和验证HTTP请求对象的Python库,内置了对流行web框架的支持,包括Flask、Django、Bottle
- 一般情况下,局域网里的终端比如本地服务器设置静态IP的好处是可以有效减少网络连接时间,原因是过程中省略了每次联网后从DHCP服务器获取IP地