Golang-流程控制4

http://c.biancheng.net/golang/flow_control/

Go语言if else(分支结构)

在Go语言中,关键字 if 是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行 if 后由大括号{}括起来的代码块,否则就忽略该代码块继续执行后续的代码。

  1. if condition {
  2. // do something
  3. }

如果存在第二个分支,则可以在上面代码的基础上添加 else 关键字以及另一代码块,这个代码块中的代码只有在条件不满足时才会执行,if 和 else 后的两个代码块是相互独立的分支,只能执行其中一个。

  1. if condition {
  2. // do something
  3. } else {
  4. // do something
  5. }

如果存在第三个分支,则可以使用下面这种三个独立分支的形式:

  1. if condition1 {
  2. // do something
  3. } else if condition2 {
  4. // do something else
  5. }else {
  6. // catch-all or default
  7. }

else if 分支的数量是没有限制的,但是为了代码的可读性,还是不要在 if 后面加入太多的 else if 结构,如果必须使用这种形式,则尽可能把先满足的条件放在前面。

关键字 if 和 else 之后的左大括号{必须和关键字在同一行,如果你使用了 else if 结构,则前段代码块的右大括号}必须和 else if 关键字在同一行,这两条规则都是被编译器强制规定的。

非法的 Go 代码:

  1. if x{
  2. }
  3. else { // 无效的
  4. }

要注意的是,在使用 gofmt 格式化代码之后,每个分支内的代码都会缩进 4 个或 8 个空格,或者是 1 个 tab,并且右大括号}与对应的 if 关键字垂直对齐。

在有些情况下,条件语句两侧的括号是可以被省略的,当条件比较复杂时,则可以使用括号让代码更易读,在使用 &&、|| 或 ! 时可以使用括号来提升某个表达式的运算优先级,并提高代码的可读性。

举例

通过下面的例子来了解 if 的写法:

  1. var ten int = 11
  2. if ten > 10 {
  3. fmt.Println(">10")
  4. } else {
  5. fmt.Println("<=10")
  6. }

代码输出如下:

>10

代码说明如下:

  • 第 1 行,声明整型变量并赋值 11。
  • 第 2 行,判断当 ten 的值大于 10 时执行第 3 行,否则执行第 4 行。
  • 第 3 和第 5 行,分别打印大于 10 和小于等于 10 时的输出。

特殊写法

if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,代码如下:

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

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

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

这种写法可以将返回值与判断放在一行进行处理,而且返回值的作用范围被限制在 if、else 语句组合中。

Go语言for(循环结构)

与多数语言不同的是,Go语言中的循环语句只支持 for 关键字,而不支持 while 和 do-while 结构,关键字 for 的基本使用方法与C语言和 C++ 中非常接近:

  1. sum := 0
  2. for i := 0; i < 10; i++ {
  3. sum += i
  4. }

可以看到比较大的一个不同在于 for 后面的条件表达式不需要用圆括号()括起来,Go语言还进一步考虑到无限循环的场景,让开发者不用写无聊的 for(;;){}do{} while(1);,而直接简化为如下的写法:

  1. sum := 0
  2. for {
  3. sum++
  4. if sum > 100 {
  5. break
  6. }
  7. }

使用循环语句时,需要注意的有以下几点:

  • 左花括号{必须与 for 处于同一行。
  • Go语言中的 for 循环与C语言一样,都允许在循环条件中定义和初始化变量,唯一的区别是,Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。
  • Go语言的 for 循环同样支持 continue 和 break 来控制循环,但是它提供了一个更高级的 break,可以选择中断哪一个循环,如下例:
  1. for j := 0; j < 5; j++ {
  2. for i := 0; i < 10; i++ {
  3. if i > 5 {
  4. break JLoop
  5. }
  6. fmt.Println(i)
  7. }
  8. }
  9. JLoop:
  10. // ...

上述代码中,break 语句终止的是 JLoop 标签处的外层循环。

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

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

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

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

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

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

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

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

下面代码忽略条件表达式,但是保留结束语句,代码如下:

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

代码说明如下:

  • 第 3 行,无须设置 i 的初始值,因此忽略 for 的初始语句,两个分号之间是条件表达式,也被忽略,此时循环会一直持续下去,for 的结束语句为 i++,每次结束循环前都会调用。
  • 第 5 行,判断 i 大于 10 时,通过 break 语句跳出 for 循环到第 9 行。

2) 无限循环

上面的代码还可以改写为更美观的写法,代码如下:

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

