1.控制结构

Go提供了下面这些条件结构和分之机构:

  • if-else 结构
  • switch 结构
  • select 结构,用于channel的选择

可以使用迭代或循环结构来重复执行一次或多次某段代码(任务):

  • for(range) 结构

一些如 break 和 continue 这样的关键字可以用于中途改变循环的状态

还可以使用 return 来结束某个函数的执行,或使用 goto 和标签来调整程序的执行位置

Go 完全省略了if、switch 和 for 结构中条件语句两侧的括号,相比 Java、C++和C# 中减少了很多视觉混乱的因素,因此使得代码更加简洁


2.if-else 结构

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
  5. } else if condition3 {
  6. // do something
  7. } else {
  8. // default
  9. }

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

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

非法的Go代码:

  1. if condition {
  2. //
  3. }
  4. else {
  5. // 无效的
  6. }

示例:booleans.go

  1. pakcage main
  2. import "fmt"
  3. func main(){
  4. bool1 := ture
  5. if bool1 {
  6. fmt.Println("The value is true")
  7. } else {
  8. fmt.Println("The value is false")
  9. }
  10. }
  1. The value is true

注意 这里不需要使用 if bool1 == true 来判断,因为 bool1 本身已经是一个布尔类型的值;

这种做法一般都用在测试 true 或者有利条件时,但你也可以使用取反 ! 来判断值的相反结果,如:if !bool1 或者 if !(condition), 后者的括号大多数情况下是必须的,如这种情况:if !(var1==var2);

当 if 结构内有 break、continue、goto 或者 return 语句时,Go代码的常见写法是省略else部分,无法满足哪个条件都会返回x或者y时,一般使用以下写法:

  1. if condition {
  2. return x
  3. }
  4. return y

举一些有用的例子:

  • 判断一个字符串是否为空:
    • if str == "" { ... }
    • if len(str) == 0 { ... }
  • 判断运行Go程序的操作系统类型,可以通过常量 runtime.GOOS 来判断if runtime.GOOS == "windows" { ... } else { ... }

  • 函数 Abs() 用于返回一个整型数字的绝对值:``` func Abs(x int) int { if x < 0 {

    1. return -x

    } return x } ```

  • isGreater 用于比较两个整型数字的大小:``` func isGreater(x, y int) bool { if x > y {

    1. return true

    } return false } ```

if 可以包含一个初始化语句(如:给一个变量赋值),这种写法具有固定的格式(在初始化语句后方必须加上分号):

  1. if initialization; condition {
  2. ...
  3. }

例如:

  1. val := 10
  2. if val > max [
  3. ...
  4. ]

可以写成:

  1. if val := 10; val > max {
  2. ...
  3. }

3.测试多返回值函数的错误

程序应该子啊最接近的位置检查所有相关的错误,至少需要暗示用户有错误发生并对函数进行返回,甚至中断程序。

测试err变量是否包含一个真正的错误(if err != nil),如果确实存在错误,则会打印相应的错误信息然后通过return提前结束函数的执行。还可以使用携带返回值的return形式,例如 return err。这样一来,函数的调用者就可以检查函数执行过程中是否存在错误了。

  1. value, err := pack1.Function1(param1)
  2. if err != nil {
  3. fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
  4. return err
  5. }
  6. // 为发生错误,继续执行

如果我们想要在错误发生的同时终止程序的运行,可以使用 os 包的 Exit 函数:

  1. if err != nil {
  2. fmt.Printf("Program stopping with error %v", err)
  3. os.Exit(1)
  4. }

将错误的获取放置在if语句的初始化部分

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

或者将 ok-pattern 的获取放置在if语句的初始化部分,然后进行判断:

  1. if value, ok := readData(); ok {
  2. ...
  3. }

4.switch 结构

表达式:

  1. switch var1 {
  2. case val1:
  3. ...
  4. case val2:
  5. ...
  6. default:
  7. ...
  8. }

变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值;类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式;

也可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3

每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配到某个 case 或者进入 default 条件为止;

一旦成功匹配到某个分支,在执行完相应代码后就会退出整个 switch 代码块,不需要特别使用 break 语句来表示结束;

如果在执行完每个分支的代码后,还希望继续执行后续分支的代码,可以使用 fallthrough 关键字;

  1. switch i {
  2. case 0: fallthrough
  3. case 1:
  4. f () // 当 i == 0 时函数也会被调用
  5. }

switch第二种形式:

  1. switch {
  2. case condition1:
  3. ...
  4. case condition2:
  5. ...
  6. default:
  7. ...
  8. }

例如:

  1. switch {
  2. case i < 0:
  3. f1()
  4. case i == 0:
  5. f2()
  6. case i > 0:
  7. f3()
  8. }

switch 第三种形式是包含一个初始化语句:

  1. switch initialization {
  2. case val1:
  3. ...
  4. case val2:
  5. ...
  6. default:
  7. ...
  8. }
  1. switch result := calculate() {
  2. case result < 0:
  3. ...
  4. case result > 0:
  5. ...
  6. default:
  7. ...
  8. }

例:

  1. package main
  2. import "fmt"
  3. func main() {
  4. k := 6
  5. switch k {
  6. case 4:
  7. fmt.Println("was <= 4")
  8. fallthrough
  9. case 5:
  10. fmt.Println("was <= 5")
  11. fallthrough
  12. case 6:
  13. fmt.Println("was <= 6")
  14. fallthrough
  15. case 7:
  16. fmt.Println("was <= 7")
  17. fallthrough
  18. case 8:
  19. fmt.Println("was <= 8")
  20. fallthrough
  21. default:
  22. fmt.Println("default case")
  23. }
  24. }

输出:

  1. was <= 6
  2. was <= 7
  3. was <= 8
  4. default case

