亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go 循環(huán)結(jié)構(gòu)for循環(huán)使用教程全面講解

 更新時(shí)間:2023年10月12日 10:43:07   作者:賈維斯Echo  
這篇文章主要為大家介紹了Go 循環(huán)結(jié)構(gòu)for循環(huán)使用全面講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

一、for 循環(huán)介紹

日常編碼過程中,我們常常需要重復(fù)執(zhí)行同一段代碼,這時(shí)我們就需要循環(huán)結(jié)構(gòu)來幫助我們控制程序的執(zhí)行順序。一個(gè)循環(huán)結(jié)構(gòu)會(huì)執(zhí)行循環(huán)體中的代碼直到結(jié)尾,然后回到開頭繼續(xù)執(zhí)行。 主流編程語言都提供了對(duì)循環(huán)結(jié)構(gòu)的支持,絕大多數(shù)主流語言,比如:Python 提供了不止一種的循環(huán)語句,但 Go 卻只有一種,也就是 for 語句

二、for 循環(huán)結(jié)構(gòu)

2.1 基本語法結(jié)構(gòu)

Go語言的for循環(huán)的一般結(jié)構(gòu)如下:

for 初始語句;條件表達(dá)式;結(jié)束語句{
    循環(huán)體語句
}

  • 初始語句:在循環(huán)開始前執(zhí)行一次的初始化操作,通常用于聲明計(jì)數(shù)器或迭代變量的初始值。
  • 條件表達(dá)式:循環(huán)會(huì)在每次迭代之前檢查條件表達(dá)式,只有當(dāng)條件為真時(shí),循循環(huán)才會(huì)繼續(xù)執(zhí)行。如果條件為假,循環(huán)結(jié)束。
  • 結(jié)束語句:在每次迭代之后執(zhí)行的操作,通常用于更新計(jì)數(shù)器或迭代變量的值。

以下是一個(gè)示例,演示了不同類型的for循環(huán)基本用法:

var sum int
for i := 0; i < 10; i++ {
    sum += i
}
println(sum)

這種 for 語句的使用形式是 Go 語言中 for 循環(huán)語句的進(jìn)形式。我們用一幅流程圖來直觀解釋一下上面這句 for 循環(huán)語句的組成部分,以及各個(gè)部分的執(zhí)行順序:

從圖中我們看到,經(jīng)典 for 循環(huán)語句有四個(gè)組成部分(分別對(duì)應(yīng)圖中的①~④)。我們按順序拆解一下這張圖。

圖中①對(duì)應(yīng)的組成部分執(zhí)行于循環(huán)體(③ )之前,并且在整個(gè) for 循環(huán)語句中僅會(huì)被執(zhí)行一次,它也被稱為循環(huán)前置語句。我們通常會(huì)在這個(gè)部分聲明一些循環(huán)體(③ )或循環(huán)控制條件(② )會(huì)用到的自用變量,也稱循環(huán)變量或迭代變量,比如這里聲明的整型變量 i。與 if 語句中的自用變量一樣,for 循環(huán)變量也采用短變量聲明的形式,循環(huán)變量的作用域僅限于 for 語句隱式代碼塊范圍內(nèi)。

圖中②對(duì)應(yīng)的組成部分,是用來決定循環(huán)是否要繼續(xù)進(jìn)行下去的條件判斷表達(dá)式。和 if 語句的一樣,這個(gè)用于條件判斷的表達(dá)式必須為布爾表達(dá)式,如果有多個(gè)判斷條件,我們一樣可以由邏輯操作符進(jìn)行連接。當(dāng)表達(dá)式的求值結(jié)果為 true 時(shí),代碼將進(jìn)入循環(huán)體(③)繼續(xù)執(zhí)行,相反則循環(huán)直接結(jié)束,循環(huán)體(③)與組成部分④都不會(huì)被執(zhí)行。

前面也多次提到了,圖中③對(duì)應(yīng)的組成部分是 for 循環(huán)語句的循環(huán)體。如果相關(guān)的判斷條件表達(dá)式求值結(jié)構(gòu)為 true 時(shí),循環(huán)體就會(huì)被執(zhí)行一次,這樣的一次執(zhí)行也被稱為一次迭代(Iteration)。在上面例子中,循環(huán)體執(zhí)行的動(dòng)作是將這次迭代中變量 i 的值累加到變量 sum 中。

圖中④對(duì)應(yīng)的組成部分會(huì)在每次循環(huán)體迭代之后執(zhí)行,也被稱為循環(huán)后置語句。這個(gè)部分通常用于更新 for 循環(huán)語句組成部分①中聲明的循環(huán)變量,比如在這個(gè)例子中,我們?cè)谶@個(gè)組成部分對(duì)循環(huán)變量 i 進(jìn)行加 1 操作。

2.2 省略初始值

for 循環(huán)的初始語句可以被忽略,但是必須要寫初始語句后面的分號(hào)

i := 0
    for ; i < 10; i++ {
        fmt.Println(i)
    }

2.3 省略初始語句和結(jié)束語句

for循環(huán)的初始語句和結(jié)束語句都可以省略,例如:

func main() {
    var i int
    for i < 10 {
        fmt.Println(i)
        i++
    }
}

這種寫法類似于其他編程語言中的while,在while后添加一個(gè)條件表達(dá)式,滿足條件表達(dá)式時(shí)持續(xù)循環(huán),否則結(jié)束循環(huán)。

2.4 無限循環(huán)

無限循環(huán)是一種循環(huán)結(jié)構(gòu),它會(huì)一直執(zhí)行,而不受循環(huán)條件的限制,同時(shí)省略了初始語句,條件表達(dá)式,結(jié)束語句?;菊Z法格式如下:

for {
    循環(huán)體語句
}

它的形式等價(jià)于:

for true {
   // 循環(huán)體代碼
}

或者等價(jià)于:

for ; ; {
   // 循環(huán)體代碼
}

