go函数使用(三)

简介: go函数使用

go函数使用(二)https://developer.aliyun.com/article/1391409


递归函数

当一个函数在其函数体内调用自身,则称之为递归。最经典的例子便是计算斐波那契数列,即每个数均为前两个数之和。

数列如下所示:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, …

下面的程序可用于生成该数列(示例 6.13 fibonacci.go):

package main
import "fmt"
func main() {
result := 0
for i := 0; i <= 10; i++ {
result = fibonacci(i)
fmt.Printf("fibonacci(%d) is: %d\n", i, result)
}
}
func fibonacci(n int) (res int) {
if n <= 1 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
return
}

输出:

fibonacci(0) is: 1

fibonacci(1) is: 1

fibonacci(2) is: 2

fibonacci(3) is: 3

fibonacci(4) is: 5

fibonacci(5) is: 8

fibonacci(6) is: 13

fibonacci(7) is: 21

fibonacci(8) is: 34

fibonacci(9) is: 55

fibonacci(10) is: 89

在使用递归函数时经常会遇到的一个重要问题就是栈溢出:一般出现在大量的递归调用导致的程序栈内存分配耗尽。这个问题可以通过一个名为懒惰求值的技术解决,在 Go 语言中,我们可以使用管道(channel)和 goroutine来实现。

Go 语言中也可以使用相互调用的递归函数:多个函数之间相互调用形成闭环。因为 Go 语言编译器的特殊性,这些函数的声明顺序可以是任意的

将函数作为参数

函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。下面是一个将函数作为参数的简单例子:

package main
import (
"fmt"
)
func main() {
callback(1, Add)
}
func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}

输出:

The sum of 1 and 2 is: 3

闭包

当我们不希望给函数起名字的时候,可以使用匿名函数,例如: func(x, y int) int { return x + y } 。这样的一个函数不能够独立存在(编译器会返回错误:non-declaration statement outside function body ),但可以被赋值于某个变量,即保存函数的地址

到变量中: fplus := func(x, y int) int { return x + y } ,然后通过变量名对函数进行调用: fplus(3,4) 。当然,您也可以直接对匿名函数进行调用: func(x, y int) int { return x + y } (3, 4) 。下面是一个计算从 1 到 1 百万整数的总和的匿名函数:

func() {
sum = 0.0
for i := 1; i <= 1e6; i++ {
sum += i
}
}()

表示参数列表的第一对括号必须紧挨着关键字 func ,因为匿名函数没有名称。花括号 {} 涵盖着函数体,最后的一对括号表示对该匿名函数的调用。

下面的例子展示了如何将匿名函数赋值给变量并对其进行调用:

package main
import "fmt"
func main() {
f()
}
func f() {
for i := 0; i < 4; i++ {
g := func(i int) { fmt.Printf("%d ", i) }
g(i)
fmt.Printf(" - g is of type %T and has value %v\n", g, g)
}
}

输出:

0 - g is of type func(int) and has value 0x681a80

1 - g is of type func(int) and has value 0x681b00

2 - g is of type func(int) and has value 0x681ac0

3 - g is of type func(int) and has value 0x681400

应用闭包:将函数作为返回值

在程序 function_return.go 中我们将会看到函数 Add2 和 Adder 均会返回签名为 func(b int) int 的函数:

func Add2() (func(b int) int)
func Adder(a int) (func(b int) int)

函数 Add2 不接受任何参数,但函数 Adder 接受一个 int 类型的整数作为参数。

我们也可以将 Adder 返回的函数存到变量中(function_return.go)。

package main
import "fmt"
func main() {
// make an Add2 function, give it a name p2, and call it:
p2 := Add2()
fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3))
// make a special Adder function, a gets value 3:
TwoAdder := Adder(2)
fmt.Printf("The result is: %v\n", TwoAdder(3))
}
func Add2() func(b int) int {
return func(b int) int {
return b + 2
}
}
func Adder(a int) func(b int) int {
return func(b int) int {
return a + b
}
}

