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 type1st value2ndnotes
stringindexs[index]unicode, rune
array/sliceindexv[index]
mapkeyvalue
channelelement

没有相关接口实现自定义类型迭代,除非基础类型是上述类型之一。

无论普通 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:用于 switchforselect 语句,终止整个语句块执行。
  • continue:仅用于 for 循环,终止后续逻辑,立即进入下一轮循环。

配合标签,breakcontinue 可在多层嵌套中指定目标层级。