Go基础|第5章:流程控制语句

  • 原创
  • Madman
  • /
  • /
  • 0
  • 1336 次阅读

Golang.jpg

Synopsis: 在 Go 中只有很少的几个控制结构,没有 do 或者 while 循环,只有 for 循环。有 if 语句和灵活的 switch 语句,还有延迟调用 defer 语句,后续学习并发 channel 章节时,我们还会学习 select 语句

1. for

1.1 基本格式

Go 语言只有一种循环结构:for 循环。基本的 for 循环由三部分组成,它们用分号隔开:

  • 初始化语句 init statement: 在第一次迭代前执行一次
  • 条件表达式 condition:在每次迭代前求值
  • 后置语句 post statement:在每次迭代的结尾执行

初始化语句通常为 简短变量声明语句 :=,该变量声明仅在 for 语句的作用域中可见。一旦条件表达式的布尔值为 false,循环迭代就会终止。for 语句后面的三个构成部分外没有小括号,大括号 { } 则是必须的

package main

import "fmt"

func main() {
  sum := 0
  for i := 0; i < 10; i++ {
    sum += i
  }
  fmt.Println(sum)  // 45
}

1.2 类 while 循环

初始化语句和后置语句都是可选的:

package main

import "fmt"

func main() {
  n := 1
  for ; n < 5; {
    n *= 2
  }
  fmt.Println(n)  // 8
}

此时你可以去掉分号,就类似于 C 语言中的 while 语句:

package main

import "fmt"

func main() {
  n := 1
  for n < 5 {  // 省略初始化语句、后置语句以及后面的分号
    n *= 2
  }
  fmt.Println(n) // 8
}

1.3 无限循环

