defer语句是go语言提供的一种用于注册延迟调用的机制,是go语言中一种很有用的特性。
defer语句注册了一个函数调用,这个调用会延迟到defer语句所在的函数执行完毕后执行,所谓执行完毕是指该函数执行了return语句、函数体已执行完最后一条语句或函数所在协程发生了恐慌。
go
fmt.Println("test01")
defer fmt.Println("test02")
fmt.Println("test03")
编程经常会需要申请一些资源,比如数据库连接、打开文件句柄、申请锁、获取可用网络连接、申请内存空间等,这些资源都有一个共同点那就是在我们使用完之后都需要将其释放掉,否则会造成内存泄漏或死锁等其它问题。但操作完资源忘记关闭释放是正常的,而defer可以很好解决这个问题
go
// 打开文件
file_obj,err:=os.Open("满江红")
if err != nil {
fmt.Println("文件打开失败,错误原因:",err)
}
// 关闭文件
defer file_obj.Close()
// 操作文件
多个defer执行顺序
当一个函数中有多个defer语句时,会按defer定义的顺序逆序执行,也就是说最先注册的defer函数调用最后执行。
go
fmt.Println("test01")
defer fmt.Println("test02")
fmt.Println("test03")
defer fmt.Println("test04")
fmt.Println("test05")
defer的拷贝机制
go
// 案例1
foo := func() {
fmt.Println("I am function foo1")
}
defer foo()
foo = func() {
fmt.Println("I am function foo2")
}
// 案例2
x := 10
defer func(a int) {
fmt.Println(a)
}(x)
x++
// 案例3
x := 10
defer func() {
fmt.Println(x) // 保留x的地址
}()
x++
当执行defer语句时,函数调用不会马上发生,会先把defer注册的函数及变量拷贝到defer栈中保存,直到函数return前才执行defer中的函数调用。需要格外注意的是,这一拷贝拷贝的是那一刻函数的值和参数的值。注册之后再修改函数值或参数值时,不会生效。
defer执行时机
在Go语言的函数 return 语句不是原子操作,而是被拆成了两步
go
rval = xxx
ret
而 defer 语句就是在这两条语句之间执行,也就是
go
rval = xxx
defer_func
ret rval
defer x = 100
x := 10
return x // rval=10. x = 100, ret rval
经典面试题:
go
package main
import "fmt"
func f1() int {
i := 5
defer func() {
i++
}()
return i
}
func f2() *int {
i := 5
defer func() {
i++
fmt.Printf(":::%p\n", &i)
}()
fmt.Printf(":::%p\n", &i)
return &i
}
func f3() (result int) {
defer func() {
result++
}()
return 5 // result = 5;ret result(result替换了rval)
}
func f4() (result int) {
defer func() {
result++
}()
return result // ret result变量的值
}
func f5() (r int) {
t := 5
defer func() {
t = t + 1
}()
return t // ret r = 5 (拷贝t的值5赋值给r)
}
func f6() (r int) {
fmt.Println(&r)
defer func(r int) {
r = r + 1
fmt.Println(&r)
}(r)
return 5
}
func f7() (r int) {
defer func(x int) {
r = x + 1
}(r)
return 5
}
func main() {
// println(f1())
// println(*f2())
// println(f3())
// println(f4())
// println(f5())
// println(f6())
// println(f7())
}
在命名返回方式中,最终函数返回的就是命名返回变量的值,因此,对该命名返回变量的修改会影响到最终的函数返回值!
递归函数
一种计算过程,如果其中每一步都要用到前一步或前几步的结果,称为递归的。用递归过程定义的函数,称为递归函数,例如连加、连乘及阶乘等。
go
递归特性:
调用自身函数
必须有一个明确的结束条件
在计算机中,函数调用是通过栈(stack)这种数据结构实现的,
每当进入一个函数调用,栈就会加一层栈帧,每当函数返 回,栈就会减一层栈帧。
由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
go
package main
import "fmt"
func factorial(n int)int{
if n == 0{
return 1
}
return n * factorial(n-1)
}
func main() {
// 计算n的阶乘,即 n!
var ret = factorial(4)
fmt.Println(ret)
}
这个数列生成规则很简单,每一项都是前两项的和,举例 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233......
go
package main
import "fmt"
func fib(n int) int {
if n == 2 || n == 1 {
return 1
}
return fib(n-1) + fib(n-2)
}
func main() {
// 计算n的阶乘,即 n!
ret:=fib(6)
fmt.Println(ret)
}
练习题
go
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
// 构建数据存储结构
var customers []map[string]interface{}
var customersId int
func findById(id int) int {
index := -1
//遍历this.customers切⽚
for i := 0; i < len(customers); i++ {
if customers[i]["cid"] == id {
index = i
}
}
return index
}
func isBack() bool {
// 引导用户选择继续还是返回
fmt.Print("请问是否返回上一层【Y/N】:")
var backChoice string
fmt.Scan(&backChoice)
if strings.ToUpper(backChoice) == "Y" {
return true
} else {
return false
}
}
func inputInfo() (string, string, int8, string) {
var name string
fmt.Print("请输入客户姓名:")
fmt.Scan(&name)
var gender string
fmt.Print("请输入客户性别:")
fmt.Scan(&gender)
var age int8
fmt.Print("请输入客户年龄:")
fmt.Scan(&age)
var email string
fmt.Print("请输入客户邮箱:")
fmt.Scan(&email)
return name, gender, age, email
}
func addCustomer() {
for true {
// 引导用户输入学号和姓名
fmt.Printf("\033[1;35;40m%s\033[0m\n", "---------------------------添加客户开始-----------------------------")
name, gender, age, email := inputInfo()
// 创建客户的map对象
customersId++ // 客户编号不需要输入,系统自增即可
newCustomer := map[string]interface{}{
"cid": customersId,
"name": name,
"gender": gender,
"age": age,
"email": email,
}
// 添加客户map对象添加到客户切片中
customers = append(customers, newCustomer)
fmt.Printf("\033[1;35;40m%s\033[0m\n", "---------------------------添加客户完成-----------------------------")
b := isBack()
if b {
break
}
}
}
func listCustomer() {
for true {
fmt.Printf("\033[1;32;40m%s\033[0m\n", "----------------------------------客户列表开始-----------------------------------")
for _, customer := range customers {
fmt.Printf("编号:%-8d 姓名:%-8s 性别:%-8s 年龄:%-8d 邮箱:%-8s \n",
customer["cid"], customer["name"], customer["gender"], customer["age"], customer["email"])
}
fmt.Printf("\033[1;32;40m%s\033[0m\n", "----------------------------------客户列表完成-----------------------------------")
b := isBack()
if b {
break
}
}
}
func updateCustomer() {
fmt.Printf("\033[1;36;40m%s\033[0m\n", "---------------------------客户修改开始----------------------------")
for true {
var updateCid int
fmt.Print("请输入更新客户编号:")
fmt.Scan(&updateCid)
updateIndex := findById(updateCid)
if updateIndex == -1 {
fmt.Println("删除失败,输入的编号ID不存在")
continue
}
fmt.Println("请输入修改客户的信息")
name, gender, age, email := inputInfo()
customers[updateIndex]["name"] = name
customers[updateIndex]["gender"] = gender
customers[updateIndex]["age"] = age
customers[updateIndex]["email"] = email
fmt.Printf("\033[1;36;40m%s\033[0m\n", "---------------------------客户修改完成----------------------------")
b := isBack()
if b {
break
}
}
}
func deleteCustomer() {
fmt.Printf("\033[1;31;40m%s\033[0m\n", "---------------------------删除客户开始----------------------------")
var delCid int
fmt.Print("请输入删除客户编号:")
fmt.Scan(&delCid)
delIndex := findById(delCid)
if delIndex == -1 {
fmt.Println("删除失败,输入的编号ID不存在")
return
}
customers = append(customers[:delIndex], customers[delIndex+1:]...)
fmt.Printf("\033[1;31;40m%s\033[0m\n", "---------------------------删除客户完成----------------------")
}
var data = make(map[string]map[string]string)
func main() {
for true {
fmt.Printf("\033[1;33;40m%s\033[0m\n", `
----------------客户信息管理系统--------------
1、添加客户
2、查看客户
3、更新客户
4、删除客户
5、退出
-------------------------------------------
`)
var choice int
fmt.Printf("\033[1;38;40m%s\033[0m", "请输入选择【1-5】:")
stdin := bufio.NewReader(os.Stdin)
fmt.Fscan(stdin, &choice)
switch choice {
case 1:
addCustomer()
case 2:
listCustomer()
case 3:
updateCustomer()
case 4:
deleteCustomer()
default:
fmt.Println("非法输入!")
os.Exit(0)
}
}
}