代码说明如下:

  • 第 3 行,忽略 for 的所有语句,此时 for 执行无限循环。
  • 第 9 行,将 i++ 从 for 的结束语句放置到函数体的末尾是等效的,这样编写的代码更具有可读性。


无限循环在收发处理中较为常见,但需要无限循环有可控的退出方式来结束循环。

3) 只有一个循环条件的循环

在上面代码的基础上进一步简化代码,将 if 判断整合到 for 中,变为下面的代码:

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

在代码第 3 行中,将之前使用if i>10{}判断的表达式进行取反,变为判断 i 小于等于 10 时持续进行循环。

上面这段代码其实类似于其他编程语言中的 while,在 while 后添加一个条件表达式,满足条件表达式时持续循环,否则结束循环。

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

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

Go语言for range(键值循环)

for range 结构是Go语言特有的一种的迭代结构,在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道(channel),for range 语法上类似于其它语言中的 foreach 语句,一般形式为:

for key, val := range coll {
    ...
}

需要要注意的是,val 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值。一个字符串是 Unicode 编码的字符(或称之为 rune )集合,因此也可以用它来迭代字符串:

for pos, char := range str {
    ...
}

每个 rune 字符和索引在 for range 循环中是一一对应的,它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。

通过 for range 遍历的返回值有一定的规律:

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

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

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

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

代码输出如下:

key:0  value:1
key:1  value:2
key:2  value:3
key:3  value:4

遍历字符串——获得字符

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

下面这段代码展示了如何遍历字符串:

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

代码输出如下:

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 类型,以十六进制打印出来就是字符的编码。

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

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

  1. m := map[string]int{
  2. "hello": 100,
  3. "world": 200,
  4. }
  5. for key, value := range m {
  6. fmt.Println(key, value)
  7. }

代码输出如下:

hello 100
world 200

注意

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

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

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

下面代码为我们展示了通道的遍历:

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

代码说明如下:

  • 第 1 行创建了一个整型类型的通道。
  • 第 3 行启动了一个 goroutine,其逻辑的实现体现在第 5~8 行,实现功能是往通道中推送数据 1、2、3,然后结束并关闭通道。
  • 这段 goroutine 在声明结束后,在第 9 行马上被执行。
  • 从第 11 行开始,使用 for range 对通道 c 进行遍历,其实就是不断地从通道中取数据,直到通道被关闭。

在遍历中选择希望获得的变量

在使用 for range 循环遍历某个对象时,一般不会同时需要 key 或者 value,这个时候可以采用一些技巧,让代码变得更简单,下面将前面的例子修改一下,参考下面的代码示例:

  1. m := map[string]int{
  2. "hello": 100,
  3. "world": 200,
  4. }
  5. for _, value := range m {
  6. fmt.Println(value)
  7. }

代码输出如下:

100
200

在上面的例子中将 key 变成了下划线_,这里的下划线就是匿名变量。

  • 可以理解为一种占位符。
  • 匿名变量本身不会进行空间分配,也不会占用一个变量的名字。
  • 在 for range 可以对 key 使用匿名变量,也可以对 value 使用匿名变量。


再看一个匿名变量的例子:

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

代码输出如下:

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

在该例子中,value 被设置为匿名变量,只使用 key,而 key 本身就是切片的索引,所以例子输出索引。

我们总结一下 for 的功能:

    • Go语言的 for 包含初始化语句、条件表达式、结束语句,这 3 个部分均可缺省。
    • for range 支持对数组、切片、字符串、map、通道进行遍历操作。
    • 在需要时,可以使用匿名变量对 for range 的变量进行选取。

Go语言switch case语句

Go语言的 switch 要比C语言的更加通用,表达式不需要为常量,甚至不需要为整数,case 按照从上到下的顺序进行求值,直到找到匹配的项,如果 switch 没有表达式,则对 true 进行匹配,因此,可以将 if else-if else 改写成一个 switch。

相对于C语言和 Java 等其它语言来说,Go语言中的 switch 结构使用上更加灵活,语法设计尽量以使用方便为主。

基本写法

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

  1. var a = "hello"
  2. switch a {
  3. case "hello":
  4. fmt.Println(1)
  5. case "world":
  6. fmt.Println(2)
  7. default:
  8. fmt.Println(0)
  9. }

代码输出如下:

1

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

1) 一分支多值

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

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

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

2) 分支表达式

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

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

注意,这种情况的 switch 后面不再需要跟判断变量。

跨越 case 的 fallthrough——兼容C语言的 case 设计