在日常使用時(shí),建議你用它的最簡形式,也就是for {...},更加簡單。

舉個(gè)栗子:

for {
        fmt.Println("這是一個(gè)死循環(huán)!")
    }

無限循環(huán)通常在編程中用于執(zhí)行需要持續(xù)運(yùn)行的任務(wù),如服務(wù)器監(jiān)聽、事件處理等。

2.5 for 循環(huán)支持聲明多循環(huán)變量

Go 語言的 for 循環(huán)支持聲明多循環(huán)變量,并且可以應(yīng)用在循環(huán)體以及判斷條件中,比如下面就是一個(gè)使用多循環(huán)變量的、稍復(fù)雜的例子:

var sum int
    for i, j, k := 0, 1, 2; (i < 20) && (j < 10) && (k < 30); i, j, k = i+1, j+1, k+5 {
        sum += (i + j + k)
        println(sum)
    }

在這個(gè)例子中,我們聲明了三個(gè)循環(huán)自用變量 i、j 和 k,它們共同參與了循環(huán)條件判斷與循環(huán)體的執(zhí)行。這段代碼的執(zhí)行流程解釋如下:

  • 開始時(shí),i 被初始化為 0,j 被初始化為 1,k 被初始化為 2,sum 被初始化為 0。
  • 進(jìn)入循環(huán)。在每次迭代中,首先檢查三個(gè)條件:i < 20、j < 10 和 k < 30。只有在這三個(gè)條件都為真時(shí),循環(huán)才會(huì)繼續(xù)執(zhí)行。
  • 在每次迭代中,計(jì)算 i + j + k 的和,并將結(jié)果添加到 sum 中。
  • 使用 println 函數(shù)打印 sum 的當(dāng)前值。
  • 繼續(xù)迭代,i、j 和 k 分別增加 1、1 和 5。
  • 重復(fù)步驟 2、3、4 直到其中一個(gè)條件不再滿足。在這種情況下,當(dāng) i 大于或等于 20、j 大于或等于 10 或 k 大于或等于 30 時(shí),循環(huán)結(jié)束。

2.6 小練習(xí):打印九九乘法表

for y := 1; y <= 9; y++ {
        // 遍歷, 決定這一行有多少列
        for x := 1; x <= y; x++ {
            fmt.Printf("%d*%d=%d ", x, y, x*y)
        }
        // 手動(dòng)生成回車
        fmt.Println()
    }

輸出結(jié)果如下:

1*1=1 
1*2=2 2*2=4 
1*3=3 2*3=6 3*3=9 
1*4=4 2*4=8 3*4=12 4*4=16 
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25 
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36 
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49 
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64 
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81

執(zhí)行過程如下:

  • for y := 1; y <= 9; y++:這是外部的for循環(huán),它初始化一個(gè)名為 y 的循環(huán)變量,從1開始,每次迭代遞增1,一直到 y 的值小于或等于9。
  • 內(nèi)部的for循環(huán) for x := 1; x <= y; x++:這是內(nèi)部的for循環(huán),用于控制每行的列數(shù)。循環(huán)變量 x 從1開始,每次迭代遞增1,一直到 x 的值小于或等于 y。這確保了每一行都只打印與行數(shù)相等或更小的列數(shù)。
  • fmt.Printf("%d*%d=%d ", x, y, x*y):在內(nèi)部循環(huán)中,這一行代碼用于打印每個(gè)乘法表達(dá)式。它使用 fmt.Printf 函數(shù),打印了一個(gè)格式化的字符串,其中 %d 是占位符,分別用 xy 和 x*y 的值替換。這將打印類似 "11=1 "、"12=2 "、"2*2=4 " 的格式。
  • fmt.Println():在內(nèi)部循環(huán)結(jié)束后,使用 fmt.Println 打印一個(gè)換行符,以將每行的輸出分開。

三、for range(鍵值循環(huán))

3.1 基本介紹

在編程中,經(jīng)常需要遍歷和操作集合(如數(shù)組、切片、映射等)中的元素。Go語言中可以使用for range遍歷數(shù)組、切片、字符串、map 及通道(channel)。 通過for range遍歷的返回值有以下規(guī)律:

  • 數(shù)組、切片、字符串返回索引和值。
  • map返回鍵和值。
  • 通道(channel)只返回通道內(nèi)的值。

3.2 基本語法格式

for range 循環(huán)的基本語法格式如下:

for key, value := range collection {
    // 循環(huán)體代碼,使用 key 和 value
}
  • key 是元素的索引或鍵。
  • value 是元素的值。
  • collection 是要遍歷的元素,如字符串、數(shù)組、切片、映射等。

舉個(gè)例子,首先我們使用for 循環(huán)基本形式:

var sl = []int{1, 2, 3, 4, 5}
for i := 0; i < len(sl); i++ {
    fmt.Printf("sl[%d] = %d\n", i, sl[i])
}

上面的例子中,我們使用循環(huán)前置語句中聲明的循環(huán)變量 作為切片下標(biāo),逐一將切片中的元素讀取了出來。不過,這樣就有點(diǎn)麻煩了。但是使用for range 循環(huán)后如下:

var sl = []int{1, 2, 3, 4, 5}
for i, v := range sl {
    fmt.Printf("sl[%d] = %d\n", i, v)
}

我們看到,for range 循環(huán)形式除了循環(huán)體保留了下來,其余組成部分都“不見”了。其實(shí)那幾部分已經(jīng)被融合到 for range 的語義中了

具體來說,這里的 i 和 v 對(duì)應(yīng)的是for 語句形式中循環(huán)前置語句的循環(huán)變量,它們的初值分別為切片 sl 的第一個(gè)元素的下標(biāo)值和元素值。并且,隱含在 for range 語義中的循環(huán)控制條件判斷為:是否已經(jīng)遍歷完 sl 的所有元素,等價(jià)于i < len(sl)這個(gè)布爾表達(dá)式。另外,每次迭代后,for range 會(huì)取出切片 sl 的下一個(gè)元素的下標(biāo)和值,分別賦值給循環(huán)變量 i 和 v,這與 for 經(jīng)典形式下的循環(huán)后置語句執(zhí)行的邏輯是相同的。

