异常处理语句:处理程序运行过程中出现的异常,如try-catch语句、throw语句等。
if condition { // code block } else if condition { // code block } else { // code block }关于if语句,我主要说下面三点:
func bar() { if a := 1; false { } else if b := 2; false { } else if c := 3; false { } else { println(a, b, c) } }看完这段代码后,你觉得这段代码可以被正常编译吗?如果可以,那么它会输出什么信息呢?Go编译器告诉我们:上面这段可以正常编译并运行!但很多人会质疑:为何在第一个if语句中声明的变量a、第二个if中的变量b以及第三个if中的变量c,在最后的else语句中都可以有效访问呢?
func bar() { { // 堆代码 duidaima.com // 等价于第一个if的隐式代码块 a := 1 // 变量a作用域始于此 if false { } else { { // 等价于第一个else if的隐式代码块 b := 2 // 变量b的作用域始于此 if false { } else { { // 等价于第二个else if的隐式代码块 c := 3 // 变量c作用域始于此 if false { } else { println(a, b, c) } // 变量c的作用域终止于此 } } // 变量b的作用域终止于此 } } // 变量a作用域终止于此 } }通过这段展开后的代码,我们可以清楚地看到第一段代码中的最后的else语句实质上是一个最内层的else,变量a、b、c的作用域范围是可以覆盖到else的。
if a, ok := foo(); a < 10 && ok{ //使用if表达式自用变量 } vs. a, ok := foo() if a < 10 && ok { }这里建议采用第一种,即“使用if表达式自用变量”,而不是在if外部定义临时变量。因为前者除了简洁,可读性略好的优点外,还有一点优势,那就是将a放在if隐式代码块中,将变量a的作用域限制到最小范围,这样可以避免不同代码段中变量命名相同而引起的冲突问题。进而让代码实现更加清晰和易于理解。
// 最常规的for循环 for i := 0; i < 10; i++ { fmt.Println(i) } // 模拟while循环 i := 0 for i < 10 { fmt.Println(i) i++ } // 死循环 for { // do something }2.2 for range不是可有可无
// 遍历map for k, v := range aMap { } // 遍历string中的字符(非字节遍历) for i, r := range s { // rune }2.3 带label与不带label的continue和break
package main import "fmt" func main() { outerLoop: for i := 1; i <= 3; i++ { for j := 1; j <= 3; j++ { if i == 2 && j == 2 { // 跳出指定循环体 fmt.Println("跳出外层循环") break outerLoop } fmt.Printf("i=%d, j=%d\n", i, j) } } }在这个例子中,我们使用带label的break语句跳出了外层循环,从而避免了继续执行外层循环。如果使用不带label的break语句,仅会跳出内层循环,而不会跳出外层循环。
func main() { var m = []int{1, 2, 3, 4, 5} for i, v := range m { go func() { time.Sleep(time.Second * 3) fmt.Println(i, v) }() } time.Sleep(time.Second * 10) }你预期的输出是什么呢?实际输出是什么呢?在go playground中执行一下,得到如下结果:
4 5 4 5 4 5 4 5 4 5为什么会输出这个结果呢?我将上述代码做一个等价变换你就明白了:
func main() { var m = []int{1, 2, 3, 4, 5} { i, v := 0, 0 for i, v = range m { go func() { time.Sleep(time.Second * 3) fmt.Println(i, v) }() } } time.Sleep(time.Second * 10) }我们看到:i, v两个变量不是在每次循环时重新声明,而是在整个循环过程中只定义了一份,这就是为何所有goroutine输出的都是“4 5”的原因。Go团队针对这个问题正在设计优化方法[5],在后续的Go版本中,这个坑可能会被自然“修复”。
func main() { var a = [5]int{1, 2, 3, 4, 5} var r [5]int fmt.Println("original a =", a) for i, v := range a { if i == 0 { a[1] = 12 a[2] = 13 } r[i] = v } fmt.Println("after for range loop, r =", r) fmt.Println("after for range loop, a =", a) }在你的预期中,上面程序的输出结果是这样的:
original a = [1 2 3 4 5] after for range loop, r = [1 12 13 4 5] after for range loop, a = [1 12 13 4 5]不过实际运行一下,你会看到真正的输出是这样的:
original a = [1 2 3 4 5] after for range loop, r = [1 2 3 4 5] after for range loop, a = [1 12 13 4 5]究其原因,是因为参数range循环的是a的副本,我们用a'来表示,将上面代码等价变换为下面后,就更容易理解了:
for i, v := range a' { //a'是a的一个值拷贝 if i == 0 { a[1] = 12 a[2] = 13 } r[i] = v }这样变换后,我们知道for range遍历的是a的副本,对a的修改不会影响后续的遍历。因此,当使用数组、切片作为range后的待遍历的容器集合时,要十分小心。
func main() { var sl = []int{5, 19, 6, 3, 8, 12} var firstEven int = -1 // 堆代码 duidaima.com // find first even number of the interger slice for i := 0; i < len(sl); i++ { switch sl[i] % 2 { case 0: firstEven = sl[i] break case 1: // do nothing } } println(firstEven) }执行这个代码,输出结果为12,与我们预期的第一个偶数6不符。原因是什么呢?从输出结果为12来看,应该是break并未跳出for循环,导致循环继续进行到最后。
switch expression { case value1: // 执行代码块1 case value2: // 执行代码块2 default: // 执行默认代码块 }由于Go switch语句执行语义不会默认执行下一个case,因此上述switch语句等价于一个多个if-else的语句,但从可读性上来说,比多层的if else更易理解,可读性更好。在这样的场景下,我们是推荐使用switch替代多个if-else语句的。
package main import "fmt" func main() { num := 3 switch num { case 1, 3, 5: // case支持表达式列表 fmt.Println("奇数") case 2, 4, 6: fmt.Println("偶数") default: fmt.Println("其他") } }不会默认执行下一个case语句
package main import "fmt" func main() { num := 2 switch num { case 1: fmt.Println("第一个 case 块") case 2: fmt.Println("第二个 case 块") case 3: fmt.Println("第三个 case 块") } }这个例子只会输出“第二个 case 块”,不会执行case 3中的代码。如果要显式告知执行下一个case代码块,需要使用fallthrough。显然Go将常见执行逻辑作为默认语义,即每个case执行完跳出;而C语言恰做反了。
var x interface{} = 3 switch i := x.(type) { case nil: // x 的类型为 nil println(i) // 输出x中存储的动态类型值 case int: // x 的类型为 int case string: // x 的类型为 string default: // x 的类型为其他类型 }如果不需要接口变量中存储的动态类型值的话,也可以简化为:
var x interface{} = 3 switch x.(type) { case nil: // x 的类型为 nil case int: // x 的类型为 int case string: // x 的类型为 string default: // x 的类型为其他类型 }四. 小结