Go基础编程 - 12 -流程控制

流程控制

  • [1. 条件语句](#1. 条件语句)
    • [1.1. if...else 语句](#1.1. if...else 语句)
    • [1.2. switch 语句](#1.2. switch 语句)
    • [1.3. select 语句](#1.3. select 语句)
      • [1.3.1. select 语句的通信表达式](#1.3.1. select 语句的通信表达式)
      • [1.3.2. select 的基特性](#1.3.2. select 的基特性)
      • [1.3.3. select 的实现原理](#1.3.3. select 的实现原理)
      • [1.3.4. 经典用法](#1.3.4. 经典用法)
        • [1.3.4.1 超时控制](#1.3.4.1 超时控制)
        • [1.3.4.2 多任务并发控制](#1.3.4.2 多任务并发控制)
        • [1.3.4.3 监听多通道消息](#1.3.4.3 监听多通道消息)
        • [1.3.4.4 default 实现非堵塞读写](#1.3.4.4 default 实现非堵塞读写)
  • [2. 循环语句](#2. 循环语句)
    • [2.1. for 语句](#2.1. for 语句)
    • [2.2. for ... range](#2.2. for ... range)
  • [3. goto、break、continue](#3. goto、break、continue)

上一篇:函数
下一篇:单元测试


1. 条件语句

Go语言中,条件语句分为三种:if语句、switch语句和select语句。

1.1. if...else 语句

if语句基本格式如下:

go 复制代码
if 条件 {
    // 条件满足时执行的语句
} else if 条件 {
    // 条件满足时执行的语句
} else {
    // 条件为false时执行的语句
}

不支持三元操作符(三目运算符) :"a > b ? a : b"

1.2. switch 语句

基于不同的条件执行不同的动作,每个case分支都是唯一的,从上至下逐一匹配,直到匹配到一个case分支,执行该分支的代码,并终止匹配。

switch语句基本格式如下:

go 复制代码
switch 值 {
case 值1:
    // 值等于值1时执行的语句
case 值2:
    // 值等于值2时执行的语句
default:
    // 值不等于值1和值2时执行的语句
}
  • 如果switch没有表达式,它会匹配true。
  • case 分支表达式可以是任意类型,不限于常量,但必须是相同类型
  • 一个 case 可以同时测试多个值,用逗号分隔。例如:case val1, val2, val3:
  • 一个 case 分支可以使用fallthrough语句,匹配成功后强制执行相邻的下一个case语句,且fallthrough不可使用在最后一个分支。
  • Go语言的switch默认相当于每个case最后带有break,可省略。

示例:

go 复制代码
package main

import "fmt"

func main() {

    var i = 0
    switch i {
    case 0:
        println("0")    
        println("fallthrough")  // 使用fallthrough语句,匹配成功后强制执行下一个case代码:case 1,2
        fallthrough
    case 1, 2:  // 一个 case 可以同时测试多个值,用逗号分隔。
        fmt.Println("1 或 2")
    case 3:
        fmt.Println("3")
    default:
        fmt.Println("default")
        // fallthrough  // fallthrough不可使用在最后一个分支
    }
    // 输出结果:
    // 0
    // fallthrough
    // 1 或 2

    var n = 6
    switch { //省略条件表达式默认为true,可当 if...else if...else
    case n > 0 && n < 10:
        fmt.Println("i > 0 and i < 10")
    case n > 10 && n < 20:
        fmt.Println("i > 10 and i < 20")
    default:
        fmt.Println("def")
    }
    // 输出结果:
    // i > 0 and i < 10
}

1.3. select 语句

select是Go中的一个控制结构,类似 switch 语句,用于处理异步IO操作。但是select用于等待多个通信操作的完成,会随机执行一个可运行的case;如果没有case可运行,它将阻塞,直到有case可运行。

1.3.1. select 语句的通信表达式

  • 一个通信操作,如:ch <- vv := <-ch
  • 一个接收表达式,如:v := <-ch
  • 一个发送表达式,如:ch <- v
  • 一个默认通信,如:default

1.3.2. select 的基特性

  • case 语句必须是一个 cannel 操作,要么是发送,要么是接收。
  • select 中 default 语句总是可执行的(一般不写在里面,因为会很消CPU资源)。
  • 如果任意某个通信可以执行,它就执行;其它被忽略。
  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行;其它不会执行。
  • 如果没有可运行的 case 语句,且有 default 语句,那么就会执行 default 的动作。
  • 如果没有可运行的 case 语句,且没有 default 语句,select 将阻塞,直到某个 case 通信可以运行。

1.3.3. select 的实现原理

参考:Go select 底层原理select 的随机公平策略理

无 case 永久堵塞

go 复制代码
select{} 

// fatal error: all goroutines are asleep - deadlock!
// 
// goroutine 1 [select (no cases)]:

select 所有 case 均无法执行且没有 default,则阻塞

go 复制代码
ch := make(chan struct{})
select {
case data <- ch:  // 只有一个 case,实际会被编译器转换为相应 channel 相应的收发操作,其实和实际调用 data := <- ch 并没有什么区别
    fmt.Printf("ch data: %v\n", data)
}

// fatal error: all goroutines are asleep - deadlock!
// 
// goroutine 1 [chan receive]:

select多个case同时可以执行,随机选择一个去执行

go 复制代码
package main

import "fmt"

func main() {
	var c1 = make(chan int, 2)
	c2 := make(chan string, 2)
	c3 := make(chan int, 2)
	var i1 int
	i2 := "two"

	c1 <- 1
	c2 <- "one"
	c3 <- 3

	select {
	case i1 = <-c1:
		fmt.Printf("received %d from c1\n", i1)
	case c2 <- i2:
		fmt.Printf("send %s to c2\n", i2)
	case i3, ok := <-c3: // same as: i3, ok := (<-c3)
		if ok {
			fmt.Printf("received %d from c3\n", i3)
		} else {
			fmt.Printf("c3 is closed\n")
		}
	default:
		fmt.Printf("no communication\n")
	}
}
//随机输出下面一条:
// send two to c2
// received 1 from c1
// received 3 from c3

1.3.4. 经典用法

1.3.4.1 超时控制
go 复制代码
package main

import (
   "fmt"
   "time"
)

func main() {
    ch := make(chan int)
    go func() {
        time.Sleep(3 * time.Second)
        ch <- 1
    }()

    select {
    case data, ok := <-ch:
        if ok {
            fmt.Println("接收到数据: ", data)
        } else {
            fmt.Println("通道已被关闭")
        }
    case <-time.After(2 * time.Second):
        fmt.Println("超时了!")
    }
}

// 超时了!
1.3.4.2 多任务并发控制
go 复制代码
package main

import (
	"fmt"
)

func main() {
	ch := make(chan int)

	for i := 0; i < 10; i++ {
		go func(id int) {
			ch <- id
		}(i)
	}

	for i := 0; i < 10; i++ {
		select {
		case data, ok := <-ch:
			if ok {
				fmt.Println("任务完成:", data)
			} else {
				fmt.Println("通道已被关闭")
			}
		}
	}
}

// 每次执行,顺序不一致
// 任务完成: 2
// 任务完成: 0
// 任务完成: 1
// 任务完成: 4
// 任务完成: 3
// 任务完成: 6
// 任务完成: 5
// 任务完成: 7
// 任务完成: 8
// 任务完成: 9
1.3.4.3 监听多通道消息
go 复制代码
package main

import (
   "fmt"
   "time"
)

func main() {
   ch1 := make(chan int)
   ch2 := make(chan int)

   // 开启 goroutine 1 用于向通道 ch1 发送数据
   go func() {
      for i := 0; i < 5; i++ {
         ch1 <- i
         time.Sleep(time.Second)
      }
   }()

   // 开启 goroutine 2 用于向通道 ch2 发送数据
   go func() {
      for i := 5; i < 10; i++ {
         ch2 <- i
         time.Sleep(time.Second)
      }
   }()

   // 主 goroutine 从 ch1 和 ch2 中接收数据并打印
   for i := 0; i < 10; i++ {
      select {
      case data := <-ch1:
         fmt.Println("Received from ch1:", data)
      case data := <-ch2:
         fmt.Println("Received from ch2:", data)
      }
   }

   fmt.Println("Done.")
}
1.3.4.4 default 实现非堵塞读写
go 复制代码
import (
   "fmt"
   "time"
)

func main() {
   ch := make(chan int, 1)

   go func() {
      for i := 1; i <= 5; i++ {
         ch <- i
         time.Sleep(1 * time.Second)
      }
      close(ch)
   }()

   for {
      select {
      case val, ok := <-ch:
         if ok {
            fmt.Println(val)
         } else {
            ch = nil
         }
      default:
         fmt.Println("No value ready")
         time.Sleep(500 * time.Millisecond)
      }
      if ch == nil {
         break
      }
   }
}

2. 循环语句

2.1. for 语句

Go语言 for 循环有 3 种形式。

go 复制代码
for init; condition; post{}
for condition {}
for {}

示例:

go 复制代码
package main

import "fmt"

func main() {

    for i := 0; i < 10; i++ {
        fmt.Println("i =", i)
    }
    fmt.Println()

    n := 10
    for n > 0 {
        fmt.Println("n =", n)
        n--
    }
    fmt.Println()

    j := 0
    for {
        if j >= 10 {
            break
        }
        fmt.Println("j =", j)
        j++
    }
    fmt.Println()
}

2.2. for ... range

range 类似迭代器操作,返回(索引, 值)或(键, 值)。

for ... range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

go 复制代码
for key, value := range oldMap {

}

示例:

go 复制代码
package main

import "fmt"

func main() {
    s := "string中文"

    for i, v := range s {
        fmt.Printf("%d, %d, %T\n", i, v, v)
    }
    println()

    // 忽略值
    for i := range s {
        fmt.Printf("%d\n", i)
    }

    // 忽略返回值,仅迭代
    for range s {

    }

    println()
    println()

    a := [3]int{0, 1, 2}
    for i, v := range a {
        if i == 0 {
            a[1], a[2] = 222, 333 // range 会复制对象,修改 a 不影响 i, v 的值
        }
        fmt.Println(i, v, a) // 输出:[0 222 333]
        a[i] = 100 + v
    }
    fmt.Println(a) // 输出:[100 101 102]

    println()
    println()

    si := []int{1, 2, 3, 4, 5}
    for i, v := range si {
        if i == 0 {
            si = si[:3] // 对 slice 的修改,不会影响 range
            si[2] = 100 // 对底层数据的修改,影响 range
        }

        fmt.Printf("%d, %d, %v\n", i, v, si)
    }
}

for...range 可以遍历 channel, 与遍历 map、slice 不同

go 复制代码
package main

import "fmt"

func main() {
    queue := make(chan string, 2)
    
    for i := 0; i < 10; i++ {
		 queue <- "-data-" + strconv.Itoa(i)
	}
    close(queue)

    // 这个 `range` 迭代从 `queue` 中得到的每个值。
    // 因为我们在前面 `close` 了这个通道,这个迭代会在接收完 queue 中的值之后结束;
    for elem := range queue {
        fmt.Println(elem)
    }
    // 如果我们没有 `close` 它,我们将在这个循环中继续阻塞执行,等待接收下一个个值。类似下面代码 for {}
    for {
    	fmt.Println(<- queue)
    }
}

3. goto、break、continue

break:跳出循环。
continue:跳过当前循环,继续下一次循环。仅限 for 循环内使用。
goto:通过label跳转到指定位置。

  • 三个标签都可以配合标签(label)使用。
  • continue, break 配合标签可用于多层循环跳出。
  • goto 是调整执行位置,跳到指定标签代码块执行;continue 配合标签为跳到指定循环继续执行, break 配合标签跳出指定标签代码块的循环。
go 复制代码
package main

import "Learing/demo"

func main() {
    // 标签区名分大小写,若定义标签不使用则会照成编译错误。
LabelBreak: // break 的跳转标签放在循环语句前面
    for {
        for i := 0; i < 10; i++ {
            if i > 2 {
                break LabelBreak // break, 跳出 LabelBreak 标签代码块,不再执行循环
            }
            fmt.Println("break -", i)
        }
    }
    fmt.Println()

    LableContinue:  // continue 的跳转标签放在循环语句前面
    for i := 0; i < 5; i++ {
        for {
            fmt.Println("before", i)
            continue LableContinue // continue, 跳到LableContinue标签的循环,继续执行循环。
            fmt.Println("after")
        }
    }
    fmt.Println("continue label")
    fmt.Println()

    for {
        for i := 0; i < 10; i++ {
            if i > 2 {
                goto LabelGoto // goto, 跳转到指定标签代码块
            }
            fmt.Println("goto -", i)
        }
    }
LabelGoto:
    fmt.Println("goto label")
    fmt.Println()
}

func SelectBreak() {
    ch := make(chan int, 2)

    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
        }
    }()

Label:
    for {
        select {
        case v, ok := <-ch:
            fmt.Println(ok, v)
            if v == 5 {
                break Label // 在 for 的 select 体中 break 到外层循环
            }
        default:
            fmt.Println("The ch value is not ready.")
            time.Sleep(500 * time.Millisecond)
        }
    }

    fmt.Println("Break for select")
}
相关推荐
__AtYou__5 小时前
Golang | Leetcode Golang题解之第563题二叉树的坡度
leetcode·golang·题解
凡人的AI工具箱12 小时前
15分钟学 Go 第 49 天 :复杂项目开发
开发语言·人工智能·后端·算法·golang
杜杜的man13 小时前
【go从零单排】Random Numbers、Number Parsing
开发语言·python·golang
aiee15 小时前
Golang时间函数
开发语言·后端·golang
还是转转16 小时前
Go开发指南- Goroutine
开发语言·golang
蜗牛沐雨16 小时前
Go语言中的`io.Pipe`:实现进程间通信的利器
开发语言·后端·golang·进程通信·pipe
杜杜的man16 小时前
【go从零单排】泛型(Generics)、链表
开发语言·链表·golang
杜杜的man16 小时前
【go从零单排】JSON序列化和反序列化
golang·json
wuh233316 小时前
golang-基础知识(函数)
开发语言·后端·golang
杜杜的man17 小时前
【go从零单排】XML序列化和反序列化
xml·开发语言·golang