Go开发 之 流程控制(if/else、for/range、switch、goto、break、continue)

0、唠唠叨叨

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

本章主要介绍 Go 语言中的基本流程控制语句,包括分支语句(if 和 switch)、循环(for)和跳转(goto)语句。另外,还有循环控制语句(break 和 continue),break 的功能是中断循环或者跳出 switch 判断,continue 的功能是继续 for 的下一个循环。

1、分支结构(if / else)

1.1、标准写法

标准格式:

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

无效格式:

if x{
}
else { // 无效的
}

1.2、特殊写法

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

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

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

Connect 是一个带有返回值的函数,err:=Connect() 是一个语句,执行 Connect 后,将错误保存到 err 变量中。err != nil 才是 if 的判断表达式,当 err 不为空时,打印错误并返回。

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

2、循环结构(for)

Go语言中的循环语句只支持 for 关键字,而不支持while 和 do-while 结构。

2.1、标准循环

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

2.2、无限循环

sum := 0
for {
    sum++
    if sum > 100 {
        break
    }
}

2.3、for 中的初始语句(开始循环时执行的语句)

初始语句是在第一次循环前执行的语句,一般使用初始语句执行变量初始化,如果变量在此处被声明,其作用域将被局限在这个 for 的范围内。初始语句可以被忽略,但是初始语句之后的分号必须要写,例如:

step := 2
for ; step > 0; step-- {
    fmt.Println(step)
}

这段代码将 step 放在 for 的前面进行初始化,for 中没有初始语句,此时 step 的作用域就比在初始语句中声明 step 要大。

2.4、for 中的结束语句(循环结束时执行的语句)

在结束每次循环前执行的语句,如果循环被 break、goto、return、panic 等语句强制退出,结束语句不会被执行。

2.5、for 中的条件表达式(控制是否循环的开关)

2.5.1、结束循环时带可执行语句的无限循环

下面代码忽略条件表达式,但是保留结束语句,例如:

var i int
for ; ; i++ {
    if i > 10 {
        break
    }
}

2.5.2、无限循环

上面的代码还可以改写为更美观的写法。

var i int
for {
    if i > 10 {
        break
    }
    i++
}

无限循环在收发处理中较为常见,但需要无限循环有可控的退出方式来结束循环。

2.5.3、只有一个循环条件的循环

满足条件表达式时持续循环,否则结束循环。

var i int
for i <= 10 {
    i++
}

上述代码其实类似于其他编程语言中的 while,在 while 后添加一个条件表达式。

3、键值循环结构(for range)

3.1、标准循环

在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道(channel),for range 语法上类似于其它语言中的 foreach 语句,一般形式为:

for key, val := range coll { // for pos, char := range str {
    ...
}

一个字符串是 Unicode 编码的字符(或称之为 rune )集合,因此也可以用它来迭代字符串。每个 rune 字符和索引在 for range 循环中是一一对应的,它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。

通过 for range 遍历的返回值有一定的规律:

  • 数组、切片、字符串返回索引和值。
  • map 返回键和值。
  • 通道(channel)只返回通道内的值。

3.2、遍历数组、切片(获得索引和值)

在遍历代码中,key 和 value 分别代表切片的下标及下标对应的值,数组也是类似的遍历方法:

for key, value := range []int{1, 2, 3, 4} {
    fmt.Printf("key:%d  value:%d\n", key, value)
}

代码输出如下:

key:0  value:1
key:1  value:2
key:2  value:3
key:3  value:4 

3.3、遍历字符串(获得字符)

var str = "hello 你好"
for key, value := range str {
    fmt.Printf("key:%d value:0x%x\n", key, value)
}

代码输出如下:

key:0 value:0x68
key:1 value:0x65
key:2 value:0x6c
key:3 value:0x6c
key:4 value:0x6f
key:5 value:0x20
key:6 value:0x4f60
key:9 value:0x597d

代码中的变量 value,实际类型是 rune 类型,以十六进制打印出来就是字符的编码。

3.4、遍历 map(获得 map 的键和值)

m := map[string]int{
    "hello": 100,
    "world": 200,
}
for key, value := range m {
    fmt.Println(key, value)
}

代码输出如下:

hello 100
world 200

对 map 遍历时,遍历输出的键值是无序的,如果需要有序的键值对输出,需要对结果进行排序。

3.5、遍历通道 channel(接收通道数据)

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

结果:
在这里插入图片描述

3.6、在遍历中选择希望获得的变量

m := map[string]int{
    "hello": 100,
    "world": 200,
}
for _, value := range m { // "_"可以理解为一种占位符,匿名变量本身不会进行空间分配,也不会占用一个变量的名字
    fmt.Println(value)
}

