控制结构
现代计算机存储结构无论“普林斯顿结构”,还是“哈佛结构”,程序指令都是线性地存放在存储器上。程序执行从本质上来说就是两种模式:顺序和跳转。
·顺序就是按照程序指令在存储器上的存放顺序逐条执行。
跳转就是遇到跳转指令就跳转到某处继续线性执行。
Go是一门高级语言,其源程序虽然经过了高度的抽象并封装了很多语法糖,但还是跳不出这个模式(这里暂时不考虑goroutine引入并发后的执行视图变化)。
顺序在Go里面体现在从main函数开始逐条向下执行,就像我们的程序源代码顺序一样;跳转在Go里面体现为多个语法糖,包括goto语句和函数调用、分支(if、switch、select)、循环(for)等。
跳转分为两种:一种是无条件跳转,比如函数调用和goto语句;一种是有条件的跳转,比如分支和循环。
1、 if语句
- if后面的条件判断子句不需要用小括号括起来。
- {必须放在行尾,和if或if else放在一行。
- if后面可以带一个简单的初始化语句,并以分号分割,该简单语句声明的变量的作用域是整个f语句块,包括后面的else if和else分支。
- Go语言没有条件运算符(a>b?a:b),这也符合Go的设计哲学,只提供一种方法做事情。
- if分支语句遇到return后直接返回,遇到break则跳过break下方的if语句块。
简单示例:
if x <= y {
return y
} else {
return x
}
一个完整的if else语句示例:
if x := f(); x < y { // 初始化语句中的声明变量x
return x
} else if x > z { //x在else if里面一样可以被访问
return z
} else {
return y
}
最佳实践:
- 尽量减少条件语句的复杂度,如果条件语句太多、太复杂,则建议放到函数里面封装起来。
- 尽量减少if语句的嵌套层次,通过重构让代码变得扁平,便于阅读:
if err, file :== os.Open("xxxx"); err == nil {
defer file.Close()
//do something
} else {
return nil, err
}
改写后的代码:
err, file := os.Open("xxxx")
if err != nil {
return nil, err
}
defer file.Close()
//do something
2、switch语句
switch语句会根据传入的参数检测并执行复合条件的分支:
- switch和if语句一样,switch后面可以带一个可选的简单的初始化语句。
- switch后面的表达式也是可选的,如果没有表达式,则case子句是一个布尔表达式,而不是一个值,此时就相当于多重if else语句。
- switch条件表达式的值不像C语言那样必须限制为整数,可以是任意支持相等比较运算的类型变量。
- 通过fallthough语句来强制执行下一个case子句(不再判断下一个case子句的条件是否满足)。
- switch支持default语句,当所有的case分支都不符合时,执行default语句,并且default语句可以放到任意位置,并不影响switch的判断逻辑。
- switch和.(type)结合可以进行类型的查询。
switch i := "y"; i { //switch后面可以带上一个初始化语句
case "y", "Y": //多个case值使用逗号分隔
fmt.Println("yes") //yes
fallthrough //fallthrough会跳过接下来的case表达式直接执行下一个case语句
case "n", "N":
fmt.Println("no") //no
}
score := 85
grade := ' '
if score >= 90 {
grade = 'A'
} else if score >= 80 {
grade = 'B'
} else if score >= 70 {
grade = 'C'
} else if score >= 60 {
grade = 'D'
} else {
grade = 'E'
}
fmt.Printf("%c\n", grade) //B
//上面的if else可以改写为下面的switch语句
switch {
case score >= 90:
grade = 'A'
case score >= 80:
grade = 'B'
case score >= 70:
grade = 'C'
case score >= 60:
grade = 'D'
default:
grade = 'F'
}
fmt.Printf("grade=%c\n", grade)
3、for语句
Go语言仅支持一种循环语句,即for语句,同样遵循Go的设计哲学,只提供一种方法做事情,把事情做好。
Go对应C循环的三种场景如下:
- 类似C里面的for循环
for init; condition; post { }
- 类似C里面的while循环语句
for condition { }
- 类似C里面的while(1)死循环语句
for { }
for还有一种用法,是对数组、切片、字符串、map和通道的访问,语法格式如下:
//访问map
for key, value := range map { }
for key := range map { }
//访问数组
for index, value := range arry { }
for index := range arry { }
for _, value := range arry { }
//访问切片
for index, value := range slice { }
for index := range slice { }
for _, value := range slice { }
//访问通道
for value := range channel { }
4、标签和跳转
4.1 标签
Go语言使用标签(Lable)来标识一个语句的位置,用于goto、break、continue语句的跳转,标签的语法是:
Lable: Statement
4.2 goto
goto语句用于函数的内部跳转,需要配合标签一起使用,具体的格式如下:
goto Lable
- goto语句只能在函数内跳转。
- goto语句不能跳过内部变量声明语句,这些变量在goto语句的标签语句处又是可见的。
goto L //Bad,跳过v:=3这条语句是不允许的
v := 3
L:
- goto语句只能跳到同级作用域或者上层作用域内,不能跳到内部作用域内。
if n % 2 == 1 {
goto L1
}
for n > 0 {
f()
n--
L1:
f()
n--
}
4.3 break
break用于函数内部跳出for、switch、select语句的执行,有两种使用格式:
- 单独使用,用于跳出break当前所在的for、switch、select语句的执行。
- 和标签一起使用,用于跳出标签所标识的for、switch、select语句的执行,可用于跳出多重循环,但标签和break必须在同一个函数内。例如:
L1:
for i := 0; ; i++ {
for j := 0; ; j++ {
if i >= 5 {
//跳出L1标签所在的for循环
break L1
}
if j > 10 {
//默认仅跳出离break最近的内层循环
break
}
}
}
4.4 continue
continue用于跳出for循环的本次迭代,跳到for循环的下一次迭代的post语句处执行,也有两种使用格式:
- 单独使用,用于跳出continue当前所在的for循环的本次迭代。
- 和标签一起使用,用于跳出标签所标识的for语句的本次迭代,但标签和continue必须在同一个函数内。例如:
L1:
for i := 0; ; i++ {
for j := 0; ; j++ {
if i >= 5 {
//跳到L1标签所在的for循环i++处执行
continue L1
//the following is not executed
}
if j > 10 {
//默认仅跳到离break最近的内层循环j++处执行
continue
}
}
}
4.5 return和函数调用
return语句也能引发控制流程的跳转,用于函数和方法的退出。函数和方法的调用也能引发控制流的跳转。