Go-基础回顾-复合数据类型-流程控制

174 阅读5分钟

流程控制

  • if
  • switch
  • for
  • range
  • select
  • goto,break,continue

提示

  • Go中不支持三目运算符
  • 不支持while

if

  • if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
  • 花括号不能换行
  • 支持多层嵌套

语法

  • 可省略条件表达式括号。
  • 持初始化语句,可定义代码块局部变量。
  • 代码块左 括号必须在条件表达式尾部。
if 布尔表达式 {
    /* 在布尔表达式为 true 时执行 */
}     

代码示例

if n := "abc"; x > 0 {     // 初始化语句未必就是定义变量, 如 println("init") 也是可以的。
    println(n[2])
} else if x < 0 {    // 注意 else if 和 else 左大括号位置。
    println(n[1])
} else {
    println(n[0])
}     

switch

  • switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止。
  • Golang switch 分支表达式可以是任意类型,不限于常量。==可省略 break==,默认自动终止。

语法

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。 您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。

/* 定义局部变量 */
var grade string = "B"
var marks int = 90

switch marks {
  case 90: grade = "A"
  case 80: grade = "B"
  case 50,60,70 : grade = "C"
  default: grade = "D"  
}

switch {
  case grade == "A" :
     fmt.Printf("优秀!\n" )     
  case grade == "B", grade == "C" :
     fmt.Printf("良好\n" )      
  case grade == "D" :
     fmt.Printf("及格\n" )      
  case grade == "F":
     fmt.Printf("不及格\n" )
  default:
     fmt.Printf("差\n" )
}
fmt.Printf("你的等级是 %s\n", grade )

Stdout
优秀!
你的等级是 A  

其他用法 - Type Switch

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

语法
switch x.(type){
    case type:
       statement(s)      
    case type:
       statement(s)
    /* 你可以定义任意个数的case */
    default: /* 可选 */
       statement(s)
}
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("未知型")
}

for

for循环是一个循环控制结构,可以执行指定次数的循环。

语法

for init; condition; post { } for condition { } for { } init: 一般为赋值表达式,给控制变量赋初值; condition: 关系表达式或逻辑表达式,循环控制条件; post: 一般为赋值表达式,给控制变量增量或减量。 for语句执行过程如下: ①先对表达式 init 赋初值; ②判别赋值表达式 init 是否满足给定 condition 条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。

s := "abcd"

for i, n := 0, len(s); i < n; i++ { // 常见的 for 循环,支持初始化语句。
    println(s[i])
}

n := len(s)
for n > 0 {              // 替代 while (n > 0) {}
    n-- 
    println(s[n])        // 替代 for (; n > 0;) {}
}

i := 0
for  {                  // 替代 while (true) {}
	println(s)          // 替代 for (;;) {}
	if i > 10 {
		break
	}
	i = i + 1
}

for true {              // 替代 while (true) {}
    
}

提示:在初始化语句中计算出全部结果

func length(s string) int {
    println("call length.")
    return len(s)
}

func main() {
    s := "abcd"

    for i, n := 0, length(s); i < n; i++ {     // 避免多次调用 length 函数。
        println(i, s[i])
    } 
}

range

  • for range 支持对数组、切片、字符串、map、通道进行遍历操作。
    • 数组、切片、字符串返回索引和值。
    • map 返回键和值。
    • 通道(channel)只返回通道内的值。
  • 在需要时,可以使用匿名变量对 for range 的变量进行选取。
语法
for key,val := range string|array|map|chan {
    
}

==val==始终为集合中对应索引的==值拷贝==,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值。一个字符串是 Unicode 编码的字符(或称之为 rune )集合,因此也可以用它来迭代字符串

示例
s := "abc"
// 忽略 value,支持 string/array/slice/map。
for key := range s {
	println(s[key])
}
// 忽略 index。
for _, value := range s {
	println(value)
}
// 忽略全部返回值,仅迭代。
for range s {

}
// map
m := map[string]int{"a": 1, "b": 2}
// 返回 (key, value)。
for key, value := range m {
	println(key, value)
}
// 忽略 value
for key := range m {
	println(m[key])
}

// chan
c := make(chan int)
go func() {
    c <- 1
    c <- 2
    c <- 3
    close(c)
}()
for v := range c {
    fmt.Println(v)
}

提示 map 是无序的,因此多次遍历的结果顺序可能不一致

select

  • 类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
  • select 是Go中的一种控制结构,类似用于通信的switch语句。每个case必须是==IO操作==,要么是发送要么是接收。

语法

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

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

Select语句和deadlock死锁

我们将学习如何使用默认情况来避免死锁。但是首先,我们了解什么是deadlock?

死锁:当您试图从通道读取或写入数据但通道没有值时。因此,它阻塞goroutine的当前执行,并将控制传递给其他goroutine,但是如果没有其他goroutine可用或其他goroutine睡眠,由于这种情况,程序将崩溃。这种现象称为死锁。

示例 1 - 没有其他Goroutine参与

func main() {

    //创建通道
    //出现死锁是因为没有goroutine在写
    //因此,select语句被永远阻塞
    c := make(chan int)
    select {
    case <-c:
    }
}

Stdout
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()

示例 2 - 当select语句只有nil通道时,也允许使用默认情况。如下面的实例所示,通道c是nil,所以默认情况下执行,如果这里的默认情况是不可用的,那么程序将永远被阻塞,死锁出现。

func main() {

    //创建通道
    var c chan int

    select {
    case x1 := <-c:
        fmt.Println("Value: ", x1)
    default:
        fmt.Println("Default case..!")
    }
}

这里还是有疑问,表象上看似通了,但是到底为什么会造成deadlock呢?

参考