天天看點

Go開發 之 流程控制(if/else、for/range、switch、goto、break、continue)

文章目錄

  • ​​0、唠唠叨叨​​
  • ​​1、分支結構(if / else)​​
  • ​​1.1、标準寫法​​
  • ​​1.2、特殊寫法​​
  • ​​2、循環結構(for)​​
  • ​​2.1、标準循環​​
  • ​​2.2、無限循環​​
  • ​​2.3、for 中的初始語句(開始循環時執行的語句)​​
  • ​​2.4、for 中的結束語句(循環結束時執行的語句)​​
  • ​​2.5、for 中的條件表達式(控制是否循環的開關)​​
  • ​​2.5.1、結束循環時帶可執行語句的無限循環​​
  • ​​2.5.2、無限循環​​
  • ​​2.5.3、隻有一個循環條件的循環​​
  • ​​3、鍵值循環結構(for range)​​
  • ​​3.1、标準循環​​
  • ​​3.2、周遊數組、切片(獲得索引和值)​​
  • ​​3.3、周遊字元串(獲得字元)​​
  • ​​3.4、周遊 map(獲得 map 的鍵和值)​​
  • ​​3.5、周遊通道 channel(接收通道資料)​​
  • ​​3.6、在周遊中選擇希望獲得的變量​​
  • ​​4、分支結構(switch / case)​​
  • ​​4.1、基本寫法​​
  • ​​4.2、一分支多值​​
  • ​​4.3、分支表達式​​
  • ​​4.4、跨越 case 的 fallthrough(相容C語言的 case 設計)​​
  • ​​5、跳轉結構(break / continue / goto)​​
  • ​​5.1、跳出制定循環 break​​
  • ​​5.2、繼續下一次循環 continue​​
  • ​​5.3、跳轉到指定的标簽 goto​​
  • ​​5.3.1、使用 goto 退出多層循環​​
  • ​​5.3.2、使用 goto 集中處理錯誤​​
  • ​​6、示例​​
  • ​​6.1、九九乘法表​​
  • ​​6.2、冒泡排序​​
  • ​​6.3、二分查找​​
  • ​​6.4、簡易聊天機器人​​

0、唠唠叨叨

流程控制可以說是一門語言的經脈了。Go 語言的常用流程控制有 if 和 for,而 switch 和 goto 主要是為了簡化代碼、降低重複代碼而生的結構,屬于擴充類的流程控制。

本章主要介紹 Go 語言中的基本流程控制語句,包括分支語句(if 和 switch)、循環(for)和跳轉(goto)語句。另外,還有循環控制語句(break 和 continue),break 的功能是中斷循環或者跳出 switch 判斷,continue 的功能是繼續 for 的下一個循環。

1、分支結構(if / else)

1.1、标準寫法

标準格式:

if condition1 {
    // do something
} else if condition2 {
    // do something else
}else {
    // catch-all or default
}      

​無效格式:​

if x{
}
else { // 無效的
}      

1.2、特殊寫法

将傳回值與判斷放在一行進行處理,而且傳回值的作用範圍被限制在 if、else 語句組合中。

if 還有一種特殊的寫法,可以在 if 表達式之前添加一個執行語句,再根據變量值進行判斷,例如:

if err := Connect(); err != nil {
    fmt.Println(err)
    return
}      

Connect 是一個帶有傳回值的函數,err:=Connect() 是一個語句,執行 Connect 後,将錯誤儲存到 err 變量中。err != nil 才是 if 的判斷表達式,當 err 不為空時,列印錯誤并傳回。

在程式設計中,變量的作用範圍越小,所造成的問題可能性越小,每一個變量代表一個狀态,有狀态的地方,狀态就會被修改,函數的局部變量隻會影響一個函數的執行,但全局變量可能會影響所有代碼的執行狀态,是以限制變量的作用範圍對代碼的穩定性有很大的幫助。

2、循環結構(for)

Go語言中的循環語句隻支援 for 關鍵字,而​

​不支援​

​while 和 do-while 結構。

2.1、标準循環

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}      

2.2、無限循環

sum := 0
for {
    sum++
    if sum > 100 {
        break
    }
}      

2.3、for 中的初始語句(開始循環時執行的語句)

初始語句是在第一次循環前執行的語句,一般使用初始語句執行變量初始化,如果變量在此處被聲明,其作用域将被局限在這個 for 的範圍内。初始語句可以被忽略,但是初始語句之後的分号必須要寫,例如:

