共计 4893 个字符,预计需要花费 13 分钟才能阅读完成。
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 语言个性。