8.Go语言-流程控制

1.go语言流程控制

1.if条件语句

  • 流程控制是每种编程语言控制逻辑走向和执行次序的重要部分,流程控制可以说是一门语言的“经脉”。

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

  • if 条件判断基本写法:

    if 表达式1{
        分支1
    }else if 表达式2{
        分支2
    }else{
        分支3
    }
    
  • if条件语句:在布尔表达式为true时,其后紧跟的语句块执行,如果为false则不执行。

    package main
    
    import "fmt"
    
    func main() {
            //基本写法
            var score = 65
            if score >= 90 {
                    fmt.Println("A")
            } else if score > 75 {
                    fmt.Println("B")
                    // 注意 else if 和 else 左大括号位置。
            } else {
                    fmt.Println("C")
            }
    }
    
  • if判断特殊写法

    • if条件判断还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,举个例子:
    func main() {
            //2.if判断特殊写法
            //此时score在if代码块中才生效
            if score := 65; score >= 90 {
                    fmt.Println("A")
            } else if score > 75 {
                    fmt.Println("B")
    
            } else {
                    fmt.Println("C")
            }
    }
    
    • 不支持三元操作(三目运算符) "a > b ? a:b"
  • if语句嵌套

    package main
    
    
    import "fmt"
    
    
    
    func main(){
            var a int = 10
            var b int = 20
            if a == 10 {
                    if b == 20 {
                            fmt.Printf("a=%d,b=%d\n",a,b)
                    }
            }
            fmt.Printf("a 值为:%d\n",a)
            fmt.Printf("b 值为:%d\n",b)
    }
    

2.for循环语句

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

  • Go语言中for循环有3种形式,只有其中一种使用分号:

    1.for init;condition;post {}
    2.for condition {}
    3.for {}
    
    init : 一般为赋值表达式,给控制变量赋值;
    condition:关系表达式或逻辑表达式,循环控制条件;
    post:一般为赋值表达式,给控制变量增量或减量;
    for语句执行过程如下:
            1.先对表达式 init 赋初始值;
            2.判断赋值表达式init是否满足给定condition条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。
    
  • for循环基本格式如下:

    for 初始语句;条件表达式;结束语句{
            循环体语句
    }
    
  • 条件表达式返回true时循环体不停地进行循环,直到条件表达式返回false时自动退出循环。

  • for 循环

package main

import "fmt"

//for循环
func main() {
        for i := 0; i < 10; i++ {
                fmt.Println(i)
        }
}
  • 省略初始语句,但是必须保留初始语句的分号

    func main() {
            var i = 0
            for ; i < 10; i++ {
                    fmt.Println(i)
            }
    }
    
    
  • 省略初始语句和结束语句

    func main() {
            var i = 10
            for i > 0 {
                    fmt.Println(i)
                    i--
            }
    }
    
  • 无限循环

    for {
                    fmt.Println("hello shahe")
            }
    
  • break 跳出for循环

    for i := 0; i < 5; i++ {
                    fmt.Println(i)
                    if i == 3 {
                            break
                    }
            }
    
  • continue继续下一次循环

    for i := 0; i < 5; i++ {
                    if i == 3 {
                            continue //跳过本次for循环,继续下一次循环
                    }
                    fmt.Println(i)
    
            }
    //0
    //1
    //2
    //4
    
  • 循环的嵌套

    • for循环中嵌套一个或多个for循环
    for [condition |  ( init; condition; increment ) | Range]
    {
       for [condition |  ( init; condition; increment ) | Range]
       {
          statement(s)
       }
       statement(s)
    }
    
  • 循环嵌套输出2-100素数

    func main() {
            var i,j int
            for i=2;i<100;i++ {
                    for j=2;j<=(i/j);j++ {
                            if (i%j == 0) {
                                    break// 如果发现因子,则不是素数
                            }
                    }
                    if (j > (i/j)) {
                            fmt.Printf("%d 是素数\n",i)
                    }
            }
    }
    

3.switch case循环

  • switch语句用于基于不同条件执行不同动作,每个case分支都是唯一,从上直下逐一测试,直到匹配为止。 Golang switch 分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。

    switch var1 {
            case val1:
                    ...
            case val2:
                    ...
            default:
                    ...
    }
    

    变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。 您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。

  • 使用switch语句可方便地对大量的值进行条件判断。

    package main
    
    import "fmt"
    
    //switch
    func main() {
            finger := 3
            switch finger {
            case 1:
                    fmt.Println("大拇指")
            case 2:
                    fmt.Println("食指")
            case 3:
                    fmt.Println("中指")
            case 4:
                    fmt.Println("无名指")
            case 5:
                    fmt.Println("小拇指")
            default:
                    fmt.Println("无效输入")
            }
    }
    //go语规定每个switch只能有一个default分支。一个分支可以有多个值,多个case值中间使用英文逗号分隔。
    
  • case一次判断多个值

