if-else
- 条件表达式值必须是布尔类型
- 可省略括号(推荐省略)
- 左花括号不能另起一行
- 比较特别的是对初始化语句的支持
switch
a, b, x:= 1, 2, 2
switch x {
case a, b: // 多个匹配条件命中其一即可(OR),变量
// ...
}
编译器对 if、switch 生成的机器指令可能完全相同,所谓谁性能更好须看具体情况,不能作为主观判断条件。
switch 同样支持初始化语句。
不能出现重复的 case 常量值:error: duplicate case ? in switch
无须显式执行 break 语句,case 执行完毕后自动中断。如须贯通后续 case(源码顺序),须执行 fallthrough,但不再匹配后续条件表达式。
switch x := 5; x {
default: // 编译器确保不会先执行 default 块
println(x)
case 5:
x += 10
println(x)
fallthrough // 继续执行下一case,但不再匹配条件表达式
case 6:
x += 20
println(x)
// fallthrough
// 如果在此继续fallthrough,不会执行default,完全按源码顺序
// 导致 "cannot fallthrough final case in switch" 错误
}
注意,fallthrough 必须放在 case 块结尾,可使用 break 语句阻止。
switch x:=5; x {
case 5:
x += 10
println(x)
if x >= 15{
break // 终止,不再执行后续语句
}
fallthrough // 必须是case块的最后一条语句
case 6:
x += 20
println(x)
}
某些时候,switch 还被用来替换 if 语句。被省略的 switch 条件表达式默认值为 true,继而与 case 比较表达式结果匹配。
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 语句也可用于接口类型匹配。
func bar(a interface{}) {
switch a.(type) {
case int:
// ...
case string:
// ...
}
}
for
条件表达式中如有函数调用,须确认是否会重复执行。可能会被编译器优化掉,也可能是动态结果须每次执行确认。
func count() int {
print("count.")
return 3
}
func main() {
for i, c := 0, count(); i < c; i++ { // 初始化语句的count函数仅执行一次
println("a", i)
}
c := 0
for c < count() { // 条件表达式中的count重复执行
println("b", c)
c++
}
}
for-range
支持字符串、数组、数组指针、切片、字典、通道类型,返回索引、键值数据。
date type | 1st value | 2nd | notes |
---|---|---|---|
string | index | s[index] | unicode, rune |
array/slice | index | v[index] | |
map | key | value | |
channel | element |
没有相关接口实现自定义类型迭代,除非基础类型是上述类型之一。
无论普通 for 循环,还是 range 迭代,其定义的局部变量都会重复使用,这对闭包存在一些影响。
注意,range 会复制目标数据。受直接影响的是数组,可改用数组指针或切片类型。
data := [3]int{ 10, 20, 30 }
for i, x := range data { // 从data复制品中取值
if i==0 {
data[0] += 100
data[1] += 200
data[2] += 300
}
fmt.Printf("x: %d, data: %d\n", x, data[i])
}
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 // range返回的依旧是复制值
x:30, data:330
x:110, data:210 // 当i==0修改data时,x已经取值,所以是110
x:420, data:420 // 复制的仅是slice自身,底层array依旧是原对象
x:630, data:630
相关数据类型中,字符串、切片基本结构是个很小的结构体,而字典、通道本身是指针封装,复制成本都很小,无须专门优化。
如果 range 目标表达式是函数调用,也仅被执行一次。
goto,continue,break
对于 goto 的讨伐由来已久,仿佛它是“笨蛋”标签一般。可事实上,能在很多场合见到它的身影,就连 Go 源码里都有很多。
单就 Go 1.6的源码统计结果,goto 语句就超出1000条有余。很惊讶,不是吗?虽然某些设计模式可用来消除 goto 语句,但在性能优先的场合,它能发挥积极作用。
- 使用 goto 前,须先定义标签
- 标签区分大小写
- 未使用的标签会引发编译错误
- 不能跳转到其他函数,或内层代码块内
func test() {
test:
println("test")
println("test exit.")
}
func main() {
// bar: // 错误:label bar defined and not used
for i := 0; i < 3; i++ {
loop:
println(i)
}
goto test // 错误:label test not defined
goto loop // 错误:goto loop jumps into block
}
break
:用于switch
、for
、select
语句,终止整个语句块执行。continue
:仅用于for
循环,终止后续逻辑,立即进入下一轮循环。
配合标签,break
和 continue
可在多层嵌套中指定目标层级。