3.3 for range 語句幾個(gè)常見的“變種”

3.3.1 省略value

有時(shí)候,您可能只對(duì)元素中的index感興趣,而不需要值value。在這種情況下,您可以省略值部分,只使用鍵。示例如下:

fruits := []string{"apple", "banana", "cherry"}
for index := range fruits {
    fmt.Printf("Index: %d\n", index)
}

3.3.2 省略 key

如果我們不關(guān)心元素下標(biāo),只關(guān)心元素值,那么我們可以用空標(biāo)識(shí)符替代代表下標(biāo)值的變量 i。這里一定要注意,這個(gè)空標(biāo)識(shí)符不能省略,否則就與上面形式一樣了,Go 編譯器將無法區(qū)分:

for _, v := range sl {
  // ... 
}

3.3.3 同時(shí)省略 key 和 value

如果我們既不關(guān)心元素下標(biāo)值,也不關(guān)心元素值,那是否能寫成下面這樣呢:

for _, _ = range sl {
  // ... 
}

這種形式在語法上沒有錯(cuò)誤,就是看起來不太優(yōu)雅。Go 在Go 1.4 版本中就提供了一種優(yōu)雅的等價(jià)形式,后續(xù)直接使用這種形式就好了:

for range sl {
  // ... 
}

四、for 循環(huán)常用操作

4.1 遍歷數(shù)組、切片——獲得索引和元素

在遍歷代碼中,key 和 value 分別代表切片的下標(biāo)及下標(biāo)對(duì)應(yīng)的值。下面的代碼展示如何遍歷切片,數(shù)組也是類似的遍歷方法:

package main
import "fmt"
func main() {
    for key, value := range []int{1, 2, 3, 4} {
        fmt.Printf("key:%d value:%d\n", key, value)
    }
}
/*
代碼輸出如下:
key:0 value:1
key:1 value:2
key:2 value:3
key:3 value:4
 */

4.2 遍歷string 類型--獲得字符串

下面這段代碼展示了如何遍歷字符串:

var s = "中國人"
for i, v := range s {
    fmt.Printf("%d %s 0x%x\n", i, string(v), v)
}

輸出結(jié)果如下:

0 中 0x4e2d
3 國 0x56fd
6 人 0x4eba

我們看到:for range 對(duì)于 string 類型來說,每次循環(huán)得到的 v 值是一個(gè) Unicode 字符碼點(diǎn),也就是 rune 類型值,而不是一個(gè)字節(jié),返回的第一個(gè)值 i 為該 Unicode 字符碼點(diǎn)的內(nèi)存編碼(UTF-8)的第一個(gè)字節(jié)在字符串內(nèi)存序列中的位置。

4.3 遍歷map——獲得map的鍵和值

map 就是一個(gè)鍵值對(duì)(key-value)集合,最常見的對(duì) map 的操作,就是通過 key 獲取其對(duì)應(yīng)的 value 值。但有些時(shí)候,我們也要對(duì) map 這個(gè)集合進(jìn)行遍歷,這就需要 for 語句的支持了。

但在 Go 語言中,我們要對(duì) map 進(jìn)行循環(huán)操作,for range 是唯一的方法,for 經(jīng)典循環(huán)形式是不支持對(duì) map 類型變量的循環(huán)控制的。下面是通過 for range,對(duì)一個(gè) map 類型變量進(jìn)行循環(huán)操作的示例:

var m = map[string]int {
  "Rob" : 67,
    "Russ" : 39,
    "John" : 29,
}
for k, v := range m {
    println(k, v)
}

運(yùn)行這個(gè)示例,我們會(huì)看到這樣的輸出結(jié)果:

John 29
Rob 67
Russ 39

通過輸出結(jié)果我們看到:for range 對(duì)于 map 類型來說,每次循環(huán),循環(huán)變量 k 和 v 分別會(huì)被賦值為 map 鍵值對(duì)集合中一個(gè)元素的 key 值和 value 值。而且,map 類型中沒有下標(biāo)的概念,通過 key 和 value 來循環(huán)操作 map 類型變量也就十分自然了。

4.4 遍歷通道(channel)——接收通道數(shù)據(jù)

除了可以針對(duì) string、數(shù)組 / 切片,以及 map 類型變量進(jìn)行循環(huán)操作控制之外,for range 還可以與 channel 類型配合工作。

c := make(chan int)
go func() {
    c <- 1
    c <- 2
    c <- 3
    close(c)
}()
for v := range c {
    fmt.Println(v)
}

channel 是 Go 語言提供的并發(fā)設(shè)計(jì)的原語,它用于多個(gè) Goroutine 之間的通信。當(dāng) channel 類型變量作為 for range 語句的迭代對(duì)象時(shí),for range 會(huì)嘗試從 channel 中讀取數(shù)據(jù),使用形式是這樣的:

var c = make(chan int)
for v := range c {
   // ... 
}

在這個(gè)例子中,for range 每次從 channel 中讀取一個(gè)元素后,會(huì)把它賦值給循環(huán)變量 v,并進(jìn)入循環(huán)體。當(dāng) channel 中沒有數(shù)據(jù)可讀的時(shí)候, for range 循環(huán)會(huì)阻塞在對(duì) channel 的讀操作上。直到 channel 關(guān)閉時(shí),for range 循環(huán)才會(huì)結(jié)束,這也是 for range 循環(huán)與 channel 配合時(shí)隱含的循環(huán)判斷條件。

五、跳出循環(huán)與終止循環(huán)

5.1 continue 語句(繼續(xù)下次循環(huán))

5.1.1 continue 基本語法

