加载中...

控制结构


Go的控制结构与C的相关,但是有重要的区别。没有do或者while循环,只有一个稍微广义的forswitch更加灵活;ifswitch接受一个像for那样可选的初始化语句;breakcontinue语句接受一个可选的标号来指定中断或继续什么;还有一些新的控制结构,包括类型switch和多路通信复用器(multiway communications multiplexer),select。语句也稍微有些不同:没有圆括号,并且控制结构体必须总是由大括号包裹。

If

Go中,简单的if看起来是这样的:

  1. if x > 0 {
  2. return y
  3. }

强制的大括号可以鼓励大家在多行中编写简单的if语句。不管怎样,这是一个好的风格,特别是当控制结构体包含了一条控制语句,例如return或者break

既然ifswitch接受一个初始化语句,那么常见的方式是用来建立一个局部变量。

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

在Go的库中,你会发现当if语句不会流向下一条语句时—也就是说,控制结构体结束于breakcontinuegoto或者return—则不必要的else会被省略掉。

  1. f, err := os.Open(name)
  2. if err != nil {
  3. return err
  4. }
  5. codeUsing(f)

这个例子是一种常见的情况,代码必须防范一系列的错误条件。如果成功的控制流是沿着页面往下走,来消除它们引起的错误情况,那么代码会非常易读。由于错误情况往往会结束于return语句,因此代码不需要有else语句。

  1. f, err := os.Open(name)
  2. if err != nil {
  3. return err
  4. }
  5. d, err := f.Stat()
  6. if err != nil {
  7. f.Close()
  8. return err
  9. }
  10. codeUsing(f, d)

重新声明和重新赋值

另外:上一章节的最后一个例子,展示了:=短声明形式的工作细节。该声明调用了os.Open进行读取,

  1. f, err := os.Open(name)

该语句声明了两个变量,ferr。几行之后,又调用了f.Stat进行读取,

  1. d, err := f.Stat()

这看起来像是又声明了derr。但是,注意err在两条语句中都出现了。这种重复是合法的:err是在第一条语句中被声明,而在第二条语句中只是被重新赋值。这意味着使用之前已经声明过的err变量调用f.Stat,只会是赋给其一个新的值。

:=声明中,变量v即使已经被声明过,也可以出现,前提是:

  • 该声明和v已有的声明在相同的作用域中(如果v已经在外面的作用域里被声明了,则该声明将会创建一个新的变量 §)
  • 初始化中相应的值是可以被赋给v
  • 并且,声明中至少有其它一个变量将被声明为一个新的变量

这种不寻常的属性纯粹是从实用主义方面来考虑的。例如,这会使得在一个长的if-else链中,很容易地使用单个err值。你会经常看到这种用法。

§ 值得一提的是,在Go中,函数参数和返回值的作用域与函数体的作用域是相同的,虽然它们在词法上是出现在包裹函数体的大括号外面。

For

Go的for循环类似于—但又不等同于—C的。它统一了forwhile,并且没有do-while。有三种形式,其中只有一个具有分号。

  1. // Like a C for
  2. for init; condition; post { }
  3. // Like a C while
  4. for condition { }
  5. // Like a C for(;;)
  6. for { }

短声明使得在循环中很容易正确的声明索引变量。

  1. sum := 0
  2. for i := 0; i < 10; i++ {
  3. sum += i
  4. }

如果你是在数组,切片,字符串或者map上进行循环,或者从channel中进行读取,则可以使用range子句来管理循环。

  1. for key, value := range oldMap {
  2. newMap[key] = value
  3. }

如果你只需要range中的第一项(key或者index),则可以丢弃第二个:

  1. for key := range m {
  2. if key.expired() {
  3. delete(m, key)
  4. }
  5. }

如果你只需要range中的第二项(value),则可以使用空白标识符,一个下划线,来丢弃第一个:

  1. sum := 0
  2. for _, value := range array {
  3. sum += value
  4. }

空白标识符有许多用途,这在后面的章节中会有介绍。

