x := 0
// if x > 10 // Error: missing condition in if statement
// {
// }
if n := "abc"; x > 0 { // 初始化语句未必就是定义变量,⽐如 println("init") 也是可以的。
println(n[2])
} else if x < 0 { // 注意 else if 和 else 左⼤括号位置。
println(n[1])
} else {
println(n[0])
}
不⽀持三元操作符 "a > b ? a : b"。
FOR
⽀持三种循环⽅式,包括类 while 语法。
s := "abc"
for i, n := 0, len(s); i < n; i++ { // 常⻅的 for 循环,⽀持初始化语句。
println(s[i])
}
n := len(s)
for n > 0 { // 替代 while (n > 0) {}
println(s[n]) // 替代 for (; n > 0;) {}
n--
}
for { // 替代 while (true) {}
println(s) // 替代 for (;;) {}
}
不要期望编译器能理解你的想法,在初始化语句中计算出全部结果是个好主意。
func length(s string) int {
println("call length.")
return len(s)
}
func main() {
s := "abcd"
for i, n := 0, length(s); i < n; i++ { // 避免多次调⽤ length 函数。
println(i, s[i])
}
}
输出:
call length.
0 97
1 98
2 99
3 100
RANGE
类似迭代器操作,返回 (索引, 值) 或 (键, 值)。
| x|1st value |2nd value
string |index |s[index] |unicode, rune
array/slice| index| s[index]
map| key |m[key]
channel| element
可忽略不想要的返回值,或⽤ "_" 这个特殊变量。
s := "abc"
for i := range s { // 忽略 2nd value,⽀持 string/array/slice/map。
println(s[i])
}
for _, c := range s { // 忽略 index。
println(c)
}
for range s { // 忽略全部返回值,仅迭代。
...
}
m := map[string]int{"a": 1, "b": 2}
for k, v := range m { // 返回 (key, value)。
println(k, v)
}
注意,range 会复制对象。
a := [3]int{0, 1, 2}
for i, v := range a { // index、value 都是从复制品中取出。
if i == 0 { // 在修改前,我们先修改原数组。
a[1], a[2] = 999, 999
fmt.Println(a) // 确认修改有效,输出 [0, 999, 999]。
}
a[i] = v + 100 // 使⽤复制品中取出的 value 修改原数组。
}
fmt.Println(a) // 输出 [100, 101, 102]。
建议改⽤引⽤类型,其底层数据不会被复制。
s := []int{1, 2, 3, 4, 5}
for i, v := range s { // 复制 struct slice { pointer, len, cap }。
if i == 0 {
s = s[:3] // 对 slice 的修改,不会影响 range。
s[2] = 100 // 对底层数据的修改。
}
println(i, v)
}
//输出:
0 1
1 2
2 100
3 4
4 5
另外两种引⽤类型 map、channel 是指针包装,⽽不像 slice 是 struct。
SWITCH
分⽀表达式可以是任意类型,不限于常量。可省略 break,默认⾃动终⽌。
x := []int{1, 2, 3}
i := 2
switch i {
case x[1]:
println("a")
case 1, 3:
println("b")
default:
println("c")
}
// 输出:
a
如需要继续下⼀分⽀,可使⽤ fallthrough,但不再判断条件。
x := 10
switch x {
case 10:
println("a")
fallthrough
case 0:
println("b")
}
//输出:
a
b
省略条件表达式,可当 if...else if...else 使⽤。
switch {
case x[1] > 0:
println("a")
case x[1] < 0:
println("b")
default:
println("c")
}
switch i := x[2]; { // 带初始化语句
case i > 0:
println("a")
case i < 0:
println("b")
default:
println("c")
}
GOTO, BREAK, CONTINUE
⽀持在函数内 goto 跳转。标签名区分⼤⼩写,未使⽤标签引发错误。
func main() {
var i int
for {
println(i)
i++
if i > 2 { goto BREAK }
}
BREAK:
println("break")
EXIT: // Error: label EXIT defined and not used
}
配合标签,break 和 continue 可在多级嵌套循环中跳出。
func main() {
L1:
for x := 0; x < 3; x++ {
L2:
for y := 0; y < 5; y++ {
if y > 2 { continue L2 }
if x > 1 { break L1 }
print(x, ":", y, " ")
}
println()
}
}
//输出:
0:0 0:1 0:2
1:0 1:1 1:2
附:break 可⽤于 for、switch、select,⽽ continue 仅能⽤于 for 循环。
x := 100
switch {
case x >= 0:
if x == 0 { break }
println(x)
}
流程控制和异常处理
流程控制
IF
很特别的写法: • 可省略条件表达式括号。 • ⽀持初始化语句,可定义代码块局部变量。 • 代码块左⼤括号必须在条件表达式尾部。
不⽀持三元操作符 "a > b ? a : b"。
FOR
⽀持三种循环⽅式,包括类 while 语法。
不要期望编译器能理解你的想法,在初始化语句中计算出全部结果是个好主意。
输出: call length. 0 97 1 98 2 99 3 100
RANGE
类似迭代器操作,返回 (索引, 值) 或 (键, 值)。
可忽略不想要的返回值,或⽤ "_" 这个特殊变量。
注意,range 会复制对象。
建议改⽤引⽤类型,其底层数据不会被复制。
另外两种引⽤类型 map、channel 是指针包装,⽽不像 slice 是 struct。
SWITCH
分⽀表达式可以是任意类型,不限于常量。可省略 break,默认⾃动终⽌。
如需要继续下⼀分⽀,可使⽤ fallthrough,但不再判断条件。
省略条件表达式,可当 if...else if...else 使⽤。
GOTO, BREAK, CONTINUE
⽀持在函数内 goto 跳转。标签名区分⼤⼩写,未使⽤标签引发错误。
配合标签,break 和 continue 可在多级嵌套循环中跳出。
附:break 可⽤于 for、switch、select,⽽ continue 仅能⽤于 for 循环。
异常处理
没有结构化异常,使⽤ panic 抛出错误,recover 捕获错误。
由于 panic、recover 参数类型为 interface{},因此可抛出任何类型对象。
延迟调⽤中引发的错误,可被后续延迟调⽤捕获,但仅最后⼀个错误可被捕获。
捕获函数 recover 只有在延迟调⽤内直接调⽤才会终⽌错误,否则总是返回 nil。任何未 捕获的错误都会沿调⽤堆栈向外传递。
使⽤延迟匿名函数或下⾯这样都是有效的。
如果需要保护代码⽚段,可将代码块重构成匿名函数,如此可确保后续代码被执⾏。
除⽤ panic 引发中断性错误外,还可返回 error 类型错误对象来表⽰函数调⽤状态。
标准库 errors.New 和 fmt.Errorf 函数⽤于创建实现 error 接⼝的错误对象。通过判断 错误对象实例来确定具体错误类型。
如何区别使⽤ panic 和 error 两种⽅式?惯例是:导致关键流程出现不可修复性错误的 使⽤ panic,其他使⽤ error。