第三章 流程控制

作者: 牧码人爱跑马 | 来源:发表于2018-07-11 21:10 被阅读1次

    3.1 If语句

    语法格式:

    if 布尔表达式 {
       /* 在布尔表达式为 true 时执行 */
    }
    
    if 布尔表达式 {
       /* 在布尔表达式为 true 时执行 */
    } else {
      /* 在布尔表达式为 false 时执行 */
    }
    
    if 布尔表达式1 {
       /* 在布尔表达式1为 true 时执行 */
    } else if 布尔表达式2{
       /* 在布尔表达式1为 false ,布尔表达式2为true时执行 */
    } else{
       /* 在上面两个布尔表达式都为false时,执行*/
    }
    

    示例代码:

    package main
    
    import "fmt"
    
    func main() {
       /* 定义局部变量 */
       var a int = 10
     
       /* 使用 if 语句判断布尔表达式 */
       if a < 20 {
           /* 如果条件为 true 则执行以下语句 */
           fmt.Printf("a 小于 20\n" )
       }
       fmt.Printf("a 的值为 : %d\n", a)
    }
    

    如果其中包含一个可选的语句组件(在评估条件之前执行),则还有一个变体。它的语法是

    if statement; condition {  
    }
    

    示例代码:

    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        if num := 10; num % 2 == 0 { //checks if number is even
            fmt.Println(num,"is even") 
        }  else {
            fmt.Println(num,"is odd")
        }
    }
    

    需要注意的是,num的定义在if里,那么只能够在该if..else语句块中使用,否则编译器会报错的。

    3.2 switch

    与if类似,switch语句也用于选择执行,但具体的使用场景会有所不同。

    package main
    
    func main() {
        a,b,c,x:= 1,2,3,2
    
        switch x {
        case a,b:
            println("a | b")
        case c:
            println("c")
        case 4:
            println("d")
        default:
            println("z")
        
        }
    }
    

    输出:

    a | b


    条件表达式支持非常量值,这要比C更加灵活。相比if表达式,switch值列表要更加简洁。
    编译器对if、switch生成的机器码可能完全相同,谁性能更好要看具体情况。


    switch 同样支持初始化语句。只有全部匹配失败时,才会执行default。

    func main(){
        switch x:=5;x {
        default:  //default虽然放在最上边了,但编译器不会先执行它。
            x+=100
            println(x)
        case 5:
            x+=50
            println(x)
        }
    }
    

    输出:55
    但一般建议把default放在switch末尾。

    相邻的空case不构成多条件匹配,例如:

    switch x {
    case a :            //单条件,内容为空。隐式“case a: break;”
    

    不能出现重复的case常量值。

    无须显式执行break语句,case执行完毕后自动中断。如须贯通后续case(源码顺序),须执行fallthrough,但不再匹配后续条件表达式。

    func main(){
        switch x:=5;x {
        default:
            println(x)
        case 5:
            x+=10
            println(x)
            if x >=25{
                break  //终止,不再执行后续语句
            }
            fallthrough  //必须是case块的最后一条语句
        case 6:
            x+=20
            println(x)
        }
    }
    

    输出:15 35

    某些时候,switch还被用来替代if语句。被省略的switch条件表达式默认值为true,继而与case比较表达式结果匹配。

    func main(){
        switch x:=5; {   //相当于“switch x:=5;true {...}”
        case x>5:
            println("a")
        case x>0 && x<=5:      //不能写成case x>0, x<=5,因为多条件表达式是or的关系
            println("b")
        default:
            println("z")
        }
    }
    

    switch语句也可用于接口类型匹配,详见后续章节。

    Type Switch

    switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

    switch x.(type){
        case type:
           statement(s);      
        case type:
           statement(s); 
        /* 你可以定义任意个数的case */
        default: /* 可选 */
           statement(s);
    }
    
    package main
    
    import "fmt"
    
    func main() {
       var x interface{}
         
       switch i := x.(type) {
          case nil:   
             fmt.Printf(" x 的类型 :%T",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("未知型")     
       }   
    }
    

    结果

    x 的类型 :<nil>
    

    3.3 select 语句

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

    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("sent ", i2, " to c2\n")
          case i3, ok := (<-c3):  // same as: 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
    
    • 每个case都必须是一个通信

    • 所有channel表达式都会被求值

    • 所有被发送的表达式都会被求值

    • 如果任意某个通信可以进行,它就执行;其他被忽略。

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

    • 否则:

      如果有default子句,则执行该语句。

      如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。

    3.4 for循环

    go中只有for一种循环语句,没有while,但常用的方式都支持。

    func main() {
        for i:=0; i<3; i++{  //初始化表达式支持函数调用或定义局部变量
            
        }
        for x<5{
            x++
        }
        for {   //类似while true
            break
        }
    }
    

    初始化语句仅被执行一次。 条件表达式中如有函数调用,须确认是否会重复执行。可能会被编译器优化掉,也可能是动态结果须每次执行确认。

    func count()int{
        println("count.")
        return 3
    }
    func main() {
        for i,c:=0,count();i<c;i++{  //初始化语句中的count函数仅执行一次
            println("a",i)
        }
        d:=0
        for d<count(){    //条件表达式中的count重复执行
            println("b",d)
            d++
        }
    }
    

    输出结果:

    count.
    a 0
    a 1
    a 2
    count.
    b 0
    count.
    b 1
    count.
    b 2
    count.

    规避方式就是在初始化表达式中定义局部变量保存count结果。

    可用for ...range完成数据迭代,支持字符串、数组、数组指针、切片、字典、通道类型,返回索引、键值数据。

    没有相关接口实现自定义类型迭代,除非基础类型是上述类型之一。

    允许返回单值,或用_忽略。甚至可以仅迭代,不返回。可用来执行清空channel操作。

    func main() {
        data:=[3]string{"a","b","c"}
        for i:=range data{
            println(i,data[i])
        }
        for _,s :=range data{
            println(s)
        }
        for range data{  //仅迭代,不返回。可用来执行清空channel操作。
    
        }
    }
    

    无论普通for循环,还是range迭代,其定义的局部变量都会重复使用。

    func main(){
        data:=[3]string{"a","b","c"}
        for i,s:=range data{
            println(&i,&s)
        }
    }
    

    输出:

    0xc04202fee8 0xc04202ff00
    0xc04202fee8 0xc04202ff00
    0xc04202fee8 0xc04202ff00


    这会对闭包产生一定的影响,后面会详述解决方案。


    注意,range会复制目标数据。受直接影响的是数组,可改用数组指针或切片类型。

    func main() {
        data := [3]int{10, 20, 30}
        for i,x:=range data{   //从data的副本中取值,所以下面对data原值的修改不影响这个副本,所以x的值还是原值。
            if i ==0{
                data[0] += 100
                data[1] += 200
                data[2] += 300
            }
            fmt.Println(data)
            fmt.Printf("x: %d, data: %d\n", x, data[i])  //注意,这里的data是原值,并不是data副本。data副本只存在于range取值。
        }
    
        for i, x := range data[:] { //仅复制slice,不包括底层array。
            if i == 0 {
                data[0] += 100
                data[1] += 200
                data[2] += 300
            }
            fmt.Printf("x: %d, data: %d\n", x, data[i])
        }
    

    输出:

    x: 10, data: 110
    x: 20, data: 220
    x: 30, data: 330
    x: 110, data: 210          // 当i=0时,x已经取完值,所以是110不是210.
    x: 420, data: 420         //复制的仅是slice自身,底层array依旧是原对象
    x: 630, data: 630
    

    相关数据类型中,字符串、切片基本结构是个很小的结构体,而字典、通道本身是指针封装,复制成本都很小,无须专门优化。


    如果range目标是函数调用,也仅被执行一次。

    func data()[]int{
        for i:=0;i<3;i++{
            println("orgin data.",i)
            return []int{10,20,30}
        }
        return []int{20,30,40}
    }
    
    func main(){
        for i,x:=range data(){
            println(i,x)
        }
    }
    

    输出:

    orgin data. 0
    0 10
    1 20
    2 30
    

    建议循环嵌套不要超过2层,否则会难以维护。必要时可剥离,重构为函数。

    3.4 跳出循环:goto, continue,break

    break语句

    break:用于switch、for、Select循环体。break终止整个语句块的执行。

    示例代码:

    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        for i := 1; i <= 10; i++ {
            if i > 5 {
                break //loop is terminated if i > 5
            }
            fmt.Printf("%d ", i)
        }
        fmt.Printf("\nline after for loop")
    }
    

    continue语句

    continue:continue语句仅用于跳过for循环的当前迭代,终止后续逻辑,立即进入下一轮循环。

    示例代码:

    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        for i := 1; i <= 10; i++ {
            if i%2 == 0 {
                continue
            }
            fmt.Printf("%d ", i)
        }
    }
    

    goto语句

    网上对于goto的诟病很多。但是还是有很多地方用到,就连go源码里也随处可见。
    虽然某些设计模式可用来消除goto语句,但在性能优先的场合,它能发挥积极作用。
    使用goto前,须先定义标签。
    goto:可以无条件地转移到过程中指定的行

    package main
    
    import "fmt"
    
    func main() {
       /* 定义局部变量 */
       var a int = 10
    
       /* 循环 */
       LOOP: for a < 20 {
          if a == 15 {
             /* 跳过迭代 */
             a = a + 1
             goto LOOP
          }
          fmt.Printf("a的值为 : %d\n", a)
          a++     
       }  
    }
    

    配合标签,break和continue可在多层嵌套中指定目标层级。

    package main
    
    func main() {
    
        outer:
            for x:=0; x<5; x++{
                for y:=0; y<10; y++{
                    if y>2 {
                        println()
                        continue outer
                    }
    
                    if x > 2{
                        break outer
                    }
    
                    print(x,":",y, " ")
                }
            }
    }
    

    输出:

    0:0 0:1 0:2 
    1:0 1:1 1:2 
    2:0 2:1 2:2 
    

    相关文章

      网友评论

        本文标题:第三章 流程控制

        本文链接:https://www.haomeiwen.com/subject/jrghpftx.html