乐趣区

Golang-学习笔记流程控制

条件语句

Go 语言没有 do-while 语句,而 for 语句拥有更广泛的含义和用途 switch 语句也有进一步的扩展,支持类型判断和初始化子句等。

常见的流程控制的关键字有:

  • defer:用于捕获异常和资源回收等工作
  • select:用于多支选择(配合通道使用)
  • go:用于异步启动 goroutine 并执行特定函数
  • if 判断语句
func main() {
    a := 15
    if a < 20 {fmt.Println("a 小于 20")
    }
    // 顺序执行
    fmt.Println("a 的值是", a)
}

if 判断语句是最简单的判断,缺点在于无法返回 false 的情况,为了增加这一功能就要用到 if-else 语句。

  • if-else 语句
func main() {
    a := 20
    if a < 20 {fmt.Println("a 小于 20")
    } else {fmt.Println("a 大于等于 20")
    }
}

如果想判断 a 值是否大于 10 同时小于 20 时,if-else 语句就不能够满足需求了。避免使用嵌套 if-else 语句,因为实现逻辑复杂,使得代码可读性较差。接下来要提到到的 else-if 语句可以很好的解决这个问题。

  • else-if 语句
// 判断 a 值是否在区间(10,20)
func main() {
    a := 15
    if a > 20 {fmt.Println("a 大于 20")
    } else if a < 10 {fmt.Println("a 小于 10")
    } else {fmt.Println("a 小于 20")
        fmt.Println("a 大于 10")
    }
    fmt.Println("a 的值为:", a)
}
/*
a 小于 20
a 大于 10
a 的值为: 15
*/

else-if 语句可以使用多个 else-if 关键字

func main() {
    a := 13
    if a > 20 {fmt.Println("a 大于 20")
    } else if a < 10 { //else-if 语句后
        fmt.Println("a 小于 10")
    } else if a == 15 {fmt.Println("a 等于 15")
    } else {fmt.Println("a 小于 20")
        fmt.Println("a 大于 10")
        fmt.Println("a 不等于 15")
    }
    fmt.Println("a 的值为:", a)
}
/*
a 小于 20
a 大于 10
a 不等于 15
a 的值为: 13
*/

选择语句

switch 语句

switch 表示选择语句的关键字,根据初始化表达式得出一个值,然后根据 case 语句的条件,执行相应的代码块,最终返回特定内容。
如果没有遇到特定的 case,则可以使用 default case;如果已经遇到符合条件的 case,那么后面的 case 都不会被执行。
在 Go 语言中,switch 有两种类型

  • 表达式 switch:case 包含与 switch 表达式的值进行比较的表达式
  • 类型 switch:case 包含于特殊注释的 switch 表达式的类型进行比较的类型

表达式 switch

func main() {
    score := 90
    level := "C"

    switch score { // 对应表达式的值选择 case
    case 90:
        level = "A"
    case 80:
        level = "B"
    case 70:
        level = "C"
    case 60:
        level = "D"
    default:
        level = "NULL"
    }
    fmt.Printf("你的等级是 %s\n", level)
}

这种语句写法虽然简单,但是缺点也很明显,原因是它直能判断单值的 case,对于除了固定的 case 以外的值,全都被返回到了 default,使得输出数据变得不合理。例如,socre 值为 91,却打印出“你的等级是 NULL”。

为了解决这一问题,我们可以使用完整的 switch 表达式,同时允许多个 switch 语句相互关联,比如下面的代码块,允许第二个 switch 语句接收第一个 switch 语句返回值作为选择条件。

func main() {
    score := 90
    level := "C"
    switch score { // 判断成绩所属区间
    case 90:
        level = "A"
    case 80:
        level = "B"
    default:
        level = "NULL"
    }

    switch { // 可以接受上一个 switch 语句的 case 值,switch 语句后的条件表达式不限制为常量或表达式,允许什么都不写
    case level == "A":
        fmt.Println("优秀") // 单个 case 可以多个结果选项
        fmt.Println("Good!")

    case level == "B":
        fmt.Println("良好")

    default:
        fmt.Println("你的成绩不存在!")
    }
    fmt.Println("你的成绩是:", score)
}
/*
优秀
Good!
你的成绩是: 90
 */

我们可以总结出 Go 语言的 switch 语句有以下特点:

  • switch 后面的条件表达式可以为空、常量、整数
  • case 表达式可以有多个
  • 左花括号必须同 switch 一行
  • 无需像 C 语言一样使用 break 关键字结束 case(待补充)
  • 单个 case 值可以出现多个选择结果
  • 多个 switch 语句之间可以相互关联(允许接收前一个 switch 语句的返回结果)
  • fallthrough 关键字可以把当前 case 控制权交给下一个 case 语句执行(不管什么条件都会被强制执行)
