> 项目地址:https://github.com/Zhouchaowen/golang-tutorial
> 视频地址:b站:[10节课学会Golang,Go快速入门](https://www.bilibili.com/video/BV1zc411W7JZ/?share_source=copy_web&vd_source=6e9f5b30fe025c05ef515831e3154768)
# 流程控制
## 循环语句
`Golang`中有三种类型的循环语句:`for` 循环、`range` 循环和 `goto` 语句。
- **For循环**
`Golang`中通过`For`关键字来定义一个循环并且只有`For`关键字(`Golang`中没有`while`关键字),格式
`for initialization; condition; post { // do something }` 其中,`initialization` 是循环开始前的初始化语句,`condition` 是循环条件,`post` 是每次循环结束后执行的语句。这些语句都是可选的,如果不需要可以省略。
```go
package main
import "fmt"
// Steps1 通过 for 循环累加 0-9
func Steps1() {
sum := 0
// for 循环
// i := 0 初始化语句:在第一次迭代前执行
// i < 10 条件表达式:在每次迭代前求值
// i++ 后置语句:在每次迭代的结尾执行
for i := 0; i < 10; i++ {
sum += i
}
fmt.Printf("\tsum: %d\n", sum)
}
func main() {
fmt.Println("Steps1():")
Steps1()
}
```
通过`For`实现类似`while`的语义
```go
package main
import "fmt"
// Steps2 for循环初始化语句和后置语句不是必须的
func Steps2() {
sum := 0
// 初始化语句和后置语句是可选的
for sum < 5 {
sum++
}
fmt.Printf("\tsum: %d\n", sum)
}
func main() {
fmt.Println("Steps2():")
Steps2()
}
```
- **Range**
通过`Range`关键字来遍历字符串,数组,切片或映射
```go
package main
import "fmt"
// Steps3 range形式的循环遍历
func Steps3() {
str := "Golang Tutorial"
for i, v := range str { // 遍历字符串
fmt.Printf("\ti:%d,v:%c\n", i, v)
}
}
func main() {
fmt.Println("Steps3():")
Steps3()
}
```
`Range`和`for`遍历的区别
```go
package main
import "fmt"
// Steps4 range和for遍历的区别
func Steps4() {
str := "Golang 教程"
for i := 0; i < len(str); i++ {
fmt.Printf("\ti:%d,v:%c\n", i, str[i])
}
for i, v := range str { // 遍历字符串
fmt.Printf("\ti:%d,v:%c\n", i, v)
}
}
func main() {
fmt.Println("Steps4():")
Steps4()
}
```
`For`循环中的`break`和`continue`
```go
package main
import "fmt"
// Steps5 for 循环中的 break 和 continue
func Steps5() {
for i := 0; i < 10; i++ {
if i == 5 { // 下一小节介绍
fmt.Printf("\ti:%d, continue\n", i)
continue
}
if i == 6 {
fmt.Printf("\ti:%d, break\n", i)
break
}
}
}
func main() {
fmt.Println("Steps5():")
Steps5()
}
```
- **Goto实现循环**
```go
package main
import "fmt"
// Steps6 goto 实现循环
func Steps6() {
i := 0
Next: // 跳转标签声明
fmt.Printf("\ti:%d\n", i)
i++
if i < 5 {
goto Next // 跳转
}
}
func main() {
fmt.Println("Steps6():")
Steps6()
}
```
`goto` 语句用于无条件跳转到程序的另一个位置。其中,`Next` 是一个标识符,用于指定要跳转到的位置。注意,`Next` 必须在当前函数内部定义。
## If判断
`Golang`中`If`语句和其它语言语义相同
```go
package main
import "fmt"
// if 分支打印不同字符
func main() {
flag := 10
if flag > 5 { // 判断表达式
fmt.Println("flag:", flag)
}
flag = 14
//flag = 16
//flag = 21
if flag > 20 {
fmt.Println("flag:", flag)
} else if flag < 15 {
fmt.Println("flag:", flag)
} else {
fmt.Println("flag:", flag)
}
}
```
## Switch选择
`Golang`中可以通过`switch-case`来实现分支选择, 每一个`case`分支都是唯一的,从上往下逐一判断,直到匹配为止,如果某些`case`分支条件重复了,编译会报错。
每个`case`分支最后自带`break`效果,匹配成功就不会执行其它`case`; 如果所有分支都没有匹配成功并且又定义了`default`分支, 那最终会走`default`分支。
`case` 后面的值可以是任何常量表达式,例如字符串、数字、布尔值等等。
示例一:
```go
package main
import "fmt"
// Steps1 基础用法
func Steps1() {
flag := 1
//flag = 2
//flag = 3
//flag = 4
//flag = 5
switch flag { // flag 待判断条件
case 1: // 条件 flag 是否等于 1。是:执行该case下的流程,否:选择其它满足条件的 case
fmt.Println("\tcase:", flag)
// Golang 中每个 case 后面不需要 break 语句。当然 return 是可选的
case 2:
fmt.Println("\tcase:", flag)
case 3, 4: // case 可以设置多个条件。只要 flag 等于3或4都能执行当前case流程
fmt.Println("\tcase:", flag)
case 5:
fmt.Println("\tcase:", flag)
return
default: // 当所有case都无法满足, 会执行 default 的流程。如果没有 default 那当前 switch 执行完成
fmt.Println("\tdefault:", flag)
}
}
// Steps2 switch 条件可以是任何支持判断的类型
func Steps2() {
flag := "Hello"
flag = "World"
flag = "Golang"
flag = "Tutorial"
flag = "Process"
switch flag { // flag 待判断条件
case "Hello": // 条件 flag 是否等于 "Hello"。是:执行该case下的流程,否:选择其它满足条件的 case
fmt.Println("\tcase:", flag)
case "World":
fmt.Println("\tcase:", flag)
case "Golang", "Tutorial": // case 可以设置多个条件。只要 flag 等于"Golang"或"tutorial"都能执行当前case流程
fmt.Println("\tcase:", flag)
default: // 当所有case都无法满足, 会执行 default 的流程。如果没有 default 那当前 switch 执行完成
fmt.Println("\tdefault:", flag)
}
}
// switch 是编写一连串 if - else 语句的简便方法
func main() {
fmt.Println("Steps1():")
Steps1()
fmt.Println("Steps2():")
Steps2()
}
```
示例二:
```go
package main
import "fmt"
// Steps3 switch true 可以将一长串 if-then-else 写得更加清晰
func Steps3() {
flag := 1
//flag = 2
//flag = 3
//flag = 4
//flag = 5
//flag = 7
switch { // flag 待判断条件
case flag < 2: // 条件 flag 是否小于 2。是:执行该case下的流程,否:选择其它满足条件的 case
fmt.Println("\tcase flag < 2 flag:", flag)
case flag < 4:
fmt.Println("\tcase flag < 4, flag:", flag)
case flag > 6, flag < 10: // case 可以设置多个条件。flag 大于6或小于10都能执行当前case流程
fmt.Println("\tcase flag > 6 || flag < 10 flag:", flag)
case flag > 6 && flag < 10: // case 可以设置组合条件。flag 大于6并且小于10都才能执行当前case流程
fmt.Println("\tcase flag > 6 || flag < 10 flag:", flag)
}
}
// Steps4 for + switch 的使用
func Steps4() {
for flag := 0; flag < 11; flag++ {
switch { // flag 待判断条件
case flag < 2: // 条件 flag 是否小于 2。是:执行该case下的流程,否:选择其它满足条件的 case
fmt.Println("\tcase flag < 2 flag:", flag)
case flag < 4:
fmt.Println("\tcase flag < 4, flag:", flag)
case flag > 6, flag < 8: // case 可以设置多个条件。flag 大于6或小于10都能执行当前case流程
fmt.Println("\tcase flag > 6 || flag < 8 flag:", flag)
case flag > 6 && flag < 10: // case 可以设置组合条件。flag 大于6并且小于10都才能执行当前case流程
fmt.Println("\tcase flag > 6 && flag < 10 flag:", flag)
}
}
}
// switch 是编写一连串 if - else 语句的简便方法
func main() {
fmt.Println("Steps3():")
Steps3()
fmt.Println("Steps4():")
Steps4()
}
```
## Defer
` Golang`中通过`defer`来实现延时调用, 用于指定一个函数调用在函数返回之前执行。常用来做一些收尾工作: **关闭连接,清理资源**
```go
package main
import "fmt"
// defer作用:
// 释放占用的资源
// 捕捉处理异常 recover
// Steps1 defer 语句会将函数推迟到外层函数返回之后执行。
// 推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
func Steps1() {
defer fmt.Printf(" world\n")
fmt.Printf("\thello")
}
func main() {
fmt.Println("Steps1():")
Steps1()
}
```
在上面的示例中,`defer` 语句都在函数中定义,它们会在函数返回之前执行。`defer fmt.Printf(" world\n")`,会在`func Steps1()`返回前执行。
通过defer延时打印数字:
```go
package main
import "fmt"
// Steps2 推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
func Steps2() {
fmt.Println("\tbegin")
for i := 0; i < 3; i++ {
defer fmt.Println("\t\ti:", i)
fmt.Printf("\t\ti:%d\n", i)
}
fmt.Println("\tend")
}
func main() {
fmt.Println("Steps2():")
Steps2()
}
/*
----- -----
| | | |
| | V | | |
| | | V
| ... |
| 3 |
| 2 |
| 1 |
| 0 |
—————
*/
/* 执行结果
begin
i:0
i:1
i:2
end
i: 2
i: 1
i: 0
*/
```
`defer` 语句中**引用函数中的变量**,会在函数调用是根据最新的值计算(`Steps3`); `defer` 语句中的**函数参数**会在 `defer` 语句定义时计算,而不是在函数调用时计算(`Steps4`)。
```go
package main
import "fmt"
func Steps3() {
fmt.Println("\tbegin")
x := 2
defer func() {
x = x * x
fmt.Println("\tx =", x) // x = 9
}()
fmt.Println("\tend")
x = 3
}
func Steps4() {
fmt.Println("\tbegin")
x := 2
defer func(x int) {
x = x * x
fmt.Println("\tx =", x) // x = 4
}(x)
fmt.Println("\tend")
x = 3
}
func main() {
fmt.Println("Steps3():")
Steps3()
fmt.Println("Steps4():")
Steps4()
}
```
## recover
`recover`是 `Go `语言中用于从 `panic` 恢复的内置函数。
当函数中发生 `panic` 时,程序会停止执行当前函数的代码,但是会继续执行当前函数的 `defer` 语句,直到所有的 `defer` 语句都执行完毕。如果其中某个 `defer` 语句调用了 `recover`,则程序会停止向上传递 `panic`,并在调用 `recover` 的地方继续执行代码,而不是终止程序的运行。
```go
package main
import "fmt"
// 捕捉处理异常 recover
func main() {
defer func() {
if err := recover(); err != nil {
// 捕捉错误 run err: runtime error: integer divide by zero
fmt.Println("run err:", err)
}
}()
a := 10
b := 0
_ = a / b
fmt.Println("return")
}
```
在这个示例中,我们通过`a/b`,因为`b` 是 0 所以会导致程序 `panic`。但是,我们在 `defer` 语句中使用了 `recover`,当程序 `panic` 时,会执行 `defer` 语句中的匿名函数。这个匿名函数调用 `recover` 函数,如果有错误信息,则输出错误信息,并恢复程序的正常执行。
需要注意的是,`recover` 函数只能在 `defer` 函数中使用,否则会引发运行时错误。此外,`recover` 函数只会在发生 `panic` 时返回错误信息,如果没有 `panic `,则会返回 `nil`。
## 思考题
1. 计算 100000 以内偶数,并且不是 4 的倍数外的所有数值和
2. 定义函数`Calculation`通过`Switch`实现加减乘除
```go
// 参考
func Calculation(option byte, a float64,b float64) float64{
switch option {
case '-':
.......
}
}
```
3. 通过`For`循环打印如下图形
```bigquery
*
**
***
****
*****
******
```
## 参考
https://gfw.go101.org/article/control-flows.html
---
## 项目地址
https://github.com/Zhouchaowen/golang-tutorial
## 交流群
> 微信关注【面试情报局】我们一起干翻面试官, 回复golang加入交流群
有疑问加站长微信联系(非本文作者)