Go后端开发 – 条件、循环语句 && defer语句
文章目录
- Go后端开发 -- 条件、循环语句 && defer语句
- 一、条件语句
- 1.if ... else 语句
- 2.switch语句
- 3.select语句
- 二、循环语句
- 1.for循环
- 三、defer语句
- 1.defer语句的作用
- 2.defer和return的先后顺序
- 3.recover错误拦截
一、条件语句
1.if … else 语句
语法:
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}
与C的if … else语句的区别是条件不用加括号
实例
package main
import "fmt"
func main() {
/* 局部变量定义 */
var a int = 100;
/* 判断布尔表达式 */
if a < 20 {
/* 如果条件为 true 则执行以下语句 */
fmt.Printf("a 小于 20\n" );
} else {
/* 如果条件为 false 则执行以下语句 */
fmt.Printf("a 不小于 20\n" );
}
fmt.Printf("a 的值为 : %d\n", a);
}
2.switch语句
语法:
switch var1 {
case val1:
...
case val2:
...
default:
...
}
- switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加
break
。 - switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用
fallthrough
- 变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。
- 可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。
- switch语句可以不在switch开头确定判断的条件,可以在case中进行条件判断;
实例:
package Switch
import "fmt"
func Switch() {
grade := 'B'
marks := 90
switch marks {
case 90:
grade = 'A'
case 80:
grade = 'B'
case 70, 60, 50:
grade = 'C'
default:
grade = 'D'
}
switch {
case grade == 'A':
fmt.Println("优秀")
case grade == 'B':
fmt.Println("良好")
case grade == 'C':
fmt.Println("及格")
default:
fmt.Println("不及格")
}
fmt.Println("你的等级是:", grade)
}
- switch语句还可以被用于
type-switch
来判断某个interface
变量中实际存储的变量类型。
Type Switch 语法格式如下
switch x.(type){
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}
实例:
package Switch
import "fmt"
func TypeSwitch() {
var x interface{}
switch i := x.(type) {
case nil:
fmt.Printf("x的类型: %T\n", i)
case int:
fmt.Println("x是int型")
case float64:
fmt.Println("x是float64型")
case func(int) float64:
fmt.Println("x是func(int)型")
case bool, string:
fmt.Println("x是bool或string型")
default:
fmt.Println("未知型")
}
}
fallthrough
使用fallthrough
会强制执行后面的 case 语句,包括default
,fallthrough
不会判断下一条case的表达式结果是否为 true。fallthrough
只会强制执行后面一条case语句,而不是后面的全部;
实例1:
package Switch
import "fmt"
func Fallthrough() {
switch {
case false:
fmt.Println("1.case条件语句为false")
fallthrough
case true:
fmt.Println("2.case条件语句为true")
fallthrough
case false:
fmt.Println("3.case条件语句为false")
fallthrough
case true:
fmt.Println("4.case条件语句为true")
fallthrough
case false:
fmt.Println("5.case条件语句为false")
fallthrough
default:
fmt.Println("6.默认case")
}
}
实例2:
package Switch
import "fmt"
func Fallthrough() {
switch {
case false:
fmt.Println("1.case条件语句为false")
fallthrough
case true:
fmt.Println("2.case条件语句为true")
fallthrough
case false:
fmt.Println("3.case条件语句为false")
//fallthrough
case true:
fmt.Println("4.case条件语句为true")
//fallthrough
case false:
fmt.Println("5.case条件语句为false")
//fallthrough
default:
fmt.Println("6.默认case")
}
}
3.select语句
select 是 Go 中的一个控制结构,类似于 switch 语句。
- select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。
- select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。
- 如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。
Go 编程语言中 select 语句的语法如下:
select {
case <- channel1:
// 执行的代码
case value := <- channel2:
// 执行的代码
case channel3 <- value:
// 执行的代码
// 你可以定义任意数量的 case
default:
// 所有通道都没有准备好,执行的代码
}
- 每个 case 都必须是一个通道
- 所有 channel 表达式都会被求值
- 所有被发送的表达式都会被求值
- 如果任意某个通道可以进行,它就执行,其他被忽略。
- 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。
否则:- 如果有 default 子句,则执行该语句。
- 如果没有 default 子句,select 将阻塞,直到某个通道 可以运行;Go 不会重新对 channel 或值进行求值。
实例1:
package Select
import (
"fmt"
"time"
)
func Select() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
- 以上实例中,我们创建了两个通道 c1 和 c2。
select 语句等待两个通道的数据。如果接收到 c1 的数据,就会打印 “received one”;如果接收到 c2 的数据,就会打印 “received two”。
实例2:
package Select
import (
"fmt"
"time"
)
func Select1() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
func Select2() {
// 定义两个通道
ch1 := make(chan string)
ch2 := make(chan string)
// 启动两个 goroutine,分别从两个通道中获取数据
go func() {
for {
ch1 <- "from 1"
}
}()
go func() {
for {
ch2 <- "from 2"
}
}()
// 使用 select 语句非阻塞地从两个通道中获取数据
for {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:
// 如果两个通道都没有可用的数据,则执行这里的语句
fmt.Println("no message received")
}
}
}
- 以上实例中,我们定义了两个通道,并启动了两个协程(Goroutine)从这两个通道中获取数据。在 main 函数中,我们使用 select 语句在这两个通道中进行非阻塞的选择,如果两个通道都没有可用的数据,就执行 default 子句中的语句。
以上实例执行后会不断地从两个通道中获取到的数据,当两个通道都没有可用的数据时,会输出 “no message received”。
二、循环语句
1.for循环
for 循环是一个循环控制结构,可以执行指定次数的循环。
Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。
- 和 C 语言的
for
一样:
for init; condition; post { }
init: 一般为赋值表达式,给控制变量赋初值;
condition: 关系表达式或逻辑表达式,循环控制条件;
post: 一般为赋值表达式,给控制变量增量或减量。
- 和 C 的
while
一样:
for condition { }
- 和 C 的
for(;;)
一样:
for { }
- for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
以上代码中的 key 和 value 是可以省略。
如果只想读取 key,格式如下:
for key := range oldMap
或者这样:
for key, _ := range oldMap
如果只想读取 value,格式如下:
for _, value := range oldMap
实例1:
package For
import "fmt"
func For() {
sum := 0
for i := 1; i <= 10; i++ {
sum += i
}
fmt.Println(sum)
}
实例2:
package For
import "fmt"
func While() {
sum := 1
for sum <= 10 {
sum += sum
}
fmt.Println(sum)
}
- 以上实例是将init 和 post参数去掉,相当于是一个while循环
实例3:
package For
import "fmt"
func main() {
sum := 0
for {
sum++ // 无限循环下去
}
fmt.Println(sum) // 无法输出
}
- for后面什么也不跟就是无限循环;
实例4:
package For
import "fmt"
func Range() {
strings := []string{"google", "runoob"}
for i, s := range strings {
fmt.Println(i, s)
}
numbers := [6]int{1, 2, 3, 5}
for i, x := range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i, x)
}
}
- For-each range 循环可以对字符串、数组、切片等进行迭代输出元素。
实例5:
package For
import "fmt"
func Range() {
map1 := make(map[int]float32)
map1[1] = 1.0
map1[2] = 2.0
map1[3] = 3.0
map1[4] = 4.0
// 读取 key 和 value
for key, value := range map1 {
fmt.Printf("key is: %d - value is: %f\n", key, value)
}
// 读取 key
for key := range map1 {
fmt.Printf("key is: %d\n", key)
}
// 读取 value
for _, value := range map1 {
fmt.Printf("value is: %f\n", value)
}
}
- For-each range也可以读取map中的k和v
三、defer语句
1.defer语句的作用
defer语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数。
defer语句会将其后的函数调用推迟到当前所在函数体的生命周期结束后再执行,类似于c++的析构函数。这个特性常用于处理成对的操作,如打开/关闭文件、获取/释放锁、连接/断开连接等,确保资源被适当地释放,即使在发生错误或提前返回的情况下也能保证执行。
defer作用:
- 释放占用的资源
- 捕捉处理异常
- 输出日志
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
实例
package Defer
import "fmt"
func Defer() {
defer fmt.Println("main end1")
defer fmt.Println("main end2")
fmt.Println("0")
fmt.Println("1")
}
- defer语句调用的函数会在当前函数体生命周期结束后再出栈执行,多个defer语句调用函数的顺序类似于堆栈,后进先出;
2.defer和return的先后顺序
package Defer
import "fmt"
func deferFunc() int {
fmt.Println("defer func called...")
return 0
}
func returnFunc() int {
fmt.Println("return func caller...")
return 0
}
func ReturnAndDefer() int {
defer deferFunc()
return returnFunc()
}
- 由于defer语句会将其后的函数调用推迟到当前所在函数体的生命周期结束后再出栈,而return语句是在
}
之前的,因此return会先执行,defer再执行;
3.recover错误拦截
运行时panic异常一旦被引发就会导致程序崩溃。
- Go语言提供了专用于“拦截”运行时panic的内建函数
recover
。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。 - 注意:recover只有在defer调用的函数中有效。
实例1:
package main
import "fmt"
func recoverFunc(i int) {
//定义10个元素的数组
var arr [10]int
//错误拦截要在产生错误前设置
//这里使用匿名函数进行错误拦截,在进行defer调用
//相当于这个匿名函数在recoverFunc的生命周期结束后才被调用
defer func() {
//设置recover拦截错误信息
err := recover()
//产生panic异常,打印错误信息
if err != nil {
fmt.Println(err)
}
}()
//根据函数参数为数组元素赋值
//如果i的值超过数组下标 会报错误:数组下标越界
arr[i] = 2
}
func main() {
recoverFunc(10)
fmt.Println("程序继续执行...")
}
- 在上述实例中,我们定义了一个匿名函数来拦截panic,并用defer去掉用,这个匿名函数会在当前
recoverFunc
的生命周期结束后进行调用,recoverFunc
函数中出现了数组越界的错误,之后被recover()
拦截了,程序得以继续执行;
实例2:
package main
import "fmt"
func handlePanic() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
}
func performTask() {
defer handlePanic()
fmt.Println("Performing some task...")
panic("Oops! Something went wrong!")
fmt.Println("Task completed.")
}
func main() {
performTask()
fmt.Println("Main function continues.")
}
- 在以上的实例中,panic函数调用后,异常被引发,当前
performTask
函数执行结束,defer调用的函数开始出栈,执行handlePanic
函数,panic异常引发后,在handlePanic
函数中recover拦截了异常,打印出错误信息,并让main
函数继续运行。