在Go语言中 case 是一个独立的代码块,执行完毕后不会像C语言那样紧接着执行下一个 case,但是为了兼容一些移植代码,依然加入了 fallthrough 关键字来实现这一功能,代码如下:

  1. var s = "hello"
  2. switch {
  3. case s == "hello":
  4. fmt.Println("hello")
  5. fallthrough
  6. case s != "world":
  7. fmt.Println("world")
  8. }

代码输出如下:

hello
world

新编写的代码,不建议使用 fallthrough。

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

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

使用 goto 退出多层循环

下面这段代码在满足条件时,需要连续退出两层循环,使用传统的编码方式如下:

  1. package main
  2. import "fmt"
  3. func main() {
  4. var breakAgain bool
  5. // 外循环
  6. for x := 0; x < 10; x++ {
  7. // 内循环
  8. for y := 0; y < 10; y++ {
  9. // 满足某个条件时, 退出循环
  10. if y == 2 {
  11. // 设置退出标记
  12. breakAgain = true
  13. // 退出本次循环
  14. break
  15. }
  16. }
  17. // 根据标记, 还需要退出一次循环
  18. if breakAgain {
  19. break
  20. }
  21. }
  22. fmt.Println("done")
  23. }

代码说明如下:

  • 第 10 行,构建外循环。
  • 第 13 行,构建内循环。
  • 第 16 行,当 y==2 时需要退出所有的 for 循环。
  • 第 19 行,默认情况下循环只能一层一层退出,为此就需要设置一个状态变量 breakAgain,需要退出时,设置这个变量为 true。
  • 第 22 行,使用 break 退出当前循环,执行后,代码调转到第 28 行。
  • 第 28 行,退出一层循环后,根据 breakAgain 变量判断是否需要再次退出外层循环。
  • 第 34 行,退出所有循环后,打印 done。


将上面的代码使用Go语言的 goto 语句进行优化:

  1. package main
  2. import "fmt"
  3. func main() {
  4. for x := 0; x < 10; x++ {
  5. for y := 0; y < 10; y++ {
  6. if y == 2 {
  7. // 跳转到标签
  8. goto breakHere
  9. }
  10. }
  11. }
  12. // 手动返回, 避免执行进入标签
  13. return
  14. // 标签
  15. breakHere:
  16. fmt.Println("done")
  17. }

代码说明如下:

  • 第 13 行,使用 goto 语句跳转到指明的标签处,标签在第 23 行定义。
  • 第 20 行,标签只能被 goto 使用,但不影响代码执行流程,此处如果不手动返回,在不满足条件时,也会执行第 24 行代码。
  • 第 23 行,定义 breakHere 标签。


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

使用 goto 集中处理错误

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

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

代码说明如下:

  • 第 1 行,执行某逻辑,返回错误。
  • 第 2~6 行,如果发生错误,打印错误退出进程。
  • 第 8 行,执行某逻辑,返回错误。
  • 第 10~14 行,发生错误后退出流程。
  • 第 16 行,没有任何错误,打印完成。


在上面代码中,有一部分都是重复的错误处理代码,如果后期在这些代码中添加更多的判断,就需要在这些雷同的代码中依次修改,极易造成疏忽和错误。

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

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

代码说明如下:

  • 第 3 行和第 9 行,发生错误时,跳转错误标签 onExit。
  • 第 17 行和第 18 行,汇总所有流程进行错误打印并退出进程。

Go语言break(跳出循环)

Go语言中 break 语句可以结束 for、switch 和 select 的代码块,另外 break 语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的 for、switch 和 select 的代码块上。

跳出指定循环:

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

代码输出如下:

0 2

代码说明如下:

    • 第 7 行,外层循环的标签。
    • 第 8 行和第 9 行,双层循环。
    • 第 10 行,使用 switch 进行数值分支判断。
    • 第 13 和第 16 行,退出 OuterLoop 对应的循环之外,也就是跳转到第 20 行。

Go语言continue(继续下一次循环)

Go语言中 continue 语句可以结束当前循环,开始下一次的循环迭代过程,仅限在 for 循环内使用,在 continue 语句后添加标签时,表示开始标签对应的循环,例如:

  1. package main
  2. import "fmt"
  3. func main() {
  4. OuterLoop:
  5. for i := 0; i < 2; i++ {
  6. for j := 0; j < 5; j++ {
  7. switch j {
  8. case 2:
  9. fmt.Println(i, j)
  10. continue OuterLoop
  11. }
  12. }
  13. }
  14. }

代码输出结果如下:

0 2
1 2

代码说明:第 14 行将结束当前循环,开启下一次的外层循环,而不是第 10 行的循环。

 

posted @ 2022-04-07 10:59  hanease  阅读(38)  评论(0编辑  收藏  举报