Go语言基础之IF控制结构

作为一名程序员,应该都听过“程序=数据结构+算法”这一公式吧?它是由著名的计算机看科学家尼古拉斯·沃斯提出的。

在之前的文章中,我们了解了Go语言的基本数据类型和复合数据类型,他们对应的就是数据结构。在本篇文章中将介绍Go语言的控制结构,控制结构是算法的基础,再复杂的算法都可以通过分支、循环、顺序这三种控制结构来进行构造。

在Go语言中,顺序结构就不说了,针对程序的分支控制,Go语言提供了ifswitch-case两种语句形式。而针对循环结构,Go语言只提供了for这一循环控制语句。

IF语句基础

在前面我们了解到Go语言是基于C语言发展起来的,它继承了C语言许多语法,同时也摒弃了许多糟粕,这里也包括控制结构。下面举一些Go语言在控制结构主要的改进和优化:

  • 只保留了 for 这一种循环结构,去掉了 C 语言中的 while 和 do-while 循环结构;
  • 填平了 C 语言中 switch 分支结构中每个 case 语句都要以 break 收尾的“坑”;
  • 支持了 type switch 特性,让“类型”信息也可以作为分支选择的条件;
  • switch 控制结构的 case 语句还支持表达式列表,让相同处理逻辑的多个分支可以合并为一个分支;
  • ……

IF单分支控制结构

if单分支控制结构是Go中最常用、最简单的一种分支控制结构,它根据布尔表达式的值在两个分支中选择一个执行。

1
2
3
4
5
if boolean_expression {
// 新分支
}

// 原分支

分支结构是传统结构化程序设计中的基础构件,这个 if 语句中的代码执行流程就等价于下面这幅流程图:

-

代码执行过程中遇到if分支后,首先判断if中的布尔表达式,如果结果为true则进入新分支,否则继续按照原分支执行。

Go语言的IF分支语句特点:

  • 和 Go 函数一样,if 语句的分支代码块的左大括号与 if 关键字在同一行上;
  • if 语句的布尔表达式整体不需要用括号包裹;
  • if关键字后的表达式结果要么为true,要么为false,不能有其他值;
1
2
3
4
5
6
7
8
import (
"fmt"
"runtime"
)

if runtime.GOOS == "linux" {
println("我们运行在linux操作系统上")
}

当判断条件较多,可以采用逻辑操作符对多个判断条件进行连接:

1
2
3
if (runtime.GOOS == "linux") && (runtime.GOARCH == "amd64") && (runtime.Compiler != "gccgo") {
fmt.Println("我们现在使用的是标准Go编译器运行在Linux操作系统上,架构是AMD64")
}

此外,在使用逻辑操作符的时候还需要注意操作符的优先级:

1
2
3
4
5
6
7
8
9
10
11
func main() {
x, y := false, true

if x && y != true {
fmt.Println("(x && y) != true")
}
fmt.Println("x && (y != true)")
}
main()

>> x && (y != true)

最一劳永逸的方法就是通过括号将不同的逻辑表达式分隔开,这样既不需要关心操作符优先级,又提高了代码的可读性。

IF二分支、多分支结构

二分支结构,当 boolean_expression 求值为 true 时,执行分支 1,否则,执行分支 2:

1
2
3
4
5
6

if boolean_expression {
// 分支1
} else {
// 分支2
}

多分支结构则显得更加繁琐点,相当于是一个层层缩进的二分之结构,代码可读性较差:

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

if boolean_expression1 {
// 分支1
} else if boolean_expression2 {
// 分支2

... ...

} else if boolean_expressionN {
// 分支N
} else {
// 分支N+1
}

IF语句自用变量

在IF控制结构中,我们可以在if后的布尔表达式前进行一些变量的声明,这些声明的变量作用域只能是在if语句块内部使用,所以被称之为if语句的自用变量。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
if a, c := -1, 1; a > 0 {
fmt.Println(a)
}else if b := 1; b > 0 {
fmt.Println(a, b)
}else {
fmt.Println(a, b, c)
}

// if语句块之后打印变量值会报错
// fmt.Println(a, b, c) //repl.go:10:17: undefined identifier: a
}

main()

IF语句的编写原则——“HAPPY PATH”

在日常编码中,过多的使用二分之或多分支结构对代码的可读性影响很大,同时也会降低代码的可维护性。对此,Go社区推荐我们在使用IF语句的时候,尽量使用快乐路径原则(happy path),它对if分支结构的要求特点如下:

  • 仅使用单分支控制结构;
  • 当布尔表达式求值为 false 时,也就是出现错误时,在单分支中快速返回;
  • 正常成功的逻辑在代码布局上始终“靠左”,这样可以从上到下一眼看到该函数正常逻辑的全貌;
  • 函数执行到最后一行代表一种成功状态;

具体代码如下面的代码1所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

//伪代码段1:

func doSomething() error {
if errorCondition1 {
// some error logic
... ...
return err1
}

// some success logic
... ...

if errorCondition2 {
// some error logic
... ...
return err2
}

// some success logic
... ...
return nil
}

// 伪代码段2:

func doSomething() error {
if successCondition1 {
// some success logic
... ...

if successCondition2 {
// some success logic
... ...

return nil
} else {
// some error logic
... ...
return err2
}
} else {
// some error logic
... ...
return err1
}
}

对于不符合该原则的if语句块,我们可以按照下面步骤进行重构:

  • 尝试将“正常成功逻辑”提取出来,放到“快乐路径”中;
  • 如果无法做到上一点,很可能是函数内的逻辑过于复杂,可以将深度缩进到 else 分支中的代码析出到一个函数中,再对原函数实施“快乐路径”原则。

思考题

如果一个 if 语句使用了多分支结构,如下面代码这样,那么 if 语句中的几个布尔表达式如何排列能达到最好的效果呢?

提示一下,几个布尔表达式能够被命中的概率是不同的,你在答案中可以自行假设一下。

各位大佬可以在评论区回答哦,回答有惊喜哦~

1
2
3
4
5
6
7
8
9
10
11
func foo() {
if boolean_expression1 {

} else if boolean_expression2 {

} else if boolean_expression3 {

} else {

}
}

预告

下一篇:Go在常量上的设计创新