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

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)
}

結果:
在這裏插入圖片描述

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()
	}
}

結果:
在這裏插入圖片描述

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)
}

結果:
在這裏插入圖片描述

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("對不起,我不清楚你說什麼……")
		}
	}
}

結果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章