首先,我們來看第一種場(chǎng)景。如果循環(huán)體中的代碼執(zhí)行到一半,要中斷當(dāng)前迭代,忽略此迭代循環(huán)體中的后續(xù)代碼,并回到 for 循環(huán)條件判斷,嘗試開啟下一次迭代,這個(gè)時(shí)候我們可以怎么辦呢?我們可以使用 continue 語句來應(yīng)對(duì)?;菊Z法如下:

for initialization; condition; update {
    // 循環(huán)體
    if someCondition {
        continue
    }
    // 其他循環(huán)體的代碼
}
  • initialization 是初始化語句,通常用于初始化循環(huán)變量。
  • condition 是循環(huán)條件,當(dāng)條件為真時(shí)繼續(xù)循環(huán),否則退出。
  • update 是在每次迭代后執(zhí)行的操作,通常用于更新循環(huán)變量。

帶標(biāo)簽的 continue 語句用于跳過當(dāng)前迭代中 if 語句中的 someCondition 滿足的部分,直接進(jìn)行下一次迭代。如果沒有標(biāo)簽,continue 將默認(rèn)跳過當(dāng)前循環(huán)的下一次迭代。

以下是一個(gè)示例,演示 continue 語句的基本語法:

var sum int
var sl = []int{1, 2, 3, 4, 5, 6}
for i := 0; i < len(sl); i++ {
    if sl[i]%2 == 0 {
        // 忽略切片中值為偶數(shù)的元素
        continue
    }
    sum += sl[i]
}
println(sum) // 9

這段代碼會(huì)循環(huán)遍歷切片中的元素,把值為奇數(shù)的元素相加,然后存儲(chǔ)在變量 sum 中。我們可以看到,在這個(gè)代碼的循環(huán)體中,如果我們判斷切片元素值為偶數(shù),就使用 continue 語句中斷當(dāng)前循環(huán)體的執(zhí)行,那么循環(huán)體下面的 sum += sl[i] 在這輪迭代中就會(huì)被忽略。代碼執(zhí)行流會(huì)直接來到循環(huán)后置語句i++,之后對(duì)循環(huán)條件表達(dá)式(i < len(sl))進(jìn)行求值,如果為 true,將再次進(jìn)入循環(huán)體,開啟新一次迭代。

5.1.2 帶標(biāo)簽的continue語句

Go 語言中的 continue 在 C 語言 continue 語義的基礎(chǔ)上又增加了對(duì) label 的支持。label 語句的作用,是標(biāo)記跳轉(zhuǎn)的目標(biāo)。帶標(biāo)簽的continue語句的基本語法格式如下:

loopLabel:
for initialization; condition; update {
    // 循環(huán)體
    if someCondition {
        continue loopLabel
    }
}
  • loopLabel 是一個(gè)用戶定義的標(biāo)簽(標(biāo)識(shí)符),用于標(biāo)記循環(huán)。
  • initialization 是初始化語句,通常用于初始化循環(huán)變量。
  • condition 是循環(huán)條件,當(dāng)條件為真時(shí)繼續(xù)循環(huán),否則退出。
  • update 是在每次迭代后執(zhí)行的操作,通常用于更新循環(huán)變量。

帶標(biāo)簽的continue語句用于在嵌套循環(huán)中指定要跳過的循環(huán),其工作方式是:如果某個(gè)條件滿足,執(zhí)行continue loopLabel,其中loopLabel是要跳過的循環(huán)的標(biāo)簽,它將控制流轉(zhuǎn)移到帶有相應(yīng)標(biāo)簽的循環(huán)的下一次迭代。如果沒有指定標(biāo)簽,continue將默認(rèn)跳過當(dāng)前循環(huán)的下一次迭代。

我們可以把上面的代碼改造為使用 label 的等價(jià)形式:

func main() {
    var sum int
    var sl = []int{1, 2, 3, 4, 5, 6}
loop:
    for i := 0; i < len(sl); i++ {
        if sl[i]%2 == 0 {
            // 忽略切片中值為偶數(shù)的元素
            continue loop
        }
        sum += sl[i]
    }
    println(sum) // 9
}

你可以看到,在這段代碼中,我們定義了一個(gè) label:loop,它標(biāo)記的跳轉(zhuǎn)目標(biāo)恰恰就是我們的 for 循環(huán)。也就是說,我們?cè)谘h(huán)體中可以使用 continue+ loop label 的方式來實(shí)現(xiàn)循環(huán)體中斷,這與前面的例子在語義上是等價(jià)的。不過這里僅僅是一個(gè)演示,通常我們?cè)谶@樣非嵌套循環(huán)的場(chǎng)景中會(huì)直接使用不帶 label 的 continue 語句。

而帶 label 的 continue 語句,通常出現(xiàn)于嵌套循環(huán)語句中,被用于跳轉(zhuǎn)到外層循環(huán)并繼續(xù)執(zhí)行外層循環(huán)語句的下一個(gè)迭代,比如下面這段代碼:

func main() {
    var sl = [][]int{
        {1, 34, 26, 35, 78},
        {3, 45, 13, 24, 99},
        {101, 13, 38, 7, 127},
        {54, 27, 40, 83, 81},
    }
outerloop:
    for i := 0; i < len(sl); i++ {
        for j := 0; j < len(sl[i]); j++ {
            if sl[i][j] == 13 {
                fmt.Printf("found 13 at [%d, %d]\n", i, j)
                continue outerloop
            }
        }
    }
}

在這段代碼中,變量 sl 是一個(gè)元素類型為[]int 的切片(二維切片),其每個(gè)元素切片中至多包含一個(gè)整型數(shù) 13。main 函數(shù)的邏輯就是在 sl 的每個(gè)元素切片中找到 13 這個(gè)數(shù)字,并輸出它的具體位置信息。

那這要怎么查找呢?一種好的實(shí)現(xiàn)方式就是,我們只需要在每個(gè)切片中找到 13,就不用繼續(xù)在這個(gè)切片的剩余元素中查找了。

