程序的流程结构

程序的流程控制结构一共有三种:顺序结构,选择结构,循环结构。

顺序结构:从上向下,逐行执行。

选择结构:条件满足,某些代码才会执行。0-1次

​ 分支语句:if,switch,select

循环结构:条件满足,某些代码会被反复的执行多次。0-N次

​ 循环语句:for

条件语句

If语句

语法格式:

1
2
3
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
}
1
2
3
4
5
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}
1
2
3
4
5
6
7
if 布尔表达式1 {
/* 在布尔表达式1为 true 时执行 */
} else if 布尔表达式2{
/* 在布尔表达式1为 false ,布尔表达式2为true时执行 */
} else{
/* 在上面两个布尔表达式都为false时,执行*/
}

示例代码:

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

import "fmt"

func main() {
/* 定义局部变量 */
var a int = 10

/* 使用 if 语句判断布尔表达式 */
if a < 20 {
/* 如果条件为 true 则执行以下语句 */
fmt.Printf("a 小于 20\n" )
}
fmt.Printf("a 的值为 : %d\n", a)
}

如果其中包含一个可选的语句组件(在评估条件之前执行),则还有一个变体。它的语法是

1
2
3
4
5
if statement; condition {  
}

if condition{
}

示例代码:

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

import "fmt"

func main() {

if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}

if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}

if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}

需要注意的是,num的定义在if里,那么只能够在该if..else语句块中使用,否则编译器会报错的。

switch语句:“开关”

switch是一个条件语句,它计算表达式并将其与可能匹配的列表进行比较,并根据匹配执行代码块。它可以被认为是一种惯用的方式来写多个if else子句。

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止。
switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加break

而如果switch没有表达式,它会匹配true

Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码。

变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。
可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。

1
2
3
4
5
6
7
8
switch var1 {
case val1:
...
case val2:
...
default:
...
}

示例代码:

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
package main

import (
"fmt"
"time"
)

func main() {
/* 定义局部变量 */
var grade = "B"
var marks = 90

switch marks {
case 90:
grade = "A"
case 80:
grade = "B"
case 50, 60, 70:
grade = "C" //case 后可以由多个数值
default:
grade = "D"
}

switch {
case grade == "A":
fmt.Printf("优秀!\n")
case grade == "B", grade == "C":
fmt.Printf("良好\n")
case grade == "D":
fmt.Printf("及格\n")
case grade == "F":
fmt.Printf("不及格\n")
default:
fmt.Printf("差\n")
}
fmt.Printf("你的等级是 %s\n", grade)

t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}

输出:

1
2
3
优秀!
你的等级是 A
It's before noon

fallthrough

如需贯通后续的case,就添加fallthrough

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

import (
"fmt"
)

func main() {
switch x := 5; x {
default:
fmt.Println(x)
case 5:
x += 10
fmt.Println(x)
fallthrough
case 6:
x += 20
fmt.Println(x)

}

}

运行结果:

1
2
15
35

解释:

根据switch语句的逻辑,首先会执行default分支,但是由于我们没有在default分支中写入任何代码,所以会直接跳过。在case 5分支的最后,我们使用了fallthrough关键字。fallthrough关键字的作用是强制执行下一个case分支的代码,而不进行条件判断。

switch语句中,default用于处理没有匹配到任何case的情况。当switch表达式的值与所有的case都不匹配时,程序会执行default分支中的代码。

default分支是可选的,也就是说可以选择是否在switch语句中包含它。如果没有default分支,而且没有任何一个case匹配到switch表达式的值,那么switch语句将不会执行任何代码。

default分支通常被用作最后一个分支,用于处理不常见或者意外的情况。它可以用来提供一个默认的处理逻辑,或者给用户一个错误提示。

以下是一个示例,展示了default的使用:

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

import "fmt"

func main() {
num := 10

switch num {
case 1, 2, 3:
fmt.Println("小数")
case 4, 5, 6:
fmt.Println("中数")
case 7, 8, 9:
fmt.Println("大数")
default:
fmt.Println("未知数")
}
}

在上面的示例中,如果num的值不属于1到9之间的任何一个数,那么就会执行default分支,并输出”未知数”。default分支可以用来处理一些边缘情况或者未预料到的情况,确保程序的健壮性。


case中的表达式是可选的,可以省略。如果该表达式被省略,则被认为是switch true,并且每个case表达式都被计算为true,并执行相应的代码块

示例代码:

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

import (
"fmt"
)

func main() {
num := 75
switch { // expression is omitted
case num >= 0 && num <= 50:
fmt.Println("num is greater than 0 and less than 50")
case num >= 51 && num <= 100:
fmt.Println("num is greater than 51 and less than 100")
case num >= 101:
fmt.Println("num is greater than 100")
}
}

输出:

1
num is greater than 51 and less than 100

switch的注意事项

  1. case后的常量值不能重复
  2. case后可以有多个常量值
  3. fallthrough应该是某个case的最后一行。如果它出现在中间的某个地方,编译器就会抛出错误。

Type Switch

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

1
2
3
4
5
6
7
8
9
switch x.(type){
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}

示例代码:

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

package main

import "fmt"

func main() {
var x interface{}

switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型" )
default:
fmt.Printf("未知型")
}
}

运行结果:

1
x 的类型 :<nil>

解释:

在这段代码中,变量x的类型被声明为interface{},它是一个空接口。空接口可以表示任意类型的值,包括nil。当我们使用类型断言x.(type)来获取x的具体类型时,如果x的值是nil,那么case nil分支会匹配成功。这里需要注意的是,nil是一个特殊的值,表示一个指针类型或接口类型的零值,表示该指针或接口不指向任何具体的对象。在这种情况下,我们可以将其视为一种特殊的类型。因此,case nil分支被用来处理xnil的情况。