输出:

Call Add2 for 3 gives: 5

The result is: 5

计算函数执行时间

有时候,能够知道一个计算执行消耗的时间是非常有意义的,尤其是在对比和基准测试中。最简单的一个办法就是在计算开始之前设置一个起始时候,再由计算结束时的结束时间,最后取出它们的差值,就是这个计算所消耗的时间。想要实现这样的做法,可以使用 time 包中的 Now() 和 Sub 函数:

start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)

通过内存缓存来提升性能

当在进行大量的计算时,提升性能最直接有效的一种方式就是避免重复计算。通过在内存中缓存和重复利用相同计算的结果,称之为内存缓存。

普通写法:4.730270 秒

内存缓存:0.001000 秒

内存缓存的优势显而易见,而且您还可以将它应用到其它类型的计算中,例如使用 map而不是数组或切片(Listing 6.21 - fibonacci_memoization.go):

package main
import (
"fmt"
"time"
)
const LIM = 41
var fibs [LIM]uint64
func main() {
var result uint64 = 0
start := time.Now()
for i := 0; i < LIM; i++ {
result = fibonacci(i)
fmt.Printf("fibonacci(%d) is: %d\n", i, result)
}
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
}
func fibonacci(n int) (res uint64) {
// memoization: check if fibonacci(n) is already known in array:
if fibs[n] != 0 {
res = fibs[n]
return
}
if n <= 1 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
fibs[n] = res
return
}

内存缓存的技术在使用计算成本相对昂贵的函数时非常有用(不仅限于例子中的递归),譬如大量进行相同参数的运算。这种技术还可以应用于纯函数中,即相同输入必定获得相同输出的函数。

相关文章
|
4月前
|
存储 算法 Go
go语言中的延迟执行函数
【5月更文挑战第13天】`defer`是Go语言中用于延迟执行函数的关键字,尤其适用于资源管理,如文件关闭和锁的释放。它在函数返回前按照LIFO顺序执行,确保资源在任何返回路径下都能正确释放。`defer`可以拦截`panic`并在函数返回前执行,但无法阻止某些致命的`panic`。此外,`defer`可用于修改返回值、输出调试信息和还原变量值。尽管在某些场景下可能影响性能,但Go的优化使得其在多数情况下性能表现良好,特别是在资源清理方面。在Go 1.20及以后的版本,`defer`的性能已显著提升,尤其是在高计算量的场景下。
262 2
|
1月前
|
Prometheus Cloud Native Go
Go 1.22 标准库 slices 新增函数和一些旧函数增加新特性
Go 1.22 标准库 slices 新增函数和一些旧函数增加新特性
|
1月前
|
安全 编译器 Go
Go 1.21: 泛型函数的全面回顾
Go 1.21: 泛型函数的全面回顾
|
1月前
|
Go
深入理解 Go 中的 new() 和 make() 函数
深入理解 Go 中的 new() 和 make() 函数
|
11天前
|
设计模式 Java 数据库连接
|
11天前
|
Go 开发者
|
14天前
|
存储 机器学习/深度学习 设计模式
Go从入门到放弃之函数
Go从入门到放弃之函数
|
1月前
|
Go 开发者
Go1.22 新特性:Slices 变更 Concat、Delete、Insert 等函数,对开发挺有帮助!
Go1.22 新特性:Slices 变更 Concat、Delete、Insert 等函数,对开发挺有帮助!
|
2月前
|
存储 Go
go语言中fmt格式化包和内置函数汇总
【7月更文挑战第10天】本文介绍fmt包和`Errorf`用于创建格式化的错误消息。`fmt`包还涉及一些接口,如`Formatter`、`GoStringer`、`ScanState`、`Scanner`和`Stringer`,支持自定义格式化和输入/输出处理。
38 1
|
4月前
|
编译器 Go
Go 语言函数
Go 语言函数
28 7