5.for 结构

5.1 基于计数器的迭代

基本形式:for 初始化语句; 条件语句; 修饰语句 {}

for1.go:

  1. package main
  2. import "fmt"
  3. func main() {
  4. for i := 0; i < 5; i++ {
  5. fmt.Printf("This is the %d iteration\n", i)
  6. }
  7. }

输出:

  1. This is the 0 iteration
  2. This is the 1 iteration
  3. This is the 2 iteration
  4. This is the 3 iteration
  5. This is the 4 iteration

由花括号括起来的代码块会被重复执行已知次数,该次数是根据计数器(此例为 i)决定的。循环开始前,会执行且仅会执行一次初始化语句 i := 0;;这比在循环之前声明更为简短。紧接着的是条件语句 i < 5;,在每次循环开始前都会进行判断,一旦判断结果为 false,则退出循环体。最后一部分为修饰语句 i++,一般用于增加或减少计数器;

这三部分组成的循环的头部,它们之间使用分号 ; 相隔,但并不需要括号 () 将它们括起来。例如:for (i = 0; i < 10; i++) {},这是无效的代码!

特别注意,永远不要在循环体内修改计数器,这在任何语言中都是非常差的实践!

也可以在循环中同时使用多个计数器:

for i, j := 0, N; i < j; i, j = i+1, j-1 {}

也可以将两个for循环嵌套起来:

  1. for i:=0; i<5; i++ {
  2. for j:=0; j<10; j++ {
  3. println(j)
  4. }
  5. }

5.2 基于条件判断的迭代

for 结构的第二种形式是没有头部的条件判断迭代(类似其它语言中的while循环),基本形式为:for 条件语句 {}

可以认为这是没有初始化语句和修饰语句的for结构,因此 ;; 便是多余的了

for2.go:

  1. package main
  2. import "fmt"
  3. func main() {
  4. var i int = 5
  5. for i >= 0 {
  6. i = i - 1
  7. fmt.Printf("The variable i is now: %d\n", i)
  8. }
  9. }

输出:

  1. The variable i is now: 4
  2. The variable i is now: 3
  3. The variable i is now: 2
  4. The variable i is now: 1
  5. The variable i is now: 0
  6. The variable i is now: -1

5.3 无限循环

条件语句是可以被省略的,如 i:=0; ; i++ 或 for {} 或 for ;; {}(;; 会在使用 gofmt 时被移除):这些循环的本质就是无限循环。最后一个形式也可以被改写为 for true {},但一般情况下都会直接写 for {}。

如果 for 循环的头部没有条件语句,那么就会认为条件永远为 true,因此循环体内必须有相关的条件判断以确保会在某个时刻退出循环。

想要直接退出循环体,可以使用 break 语句或 return 语句直接返回。

但这两者之间有所区别,break 只是退出当前的循环体,而 return 语句提前对函数进行返回,不会执行后续的代码。

无限循环的经典应用是服务器,用于不断等待和接受新的请求。

  1. for t, err = p.Token(); err == nil; t, err = p.Token() {
  2. ...
  3. }

5.4 for-range 结构

它可以迭代任何一个集合(包括数组和map),语法上很类似其它语言中foreach语句, 一般形式为:for index, val := range coll {}

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

  1. for pos, char := range str {
  2. ...
  3. }

5.5 break和continue

一个 break 的作用范围为该语句出现后的最内部的结构,它可以被用于任何形式的 for 循环(计数器、条件判断等)。但在 switch 或 select 语句中,break 语句的作用结果是跳过整个代码块,执行后续的代码。

下面的示例中包含了嵌套的循环体(for3.go),break 只会退出最内层的循环:

for3.go:

  1. package main
  2. func main() {
  3. for i:=0; i<3; i++ {
  4. for j:=0; j<10; j++ {
  5. if j>5 {
  6. break
  7. }
  8. print(j)
  9. }
  10. print(" ")
  11. }
  12. }

输出:

  1. 012345 012345 012345

关键字 continue 忽略剩余的循环体而直接进入下一次循环,但不是无条件执行下一次循环,执行之前依旧需要满足循环的判断条件。

for4.go:

  1. package main
  2. func main() {
  3. for i := 0; i < 10; i++ {
  4. if i == 5 {
  5. continue
  6. }
  7. print(i)
  8. print(" ")
  9. }
  10. }

输出:

  1. 0 1 2 3 4 6 7 8 9

5.6 标签和goto

for、switch或select语句都可以配合标签(label)形式的标识符使用,即某一行第一个以冒号(:)极为的单词

标签的名称是大小写敏感的,为了提升可读性,一般建议使用全部大写字母

for5.go:

  1. package main
  2. import "fmt"
  3. func main() {
  4. LABEL1:
  5. for i := 0; i <= 5; i++ {
  6. for j := 0; j <= 5; j++ {
  7. if j == 4 {
  8. continue LABEL1
  9. }
  10. fmt.Printf("i is: %d, and j is: %d\n", i, j)
  11. }
  12. }
  13. }

当 j==4 和 j==5 的时候,没有任何输出:标签的作用对象为外部循环,因此 i 会直接变成下一个循环的值,而此时 j 的值就被重设为 0,即它的初始值。如果将 continue 改为 break,则不会只退出内层循环,而是直接退出外层循环了。另外,还可以使用 goto 语句和标签配合使用来模拟循环。

goto.go:

  1. package main
  2. func main() {
  3. i:=0
  4. HERE:
  5. print(i)
  6. i++
  7. if i==5 {
  8. return
  9. }
  10. goto HERE
  11. }

特别注意 使用标签和 goto 语句是不被鼓励的,它们会很快导致非常糟糕的程序设计,而且总有更加可读的替代方案来实现相同的需求。