1.概述
代码的执行顺序,有 3 大类,为:
- 顺序,从上到下依次执行,基础流程(核心流程)
- 循环,某段代码重复执行。
- 分支,选择性执行某段代码。
2.流程图回顾
使用如下的典型图例,描述流程节点:
-
椭圆,表示开始和结束:
-
矩形,表示某个具体的进程步骤:
-
菱形,表示条件判断:
3. 分支if
1) 语法
- 语法 1,只有 if 语句
// 语法 1,条件成立,执行语句块内容
if condition {
// 语句块
}
- 语法 2,if + else
// 语法 2,条件成立,执行 if 语句块,否则执行 else 语句块
if condition {
// if 语句块
} else {
// else 语句块
}
- 语法 3,else if
// 语法 3,条件 1 成立,执行语句块 1 然后结束,若条件 1 不成立,继 续判断条件 2,若成立执行语句块 2 然后结束。直到全部条件判断完毕。
condition1, condition2, conditionN := true, true, true
if condition1 {
/ / 语句块 1
} else if condition2 {
// 语句块 2
} else if conditionN {
// 语句块 N
}
- 语法 4,else if else
// 语法 4,与语法 3 一致,多一个 else 语句块,当全部条件不满足时,执行 else 语句块
if condition1 {
// 语句块 1
} else if condition2 {
// 语句块 2
} else if conditionN {
// 语句块 N
} else {
// else 语句块
}
- 条件表达式为布尔表达式
条件表达式一定是可以返回布尔值的表达式。不能直接判断 0,”” 这种非布尔类型数
据,没有自动向布尔值转换的过程,下面的语法是不对的:
if 0 {
// non-bool 0 (type int) used as if condition
fmt.Println("if statement block")
}
- 支持条件初始化语句
在 if 后,支持先完成条件初始化,再去完成条件表达式的语法,结构为:
if 条件初始化语句;条件表达式 {
}
if condition := 10>8; condition {
fmt.Println("使用条件初始化语句")
fmt.Println("条件成立")
}
注意:以上结构中,条件的是否成立,仅仅与条件表达式相关,与条件初始化语句无关。
if c:=10<8;true{
fmt.Println(c)
fmt.Println("使用条件初始化语句")
fmt.Println("条件成立")
}
// false
// 使用条件初始化语句
// 条件成立
注意:使用条件初始化的意思,主要是为了将判断条件变量的作用域(有效区域)限定在 if 语句 范围内(缩小变量有效作用域策略), condition2,在 if 外无效。
if condition2 := 10>8; condition2 {
fmt.Println("使用条件初始化语句")
fmt.Println("条件成立")
fmt.Println(condition2)
}
fmt.Println(condition2) //undefined: condition2
4.分支 switch
1) if 和 switch 的差异?
if,条件分支,利用条件表达式确定条件是否成立。
switch,状态分支,利用状态值确定分支条件。
代码演示:
// score >= 80 就是条件
score := 98
if score >= 90 {
fmt.Println("A")
} else if score >= 80 {
fmt.Println("B")
} else if score >=60 {
fmt.Println("C")
} else {
fmt.Println("呵呵")
}
// int,string,bool,就是特定的状态!
switch d := 10; d {
case 9 :
fmt.Println(9)
case 10 :
fmt.Println(10)
case 11 :
fmt.Println(11)
default:
fmt.Println("default")
}
2) 语法,switch case default
value := 4
switch value {
default:
fmt.Println("six")
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
//...
case 3, 4, 5: // value == 3 || value == 4 || value == 5
fmt.Println("three, four, five") }
执行流程是,判断 switch 表达式的值与 case 给定的状态是否相等,相等则执行对应的语句 块,然后 switch 结束。
注意,多个状态值,可以在一个 case 中描述。
注意,若没有任何 case 匹配,则会执行 default 分支。前提是存在 default 分支。(default 分
支是可选的)。default 的位置无关。
在 switch 表达式中,同样支持初始化表达式。 支持:
switch 初始化表达式; switch 表达式 {
}
目的是将条件分支值变量的有效作用域控制在 switch 语句的范围内,避免全局污染!
代码演示:
switch value := 4; value {
default:
fmt.Println("six")
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
//...
case 3, 4, 5: // value == 3 || value == 4 || value == 5
fmt.Println("three, four, five")
}
fmt.Println(value) //undefined: value
格外注意的是,switch 的 switch 表达式是也可以省略的。(if 的条件表达式是不能省略的。) 省略后,case 应该使用条件语句完成分支判断,不能再使用状态值了,因为没有值可以用
于判定了。
switch value := 4; {
default:
fmt.Println(value)
fmt.Println("six")
case value == 1:
fmt.Println("one")
case value == 2:
fmt.Println("two")
case value == 3 || value == 4 || value == 5:
fmt.Println("three, four, five")
}
3) fallthrough,穿越到下个 case 块
go 的 switch case 在满足了一个 case 后,执行对应的语句块,之后 swtich 直接结束。
与很多其他语言不通的是,很多其他语句的 swtich 当 case 满足后会依次执行余下的 全部语句块。
为了在语法上与其他语言类似,go 提供了 fallthrough 语句,用于在 case 语句块间穿越。 当 case 语句块的最后的语句为 fallthrought 时,会执行下一个 case 语句块:
switch value := 2; value {
default:
fmt.Println("six")
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
fallthrough //
//...
case 3, 4, 5: // value == 3 || value == 4 || value == 5
fmt.Println("three, four, five")
//fallthrough // 最后一个语句块中不能存在 fallthrough
}
// 输出 two
// three, four, five
注意:
fallthrough 只能是语句块的最后一条语句。
而且最后一个语句块中不能存在 fallthrough。
若执行的语句块不存在 fallthrough,会停止穿越!
4) switch interface.(type) 结构,type switch
该结构可以完成类型检测,执行对应的代码。
// 类型检测
var x interface{}
//x = 42
x = "hank"
//x = false
//
switch d := x.(type) {
case int:
fmt.Println(d, d+42)
case string:
fmt.Println(d, d + " GoLang")
//fallthrough //cannot fallthrough in type switch
case bool:
fmt.Println(d, !d)
}
该结构也是 swtich 的语法。
特殊性在于: 不支持 falltrhrough。
5. 循环 for
1) 概述
基于循环条件,重复执行代码块的结构。 流程图上,通常是一个闭合的环。
语法上使用 for 完成循环结构。(go 语言中没有 while 结构循环)
2) 基本语法
for 条件初始化语句;条件表达式;条件更新语句{
//循环体
}
for i:=0; i<10; i++ {
fmt.Println(i)
}
for 存在三个循环条件部分,初始化,条件判断,条件变化。
若 循环体执行了 N 次,那么:
- 条件初始化执行 1 次。
- 条件判断执行 N+1 次。
- 条件变化执行 N 次。
可见,应该将条件数据放在初始化中完成,避免重复执行,例如,当使用 i 作为索引, 遍历数组或切片的时候,语法为:
arr := [...]int{42, 1024, 365, 2048, 1, 36, 88, 66}
for i, l := 0, len(arr); i
3) 条件循环语法(其他语言的 while 语法)
for 条件表达式 {
}
result, n := 0, 0
for result <= 100 {
n ++
result += n
}
fmt.Println(result)
条件的数据依赖于外部数据完成。
通常循环体内部,要完成循环条件的更新。
4) 无限循环语法
for {
}
i := 0
for { // for true {
fmt.Println(i) i ++
if i >= 10 {
break // 强制 for 终止
}
}
不需要 循环条件,表示条件永远为 true。 意味着,循环体会无限次数的循环执行下去。
通常,需要配合循环体内部的条件变化以及在循环体内部强制终止来实现。上面的 break
结构。
该结构通常用于实现守护进程。需要一直运行的程序。
6. 循环遍历,for range
适用于遍历:array,slice,map 复合结构。
语法一致:
for i, v := range data {
}
for i := range data {
}
for _, v := range data {
}
7. 注意,语句块的左大括号,与语句标签在一行
for ;; {
if ; {
swtich ; {
for := range {
\\ 不能是:
for ;;
{
if ;
{
switch ;
{
// 包括函数的大括号,也需要与函数声明在一行。
func funcName() {
8. goto label
label: 语句标签。类似于路标,用于标识代码位置。
使用 goto 语句可以跳转到特定的 label 位置。
演示,例如在处理错误时:
// 错误处理
// 可能的错误有很多种,逐一检测。
// 一旦检测到任意的错误,则立即处理错误,不再检测后续的错误,逻辑如
下:
if error1 {
goto errorLabel
}
if error2 {
goto errorLabel
}
if error3 { //标签跳转
goto errorLabel
}
errorLabel: // 标签定义
fmt.Println("error processing")
上面的语法中,涉及到了:
- 定义标签: 为标签设置特定的名字,满足标识符定义即可。
- 使用标签: 完成。 跳转标签:使用 goto 完成跳转。
注意: 跳转 goto,不是任意跳转。跳转的目标不是任何位置都可达到,例如: 不能跨函数跳转。
不能跳转到内部的语句块中。
总结下来:标识符必须可见,才可以 goto 成功。
标识符可见的语法: 语句块嵌套的可见性。内层可以看见外层,但是外层看不到内层。
不同的语句块也不可以跳转。
func main() {
goto labelName
// 内层可以看见外层,但是外层看不到内层。
for i:=0;i<10;i++ {
labelName: // XXX 不可
fmt.Println()
}
}
// 不同的语句块也不可以跳转。
func F() {
labelName: // XXX 不可
fmt.Println()
}
9. break,强行终止循环执行
break 用于循环内部,当执行到 break 时,会终止 break 所在的循环的执行。
代码演示
for i:=0; i<10; i++ {
if i == 6 {
break
}
fmt.Println(i)
}
以上结果,6 及后续数字没有输出。执行到了 break,会立即终止。
10.continue,强行终止本次循环体执行,循环继续
continue,仅仅终止本次循环体执行。但是 for 的整体循环并没有结束,会继续修改循
环条件,执行下一次循环体。
代码演示:
for i:=0; i<10; i++ {
if i == 6 {
continue
}
fmt.Println(i)
}
以上的结果,没有 6 的输出。continue 之后的代码,不会继续执行,而是指向下一次 i== 7 的循环体。
11.break 或 continue 多重循环
当循环语句出现嵌套时,若需要在内部循环体中,直接终止外部的循环执行,则需要配
合 标签 语法完成终止。 演示:
outerLabel:
for i:=0; i<3; i++ {
for j:=0; j<3; j++ {
if i + j > 2{
break outerLabel
}
fmt.Println(i, j)
}
}
此时,break outerLabel, 会导致 outerLabel 对应的 for 全部终止! 否则,break,仅仅会终止 所在的 for j 的执行,而不会终止 for i 的执行。
12.switch 中的 break 一定要使用标签
for 中使用 switch 结构判断执行 break,需要使用标签语法。
代码演示:
forLabel:
for i:=0; i<10; i++ {
switch i {
case 6:
break forLabel
}
// if i == 6 {
// break
// }
fmt.Println(i)
}
若不使用标签语法,switch 中的 break 不会终止循环。
原因,switch 中的 break,是终止 switch 的作用。在实现 switch 时,被视为循环结构。