Golang 入门资料+笔记Golang

第三章 流程控制

2018-07-11  本文已影响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

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 
上一篇下一篇

猜你喜欢

热点阅读