3.5 Go(特殊函数)
目录
一、匿名函数
1、匿名函数的特点:
2、匿名函数代码示例
2、匿名函数的类型
二、递归函数
1. 递推公式版本
2. 循环改递归
三、嵌套函数
1、嵌套函数用途
2、代码示例
3、作用域 & 变量生存周期
四、闭包
1、闭包使用场景
2、代码示例
五、Defer
1、defer 关键特点
2、defer 代码示例
3、defer 在 return 之后执行
4、defer 绑定的是“实参值”,执行顺序遵循 LIFO
一、匿名函数
匿名函数是没有名称的函数,通常在定义时直接使用其功能,而不需要为其命名。匿名函数的定义一般是在代码中动态地创建并立即使用。
1、匿名函数的特点:
• 无名称:与普通函数不同,匿名函数不需要指定名字。
• 即时使用:常用于需要临时函数的场景,尤其是作为参数传递给其他函数。
应用场景
匿名函数的应用广泛,尤其是在以下两种情况下:
1. 回调函数:匿名函数常作为回调函数使用,尤其是在处理异步操作时。例如,JavaScript中的事件监听、Go中的channel处理等。
2、匿名函数代码示例
- 匿名函数被作为回调来处理异步任务。
func() {
fmt.Println("异步操作完成!")
}()
钩子函数(Hook):钩子函数允许在特定事件发生时执行额外的代码。匿名函数可以作为钩子函数,方便在特定逻辑中插入自定义操作。
type Hook func()
func RegisterHook(h Hook) {
h()
}
func main() {
RegisterHook(func() {
fmt.Println("执行钩子函数!")
})
}
2、匿名函数的类型
匿名函数与命名函数一样,也有自己的输入参数和输出参数,并且可以像普通函数一样进行类型定义。匿名函数的类型由其参数类型和返回值类型来决定。
• 入参和出参一致的函数:如果两个匿名函数的输入参数和返回值类型完全相同,我们称这两个函数为相同类型的匿名函数。即使它们的具体实现不同,只要入参和出参相同,它们就可以互换使用。
• 函数体不同:类型相同的匿名函数不代表它们的具体实现逻辑必须一样。即使两个匿名函数的入参、出参相同,它们在处理逻辑上的实现可以完全不同。
二、递归函数
递归(Recursion)是一种 函数自己调用自己 的编程技巧,通常用于解决 分解问题 和 重复子问题 的场景,比如 阶乘、斐波那契数列、树遍历 等。
递归的关键点
1.边界条件(Base Case): 防止无限递归,确保递归能终止。
2.递归前进(Recursive Case): 递归调用自身,逐步逼近边界条件。
3.递归返回(Return Phase): 当达到边界条件时,返回结果,逐层回溯。
递归实现方式
1. 递推公式版本
适用于具有明显的数学递推公式的问题,如 阶乘、斐波那契数列 等。
计算 n 的阶乘(n!)
阶乘公式:
n! = n \times (n-1)!, 当 n > 1 时
0! = 1, 递归终止条件
package main
import "fmt"
// 递归实现阶乘
func factorial(n int) int {
// 递归终止条件
if n == 0 {
return 1
}
// 递归前进:n * (n-1)!
return n * factorial(n-1)
}
func main() {
fmt.Println("5 的阶乘是:", factorial(5)) // 输出 120
}
2. 循环改递归
有些递归实现的算法可以改用循环来提高效率,比如斐波那契数列。
package main
import "fmt"
// 斐波那契数列(循环版)
func fibonacciLoop(n int) int {
if n == 0 {
return 0
}
if n == 1 {
return 1
}
a, b := 0, 1
for i := 2; i <= n; i++ {
a, b = b, a+b
}
return b
}
func main() {
fmt.Println("斐波那契数列前10项(循环版):")
for i := 0; i < 10; i++ {
fmt.Print(fibonacciLoop(i), " ")
}
}
三、嵌套函数
1、嵌套函数用途
逻辑封装,避免代码重复
当某些操作只在特定函数内部使用,并且不会被外部调用时,可以使用嵌套函数来减少代码重复,提升代码可读性。
2、代码示例
函数中定义另外一个函数,
函数嵌套形成嵌套作用域
package main
import "fmt"
// 函数嵌套形成嵌套作用域
func outer() {
c := 99
var inner = func() {
c = 100 //修改outer的c值,修改完成后会覆盖outer,其实修改的outer 上的变量内容
fmt.Println("inner c==", c, &c) //对outer的c进行赋值修改
c := c + 1 //inner的局部变量,外部无法调用。
fmt.Println("inner c==", c, &c)
}
inner()
fmt.Println("outer c==", c, &c)
}
func main() {
outer() //先执行inner函数
}
3、作用域 & 变量生存周期
在 Go 语言中,变量的作用域决定了它的 生存周期 和 可访问性:
1. 全局作用域:在 package 级别定义的变量,整个包都能访问。
2. 函数作用域:在函数内定义的变量,只有这个函数可以访问。
3. 嵌套作用域:
- inner() 可以访问 outer() 的 c(因为 Go 支持闭包)。
- 但 inner() 不能修改 outer() 的 c 的地址,只能修改其值。
四、闭包
1、闭包使用场景
闭包使用的原因,解决栈帧消亡后值不能消失。变量从栈中逃逸到堆上,本次调用的栈消亡,堆不消亡
2、代码示例
func outer() func() {
c := 99 // 变量 c 在 outer 作用域内
var inner = func() {
fmt.Println("1 inner c==", c, &c) // 这里 c 仍然是 outer 作用域的变量
}
fmt.Println("2 outer c==", c, &c) // 这里打印 outer 内的 c
return inner // 返回 inner 函数,但并不调用它
}
outer() 里定义了 c,然后 inner 函数捕获了 c,但 inner 并没有被执行,而是作为返回值返回。
• outer() 先执行,打印 c 的值和地址。
• main() 里 fn := outer(),将 inner 赋值给 fn,但 inner 还未执行,所以 c 依然没有被打印或修改。
• fmt.Println(fn) 只是打印 fn 这个函数本身的地址,而 inner 仍然未执行。
• inner 形成闭包,捕获 c,并且 c 不会因为 outer 结束而消失。
• 只有当 fn() 被执行时,inner 才会访问 c 并打印它。
• 由于 c 被 inner 捕获,它存活的时间超出了 outer 的生命周期(变量逃逸到堆上)。
五、Defer
defer 关键字用于推迟执行某个函数,直到**当前函数返回(正常 return 或 panic 发生时)**才会执行。
1、defer 关键特点
1. 延迟执行:defer 语句不会立即执行,而是在当前函数返回时执行。
2. 执行顺序:LIFO(后进先出),即多个 defer 按照注册顺序的反向顺序执行。
3. 保证执行:无论是正常返回(return)还是异常崩溃(panic),defer 代码块都会执行。
4. 不执行的情况:os.Exit() 或 log.Fatal() 会导致程序立即退出,使 defer 代码块无法执行。
2、defer 代码示例
package main
import "fmt"
func main() {
defer fmt.Println("1") // 最后执行
defer fmt.Println("2") // 倒数第二执行
defer fmt.Println("3") // 倒数第三执行
fmt.Println("start")
fmt.Println("stop")
}
代码输出结果:
start
stop
3
2
1
fmt.Println("start") 立即执行
• fmt.Println("stop") 立即执行
• defer 语句按照 后进先出 顺序执行
3、defer 在 return 之后执行
package main
import "fmt"
func test() {
defer fmt.Println("defer 执行")
fmt.Println("函数内执行")
return
}
func main() {
test()
fmt.Println("函数返回后执行")
}
输出结果:
函数内执行
defer 执行
函数返回后执行
4、defer 绑定的是“实参值”,执行顺序遵循 LIFO
package main
import (
"fmt"
)
func main() {
a := 1024
fmt.Println("start ~~~~~~~~~~~~~~")
defer fmt.Println(a) // defer 1: 绑定值 1024
a++ // a = 1025
defer fmt.Println(a) // defer 2: 绑定值 1025
a++ // a = 1026
defer fmt.Println(a) // defer 3: 绑定值 1026
a++ // a = 1027
fmt.Println("stop~~~~~~~~~~~~~~~~")
}
输出结果
start ~~~~~~~~~~~~~~
stop~~~~~~~~~~~~~~~~
1026
1025
1024
代码执行时,defer 语句在注册时就会确定参数的值,不会受到后续 a 值变化的影响。
defer 语句执行时,实参值已经确定
• defer fmt.Println(a) 注册时,参数 a 当前的值会被存下来,后续 a 的变化不会影响 defer 绑定的值。
• 多个 defer 语句按 LIFO 顺序执行(后进先出)。