/*fallthrough 写法 */
func main() {
    score := 80 
    level := "B"
    switch score { // 对应表达式的值选择 case
    case 90:
        level = "A"
    case 80:
        level = "B"
        fallthrough // 把当前 case 控制权交给下一个语句判断,当前 case 语句被忽略
    default:
        level = "NULL"
    }
    fmt.Printf("你的等级是 %s\n", level)
}
/*
你的等级是 NULL
*/

以上语句 score 等于 80,正常来看会选择对应 case 值 80 的选择语句,但是fallthrough 关键字会忽略本层 case 语句的选择条件,强制执行下一个 case 语句的内容(不管什么条件都会被执行),所以输出的内容是 default 的选择语句。

type-switch 语句

类型 switch 语句可以根据条件表达式的类型自动选择执行哪个 case 代码块。使用类型 switch 语句时,需要判断的变量必须是 接口类型 的变量。有关接口的知识点将在以后的学习补充和完善。
注意:type-switch 语句不允许使用 fallthrough

type Element interface{} // 声明接口类型

func main() {
    var e Element = "这是一个字符串"
    switch value := e.(type) { // 根据类型选择对应 case 语句
    case int:
        fmt.Println("int", value)
    case string:
        fmt.Println("string", value)
    default:
        fmt.Println("unknown", value)
    }
}
/*
string 这是一个字符串
*/

初始化 switch

同样的,switch 语句后面也可以写上初始化表达式,并且只能有一句语句

(待补充)

select 语句

在选择语句中,除了 switch 语句,还有另一种 select 语句,这种语句用于配合通道 (channel) 的读写操作,用于多个 channel 的并发读写特性。有关并发的特性将在后面的学习中介绍。**switch 语句是按顺序从上到下依次执行的,select 语句是随机判断一个 case 来判断,直到匹配其中的一个 case 为止。

package main

import ("fmt")

// 并发编程的内容
func main() {a := make(chan int, 1024)
    b := make(chan int, 1024)

    for i := 0; i < 10; i++ {fmt.Printf("第 %d 次", i)
        a <- 1 //
        b <- 1

        select {
        case <-a:
            fmt.Println("from a")
        case <-b:
            fmt.Println("from b")
        }
    }
}

循环语句

在 Go 语言中,循环语句的关键字是 for,没有 while 关键字。for 语句可以根据指定的条件重复执行其内部的代码块,这个判断条件一般是由 for 后面的子语句给出的。

for 的子语句

for 语句后面的三个子语句我们称为:初始化子语句 条件子语句 后置子语句,这三个子语句顺序不能颠倒,其中条件子语句是必需的,条件子语句会返回一个布尔型,true 执行代码块,false 则跳出循环。

func main() {
    a := 0
    b := 5
    for a < b { // 只有条件子语句,实际是 for ; a<b ; 的简写 
        a++
        fmt.Println("a 的值是:%d", a)
    }
}
/*
a 的值是:%d 1
a 的值是:%d 2
a 的值是:%d 3
a 的值是:%d 4
a 的值是:%d 5
*/

range 子语句

每一个 for 语句都可以使用一个特殊的 range 子语句,其作用 类似迭代器,用于轮询数组或者切片值的每一个元素,也可以用于轮询字符串的每一个字符串和字典值中的键值对,甚至还可以持续读取一个通道类型值中的元素

注意:range 关键字左边表示是一对索引 - 值对

package main

import ("fmt")

func main() {
    str := "asdf"
    for i, char := range str { //range 关键字右边是 range 表达式,表达式一般写在 for 语句前面,提高代码可读性
        fmt.Printf("这是第 %d 个字符串的值是:%d\n", i, char)
    }
    for _, char := range str {fmt.Println(char) // 使用空标识符_屏蔽索引值,只打印字符值
    }
    for i := range str {fmt.Println(i) // 使用空标识符_屏蔽字符值,只打印索引值
    }
    for range str {fmt.Println("轮询完毕")
    }
}

/*
这是第 0 个字符串的值是:97
这是第 1 个字符串的值是:115
这是第 2 个字符串的值是:100
这是第 3 个字符串的值是:102
97
115
100
102
0
1
2
3
轮询完毕
轮询完毕
轮询完毕
轮询完毕
*/

对于空字典或切片、空数组、空字符串等情况,for 语句会直接结束,不会循环。

