0%

Go语言学习笔记3-流程控制

[TOC]

Go 语言的常用流程控制有 if 和 for,而 switch 和 goto 主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。

Go 语言中的基本流程控制语句,包括分支语句(if 和 switch)、循环(for)和跳转(goto)语句。还有循环控制语句(break 和 continue),前者的功能是中断循环或者跳出 switch 判断,后者的功能是继续 for 的下一个循环。

if else (分支结构)

在Go语言中可以通过 if 关键字进行条件判断,格式如下:

1
2
3
4
5
6
7
if 表达式1 {
分支1
} else if 表达式2 {
分支2
} else{
分支3
}

Go 语言规定与 if 匹配的左括号{必须与 if 和表达式放在同一行,如果尝试将{放在其他位置,将会触发编译错误。

特殊写法

Go 语言规定与 if 匹配的左括号{必须与 if 和表达式放在同一行,如果尝试将{放在其他位置,将会触发编译错误。

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

Connect 是一个带有返回值的函数,err:=Connect() 是一个语句,执行 Connect 后,将错误保存到 err 变量中。

err!=nil 才是 if 的判断表达式,当 err 不为空时,打印错误并返回。

循环语句for

Go语言中所有的循环类型均可以使用for 关键字来完成

基于语句和表达式的基本for 循环格式如下:

1
2
3
for 初始语句;条件表达式;结束表达式{
循环体代码
}

循环体不停地进行循环,直到条件表达式返回 false 时自动退出循环,执行 for 的}之后的语句

for 循环可以通过 breakgotoreturnpanic 语句强制退出循环。for 的初始语句、条件表达式、结束语句的详细介绍如下。

for 中的初始语句——开始循环时执行的语句

初始语句是在第一次循环前执行的语句,一般使用初始语句执行变量初始化,如果变量在此处被声明,其**作用域**将被局限在这个for 的范畴内

注意:初始语句可以被忽略,但是初始语句之后的分号必须填写,代码如下:

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

这段代码将 step 放在 for 的前面进行初始化,for 中没有初始语句,此时 step 的作用域就比在初始语句中声明 step 要大。

for 中的条件表达式——控制是否循环的开关

对每次循环开始前计算的表达式,如果表达式为true ,则循环继续,否则结束循环,条件表达式可以被忽略,被忽律条件的条件表达式默认形成无限循环

结束循环时带可执行语句的无限循环

1
2
3
4
5
6
var i int
for ; ; i++ {
if i > 10 {
break
}
}

无线循环

1
2
3
4
5
6
7
var i int
for {
if i > 10 {
break
}
i++
}

只有一个循环条件的循环

1
2
3
4
var i int
for i <= 10 {
i++
}

for 中的结束语句——每次循环结束时执行的语句

在结束每次循环前执行的语句,如果循环被 break、goto、return、panic 等语句强制退出,结束语句不会被执行。

Demo 九九乘法表

1
2
3
4
5
6
for x := 1; x <= 9; x++ {
for y := 1; y <= x; y++ {
fmt.Printf("%d*%d=%d ", x, y, x*y)
}
fmt.Println();
}
1
2
3
4
5
6
7
8
9
1*1=1 
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81

for range (键值循环)

Go 语言可以使用for range遍历数组切片字符串map通道(channel)。通过 for range 遍历的返回值有一定的规律:

  • 数组、切片、字符串返回索引和值。
  • map 返回键和值。
  • 通道(channel)只返回通道内的值。

遍历数组、切片——获得索引和元素

在遍历代码中,key 和 value 分别代表切片的下标及下标对应的值。下面的代码展示如何遍历切片,数组也是类似的遍历方法:

1
2
3
4
5
6
7
for key, value := range []int{1, 2, 3, 4} {
fmt.Println(key, "=>", value)
// 0 => 1
// 1 => 2
// 2 => 3
// 3 => 4
}

遍历字符串——获得索引和元素

Go 语言和其他语言类似,可以通过 for range 的组合,对字符串进行遍历,遍历时,key 和 value 分别代表字符串的索引(base0)和字符串中的每一个字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
str := "你好,zhimma"
for key, value := range str {
fmt.Printf("key:%d value:0x%x\n", key, value)
/**
key:0 value:0x4f60 type:int32
key:3 value:0x597d type:int32
key:6 value:0xff0c type:int32
key:9 value:0x7a type:int32
key:10 value:0x68 type:int32
key:11 value:0x69 type:int32
key:12 value:0x6d type:int32
key:13 value:0x6d type:int32
key:14 value:0x61 type:int32
*/
}

代码中的 value 变量,实际类型是 rune,实际上就是 int32,以十六进制打印出来就是字符的编码。

遍历map——获得map的键和值

对于 map 类型来说,for range 遍历时,key 和 value 分别代表 map 的索引键 key 和索引对应的值,一般被称为 map 的键值对,因为它们总是一对一对的出现。下面的代码演示了如何遍历 map:

1
2
3
4
5
6
7
8
9
10
11
12
13
family := map[string]string{
"dad": "zhimma dad",
"mom": "zhimma mom",
"daughter": "zhimma",
}
for key, value := range family {
fmt.Println("hello", key, value)
/**
hello dad zhimma dad
hello mom zhimma mom
hello daughter zhimma
*/
}

对 map 遍历时,遍历输出的键值是无序的,如果需要有序的键值对输出,需要对结果进行排序。

遍历通道(channel)——接收通道数据

for range 可以遍历通道(channel),但是通道在遍历时,只输出一个值,即管道内的类型对应的数据

1
2
3
4
5
6
7
8
9
10
c := make(chan int)
go func() {
c <- 1
c <- 2
c <- 3
close(c)
}()
for v := range c {
fmt.Println(v)
}

switch case 语句

分支选择可以理解为一种批量的if语句,使用 switch 语句可方便地对大量的值进行判断。

在 Go 语言中的 switch,不仅可以基于常量进行判断,还可以基于表达式进行判断。

基本写法

Go 语言改进了 switch 的语法设计,避免人为造成失误。Go 语言的 switch 中的每一个 case 与 case 间是独立的代码块,不需要通过 break 语句跳出当前 case 代码块以避免执行到下一行。示例代码如下:

1
2
3
4
5
6
7
8
9
10
a := "hello"

switch a {
case "hello":
fmt.Println("hello")
case "other":
fmt.Println("other")
default:
fmt.Println("default")
}

上面例子中,每个case 均是字符串格式,且使用了default 分支,Go语言规定每个 switch 只能有一个default 分支

一分支多值

当出现多个 case 要放在一起的时候,可以像下面代码这样写:

1
2
3
4
5
var a = "mum"
switch a {
case "mum" , "dad":
fmt.Println("family");
}

不通的 case 表达式使用逗号分隔

分支表达式

case 后不仅仅只是常量,还可以和 if 一样添加表达式,代码如下:

1
2
3
4
5
6
num := 10
switch {
case num < 20 || num > 20:
fmt.Println("num value is ", num)
// num value is 10
}

这种情况的 switch 后面不再跟判断变量,连判断的目标都没有了。

goto 语句——跳转到指定的标签

goto 语句通过标签进行代码间的无条件跳转。goto 语句可以在快速跳出循环、避免重复退出上有一定帮助,使用 goto 语句能简化一些代码的实现过程。

使用 goto 退出多层循环

多层循环中,传统方式退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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方式优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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")
}

第13行 :标签只能被 goto 使用,但不影响代码执行流程,此处如果不手动返回,在不满足条件时,也会执行第 16 行代码。

使用 goto 语句后,无须额外的变量就可以快速退出所有的循环。

统一错误处理

多处错误处理存在代码重复时是非常棘手的,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
err := firstCheckError()
if err != nil {
fmt.Println(err)
exitProcess()
return
}
err = secondCheckError()
if err != nil {
fmt.Println(err)
exitProcess()
return
}
fmt.Println("done")

使用 goto 语句实现上面同样的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
err := firstCheckError()
if err != nil {
goto onExit
}
err = secondCheckError()
if err != nil {
goto onExit
}
fmt.Println("done")
return
onExit:
fmt.Println(err)
exitProcess()

break (跳出循环)

break 语句可以结束forswitchselect代码块。break 语句还可以在语句后面添加标签,表示退出摸个标签对应的代码块,标签要求必须定义在对应的forswitchselect的代码块上

下面看下跳出指定循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OuterLoop:
for i := 0; i < 2; i++ {
for j := 0; j < 5; j++ {
fmt.Println(i , j)
switch j {
case 2:
fmt.Println(i, j)
break OuterLoop
case 3:
fmt.Println(i, j)
break OuterLoop
}
}
}
}

代码输出 :

1
0 2

代码说明如下:

  • 第 1 行,外层循环的标签。
  • 第 2 行和第 3 行,双层循环。
  • 第 5 行,使用 switch 进行数值分支判断。
  • 第 8 和第 11 行,退出 OuterLoop 对应的循环之外,也就是跳转到第 1 行。

continue(中断本次循环,继续下一次循环)

continue 语句可以结束当前循环,开始下一次的循环迭代过程,仅限在 for 循环内使用

在 continue 语句后添加标签时,表示开始标签对应的循环

1
2
3
4
5
6
7
8
9
10
OuterLoops:
for i := 0; i < 2; i++ {
for j := 0; j < 5; j++ {
switch j {
case 2:
fmt.Println(i, j)
continue OuterLoops
}
}
}

代码输出 :

1
2
0 2
1 2

第 7 行将结束当前循环,开启下一次的外层循环,而不是第 3 行的循环。