浅析Go语言中的Range关键字
前言
相信用过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)
}
}
总结
以上就是关于Go语言中Range关键字的全部内容,这篇文章介绍的还是很详细的,相信本文会对大家学习Go语言具有一定的参考价值,如果有疑问大家可以留言交流,小编会尽快给大家回复的,也请大家继续支持脚本之家。
- 生产环境sql语句调优实战第四篇(r2笔记41天)
- 生产环境sql语句调优实战第五篇(r2笔记41天)
- python实现逻辑logistic回归:预测病马的死亡率
- 开发 | 图片数据集太少?看我七十二变,Keras Image Data Augmentation 各参数详解
- linux过滤空文件的命令总结(r2笔记40天)
- shell脚本自动化采集性能sql(r2笔记39天)
- R语言与点估计学习笔记(EM算法与Bootstrap法)
- 开发 | 为个人深度学习机器选择合适的配置
- 阿里音乐流行趋势预测竞赛数据清洗整合——纯python
- 生产环境sql语句调优实战第二篇(r2第38天)
- 生产环境sql语句调优实战第三篇(r2笔记38天)
- 简单易学的机器学习算法——K-Means算法
- 通过shell脚本定位性能sql和生成报告(r2笔记37天)
- VXFS启用异步IO导致的严重问题(r2笔记56天)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 使用gateway作服务网关
- 使用feign进行服务调用
- sentinel的实际应用
- SpringMVC异常的处理机制
- 刷面试题的正确姿势!Android400道面试题+通关知识宝典助你进大厂,查漏补缺
- 3分钟短文:Laravel路由加模型等于?
- TSF微服务无法查看JVM监控问题
- Mitmproxy 的安装与使用案例
- leetcode之Bigram分词
- TensorFlow2 开发指南 | 01 手写数字识别快速入门
- Spring 数据初始 H2 后进行数据查询提示 Schema not found 错误
- Discourse CentOS 8 全新安装手册
- H2 数据库插入时间的方法
- Spring 配置的 H2 控制台 frameOptions 导致无法访问
- Spring Boot 和 Hibernate 的 H2 数据库配置来进行启动测试