函数
1659字约6分钟
2025-02-03
函数
函数 是 Go 的核心。
在Go语言中,函数可以像其他类型一样被当作参数传递给其他函数,也可以作为函数的返回值。因此,函数是一等公民。
package main
import "fmt"
// 定义一个简单的函数,接收两个整数并返回它们的和
func add(a, b int) int {
return a + b
}
// 定义一个函数,接收一个整数并返回其平方
func square(x int) int {
return x * x
}
// 定义一个函数,接收一个函数作为参数并调用该函数
func applyFunc(funcName func(int, int) int, a, b int) int {
return funcName(a, b)
}
// 函数多返回值
func swap(a, b string) (string, string) {
return b, a
}
// 变参函数
func sum(numbers ...int) int {
sum := 0
for _, number := range numbers {
sum += number
}
return sum
}
func main() {
// 调用add函数并打印结果
sum1 := add(3, 4)
fmt.Println("3 + 4 =", sum1)
// 调用square函数并打印结果
squared := square(5)
fmt.Println("5 squared =", squared)
// 将函数作为参数传递给另一个函数
sum2 := applyFunc(add, 1, 2)
fmt.Println("sum2:", sum2)
// 将函数作为返回值返回
double := applyFunc(func(a, b int) int {
return a + b
}, 5, 6)
fmt.Println("double:", double)
// 函数多返回值
a, b := swap("hello", "world")
fmt.Println("a:", a, "b:", b)
// 变参函数
// 使用range循环生成1到100的数字
var numbers []int
for i := 1; i <= 100; i++ {
numbers = append(numbers, i)
}
// 计算1加到100的和
result := sum(numbers...)
fmt.Println("1+2+3+...+100 =", result)
}
闭包函数
什么是闭包?
闭包(Closure)是指一个函数包含了它外部作用域中的变量,即使在外部作用域结束后,这些变量依然可以被内部函数访问和修改。闭包使得函数可以“记住”外部作用域的状态,这种状态在函数调用之间是保持的。
闭包的核心概念是函数内部可以引用外部作用域的变量,即使在函数内部外部作用域已经结束。
这段Go语言代码演示了基本的闭包和函数值。
package main
import "fmt"
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
nextInt := intSeq()
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
newInts := intSeq()
fmt.Println(newInts())
}
首先,我们定义了一个名为intSeq的函数,它返回一个函数。这个返回的函数是一个闭包,因为它引用了其外部函数的变量i。intSeq函数初始化一个变量i为0,并返回一个匿名函数,这个匿名函数将i递增并返回。
然后,在main函数中,我们调用intSeq函数并将其返回的闭包赋值给nextInt。这样,nextInt就是一个闭包,它引用了intSeq函数中的i变量。
当我们连续调用nextInt()三次时,每次调用都会递增i的值并返回。因此,输出结果为:
1
2
3
接下来,我们创建了一个新的intSeq函数的实例,并将其返回的闭包赋值给newInts。由于闭包引用的是创建它们时的上下文中的变量,所以newInts和nextInt实际上是引用了不同的i变量。因此,当我们调用newInts()时,输出结果为:
1
这表明newInts闭包中的i变量是从新的intSeq函数中初始化的,而nextInt闭包中的i变量是从原始的intSeq函数中初始化的。
闭包的应用场景
状态保持和共享
闭包常用于实现状态保持和共享。通过闭包,我们可以在函数调用之间保持状态,而无需使用全局变量。
func makeAccumulator() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
acc := makeAccumulator()
fmt.Println(acc(5)) // 输出 5
fmt.Println(acc(3)) // 输出 8
}
函数式编程
在函数式编程中,函数是一等公民,可以作为参数传递、返回值和变量赋值。闭包使得函数可以更加灵活地用于函数式编程,实现函数的组合和转换。
func mapInts(nums []int, f func(int) int) []int {
result := make([]int, len(nums))
for i, num := range nums {
result[i] = f(num)
}
return result
}
func main() {
double := func(x int) int {
return x * 2
}
nums := []int{1, 2, 3, 4}
doubledNums := mapInts(nums, double)
fmt.Println(doubledNums)
}
并发编程
在并发编程中,闭包使得可以将状态隔离在每个goroutine中,避免竞态条件和数据不一致问题。
func startWorker(id int) {
go func() {
for {
fmt.Printf("Worker %d is workding\n", id)
time.Sleep(time.Second)
break
}
}()
}
func main() {
start := time.Now()
for i := 0; i < 3; i++ {
startWorker(i)
}
time.Sleep(time.Second)
fmt.Println("end")
fmt.Println(time.Since(start))
}
闭包的注意事项
内存泄漏
由于闭包持有外部作用域的变量引用,如果闭包一直被引用,外部作用域的变量不会被销毁,可能会导致内存泄漏。在使用闭包时,需要注意外部作用域变量的生命周期。
竞态条件
在并发编程中,由于多个goroutine可以共享闭包中的变量,可能会引发竞态条件和数据不一致问题。在并发场景下使用闭包时,需要保证变量的访问是安全的。
总结
闭包是Go语言中强大的特性之一,它允许函数持有外部作用域的变量引用,实现状态保持和共享。通过闭包,我们可以实现更加灵活和复杂的编程模式,如函数式编程、并发编程等。闭包使得函数在语法层面具有更强的表达力和灵活性,为开发者提供了更多的选择和工具。
在使用闭包时,需要注意变量的生命周期、内存泄漏和竞态条件问题。通过合理地使用闭包,可以提高代码的可维护性和可靠性,为项目开发带来更多的优势。
递归函数
当一个函数在其函数体内调用自身,则称之为递归。
最经典的例子便是计算斐波那契数列,即前两个数为1,
从第三个数开始每个数均为前两个数之和。
数列如下所示: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …
package main
import (
"fmt"
"time"
)
func main() {
result := 0
start := time.Now()
for i := 0; i <= 40; 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 int) {
if n <= 1 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
return
}
递归函数列出当前目录下所有文件
package main
import (
"fmt"
"os"
"path/filepath"
)
func listFile(rootDir string) {
files, err := os.ReadDir(rootDir)
if err != nil {
fmt.Println("error reading directory:", err)
return
}
for _, file := range files {
if file.IsDir() {
listFile(filepath.Join(rootDir, file.Name()))
} else {
fmt.Printf("dir: %s, file: %s\n", rootDir, file.Name())
}
}
}
func main() {
listFile(os.Args[1])
}