Go基础-流程控制

2022/12/13 go

# if else (条件判断)

Goif else写法跟JavaC等语言的方式有点区别,就是表达式不需要使用()括起来

if else 条件判断格式如下:

if 表达式1 {
  分支1
} else if 表达式2 {
  分支2
} else {
  分支3
}
1
2
3
4
5
6
7
  1. 当表达式1的结果为 true 时,会执行分支1的代码;
  2. 当表达式1的结果为 false 时,会执行表达式2,若结果为 true, 则会执行分支2的代码。
  3. 当前面的条件都不满足,即都为 false 时,会执行分支3的代码。
package main

import "fmt"

func main() {
	num := 2
	if num == 1 {
		fmt.Println("等于1")
	} else if num < 3 {
		fmt.Println("小于3")
	} else {
		fmt.Println(num)
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

代码输出:

小于3
1

表达式2、分支2 、分支3都是可选的,可以根据实际情况自行选择ifif elseif else if ... else的方式

注意:Go 语言中规定:if 的 左括号 { 必须与 if 表达式放在同一行,如果强行放置在其他位置,会发生如下编译错误。

unexpected newline, expecting { after if clause
1

# if 的特殊写法

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

package main

import "fmt"

//创建一个返回int类型为1的函数
func getRes() int {
	return 1
}

func main() {
	//先赋值再判断
	if num := getRes(); num != 0 {
		fmt.Println(num)
		return
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

代码输出:

1
1

getRes() 是一个带有返回值的函数,它,num := getRes() 是一个语句,执行 getRes() 函数后,将返回值赋值给 num 变量。

num != 0if 的条件判断表达式,当 num不为0时,打印num并返回。

这种写法可以将返回值和判断放在一行进行处理,使得返回值的作用域范围被限制在了 if else 语句组合中,这样做的好处是,变量的作用域范围越小,那么它可能造成的问题的可能性越小,这对代码的稳定性很有帮助,另外,也可以做炫技(装逼)使用。

# for(循环)

Go跟大部分语言一样,循环需要使用关键字 for 来完成,格式如下:

for 初始语句;条件表达式;末尾循环体 {
  循环体代码
}
1
2
3
  • 初始语句:初始语句是第一次执行循环前执行的语句,通常情况下,我们会通过它来执行变量初始化操作。注意: 通过初始语句声明的变量,作用域局限在 for 循环内

  • 条件表达式:每次在循环前,会计算条件表达式的值,如果结果为 true, 则继续循环;否则将结束循环。注意:条件表达式同样可以忽略不写,若不写,由于没有终止条件,则会陷入无限死循环,导致内存溢出

  • 末尾循环体:一般为赋值表达式,给控制变量增量或减量。每次执行完{}中的代码后执行。

执行过程

  1. 先对初始语句赋初值;

  2. 判别初始语句 是否满足给定 条件表达式的条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 末尾循环体,进入第二次循环,再判别 条件表达式;否则判断 条件表达式 的值为假,不满足条件,就终止for循环,执行循环体外语句。

不支持 while 和 do-while 结构

# 循环控制语句

循环执行过程中,如果需要跳过本次循环到下一次循环,可以使用continue关键字跳过本次循环,若循环被 breakgotoreturnpanic 等语句强制退出,执行循环体外的语句。

package main

import "fmt"

func main() {
	
	for i := 0; i < 10;i++ {
		//如果i==2,跳过本次循环
		if i == 2 {
			continue
		}
		//如果i==8,终止循环
		if i == 8 {
			break
		}
		fmt.Println(i)
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

代码输出:

0
1
3
4
5
6
7
1
2
3
4
5
6
7

# 无限循环(死循环)

语法很简单,就是初始语句条件表达式末尾循环体都不写:

for {
    //循环体代码,会一直执行
}
1
2
3

# for range (键值循环)

for range 通常来遍历数组 、切片 、字符串 、map 以及通道(channel

# 遍历数组、切片、字符串

通过 for range 遍历数组、切片、字符串的返回值都有一个规律:

  • 下标索引 index,即接收的第一个变量,变量名不一定是index,可自定义
  • 下标对应的值 value,即接收的第二个变量,变量名不一定是value,可自定义

# 遍历切片,数组

package main

import "fmt"

func main()  {
	// 初始化一个切片
	slice := []int{1, 2, 3, 4}
	
	// 通过 for range 循环切片
	for index, value := range slice {
		fmt.Printf("index: %d, value: %d\n", index, value)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

代码输出:

index: 0, value: 1
index: 1, value: 2
index: 2, value: 3
index: 3, value: 4
1
2
3
4

# 遍历字符串

package main

import "fmt"

func main() {
	str := "abcd"
	for index, value := range str {
		fmt.Printf("index: %d, value: 0x%x\n", index, value)
	}
}
1
2
3
4
5
6
7
8
9
10

代码输出:

index: 0, value: 0x61
index: 1, value: 0x62
index: 2, value: 0x63
index: 3, value: 0x64
1
2
3
4

上面代码中的 value 变量实际类型是 runne, 本质上是 int32, 以十六进制格式打印出来的是字符串编码。

# 遍历 map 字典

通过 for range 遍历 map 字典会返回,称为键值对,它们总是一对一出现

  • 字典的key,即接收的第一个变量,变量名不一定是key,可自定义
  • 字典的key对应的值 value,即接收的第二个变量,变量名不一定是value,可自定义
package main

import "fmt"

func main() {
	m := map[int]string{
		1: "haha",
		2: "哈哈",
	}
	for key, value := range m {
		fmt.Printf("key: %d, value: %s\n", key, value)
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

代码输出:

key: 1, value: haha
key: 2, value: 哈哈
1
2

注意: 对 map 字典进行遍历时,遍历出的键值对是无序的,如果需要有序输出,需要先排序。

# 遍历通道(channel)

遍历通道(channel)有点不同,此时只会输出一个值,即管道内的类型对应的数据

package main

import "fmt"

func main() {
	// 初始化一个通道
	c := make(chan int)

	// 启动一个 goroutine, 功能是往通道中推送数据 1、2、3,然后关闭通道
	go func() {
		c <- 1
		c <- 2
		c <- 3
		close(c)
	}()

	for value := range c {
		fmt.Println(value)
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

代码输出:

1
2
3
1
2
3

# 使用匿名变量

什么是匿名变量?

  • 1、它可以理解成一种占位符,用下划线_表示;
  • 2、这种变量不会分配内存,也不会占用变量名;
  • 3、在 for range 中,可以通过匿名变量接受键 key,也可以接受值 value。
package main

import "fmt"

func main() {
	m := map[int]string{
		1: "haha",
		2: "哈哈",
	}
    //使用匿名变量接收key
	for _, value := range m {
		fmt.Printf("value: %s\n", key, value)
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

代码输出:

value: haha
value: 哈哈
1
2

# switch 语句(分支判断)

Go语言中, switch 中的每一个 case 都是独立代码块,无需通过 break 语句跳出代码块,以避免执行到下一个 case 代码块。

比如JAVAC/C++语言case 代码块中的 break 语句印象深刻。

# 基本格式

switch 表达式 {
    case 目标值1:
    		执行语句
    case 目标值2:
    		执行语句
    default:
    		执行语句
}
1
2
3
4
5
6
7
8
  • 表达式:可以是变量,也可以是一系列计算公式
  • 目标值:根表达式比对的值,如果匹配就会执行对应的执行语句
  • default:如果前面case目标值都不匹配,就会执行defalut里面的执行语句,每一个 swtich 语句中只能有一个 default 分支,也可省略不写

例子:

package main

import "fmt"

func main() {
	num := 1
	switch num {
		case 1:
			fmt.Println("我是1")
		case 2:
			fmt.Println("我是2")
		default:
			fmt.Println("我是default")

	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

代码输出:

我是1
1

# 一分支多值

表达式直接用逗号 , 隔开即可:

package main

import "fmt"

func main() {
	num := 1
	switch num {
        case 1, 2:
            fmt.Println("1和2都是我")
        default:
            fmt.Println("我是default")
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

代码输出:

12都是我
1

不需要像JAVA那样需要声明多个case:

//java写法
switch (num) {
    case 1:
    case 2:
        System.out.println("1和2都是我")
    default:
        System.out.println("我是default")
}
1
2
3
4
5
6
7
8

# 分支表达式

case 后面除了定义常量以外,还可以像if 语句一样添加表达式,这个时候,switch 后面不再跟判断变量,直接不写即可。

package main

import "fmt"

func main() {
	num := 1

	switch {
	case num > 0 && num < 2:
		fmt.Println("大于0小于2")
	}
}
1
2
3
4
5
6
7
8
9
10
11
12

代码输出:

大于0小于2
1

# fallthrough 关键字 —— 兼容 C 语言的 case 设计

Go 语言中 swtich 语句中的 case 均为独立代码块,执行完成后,不会像 JAVAC/C++ 语言如果不加break关键字的话会继续执行下一个 case,如果要实现继续执行下一个case的话,可以用 fallthrough 关键字来实现这一功能。

package main

import "fmt"

func main() {
	num := 1
	switch {
	case num > 0:
		fmt.Println("大于0")
		fallthrough
	case num < 2:
		fmt.Println("小于2")
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

代码输出:

大于0
小于2
1
2

# goto 语句(代码跳转)

JAVA的可能不知道goto,但在JAVAgoto是一个保留字,只是暂时没有用处,但学过C/C++的就比较熟悉了。

goto 语句通过标签实现代码间的跳转。它通常会被使用在快速跳出循环、避免定义重复代码等。

package main

import "fmt"

func main() {
	// 外循环
	for i := 0; i < 10; i++ {
		fmt.Println(i)
        //当i等于5的时候跳转到breakHere标签
		if i == 5 {
			goto breakHere
		}
	}
	// 定义一个标签
	breakHere:
		fmt.Println("跳到这里 ...")
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

代码输出:

0
1
2
3
4
5
跳到这里 ...
1
2
3
4
5
6
7

# break 语句(跳出循环)

break 语句可以结束 forswitchselect 代码块。

# 跳出单层循环

当循环到 6 时,通过 break 语句跳出循环:

package main

import "fmt"

func main()  {
	// 外循环
	for i := 0; i < 10; i++ {
		fmt.Printf("i: %d\n", i)

		// 当 i 等于 6 时,跳转到循环
		if i == 6 {
			// 跳出循环
			break
		}
	}

	fmt.Println("跳出循环 ...")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

代码输出:

i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
跳出循环 ...
1
2
3
4
5
6
7
8

# 跳出代码块 - 退出多层循环

通过 break 语句搭配标签可以跳出代码块,如跳出 forswitchselect 代码块。

package main

import "fmt"

func main()  {

    //定义一个标签
	OuterLoop:
		// 外循环
		for i := 0; i < 10; i++ {
			fmt.Printf("i: %d\n", i)

			// 内循环
			for j := 0; j < 10; j++ {
				fmt.Printf("j: %d\n", j)
				if i == 0 && j == 6 {
					// 跳出 OuterLoop 代码块
					break OuterLoop
				}
			}
		}

	fmt.Println("跳出循环 ...")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

代码输出:

i: 0
j: 0
j: 1
j: 2
j: 3
j: 4
j: 5
j: 6
跳出循环 ...
1
2
3
4
5
6
7
8
9

# continue 语句(继续下次循环)

continue 语句可以结束当前 for 循环 ,进入下一次 for 循环。

比如循环输出0-9,当i==5的时候跳过输出:

package main

import "fmt"

func main() {
	for i := 0; i < 10; i++ {
		if i == 5 {
			continue
		}
		fmt.Println(i)
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13

代码输出:

0
1
2
3
4
6
7
8
9
1
2
3
4
5
6
7
8
9

另外,我们还可以在 continue 语句后添加标签,表示开始标签处的循环:

package main

import "fmt"

func main() {
//定义一个标签
OuterLoop:
	// 外循环
	for i := 0; i < 3; i++ {
		// 内循环
		for j := 0; j < 3; j++ {
			// 当 j 等于 1 时,执行 continue 语句跳转到 OuterLoop 标签处,即跳出至外循环,而不是内循环
			if j == 1 {
				continue OuterLoop
			}
			fmt.Printf("i: %d, j: %d\n", i, j)
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

代码输出:

i: 0, j: 0
i: 1, j: 0
i: 2, j: 0
1
2
3