1. 引言

在Go语言中,咱们常常须要对数据汇合进行遍历操作。对于数组来说,应用for语句能够很不便地实现遍历。然而,当咱们面对其余数据类型,如map、string 和 channel 时,应用一般的for循环无奈间接实现遍历。为了更加便捷地遍历这些数据类型,Go语言引入了for...range语句。本文将以数组遍历为终点,逐渐介绍for...range语句在不同数据类型中的利用。

2. 问题引入

假如咱们有一个整数数组,咱们想要遍历数组中的每个元素并对其进行解决。在这种状况下,咱们能够应用for语句联合数组的长度来实现遍历,例如:

package mainimport "fmt"func main() {    numbers := [5]int{1, 2, 3, 4, 5}    for i := 0; i < len(numbers); i++ {        fmt.Println(numbers[i])    }}

在上述代码中,咱们定义了一个整数数组numbers,通过一般的for循环遍历了数组并打印了每个元素。然而,当咱们遇到其余数据类型时,如mapstring 或者channel时,此时应用for语句将无奈简略对其进行遍历。那有什么形式可能不便实现对mapstring等类型的遍历呢?

事实上,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: HIndex: 1, Character: eIndex: 2, Character: lIndex: 3, Character: lIndex: 4, Character: oIndex: 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 3for 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 mainimport "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 mainimport "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: 1Index: 1 Value: 2Index: 2 Value: 3Index: 3 Value: 4Index: 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: 1Index: 1 Value: 100Index: 2 Value: 200Index: 3 Value: 4Index: 4 Value: 5

4.3 map遍历程序是不确定的

对于Go语言中的map类型,遍历其键值对时的程序是不确定的,上面是一个简略代码的示例:

package mainimport "fmt"func main() {        data := map[string]int{                "apple":  1,                "banana": 2,                "cherry": 3,        }        for key, value := range data {                fmt.Println(key, value)        }}

运行上述代码,每次输入的后果可能是不同的,即键值对的程序是不确定的。有可能第一次运行的后果为:

banana 2cherry 3apple 1

而后第二次运行的后果又与第一次运行的后果不同,可能为:

apple 1banana 2cherry 3

从这个例子能够证实,对map进行遍历,其遍历程序是不固定的,所以咱们须要留神,不能依赖map的遍历程序。

如果须要每次map中的数据依照某个程序输入,此时能够先把key保留到切片中,对切片依照指定的程序进行排序,之后遍历排序后的切片,并应用切片中的key来拜访map中的value。此时map中的数据便可能依照指定的程序来输入,上面是一个简略的代码代码示例:

package mainimport (        "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语句仿佛无奈简略实现对stringmap等类型的遍历操作,从而引出了for...range语句。

接着咱们认真介绍了,如何应用for...rangestring,map,channel等类型的遍历操作。而后咱们再认真介绍了应用for...range的三个注意事项,如参加迭代的为range表达式的正本数据。通过对这些注意事项的理解,咱们可能更好得应用for...range语句,避免出现意料之外的状况。

基于以上内容,实现了对for...range的介绍,心愿能帮忙你更好地了解和应用这个重要的Go语言个性。