深入探究Go語言中for?range語句
1. 引言
在Go語言中,我們經(jīng)常需要對數(shù)據(jù)集合進行遍歷操作。對于數(shù)組來說,使用for語句可以很方便地完成遍歷。然而,當我們面對其他數(shù)據(jù)類型,如map、string 和 channel 時,使用普通的for循環(huán)無法直接完成遍歷。為了更加便捷地遍歷這些數(shù)據(jù)類型,Go語言引入了for...range語句。本文將以數(shù)組遍歷為起點,逐步介紹for...range語句在不同數(shù)據(jù)類型中的應(yīng)用。
2. 問題引入
假設(shè)我們有一個整數(shù)數(shù)組,我們想要遍歷數(shù)組中的每個元素并對其進行處理。在這種情況下,我們可以使用for語句結(jié)合數(shù)組的長度來實現(xiàn)遍歷,例如:
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]) } }
在上述代碼中,我們定義了一個整數(shù)數(shù)組numbers
,通過普通的for循環(huán)遍歷了數(shù)組并打印了每個元素。然而,當我們遇到其他數(shù)據(jù)類型時,如map
、string
或者channel
時,此時使用for
語句將無法簡單對其進行遍歷。那有什么方式能夠方便完成對map
,string
等類型的遍歷呢?
事實上,go
語言中存在for....range
語句,能夠?qū)崿F(xiàn)對這些類型的遍歷,下面我們來仔細介紹下for...range
。
3. 基本介紹
在Go語言中,for...range
語句為遍歷數(shù)組、切片、映射和通道等數(shù)據(jù)結(jié)構(gòu)提供了一種便捷的方式。它隱藏了底層的索引或迭代器等細節(jié),是Go語言為遍歷各種數(shù)據(jù)結(jié)構(gòu)提供的一種優(yōu)雅而簡潔的語法糖,使得遍歷操作更加方便和直觀。下面仔細簡介使用for...range
完成對切片, map, channel的遍歷操作。
3.1 遍歷切片
當使用for...range
語句遍歷切片時,它會逐個迭代切片中的元素,并將索引和對應(yīng)的值賦值給指定的變量。示例代碼如下:
numbers := [5]int{1, 2, 3, 4, 5} for index, value := range numbers { // 在這里處理 index 和 value }
其中numbers
是我們要遍歷的切片。index
是一個變量,它在每次迭代中都會被賦值為當前元素的索引(從0開始)。value
是一個變量,它在每次迭代中都會被賦值為當前元素的值。
如果只關(guān)注切片中的值而不需要索引,可以使用下劃線 _
替代索引變量名,以忽略它:
numbers := []int{1, 2, 3, 4, 5} for _, value := range numbers { fmt.Println("Value:", value) }
這樣,循環(huán)體只會打印出切片中的值而不顯示索引。
通過for...range
語句遍歷切片,我們可以簡潔而直觀地訪問切片中的每個元素,無需手動管理索引,使得代碼更加簡潔和易讀。
3.2 遍歷map
當使用for...range
語句遍歷map
時,它會迭代映射中的每個鍵值對,并將鍵和對應(yīng)的值賦值給指定的變量。示例代碼如下:
students := map[string]int{ "Alice": 25, "Bob": 27, "Charlie": 23, } for key, value := range students { // 在這里處理 key 和 value }
這里for...range
會遍歷所有的鍵值對,無需我們?nèi)ナ謩犹幚淼鞯倪壿?,即可完成對map的遍歷操作。
3.3 遍歷string
當使用for...range
語句遍歷字符串時,它會逐個迭代字符串中的字符,并將每個字符的索引和值賦值給指定的變量。以下是遍歷字符串的示例代碼:
text := "Hello, 世界!" for index, character := range text { fmt.Printf("Index: %d, Character: %c\n", index, character) }
輸出結(jié)果為:
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字符可能會占用不同數(shù)量的字節(jié)。而index
的值表示每個字符在字符串中的字節(jié)索引位置,所以字符的索引位置并不一定是連續(xù)的。
這里通過for...range
語句遍歷字符串,我們可以方便地處理每個字符,無需手動管理索引和字符編碼問題,使得處理字符串的邏輯更加簡潔和易讀。
3.4 遍歷channel
當使用for...range語句遍歷channel
時,它會迭代通道中的每個值,直到通道關(guān)閉為止。下面是一個示例代碼:
ch := make(chan int) // 向通道寫入數(shù)據(jù)的例子 go func() { ch <- 1 ch <- 2 ch <- 3 close(ch) }() // 將輸出 1 2 3 for value := range ch { fmt.Println("Value:", value) }
在示例中,我們向通道寫入了3個整數(shù)值。然后,使用for...range
語句遍歷通道,從中獲取每個值并進行處理。
需要注意的是,如果通道中沒有數(shù)據(jù)可用,for...range
語句會阻塞,直到有數(shù)據(jù)可用或通道被關(guān)閉。因此,當通道中沒有數(shù)據(jù)時,它會等待數(shù)據(jù)的到達。
通過for...range
語句遍歷通道,可以非常方便得不斷從channel
中取出數(shù)據(jù),然后對其進行處理。
4. 注意事項
for...range
語句可以認為是go
語言的一個語法糖,簡化了我們對不同數(shù)據(jù)結(jié)構(gòu)的遍歷操作,但是使用for...range
語句還是存在一些注意事項的,充分了解這些注意事項,能夠讓我們更好得使用該特性,下面我們將對其來進行敘述。
4.1 迭代變量是會被復用的
當使用for...range
循環(huán)時,迭代變量是會被復用的。這意味著在每次循環(huán)迭代中,迭代變量都將被重用,而不是在每次迭代中創(chuàng)建一個新的迭代變量。
下面是一個簡單的示例代碼,演示了迭代變量被復用的情況:
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
循環(huán)遍歷切片numbers
,并在每次循環(huán)迭代中創(chuàng)建一個匿名函數(shù)并啟動一個goroutine。該匿名函數(shù)打印當前迭代的value
變量。下面是一個可能的結(jié)果:
4 5 5 5 5
出現(xiàn)這個結(jié)果的原因,就是由于迭代變量被復用,所有的goroutine都會共享相同的value
變量。當goroutine開始執(zhí)行時,它們可能會讀取到最后一次迭代的結(jié)果,而不是預(yù)期的迭代順序。這會導致輸出結(jié)果可能是重復的數(shù)字或者不按照預(yù)期的順序輸出。
如果不清楚迭代變量會被復用的特點,這個在某些場景下可能會導致意料之外結(jié)果的出現(xiàn)。因此,如果for...range
循環(huán)中存在并發(fā)操作,延遲函數(shù)等操作時,同時也依賴于迭代變量的值,這個時候需要確保在循環(huán)迭代中創(chuàng)建新的副本,以避免意外的結(jié)果。
4.2 參與迭代的為range表達式的副本數(shù)據(jù)
對于for...range
循環(huán),是使用range表達式的副本數(shù)據(jù)進行迭代。這意味著迭代過程中對原始數(shù)據(jù)的修改,并不會對迭代的結(jié)果造成影響,一個簡單的代碼示例如下:
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 // 修改原始數(shù)據(jù)的值 numbers[2] = 200 } fmt.Println("Index:", i, "Value:", v) } }
在上述代碼中,我們使用for...range
循環(huán)遍歷數(shù)組numbers
, 然后在循環(huán)體內(nèi)修改了數(shù)組中元素的值。遍歷結(jié)果如下:
Index: 0 Value: 1
Index: 1 Value: 2
Index: 2 Value: 3
Index: 3 Value: 4
Index: 4 Value: 5
可以看到,雖然在迭代過程中,對numbers
進行遍歷,但是并沒有影響到遍歷的結(jié)果。從這里也可以證明,參與迭代的為range
表達式的副本數(shù)據(jù),而不是副本數(shù)據(jù)。
如果循環(huán)中的操作,需要依賴中間修改后的數(shù)據(jù)結(jié)果,此時最好分成兩個遍歷,首先遍歷數(shù)據(jù),修改其中的數(shù)據(jù),之后再遍歷修改后的數(shù)據(jù)。對上述代碼改進如下:
numbers := [5]int{1, 2, 3, 4, 5} // 1. 第一個遍歷修改數(shù)據(jù) for i, _ := range numbers { if i == 0 { numbers[1] = 100 // 修改原始數(shù)據(jù)的值 numbers[2] = 200 } } // 2. 第二個遍歷輸出數(shù)據(jù) for i, v := range numbers { fmt.Println("Index:", i, "Value:", v) }
這次遍歷的結(jié)果,就是修改后的數(shù)據(jù),如下:
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) } }
運行上述代碼,每次輸出的結(jié)果可能是不同的,即鍵值對的順序是不確定的。有可能第一次運行的結(jié)果為:
banana 2
cherry 3
apple 1
然后第二次運行的結(jié)果又與第一次運行的結(jié)果不同,可能為:
apple 1
banana 2
cherry 3
從這個例子可以證明,對map進行遍歷,其遍歷順序是不固定的,所以我們需要注意,不能依賴map的遍歷順序。
如果需要每次map中的數(shù)據(jù)按照某個順序輸出,此時可以先把key保存到切片中,對切片按照指定的順序進行排序,之后遍歷排序后的切片,并使用切片中的key來訪問map中的value。此時map中的數(shù)據(jù)便能夠按照指定的順序來輸出,下面是一個簡單的代碼代碼示例:
package main import ( "fmt" "sort" ) func main() { data := map[string]int{ "apple": 1, "banana": 2, "cherry": 3, } // 創(chuàng)建保存鍵的切片 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. 總結(jié)
本文對Go語言中的for...range
進行了基本介紹,首先從一個簡單遍歷問題出發(fā),發(fā)現(xiàn)基本的for
語句似乎無法簡單實現(xiàn)對string
,map
等類型的遍歷操作,從而引出了for...range
語句。
接著我們仔細介紹了,如何使用for...range
對string
,map
,channel
等類型的遍歷操作。然后我們再仔細介紹了使用for...range
的三個注意事項,如參與迭代的為range表達式的副本數(shù)據(jù)。通過對這些注意事項的了解,我們能夠更好得使用for...range
語句,避免出現(xiàn)預(yù)料之外的情況。
以上就是深入探究Go語言中for range語句的詳細內(nèi)容,更多關(guān)于Go語言for range的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
gorm FirstOrCreate和受影響的行數(shù)實例
這篇文章主要介紹了gorm FirstOrCreate和受影響的行數(shù)實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12golang使用viper加載配置文件實現(xiàn)自動反序列化到結(jié)構(gòu)
這篇文章主要為大家介紹了golang使用viper加載配置文件實現(xiàn)自動反序列化到結(jié)構(gòu)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08Go語言服務(wù)器開發(fā)之簡易TCP客戶端與服務(wù)端實現(xiàn)方法
這篇文章主要介紹了Go語言服務(wù)器開發(fā)之簡易TCP客戶端與服務(wù)端實現(xiàn)方法,實例分析了基于Go語言實現(xiàn)的簡易服務(wù)器的TCP客戶端與服務(wù)器端實現(xiàn)技巧,需要的朋友可以參考下2015-02-02使用Gin框架搭建一個Go Web應(yīng)用程序的方法詳解
在本文中,我們將要實現(xiàn)一個簡單的 Web 應(yīng)用程序,通過 Gin 框架來搭建,主要支持用戶注冊和登錄,用戶可以通過注冊賬戶的方式創(chuàng)建自己的賬號,并通過登錄功能進行身份驗證,感興趣的同學跟著小編一起來看看吧2023-08-08