step := 2
for ; step > 0; step-- {
    fmt.Println(step)
}      

這段代碼将 step 放在 for 的前面進行初始化,for 中沒有初始語句,此時 step 的作用域就比在初始語句中聲明 step 要大。

2.4、for 中的結束語句(循環結束時執行的語句)

在結束每次循環前執行的語句,如果循環被 break、goto、return、panic 等語句強制退出,結束語句不會被執行。

2.5、for 中的條件表達式(控制是否循環的開關)

2.5.1、結束循環時帶可執行語句的無限循環

下面代碼忽略條件表達式,但是保留結束語句,例如:

var i int
for ; ; i++ {
    if i > 10 {
        break
    }
}      

2.5.2、無限循環

上面的代碼還可以改寫為更美觀的寫法。

var i int
for {
    if i > 10 {
        break
    }
    i++
}      

無限循環在收發進行中較為常見,但需要無限循環有可控的退出方式來結束循環。

2.5.3、隻有一個循環條件的循環

滿足條件表達式時持續循環,否則結束循環。

var i int
for i <= 10 {
    i++
}      

上述代碼其實類似于其他程式設計語言中的 while,在 while 後添加一個條件表達式。

3、鍵值循環結構(for range)

3.1、标準循環

在許多情況下都非常有用,for range 可以周遊數組、切片、字元串、map 及通道(channel),for range 文法上類似于其它語言中的 foreach 語句,一般形式為:

for key, val := range coll { // for pos, char := range str {
    ...
}      

一個字元串是 Unicode 編碼的字元(或稱之為 rune )集合,是以也可以用它來疊代字元串。每個 rune 字元和索引在 for range 循環中是一一對應的,它能夠自動根據 UTF-8 規則識别 Unicode 編碼的字元。

通過 for range 周遊的傳回值有一定的規律:

  • 數組、切片、字元串傳回索引和值。
  • map 傳回鍵和值。
  • 通道(channel)隻傳回通道内的值。

3.2、周遊數組、切片(獲得索引和值)

在周遊代碼中,key 和 value 分别代表切片的下标及下标對應的值,數組也是類似的周遊方法:

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      

3.3、周遊字元串(獲得字元)

var str = "hello 你好"
for key, value := range str {
    fmt.Printf("key:%d value:0x%x\n", key, value)
}      

代碼輸出如下:

key:0 value:0x68
key:1 value:0x65
key:2 value:0x6c
key:3 value:0x6c
key:4 value:0x6f
key:5 value:0x20
key:6 value:0x4f60
key:9 value:0x597d      

代碼中的變量 value,實際類型是 rune 類型,以十六進制列印出來就是字元的編碼。

3.4、周遊 map(獲得 map 的鍵和值)

m := map[string]int{
    "hello": 100,
    "world": 200,
}
for key, value := range m {
    fmt.Println(key, value)
}      

代碼輸出如下:

hello 100
world 200      

對 map 周遊時,周遊輸出的鍵值是無序的,如果需要有序的鍵值對輸出,需要對結果進行排序。

3.5、周遊通道 channel(接收通道資料)

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

結果:

Go開發 之 流程控制(if/else、for/range、switch、goto、break、continue)

3.6、在周遊中選擇希望獲得的變量

m := map[string]int{
    "hello": 100,
    "world": 200,
}
for _, value := range m { // "_"可以了解為一種占位符,匿名變量本身不會進行空間配置設定,也不會占用一個變量的名字
    fmt.Println(value)
}      

代碼輸出如下:

100
200      

再看一個匿名變量的例子:

for key, _ := range []int{1, 2, 3, 4} {
    fmt.Printf("key:%d \n", key)
}      

代碼輸出如下:

key:0
key:1
key:2
key:3      

4、分支結構(switch / case)

Go語言的 switch 表達式不需要為常量,甚至不需要為整數,case 按照從上到下的順序進行求值,直到找到比對的項,如果 switch 沒有表達式,則​

​對 true​

​進行比對,是以,可以将 if else-if else 改寫成一個 switch。

4.1、基本寫法

Go語言改進了 switch 的文法設計,case 與 case 之間是獨立的代碼塊,不需要通過 break 語句跳出目前 case 代碼塊以避免執行到下一行。每個 switch 隻能有一個 default 分支。例如:

var a = "hello"
switch a {
case "hello":
    fmt.Println(1)
case "world":
    fmt.Println(2)
default:
    fmt.Println(0)
}      

4.2、一分支多值

不同的 case 表達式使用逗号分隔。

var a = "mum"
switch a {
case "mum", "daddy":
    fmt.Println("family")
}      

4.3、分支表達式

var r int = 11
switch {
case r > 10 && r < 20:
    fmt.Println(r)
}      

4.4、跨越 case 的 fallthrough(相容C語言的 case 設計)

var s = "hello"
switch {
case s == "hello":
    fmt.Println("hello")
    fallthrough
case s != "world":
    fmt.Println("world")
}      

結果:

hello
world      

新編寫的代碼,不建議使用 fallthrough。

5、跳轉結構(break / continue / goto)

5.1、跳出制定循環 break

package main
import "fmt"
func main() {
OuterLoop:
    for i := 0; i < 2; i++ {
        for j := 0; j < 5; j++ {
            switch j {
            case 2:
                fmt.Println(i, j)
                break OuterLoop
            case 3:
                fmt.Println(i, j)
                break OuterLoop
            }
        }
    }
}      

結果:

0 2      

5.2、繼續下一次循環 continue

package main
import "fmt"
func main() {
OuterLoop:
    for i := 0; i < 2; i++ {
        for j := 0; j < 5; j++ {
            switch j {
            case 2:
                fmt.Println(i, j)
                continue OuterLoop
            }
        }
    }
}      

結果:

0 2
1 2      

5.3、跳轉到指定的标簽 goto

goto 語句通過标簽進行代碼間的無條件跳轉,同時 goto 語句在快速跳出循環、避免重複退出上也有一定的幫助,使用 goto 語句能簡化一些代碼的實作過程。

5.3.1、使用 goto 退出多層循環

原始代碼:

package main
import "fmt"
func main() {
    var breakAgain bool
    // 外循環
    for x := 0; x < 10; x++ {
        // 内循環
        for y := 0; y < 10; y++ {
            // 滿足某個條件時, 退出循環
            if y == 2 {
                // 設定退出标記
                breakAgain = true
                // 退出本次循環
                break
            }
        }
        // 根據标記, 還需要退出一次循環
        if breakAgain {
                break
        }
    }
    fmt.Println("done")
}      

經過 goto 後優化:

package main
import "fmt"
func main() {
    for x := 0; x < 10; x++ {
        for y := 0; y < 10; y++ {
            if y == 2 {
                // 跳轉到标簽
                goto breakHere
            }
        }
    }
    // 手動傳回, 避免執行進入标簽
    return
    // 标簽
breakHere:
    fmt.Println("done")
}      

使用 goto 語句後,無須額外的變量就可以快速退出所有的循環。

5.3.2、使用 goto 集中處理錯誤

err := firstCheckError()
if err != nil {
    fmt.Println(err)
    exitProcess()
    return
}
err = secondCheckError()
if err != nil {
    fmt.Println(err)
    exitProcess()
    return
}
fmt.Println("done")      

使用 goto 後:

err := firstCheckError()
    if err != nil {
        goto onExit
    }
    err = secondCheckError()
    if err != nil {
        goto onExit
    }
    fmt.Println("done")
    return
onExit:
    fmt.Println(err)
    exitProcess()      

6、示例

6.1、九九乘法表

package main
import "fmt"

var Info string = "沙師弟 -- 九九乘法表"

func main() {
  fmt.Println(Info)
  fmt.Println()
  // 周遊, 決定處理第幾行
  for y := 1; y <= 9; y++ {
    // 周遊, 決定這一行有多少列
    for x := 1; x <= y; x++ {
      fmt.Printf("%d*%d=%d ", x, y, x*y)
    }
    // 手動生成回車
    fmt.Println()
  }
}      

結果:

Go開發 之 流程控制(if/else、for/range、switch、goto、break、continue)

6.2、冒泡排序

package main
import "fmt"

var Info string = "沙師弟 -- 冒泡排序"

