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