循环语句

循环语句表示条件满足,可以反复的执行某段代码。

for是唯一的循环语句。(Go没有while循环)

for语句

语法结构:

1
for init; condition; post { }

初始化语句只执行一次。在初始化循环之后,将检查该条件。如果条件计算为true,那么{}中的循环体将被执行,然后是post语句。post语句将在循环的每次成功迭代之后执行。在执行post语句之后,该条件将被重新检查。如果它是正确的,循环将继续执行,否则循环终止。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)

func main() {
for i := 1; i <= 10; i++ {
fmt.Printf(" %d",i)
}
}

在for循环中声明的变量仅在循环范围内可用。因此,i不能在外部访问循环。

所有的三个组成部分,即初始化、条件和post都是可选的。

1
for condition { }

效果与while相似

1
for { }

效果与for(;;) 一样

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环

1
2
3
for key, value := range oldMap {
newMap[key] = value
}
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
package main

import "fmt"

func main() {

var b int = 9
var a int

numbers := [6]int{1, 2, 3, 5}

/* for 循环 */
for a := 0; a < 5; a++ {
fmt.Printf("a 的值为: %d\n", a)
}

for a < b {
a++
fmt.Printf("a 的值为: %d\n", a)
}

for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
a 的值为: 0
a 的值为: 1
a 的值为: 2
a 的值为: 3
a 的值为: 4
a 的值为: 1
a 的值为: 2
a 的值为: 3
a 的值为: 4
a 的值为: 5
a 的值为: 6
a 的值为: 7
a 的值为: 8
a 的值为: 9
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0

多层for循环

for循环中又有循环嵌套,就表示多层循环了。

跳出循环的语句

break语句

break:跳出循环体。break语句用于在结束其正常执行之前突然终止for循环

示例代码:

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

import (
"fmt"
)

func main() {
for i := 1; i <= 10; i++ {
if i > 5 {
break //loop is terminated if i > 5
}
fmt.Printf("%d ", i)
}
fmt.Printf("\nline after for loop")
}

输出:

1
2
1 2 3 4 5 
line after for loop

continue语句

continue:跳出一次循环。continue语句用于跳过for循环的当前迭代。在continue语句后面的for循环中的所有代码将不会在当前迭代中执行。循环将继续到下一个迭代。

示例代码:

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

import (
"fmt"
)

func main() {
for i := 1; i <= 10; i++ {
if i%2 == 0 {
continue
}
fmt.Printf("%d ", i)
}
}

输出:

1
1 3 5 7 9 

goto语句

goto:可以无条件地转移到过程中指定的行。

语法结构:

1
2
3
4
goto label;
..
..
label: statement;

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

import "fmt"

func main() {
/* 定义局部变量 */
var a = 10

/* 循环 */
LOOP:
for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
}

输出:

1
2
3
4
5
6
7
8
9
a的值为 : 10
a的值为 : 11
a的值为 : 12
a的值为 : 13
a的值为 : 14
a的值为 : 16
a的值为 : 17
a的值为 : 18
a的值为 : 19

统一错误处理
多处错误处理存在代码重复时是非常棘手的,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
	err := firstCheckError()
if err != nil {
goto onExit
}
err = secondCheckError()
if err != nil {
goto onExit
}
fmt.Println("done")
return
onExit:
fmt.Println(err)
exitProcess()

生成随机数

示例代码:

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
package main

import (
"fmt"
"math/rand"
"time"
)

func main() {
num1 := rand.Int()
fmt.Println(num1)

for i := 0; i < 10; i++ {
num := rand.Intn(10)
fmt.Println(num)
}
rand.Seed(1000)
num2 := rand.Intn(10)
fmt.Println("--->", num2)

t1 := time.Now()
fmt.Println(t1)
fmt.Printf("%T\n", t1)

timeStamp1 := t1.Unix()
fmt.Println(timeStamp1)

timestamp2 := t1.UnixNano()
fmt.Println(timestamp2)

rand.Seed(time.Now().UnixNano())
for i := 0; i < 10; i++ {
fmt.Println("--->", rand.Intn(100))
}

num3 := rand.Intn(46) + 3
fmt.Println(num3)
num4 := rand.Intn(62) + 15
fmt.Println(num4)
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2359980755445512729
6
0
9
3
2
---> 5
2023-06-02 12:00:50.6796224 +0800 CST m=+0.001542201
time.Time
1685678450
1685678450679622400
---> 74
---> 79
---> 18
---> 21
---> 68
---> 41
---> 33
---> 31
---> 26
---> 37
15

解释:

在随机数生成中,种子(seed)是用于初始化随机数生成器的值。种子决定了随机数序列的起始点。在某种程度上,相同种子会生成相同的随机数序列。

在上述代码中,rand.Seed()方法用于设置随机数生成器的种子。如果没有显式地设置种子,Go语言的math/rand包默认使用一个固定的种子,这意味着每次程序运行时都会生成相同的随机数序列。这在某些情况下可能不是我们所期望的。

通过调用rand.Seed()方法并传入一个不同的种子值,我们可以改变随机数生成器的起始点,从而产生不同的随机数序列。通常情况下,我们会使用当前时间的纳秒级时间戳作为种子,以确保每次运行程序时都能生成不同的随机数序列。

在代码中,rand.Seed(1000)将种子设置为固定值1000,因此后续生成的随机数序列将始终相同。而rand.Seed(time.Now().UnixNano())使用当前时间的纳秒级时间戳作为种子,可以产生不同的随机数序列。

但是自从Go 1.2 0以来,rand.Seed就已经被弃用了