func main() {
  arr := [...]int{11,12,42,83,34,34,47,54}
  var n = len(arr)
  fmt.Println(Info)
  fmt.Println()
  fmt.Println("--------沒排序前--------\n",arr)
  for i := 0; i <= n-1; i++ {
    fmt.Println("--------第",i+1,"次冒泡--------")
    for j := i; j <= n-1; j++ {
      if arr[i] > arr[j] {
        t := arr[i]
        arr[i] = arr[j]
        arr[j] = t
      }
      fmt.Println(arr)
    }
  }
  fmt.Println("--------最終結果--------\n",arr)
}      

結果:

沙師弟 -- 冒泡排序

--------沒排序前--------
[11 12 42 83 34 34 47 54]
--------第 1 次冒泡--------
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
--------第 2 次冒泡--------
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
--------第 3 次冒泡--------
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 34 83 42 34 47 54]
[11 12 34 83 42 34 47 54]
[11 12 34 83 42 34 47 54]
[11 12 34 83 42 34 47 54]
--------第 4 次冒泡--------
[11 12 34 83 42 34 47 54]
[11 12 34 42 83 34 47 54]
[11 12 34 34 83 42 47 54]
[11 12 34 34 83 42 47 54]
[11 12 34 34 83 42 47 54]
--------第 5 次冒泡--------
[11 12 34 34 83 42 47 54]
[11 12 34 34 42 83 47 54]
[11 12 34 34 42 83 47 54]
[11 12 34 34 42 83 47 54]
--------第 6 次冒泡--------
[11 12 34 34 42 83 47 54]
[11 12 34 34 42 47 83 54]
[11 12 34 34 42 47 83 54]
--------第 7 次冒泡--------
[11 12 34 34 42 47 83 54]
[11 12 34 34 42 47 54 83]
--------第 8 次冒泡--------
[11 12 34 34 42 47 54 83]
--------最終結果--------
[11 12 34 34 42 47 54 83]      

6.3、二分查找

package main
import "fmt"

var Info string = "沙師弟 -- 二分查找法"

//二分查找函數  假設有序數組的順序是從小到大(很關鍵,決定左右方向)
func BinaryFind(arr *[]int, leftIndex int, rightIndex int, findVal int)  {
  if leftIndex > rightIndex { //判斷 leftIndex是否大于rightIndex
    fmt.Println("找不到")
    return
  }
  middle := (leftIndex + rightIndex) / 2 //先找到 中間的下标
  if (*arr)[middle] > findVal {
    BinaryFind(arr, leftIndex, middle-1, findVal) //要查找的數,範圍應該在 leftIndex 到 middle+1
  } else if (*arr)[middle] < findVal {
    BinaryFind(arr, middle+1, rightIndex, findVal) //要查找的數,範圍應該在 middle+1 到 rightIndex
  } else {
    fmt.Printf("找到了,下标為:%v \n", middle)
  }
}
func main() {
  fmt.Println(Info)
  fmt.Println()
  //定義一個數組
  arr := []int{2, 3, 6, 8, 11, 22, 31, 36, 39, 54, 67, 76, 80, 81, 85, 91, 94, 98}
  BinaryFind(&arr, 0, len(arr) - 1, 36)
  fmt.Println("main arr :",arr)
}      

結果:

Go開發 之 流程控制(if/else、for/range、switch、goto、break、continue)

6.4、簡易聊天機器人

package main

import (
  "bufio"
  "fmt"
  "os"
  "strings"
)

var Info string = "沙師弟 -- 簡易聊天機器人"

func main() {
  fmt.Println(Info)
  fmt.Println()
  // 準備從标準輸入讀取資料。
  inputReader := bufio.NewReader(os.Stdin)
  fmt.Println("請輸入你的名字:")
  // 讀取資料直到碰到 \n 為止。
  input, err := inputReader.ReadString('\n')
  if err != nil {
    fmt.Printf("異常退出: %s\n", err)
    // 異常退出。
    os.Exit(1)
  } else {
    // 用切片操作删除最後的 \n 。
    name := input[:len(input)-1]
    fmt.Printf("你好, %s! 我能為你做什麼?\n", name)
  }
  for {
    input, err = inputReader.ReadString('\n')
    if err != nil {
      fmt.Printf("異常退出: %s\n", err)
      continue
    }
    input = input[:len(input)-1]
    // 全部轉換為小寫。
    input = strings.ToLower(input)
    switch input {
    case "":
      continue
    case "拜拜", "bye":
      fmt.Println("下次再回!")
      // 正常退出。
      os.Exit(0)
    default:
      fmt.Println("對不起,我不清楚你說什麼……")
    }
  }
}