文章目錄
- 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)
}
結果:

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