golang-08 | 分支语句

198 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

引言

河神:年轻的小伙子哟,你掉的是这个金斧头,还是这个银斧头,还是这个铁斧头呢

在我们的生活中会面临很多的抉择,而在程序的执行过程中,也是如此。

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

if 我的斧头等于金斧头 {
    我掉的是金斧头
}else if 我的斧头等于银斧头{
    我掉的是银斧头
}else{
    我掉的是铁斧头
}

使用

在go中分支语句共有5种,上述的例子中拆开来看就包含了3种:if ...if ... else ...if...else if ... else ...,另外的2种分别是:switchselect

switch

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。

switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。

switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用fallthrough

switch var1 {
    case val1:
        ...
    case val2:
        ...
        fallthrough
    default:
        ...
}
package main
​
import "fmt"func main() {
    /* 定义局部变量 */
    ax := "银斧头"
    var res stringswitch ax {
    case "金斧头":
        res = "金斧头"
    case "银斧头":
        res = "银斧头"
        fallthrough
    case "铁斧头":
        res = "铁斧头"
    default:
        res = "2块钱"
    }
    fmt.Printf("我丢的是:%s,但是我收回的是:%s", ax, res)
}
​
/*
我丢的是:银斧头,但是我收回的是:铁斧头
*/

原本我是该收回银斧头,但是遇到了气势比较强硬的fallthrough使得我只能拿回铁斧头。

select

select语句是用于在通道中进行操作的分支语句(通道详情请查阅 ”12 | 通道“),它⼀般由若⼲个分⽀组成。

每次执⾏这种语句的时候,⼀般只有⼀个分⽀中的代码会被运⾏。

select 语句的分⽀分为两种,⼀种叫做候选分⽀(case),另⼀种叫做默认分⽀(default)。

  • 候选分⽀总是以关键字 case 开头,后 跟⼀个 case 表达式和⼀个冒号,然后我们可以从下⼀⾏开始写⼊当分⽀被选中时需要执⾏的语句。
  • 默认分⽀其实就是default case,因为,当且仅当没有候选分⽀被选中时它才会被执⾏,所以它以关键字 default 开头并直接后跟⼀个冒号。同样的,我们可以在 default: 的下⼀⾏写⼊要执⾏的语句。

由于 select 语句是专为通道⽽设计的,所以每个 case 表达式中都只能包含操作通道的表达式,⽐如接收表达式。 当然,如果我们需要把接收表达式的结果赋给变量的话,还可以把这⾥写成赋值语句或者短变量声明。

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")
   }    
}

在使⽤ select 语句的时候,我们⾸先需要注意下⾯⼏个事情:

  • 如果像上述示例那样加⼊了默认分⽀,那么⽆论涉及通道操作的表达式是否有阻塞, select 语句都不会被阻塞。如果那⼏个表达式都阻塞了,或者说都没有满⾜求值的条件,那么默认分⽀就会被选中并执⾏。

  • 如果没有加⼊默认分⽀,那么⼀旦所有的 case 表达式都没有满⾜求值条件,那么 select 语句就会被阻塞。 直到⾄少有⼀个 case 表达式满⾜条件为⽌。

  • 可能会因为通道关闭了,⽽直接从通道接收到⼀个其元素类型的零值。所以,在很多时候,需要通过接收表达式的第⼆个结果值来判断通道是否已经关闭。⼀旦发现某个通道关闭了,就应该及时地屏蔽掉对应的分⽀或者采取其他措施。这对于程序逻辑和程序性能都是有好处的。

  • select 语句只能对其中的每⼀个 case 表达式各求值⼀次。所以,如果想连续或定时地操作其中的通道 的话,就往往需要通过在 for 语句中嵌⼊ select 语句的⽅式实现。但这时要注意,简单地在 select 语句的 分⽀中使⽤ break 语句,只能结束当前的 select 语句的执⾏,⽽并不会对外层的 for 语句产⽣作⽤。这种 错误的⽤法可能会让这个 for 语句⽆休⽌地运⾏下去

    select 语句的分⽀选择规则:

  • 对于每⼀个 case 表达式,都⾄少会包含⼀个代表发送操作的发送表达式或者⼀个代表接收操作的接收表达式,同时也可能会包含其他的表达式。⽐如,如果 case 表达式是包含了接收表达式的短变量声明时,那么在赋值符号左边的就可以是⼀个或两个表达式,不过此处的表达式的结果必须是可以被赋值的。当这样的 case 表达式被求值时,它包含的多个表达式总会以从左到右的顺序被求值。

  • select 语句包含的候选分⽀中的 case 表达式都会在该语句执⾏开始时先被求值,并且求值的顺序是依从代 码编写的顺序从上到下的。结合上⼀条规则,在 select 语句开始执⾏时,排在最上边的候选分⽀中最左边的 表达式会最先被求值,然后是它右边的表达式。仅当最上边的候选分⽀中的所有表达式都被求值完毕后,从上 边数第⼆个候选分⽀中的表达式才会被求值,顺序同样是从左到右,然后是第三个候选分⽀、第四个候选分⽀,以此类推。

  • 对于每⼀个 case 表达式,如果其中的发送表达式或者接收表达式在被求值时,相应的操作正处于阻塞状态, 那么对该 case 表达式的求值就是不成功的。在这种情况下,我们可以说,这个 case 表达式所在的候选分⽀ 是不满⾜选择条件的。

  • 仅当 select 语句中的所有 case 表达式都被求值完毕后,它才会开始选择候选分⽀。这时候,它只会挑选满 ⾜选择条件的候选分⽀执⾏。如果所有的候选分⽀都不满⾜选择条件,那么默认分⽀就会被执⾏。如果这时没有默认分⽀,那么 select 语句就会⽴即进⼊阻塞状态,直到⾄少有⼀个候选分⽀满⾜选择条件为⽌。⼀旦有⼀个候选分⽀满⾜选择条件, select 语句(或者说它所在的goroutine)就会被唤醒,这个候选分⽀就会被 执⾏。

  • 如果 select 语句发现同时有多个候选分⽀满⾜选择条件,那么它就会⽤⼀种伪随机的算法在这些分⽀中选择⼀个并执⾏。注意,即使 select 语句是在被唤醒时发现的这种情况,也会这样做。

  • ⼀条 select 语句中只能够有⼀个默认分⽀。并且,默认分⽀只在⽆候选分⽀可选时才会被执⾏,这与它的编 写位置⽆关。

  • select 语句的每次执⾏,包括 case 表达式求值和分⽀选择,都是独⽴的。不过,⾄于它的执⾏是否是并发安全的,就要看其中的 case 表达式以及分⽀中,是否包含并发不安全的代码了