条件语句需要开发者通过指定一个或多个条件,并通过测试条件是否为true来决定是否执行指定语句,并在条件为false的情况下执行另外的语句。
Go语言提供了一下几种条件判断语句
一. 条件语句if
1.1 if语句由一个布尔表达式后紧跟一个或多个语句组成
语法:
if 布尔表达式{
/*在布尔表达式为true时执行*/
}
特点:
- 可省略条件表达式括号
- 持初始化语句,可定义代码块局部变量
- 代码左括号必须在条件表达式尾部
- Go语言不支持三目运算符 "a > b ? a : b"
package main
import "fmt"
func main() {
n := 0
//报错
// if n > 0
// {
// }
if str := "abc"; n > 0 { //初始化语句 不一定是定义变量,如println("init")也可以
fmt.Println(str[2])
} else if n < 0 { //注意else if 和else左大括号位置
fmt.Println(str[1])
} else {
fmt.Println(str[0])
}
}
if在布尔表达式为true时,其后紧跟的语句块执行,如果为false则不执行。
1.2 if ... else语句
if语句后可以使用可选的else语句,else语句中的表达式在布尔表达式为false时执行。
语法:
if 布尔表达式 {
/*在布尔表达式为true时执行*/
} else {
/*在布尔表达式为false时执行*/
}
实例:
1.3 if ... else if ... else语句
语法:
if 布尔表达式1 {
/*布尔表达式1为true*/
} else if 布尔表达式2{
/*布尔表达式1为false, 布尔表达式2为true*/
} else {
/*布尔表达似1和布尔表达式2为false*/
}
实例:
1.4 if嵌套语句
语法:
if 布尔表达式1 {
/*在布尔表达式1为true时执行*/
if 布尔表达式2 {
/*在布尔表达式2为true时执行*/
}
}
实例:
二.条件语句switch
2.1 switch语句
switch语句用于基于不同条件执行不同的动作,每个分支的case都是唯一的,从上往下逐一测试,直到匹配为止。
Golang的switch分支表达式可以是任意类型,不限于常量。可省略break,默认自动终止。
当所有的case都匹配不上,执行default里的代码。
语法:
switch var1 {
case val1:
...
case val2:
...
default:
...
}
变量var1可以是任意类型,而val1和val2可以是同类型的任意值。类型不被局限于常量或者整数,但必须是同类型,或者最终结果为相同类型的表达式。
case中可以同时测试多个可能符合条件的值,使用逗号分隔它们。例如:case val1, val2, val3。
实例:
2.2 Type Switch
switch语句还可以被用于type-switch来判断某个interface变量中实际存储的变量类型。
语法:
switch x.(type) {
case type1:
statement1(s)
case type2:
statement1(s)
/*你可以定义任意个数的case*/
default:
statementn(s)
}
实例:
- Go语言switch非常灵活,表达式可以不是常量或整数,执行过程从上到下,直到找到匹配的。
- 如果switch没有表达式,它会匹配case后值为true的
- Go里面switch默认每个case最后带有break,匹配成功不会自动向下执行其他case,而是跳出整个switch
- 可以使用fallthrough强制执行后面的case代码,不管匹不匹配
package main
import "fmt"
func main() {
var x interface{}
//写法1
switch i := x.(type) { //带初始化语句
case nil:
fmt.Printf("x 的类型: %T\r\n", i)
case int:
fmt.Printf("x 的类型是int\r\n")
case float64:
fmt.Printf("x 的类型是float64\r\n")
case func(int) float64:
fmt.Printf("x 的类型是func(int)\r\n")
case bool, string:
fmt.Printf("x 的类型是bool或string\r\n")
default:
fmt.Printf("类型未知\r\n")
}
//写法2 没有输出
var j = 0
switch j {
case 0:
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("def")
}
//写法3
var k = 0
switch k {
case 0:
fmt.Println("fallthrough")
fallthrough
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("def")
}
//写法4
var m = 0
switch m {
case 0, 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("def")
}
//写法5
var n = 0
switch {
case n > 0 && n < 10:
fmt.Println("n > 0 && n < 10")
case n > 10 && n < 20:
fmt.Println("n > 0 && n < 10")
default:
fmt.Println("def")
}
}
三.条件语句select
1.1 介绍
select是Go的一个控制结构,类似于用于通信的switch语句,每一个case必须是一个通信操作,要么是发送,要么是接收。select随机执行一个可运行的case。如果没有case可以运行,它将堵塞,直到有case可以运行。一个默认子句应该总是可运行的。
语法:
- 每一个case必须是一个通信
- 所有channel表达式都会被求值
- 所有被发送的表达式都会被求值
- 如果任意某个通信可以进行,它就执行,其他被忽略
- 如果有多个case可以运行,select会随机公平的选出一个执行。其它不会执行
- 如果有default,没有case可以执行,执行default语句
- 如果没有default语句,没有case可以执行,select将阻塞,直到某个通信可以运行,Go不会重新对channel或值进行求值。
select {
case communication clause:
statement(s)
case communication clause:
statement(s)
/*你可以定义任意数量的case*/
default:/*可选*/
statement(s)
}
实例:
- 执行过程
select可以监听channel的数据流动。select的用法于switch语法非常类似,由select开始的一个新的选择块,每一个选择条件由case语句类描述。
与switch语句可以选择任何使用相等比较的条件相比,select有比较多的限制条件,其中最大的一条限制条件就是每一个case语句必须是一个I/O操作。
select {
case <-chan1://检测有没有数据可读
//如果chan1成功读取到数据,则执行该case处理语句
case chan2<-1://检测有没有可以写
//如果成功向chan2写入数据,则进行该case处理语句
//假如没有default,那么在以上两个条件都不成立的情况下,就会在此阻塞
//一般default不会写在里面,select中的default子句总是可以运行的,因为会很消耗CPU资源
default:
//如果以上都没有符合条件,那么则进行default处理流程
}
在select语句中,Go会按顺序从头到尾评估每一个发送和接收语句。
如果其中的任意一个语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。
如果没有一条语句可以执行(即所有的通道都被阻塞),那么有两种情况:
- 如果给出default语句,那么就会执行default的流程,然后程序会继续往下执行
- 如果没有default语句,那么select语句将被阻塞,直到至少有一个case可以进行下去
1.2 Golang select的使用及典型用法
基本使用:
select是Go中的一个控制结构,类似于switch语句,用于异步IO操作。select会监听case语句中的channel的读写操作,当case中的channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。select中的case语句必须是一个channel操作。
- select中的default语句总是可运行的
- 如果有多个case都可以运行,select会随机公平的选出一个执行,其它不会执行
- 如果没有可运行的case语句,且有default语句,那么就会执行default的动作
- 如果没有可运行的case语句,且没有default语句,select将会阻塞,直到某个case通信可以运行。
典型用法:
- 超时判断
如下,使用全局变量resChan来接收response,如果时间超过3秒,resChan中还没有数据返回,则第二个case将执行。
package main
import (
"fmt"
"time"
)
var resChan = make(chan int)
func test() {
select {
case data := <-resChan:
doData(data)
case <-time.After(time.Second * 3):
fmt.Println("request time out")
}
}
func doData(data int) {
}
func main() {
test()
}
- 退出
在另外协程中,如果运行遇到非法操作或者不可处理的错误,就像shouldQuit发送数据通知程序停止运行。
package main
var shouldQuit = make(chan struct{})
func clearUp() {
}
//主协程
func main() {
{
//loop
}
//out of loop
select {
case <-shouldQuit:
clearUp()
return
default:
}
//...
}
close(shouldQuit)
- 判断channel是否阻塞
在某些情况下是存在不希望channel缓存满的需求
package main
var ch = make(chan int, 5)
func main() {
data := 0
select {
case ch <- data:
default:
//做相应操作,比如丢弃data s视需求而定
}
}
四. 循环语句for
1.1 三种循环方式
Golang支持三种循环方式,包括类似while的语法。
for循环是一个循环控制结构,可以执行指定次数的循环。
语法:
Go语言的for循环有3种形式,只有其中的一种使用分号。
- for init; condition;post{}
- init:一般为赋值表达式,给控制变量赋初值
- condition:关系表达式或逻辑表达式,循环控制条件
- post:一般为赋值表达式,给控制表里增量或减量
- for condition{}
- for {}
执行过程:
- 先对表达式init赋初值
- 判断赋值表达式init是否满足给定condition条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行post,进入第二次循环,再判别condition,当condition的值为假,不满足条件,就终止循环。
实例:
package main
import "fmt"
func main() {
s := "abc"
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
}
n := len(s)
for n > 0 {
fmt.Println(s[n-1])
n--
}
//死循环
for {
fmt.Println(s)
}
}
我们可以在初始化语句中计算出全部结果:
for循环上初始化的变量,只在当前for 循环内有效。
package main
import "fmt"
func main() {
//for初始化定义的值不会受已定义的同名变量影响
//并且不能再for循环外使用
for a := 0; a < 10; a++ {
fmt.Printf("a的值为%d\n", a)
}
//报错
//fmt.Println(a)
var a, b = 0, 15
for a < b {
fmt.Printf("a的值为%d\n", a)
a++
}
}
1.2 循环嵌套
实例:
使用循环嵌套来输出2到100间的素数:
package main
import "fmt"
func main() {
for i := 2; i <= 100; i++ {
j := 2
for ; j <= (i / j); j++ { //只需要检测一半的数
if i%j == 0 {
break //不是素数
}
}
if j > (i / j) {
fmt.Printf("素数%d\n", i) //遍历完 说明是素数
}
}
}
1.3 死循环
如果循环中的条件永远不会为false,则会进行无限循环。
package main
import "fmt"
func main() {
for {
fmt.Println("这是死循环")
}
for true {
fmt.Println("这是死循环")
}
}
五. 循环语句range
Golang range类似迭代器操作,返回(索引,值)或(键,值)。
for循环的range格式对slice,map,数组,字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
可以忽略不想要的返回值,或"_"这个特殊变量。
package main
func main() {
s := "abc"
//忽略value,支持string/array/slice/map
for i := range s {
println(s[i])
}
//忽略index
for _, val := range s {
println(val)
}
//忽略全部返回值,仅迭代
for range s {
}
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
println(k, v)
}
}
- 注意
在使用range进行遍历的时候,如果遍历的是值类型对象。首先会复制一份遍历的数据,然后键/索引和值是从复制的数据中取出来的。所以当修改原数组或者range后的值,互相不会产生影响。
如果是引用类型(slice,map,chan等),其底层数据不会被复制,复制的只是指针,但是指针指向的底层数据一样。
所以修改原数据,即修改了底层数据,会影响range后的值。但是修改range后的值,不会影响原数据,因为range后的值是一份拷贝。
对引用类型range的数据范围进行修改不会影响range。
5.1 for和for range的区别
主要是使用场景不同:
for可以遍历数组,切片,key为整型递增的map和string。
for range可以完成所有for可以做的事,也能做for不能做的事,包括遍历key为string类型的map,并同时获取key和value。 还可以遍历channel。
六.循环控制语句
循环控制语句可以控制循环体语句的执行过程。循环控制语句包括break,continue,goto。
Go语言的标签可以用来标记代码特定的位置。
- 三个语句都可以配合标签使用
- 标签名区分大小写,定义后若不使用会造成编译错误
- continue和break配合标签可用于多层循环的退出
- goto是调整执行位置,与break,continue配合标签结果不同
- break
在循环中的作用是跳出循环。但是只能跳出当前循环,无法跳出多层循环。
想跳出到指定位置,可以结合标签实现。标志位置表示要跳出的循环。
- continue
在循环中的意思是当次循环后续语句不再执行,但是不会跳出循环。
结合标志,表示要结束的单次循环的循环。标志指定的for循环,continue后的语句都不会执行。
- goto
goto需要结合标志来使用,表示直接跳到对应的代码逻辑。