我們用 for 經(jīng)典形式來實(shí)現(xiàn)這個(gè)邏輯。面對(duì)這個(gè)問題,我們要使用嵌套循環(huán),具體來說就是外層循環(huán)遍歷 sl 中的元素切片,內(nèi)層循環(huán)遍歷每個(gè)元素切片中的整型值。一旦內(nèi)層循環(huán)發(fā)現(xiàn) 13 這個(gè)數(shù)值,我們便要中斷內(nèi)層 for 循環(huán),回到外層 for 循環(huán)繼續(xù)執(zhí)行。

如果我們用不帶 label 的 continue 能不能完成這一功能呢?答案是不能。因?yàn)樗荒苤袛鄡?nèi)層循環(huán)的循環(huán)體,并繼續(xù)開啟內(nèi)層循環(huán)的下一次迭代。而帶 label 的 continue 語句是這個(gè)場(chǎng)景下的“最佳人選”,它會(huì)直接結(jié)束內(nèi)層循環(huán)的執(zhí)行,并回到外層循環(huán)繼續(xù)執(zhí)行。

這一行為就好比在外層循環(huán)放置并執(zhí)行了一個(gè)不帶 label 的 continue 語句。它會(huì)中斷外層循環(huán)中當(dāng)前迭代的執(zhí)行,執(zhí)行外層循環(huán)的后置語句(i++),然后再對(duì)外層循環(huán)的循環(huán)控制條件語句進(jìn)行求值,如果為 true,就將繼續(xù)執(zhí)行外層循環(huán)的新一次迭代。

5.2 goto(跳轉(zhuǎn)到指定標(biāo)簽)

goto語句通過標(biāo)簽進(jìn)行代碼間的無條件跳轉(zhuǎn)。goto語句可以在快速跳出循環(huán)、避免重復(fù)退出上有一定的幫助。Go語言中使用goto語句能簡化一些代碼的實(shí)現(xiàn)過程。 例如雙層嵌套的for循環(huán)要退出時(shí):

func main() {
    var breakFlag bool
    for i := 0; i < 10; i++ {
        for j := 0; j < 10; j++ {
            if j == 2 {
                // 設(shè)置退出標(biāo)簽
                breakFlag = true
                break
            }
            fmt.Printf("%v-%v\n", i, j)
        }
        // 外層for循環(huán)判斷
        if breakFlag {
            break
        }
    }
}

使用goto語句能簡化代碼:

func main() {
    for i := 0; i < 10; i++ {
        for j := 0; j < 10; j++ {
            if j == 2 {
                // 設(shè)置退出標(biāo)簽
                goto breakTag
            }
            fmt.Printf("%v-%v\n", i, j)
        }
    }
    return
    // 標(biāo)簽
breakTag:
    fmt.Println("結(jié)束for循環(huán)")
}

goto 是一種公認(rèn)的、難于駕馭的語法元素,應(yīng)用 goto 的代碼可讀性差、代碼難于維護(hù)還易錯(cuò)。雖然 Go 語言保留了 goto,在平常開發(fā)中,不推薦使用。

5.3 break(跳出循環(huán))

日常編碼中,我們還會(huì)遇到一些場(chǎng)景,在這些場(chǎng)景中,我們不僅要中斷當(dāng)前循環(huán)體迭代的進(jìn)行,還要同時(shí)徹底跳出循環(huán),終結(jié)整個(gè)循環(huán)語句的執(zhí)行。面對(duì)這樣的場(chǎng)景,continue 語句就不再適用了,Go 語言為我們提供了 break 語句來解決這個(gè)問題。

5.3.1 break基本語法

break語句的基本語法如下:

for initialization; condition; update {
    // 循環(huán)體
    if someCondition {
        break
    }
    // 其他循環(huán)體的代碼
}
  • initialization 是初始化語句,通常用于初始化循環(huán)變量。
  • condition 是循環(huán)條件,當(dāng)條件為真時(shí)繼續(xù)循環(huán),否則退出。
  • update 是在每次迭代后執(zhí)行的操作,通常用于更新循環(huán)變量。

當(dāng)在循環(huán)中執(zhí)行 break 語句時(shí),它會(huì)立即終止當(dāng)前的循環(huán),無論條件是否滿足,然后將控制流傳遞到循環(huán)之后的代碼。

我們來看下面這個(gè)示例中 break 語句的應(yīng)用:

func main() {
    var sl = []int{5, 19, 6, 3, 8, 12}
    var firstEven int = -1
    // 找出整型切片sl中的第一個(gè)偶數(shù)
    for i := 0; i < len(sl); i++ {
        if sl[i]%2 == 0 {
            firstEven = sl[i]
            break
        }
    }
    println(firstEven) // 6
}

這段代碼邏輯很容易理解,我們通過一個(gè)循環(huán)結(jié)構(gòu)來找出切片 sl 中的第一個(gè)偶數(shù),一旦找到就不需要繼續(xù)執(zhí)行后續(xù)迭代了。這個(gè)時(shí)候我們就通過 break 語句跳出了這個(gè)循環(huán)。

5.3.2 帶標(biāo)簽的break語法

 continue 語句一樣,Go 也 break 語句增加了對(duì) label 的支持。而且,和前面 continue 語句一樣,如果遇到嵌套循環(huán),break 要想跳出外層循環(huán),用不帶 label 的 break 是不夠,因?yàn)椴粠?nbsp;label 的 break 僅能跳出其所在的最內(nèi)層循環(huán)。要想實(shí)現(xiàn)外層循環(huán)的跳出,我們還需給 break 加上 label。所以,帶標(biāo)簽的 break 語句允許您從嵌套循環(huán)中跳出特定循環(huán),而不是默認(rèn)跳出當(dāng)前循環(huán)。帶標(biāo)簽的 break 語法如下:

