golang 高阶函数是什么? 将函数作为参数的做法
关键词
那都不讲,波哥来讲吧~
那么下面,波哥带你们更加深入浅出的了解一下这种用法嘎~
高阶函数
回调函数
函数作为参数的用法
设计模式
背景以及为什么会有这篇blog
今天在写短视频脚本时候,copilot 自动给我补全了一段代码。
原始代码 就是一个简单func
补全后,变了个样子
如下:
package main
type Traversal interface {
Traversal(path string, f func(path string, info os.FileInfo) error) error
}
func main() {
traversal := getTraversal()
traversal.Traversal(".", func(path string, info os.FileInfo) error {
fmt.Println(path)
return nil
})
}
我本来是不想写复杂的,但是这个出来了一个非常明显的一个回调函数的问题。
我觉得有必要简单的说明一下,因为我看百度 google 网页上,还有bilibili、什么视频 什么课程,几乎没人好好的去讲解这个知识点。
那都不讲,波哥来讲吧~
我们接下来简单的解释一下这段代码。
这段 Go 语言代码中涉及到一些基本的编程概念,包括接口(interface)、函数作为参数,以及闭包。我将一步步解释这段代码的每个部分。
- 定义接口(Interface):
type Traversal interface {
Traversal(path string, f func(path string, info os.FileInfo) error) error
}
这是一个名为
Traversal
的接口,它定义了一个同名的方法Traversal
。该方法接受两个参数:path string
: 一个字符串,表示文件或目录的路径。f func(path string, info os.FileInfo) error
: 一个函数,这个函数本身接受一个字符串和os.FileInfo
类型的参数,并返回一个错误(error
)。这种将函数作为参数的做法在 Go 中用于实现回调、迭代器或其他自定义操作。
os.FileInfo
的用途:os.FileInfo
是一个接口,提供关于文件的信息,如文件的大小、权限、修改时间等。在这个上下文中,它被用于传递给回调函数f
,以便在函数内部可以使用文件的详细信息。主函数(
main
):
func main() {
traversal := getTraversal()
traversal.Traversal(".", func(path string, info os.FileInfo) error {
fmt.Println(path)
return nil
})
}
getTraversal()
: 这个函数调用(其定义未在代码中给出)应返回一个实现了Traversal
接口的对象。这个对象的具体实现会决定如何遍历文件和目录。traversal.Traversal(".", func(...) {...})
: 这里调用了Traversal
接口的Traversal
方法。第一个参数是"."
,表示当前目录。第二个参数是一个匿名函数(闭包),这个函数打印每个遍历到的文件或目录的路径。
- 函数作为参数(高阶函数): 在 Go 语言中,函数可以作为参数传递给其他函数,这是一种强大的功能,允许你编写灵活且可重用的代码。在这个例子中,你传递的函数用于处理遍历过程中每个文件或目录的操作。
- 打印和返回: 匿名函数中,
fmt.Println(path)
用于打印每个文件或目录的路径。函数返回nil
表示没有发生错误。
通过这种方式,Traversal
方法可以灵活地应用于多种文件遍历任务,而具体的文件处理逻辑则可以通过不同的回调函数来实现,使代码更加模块化和可测试。
那么下面,波哥带你们更加深入浅出的了解一下这种用法嘎~
前言
在 Go 语言中,将函数作为参数传递给另一个函数是一种常见的做法,通常用于实现回调、自定义处理逻辑、策略模式等多种编程场景。这种技术被称为高阶函数(Higher-order function)。理解这种做法的关键是认识到函数本身可以被视为一种类型,就像整数或字符串一样,可以作为参数传递,或作为结果返回。
理解函数作为参数
在你的代码示例中,函数被用作参数来允许调用者自定义文件遍历时的行为。通过传递不同的函数,可以在不改变遍历逻辑的前提下,对遍历到的每个文件执行不同的操作。
常见使用场景
迭代器模式: 当你需要对一组元素进行操作,但操作的具体内容可能随上下文变化时,可以将操作封装为函数,并将其作为参数传递给迭代器。比如对一个文件列表进行处理,可以传递一个处理单个文件的函数。
回调函数: 在异步编程中,当一个长时间运行的任务完成后需要执行某些操作,可以将这些操作定义在一个函数中,并将该函数作为回调参数传递给任务处理函数。例如,在网络请求或数据库操作完成后处理返回的数据。
策略模式: 当你有多种算法或策略可以应用于同一个问题时,可以将每种算法实现为一个函数,并根据需要将不同的函数作为参数传递给执行逻辑。这样可以在运行时动态选择最合适的策略。
事件处理: 在设计事件驱动的系统时,可以将事件处理逻辑封装在函数中,并将这些函数作为事件监听器注册。当事件发生时,相应的函数被调用。
测试和模拟: 在单元测试中,你可能需要模拟一些复杂的行为。通过将函数作为参数传递,可以在测试中插入自定义的模拟逻辑,而不需要依赖真实的复杂环境。
优点
- 灵活性:允许在运行时根据需要动态更换要执行的代码。
- 代码重用:可以将通用的处理逻辑编写在一个地方,通过传递不同的函数进行定制化操作,减少代码重复。
- 解耦:高阶函数可以帮助将算法的核心逻辑与操作的具体实现分离,使得代码更易于管理和扩展。
通过这种方式,你的代码可以在保持高度灵活性和可扩展性的同时,实现强大的功能。在 Go 语言中,这是一种非常强大且常用的模式,值得在实际开发中加以利用。
接下来举例子说明
当然!来看几个具体的例子,以更好地理解将函数作为参数在实际编程中的应用。
示例 1: 数组筛选 (Filter)
比如你想从一个整数数组中筛选出所有偶数。你可以定义一个通用的 Filter
函数,然后传入一个判断是否为偶数的函数作为参数。
package main
import "fmt"
// Filter 函数接受整数数组和一个过滤条件函数
func Filter(numbers []int, condition func(int) bool) []int {
var result []int
for _, num := range numbers {
if condition(num) {
result = append(result, num)
}
}
return result
}
// IsEven 函数用于判断整数是否为偶数
func IsEven(number int) bool {
return number%2 == 0
}
func main() {
array := []int{1, 2, 3, 4, 5, 6}
evenNumbers := Filter(array, IsEven)
fmt.Println("偶数:", evenAllenNumbers)
}
示例 2: 异步执行和回调
假设你有一个发送邮件的函数,由于发送邮件可能需要一些时间,你希望在邮件发送完成后执行某个操作。你可以将这个操作封装在一个回调函数中,并将其作为参数传递给邮件发送函数。
package main
import (
"fmt"
"time"
)
// SendEmail 发送邮件,完成后调用回调函数
func SendEmail(content string, callback func(result string)) {
// 模拟邮件发送延时
time.Sleep(2 * time.Second)
callback("邮件发送成功")
}
func main() {
fmt.Println("开始发送邮件...")
SendEmail("Hello, World!", func(result string) {
fmt.Println(result)
fmt.Println("执行其他相关操作...")
})
fmt.Println("可以继续执行,不需要等待邮件发送完毕...")
}
示例 3: 自定义排序
在 Go 语言中,sort.Slice
函数允许你对任何切片进行排序,通过传递一个比较函数来定义排序的具体行为。
package main
import (
"fmt"
"sort"
)
func main() {
people := []struct {
Name string
Age int
}{
{"Alice", 23},
{"David", 30},
{"Eve", 20},
}
// 按年龄排序
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
fmt.Println("按年龄排序:", people)
}
这些示例展示了将函数作为参数的多种用途,从灵活的数据处理到异步编程,再到自定义复杂的操作,此方法可以极大地提升代码的适应性和重用性。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: