【Go】Go 的条件语句:if、switch、select


一、Go语言流程控制

流程控制是每种编程语言控制逻辑走向和执行次序的重要部分,流程控制可以说是一门语言的“经脉”。

Go 语言的常用流程控制有 if 和 for,而 switch 和 goto 主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。

本文介绍 Go 语言中的基本流程控制语句中的条件/分支语句(if、switch、select)。循环(for)和跳转(goto)语句、循环控制语句(break 和 continue)的介绍请参考我的下一篇文章:Go 语言的循环语句


二、Go 的条件语句

条件语句通过测试一个或多个条件是否为 true 来决定是否执行指定语句,如果条件是 true 则执行本块语句,并在条件为 false 的情况在执行另外的语句。

Go 语言提供了以下几种条件判断语句:
在这里插入图片描述

接下来将一一讲解。


三、Go 的 if else

1. 语法

在Go语言中,关键字 if 是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行 if 后由大括号 {} 括起来的代码块,否则就忽略该代码块继续执行后续的代码。

if condition {
    
    
    // do something
}

如果存在第二个分支,则可以在上面代码的基础上添加 else 关键字以及另一代码块,这个代码块中的代码只有在条件不满足时才会执行,if 和 else 后的两个代码块是相互独立的分支,只能执行其中一个。

if condition {
    
    
    // do something
} else {
    
    
    // do something
}

如果存在第三个分支,则可以使用下面这种三个独立分支的形式:

if condition1 {
    
    
    // do something
} else if condition2 {
    
    
    // do something else
}else {
    
    
    // catch-all or default
}

注意:

  1. else if 分支的数量是没有限制的,但是为了代码的可读性,还是不要在 if 后面加入太多的 else if 结构,如果必须使用这种形式,则尽可能把先满足的条件放在前面。

  2. 关键字 if 和 else 之后的左大括号 { 必须和关键字在同一行,如果你使用了 else if 结构,则前段代码块的右大括号 } 必须和 else if 关键字在同一行,这两条规则都是被编译器强制规定的。

  3. 在使用 gofmt 格式化代码之后,每个分支内的代码都会缩进 4 个或 8 个空格,或者是 1 个 tab,并且右大括号 } 与对应的 if 关键字垂直对齐。

  4. 在有些情况下,条件语句两侧的括号是可以被省略的,当条件比较复杂时,则可以使用括号让代码更易读,在使用 &&、|| 或 ! 时可以使用括号来提升某个表达式的运算优先级,并提高代码的可读性。

2. 一种特殊写法

if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,代码如下:

if err := Connect(); err != nil {
    
    
    fmt.Println(err)
    return
}

Connect 是一个带有返回值的函数,err:=Connect() 是一个语句,执行 Connect 后,将错误保存到 err 变量中。

err != nil 才是 if 的判断表达式,当 err 不为空时,打印错误并返回。

这种写法可以将返回值与判断放在一行进行处理,而且返回值的作用范围被限制在 if、else 语句组合中。

多一句话:
在编程中,变量的作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助。

总结:

  1. Go 编程语言中 if 语句的语法如下:

    • 可省略条件表达式括号。
    • 持初始化语句,可定义代码块局部变量。
    • 代码块左 括号必须在条件表达式尾部。
    if [执行语句;] 布尔表达式 {
          
          
    /* 在布尔表达式为 true 时执行 */
    }
    
  2. if 嵌套语句:你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。


四、Go 的 switch 语句

1. switch 语法

(1)switch 的执行规则

没错,如上图所示,switch 语句在满足不同条件时执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止(遇到匹配后则跳出,不再执行后面的语句)。

switch 语句执行的过程从上至下,直到找到匹配项。switch 默认情况下 case 到匹配项后,自带 break 语句(所以我们不需要显式地写 break),匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough

不同的 case 之间不需要使用 break 分隔,Go 默认每一个case 之后都有一个break (只是没有写出来),Go 只会执行一个 case。如果想要执行多个 case,需要使用 fallthrough 关键字。

(2)switch 的语法

switch var1(变量) {
    
    
    case value1:
        ...
    case value2:
        ...
    default:
        ...
}

var1 是一个变量,可以是任何数据类型。
val1 和 val2 可以是 同类型 的任意值。数据类型不限,但必须是相同的类型;或者最终结果为相同类型的表达式。

多条件匹配:
在一个case语句下可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:

case value1, value2, value3:

2. Type Switch

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

Type Switch 语法格式如下:

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\r\n", 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. fallthrough

使用 fallthrough 会强制执行后面一句 case 语句下的程序体:不会判断下一条 case 的表达式结果是否为 true,而是直接执行。

例:

package main

import "fmt"

func main() {
    
    

	switch {
    
    
	case false:
		fmt.Println("1、case 条件语句为 false")
		fallthrough
	case true:      //匹配,从这里开始进入case
		fmt.Println("2、case 条件语句为 true")
		fallthrough
	case false:     //这一句被强制执行
		fmt.Println("3、case 条件语句为 false")
		fallthrough
	case true:      //这一句被强制执行,执行结束后由于没有fallthrough,默认break跳出
		fmt.Println("4、case 条件语句为 true")
	case false:
		fmt.Println("5、case 条件语句为 false")
		fallthrough
	default:
		fmt.Println("6、默认 case")
	}
}

输出结果:

2case 条件语句为 true
3case 条件语句为 false
4case 条件语句为 true

分析:switch 从第一个判断表达式为 true 的 case 开始执行,如果 case 带有 fallthrough,程序会继续执行下一条 case,且它不会去判断下一个 case 的表达式是否为 true。break跳出后不会再执行其他语句。


五、Go 的 select 语句

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。

select 随机 执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

1. 语法

Go 编程语言中 select 语句的语法如下:

select {
    
    
    case communication clause  :
       statement(s);      
    case communication clause  :
       statement(s);
    /* 你可以定义任意数量的 case */
    default : /* 可选 */
       statement(s);
}

以下描述了 select 语句的语法:

  1. 每个 case 都必须是一个通信
  2. 所有 channel 表达式都会被求值
  3. 所有被发送的表达式都会被求值
  4. 如果任意某个通信可以进行,它就执行,其他被忽略。
  5. 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
    否则:
    如果有 default 子句,则执行该语句。
    如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。

实例:

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

2. select 和 switch 比较:

  1. select可以监听channel的数据流动
  2. select的用法与switch语法非常类似,由select开始的一个新的选择块,每个选择条件由case语句来描述
  3. 与switch语句可以选择任何使用相等比较的条件相比,select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作
select {
    
     //不停的在这里检测
	case <-chanl : //检测有没有数据可以读
	//如果chanl成功读取到数据,则进行该case处理语句
	case chan2 <- 1 : //检测有没有可以写
	//如果成功向chan2写入数据,则进行该case处理语句
	
	
	//假如没有default,那么在以上两个条件都不成立的情况下,就会在此阻塞//一般default会不写在里面,select中的default子句总是可运行的,因为会很消耗CPU资源
	default:
	//如果以上都没有符合条件,那么则进行default处理流程
}

在一个select语句中,Go会按顺序从头到尾评估每一个发送和接收的语句。

如果其中的任意一个语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。 如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况: ①如果给出了default语句,那么就会执行default的流程,同时程序的执行会从select语句后的语句中恢复。 ②如果没有default语句,那么select语句将被阻塞,直到至少有一个case可以进行下去。

3. Go中select的典型用法

select是Go中的一个控制结构,类似于switch语句,用于 处理异步IO 操作。select会监听case语句中channel的读写操作,当case中channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。 select中的case语句必须是一个channel操作。

select中的default子句总是可运行的。

如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行。

如果没有可运行的case语句,且有default语句,那么就会执行default的动作。

如果没有可运行的case语句,且没有default语句,select将阻塞,直到某个case通信可以运行。

典型用法:

  1. 超时判断
//比如在下面的场景中,使用全局resChan来接受response,如果时间超过3S,resChan中还没有数据返回,则第二条case将执行
var resChan = make(chan int)
// do request
func test() {
    
    
    select {
    
    
    case data := <-resChan:
        doData(data)
    case <-time.After(time.Second * 3):
        fmt.Println("request time out")
    }
}

func doData(data int) {
    
    
    //...
}
  1. 退出
//主线程(协程)中如下:
var shouldQuit=make(chan struct{
    
    })
fun main(){
    
    
    {
    
    
        //loop
    }
    //...out of the loop
    select {
    
    
        case <-c.shouldQuit:
            cleanUp()
            return
        default:
        }
    //...
}

//再另外一个协程中,如果运行遇到非法操作或不可处理的错误,就向shouldQuit发送数据通知程序停止运行
close(shouldQuit)
  1. 判断channel是否阻塞
//在某些情况下是存在不希望channel缓存满了的需求的,可以用如下方法判断
ch := make (chan int, 5)
//...
data:=0
select {
    
    
case ch <- data:
default:
    //做相应操作,比如丢弃data。视需求而定
}

参考链接

  1. Go 语言 switch 语句
  2. Go语言流程控制
  3. Go语言if else(分支结构)
  4. 条件语句select

猜你喜欢

转载自blog.csdn.net/weixin_44211968/article/details/121241719