func main() {
    str := "" // 空字符串
    for i, char := range str { // 只有条件子语句
        fmt.Printf("这是第 %d 个字符串的值是:%d\n", i, char)
    }
    for _, char := range str {fmt.Println(char) // 值对
    }
    for i := range str {fmt.Println(i) // 索引
    }
    for range str {fmt.Println("轮询完毕")
    }
    fmt.Println("这是一个空字符串") // 字符串为空,跳出 for 循环
}

/*
这是一个空字符串
*/

延迟语句

在 Go 语言中,除了上述提到的常规流程控制的语句之外,还有一些特殊的控制语句,也是 Go 语言的语法特性之一。defer 语句就是其中一个,用于延迟调用指定函数,defer 关键字只能出现在函数内部,并且只能用于调用外部函数。例如,如下的代码块,defer 就是 fmt.Println 外部函数的延迟调用,被延迟的操作是 defer 后面的语句。

func main() {defer fmt.Println("请延迟执行这条语句") //defer 关键字只能出现于函数内部,并且用于外部函数的调用
    fmt.Println("请先执行这条语句")
}

/*
请先执行这条语句
请延迟执行这条语句
*/

defer 有两大特点:

  • 只有当 defer 语句全部执行完毕时,defer 所在函数才算真正结束执行
  • 当函数中有 defer 语句时,需要等待所有 defer 语句执行完毕,才会执行 return 语句。

    /* 利用 defer 特性打印反向数字列表 */
    var i int = 0
    func print(i int) { // 构造打印函数
        fmt.Println(i)
    }
    func main() {
        for ; i < 5; i++ { // 省略初始化子语句
            defer print(i) // 由于 defer 的延迟特性,满足先进后出,所以可将 defer 看成一个栈,依次打印出栈顺序
        }
    }
    
    /*
    4
    3
    2
    1
    0
    */
    

    标签

    在 Go 语言中,还有一个特殊的概念那就是标签(类似汇编语言),可以给 for、switch、select 语句等流程控制代码块打上一个标签,配合标签标识符可以方便跳转到某一个地方继续执行,有助于提高编程效率。

    func main() {
    LOOP1: // 标签
        for i := 0; i < 5; i++ {
            switch {
            case i < 5:
              fmt.Println("A")
                break LOOP1 // 跳到标签 LOOP1,处于 for 循环以外,不再执行任务
          case i == 5:
                fmt.Println("B")
          }
            fmt.Println("i 的值是:", i) // 由于使用 break 跳转到标签 LOOP1,此条语句不再执行
      }
    }
    

A
*/


为了提高代码的可读性,建议标签名称使用大写字母和数字。标签可以标记任何语句,并不限定于流程控制语句,** 未使用的标签会引发错误 **。### break 语句

break 语句用于打断当前流程控制,例如:

package main

import (

  "fmt"

)

func main() {

  for i := 0; i < 10; i++ {fmt.Println("i 的值是:", i)
      if i > 4 {break // 打断,跳出当前 for 循环}
  }

}

/*
i 的值是: 0
i 的值是: 1
i 的值是: 2
i 的值是: 3
i 的值是: 4
i 的值是: 5
*/


对于嵌套流程控制语句,break 只能中断当前循环,不能跳出外层循环。

package main

import (

  "fmt"

)

func main() {

  for i := 0; i < 5; i++ {
      switch {
      case i < 5:
          fmt.Println("A")
          break // 跳出当前 switch 循环,未跳出外层 for 循环
      case i == 5:
          fmt.Println("B")
      }
      fmt.Println("i 的值是:", i)
  }

}

/*
A
i 的值是: 0
A
i 的值是: 1
A
i 的值是: 2
A
i 的值是: 3
A
i 的值是: 4
*/


### continue 语句

与 break 语句相反,continue 语句则用于跳转到指定代码块或标签位置 ** 继续执行任务,且只能用于 for 循环 **。

func main() {
LOOP1: // 标签

  for i := 0; i < 5; i++ {
      switch {
      case i < 5:
          fmt.Println("A")
          continue LOOP1 // 跳到标签 LOOP1,处于 for 循环以外,继续执行任务
      case i == 5:
          fmt.Println("B")
      }
      fmt.Println("i 的值是:", i) /
  }

}

/*
A
A
A
A
A
*/


### goto 语句

goto 语句用于 ** 无条件跳转到相同函数的带标签语句,且只能在同一函数跳转 **。

func main() {

  for i := 0; i < 3; i++ {fmt.Println("A")
      goto LOOP2       // 跳到标签 LOOP2
      fmt.Println("B") // 忽略不执行
  LOOP2:
      fmt.Println("i 的值是:", i) // 顺序执行
  }

}

/*
A
i 的值是: 0
A
i 的值是: 1
A
i 的值是: 2
*/

退出移动版