代码输出如下:

100
200

再看一个匿名变量的例子:

for key, _ := range []int{1, 2, 3, 4} {
    fmt.Printf("key:%d \n", key)
}

代码输出如下:

key:0
key:1
key:2
key:3

4、分支结构(switch / case)

Go语言的 switch 表达式不需要为常量,甚至不需要为整数,case 按照从上到下的顺序进行求值,直到找到匹配的项,如果 switch 没有表达式,则对 true进行匹配,因此,可以将 if else-if else 改写成一个 switch。

4.1、基本写法

Go语言改进了 switch 的语法设计,case 与 case 之间是独立的代码块,不需要通过 break 语句跳出当前 case 代码块以避免执行到下一行。每个 switch 只能有一个 default 分支。例如:

var a = "hello"
switch a {
case "hello":
    fmt.Println(1)
case "world":
    fmt.Println(2)
default:
    fmt.Println(0)
}

4.2、一分支多值

不同的 case 表达式使用逗号分隔。

var a = "mum"
switch a {
case "mum", "daddy":
    fmt.Println("family")
}

4.3、分支表达式

var r int = 11
switch {
case r > 10 && r < 20:
    fmt.Println(r)
}

4.4、跨越 case 的 fallthrough(兼容C语言的 case 设计)

var s = "hello"
switch {
case s == "hello":
    fmt.Println("hello")
    fallthrough
case s != "world":
    fmt.Println("world")
}

结果:

hello
world

新编写的代码,不建议使用 fallthrough。

5、跳转结构(break / continue / goto)

5.1、跳出制定循环 break

package main
import "fmt"
func main() {
OuterLoop:
    for i := 0; i < 2; i++ {
        for j := 0; j < 5; j++ {
            switch j {
            case 2:
                fmt.Println(i, j)
                break OuterLoop
            case 3:
                fmt.Println(i, j)
                break OuterLoop
            }
        }
    }
}

结果:

0 2

5.2、继续下一次循环 continue

package main
import "fmt"
func main() {
OuterLoop:
    for i := 0; i < 2; i++ {
        for j := 0; j < 5; j++ {
            switch j {
            case 2:
                fmt.Println(i, j)
                continue OuterLoop
            }
        }
    }
}

结果:

0 2
1 2

5.3、跳转到指定的标签 goto

goto 语句通过标签进行代码间的无条件跳转,同时 goto 语句在快速跳出循环、避免重复退出上也有一定的帮助,使用 goto 语句能简化一些代码的实现过程。

5.3.1、使用 goto 退出多层循环

原始代码:

package main
import "fmt"
func main() {
    var breakAgain bool
    // 外循环
    for x := 0; x < 10; x++ {
        // 内循环
        for y := 0; y < 10; y++ {
            // 满足某个条件时, 退出循环
            if y == 2 {
                // 设置退出标记
                breakAgain = true
                // 退出本次循环
                break
            }
        }
        // 根据标记, 还需要退出一次循环
        if breakAgain {
                break
        }
    }
    fmt.Println("done")
}

经过 goto 后优化:

package main
import "fmt"
func main() {
    for x := 0; x < 10; x++ {
        for y := 0; y < 10; y++ {
            if y == 2 {
                // 跳转到标签
                goto breakHere
            }
        }
    }
    // 手动返回, 避免执行进入标签
    return
    // 标签
breakHere:
    fmt.Println("done")
}

使用 goto 语句后,无须额外的变量就可以快速退出所有的循环。

5.3.2、使用 goto 集中处理错误

err := firstCheckError()
if err != nil {
    fmt.Println(err)
    exitProcess()
    return
}
err = secondCheckError()
if err != nil {
    fmt.Println(err)
    exitProcess()
    return
}
fmt.Println("done")

使用 goto 后:

	err := firstCheckError()
    if err != nil {
        goto onExit
    }
    err = secondCheckError()
    if err != nil {
        goto onExit
    }
    fmt.Println("done")
    return
onExit:
    fmt.Println(err)
    exitProcess()

6、示例

6.1、九九乘法表

package main
import "fmt"

var Info string = "沙师弟 -- 九九乘法表"

func main() {
	fmt.Println(Info)
	fmt.Println()
	// 遍历, 决定处理第几行
	for y := 1; y <= 9; y++ {
		// 遍历, 决定这一行有多少列
		for x := 1; x <= y; x++ {
			fmt.Printf("%d*%d=%d ", x, y, x*y)
		}
		// 手动生成回车
		fmt.Println()
	}
}

结果:
在这里插入图片描述

6.2、冒泡排序

package main
import "fmt"

var Info string = "沙师弟 -- 冒泡排序"

