Go 学习笔记(十二)流程控制结构

本文原创地址:博客园骏马金龙Go 基础系列:流程控制结构

条件判断结构:if else
分支选择结构:switch case
循环结构:for
break:退出 for 或 switch 结构 (以及 select)
continue:进入下一次 for 迭代

虽然 Go 是类 C 的语言,但 Go 在这些流程控制语句中的条件表达式部分不使用括号。甚至有些时候使用括号会报错,但有些复杂的条件判断需要使用括号改变优先级。

如:

if (name == "longshuai" && age > 23) || (name == "xiaofang" && age < 22) {
	print("yeyeye!!!")
}

if 语句

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

注意,Go 对语法要求很严格。左大括号{必须和 if、else 或 else if 在同一行,右大括号}必须换行,如果有 else 或 else if,则必须紧跟这两个关键字。也就是说,上面的代码结构中,大括号的使用位置是强制规范的,不能随意换行放置。

在 Go 中,if 语句的 condition 前面可以加上初始化语句,例如 Go 中很常见的:

if val := 10; val > max {
	// do something
}

它在一定程度上等价于:

val := 10
if val > max {
	// do something
}

但注意,前面简写的方式中,val的作用域只在 if 范围内,if 外面无法访问这个 val。如果在 if 语句之前已经定义了一个 val,那么这个 val 将被 if 中的 val 掩盖,直到 if 退出后才恢复

func main() {
	val := 20
	if val := 10; val > 3 {
		println("true")
	}
	println(val)    // 输出20
}

一种解决方式是 if 中的初始化语句不要使用:=,而是直接使用=,但这样会修改原始的值

func main() {
	val := 20
	if val = 10; val > 3 {
		println("true")
	}
	println(val)    // 输出10
}

在 Go 中,经常使用两个 (或多个) 返回值的函数,一个返回值作为值,另一个作为布尔类型的判断值,或者作为错误信息。通常会使用 if 语句去检测多个返回值的函数是否成功。

但注意,一般有两种判断返回值:一种是 ok 类型,一种是 err 类型的错误信息。前者是布尔值,后者是表明错误信息的字符串,如果没错误,则 err 为 nil。

value,ok := func_name()
if !ok {
	// func_name执行错误
	os.Exit(1)
}

value,err := func_name()
if err != nil {
	// func_name执行错误
	os.Exit(1)
	// 或 return err
}

将上面的简写一下,得到更常见的判断方式:

if value,ok := func_name();ok {
	// ok为true,函数执行成功
} else {
	// ok为false,函数执行失败
	os.Exit(1)
}

if value,err := func_name();err != nil {
	// err不为nil,说明出现错误
	return err
	//或os.Exit(1)
} else {
	// err为空,说明执行正确
}

switch 语句

switch 语句用于提供分支测试。有两种 swithc 结构:expression switch 和 type switch,本文暂时只介绍 expression switch,它用于判断表达式是否为 true。

对于 expression switch,也有三种形式:等值比较、表达式比较、初始化表达式。

等值比较结构 :当 var1 的值为 val1 时,执行 statement1,当 var1 的值为 val2 时,执行 statement2,都不满足时,执行默认的语句 statement。

switch var1 {
	case val1:
		statement1
	case val2:
		statement2
	default:
		statement
}

等值比较局限性很大,只能将 var1 和 case 中的值比较是否相等。如果想比较不等,或者其它表达式类型,可以使用下面的表达式比较结构。

表达式比较结构 :评估每个 case 结构中的 condition,只要评估为真就执行,然后退出 (默认情况下)。

switch {
	case condition1:
		statement1
	case condition2:
		statement2
	default:
		statement
}

初始化表达式 :可以和 if 一样为 switch 加上初始化表达式,同样作用域只在 switch 可见。但注意,initialization 后面记得加上分号 ";" 结尾。见下文示例。

switch initialization; {  // 不要省略分号
	case condition1:
		statement1
	case condition2:
		statement2
	defautl:
		statement
}

default是可选的,且可以写在 switch 的任何位置。

如果 case 中有多个要执行的语句,可以加大括号,也可以不加大括号。当只有一个语句的时候,statement 可以和 case 在同一行。

case 中可以提供多个用于测试的值,使用逗号分隔,只要有一个符合,就满足条件:

switch var1 {
	case val1,val2,val3:
		statement1
	case val4,val5: 
		statement2
	default:
		statement
}

例如:

val := 20
switch val {
case 10, 11, 15:
	println(11, 15)
case 16, 20, 22:      // 命中
	println(16, 20, 22)
default:
	println("nothing")
}

即使是表达式比较结构,也一样可以使用逗号分隔多个表达式,这时和使用逻辑或 "||" 是等价的:

func main() {
	val := 21
	switch {
	case val % 4 == 0:
		println(0)
	case val % 4 == 1, val % 4 == 2:  //命中
		println(1, 2)
	default:
		println("3")
	}
}

