失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Go 学习笔记(29)— range 作用于字符串 数组 切片 字典 通道

Go 学习笔记(29)— range 作用于字符串 数组 切片 字典 通道

时间:2024-06-16 19:40:26

相关推荐

Go 学习笔记(29)— range 作用于字符串 数组 切片 字典 通道

1. 使用说明

range应用于不同数据类型时,类似迭代器操作,返回 (索引, 值) 或 (键, 值)。 下表是对应的结构:

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 54 54 54 54 5

预期结果

0 11 22 33 44 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中的数组相比,又是有一些不同的

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 11 22 1003 44 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 作用于字符串 数组 切片 字典 通道》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。