loopLabel:
for initialization; condition; update {
    // 循環(huán)體
    if someCondition {
        break loopLabel
    }
    // 其他循環(huán)體的代碼
}
  • loopLabel 是用戶定義的標(biāo)簽(標(biāo)識(shí)符),用于標(biāo)記循環(huán)。
  • initialization 是初始化語句,通常用于初始化循環(huán)變量。
  • condition 是循環(huán)條件,當(dāng)條件為真時(shí)繼續(xù)循環(huán),否則退出。
  • update 是在每次迭代后執(zhí)行的操作,通常用于更新循環(huán)變量。

當(dāng)帶標(biāo)簽的 break 語句執(zhí)行時(shí),它會(huì)終止帶有相應(yīng)標(biāo)簽的循環(huán),而不是默認(rèn)的當(dāng)前循環(huán)。

我們來看一個(gè)具體的例子:

var gold = 38
func main() {
    var sl = [][]int{
        {1, 34, 26, 35, 78},
        {3, 45, 13, 24, 99},
        {101, 13, 38, 7, 127},
        {54, 27, 40, 83, 81},
    }
outerloop:
    for i := 0; i < len(sl); i++ {
        for j := 0; j < len(sl[i]); j++ {
            if sl[i][j] == gold {
                fmt.Printf("found gold at [%d, %d]\n", i, j)
                break outerloop
            }
        }
    }
}

這個(gè)例子和我們前面的帶 label 的 continue 語句的例子很像,main 函數(shù)的邏輯就是,在 sl 這個(gè)二維切片中找到 38 這個(gè)數(shù)字,并輸出它的位置信息。整個(gè)二維切片中至多有一個(gè)值為 38 的元素,所以只要我們通過嵌套循環(huán)發(fā)現(xiàn)了 38,我們就不需要繼續(xù)執(zhí)行這個(gè)循環(huán)了。這時(shí),我們通過帶有 label 的 break 語句,就可以直接終結(jié)外層循環(huán),從而從復(fù)雜多層次的嵌套循環(huán)中直接跳出,避免不必要的算力資源的浪費(fèi)。

六、for 循環(huán)常見“坑”與避坑指南

for 語句的常見“坑”點(diǎn)通常和 for range 這個(gè)“語法糖”有關(guān)。雖然 for range 的引入提升了 Go 語言的表達(dá)能力,也簡化了循環(huán)結(jié)構(gòu)的編寫,但 for range 也不是“免費(fèi)的午餐”,在開發(fā)中,經(jīng)常會(huì)遇到一些問題,下面我們就來看看這些常見的問題。

6.1 循環(huán)變量的重用

我們前面說過,for range 形式的循環(huán)語句,使用短變量聲明的方式來聲明循環(huán)變量,循環(huán)體將使用這些循環(huán)變量實(shí)現(xiàn)特定的邏輯,但你在剛開始學(xué)習(xí)使用的時(shí)候,可能會(huì)發(fā)現(xiàn)循環(huán)變量的值與你之前的“預(yù)期”不符,比如下面這個(gè)例子:

func main() {
    var m = []int{1, 2, 3, 4, 5}  
    for i, v := range m {
        go func() {
            time.Sleep(time.Second * 3)
            fmt.Println(i, v)
        }()
    }
    time.Sleep(time.Second * 10)
}

這個(gè)示例是對(duì)一個(gè)整型切片進(jìn)行遍歷,并且在每次循環(huán)體的迭代中都會(huì)創(chuàng)建一個(gè)新的 Goroutine(Go 中的輕量級(jí)協(xié)程),輸出這次迭代的元素的下標(biāo)值與元素值。

現(xiàn)在我們繼續(xù)看這個(gè)例子,我們預(yù)期的輸出結(jié)果可能是這樣的:

0 1
1 2
2 3
3 4
4 5

那實(shí)際輸出真的是這樣嗎?我們實(shí)際運(yùn)行輸出一下:

4 5
4 5
4 5
4 5
4 5

我們看到,Goroutine 中輸出的循環(huán)變量,也就是 i 和 v 的值都是 for range 循環(huán)結(jié)束后的最終值,而不是各個(gè) Goroutine 啟動(dòng)時(shí)變量 i 和 v 的值,與我們最初的“預(yù)期”不符,這是為什么呢?

這是因?yàn)槲覀冏畛醯?ldquo;預(yù)期”本身就是錯(cuò)的。這里,很可能會(huì)被 for range 語句中的短聲明變量形式“迷惑”,簡單地認(rèn)為每次迭代都會(huì)重新聲明兩個(gè)新的變量 i 和 v。但事實(shí)上,這些循環(huán)變量在 for range 語句中僅會(huì)被聲明一次,且在每次迭代中都會(huì)被重用。

基于隱式代碼塊的規(guī)則,我們可以將上面的 for range 語句做一個(gè)等價(jià)轉(zhuǎn)換,這樣可以幫助你理解 for range 的工作原理。等價(jià)轉(zhuǎn)換后的結(jié)果是這樣的:

func main() {
    var m = []int{1, 2, 3, 4, 5}  
    {
      i, v := 0, 0
        for i, v = range m {
            go func() {
                time.Sleep(time.Second * 3)
                fmt.Println(i, v)
            }()
        }
    }
    time.Sleep(time.Second * 10)
}

通過等價(jià)轉(zhuǎn)換后的代碼,我們可以清晰地看到循環(huán)變量 i 和 v 在每次迭代時(shí)的重用。而 Goroutine 執(zhí)行的閉包函數(shù)引用了它的外層包裹函數(shù)中的變量 i、v,這樣,變量 i、v 在主 Goroutine 和新啟動(dòng)的 Goroutine 之間實(shí)現(xiàn)了共享,而 i, v 值在整個(gè)循環(huán)過程中是重用的,僅有一份。在 for range 循環(huán)結(jié)束后,i = 4, v = 5,因此各個(gè) Goroutine 在等待 3 秒后進(jìn)行輸出的時(shí)候,輸出的是 i, v 的最終值。

