关于go:深入探究forrange语句

41次阅读

共计 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 循环遍历了数组并打印了每个元素。然而,当咱们遇到其余数据类型时,如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: 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 语句仿佛无奈简略实现对 stringmap 等类型的遍历操作,从而引出了 for...range 语句。

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

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

正文完
 0