当编写一个有一丢丢难度的程序的时候,就一定会涉及到程序结构(流向)的控制:需要一步一步顺序执行;需要根据不同情况进行选择,运行不同的代码;对相同的重复的事情,循环重复N次;两段代码同时运行,要可能要修改某一个变量值,但到底谁可以在什么时间、什么情况改需要精细控制。
在Go中,为了完成上述程序执行逻辑的控制,需要用的if, for, switch, func, select
等内容,逐个来看。
顺序结构
Go 默认会按照顺序结构执行,即按照 main()
函数的语句的顺序,一条一条执行,直到执行碰到return
正常退出程序,或遇到程序异常,异常退出程序。当程序执行到函数main()
中遇到另外一个函数时,系统就会将main 函数的执行状态保存到一个叫”栈“的数据结构中,跳出main函数,去执行函数FuncA
,在FuncA
中遇到函数 FuncB
也一样。等函数FuncB
执行完成后,会返回FuncA
,系统会从栈中找到FuncA
的信息并恢复,继续执行FuncA
,知道FuncA
返回,然后需要执行 main()
函数,直到 main()
完成,"交还"系统控制权。
栈是一种数据结构,具有”先进后出“(FILO)这样的特性,更多细节可以参考维基百科 。
选择结构
当任务遇到不同的情况需要不同处理时,就需要用到 if
语句;当然还有其一家兄弟if..else if...else
if condition{
/* true logic code */
} // condition满足就执行,否则不执行,执行下面的。
//最为常见的是错误处理,如果有错误就进行错误处理,没错误就继续
if condition{
/* true logic code */
}else {
/* false logic code */
}// 条件满足就执行true逻辑,否则执行false逻辑。
if conditionA{
/* A condition */
}else if conditionB{
/*B condition */
}else if conditionC{
/*C condition */
}else{
/* other condition*/
}// 多条件就从上往下逐个执行,如果条件都不满足,就执行else。
//还记得90-100评A,80-90评B,...吗?
if else
除了上述用法以外,还可以嵌套执行,就是如下的效果,用于复杂的条件判定。
if conditionA{
if conditonA.1 {
//A.1
}else {
//A.x(x!=1)
}
}else{
// !A
}
上述特性,其他语言一般都会提供,Go中还提供一种方式是if statement;conditon{}
其中 statement
是一个简短的声明或赋值,用于获取condition
变量的值。与statement; if conditon{}
区别是什么呢?……作用域。作用域是一种表明变量或函数等可见范围与时间的描述。刚刚if
语句中,如果statement
在if
中,则statement中变量作用范围是该 if 块中,一旦离开if,就无法访问。而 statement
在if
外,则上下文对其变量都可以看到或修改。Go 中有一个原则,与含义不相关的简单变量使用简短名称定义,并且其作用域最小,这样就可以多处重复利用这些简短名称,且不会影响程序执行。最为常见到例子就是错误状态判断,在数据类型中map一节中,我们知道查询一个key会返回两个值,一个值本身,另外一个是值的状态。一个大的程序中有很多错误情况要判断,此时都可以通过 if value, ok := mapName[key];!ok{/*do something*/}
多选择结构
除了if...else
外,还有一种选择结构叫 switch
,它通过用于很多个情况的选择,用来替代复杂的if...else if...elseif...else
。switch
一般情况下,是后面跟有一个变量,case condition 对变量取值进行判断,满足则执行,执行完当前条件,退出 switch
; 如果除 default
外的条件都不满足,那就执行defalut
语句,如果没有defalut
语句,那就直接跳过 switch
。如果一个语句可以应对多个condition,则可以使用逗号,将所有满足条件的罗列出来。
switch V{
case conditonA:
//statement
case conditonB, conditonC, conditionD:
//statemtnt
case conditionE:
// statement
default:
// statement
}
switch
还有一种特殊情况,就是没有变量 v
,那么变量可以直接在condition中表达,就更像if else
。
如果执行完第一个case,还想继续向下判断,那么可以通过 fallthrough
来继续测试后面的case条件,满足则再次执行
// 成绩score
switch {
case score < 60 && score >= 60:
fmt.Printf("不及格,D\n")
case score >= 60:
fmt.Printf("及格,")
fallthrough
case score >=70 && score < 80 :
fmt.Println("C\n")
case score >=80 && score < 90:
fmt.Println("B\n")
case score >=90 && score <= 100:
fmt.Println("A\n")
default:
fmt.Println("录错成绩了,别激动!")
}
循环结构
循环,就是重复执行某一个需要重复的工作,例如一堆数据中找最大,或者单纯的想看看这个数据集。如果循环没没有退出条件的限制,最简单后果是CPU使用率一直很高,或者内存被消耗尽,也或者你设计良好,它默默的一直干活,总之设计循环退出条件是一件聪明的事情,可以在任意情况下选择退出,尽管你可以把退出条件设置的比较隐蔽。
Go 循环只有一种叫做for
, 没有其他。for
结构:
for init;condition;post{
/* do something */
}
其中 init
为变量初始化条件,如果if
一样,在for
结构声明的变量,只能在该块中调用,初始化条件只执行一次;在初始化完成后,for
开始判断condition
是否满足,如果条件满足,则执行循环块;执行完循环后执行post
语句。然后继续检查条件是否满足,重复下去,直到条件不满足,退出循环条件。如果初始化条件声明在for
之外,初始化条件可以跳过;如果post
语句在循环中执行,那么post
也可以省略;如果看到下面的循环中断关键字 break,可以用来手动判断是否退出,那么condition
也可以省略。
中断循环的关键字 break
,当程序执行到循环中的break 语句时,循环退出
跳出当前循环的关键字 continue
, 当程序执行到循环中的 continue 时,循环剩下的语句将被跳过,执行post,开始下一次循环,计算循环条件。
package main
import "fmt"
func main() {
for i := 1; i <= 5; i++ {
for j := 1; j <= i; j++ {
if i == 4 && j == 2 {
fmt.Printf("\t\t") //当碰到i=4,j=2的时候,则打印两个制表符
continue // 跳出本地循环,即不打印下面 2x4=8这一项
}
fmt.Printf("%d x %d = %d\t", j, i, i*j)
}
fmt.Println()
}
}
/* another
内部for循环还可以写为
--------------version 1-------------------
for i := 1; i <= 5; i++ {
j := 1
for ; j <= i; j++ { // 虽然省略了init,但是分号不可省,注意
if i == 4 && j == 2 {
fmt.Printf("\t\t")
continue
}
fmt.Printf("%d x %d = %d\t", j, i, i*j)
}
fmt.Println()
}
---------------version 2 -------------------
for i := 1; i <= 5; i++ {
j := 1
for j <= i {// 支持只有条件的for
if i == 4 && j == 2 {
fmt.Printf("\t\t")
continue
}
fmt.Printf("%d x %d = %d\t", j, i, i*j)
j++ // 还是要更新条件变量,否则真的时一直循环,直到死机或被系统强制结束
}
fmt.Println()
}
--------------version 3 ------------------------
for i := 1; i <= 5; i++ {
j := 1
for { // 如果没有条件就一直循环,直到
if j > i {
break // 直到碰到break,break 可以通过if设置,如果没有if,那就没有循环
}
if i == 4 && j == 2 {
fmt.Printf("\t\t")
continue
}
fmt.Printf("%d x %d = %d\t", j, i, i*j)
j++
}
fmt.Println()
}
/* Result
1 x 1 = 1
1 x 2 = 2 2 x 2 = 4
1 x 3 = 3 2 x 3 = 6 3 x 3 = 9
1 x 4 = 4 3 x 4 = 12 4 x 4 = 16
1 x 5 = 5 2 x 5 = 10 3 x 5 = 15 4 x 5 = 20 5 x 5 = 25
*/
需要注意的是,break只能退出当前循环,对于如上面嵌套循环,如果全部退出循环,则需要与嵌套循环数量完全一致的break语句。
这是一般程序结构控制,对于并发或者说多线程程序控制,更加复杂且难以预测。好消息是,Go 有原生并发支持,但不会再这一节展开。