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 语句中的短声明变量形式“迷惑”,简单地认为每次迭代都会重新声明两个新的变量 iv。但事实上,这些循环变量在 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)
}

通过等价转换后的代码,我们可以清晰地看到循环变量 iv 在每次迭代时的重用。而 Goroutine 执行的闭包函数引用了它的外层包裹函数中的变量 iv,这样,变量 iv 在主 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 中的数组相比,又是有一些不同的

  1. Go 中的数组是值类型,换句话说,如果你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份;
  2. 如果 Go 中的数组作为函数的参数,那么实际传递的参数是一份数组的拷贝,而不是数组的指针。这个和 C 要区分开。因此,在 Go 中如果将数组作为函数的参数传递的话,那效率就肯定没有传递指针高了;
  3. 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 作用于字符串、数组、切片、字典、通道相关推荐

  1. Python 学习笔记 列表 range() xxx XXX

    Python 学习笔记 列表 range() xxx XXX print("-" * 30) for value in range(1, 5):print(value)number ...

  2. Python学习笔记第二十九天(N维数组(ndarray))

    Python学习笔记第二十九天 N维数组(ndarray) 构建阵列 索引阵列 ndarray的内部内存布局 阵列属性 内存布局 数据类型 其他属性 阵列接口 ctypes外部功能接口 Array方法 ...

  3. 数据结构与算法学习笔记之 从0编号的数组

    数据结构与算法学习笔记之 从0编号的数组 前言 数组看似简单,但掌握精髓的却没有多少:他既是编程语言中的数据类型,又是最基础的数据结构: 一个小问题: 为什么数据要从0开始编号,而不是 从1开始呢? ...

  4. Java快速入门学习笔记8 | Java语言中的数组

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

  5. 【C语言】字符串数组按字典升序

    [C语言]字符串数组按字典升序 文章目录 [C语言]字符串数组按字典升序 一.使用strcpy深拷贝实现字符串交换 二.交换字符指针数组中的指针位置,实现字符串交换 在使用C语言操作字符串时,容易出现 ...

  6. 数据结构与算法 学习笔记(5):字符串

    数据结构与算法 学习笔记(5)- 字符串 本次笔记记录了LeetCode中关于字符串的一些问题,并给出了相应的思路说明和代码.题目编号与LeetCode对应,方便查找. 题目1:LeetCode 13 ...

  7. Objective-C学习笔记2013[NSString]字符串[可变字符串中,加用app减用delete]

    Objective-C学习笔记 小常识: NS是Cocoa类对象类型的前缀,来源于史蒂夫-乔布斯被苹果开除那段时间建立的公司NeXT. @是Cocoa元素的前缀,很多地方我们会看到,比如接下来... ...

  8. (java)玩转算法系列-数据结构精讲[学习笔记](一)不要小瞧数组

    前言: 课程:玩转算法系列–数据结构精讲 更适合0算法基础入门到进阶(java版) 此处是个人学习笔记,用作回顾用途 不要小瞧数组 1.使用java中的数组 Main.java: public cla ...

  9. Python学习笔记(二)情侣关系的典范——————字典

    之前的学习笔记中,对列表的进行了介绍,之后对列表介绍的博客看了一下,感觉不是很好懂,所以,这一次用尽量简洁的语言将字典与列表进行对比,方便大家对两种数据的理解. 与列表相比,字典最大的优势在于查找关键 ...

最新文章

  1. (转)记忆杭州中的(非杭户籍人)
  2. 人与人的差距在于认知
  3. java dagger2_从零开始搭建一个项目(rxJava+Retrofit+Dagger2) --完结篇
  4. QT打开和保存文件对话框
  5. php网页微信登录验证失败,php 微信添加分账接收方-验证签名失败
  6. Bzoj 2749: [HAOI2012]外星人 欧拉函数,数论,线性筛
  7. “华为鸿蒙”操作系统下月发布?华为辟谣:请以官方声明为准
  8. Android 助力云计算
  9. 190517每日一句
  10. 一个大牛写的有关游戏的
  11. web技术基础---网站设计说明书
  12. ▼ 系列 | 漫谈数仓第四篇NO.4 『BI选型』
  13. cacheable更新_详解Spring缓存注解@Cacheable,@CachePut , @CacheEvict使用
  14. 华为模拟器ENSP router设备上display ip routing-table详解
  15. 20230316 作业
  16. Docker Hub的使用以及配置阿里云镜像加速
  17. 数据网站 免费数据网站
  18. 远征日服·信喵之野望 按键精灵脚本6.高级自动抽吉
  19. bbs.php ww1.dzxa.me_bbs论坛小结
  20. 2019AMC美国数学竞赛历年晋级分数线cutoff/DHR公布

热门文章

  1. jquery.raty评星插件
  2. jquery autocomplete demo
  3. Transformer的PyTorch实现
  4. Tensorflow安装问题: Could not find a version that satisfies the requirement tensorflow pip命令...
  5. LeetCode简单题之七进制数
  6. Node.js 简单入门
  7. 单周期十条指令CPU设计与verilog实现(Modelsim)
  8. Arm Cortex-M23 MCU,Arm Cortex-M33 MCU与RISC-V MCU技术
  9. LLVM Backend技术
  10. PyTorch 自动微分示例