闽公网安备 35020302035485号
异常处理语句:处理程序运行过程中出现的异常,如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和breakpackage 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 的类型为其他类型
}
四. 小结