第三章 流程控制
3.1 If语句
语法格式:
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
}
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}
if 布尔表达式1 {
/* 在布尔表达式1为 true 时执行 */
} else if 布尔表达式2{
/* 在布尔表达式1为 false ,布尔表达式2为true时执行 */
} else{
/* 在上面两个布尔表达式都为false时,执行*/
}
示例代码:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 10
/* 使用 if 语句判断布尔表达式 */
if a < 20 {
/* 如果条件为 true 则执行以下语句 */
fmt.Printf("a 小于 20\n" )
}
fmt.Printf("a 的值为 : %d\n", a)
}
如果其中包含一个可选的语句组件(在评估条件之前执行),则还有一个变体。它的语法是
if statement; condition {
}
示例代码:
package main
import (
"fmt"
)
func main() {
if num := 10; num % 2 == 0 { //checks if number is even
fmt.Println(num,"is even")
} else {
fmt.Println(num,"is odd")
}
}
需要注意的是,num的定义在if里,那么只能够在该if..else语句块中使用,否则编译器会报错的。
3.2 switch
与if类似,switch语句也用于选择执行,但具体的使用场景会有所不同。
package main
func main() {
a,b,c,x:= 1,2,3,2
switch x {
case a,b:
println("a | b")
case c:
println("c")
case 4:
println("d")
default:
println("z")
}
}
输出:
a | b
条件表达式支持非常量值,这要比C更加灵活。相比if表达式,switch值列表要更加简洁。
编译器对if、switch生成的机器码可能完全相同,谁性能更好要看具体情况。
switch 同样支持初始化语句。只有全部匹配失败时,才会执行default。
func main(){
switch x:=5;x {
default: //default虽然放在最上边了,但编译器不会先执行它。
x+=100
println(x)
case 5:
x+=50
println(x)
}
}
输出:55
但一般建议把default放在switch末尾。
相邻的空case不构成多条件匹配,例如:
switch x {
case a : //单条件,内容为空。隐式“case a: break;”
不能出现重复的case常量值。
无须显式执行break语句,case执行完毕后自动中断。如须贯通后续case(源码顺序),须执行fallthrough,但不再匹配后续条件表达式。
func main(){
switch x:=5;x {
default:
println(x)
case 5:
x+=10
println(x)
if x >=25{
break //终止,不再执行后续语句
}
fallthrough //必须是case块的最后一条语句
case 6:
x+=20
println(x)
}
}
输出:15 35
某些时候,switch还被用来替代if语句。被省略的switch条件表达式默认值为true,继而与case比较表达式结果匹配。
func main(){
switch x:=5; { //相当于“switch x:=5;true {...}”
case x>5:
println("a")
case x>0 && x<=5: //不能写成case x>0, x<=5,因为多条件表达式是or的关系
println("b")
default:
println("z")
}
}
switch语句也可用于接口类型匹配,详见后续章节。
Type Switch
switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。
switch x.(type){
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}
package main
import "fmt"
func main() {
var x interface{}
switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型" )
default:
fmt.Printf("未知型")
}
}
结果
x 的类型 :<nil>
3.3 select 语句
select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
package main
import "fmt"
func main() {
var c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1\n")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
fmt.Printf("received ", i3, " from c3\n")
} else {
fmt.Printf("c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
}
运行结果:
no communication
-
每个case都必须是一个通信
-
所有channel表达式都会被求值
-
所有被发送的表达式都会被求值
-
如果任意某个通信可以进行,它就执行;其他被忽略。
-
如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
-
否则:
如果有default子句,则执行该语句。
如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
3.4 for循环
go中只有for一种循环语句,没有while,但常用的方式都支持。
func main() {
for i:=0; i<3; i++{ //初始化表达式支持函数调用或定义局部变量
}
for x<5{
x++
}
for { //类似while true
break
}
}
初始化语句仅被执行一次。 条件表达式中如有函数调用,须确认是否会重复执行。可能会被编译器优化掉,也可能是动态结果须每次执行确认。
func count()int{
println("count.")
return 3
}
func main() {
for i,c:=0,count();i<c;i++{ //初始化语句中的count函数仅执行一次
println("a",i)
}
d:=0
for d<count(){ //条件表达式中的count重复执行
println("b",d)
d++
}
}
输出结果:
count.
a 0
a 1
a 2
count.
b 0
count.
b 1
count.
b 2
count.
规避方式就是在初始化表达式中定义局部变量保存count结果。
可用for ...range完成数据迭代,支持字符串、数组、数组指针、切片、字典、通道类型,返回索引、键值数据。
没有相关接口实现自定义类型迭代,除非基础类型是上述类型之一。
允许返回单值,或用_忽略。甚至可以仅迭代,不返回。可用来执行清空channel操作。
func main() {
data:=[3]string{"a","b","c"}
for i:=range data{
println(i,data[i])
}
for _,s :=range data{
println(s)
}
for range data{ //仅迭代,不返回。可用来执行清空channel操作。
}
}
无论普通for循环,还是range迭代,其定义的局部变量都会重复使用。
func main(){
data:=[3]string{"a","b","c"}
for i,s:=range data{
println(&i,&s)
}
}
输出:
0xc04202fee8 0xc04202ff00
0xc04202fee8 0xc04202ff00
0xc04202fee8 0xc04202ff00
这会对闭包产生一定的影响,后面会详述解决方案。
注意,range会复制目标数据。受直接影响的是数组,可改用数组指针或切片类型。
func main() {
data := [3]int{10, 20, 30}
for i,x:=range data{ //从data的副本中取值,所以下面对data原值的修改不影响这个副本,所以x的值还是原值。
if i ==0{
data[0] += 100
data[1] += 200
data[2] += 300
}
fmt.Println(data)
fmt.Printf("x: %d, data: %d\n", x, data[i]) //注意,这里的data是原值,并不是data副本。data副本只存在于range取值。
}
for i, x := range data[:] { //仅复制slice,不包括底层array。
if i == 0 {
data[0] += 100
data[1] += 200
data[2] += 300
}
fmt.Printf("x: %d, data: %d\n", x, data[i])
}
输出:
x: 10, data: 110
x: 20, data: 220
x: 30, data: 330
x: 110, data: 210 // 当i=0时,x已经取完值,所以是110不是210.
x: 420, data: 420 //复制的仅是slice自身,底层array依旧是原对象
x: 630, data: 630
相关数据类型中,字符串、切片基本结构是个很小的结构体,而字典、通道本身是指针封装,复制成本都很小,无须专门优化。
如果range目标是函数调用,也仅被执行一次。
func data()[]int{
for i:=0;i<3;i++{
println("orgin data.",i)
return []int{10,20,30}
}
return []int{20,30,40}
}
func main(){
for i,x:=range data(){
println(i,x)
}
}
输出:
orgin data. 0
0 10
1 20
2 30
建议循环嵌套不要超过2层,否则会难以维护。必要时可剥离,重构为函数。
3.4 跳出循环:goto, continue,break
break语句
break:用于switch、for、Select循环体。break终止整个语句块的执行。
示例代码:
package main
import (
"fmt"
)
func main() {
for i := 1; i <= 10; i++ {
if i > 5 {
break //loop is terminated if i > 5
}
fmt.Printf("%d ", i)
}
fmt.Printf("\nline after for loop")
}
continue语句
continue:continue语句仅用于跳过for循环的当前迭代,终止后续逻辑,立即进入下一轮循环。
示例代码:
package main
import (
"fmt"
)
func main() {
for i := 1; i <= 10; i++ {
if i%2 == 0 {
continue
}
fmt.Printf("%d ", i)
}
}
goto语句
网上对于goto的诟病很多。但是还是有很多地方用到,就连go源码里也随处可见。
虽然某些设计模式可用来消除goto语句,但在性能优先的场合,它能发挥积极作用。
使用goto前,须先定义标签。
goto:可以无条件地转移到过程中指定的行
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 10
/* 循环 */
LOOP: for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
}
配合标签,break和continue可在多层嵌套中指定目标层级。
package main
func main() {
outer:
for x:=0; x<5; x++{
for y:=0; y<10; y++{
if y>2 {
println()
continue outer
}
if x > 2{
break outer
}
print(x,":",y, " ")
}
}
}
输出:
0:0 0:1 0:2
1:0 1:1 1:2
2:0 2:1 2:2