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語言特性,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555046.html
標籤:Go
上一篇:vs2022 wxWidgets
下一篇:返回列表
