大家好,今天将梳理出的 Go语言函数用法内容,分享给大家。 请多多指教,谢谢。
本次《Go语言函数使用》内容共分为三个章节,本文为第二章节。
- Golang 基础之函数使用 (一)
- Golang 基础之函数使用 (二)
- Golang 基础之函数使用 (三)
本章节内容
- 匿名函数
- 闭包
- 递归函数
- 延迟调用 (defer)
匿名函数
介绍
在Go语言中,函数可以像普通变量一样被传递或使用,支持随时在代码里定义匿名函数。
匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
匿名函数有动态创建的特性,该特性使得匿名函数不用通过参数传递的方式,就可以直接引用外部的变量。
使用
第一种用法:将匿名函数赋给变量,在通过变量调用匿名函数
sum := func(a, b int) int {
return a + b
}
fmt.Println(sum(1, 2)) // 输出 3
第二种用法:在定义匿名函数的时候直接使用,这种方式只能使用一次传参
sum := func(a, b int) int {
return a + b
}(1,2) // 传入参数
fmt.Println(sum) // 输出 3
第三种用法:将匿名函数赋给一个全局变量,匿名函数在当前程序中都可以使用
package main
import "fmt"
var (
// 全局变量必须首字母大写
Sum = func(a, b int) int {
return a + b
}
)
func main() {
sum := Sum(1, 2)
fmt.Println("sum=", sum) // 输出 sum= 3
}
闭包
介绍
所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
闭包(Closure),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
可以理解为:闭包是匿名函数与匿名函数所引用环境的组合,类似常规函数引用全局变量处于一个包的环境。
闭包的优点
- 变量可以常驻内存
- 变量不污染全局
闭包里作用域返回的局部变量不会被立刻销毁回收,可能会占用更多内存过度使用闭包会导致性能下降。
使用
package main
import "fmt"
func main() {
n := 0
count := func() int { // 这就是一个闭包
n += 1
return n
}
fmt.Println(count()) // 输出 1
fmt.Println(count()) // 输出 2
}
常规函数、匿名函数 + 全局变量 + 包就等同于闭包, count
不仅存储了函数的返回值,还存储了闭包的状态。
闭包被返回赋予一个同类型的变量时,同时赋值的是整个闭包的状态,该状态会一直存在外部被赋值的变量count
中,直到count
被销毁,整个闭包生命周期结束。
也可以写成下列形式
package main
import "fmt"
func Count() func() int { // 返回函数
n := 0
return func() int {
n++
return n
}
}
func main() {
count := Count()
fmt.Println(count()) // 输出 1
fmt.Println(count()) // 输出 2
}
高级闭包特性,比如并发中的闭包。 将放到后面章节为大家介绍。
递归函数
介绍
递归,就是在运行的过程中调用自己。
一个函数调用自己, 就叫做递归函数。
构成递归具备的条件:
- 子问题需要与原始问题为同样的事,且更为简单。
- 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
使用
举例:数字阶乘
一个正整数的阶乘是所有小于及等于该数的正整数的积,并且0的阶乘为1。
package main
import "fmt"
func factorial(i int) int { // 解读为 5*4*3*2*1=120
if i <= 1 {
return 1
}
return i * factorial(i-1)
}
func main() {
var i int = 5
fmt.Printf("%d\n", factorial((i))) // 120
}
1808年,基斯顿·卡曼引进这个表示法。
举例:裴波那契数列(Fibonacci)
这个数列从第3项开始,每一项都等于前两项之和。
package main
import "fmt"
func fibonaci(i int) int {
if i == 0 {
return 0
}
if i == 1 {
return 1
}
return fibonaci(i-1) + fibonaci(i-2)
}
func main() {
var i int
for i = 0; i < 10; i++ {
fmt.Printf("%d ", fibonaci(i)) // 0 1 1 2 3 5 8 13 21 34
}
}
延迟调用 (defer)
介绍
在基础语法中已经介绍了defer延迟调用的使用,今天深入了解使用一下defer机制。
defer特性
- 关键字 defer 用于注册延迟调用。
- 这些调用直到 return 前才被执。因此,可以用来做资源清理。
- 多个defer语句,按先进后出的方式执行。
- defer语句中的变量,在defer声明时就决定了。
- 某个延迟调用发生错误,这些调用依旧会被执行。
defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。
defer用途
- 关闭文件句柄
- 锁资源释放
- 数据库连接释放
使用
多个 defer 注册,按 FILO 次序执行 ( 先进后出 ) 原则。
package main
func main() {
defer println("1") // 先进来, 最后出去
defer println("2")
defer println("3") // 最后进来, 先出去
}
输出
3
2
1
延迟调用参数在注册时求值或复制,可以用指针或闭包 “延迟” 读取。
package main
import "fmt"
func main() {
x, y := 10, 100
defer func(i int) {
fmt.Printf("defer x = %v, y = %v\n", i, y) // y 闭包引用
}(x) // x 被复制
x += 10
y += 20
println("x = ", x, "y = ", y)
}
输出
x = 20 y = 120
defer x = 10, y = 120
defer和return两者执行顺序
- 有名返回值 (函数返回值为已经命名的返回值)
package main
import "fmt"
func foo() (i int) { // 3.return i 值
i = 0
defer func() {
fmt.Println(i) // 2.读取临时变量地址(返回值)
}()
return 2 // 1.返回值赋值,写入临时变量
}
func main() {
foo()
}
输出
2
在 foo()
返回值的函数中 (这里返回值为 i),执行 return 2
的时候实际上已经将i 的值重新赋值为2。 所以defer closure输出结果为2。
解析:
return 的机制:1.首先将返回值放到一个临时变量中(为返回值赋值) 2. 然后将返回值返回到被调用处。
而defer函数恰在return的两个操作之间执行。
执行顺序是: 先为返回值赋值,即将返回值放到一个临时变量中,然后执行defer,然后return到函数被调用处。
- 无名返回值 (即函数返回值为没有命名的函数返回值)
package main
import "fmt"
func foo() int {
var i int
defer func() {
i++ // 这个地方 i,不是临时变量
fmt.Println("defer = ", i) // 输出 defer = 1
}()
return i // 返回值赋值,写入临时变量
}
func main() {
fmt.Println("return = ", foo()) // 输出 return = 0
}
解析:
return 先把返回值放到一个临时变量中,defer函数无法获取到这个临时变量地址 (没有函数返回值),所以无论defer函数做任何操作,都不会对最终返回值造成任何变动。
技术文章持续更新,请大家多多关注呀~~
搜索微信公众号【 帽儿山的枪手 】,关注我