num := 5
        switch num {
        case 1, 3, 5, 7, 9:
                fmt.Println("奇数")
        case 2, 4, 6, 8:
                fmt.Println("偶数")
}
  • case 中使用表达式
    • 分支还可以使用表达式,这时候switch语句后面不需要再跟判断变量
age := 30
switch {
    case age > 18:
        fmt.Println("成年人")
    case age < 18:
        fmt.Println("未成年人")
    default:
        fmt.Println("不是人")
}
  • fallthrough语法可以执行满足条件的case的下一个case.是为了兼容C语言中case设计的。

  • Type Switch

    • switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。
    • 语法:
    switch x.(type) {
            case type:
                    statement(s)
            case type:
                    statement(s)
            default:
                    statement(s)
    }
    
  • demo1

    func main(){
            var x interface{}
            // 带初始化语句
            switch i := x.(type) {
            case nil:
                    fmt.Printf("x的类型:%T\r\n",i)
            case int:
                    fmt.Printf("x 是 int 型")
            case float64:
                    fmt.Printf("x 是 float64 型")
            case func(int) float64:
                    fmt.Printf("x 是 func(int) 型")
            case bool, string:
                    fmt.Printf("x 是 bool 或 string型")
            default:
                    fmt.Printf("未知型")
            }
    }
    
  • demo2

    func main() {
            var j = 0
            switch j {
                    case 0:
                    case 1:
                            fmt.Println("1")
                    case 2:
                            fmt.Println("2")
                    default:
                            fmt.Println("def")
            }
    }
    
    
  • demo3

    var k = 0
        switch k {
        case 0:
            println("fallthrough")
            fallthrough
            /*
                Go的switch非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;
                而如果switch没有表达式,它会匹配true。
                Go里面switch默认相当于每个case最后带有break,
                匹配成功后不会自动向下执行其他case,而是跳出整个switch,
                但是可以使用fallthrough强制执行后面的case代码。
            */
        case 1:
            fmt.Println("1")
        case 2:
            fmt.Println("2")
        default:
            fmt.Println("def")
        }
    // fallthrough
    // 1
    

4.条件语句select

  • select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

  • select 是Go中的一个控制结构,类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。 select 随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。

  • Go 编程语言中 select 语句的语法如下:

    select {
        case communication clause  :
           statement(s);      
        case communication clause  :
           statement(s);
        /* 你可以定义任意数量的 case */
        default : /* 可选 */
           statement(s);
    }
    
  • select语句的语法:

    每个case都必须是一个通信
    所有channel表达式都会被求值
    所有被发送的表达式都会被求值
    如果任意某个通信可以进行,它就执行;其他被忽略。
    如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
    否则:
    如果有default子句,则执行该语句。
    如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
    
  • 实例:

    package main
    
    import "fmt"
    
    
    func main(){
            var c1,c2,c3 chan int
            var i1,i2 int
            select {
                    case i1 = <-c1:
                            fmt.Printf("received ",i1," from c1\n")
                    case c2 <- i2:
                            fmt.Printf("send ",i2," to c2\n")
                    case i3,ok := (<-c3):
                            if ok {
                                    fmt.Printf("received ", i3, " from c3\n")
                            } else {
                                    fmt.Printf("c3 is closed\n")
                            }
                    default:
                            fmt.Printf("no communication\n")
            }
    }
    // no communication
    

    select可以监听channel的数据流动

    select的用法与switch语法非常类似,由select开始的一个新的选择块,每个选择条件由case语句来描述

    与switch语句可以选择任何使用相等比较的条件相比,select由比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作

    select { //不停的在这里检测
    case <-chanl : //检测有没有数据可以读
    //如果chanl成功读取到数据,则进行该case处理语句
    case chan2 <- 1 : //检测有没有可以写
    //如果成功向chan2写入数据,则进行该case处理语句
    
    
    //假如没有default,那么在以上两个条件都不成立的情况下,就会在此阻塞//一般default会不写在里面,select中的default子句总是可运行的,因为会很消耗CPU资源
    default:
    //如果以上都没有符合条件,那么则进行default处理流程
    }
    

    在一个select语句中,Go会按顺序从头到尾评估每一个发送和接收的语句。

    如果其中的任意一个语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。 如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况: ①如果给出了default语句,那么就会执行default的流程,同时程序的执行会从select语句后的语句中恢复。 ②如果没有default语句,那么select语句将被阻塞,直到至少有一个case可以进行下去。

  • Go语言中select的使用及典型用法

    • select是Go中的一个控制结构,类似于switch语句,用于处理异步IO操作。select会监听case语句中channel的读写操作,当case中channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。 select中的case语句必须是一个channel操作

      select中的default子句总是可运行的。

      如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行。

      如果没有可运行的case语句,且有default语句,那么就会执行default的动作。

      如果没有可运行的case语句,且没有default语句,select将阻塞,直到某个case通信可以运行。

    • 超时判定

      //比如在下面的场景中,使用全局resChan来接受response,如果时间超过3S,resChan中还没有数据返回,则第二条case将执行
      var resChan = make(chan int)
      // do request
      func test() {
          select {
          case data := <-resChan:
              doData(data)
          case <-time.After(time.Second * 3):
              fmt.Println("request time out")
          }
      }
      
      func doData(data int) {
          //...
      }
      
    • 退出

      //主线程(协程)中如下:
      var shouldQuit=make(chan struct{})
      fun main(){
          {
              //loop
          }
          //...out of the loop
          select {
              case <-c.shouldQuit:
                  cleanUp()
                  return
              default:
              }
          //...
      }
      
      //再另外一个协程中,如果运行遇到非法操作或不可处理的错误,就向shouldQuit发送数据通知程序停止运行
      close(shouldQuit)
      
    • 判断channel是否阻塞

      //在某些情况下是存在不希望channel缓存满了的需求的,可以用如下方法判断
      ch := make (chan int, 5)
      //...
      data:=0
      select {
      case ch <- data:
      default:
          //做相应操作,比如丢弃data。视需求而定
      }
      

