Go语言入门-流程控制(for、switch、select、range、break、goto、continue)
目录
条件语句
if语句
if语句语法
if bool表达式 { //左花括号必须跟随bool表达式
//当bool表达式为true是执行。
Todo....
}示例如下:
package main
func main() {
a := 10
if a >= 10 {
println("a 大于等于10") //10
}
if a < b100 {
println("a小于等于100") //100
}
}
/**
output:
a 大于等于10
a小于等于100
*/
if...else 语法
if bool表达式1 {
Todo..分支1
} else if bool表达式2 {
Todo..分支2
} else{
Todo..分支3
}示例如下:
//获取绩效等级
func getPerformanceLevel(score int) {
if score > 120 {
fmt.Println("怕是要上天")
} else if score > 100 {
fmt.Println("超越")
} else if score >= 90 {
fmt.Println("优秀")
} else if score >= 80 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("合格")
} else {
fmt.Println("卷铺盖走人吧")
}
}
func main() {
score1 := 300
score2 := 110
score3 := 95
score4 := 81
score5 := 61
score6 := 50
getPerformanceLevel(score1)
getPerformanceLevel(score2)
getPerformanceLevel(score3)
getPerformanceLevel(score4)
getPerformanceLevel(score5)
getPerformanceLevel(score6)
}
/**
output:
怕是要上天
超越
优秀
良好
合格
卷铺盖走人吧
*/
if 初始化语句
func main() {
if a := 10; a > 0 {
fmt.Println("if初始化测试")
}
}
/**
output:
if初始化测试
*/以上例子中定义了局部变量a,局部变量a的作用域为整个if else语句块。这一点区别于C、C++、Java语言。
备注:Go语言中不支持三元操作符(三目运算符) "a > b ? a : b"。
switch语句
“ Switch”语句提供分支执行功能。 将表达式或类型说明符与“ switch”中的“ case”表达式进行比较,以确定要执行的分支。也就是说switch分为两类,第一类是 基于表达式选择的switch语句,第二类是基于类型选择的switch语句。
表达式switch语句语法
switch expr { //expr 表达式
case var1: Todo1 //expr表达式的值同var1进行比较,如果相等 执行Todo1语句并且退出switch
case var2: Todo2 //expr表达式的值同var2进行比较,如果相等 执行Todo2语句并且退出switch
case var3: Todo3 //expr表达式的值同var3进行比较,如果相等 执行Todo3语句并且退出switch
default: Todo4 //expr表达式的值都不满足case语句后面表达式的值 则会执行default
}
//分支使用表达式,类似于if else
switch { //无表达式
case expr1: Todo1 //expr表达式为true 执行Todo1语句并且退出switch
case expr2: Todo2 //expr表达式为true 执行Todo3语句并且退出switch
case expr3: Todo3 //expr表达式为true 执行Todo3语句并且退出switch
default: Todo4 //以上case都不满足时, 执行Todo4语句并且退出switch
}
通过以上可以看出是与C语言、C++、Java是不一样的,执行了某一条case语句以后实际上不需要再次break主动退出switch语句。
示例如下
func getWeekDay(day int) {
switch day {
case 7 :
fmt.Println("周 日")
case 6:
fmt.Println("周", day)
case 5:
fmt.Println("周", day)
case 4:
fmt.Println("周", day)
case 3:
fmt.Println("周", day)
case 2:
fmt.Println("周", day)
case 1:
fmt.Println("周", day)
default:
fmt.Println("ERROR")
}
}
func main() {
for i := 0; i < 9; i++ {
getWeekDay(i)
}
}
/**
ouput:
ERROR
周 1
周 2
周 3
周 4
周 5
周 6
周 日
ERROR
*/由此可以看出,switch后跟表达式语句时,更加适用与某些点固定值的分支执行。ifelse适用与某些段的分支执行,当然两种也可以互相转换。那么switch能够模拟if ... else按段执行吗?答案是可以的。可以使用switch { case expr:...}的方式进行处理。如下:
//用switch改造if...else
//获取绩效等级
func getPerformanceLevelBySwitch(score int) {
switch {
case score > 120:
fmt.Println("怕是要上天")
case score > 100:
fmt.Println("超越")
case score >= 90:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
case score >= 60:
fmt.Println("合格")
default:
fmt.Println("卷铺盖走人吧")
}
}
func main() {
score1 := 300
score2 := 110
score3 := 95
score4 := 81
score5 := 61
score6 := 50
getPerformanceLevelBySwitch(score1)
getPerformanceLevelBySwitch(score2)
getPerformanceLevelBySwitch(score3)
getPerformanceLevelBySwitch(score4)
getPerformanceLevelBySwitch(score5)
getPerformanceLevelBySwitch(score6)
}
/**
output:
怕是要上天
超越
优秀
良好
合格
卷铺盖走人吧
*/以上例子通过switch后面无表达式的方式,通过case关键字后增加bool表达式来判断是否执行case语句。从而达到模拟if...else进行数据分段的逻辑判断。
该用法为go语言特殊用法
类型switch语句语法
switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型如下:
func typeSwitchDemo(x interface{}) {
switch x.(type) {
case nil:
fmt.Println("nil")
case int:
fmt.Println("int")
case float64:
fmt.Println("float64")
case bool, string:
fmt.Println("bool or string")
default:
fmt.Println("no found Type")
}
}
func main() {
var a1 chan int
a2 := 1
a3 := 1.0
a4 := true
a5 := "dsfsd"
typeSwitchDemo(a1) //no found Type
typeSwitchDemo(nil) //nil
typeSwitchDemo(a2) //int
typeSwitchDemo(a3) //float64
typeSwitchDemo(a4) //bool or string
typeSwitchDemo(a5) //bool or string
}
/**
output:
no found Type
nil
int
float64
bool or string
bool or string
*/类型switch官网示例2
switch i := x.(type) { //带了初始化语句。
case nil:
printString("x is nil") // type of i is type of x (interface{})
case int:
printInt(i) // type of i is int
case float64:
printFloat64(i) // type of i is float64
case func(int) float64:
printFunction(i) // type of i is func(int) float64
case bool, string:
printString("type is bool or string") // type of i is type of x (interface{})
default:
printString("don't know the type") // type of i is type of x (interface{})
}以上例子中定义了局部变量i (switch i := x.(type)) 实际i存放x的类型。然后使用i去和case条件进行匹配。 同时可以发现 case bool,string的写法。也就是 case后面可以并联多个条件。如下:
//case后多个条件并列写法
func main() {
for tmp := 0; tmp < 4; tmp++ {
switch tmp {
case 0, 1: //tmp = 0 或者tmp = 1走该分支
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("def")
}
}
}
/**
output:
1
1
2
def
*/特殊用法fallthough
Go里面switch默认相当于每个case执行后后就结束switch块,匹配成功后不会自动向下执行其他case,而是跳出整个switch,
但是可以使用fallthrough强制执行后面的case代码。
func main() {
for tmp := 0; tmp < 4; tmp++ {
fmt.Println("循环次数 tmp----->:", tmp)
switch tmp {
case 0, 1: //tmp = 0
fmt.Println(tmp, "此处fallthrough")
fallthrough
case 2:
fmt.Println(tmp, "2")
default:
fmt.Println(tmp, "def")
}
}
}
/**
output:
循环次数 tmp----->: 0
0 此处fallthrough
0 2
循环次数 tmp----->: 1
1 此处fallthrough
1 2
循环次数 tmp----->: 2
2 2
循环次数 tmp----->: 3
3 def
*/以上例子中,当tmp为0和1是,执行完case 0,1后,即使不满足case 2:也会执行 case2的语句。
fallthrough强制执行后面的case代码,但是不能用于类型switch中。
The "fallthrough" statement is not permitted in a type switch.-------划重点。。。
示例如下:
func typeSwitchDemo(x interface{}) {
switch x.(type) {
case nil:
fmt.Println("nil")
//Cannot fallthrough in type switch--编译报错
fallthrough
case int:
fmt.Println("int")
case float64:
fmt.Println("float64")
case bool, string:
fmt.Println("bool or string")
default:
fmt.Println("no found Type")
}
}select语句
select 类似于switch语句,但是select会随机执行一个可以运行的case语句。每个case语句后面必须是通讯操作(io),要么是发送操作要么是接收语句。select会监听case语句的读写操作。当case语句中读写操作变为非阻塞时(读取到值或者能写入值)就会被select监听到,当select监听到只有一个case语句读写动作后就会执行改case语句。当select监听到多个case语句时的读写动作时会随机选择一条case语句执行。当没有读写动作发送时,如果存在default则会执行default语句否则就会阻塞在select监听上,直到有读写事件触发。
select语法
select {
case sendOrReceive1: block1 //通讯操作IO
...
case sendOrReceive1: blockN //通讯操作IO
default:
blockDefault //没有事件触发则执行default
}简单示例
unc main() {
ch := make(chan int) //无缓存通道
ch1 := make(chan int) //无缓存通道
var get, put int
go func() { //go rountine启动读取循环读取ch1的值
for {
<-ch1
time.Sleep(time.Second * 2)
}
}()
go func() {
for { //每隔2秒定时往通道ch写一个0-10的随机整数。
ch <- rand.Intn(10)
time.Sleep(time.Second * 2)
}
}()
for {
select {
case ch1 <- put: //监听通道ch是否发生写事件,因为是无缓存通道,只有当消息被获取到认为写成功
fmt.Println("put:", put)
case get = <-ch: //监听通道ch中是否有值如果有值取出来赋值给get,
fmt.Println("get:", get)
default: //如果没有监听到读写事件则执行default
fmt.Println("no event")
time.Sleep(time.Second)
}
}
}
/**
output:
no event
put: 0
get: 1
no event
no event
no event
put: 0
get: 7
no event
no event
put: 0
no event
get: 7
no event
put: 0
no event
.....
*/
以上实例中 有两个协程分别往无缓冲的通道中每隔2秒写数据和读数据,select语句分别监控读写动作,当没有读写动作发生是执行default语句休眠一秒以后通过循环继续监听读写动作。所以在结果打印中no event 出现的概率大概是put或者get2倍。
进阶示例1 -超时判断
var ch = make(chan int)
func selectWaitTimeTest() {
select {
case data := <- ch: //监听读数据是否完成
fmt.Println("get data :", data)
case <-time.After(time.Second * 2): //3秒后出发读数据
fmt.Println("select wait time")
}
}
func main() {
//模拟超时判断
selectWaitTimeTest()
}
/**
output:
3秒后打印
select wait time
*/进阶示例2 -安全退出程序
//模拟协同退出
var quitChan = make (chan struct{})
func main() {
go func() { //子协程控制主协程退出
i := 0
for i < 5 {
i++
time.Sleep(time.Second * 1)
fmt.Printf("休眠第[%d]秒\n", i)
}
fmt.Println("5秒后通知主协程退出")
quitChan <- struct{}{}
}()
select {
case <- quitChan: //只有一个case,主协程阻塞等待通道中有数据并出发读操作。从通道中获取到值以后打印退出数据,并退出
println("exit!!!")
return
}
}
/**
output:
休眠第[1]秒
休眠第[2]秒
休眠第[3]秒
休眠第[4]秒
休眠第[5]秒
5秒后通知主协程退出
exit!!!
*/
循环语句
for语句
Go语言中循环语句的关键词只有for没有其他语言中while,但是while语句可以通过for语句来模拟实现
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .
//语法1
for 初始化表达式; 条件表达式; 赋值表达式 {
Todo
}
//语法2
for 条件表达式 {
Todo
}
//相当于其他语言中
while 条件表达式 {
Todo
}
//语法3--死循环
for {
Todo
}
//相当于其他语言中
while (1) {
Todo
}
基本用法示例
示例1-简单的 for 初始化表达式; 条件表达式; 赋值表达式 {Todo}
func main() {
s := "test"
//循环遍历字符串
for i := 0; i < len(s); i++ {
fmt.Printf("%c\n", s[i])
}
}
/**
output:
t
e
s
t
*/示例2-for 条件表达式 {Todo}
func main() {
i := 5
for i < 10 {
i++
fmt.Println(i)
}
}
/**
output:
6
7
8
9
10
*/
示例3-//语法3--死循环for {Todo}
func main() {
i := 0
for { //死循环
fmt.Println(i)
i++
if i > 5 {
break //i自增到5以后退出循环
}
}
}
/**
output:
0
1
2
3
4
5
*/
for range语句
go语言中可以使用for range语句来遍历数组、切片、字符串、通道、map。
数组、切片、字符串返回索引值
map返回键和值
通道返回通道内元素的值
如下:
Range expression 1st value 2nd value array or slice a [n]E, *[n]E, or []E index i int a[i] E string s string type index i int see below rune map m map[K]V key k K m[k] V channel c chan E, <-chan E element e E
示例1--演示字符串迭代
func main() {
s := "abcdef"
for i := range s { //i为第一个返回值下标
fmt.Printf("%c\n", s[i])
}
fmt.Println("=============================================")
for i, v := range s { //同时获取第一个返回值-下标。和第二个返回值-元素值
fmt.Printf("index[%d] value=[%c]\n", i, v)
}
fmt.Println("=============================================")
for _, v := range s { //丢弃下标。获取第二个返回值-元素值
fmt.Printf("value=[%c]\n", v)
}
fmt.Println("=============================================")
for i, _ := range s { //获取下标。丢弃-元素值
fmt.Printf("index[%d]\n", i)
}
}
/**
output:
a
b
c
d
e
f
=============================================
index[0] value=[a]
index[1] value=[b]
index[2] value=[c]
index[3] value=[d]
index[4] value=[e]
index[5] value=[f]
=============================================
value=[a]
value=[b]
value=[c]
value=[d]
value=[e]
value=[f]
=============================================
index[0]
index[1]
index[2]
index[3]
index[4]
index[5]
*/示例2--演示map迭代
func main() {
m := map[string]int {"key1":1,"key2":2, "key3":3}
// range打印的map为无序的
for k, v := range m {
fmt.Println(k, v)
}
fmt.Println("=============================================")
//丢弃值
for k, _ := range m {
fmt.Println(k)
}
fmt.Println("=============================================")
//丢弃键
for _, v := range m {
fmt.Println(v)
}
}
/**
output:
key2 2
key3 3
key1 1
=============================================
key1
key2
key3
=============================================
1
2
3
*/示例3-通道range遍历
func main() {
var ch = make(chan int, 5)
var ch1 = make(chan int, 5)
for i := 0; i < 5; i++ {
ch <-i
}
close(ch) //通道要么被关闭,要么持续有值写入,否则使用range会发生死锁
for v := range ch {
fmt.Println(v)
}
go func() {
for { //无限循环,通道没有关闭,也可以通过range持续获取值。
ch1 <- 0
time.Sleep(time.Second)
}
}()
//遍历通道ch1
for v := range ch1 {
fmt.Println(v)
}
}
/**
0
1
2
3
4
0
0
0
0
0
0
0
主动退出
Process finished with exit code 2
*/
循环跳转语句
goto语句
goto statement transfers control to the statement with the corresponding label within the same function.--goto语句将控制权转移到同一函数中带有相应标签的语句。重点是同一函数中。
goto语句语法
goto Lable //指定跳转的标签
....
Lable:statement //标签指定的语句块。
示例1-简单使用goto1
func main() {
i := 0
if i == 0 {
fmt.Println("will goto exit")
goto exit//跳转到exit标签指定的语句。
}
fmt.Println("normal")
exit://exit只是一个标签,即使前面没有发生goto跳转会执行exit标记的后续语句
fmt.Println("exit")
}
/**
output:
will goto exit
exit
*/上面示例中当i为0的时候,就会执行goto语句,直接执行exit标记的后面语句,而跳过fmt.Println("normal")的执行。
示例2-简单使用goto2
func main() {
i := 1
if i == 0 {
fmt.Println("will goto exit")
goto exit//跳转到exit标签指定的语句。
}
fmt.Println("normal")
exit://exit只是一个标签,即使前面没有发生goto跳转会执行exit标记的后续语句
fmt.Println("exit")
}
/**
output:
normal
exit
*/上面示例中当i是1的时候,不会在执行goto语句,但是exit标记语句还会执行。因此exit标记后的语句。不是goto语句专属的。只要满足执行条件也会执行exit后续的语句。
使用要点:
1. goto语句只能在一个函数中使用,并且不能跳转到其他块的内部作用域。举个例子:
func main44() {
i := 0
if i == 0 {
goto Label1
}
for {
//goto不在Label1的作用域中所以报错。
Label1: //Unused label 'Label1'
fmt.Println("vvvvvvvvvvvv")
}
return
}
2. goto语句和标签label中间没有变量声明,这点很好理解。如果goto语句跳转到标签后要使用一个变量,而这个变量没有在goto前面定义,编译器就会报错。算是逻辑错误+语法错误。(在这里:为啥会一棒子打死,按理如果goto和lable中有变量声明,但是label后面没有用到该变量应该也没啥问题吧?)
举个例子:
func main55() {
goto Lable2 //Lable2 jumps over declaration of i
i := 0
fmt.Println(i)
Lable2:
fmt.Println("exit")
return
}3. goto不能跨函数使用,同第二点很好理解。不在一个作用域中。
最后备注:在能避免使用goto的情况下尽量避免使用goto。在需要集中进行资源清理的情况下,可以考虑使用goto。对于goto是谨慎使用,而不是滥用。
break语句
A "break" statement terminates execution of the innermost "for", "switch", or "select" statement within the same function.---“ break”语句终止同一函数中最内层的“ for”,“ switch”或“ select”语句的执行。
break语法:
func funcname() {
[label:]
for/select/switch {
break [label]
}
}示例1-break的基本使用场景
func main() {
//在for语句中的使用
for {
fmt.Println("测试 break")
break//跳出当前for循环
}
fmt.Println("for 语句结束")
//在select中使用 实际上在select中使用break(不带标签是没有任何意义的)
select {
case <-time.After(time.Second):
fmt.Println("一秒后退出")
break
case <-time.After(time.Second * 10):
fmt.Println("十秒后退出")
}
fmt.Println("select 语句结束")
//在switch中使用
switch {
case true:
fmt.Println("true")
fallthrough //模拟c语言中 switch
case false:
fmt.Println("false")
break //跳出switch不会在去执行接下来的fallthrough后面的分支
fallthrough
default:
fmt.Println("default")
break //分支中没有fallthrough使用单独的break(没有label是没有啥意义的,本身执行完就会结束switch)
}
fmt.Println("switch 语句结束")
}
/**
output:
测试 break
for 语句结束
一秒后退出
select 语句结束
true
false
switch 语句结束
*/可以看出:
1. 在select中使用break是没有用的(break没有标签)
2.在表达式switch中如果没有fallthought也是没有用的, 如果存在fallthough,那么break就会跳出switch循环。
示例2-break的进阶-带标签
func main() {
//在for语句中的使用
FOR:
for {
for {
fmt.Println("测试 break")
//break//跳出当前for循环,
break FOR //带标签break 跳出当前for语句块的上一个for语句块的中
}
}
fmt.Println("for 语句结束")
//在select中使用 实际上在select中使用break(不带标签是没有任何意义的)
SELECT:
for {
select {
case <-time.After(time.Second):
fmt.Println("一秒后退出")
//break 跳出select
break SELECT //带标签的break,实际上跳出到select外层的for语句块
case <-time.After(time.Second * 10):
fmt.Println("十秒后退出")
break
}
}
fmt.Println("select 语句结束")
//在switch中使用
SWITCH:
for {
for {
switch {
case true:
//fmt.Println("true")
//fallthrough //模拟c语言中 switch
break SWITCH //带标签的break,实际上跳出是作用域是 switch -> for -> for最外层的for语句
case false:
fmt.Println("false")
break //跳出switch不会在去执行接下来的fallthrough后面的分支
fallthrough
default:
fmt.Println("default")
break //分支中没有fallthrough使用单独的break(没有label是没有啥意义的,本身执行完就会结束switch)
}
}
fmt.Println("switch 语句结束")
}
/**
output:
测试 break
for 语句结束
一秒后退出
select 语句结束
switch 语句结束
*/以上例子可以看出:
1. 带标签的break,可以跳出多层select/for/ switch作用域。让break更加灵活,写法更加简单灵活,不需要使用控制变量一层一层跳出循环,没有带break的只能跳出当前语句块。
2. 同时goto语句使用标签和break使用标签也是有区别。break使用标签只是标记跳转到哪层作用域,并跳跃到被跳转的语句块后面执行,而goto使用标签是跳转指定代码行,并执行后续代码。
continue语句
A "continue" statement begins the next iteration of the innermost "for" loop at its post statement. The "for" loop must be within the same function.--“ continue”语句可以结束当前循环,开始下一次的循环迭代过程。 “ for”循环必须在同一函数内。
continue语法
func funcname() {
[label:]
for {
continue [label]
}
}示例1-continue基本使用
func main() {
for i := 0; i < 10; i++ {
if i % 2 != 0 {
fmt.Println(i)
} else {
continue //当i是偶数时跳过当次循环
}
}
}
/***
output:
1
3
5
7
9
*/
示例1-continue进阶使用带label
func main() {
j := 0
CONTINUEFOR: //跳转到和标签同级循环,并执行。
for j < 10 {
j++
fmt.Println("j = ", j)
for i := 0; i < 10; i++ {
if i % 2 != 0 {
fmt.Println(i)
} else {
//continue //当i是偶数时跳过当次循环
continue CONTINUEFOR
}
}
}
fmt.Println("====================")
CONTINUEBREAK: //跳转到和标签同级循环,并跳过同级作用域后继续执行
for j < 10 {
j++
fmt.Println("j = ", j) //不会打印,因为break已经跳转到CONTINUEBREAK,并跳过同级作用域。
for i := 0; i < 10; i++ {
if i % 2 != 0 {
fmt.Println(i)
} else {
break CONTINUEBREAK
}
}
}
fmt.Println("====================")
}由此可以看出:
1. continue带标签是跳转到标签同级的作用域(循环)后继续执行循环作用域。而break则是跳过同级作用域后继续执行。
2. continue只能用于for语句中。
3.continue带标签和goto带标签在循环中可以转换。-等价 (如下)
func main() {
j := 0
CONTINUEFOR: //跳转到和标签同级循环,并执行。
for j < 3 {
j++
fmt.Println("j = ", j)
for i := 0; i < 10; i++ {
if i % 2 != 0 {
fmt.Println(i)
} else {
//continue //当i是偶数时跳过当次循环
continue CONTINUEFOR
//goto CONTINUEFOR
}
}
}
fmt.Println("====================")
j = 0
GOTO: //跳转到和标签同级循环,并跳过同级作用域后继续执行
for j < 3 {
j++
fmt.Println("j = ", j)
for i := 0; i < 10; i++ {
if i % 2 != 0 {
fmt.Println(i)
} else {
//continue //当i是偶数时跳过当次循环
goto GOTO
}
}
}
fmt.Println("====================")
}
/**
output:
j = 1
j = 2
j = 3
====================
j = 1
j = 2
j = 3
====================
*/