如果连循环条件也省略掉的话,那么该循环就永远不会结束(无限循环

sum := 0
for {
  sum++  // repeated forever
}
fmt.Println(sum)  // never reached

1.4 for-range 遍历

可以使用 for := range 循环 string / array / slice / map / channel 中的每个元素:

package main

import "fmt"

func main() {
  strings := []string{"hello", "world"}
  for i, s := range strings {
    fmt.Println(i, s)
  }
}

/* Output:
0 hello
1 world
*/

1.5 continue 和 break

continuebreak 关键字跟 C 语言中的作用一样,表示立即进入下一次循环、跳出最内层循环

sum := 0
for i := 1; i < 5; i++ {
  if i%2 != 0 {  // skip odd numbers
    continue
  }
  sum += i
}
fmt.Println(sum)  // 6

1.6 类 do-while 语句

实现类似 C 中的 do-while 语句,先执行循环中的语句,然后再判断表达式是否为真,如果为真则继续循环;如果为假,则终止循环:

do {
  commands
}
while (condition);

方式一:

for ok := true; ok; ok = condition {
  commands
}
package main

import "fmt"

func main() {
  i := 0
  for ok := true; ok; ok = i < 3 {
    fmt.Println(i)
    i++
  }
}

/* Output:
0
1
2
*/

方式二:

for {
  commands
  if !condition {
    break
  }
}
package main

import "fmt"

func main() {
  i := 0
  for {
    fmt.Println(i)
    i++
    if !(i < 3) {
      break
    }
  }
}

/* Output:
0
1
2
*/

1.7 类 until-do 语句

实现类似 Bash 中的 until-do 语句,只要不符合判断条件,就不断循环执行指定的语句;一旦符合判断条件,就退出循环:

until condition; do
  commands
done

方式一:

for ok := true; ok; ok = !condition {
  commands
}
package main

import "fmt"

func main() {
  i := 0
  for ok := true; ok; ok = !(i >= 3) {
    fmt.Println(i)
    i++
  }
}

/* Output:
0
1
2
*/

方式二:

for {
  commands
  if condition {
    break
  }
}
package main

import "fmt"

func main() {
  i := 0
  for {
    fmt.Println(i)
    i++
    if i >= 3 {
      break
    }
  }
}

/* Output:
0
1
2
*/

2. if

2.1 基本格式

条件表达式的值为 true 时,执行 if 后面的语句。与 for 循环类似,表达式外无需小括号,而大括号 { } 则是必须的

package main

import (
  "fmt"
  "math"
)

func sqrt(x float64) string {
  if x < 0 {
    return sqrt(-x) + "i"
  }
  return fmt.Sprint(math.Sqrt(x))
}

func main() {
  fmt.Println(sqrt(2), sqrt(-4))
}

/* Output:
1.4142135623730951 2i
*/

2.2 带初始化语句

同 for 一样,if 语句可以在条件表达式前执行一个简单的初始化语句,该语句声明的变量作用域仅在 if 之内

package main

import (
  "fmt"
  "math"
)

func pow(x, n, lim float64) float64 {
  if v := math.Pow(x, n); v < lim {
    return v
  }
  // 报错 undefined: v
  // fmt.Printf("v = %v", v)
  return lim
}

func main() {
  fmt.Println(
    pow(3, 2, 10),
    pow(3, 3, 20),
  )
}

/* Output:
9 20
*/

2.3 带 else if / else 语句

在 if 的简短变量声明语句中声明的变量,可以在对应的 else 块中使用

func area(x, y int) {
  if i := x * y; i > 200 {
    fmt.Printf("Big house")
  } else if i > 100 {
    fmt.Printf("Good house")
  } else {
    fmt.Printf("Just ok")
  }
}

Go 语言没有三元条件运算符 res = expr ? x : y,只能写成:

if expr {
  res = x
} else {
  res = y
}

或者创建一个函数:

func Min(x, y int) int {
  if x <= y {
    return x
  }
  return y
}

3. switch

switch 是编写一连串 if-else 语句的简便方法,从上往下 依次判断各 case 分支的值是否与 switch 后面的条件表达式值相等,一旦找到满足条件的 case 分支就运行它里面的语句,并退出(忽略后续的 case 分支,除非主动在 case 分支中以 fallthrough 语句结束)。如果所有 case 分支都不匹配,并且提供了 default 分支时,将执行 default 分支里的语句

3.1 基本格式

package main

import (
  "fmt"
  "runtime"
)

func main() {
  fmt.Print("Go runs on ")
  switch os := runtime.GOOS; os {
  case "darwin":
    fmt.Println("OS X.")
  case "linux":
    fmt.Println("Linux.")
  default:
    // freebsd, openbsd,
    // plan9, windows...
    fmt.Printf("%s.\n", os)
  }
}

/* Output:
Go runs on windows.
*/

switch 的 case 分支条件无需为常量,且取值不必为整数:

package main

import (
  "fmt"
  "time"
)

func main() {
  fmt.Println("When's Saturday?")
  today := time.Now().Weekday()
  switch time.Saturday {
  case today + 0:
    fmt.Println("Today.")
  case today + 1:
    fmt.Println("Tomorrow.")
  case today + 2:
    fmt.Println("In two days.")
  default:
    fmt.Println("Too far away.")
  }
}

/* Output:
When's Saturday?
Too far away.
*/

3.2 省略条件表达式

没有条件的 switch 同 switch true {...} 一样,这种形式能将一长串 if-then-else 写得更加清晰

package main

import (
  "fmt"
  "time"
)

func main() {
  t := time.Now()
  switch {  // missing expression means "true"
  case t.Hour() < 12:
    fmt.Println("Good morning!")
  case t.Hour() < 17:
    fmt.Println("Good afternoon.")
  default:
    fmt.Println("Good evening.")
  }
}

/* Output:
Good afternoon.
*/

3.3 case 分支包含多个条件表达式

package main

import (
  "fmt"
)

func WhiteSpace(c rune) bool {
  switch c {
  case ' ', '\t', '\n', '\f', '\r':
    return true
  }
  return false
}

func main() {
  c := '\t'
  if ok := WhiteSpace(c); ok {
    fmt.Printf("%q is whitespace", c)
  }
}

/* Output:
'\t' is whitespace
*/

3.4 fallthrough

如果 fallthrough 语句是 case 分支的 最后的语句,那么执行完匹配的指定分支后不会退出 switch,而是把控制权交给下一个 case 分支

package main

import (
  "fmt"
)

func main() {
  switch 2 {
  case 1:
    fmt.Println("1")
    fallthrough
  case 2:
    fmt.Println("2")
    fallthrough
  case 3:
    fmt.Println("3")
  }
}

/* Output:
2
3
*/

3.5 break 退出 switch

break 语句终止 最内层forswitchselect 语句的执行,而 switch 的机制本来就是执行完匹配的 case 分支后就自动退出 switch 了:

switch 2 {
case 1:
  fmt.Println("1")
case 2:  // do nothing
case 3:
  fmt.Println("3")
}

或:

switch 2 {
case 1:
  fmt.Println("1")
case 2:  // skip this case
  break
case 3:
  fmt.Println("3")
}

如果 switch 在 for 循环里面,那么可以在 for 循环的外面设置一个标签,在 switch 中使用 break 可以跳出外层的 for 循环,直达标签位置:

package main

import (
  "fmt"
)

func main() {
Loop:
  for _, ch := range "a b\nc" {
    switch ch {
    case ' ': // skip space
      break
    case '\n': // break at newline
      break Loop
    default:
      fmt.Printf("%c\n", ch)
    }
  }
}

/* Output:
a
b
*/

执行顺序:

  • 首先执行 switch expression
  • 然后从左至右、从上至下执行 case 分支中的各个 case expressions
    • 第一个匹配 switch expression 的 case expression 立即运行 case 分支下的语句
    • 此分支的其它 case expressions 被忽略
package main

import (
  "fmt"
)

// Foo prints and returns n.
func Foo(n int) int {
  fmt.Println(n)
  return n
}

func main() {
  switch Foo(2) {
  case Foo(1), Foo(2), Foo(3):  // Foo(3) 不会被执行
    fmt.Println("First case")
    fallthrough
  case Foo(4):  // fallthrough 直接进入分支下面的语句,不执行 Foo(4)
    fmt.Println("Second case")
  }
}

/* Output:
2
1
2
First case
Second case
*/

4. defer

4.1 基本格式

defer 会将跟在它后面的函数,推迟到外层函数返回时执行:

package main

import "fmt"

func main() {
  defer fmt.Println("World")
  fmt.Println("Hello")
}

/* Output:
Hello
World
*/

无论外层函数是正常结束还是引发 panic 错误,defer 语句都会被执行:

package main

import "fmt"

func main() {
  defer fmt.Println("World")
  panic("Stop")
  fmt.Println("Hello")
}

/* Output:
World
panic: Stop

goroutine 1 [running]:
main.main()
  D:/MyCode/go-in-action/tour/test.go:7 +0xc0

Process finished with exit code 2
*/

如果调用了 os.Exit() 方法来退出程序,则不会执行 defer 语句:

package main

import (
  "fmt"
  "os"
)

func main() {
  defer fmt.Println("after os.Exit") // never be called.
  os.Exit(2)
}

/* Output:
exit status 2
*/

4.2 defer stack

推迟的函数调用会被压入一个栈中!当外层函数返回时,被推迟的函数会按照 后进先出 的顺序调用

注意:推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用

package main

import "fmt"

func main() {
  fmt.Println("Hello")
  for i := 1; i <= 3; i++ {
    defer fmt.Println(i)  // 推迟调用的函数的参数 i 会立即求值,但直到外层函数返回前该函数都不会被调用
  }
  fmt.Println("World")
}

/* Output:
Hello
World
3
2
1
*/

4.3 可修改外层函数的命名返回值

推迟调用的 匿名函数 可以访问和修改外层函数的 命名返回值

package main

import "fmt"

func foo() (result string) {
  defer func() {
    result = "Change World"  // change value at the very last moment
  }()
  return "Hello World"
}

func main() {
  fmt.Printf("Result of foo(): %q", foo())
}

/* Output:
Result of foo(): "Change World"
*/

4.4 常见用途

(1) 释放资源

defer 语句经常被用于处理成对的操作,比如打开/关闭、连接/断开连接、加锁/释放锁等。通 过 defer 机制,可以保证不论外层函数逻辑多复杂、在任何执行顺序下,打开的资源都能被释放(释放资源的 defer 语句应该直接跟在请求资源的语句后面)

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

(2) 处理 panic 错误

recover 函数仅在延迟调用的函数内部时才有效,用于捕获 panic 错误

package main

import "fmt"

func foo() int {
  defer func() {
    if err := recover(); err != nil {
      fmt.Println(err)
    }
  }()
  m := 1
  panic("foo: fail")
  m = 2
  return m
}

func main() {
  n := foo()  // Since the panic occurred before foo returned a value, n still has its initial zero value.
  fmt.Println("main received", n)
}

/* Output:
foo: fail
main received 0
*/

使用 defer 和 recover 修改外层函数的命名返回值:

package main

import (
  "errors"
  "fmt"
)

func foo() error {
  var err error

  defer func() {
    if r := recover(); r != nil {
      err = errors.New("recoverd in foo")
    }
  }()

  panic("foo: fail")

  return err
}

func bar() (err error) { // 命名返回值
  defer func() {
    if r := recover(); r != nil {
      err = errors.New("recoverd in bar()") // 可以修改外层函数的命名返回值
    }
  }()

  panic("bar: fail")

  return
}

func main() {
  fmt.Println(foo())
  fmt.Println(bar())
}

/* Output:
<nil>
recoverd in bar()
*/
未经允许不得转载: LIFE & SHARE - 王颜公子 » Go基础|第5章:流程控制语句

分享

作者

作者头像

Madman

如需 Linux / Python 相关问题付费解答,请按如下方式联系我

0 条评论

暂时还没有评论.

专题系列

文章目录