对于字符串,range会做更多的事情,通过解析UTF-8来拆分出单个的Unicode编码点。错误的编码会消耗一个字节,产生一个替代的符文(rune)U+FFFD。(名字(与内建类型相关联的)rune是Go的术语,用于指定一个单独的Unicode编码点。详情参见the language specification)循环

  1. for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
  2. fmt.Printf("character %#U starts at byte position %d\n", char, pos)
  3. }

会打印出

  1. character U+65E5 '日' starts at byte position 0
  2. character U+672C '本' starts at byte position 3
  3. character U+FFFD '�' starts at byte position 6
  4. character U+8A9E '語' starts at byte position 7

最后,Go没有逗号操作符,并且++--是语句而不是表达式。因此,如果你想在for中运行多个变量,你需要使用并行赋值(尽管这样会阻碍使用++--)。

  1. // Reverse a
  2. for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
  3. a[i], a[j] = a[j], a[i]
  4. }

Switch

Go的switch要比C的更加通用。表达式不需要为常量,甚至不需要为整数,case是按照从上到下的顺序进行求值,直到找到匹配的。如果switch没有表达式,则对true进行匹配。因此,可以—按照语言习惯—将if-else-if-else链写成一个switch

  1. func unhex(c byte) byte {
  2. switch {
  3. case '0' <= c && c <= '9':
  4. return c - '0'
  5. case 'a' <= c && c <= 'f':
  6. return c - 'a' + 10
  7. case 'A' <= c && c <= 'F':
  8. return c - 'A' + 10
  9. }
  10. return 0
  11. }

switch不会自动从一个case子句跌落到下一个case子句。但是case可以使用逗号分隔的列表。

  1. func shouldEscape(c byte) bool {
  2. switch c {
  3. case ' ', '?', '&', '=', '#', '+', '%':
  4. return true
  5. }
  6. return false
  7. }

虽然和其它类C的语言一样,使用break语句来提前中止switch在Go中几乎不怎么常见。不过,有时候是需要中断包含它的循环,而不是switch。在Go中,可以通过在循环上加一个标号,然后“breaking”到那个标号来达到目的。该例子展示了这些用法。

  1. Loop:
  2. for n := 0; n < len(src); n += size {
  3. switch {
  4. case src[n] < sizeOne:
  5. if validateOnly {
  6. break
  7. }
  8. size = 1
  9. update(src[n])
  10. case src[n] < sizeTwo:
  11. if n+1 >= len(src) {
  12. err = errShortInput
  13. break Loop
  14. }
  15. if validateOnly {
  16. break
  17. }
  18. size = 2
  19. update(src[n] + src[n+1]<<shift)
  20. }
  21. }

当然,continue语句也可以接受一个可选的标号,但是只能用于循环。

作为这个章节的结束,这里有一个对字节切片进行比较的程序,使用了两个switch语句:

  1. // Compare returns an integer comparing the two byte slices,
  2. // lexicographically.
  3. // The result will be 0 if a == b, -1 if a < b, and +1 if a > b
  4. func Compare(a, b []byte) int {
  5. for i := 0; i < len(a) && i < len(b); i++ {
  6. switch {
  7. case a[i] > b[i]:
  8. return 1
  9. case a[i] < b[i]:
  10. return -1
  11. }
  12. }
  13. switch {
  14. case len(a) > len(b):
  15. return 1
  16. case len(a) < len(b):
  17. return -1
  18. }
  19. return 0
  20. }

类型switch

switch还可用于获得一个接口变量的动态类型。这种类型switch使用类型断言的语法,在括号中使用关键字type。如果switch 在表达式中声明了一个变量,则变量会在每个子句中具有对应的类型。比较符合语言习惯的方式是在这些case里重用一个名字,实际上是在每个case里声名一个新的变量,其具有相同的名字,但是不同的类型。

  1. var t interface{}
  2. t = functionOfSomeType()
  3. switch t := t.(type) {
  4. default:
  5. fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
  6. case bool:
  7. fmt.Printf("boolean %t\n", t) // t has type bool
  8. case int:
  9. fmt.Printf("integer %d\n", t) // t has type int
  10. case *bool:
  11. fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
  12. case *int:
  13. fmt.Printf("pointer to integer %d\n", *t) // t has type *int
  14. }

还没有评论.