流程控制是Go语言中必不可少的一部分,也是整个编程基础的重要一环。Go语言的流程控制语句和其他编程语言的流程控制语句有些不同,主要体现在Go语言没有do-while语句。Go语言常用的流程控制包括if语句、switch语句、for语句及goto语句等,switch语句和goto语句主要是为了简化代码、降低代码重复率,属于扩展类的流程控制语句。
在Go语言中,if语句主要用于条件判断。if语句还有两个分支结构:if-else语句和else-if语句
在Go语言中,关键字if是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行if后由大括号“{}”括起来的代码块,否则就忽略该代码块继续执行后续的代码。
if 条件表达式 {
代码块
}
使用if语句判断一个变量的大小:
func main() {
/*定义局部变量*/
var a int = 20
if a < 30 {
fmt.Printf("a 小于 30\n")
}
fmt.Printf("a 的值为:%d\n", a)
}
if语句后可以使用可选的else语句,else语句中的表达式在条件表达式为false时执行。if和else后的两个代码块是相互独立的分支,只能执行其中一个。
if 条件表达式 {
代码块1
} else {
代码块2
}
func main() {
/*定义局部变量*/
var a int = 50
if a < 30 {
fmt.Printf("a 小于 30\n")
} else {
fmt.Printf("a 不小于 30\n")
}
fmt.Printf("a 的值为:%d\n", a)
}
if 条件表达式 {
代码块1
} else if 条件表达式 {
代码块2
} else {
代码块3
}
func main() {
/*定义局部变量*/
var a int = 15
if a < 10 {
fmt.Printf("a 小于 10\n")
} else if a > 20 {
fmt.Printf("a 大于 20\n")
} else {
fmt.Printf("a 大于 10\n")
fmt.Printf("a 小于 20\n")
}
fmt.Printf("a 的值为:%d\n", a)
}
(1)if后面的条件判断子句不需要使用小括号括起来,例如,if a > 30。
(2)Go语言规定,与if匹配的“{”必须与if和表达式放在同一行,如果尝试将“{”放在其他位置,将会出发编译错误。与else匹配的“{”也必须与else在同一行,else也必须与上一个if或else if的右边的大括号在一行。
(3)if后面可以带一个简单的初始化语句,并以分号进行分隔,该简单语句声明的变量的作用域是整个if语句块,包括后面的else if和else分支。
(4)Go语言没有条件运算符(a>b?a:b),符合Go语言的设计理念,只提供一种方法做事情。
(5)if分支语句如果遇到return,则直接返回
switch语句和select语句在Go语言中主要用于条件的选择。相比C语言,Go语言中的switch语句在结构上更加灵活,语法设计尽量以使用方便为主。
在Go语言中,switch表示选择语句的关键字,switch语句会根据初始化表达式得出一个值,然后根据case语句的条件,执行相应的代码块,最终返回特定内容。每个case被称为一种情况,只有当初始化语句的值符合case的条件语句时,case才会被执行。
如果没有遇到符合的case,则可以使用默认的case (default case),如果已经遇到了符合的case,那么后面的case都不会被执行。
Go语言改进了switch的语法设计,case与case之间是独立的代码块,不需要通过break语句跳出当前case代码块以避免执行到下一行
switch (表达式) {
case常数1:
代码块1;
case常数2:
代码块2;
……
default:
代码块n+1;
}
func main() {
/*定义局部变量*/
a := "hello"
switch a {
case "hello":
fmt.Println("hello")
case "world":
fmt.Println("world")
default:
fmt.Println("hello world!!!")
}
}
与其他编程语言不同的是,在Go语言编程中,switch有两种类型。
(1)表达式switch:在表达式switch中,case包含与switch表达式的值进行比较的表达式。
(2)类型switch:在类型switch中,case包含与特殊注释的switch表达式的类型进行比较的类型。
表达式switch:
func main() {
grade := "E"
marks := 95
switch {
case marks >= 90:
grade = "A"
case marks >= 80:
grade = "B"
case marks >= 70:
grade = "C"
case marks >= 60:
grade = "D"
default:
grade = "E"
}
switch {
case grade == "A":
fmt.Println("成绩优秀!")
case grade == "B":
fmt.Println("表现良好!")
case grade == "C", grade == "D":
fmt.Println("再接再厉!")
default:
fmt.Println("成绩不合格!")
}
fmt.Println("你的成绩为:", grade)
}
类型switch:
类型switch语句针对变量的类型判断该执行哪个case代码块
func main() {
x = 1
switch i := x.(type) {
case nil:
fmt.Println("这里是nil,x的类型是%T", i)
case int:
fmt.Println("这里是int,x的类型是%T", i)
case float64:
fmt.Println("这里是float64,x的类型是%T", i)
case bool:
fmt.Println("这里是bool,x的类型是%T", i)
case string:
fmt.Println("这里是string,x的类型是%T", i)
default:
fmt.Println("未知类型!")
}
}
switch的特点如下:
在Go语言中,除了switch语句外,还有一种选择结构——select。select语句可以用于配合通道(channel)的读/写操作,用于多个channel的并发读/写操作。
select语句类似于switch语句,switch语句是按照顺序从上到下依次执行,而select是随机选择一个case执行。如果没有case可运行,它将阻塞,直到有case可运行。
select {
case:
代码块1;
case:
代码块2;
default : /*可选*/
代码块n;
}
func main() {
a := make(chan int, 1024)
b := make(chan int, 1024)
for i := 0; i < 10; i++ {
fmt.Printf("第%d次,", i)
a <- 1
b <- 1
select {
case <-a:
fmt.Println("from a")
case <-b:
fmt.Println("from b")
default:
fmt.Println("from c")
}
}
}
在以上代码中,同时在a和b中进行选择,哪个有内容就从哪个读,由于channel的读/写操作是阻塞操作,使用select语句可以避免单个channel的阻塞。此外,select同样可以使用default代码块,避免所有channel同时阻塞。
在Go语言中,循环语句的关键字是for,没有while关键字。for语句可以根据指定的条件重复执行其内部的代码块,这个判断条件一般是由for关键字后面的子语句给出的。
for循环是一个循环控制结构,可以执行指定次数的循环。循环体不停地进行循环,直到循环终止条件返回false时自动退出循环,执行for的“}”之后的语句。
for 循环控制变量初始化; 循环终止条件; 循环控制变量增量 {
循环体
}
初始语句是在第一次循环前执行的语句,一般使用初始语句执行变量初始化,如果变量在此处被声明,其作用域将被局限在这个for的范围内。
初始语句可以被忽略,但是初始语句之后的分号必须写,例如:
step := 2
for ; step > 0; step-- {
fmt.Println(step)
}
注意事项:
每一个for语句都可以使用一个特殊的range子语句,其作用类似于迭代器,用于轮询数组或者切片值中的每一个元素,也可以用于轮询字符串的每一个字符,以及字典值中的每个键值对,甚至还可以持续读取一个通道类型值中的元素。
range关键字的右边是range表达式,表达式一般写在for语句的前面,以便提高代码的易读性。
range关键字的左边表示的是一对索引-值对,根据不同的表达式,返回不同的结果,range右边表达式返回的类型如下图:
range右边表达式返回的类型,除了轮询字符串外,还包括数组、切片、字典及通道等。
func main() {
numbers := [5]int{1, 2, 3, 4}
for i, x := range numbers {
fmt.Printf("第%d次,x的值为%d\n", i, x)
}
}
在以上代码中,定义了numbers的长度为5,但numbers中只有4个值,因此最后一个为空值,从for循环返回的信息可以看到第5次x的值为0,代码块的确执行了5次。
Go语言除了传统的流程控制语句外,还有一些特殊的控制语句,defer就是其中之一。defer主要用于延迟调用指定的函数,defer关键字只能出现在函数的内部:
func main() {
defer fmt.Printf("world!")
fmt.Printf("Hello ")
}
在以上代码中会首先打印hello,然后打印world,因为第一句使用了defer关键字,defer语句会在函数最后执行,defer后面的表达式必须是外部函数的调用,上面的例子就是针对fmt.Println函数的延迟调用。
defer有如下两大特点:
(1)只有当defer语句全部执行,defer所在函数才算真正结束执行。
(2)当函数中有defer语句时,需要等待所有defer语句执行完毕,才会执行return语句。
因为defer的延迟特点,可以把defer语句用于回收资源、清理收尾等工作。使用defer语句之后,不用纠结回收代码放在哪里,反正都是最后执行。
需要注意defer的执行时机:
var i = 0
func print() {
fmt.Println(i)
}
func main() {
for ; i < 5; i++ {
defer print()
}
}
var i = 0
func print(i int) {
fmt.Println(i)
}
func main() {
for ; i < 5; i++ {
defer print(i)
}
}
在Go语言中,有一个特殊的概念就是标签,可以给for、switch或select等流程控制代码块打上一个标签,配合标签标识符可以方便跳转到某一个地方继续执行,有助于提高编程效率。
标签的名称区分大小写,为了提高代码的易读性,建议标签名称使用大写字母和数字。标签可以标记任何语句,并不限定于流程控制语句,未使用的标签会引发错误。
Go语言中的break语句主要用于以下两方面:
在多重循环中,可以用标号label标出想跳出的指定循环:
func main() {
OuterLoop:
for i := 0; i < 2; i++ {
for j := 0; j < 5; j++ {
switch j {
case 2:
fmt.Println(i, j)
break OuterLoop
case 3:
fmt.Println(i, j)
break OuterLoop
}
}
}
fmt.Println("调出循环")
}
Go语言中的continue语句可以结束当前循环,开始下一次的循环迭代过程,仅限在for循环内使用,在continue语句后添加标签时,表示开始标签对应的循环:
func main() {
OuterLoop:
for i := 0; i < 2; i++ {
for j := 0; j < 5; j++ {
switch j {
case 2:
fmt.Println(i, j)
continue OuterLoop
case 3:
fmt.Println(i, j)
continue OuterLoop
}
}
}
fmt.Println("调出循环")
}
Go语言中的goto语句通过标签进行代码间的无条件跳转,同时goto语句在快速跳出循环、避免重复退出上也有一定的作用,使用goto语句能简化一些代码的实现过程。
goto语句通常与条件语句配合使用,可用来实现条件转移、构成循环、跳出循环体等功能。但是,在结构化程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序都产生困难。
goto语句有以下几个特点:
使用goto退出多层循环:
func main() {
for x := 0; x < 10; x++ {
for y := 0; y < 10; y++ {
if y == 2 {
// 跳转到标签
goto breakHere
}
}
}
// 手动返回,避免执行进入标签
return
// 标签
breakHere:
fmt.Println("done")
}