5.循环语句range

  • Golang range 类似迭代器操作,返回索引,值或键,值

  • for循环的range格式可以对slice,map,数组,字符串等进行迭代循环,格式如下:

    for key,value := range oldMap{
            newMap[key] = value
    }
    
  • 课忽略不想要的返回值,或 "_" 这个特殊变量

    package main
    
    func main() {
            s := "abcde"
            // 忽略value      
            for i:=range s{
                    println(s[i])
            }
            // 忽略index
            for _,v := range s{
                    println(v)
            }
    }
    
    
    package main
    
    func main() {
            m := map[string]int{"a":1,"b":2}
            for k,v := range m{
                    println(k,v)
            }
    }
    
  • range会复制对象

    import "fmt"
    
    func main() {
        a := [3]int{0, 1, 2}
    
        for i, v := range a { // index、value 都是从复制品中取出。
    
            if i == 0 { // 在修改前,我们先修改原数组。
                a[1], a[2] = 999, 999
                fmt.Println(a) // 确认修改有效,输出 [0, 999, 999]。
            }
    
            a[i] = v + 100 // 使用复制品中取出的 value 修改原数组。
    
        }
    
        fmt.Println(a) // 输出 [100, 101, 102]。
    }
    // [0 999 999]
    // [100 101 102]
    
  • 建议改用引用类型,其底层数据不会被复制。slice是引用类型

    package main
    
    func main() {
        s := []int{1, 2, 3, 4, 5}
    
        for i, v := range s { // 复制 struct slice { pointer, len, cap }。
    
            if i == 0 {
                s = s[:3]  // 对 slice 的修改,不会影响 range。
                s[2] = 100 // 对底层数据的修改。
            }
    
            println(i, v)
        }
    }
    // 0 1
    // 1 2
    // 2 100
    // 3 4
    

    另外两种引用类型 map、channel 是指针包装,而不像 slice 是 struct。

  • for 和 for range有什么区别?

    主要是使用场景不同
    for可以
        遍历array和slice
        遍历key为整型递增的map
        遍历string
    for range可以完成所有for可以做的事情,却能做到for不能做的,包括
        遍历key为string类型的map并同时获取key和value
        遍历channel
    

6.循环控制Goto,Break,Continue

1.三个语句都可以配合标签(label)使用
2.标签名区分大小写,不适用会造成编译错误
3.continue、break配合标签(label)可用于多层循环跳出
4.goto是调整执行位置,与continue、break配合标签(label)的结果并不相同
  • 小测试:99乘法表

    package main
    
    import "fmt"
    
    func main() {
            for i := 1; i < 10; i++ {
                    for j := 1; j <= i; j++ {
                            fmt.Printf("%v*%v=%v\t", i, j, i*j)
                    }
                    fmt.Println("")
            }
    
    }