那么如何修改代碼,可以讓實(shí)際輸出和我們最初的預(yù)期輸出一致呢?我們可以為閉包函數(shù)增加參數(shù),并且在創(chuàng)建 Goroutine 時(shí)將參數(shù)與 i、v 的當(dāng)時(shí)值進(jìn)行綁定,看下面的修正代碼:

func main() {
    var m = []int{1, 2, 3, 4, 5}
    for i, v := range m {
        go func(i, v int) {
            time.Sleep(time.Second * 3)
            fmt.Println(i, v)
        }(i, v)
    }
    time.Sleep(time.Second * 10)
}

這回的輸出結(jié)果與我們的預(yù)期就是一致的了。不過這里你要注意:你執(zhí)行這個(gè)程序的輸出結(jié)果的行序,可能與我的不同,這是由 Goroutine 的調(diào)度所決定的。

6.2 參與循環(huán)的是 range 表達(dá)式的副本

在 for range 語句中,range 后面接受的表達(dá)式的類型可以是數(shù)組、指向數(shù)組的指針、切片、字符串,還有 map 和 channel(需具有讀權(quán)限)。我們以數(shù)組為例來看一個(gè)簡單的例子:

func main() {
    var a = [5]int{1, 2, 3, 4, 5}
    var r [5]int
    fmt.Println("original a =", a)
    for i, v := range a {
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }
        r[i] = v
    }
    fmt.Println("after for range loop, r =", r)
    fmt.Println("after for range loop, a =", a)
}

這個(gè)例子說的是對(duì)一個(gè)數(shù)組 a 的元素進(jìn)行遍歷操作,當(dāng)處理下標(biāo)為 0 的元素時(shí),我們修改了數(shù)組 a 的第二個(gè)和第三個(gè)元素的值,并且在每個(gè)迭代中,我們都將從 a 中取得的元素值賦值給新數(shù)組 r。我們期望這個(gè)程序會(huì)輸出如下結(jié)果:

original a = [1 2 3 4 5]
after for range loop, r = [1 12 13 4 5]
after for range loop, a = [1 12 13 4 5]

但實(shí)際運(yùn)行該程序的輸出結(jié)果卻是:

original a = [1 2 3 4 5]
after for range loop, r = [1 2 3 4 5]
after for range loop, a = [1 12 13 4 5]

我們?cè)詾樵诘谝淮蔚^程,也就是 i = 0 時(shí),我們對(duì) a 的修改 (a[1] =12,a[2] = 13) 會(huì)在第二次、第三次迭代中被 v 取出,但從結(jié)果來看,v 取出的依舊是 a 被修改前的值:2 和 3。

為什么會(huì)是這種情況呢?原因就是參與 for range 循環(huán)的是 range 表達(dá)式的副本。也就是說,在上面這個(gè)例子中,真正參與循環(huán)的是 a 的副本,而不是真正的 a。

為了方便你理解,我們將上面的例子中的 for range 循環(huán),用一個(gè)等價(jià)的偽代碼形式重寫一下:

for i, v := range a' { //a'是a的一個(gè)值拷貝
    if i == 0 {
        a[1] = 12
        a[2] = 13
    }
    r[i] = v
}

現(xiàn)在真相終于揭開了:這個(gè)例子中,每次迭代的都是從數(shù)組 a 的值拷貝 a’中得到的元素。a’是 Go 臨時(shí)分配的連續(xù)字節(jié)序列,與 a 完全不是一塊內(nèi)存區(qū)域。因此無論 a 被如何修改,它參與循環(huán)的副本 a’依舊保持原值,因此 v 從 a’中取出的仍舊是 a 的原值,而不是修改后的值。

那么應(yīng)該如何解決這個(gè)問題,讓輸出結(jié)果符合我們前面的預(yù)期呢?在 Go 中,大多數(shù)應(yīng)用數(shù)組的場(chǎng)景我們都可以用切片替代,這里我們也用切片來試試看:

func main() {
    var a = [5]int{1, 2, 3, 4, 5}
    var r [5]int
    fmt.Println("original a =", a)
    for i, v := range a[:] {
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }
        r[i] = v
    }
    fmt.Println("after for range loop, r =", r)
    fmt.Println("after for range loop, a =", a)
}

你可以看到,在 range 表達(dá)式中,我們用了 a[:]替代了原先的 a,也就是將數(shù)組 a 轉(zhuǎn)換為一個(gè)切片,作為 range 表達(dá)式的循環(huán)對(duì)象。運(yùn)行這個(gè)修改后的例子,結(jié)果是這樣的:

original a = [1 2 3 4 5]
after for range loop, r = [1 12 13 4 5]
after for range loop, a = [1 12 13 4 5]

我們看到輸出的結(jié)果與最初的預(yù)期終于一致了,顯然用切片能實(shí)現(xiàn)我們的要求。

那切片是如何做到的呢?切片在 Go 內(nèi)部表示為一個(gè)結(jié)構(gòu)體,由(array, len, cap)組成,其中 array 是指向切片對(duì)應(yīng)的底層數(shù)組的指針,len 是切片當(dāng)前長度,cap 為切片的最大容量。

所以,當(dāng)進(jìn)行 range 表達(dá)式復(fù)制時(shí),我們實(shí)際上復(fù)制的是一個(gè)切片,也就是表示切片的結(jié)構(gòu)體。表示切片副本的結(jié)構(gòu)體中的 array,依舊指向原切片對(duì)應(yīng)的底層數(shù)組,所以我們對(duì)切片副本的修改也都會(huì)反映到底層數(shù)組 a 上去。而 v 再從切片副本結(jié)構(gòu)體中 array 指向的底層數(shù)組中,獲取數(shù)組元素,也就得到了被修改后的元素值。

6.3 遍歷 map 中元素的隨機(jī)性

