1. 前言
go语言的控制结构和C语言是类似的,本节主要讲解条件语句:if、switch、select 和 循环语句for、for-range。
2. if
package main
import "fmt"
func main() {
a := 6
b := 8
// 省略括号
if a < b {
fmt.Println("a 小于 b")
}
if a < 3 {
fmt.Println("a 小于 3")
} else {
fmt.Println("a 大于 3")
}
// 在if语句中进行赋值
if c, ok := compare(a); ok {
fmt.Printf("a 大于 c; a: %d,c: %d",a,c)
}
}
func compare(a int) (int, bool) {
c := 2
var re bool
if a > c {
re = true
} else {
re = false
}
return c, re
}
输出:
a 小于 b
a 大于 3
a 大于 c; a: 6,c: 2
3. switch
go语言里switch默认每个case最后带break,每一个 case 分支都是唯一的,从上往下直到匹配为止
package main
import "fmt"
func main() {
a := 6
switch a {
case 1: // 空分支只有a=1时才进来
case 3:
fmt.Println("只有a=3才进来")
case 5,8:
fmt.Println("只有等于5,8才进来")
default:
fmt.Println("其它值进入default")
}
// 另外表达形式
b := 8
switch {
case b == 1: // 空分支只有a=1时才进来
case b == 3:
fmt.Println("只有b=3才进来")
case b == 5 || b == 8:
fmt.Println("只有b等于5,8才进来")
default:
fmt.Println("其它值进入default")
}
}
输出:
其它值进入这里
只有b等于5,8才进来
4. select
select语句类似于switch语句,不同点是switch是从上往下匹配,select是随机执行一个可运行的case(select中的case条件(非阻塞)是并发执行的)。 如果没有 case 可运行,它将阻塞,直到有 case 可运行。
select语句只能用于信道的读写操作,是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作。
每个 case 都必须是一个通信
所有 channel 表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行,其他被忽略。
如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
否则:
如果有 default 子句,则执行该语句。
如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
对于case条件语句中,如果存在信道值为nil的读写操作,则该分支将被忽略,可以理解为从select语句中删除了这个case语句
如果有超时条件语句,判断逻辑为如果在这个时间段内一直没有满足条件的case,则执行这个超时case。
,则直接执行这个case。一般用超时语句代替了default语句
对于空的select{},会引起死锁
对于for中的select{}, 也有可能会引起cpu占用过高的问题
- select中的case语句是随机执行的
package main
import "fmt"
func main() {
size := 10
ch1 := make(chan int, size)
for i := 0; i < size; i++ {
ch1 <- 1
}
ch2 := make(chan int, size)
for i := 0; i < size; i++ {
ch2 <- 2
}
ch3 := make(chan int, 1)
select {
case v := <-ch1:
fmt.Print(v)
case b := <-ch2:
fmt.Print(b)
case ch3 <- 10:
fmt.Print(3)
default:
fmt.Println("default")
}
}
执行多次,会随机输出不同的值,1、2、3。这是因为ch1和ch2是并发执行会同时返回数据,所以会随机选择一个case执行。 因为上面的三个case都是可以操作的信道,所以永远不会执行default语句。
- 超时使用
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func(c chan int) {
// 修改时间后,再查看执行结果
time.Sleep(time.Second * 1)
ch <- 1
}(ch)
select {
case v := <-ch:
fmt.Print(v)
case <-time.After(2 * time.Second): // 等待 2s
fmt.Println("no case ok")
}
time.Sleep(time.Second * 1)
}
如果等待时间超出<2秒,则输出1,否则打印“no case ok”
5. for
package main
import "fmt"
func main() {
for a := 0; a < 5; a++ {
fmt.Println(a)
}
// 循环嵌套输出 2 到 100 间的素数
var i, j int
for i = 2; i < 10; i++ {
for j = 2; j <= (i / j); j++ {
if i%j == 0 {
break // 如果发现因子,则不是素数
}
}
if j > (i / j) {
fmt.Printf(" %d是素数\n", i)
}
}
// for 条件语句 {};类似于while
b :=3
for b>1 {
b--
fmt.Println("没有初始化和修饰语句的for结构:", b)
}
// 无限循环,只有在循环体里 break才会跳出
c := 0
for {
fmt.Println("无限循环:", c)
c++
if c > 3 {
break
}
}
}
输出:
0
1
2
3
4
2是素数
3是素数
5是素数
7是素数
没有初始化和修饰语句的for结构: 2
没有初始化和修饰语句的for结构: 1
无限循环: 0
无限循环: 1
无限循环: 2
无限循环: 3
6. for-range
这种循环语法很类似其它语言中 foreach 语句,它常用于数组、切片、map、字符串等
package main
import "fmt"
func main() {
str := "abc"
fmt.Printf("The length of str is: %d\n", len(str))
for index, value := range str {
fmt.Printf(" %d : %c \n", index, value)
}
// 忽略value
for i := range str {
println(i)
}
// 忽略 index。
for _, c := range str {
println(string(c))
}
// 忽略全部返回值,仅迭代。
for range str {
println("只迭代")
}
}
输出:
The length of str is: 3
0 : a
1 : b
2 : c
0
1
2
a
b
c
只迭代
只迭代
只迭代
7. break与continue
使用 break 语句会退出当前循环,使用 continue 跳过执行直接进入下一次循环. 另外,关键字 continue 只能被用于 for 循环中。
package main
func main() {
for i := 0; i < 2; i++ {
for j := 0; j < 8; j++ {
if j > 3 {
break
} else if j == 2 {
continue
}
println(j)
}
}
}
8. 标签与goto
标签名区分大小写,定义后若不使用会造成编译错误,标签(label)可在for、switch 或 select 语句中做标识符使用。
goto 语句通常与条件语句配合使用,使用goto可以无条件地转移到过程中指定的行
在结构化程序设计中一般不主张使用 goto、标签语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。