Go 学习笔记(29)— range 作用于字符串、数组、切片、字典、通道
1. 使用说明
range
应用于不同数据类型时,类似迭代器操作,返回 (索引, 值) 或 (键, 值)。 下表是对应的结构:
type | 1st value | 2nd value |
---|---|---|
string | index | s[index] |
array/slice | index | s[index] |
map | key | m[key] |
channel | element |
2. 使用示例
如果想忽略不想要的值时,可以使用 _
这个特殊变量。
package mainfunc main() {s := "abc"for i := range s { // 忽略 2nd value,支持 string/array/slice/map。println(s[i])}println("***************")for _, c := range s { // 忽略 index。println(c)}println("***************")for range s { // 忽略全部返回值,仅迭代。println("*")}println("***************")m := map[string]int{"a": 1, "b": 2}for k, v := range m { // 返回 (key, value)。println(k, v)}}
当 map
类型变量作为 range
表达式时,我们得到的 map
变量的副本与原变量指向同一个 map
,如果我们在循环的过程中,对 map
进行了修改,那么这样修改的结果是否会影响后续迭代呢?这个结果和我们遍历 map
一样,具有随机性。
3. 常见的坑
3.1 循环变量的重用
func main() {var m = []int{1, 2, 3, 4, 5} for i, v := range m {go func() {time.Sleep(time.Second * 3)fmt.Println(i, v)}()}time.Sleep(time.Second * 10)
}
输出结果:
4 5
4 5
4 5
4 5
4 5
预期结果
0 1
1 2
2 3
3 4
4 5
这是因为我们最初的“预期”本身就是错的。这里,初学者很可能会被 for range
语句中的短声明变量形式“迷惑”,简单地认为每次迭代都会重新声明两个新的变量 i
和 v
。但事实上,这些循环变量在 for range
语句中仅会被声明一次,且在每次迭代中都会被重用。
上面代码等价于下面
func main() {var m = []int{1, 2, 3, 4, 5} {i, v := 0, 0for i, v = range m {go func() {time.Sleep(time.Second * 3)fmt.Println(i, v)}()}}time.Sleep(time.Second * 10)
}
通过等价转换后的代码,我们可以清晰地看到循环变量 i
和 v
在每次迭代时的重用。而 Goroutine
执行的闭包函数引用了它的外层包裹函数中的变量 i
、v
,这样,变量 i
、v
在主 Goroutine
和新启动的 Goroutine
之间实现了共享,而 i
, v
值在整个循环过程中是重用的,仅有一份。在 for range
循环结束后,i = 4, v = 5,因此各个 Goroutine
在等待 3 秒后进行输出的时候,输出的是 i
,v
的最终值。
修改代码
func main() {var m = []int{1, 2, 3, 4, 5}for i, v := range m {go func(i, v int) {time.Sleep(time.Second * 3)fmt.Println(i, v)}(i, v)}time.Sleep(time.Second * 10)
}
3.2 参与循环的是 range 表达式的副本
数组是内置(build-in)类型,是一组同类型数据的集合,它是值类型,通过从 0 开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度。
当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数 len(array)
获取其长度。
注意,和 C
中的数组相比,又是有一些不同的
Go
中的数组是值类型,换句话说,如果你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份;- 如果
Go
中的数组作为函数的参数,那么实际传递的参数是一份数组的拷贝,而不是数组的指针。这个和C
要区分开。因此,在Go
中如果将数组作为函数的参数传递的话,那效率就肯定没有传递指针高了; array
的长度也是Type
的一部分,这样就说明[10]int
和[20]int
是不一样的;
内置类型切片(“动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
切片中有两个概念:一是 len
长度,二是 cap
容量,长度是指已经被赋过值的最大下标+1,可通过内置函数 len()
获得。容量是指切片目前可容纳的最多元素个数,可通过内置函数 cap()
获得。
切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
需要强调的是, range
会复制对象, range
返回的是每个元素的副本,而不是直接返回对该元素的引用。
package mainimport "fmt"func main() {a := [3]int{0, 1, 2}for i, v := range a { // index、value 都是从复制品中取出。if i == 0 { // 在修改前,我们先修改原数组。a[1], a[2] = 999, 999fmt.Println(a) // 确认修改有效,输出 [0, 999, 999]。}a[i] = v + 100 // 使用复制品中取出的 value 修改原数组。}fmt.Println(a) // 输出 [100, 101, 102]。}
建议改用引用类型,其底层数据不会被复制。
package mainfunc main() {s := []int{1, 2, 3, 4, 5}for i, v := range s { // 复制 struct slice { pointer, len, cap }。if i == 0 {s = s[:3] // 对 slice 的修改,不会影响 range。s[2] = 100 // 对底层数据的修改。}println(i, v)}}
输出:
0 1
1 2
2 100
3 4
4 5
其它示例
func main() {var a = [5]int{1, 2, 3, 4, 5}var r [5]intfmt.Println("original a =", a)for i, v := range a {if i == 0 {a[1] = 12a[2] = 13}r[i] = v}fmt.Println("after for range loop, r =", r)fmt.Println("after for range loop, a =", a)
}
期望输出结果:
original a = [1 2 3 4 5]
after for range loop, r = [1 12 13 4 5]
after for range loop, a = [1 12 13 4 5]
实际输出结果:
original a = [1 2 3 4 5]
after for range loop, r = [1 2 3 4 5]
after for range loop, a = [1 12 13 4 5]
修改循环迭代行代码为下面即可达到预期效果:
for i, v := range a[:] {# orfor i, v := range &a
Go 学习笔记(29)— range 作用于字符串、数组、切片、字典、通道相关推荐
- Python 学习笔记 列表 range() xxx XXX
Python 学习笔记 列表 range() xxx XXX print("-" * 30) for value in range(1, 5):print(value)number ...
- Python学习笔记第二十九天(N维数组(ndarray))
Python学习笔记第二十九天 N维数组(ndarray) 构建阵列 索引阵列 ndarray的内部内存布局 阵列属性 内存布局 数据类型 其他属性 阵列接口 ctypes外部功能接口 Array方法 ...
- 数据结构与算法学习笔记之 从0编号的数组
数据结构与算法学习笔记之 从0编号的数组 前言 数组看似简单,但掌握精髓的却没有多少:他既是编程语言中的数据类型,又是最基础的数据结构: 一个小问题: 为什么数据要从0开始编号,而不是 从1开始呢? ...
- Java快速入门学习笔记8 | Java语言中的数组
有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...
- 【C语言】字符串数组按字典升序
[C语言]字符串数组按字典升序 文章目录 [C语言]字符串数组按字典升序 一.使用strcpy深拷贝实现字符串交换 二.交换字符指针数组中的指针位置,实现字符串交换 在使用C语言操作字符串时,容易出现 ...
- 数据结构与算法 学习笔记(5):字符串
数据结构与算法 学习笔记(5)- 字符串 本次笔记记录了LeetCode中关于字符串的一些问题,并给出了相应的思路说明和代码.题目编号与LeetCode对应,方便查找. 题目1:LeetCode 13 ...
- Objective-C学习笔记2013[NSString]字符串[可变字符串中,加用app减用delete]
Objective-C学习笔记 小常识: NS是Cocoa类对象类型的前缀,来源于史蒂夫-乔布斯被苹果开除那段时间建立的公司NeXT. @是Cocoa元素的前缀,很多地方我们会看到,比如接下来... ...
- (java)玩转算法系列-数据结构精讲[学习笔记](一)不要小瞧数组
前言: 课程:玩转算法系列–数据结构精讲 更适合0算法基础入门到进阶(java版) 此处是个人学习笔记,用作回顾用途 不要小瞧数组 1.使用java中的数组 Main.java: public cla ...
- Python学习笔记(二)情侣关系的典范——————字典
之前的学习笔记中,对列表的进行了介绍,之后对列表介绍的博客看了一下,感觉不是很好懂,所以,这一次用尽量简洁的语言将字典与列表进行对比,方便大家对两种数据的理解. 与列表相比,字典最大的优势在于查找关键 ...
最新文章
- (转)记忆杭州中的(非杭户籍人)
- 人与人的差距在于认知
- java dagger2_从零开始搭建一个项目(rxJava+Retrofit+Dagger2) --完结篇
- QT打开和保存文件对话框
- php网页微信登录验证失败,php 微信添加分账接收方-验证签名失败
- Bzoj 2749: [HAOI2012]外星人 欧拉函数,数论,线性筛
- “华为鸿蒙”操作系统下月发布?华为辟谣:请以官方声明为准
- Android 助力云计算
- 190517每日一句
- 一个大牛写的有关游戏的
- web技术基础---网站设计说明书
- ▼ 系列 | 漫谈数仓第四篇NO.4 『BI选型』
- cacheable更新_详解Spring缓存注解@Cacheable,@CachePut , @CacheEvict使用
- 华为模拟器ENSP router设备上display ip routing-table详解
- 20230316 作业
- Docker Hub的使用以及配置阿里云镜像加速
- 数据网站 免费数据网站
- 远征日服·信喵之野望 按键精灵脚本6.高级自动抽吉
- bbs.php ww1.dzxa.me_bbs论坛小结
- 2019AMC美国数学竞赛历年晋级分数线cutoff/DHR公布
热门文章
- jquery.raty评星插件
- jquery autocomplete demo
- Transformer的PyTorch实现
- Tensorflow安装问题: Could not find a version that satisfies the requirement tensorflow pip命令...
- LeetCode简单题之七进制数
- Node.js 简单入门
- 单周期十条指令CPU设计与verilog实现(Modelsim)
- Arm Cortex-M23 MCU,Arm Cortex-M33 MCU与RISC-V MCU技术
- LLVM Backend技术
- PyTorch 自动微分示例