根據(jù)上面的講解,當(dāng) map 類型變量作為 range 表達(dá)式時(shí),我們得到的 map 變量的副本與原變量指向同一個(gè) map。如果我們?cè)谘h(huán)的過程中,對(duì) map 進(jìn)行了修改,那么這樣修改的結(jié)果是否會(huì)影響后續(xù)迭代呢?這個(gè)結(jié)果和我們遍歷 map 一樣,具有隨機(jī)性。

比如我們來看下面這個(gè)例子,在 map 循環(huán)過程中,當(dāng) counter 值為 0 時(shí),我們刪除了變量 m 中的一個(gè)元素:

var m = map[string]int{
    "tony": 21,
    "tom":  22,
    "jim":  23,
}
counter := 0
for k, v := range m {
    if counter == 0 {
        delete(m, "tony")
    }
    counter++
    fmt.Println(k, v)
}
fmt.Println("counter is ", counter)

如果我們反復(fù)運(yùn)行這個(gè)例子多次,會(huì)得到兩個(gè)不同的結(jié)果。當(dāng) k="tony"作為第一個(gè)迭代的元素時(shí),我們將得到如下結(jié)果:

tony 21
tom 22
jim 23
counter is  3

否則,我們得到的結(jié)果是這樣的:

tom 22
jim 23
counter is  2

如果我們?cè)卺槍?duì) map 類型的循環(huán)體中,新創(chuàng)建了一個(gè) map 元素項(xiàng),那這項(xiàng)元素可能出現(xiàn)在后續(xù)循環(huán)中,也可能不出現(xiàn):

var m = map[string]int{
    "tony": 21,
    "tom":  22,
    "jim":  23,
}
counter := 0
for k, v := range m {
    if counter == 0 {
        m["lucy"] = 24
    }
    counter++
    fmt.Println(k, v)
}
fmt.Println("counter is ", counter)

這個(gè)例子的執(zhí)行結(jié)果也會(huì)有兩個(gè):

tony 21
tom 22
jim 23
lucy 24
counter is  4

或:

tony 21
tom 22
jim 23
counter is  3

考慮到上述這種隨機(jī)性,我們?nèi)粘>幋a遇到遍歷 map 的同時(shí),還需要對(duì) map 進(jìn)行修改的場(chǎng)景的時(shí)候,要格外小心。

以上就是Go 循環(huán)結(jié)構(gòu)for循環(huán)使用全面講解的詳細(xì)內(nèi)容,更多關(guān)于Go for循環(huán)結(jié)構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang依賴注入工具digo的使用詳解

    Golang依賴注入工具digo的使用詳解

    這篇文章主要為大家詳細(xì)介紹了Golang中依賴注入工具digo的使用,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-06-06
  • Go簡單實(shí)現(xiàn)協(xié)程池的實(shí)現(xiàn)示例

    Go簡單實(shí)現(xiàn)協(xié)程池的實(shí)現(xiàn)示例

    本文主要介紹了Go簡單實(shí)現(xiàn)協(xié)程池的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 詳解Go語言中rand(隨機(jī)數(shù))包的使用

    詳解Go語言中rand(隨機(jī)數(shù))包的使用

    在Golang中,有兩個(gè)包提供了rand,分別為math/rand和crypto/rand對(duì)應(yīng)兩種應(yīng)用場(chǎng)景。math/rand包實(shí)現(xiàn)了偽隨機(jī)數(shù)生成器。也就是生成 整形和浮點(diǎn)型;crypto/rand包實(shí)現(xiàn)了用于加解密的更安全的隨機(jī)數(shù)生成器。本文就來和大家詳細(xì)講講math/rand的使用
    2022-08-08
  • 如何使用工具自動(dòng)監(jiān)測(cè)SSL證書有效期并發(fā)送提醒郵件

    如何使用工具自動(dòng)監(jiān)測(cè)SSL證書有效期并發(fā)送提醒郵件

    本文介紹了如何開發(fā)一個(gè)工具,用于每日檢測(cè)SSL證書剩余有效天數(shù)并通過郵件發(fā)送提醒,工具基于命令行,通過SMTP協(xié)議發(fā)送郵件,需配置SMTP連接信息,本文還提供了配置文件樣例及代碼實(shí)現(xiàn),幫助用戶輕松部署和使用該工具
    2024-10-10
  • graphql---go http請(qǐng)求使用詳解

    graphql---go http請(qǐng)求使用詳解

    這篇文章主要介紹了graphql---go http請(qǐng)求使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang switch語句的具體使用

    Golang switch語句的具體使用

    switch 語句提供了一種簡潔的方式來執(zhí)行多路分支選擇,本文主要介紹了Golang switch語句的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-08-08
  • Go語言使用protojson庫實(shí)現(xiàn)Protocol Buffers與JSON轉(zhuǎn)換

    Go語言使用protojson庫實(shí)現(xiàn)Protocol Buffers與JSON轉(zhuǎn)換

    本文主要介紹Google開源的工具庫Protojson庫如何Protocol Buffers與JSON進(jìn)行轉(zhuǎn)換,以及和標(biāo)準(zhǔn)庫encoding/json的性能對(duì)比,需要的朋友可以參考下
    2023-09-09
  • Golang 探索對(duì)Goroutine的控制方法(詳解)

    Golang 探索對(duì)Goroutine的控制方法(詳解)

    下面小編就為大家分享一篇Golang 探索對(duì)Goroutine的控制方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • Go整合ElasticSearch的示例代碼

    Go整合ElasticSearch的示例代碼

    這篇文章主要介紹了Go整合ElasticSearch的相關(guān)知識(shí),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • VSCode Golang dlv調(diào)試數(shù)據(jù)截?cái)鄦栴}及處理方法

    VSCode Golang dlv調(diào)試數(shù)據(jù)截?cái)鄦栴}及處理方法

    這篇文章主要介紹了VSCode Golang dlv調(diào)試數(shù)據(jù)截?cái)鄦栴},本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-06-06

最新評(píng)論