默认情况下 case 命中就结束,所以所有的 case 中只有一个会被执行。但如果想要执行多个,可以在执行完的某个 case 的最后一个语句上加上fallthrough,它会无条件地直接跳转到下一条 case 并执行,如果下一条 case 中还有 fallthrough,则相同的逻辑。此外,fallthrough 的后面必须只能是下一个 case 或 default,不能是额外的任何语句,否则会报错。

例如:

func main() {
	val := 21
	switch val % 4 {
	case 0:
		println(0)
	case 1, 2:         // 命中
		println(1, 2)  // 输出
		fallthrough    // 执行下一条,无需条件评估
		// println("sd") //不能加此行语句
	case 3:
		println(3)     // 输出
		fallthrough    // 执行下一条,无需条件评估
	default:
		println("end")  // 输出
	}
}

执行结果为:

1 2
3
end

fallthrough一般用于跳过某个 case。例如:

swtich i {
	case 0: fallthrough
	case 1: statement1
	default: statement
}

它表示等于 0 或等于 1 的时候都执行 statement1。这和前面 case 中多个评估值的功能是一样的。

以下是一个初始化表达式结构的 switch 示例:

func main() {
	val := 21
	switch val := 23; {
	case val % 4 == 0:
		println(0,val)
	case val % 4 == 1 || val % 4 == 2:
		println(1, 2,val)
	default:             // 命中
		println(3,val)   // 输出"3 23"
	}
	println(val)         // 输出21
}

for 语句

Go 中只有一种循环结构:for。

普通格式的 for

// 完整格式的for
for init; condition; modif { }

// 只有条件判断的for,实现while的功能
// 要在循环体中加上退出条件,否则无限循环
for condition { }

例如:

// 完整格式
func main() {
	for i := 0; i < 5; i++ {
		fmt.Println(i)
	}
}

// 只有条件的格式
func main() {
	var i int = 5
	for i >= 0 {
		i = i - 1
		fmt.Printf(i)
	}
}

无限循环

好几种方式实现 for 的无限循环。只要省略 for 的条件判断部分就可以实现无限循环。

for i := 0;;i++ 
for { } 
for ;; { }
for true { }

无限循环时,一般在循环体中加上退出语句,如 break、os.Exit、return 等。

for range 遍历

range 关键字非常好用,可以用来迭代那些可迭代的对象。比如 slice、map、array,还可以迭代字符串,甚至是 Unicode 的字符串。

for index,value := range XXX {}

但千万注意,value 是从 XXX 中拷贝的副本,所以通过 value 去修改 XXX 中的值是无效的,在循环体中应该总是让 value 作为一个只读变量。如果想要修改 XXX 中的值,应该通过 index 索引到源值去修改 (不同类型修改的方式不一样)

以迭代字符串为例。

func main() {
	var a = "Xiaofang,你好"
	for index,value := range a {
		println(index,string(value))
	}
}

输出结果:

0 X
1 i
2 a
3 o
4 f
5 a
6 n
7 g
8 ,
9 你
12 好

可见,在迭代字符串的时候,是按照字符而非字节进行索引的。

下面通过 value 去修改 slice 将无效。

func main() {
	s1 := []int{11,22,33}
	for index,value := range s1 {
		value += 1      // 只在for结构中有效
		fmt.Println(index,value)
	}
	fmt.Println(s1)   // for外面的结果仍然是[11 22 33]
}

要在循环结构中修改 slice,应该通过 index 索引的方式:

func main() {
	s1 := []int{11,22,33}
	for index,value := range s1 {
		value += 1
		s1[index] = value
		fmt.Println(index,value)
	}
	fmt.Println(s1)   // [12 23 34]
}

break 和 continue

breake 用于退出当前整个循环。如果是嵌套的循环,则退出它所在的那一层循环。break 除了可以用在 for 循环中,还可以用在 switch 结构或 select 结构。

continue 用于退出当前迭代,进入下一轮迭代。continue 只能用于 for 循环中。

标签和 goto

当某一行中第一个单词后面跟一个冒号的时候,Go 就认为这是一个标签。例如:

func main() {
LABEL1:
	for i := 0; i <= 5; i++ {
		for j := 0; j <= 5; j++ {
			if j == 4 {
				continue LABEL1
			}
			fmt.Printf("i is: %d, and j is: %d\n", i, j)
		}
	}
}

使用标签能让 break、continue 以及 goto 跳转到指定的位置继续往下执行。例如这里的continue LABEL1,当j == 4的时候,就直接跳到外层循环进入下一轮迭代。而break LABEL则指定直接退出 LABEL 所在的那一层循环。

goto 懒得介绍了,反正没人用,也强烈不建议使用,甚至标签都建议不要使用。一般能使用 LABEL 或 goto 的结构,都能改写成其它更好的语句。

空语句块

Go 中支持空 block{},这个大括号有自己的作用域,里面的代码只执行一次,退出大括号就退出作用域。

func main() {
	{
		v := 1
		{
			v := 2
			fmt.Println(v)   // 输出2
		}
		fmt.Println(v)   // 输出1
	}
}

上一篇 Go 学习笔记(十一)数据类型转换 (strconv 包)
Go 学习笔记(目录)
下一篇 Go 学习笔记(十三)函数 (1)