Go语言之函数补充defer语句,递归函数,章节练习

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)
        }
    }

}
相关推荐
吃着火锅x唱着歌3 小时前
Redis设计与实现 学习笔记 第五章 跳跃表
golang
技术卷5 小时前
Redis数据库与GO完结篇:redis操作总结与GO使用redis
数据库·redis·golang
white.tie8 小时前
vscode配置golang
ide·vscode·golang
陈序缘8 小时前
Go语言实现长连接并发框架 - 任务管理器
linux·服务器·开发语言·后端·golang
0x派大星13 小时前
【Golang】语法基础——切片:灵活、高效的数据处理利器
golang
技术卷21 小时前
GO网络编程(二):客户端与服务端通信【重要】
golang·网络编程
小帅吖21 小时前
浅析Golang的Context
开发语言·后端·golang
MarisTang21 小时前
Go语言实现随机森林 (Random Forest)算法
算法·随机森林·golang
技术卷1 天前
Redis数据库与GO(二):list,set
数据库·redis·golang
__AtYou__1 天前
Golang | Leetcode Golang题解之第441题排列硬币
leetcode·golang·题解