func main() {
	arr := [...]int{11,12,42,83,34,34,47,54}
	var n = len(arr)
	fmt.Println(Info)
	fmt.Println()
	fmt.Println("--------没排序前--------\n",arr)
	for i := 0; i <= n-1; i++ {
		fmt.Println("--------第",i+1,"次冒泡--------")
		for j := i; j <= n-1; j++ {
			if arr[i] > arr[j] {
				t := arr[i]
				arr[i] = arr[j]
				arr[j] = t
			}
			fmt.Println(arr)
		}
	}
	fmt.Println("--------最终结果--------\n",arr)
}

结果:

沙师弟 -- 冒泡排序

--------没排序前--------
[11 12 42 83 34 34 47 54]
--------1 次冒泡--------
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
--------2 次冒泡--------
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
--------3 次冒泡--------
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 34 83 42 34 47 54]
[11 12 34 83 42 34 47 54]
[11 12 34 83 42 34 47 54]
[11 12 34 83 42 34 47 54]
--------4 次冒泡--------
[11 12 34 83 42 34 47 54]
[11 12 34 42 83 34 47 54]
[11 12 34 34 83 42 47 54]
[11 12 34 34 83 42 47 54]
[11 12 34 34 83 42 47 54]
--------5 次冒泡--------
[11 12 34 34 83 42 47 54]
[11 12 34 34 42 83 47 54]
[11 12 34 34 42 83 47 54]
[11 12 34 34 42 83 47 54]
--------6 次冒泡--------
[11 12 34 34 42 83 47 54]
[11 12 34 34 42 47 83 54]
[11 12 34 34 42 47 83 54]
--------7 次冒泡--------
[11 12 34 34 42 47 83 54]
[11 12 34 34 42 47 54 83]
--------8 次冒泡--------
[11 12 34 34 42 47 54 83]
--------最终结果--------
[11 12 34 34 42 47 54 83]

6.3、二分查找

package main
import "fmt"

var Info string = "沙师弟 -- 二分查找法"

//二分查找函数  假设有序数组的顺序是从小到大(很关键,决定左右方向)
func BinaryFind(arr *[]int, leftIndex int, rightIndex int, findVal int)  {
	if leftIndex > rightIndex { //判断 leftIndex是否大于rightIndex
		fmt.Println("找不到")
		return
	}
	middle := (leftIndex + rightIndex) / 2 //先找到 中间的下标
	if (*arr)[middle] > findVal {
		BinaryFind(arr, leftIndex, middle-1, findVal) //要查找的数,范围应该在 leftIndex 到 middle+1
	} else if (*arr)[middle] < findVal {
		BinaryFind(arr, middle+1, rightIndex, findVal) //要查找的数,范围应该在 middle+1 到 rightIndex
	} else {
		fmt.Printf("找到了,下标为:%v \n", middle)
	}
}
func main() {
	fmt.Println(Info)
	fmt.Println()
	//定义一个数组
	arr := []int{2, 3, 6, 8, 11, 22, 31, 36, 39, 54, 67, 76, 80, 81, 85, 91, 94, 98}
	BinaryFind(&arr, 0, len(arr) - 1, 36)
	fmt.Println("main arr :",arr)
}

结果:
在这里插入图片描述

6.4、简易聊天机器人

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

var Info string = "沙师弟 -- 简易聊天机器人"

func main() {
	fmt.Println(Info)
	fmt.Println()
	// 准备从标准输入读取数据。
	inputReader := bufio.NewReader(os.Stdin)
	fmt.Println("请输入你的名字:")
	// 读取数据直到碰到 \n 为止。
	input, err := inputReader.ReadString('\n')
	if err != nil {
		fmt.Printf("异常退出: %s\n", err)
		// 异常退出。
		os.Exit(1)
	} else {
		// 用切片操作删除最后的 \n 。
		name := input[:len(input)-1]
		fmt.Printf("你好, %s! 我能为你做什么?\n", name)
	}
	for {
		input, err = inputReader.ReadString('\n')
		if err != nil {
			fmt.Printf("异常退出: %s\n", err)
			continue
		}
		input = input[:len(input)-1]
		// 全部转换为小写。
		input = strings.ToLower(input)
		switch input {
		case "":
			continue
		case "拜拜", "bye":
			fmt.Println("下次再回!")
			// 正常退出。
			os.Exit(0)
		default:
			fmt.Println("对不起,我不清楚你说什么……")
		}
	}
}

结果:
在这里插入图片描述

发布了264 篇原创文章 · 获赞 691 · 访问量 204万+

猜你喜欢

转载自blog.csdn.net/u014597198/article/details/103270964
今日推荐