深入探究for...range语句
1. 引言
在Go语言中,我们经常需要对数据集合进行遍历操作。对于数组来说,使用for语句可以很方便地完成遍历。然而,当我们面对其他数据类型,如map、string 和 channel 时,使用普通的for循环无法直接完成遍历。为了更加便捷地遍历这些数据类型,Go语言引入了for...range语句。本文将以数组遍历为起点,逐步介绍for...range语句在不同数据类型中的应用。
2. 问题引入
假设我们有一个整数数组,我们想要遍历数组中的每个元素并对其进行处理。在这种情况下,我们可以使用for语句结合数组的长度来实现遍历,例如:
package main
import "fmt"
func main() {
numbers := [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(numbers); i++ {
fmt.Println(numbers[i])
}
}
在上述代码中,我们定义了一个整数数组numbers,通过普通的for循环遍历了数组并打印了每个元素。然而,当我们遇到其他数据类型时,如map、string 或者channel时,此时使用for语句将无法简单对其进行遍历。那有什么方式能够方便完成对map,string等类型的遍历呢?
事实上,go语言中存在for....range语句,能够实现对这些类型的遍历,下面我们来仔细介绍下for...range。
3. 基本介绍
在Go语言中,for...range语句为遍历数组、切片、映射和通道等数据结构提供了一种便捷的方式。它隐藏了底层的索引或迭代器等细节,是Go语言为遍历各种数据结构提供的一种优雅而简洁的语法糖,使得遍历操作更加方便和直观。下面仔细简介使用for...range完成对切片, map, channel的遍历操作。
3.1 遍历切片
当使用for...range语句遍历切片时,它会逐个迭代切片中的元素,并将索引和对应的值赋值给指定的变量。示例代码如下:
numbers := [5]int{1, 2, 3, 4, 5}
for index, value := range numbers {
// 在这里处理 index 和 value
}
其中numbers 是我们要遍历的切片。index 是一个变量,它在每次迭代中都会被赋值为当前元素的索引(从0开始)。value 是一个变量,它在每次迭代中都会被赋值为当前元素的值。
如果只关注切片中的值而不需要索引,可以使用下划线 _ 替代索引变量名,以忽略它:
numbers := []int{1, 2, 3, 4, 5}
for _, value := range numbers {
fmt.Println("Value:", value)
}
这样,循环体只会打印出切片中的值而不显示索引。
通过for...range语句遍历切片,我们可以简洁而直观地访问切片中的每个元素,无需手动管理索引,使得代码更加简洁和易读。
3.2 遍历map
当使用for...range语句遍历map时,它会迭代映射中的每个键值对,并将键和对应的值赋值给指定的变量。示例代码如下:
students := map[string]int{
"Alice": 25,
"Bob": 27,
"Charlie": 23,
}
for key, value := range students {
// 在这里处理 key 和 value
}
这里for...range会遍历所有的键值对,无需我们去手动处理迭代器的逻辑,即可完成对map的遍历操作。
3.3 遍历string
当使用for...range语句遍历字符串时,它会逐个迭代字符串中的字符,并将每个字符的索引和值赋值给指定的变量。以下是遍历字符串的示例代码:
text := "Hello, 世界!"
for index, character := range text {
fmt.Printf("Index: %d, Character: %c\n", index, character)
}
输出结果为:
Index: 0, Character: H
Index: 1, Character: e
Index: 2, Character: l
Index: 3, Character: l
Index: 4, Character: o
Index: 5, Character: ,
Index: 6, Character:
Index: 7, Character: 世
Index: 10, Character: 界
需要注意的是,Go语言中的字符串是以UTF-8编码存储的,UTF-8是一种变长编码,不同的Unicode字符可能会占用不同数量的字节。而index的值表示每个字符在字符串中的字节索引位置,所以字符的索引位置并不一定是连续的。
这里通过for...range语句遍历字符串,我们可以方便地处理每个字符,无需手动管理索引和字符编码问题,使得处理字符串的逻辑更加简洁和易读。
3.4 遍历channel
当使用for...range语句遍历channel时,它会迭代通道中的每个值,直到通道关闭为止。下面是一个示例代码:
ch := make(chan int)
// 向通道写入数据的例子
go func() {
ch <- 1
ch <- 2
ch <- 3
close(ch)
}()
// 将输出 1 2 3
for value := range ch {
fmt.Println("Value:", value)
}
在示例中,我们向通道写入了3个整数值。然后,使用for...range语句遍历通道,从中获取每个值并进行处理。
需要注意的是,如果通道中没有数据可用,for...range语句会阻塞,直到有数据可用或通道被关闭。因此,当通道中没有数据时,它会等待数据的到达。
通过for...range语句遍历通道,可以非常方便得不断从channel中取出数据,然后对其进行处理。
4. 注意事项
for...range语句可以认为是go语言的一个语法糖,简化了我们对不同数据结构的遍历操作,但是使用for...range语句还是存在一些注意事项的,充分了解这些注意事项,能够让我们更好得使用该特性,下面我们将对其来进行叙述。
4.1 迭代变量是会被复用的
当使用for...range循环时,迭代变量是会被复用的。这意味着在每次循环迭代中,迭代变量都将被重用,而不是在每次迭代中创建一个新的迭代变量。
下面是一个简单的示例代码,演示了迭代变量被复用的情况:
package main
import "fmt"
func main() {
numbers := []int{1, 2, 3, 4, 5}
for _, value := range numbers {
go func() {
fmt.Print(strconv.Itoa(value) + " ")
}()
}
}
在上述代码中,我们使用for...range循环遍历切片numbers,并在每次循环迭代中创建一个匿名函数并启动一个goroutine。该匿名函数打印当前迭代的value变量。下面是一个可能的结果:
4 5 5 5 5
出现这个结果的原因,就是由于迭代变量被复用,所有的goroutine都会共享相同的value变量。当goroutine开始执行时,它们可能会读取到最后一次迭代的结果,而不是预期的迭代顺序。这会导致输出结果可能是重复的数字或者不按照预期的顺序输出。
如果不清楚迭代变量会被复用的特点,这个在某些场景下可能会导致意料之外结果的出现。因此,如果for...range循环中存在并发操作,延迟函数等操作时,同时也依赖于迭代变量的值,这个时候需要确保在循环迭代中创建新的副本,以避免意外的结果。
4.2 参与迭代的为range表达式的副本数据
对于for...range循环,是使用range表达式的副本数据进行迭代。这意味着迭代过程中对原始数据的修改,并不会对迭代的结果造成影响,一个简单的代码示例如下:
package main
import "fmt"
func main() {
numbers := [5]int{1, 2, 3, 4, 5}
for i, v := range numbers {
if i == 0 {
numbers[1] = 100 // 修改原始数据的值
numbers[2] = 200
}
fmt.Println("Index:", i, "Value:", v)
}
}
在上述代码中,我们使用for...range循环遍历数组numbers, 然后在循环体内修改了数组中元素的值。遍历结果如下:
Index: 0 Value: 1
Index: 1 Value: 2
Index: 2 Value: 3
Index: 3 Value: 4
Index: 4 Value: 5
可以看到,虽然在迭代过程中,对numbers进行遍历,但是并没有影响到遍历的结果。从这里也可以证明,参与迭代的为range表达式的副本数据,而不是副本数据。
如果循环中的操作,需要依赖中间修改后的数据结果,此时最好分成两个遍历,首先遍历数据,修改其中的数据,之后再遍历修改后的数据。对上述代码改进如下:
numbers := [5]int{1, 2, 3, 4, 5}
// 1. 第一个遍历修改数据
for i, _ := range numbers {
if i == 0 {
numbers[1] = 100 // 修改原始数据的值
numbers[2] = 200
}
}
// 2. 第二个遍历输出数据
for i, v := range numbers {
fmt.Println("Index:", i, "Value:", v)
}
这次遍历的结果,就是修改后的数据,如下:
Index: 0 Value: 1
Index: 1 Value: 100
Index: 2 Value: 200
Index: 3 Value: 4
Index: 4 Value: 5
4.3 map遍历顺序是不确定的
对于Go语言中的map类型,遍历其键值对时的顺序是不确定的,下面是一个简单代码的示例:
package main
import "fmt"
func main() {
data := map[string]int{
"apple": 1,
"banana": 2,
"cherry": 3,
}
for key, value := range data {
fmt.Println(key, value)
}
}
运行上述代码,每次输出的结果可能是不同的,即键值对的顺序是不确定的。有可能第一次运行的结果为:
banana 2
cherry 3
apple 1
然后第二次运行的结果又与第一次运行的结果不同,可能为:
apple 1
banana 2
cherry 3
从这个例子可以证明,对map进行遍历,其遍历顺序是不固定的,所以我们需要注意,不能依赖map的遍历顺序。
如果需要每次map中的数据按照某个顺序输出,此时可以先把key保存到切片中,对切片按照指定的顺序进行排序,之后遍历排序后的切片,并使用切片中的key来访问map中的value。此时map中的数据便能够按照指定的顺序来输出,下面是一个简单的代码代码示例:
package main
import (
"fmt"
"sort"
)
func main() {
data := map[string]int{
"apple": 1,
"banana": 2,
"cherry": 3,
}
// 创建保存键的切片
keys := make([]string, 0, len(data))
for key := range data {
keys = append(keys, key)
}
// 对切片进行排序
sort.Strings(keys)
// 按照排序后的键遍历map
for _, key := range keys {
value := data[key]
fmt.Println(key, value)
}
}
5. 总结
本文对Go语言中的for...range进行了基本介绍,首先从一个简单遍历问题出发,发现基本的for语句似乎无法简单实现对string,map等类型的遍历操作,从而引出了for...range语句。
接着我们仔细介绍了,如何使用for...range对string,map,channel等类型的遍历操作。然后我们再仔细介绍了使用for...range的三个注意事项,如参与迭代的为range表达式的副本数据。通过对这些注意事项的了解,我们能够更好得使用for...range语句,避免出现预料之外的情况。
基于以上内容,完成了对for...range的介绍,希望能帮助你更好地理解和使用这个重要的Go语言特性。
深入探究for...range语句的更多相关文章
- 深入探究javascript的 {} 语句块
今日学习解析json字符串,用到了一个eval()方法,解析字符串的时候为什么需要加上括号呢?摸不着头脑.原来javascript中{}语句块具有二义性,不加括号会出错,理解这种二义性对我们理解jav ...
- explain SQL语句()
坊间有传言:MySQL性能优化有个神器,叫做explain,它可以对select语句进行分析并且输出详细的select执行过程的详细信息,让开发者从这些信息中获得优化的思路. 下面来讲讲这个MySQL ...
- 实验比较python中的range和xrange
1 结论: 全用xrange,除非你需要使用返回的列表 2 实验一:性能对比 实验环境:win7 ,64位系统 python2.7 import time StartTime=time.time() ...
- go:channel(未完)
注:1)以下的所有讨论建立在包含整形元素的通道类型之上,即 chan int 2)对于“<-”我的理解是,它可能是一个操作符(接收操作符),也 可能是类型的一部分(如“chan<- in ...
- golang笔记——流程控制
条件语句 if ... else if ... else 语句,如: { fmt.Println(">100") } < num { fmt.Println(" ...
- 【GoLang】golang 闭包 closure 参数传递的蹊跷!
结论: 闭包函数可以直接引用外层代码定义的变量, 但是,注意,闭包函数里面引用的是变量的地址, 当goroutine被调度时,改地址的值才会被传递给goroutine 函数. 介绍 go的闭包是一个很 ...
- eval解析JSON字符串的一个小问题
之前写过一篇 关于 JSON 的介绍文章,里面谈到了 JSON 的解析.我们都知道,高级浏览器可以用 JSON.parse() API 将一个 JSON 字符串解析成 JSON 数据,稍微欠妥点的做法 ...
- Python基础2:流程控制语句 while / for循环
[ while 循环 ] 如果要计算1+2+3,我们可以直接写表达式: >>> 1 + 2 + 3 要计算1+2+3+...+10,勉强也能写出来. 但是,要计算1+2+3+...+ ...
- 【GoLang】50 个 Go 开发者常犯的错误
1. { 换行: Opening Brace Can't Be Placed on a Separate Line 2. 定义未使用的变量: Unused Variables 2. import ...
- go语言入门
Go语言最主要的特性: 自动垃圾回收 更丰富的内置类型 函数多返回值 错误处理 匿名函数和闭包 类型和接口 并发编程 反射 语言交互性 1.2.4 错误处理Go语言引入了3个关键字用 ...
随机推荐
- 构建基于深度学习神经网络协同过滤模型(NCF)的视频推荐系统(Python3.10/Tensorflow2.11)
毋庸讳言,和传统架构(BS开发/CS开发)相比,人工智能技术确实有一定的基础门槛,它注定不是大众化,普适化的东西.但也不能否认,人工智能技术也具备像传统架构一样"套路化"的流程,也 ...
- 垃圾回收之CMS、G1、ZGC对比
ZGC(The Z Garbage Collector)是JDK 11中推出的一款低延迟垃圾回收器,它的设计目标包括: 停顿时间不超过10ms: 停顿时间不会随着堆的大小,或者活跃对象的大小而增加: ...
- 在idea中将一个javase项目转成ee项目
在idea中将java项目转成web项目 1.创建一个java项目,new一个directory名为web(与src同级) 2. 为web指定根目录:ctrl+shift+alt+s,在module中 ...
- 亿级Web系统负载均衡几种实现方式
负载均衡(Load Balance)是集群技术(Cluster)的一种应用技术.负载均衡可以将工作任务分摊到多个处理单元,从而提高并发处理能力.目前最常见的负载均衡应用是Web负载均衡.根据实现的原理 ...
- 安装vue-lic
vue-cli是Vue.js开发的标准工具.它简化了程序员基于webppack创建工程化的Vue项目的过程.引用自vue-cli官网上的一句话:程序员可以专注在撰写应用上,而不必花好几天去纠结webp ...
- hasOwnProperty的作用、配合for in使用 、key in Object判读key
我们都知道,对象以 key|value的形式存在 它和数组一样可以遍历,对象可以通过for in 去遍历,拿到遍历对象的所有key 某些idea在使用for in 时,提示代码片段中就有出现以下这种情 ...
- Python-解三元一次方程
需要解的方程组为: x + y + z = 26 x - y = 1 2x - y + z = 18 下面进入代码实现: 1.导入数学计算库 numpy import numpy as np 2.生成 ...
- dataX源码学习
文章目录 前言 开始准备 运行配置 开始运行 JobContainer 1.进入init prepare schedule post阶段 this.invokeHooks(); 总结 前言 在用dat ...
- 讯飞星火大模型 与New Bing实测对比
昨天科大讯飞发布了讯飞星火认知大模型,在发布会现场实测大模型的7种核心能力,并发布了它在教育.办公.汽车.数字员工领域的应用成果.科大讯飞董事长刘庆峰表示:认知大模型展示了通用人工智能的曙光,讯飞星火 ...
- 【Docker】镜像管理
一.搜索镜像 1.在官方网站搜索镜像 Docker 官方镜像仓库:https://hub.docker.com/ 2.docker search 搜索镜像 Usage: docker search [ ...