Go的控制结构与C的相关,但是有重要的区别。没有do
或者while
循环,只有一个稍微广义的for
;switch
更加灵活;if
和switch
接受一个像for
那样可选的初始化语句;break
和continue
语句接受一个可选的标号来指定中断或继续什么;还有一些新的控制结构,包括类型switch和多路通信复用器(multiway communications multiplexer),select
。语句也稍微有些不同:没有圆括号,并且控制结构体必须总是由大括号包裹。
Go中,简单的if
看起来是这样的:
if x > 0 {
return y
}
强制的大括号可以鼓励大家在多行中编写简单的if
语句。不管怎样,这是一个好的风格,特别是当控制结构体包含了一条控制语句,例如return
或者break
。
既然if
和switch
接受一个初始化语句,那么常见的方式是用来建立一个局部变量。
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
在Go的库中,你会发现当if
语句不会流向下一条语句时—也就是说,控制结构体结束于break
,continue
,goto
或者return
—则不必要的else
会被省略掉。
f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)
这个例子是一种常见的情况,代码必须防范一系列的错误条件。如果成功的控制流是沿着页面往下走,来消除它们引起的错误情况,那么代码会非常易读。由于错误情况往往会结束于return
语句,因此代码不需要有else
语句。
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
另外:上一章节的最后一个例子,展示了:=
短声明形式的工作细节。该声明调用了os.Open
进行读取,
f, err := os.Open(name)
该语句声明了两个变量,f
和err
。几行之后,又调用了f.Stat
进行读取,
d, err := f.Stat()
这看起来像是又声明了d
和err
。但是,注意err
在两条语句中都出现了。这种重复是合法的:err
是在第一条语句中被声明,而在第二条语句中只是被重新赋值。这意味着使用之前已经声明过的err
变量调用f.Stat
,只会是赋给其一个新的值。
在:=
声明中,变量v
即使已经被声明过,也可以出现,前提是:
v
已有的声明在相同的作用域中(如果v
已经在外面的作用域里被声明了,则该声明将会创建一个新的变量 §)v
的这种不寻常的属性纯粹是从实用主义方面来考虑的。例如,这会使得在一个长的if-else
链中,很容易地使用单个err
值。你会经常看到这种用法。
§ 值得一提的是,在Go中,函数参数和返回值的作用域与函数体的作用域是相同的,虽然它们在词法上是出现在包裹函数体的大括号外面。
Go的for
循环类似于—但又不等同于—C的。它统一了for
和while
,并且没有do-while
。有三种形式,其中只有一个具有分号。
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
短声明使得在循环中很容易正确的声明索引变量。
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
如果你是在数组,切片,字符串或者map上进行循环,或者从channel中进行读取,则可以使用range
子句来管理循环。
for key, value := range oldMap {
newMap[key] = value
}
如果你只需要range中的第一项(key或者index),则可以丢弃第二个:
for key := range m {
if key.expired() {
delete(m, key)
}
}
如果你只需要range中的第二项(value),则可以使用空白标识符,一个下划线,来丢弃第一个:
sum := 0
for _, value := range array {
sum += value
}
空白标识符有许多用途,这在后面的章节中会有介绍。
对于字符串,range
会做更多的事情,通过解析UTF-8来拆分出单个的Unicode编码点。错误的编码会消耗一个字节,产生一个替代的符文(rune)U+FFFD。(名字(与内建类型相关联的)rune
是Go的术语,用于指定一个单独的Unicode编码点。详情参见the language specification)循环
for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
会打印出
character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7
最后,Go没有逗号操作符,并且++
和--
是语句而不是表达式。因此,如果你想在for
中运行多个变量,你需要使用并行赋值(尽管这样会阻碍使用++
和--
)。
// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
Go的switch
要比C的更加通用。表达式不需要为常量,甚至不需要为整数,case是按照从上到下的顺序进行求值,直到找到匹配的。如果switch
没有表达式,则对true
进行匹配。因此,可以—按照语言习惯—将if
-else
-if
-else
链写成一个switch
。
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
switch不会自动从一个case子句跌落到下一个case子句。但是case可以使用逗号分隔的列表。
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
虽然和其它类C的语言一样,使用break
语句来提前中止switch
在Go中几乎不怎么常见。不过,有时候是需要中断包含它的循环,而不是switch。在Go中,可以通过在循环上加一个标号,然后“breaking”到那个标号来达到目的。该例子展示了这些用法。
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]<<shift)
}
}
当然,continue
语句也可以接受一个可选的标号,但是只能用于循环。
作为这个章节的结束,这里有一个对字节切片进行比较的程序,使用了两个switch
语句:
// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
}
return 0
}
switch还可用于获得一个接口变量的动态类型。这种类型switch使用类型断言的语法,在括号中使用关键字type
。如果switch 在表达式中声明了一个变量,则变量会在每个子句中具有对应的类型。比较符合语言习惯的方式是在这些case里重用一个名字,实际上是在每个case里声名一个新的变量,其具有相同的名字,但是不同的类型。
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}