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)、函数作为参数,以及闭包。我将一步步解释这段代码的每个部分。

  1. 定义接口(Interface):
type Traversal interface {
Traversal(path string, f func(path string, info os.FileInfo) error) error
}
  1. 这是一个名为 Traversal 的接口,它定义了一个同名的方法 Traversal。该方法接受两个参数:

    • path string: 一个字符串,表示文件或目录的路径。
    • f func(path string, info os.FileInfo) error: 一个函数,这个函数本身接受一个字符串和 os.FileInfo 类型的参数,并返回一个错误(error)。这种将函数作为参数的做法在 Go 中用于实现回调、迭代器或其他自定义操作。
  2. os.FileInfo 的用途: os.FileInfo 是一个接口,提供关于文件的信息,如文件的大小、权限、修改时间等。在这个上下文中,它被用于传递给回调函数 f,以便在函数内部可以使用文件的详细信息。

  3. 主函数(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 方法。第一个参数是 ".",表示当前目录。第二个参数是一个匿名函数(闭包),这个函数打印每个遍历到的文件或目录的路径。
    1. 函数作为参数(高阶函数): 在 Go 语言中,函数可以作为参数传递给其他函数,这是一种强大的功能,允许你编写灵活且可重用的代码。在这个例子中,你传递的函数用于处理遍历过程中每个文件或目录的操作。
  1. 打印和返回: 匿名函数中,fmt.Println(path) 用于打印每个文件或目录的路径。函数返回 nil 表示没有发生错误。

通过这种方式,Traversal 方法可以灵活地应用于多种文件遍历任务,而具体的文件处理逻辑则可以通过不同的回调函数来实现,使代码更加模块化和可测试。

那么下面,波哥带你们更加深入浅出的了解一下这种用法嘎~

前言

在 Go 语言中,将函数作为参数传递给另一个函数是一种常见的做法,通常用于实现回调、自定义处理逻辑、策略模式等多种编程场景。这种技术被称为高阶函数(Higher-order function)。理解这种做法的关键是认识到函数本身可以被视为一种类型,就像整数或字符串一样,可以作为参数传递,或作为结果返回。

理解函数作为参数

在你的代码示例中,函数被用作参数来允许调用者自定义文件遍历时的行为。通过传递不同的函数,可以在不改变遍历逻辑的前提下,对遍历到的每个文件执行不同的操作。

常见使用场景

  1. 迭代器模式: 当你需要对一组元素进行操作,但操作的具体内容可能随上下文变化时,可以将操作封装为函数,并将其作为参数传递给迭代器。比如对一个文件列表进行处理,可以传递一个处理单个文件的函数。

  2. 回调函数: 在异步编程中,当一个长时间运行的任务完成后需要执行某些操作,可以将这些操作定义在一个函数中,并将该函数作为回调参数传递给任务处理函数。例如,在网络请求或数据库操作完成后处理返回的数据。

  3. 策略模式: 当你有多种算法或策略可以应用于同一个问题时,可以将每种算法实现为一个函数,并根据需要将不同的函数作为参数传递给执行逻辑。这样可以在运行时动态选择最合适的策略。

  4. 事件处理: 在设计事件驱动的系统时,可以将事件处理逻辑封装在函数中,并将这些函数作为事件监听器注册。当事件发生时,相应的函数被调用。

  5. 测试和模拟: 在单元测试中,你可能需要模拟一些复杂的行为。通过将函数作为参数传递,可以在测试中插入自定义的模拟逻辑,而不需要依赖真实的复杂环境。

优点

  • 灵活性:允许在运行时根据需要动态更换要执行的代码。
  • 代码重用:可以将通用的处理逻辑编写在一个地方,通过传递不同的函数进行定制化操作,减少代码重复。
  • 解耦:高阶函数可以帮助将算法的核心逻辑与操作的具体实现分离,使得代码更易于管理和扩展。

通过这种方式,你的代码可以在保持高度灵活性和可扩展性的同时,实现强大的功能。在 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 协议》,转载必须注明作者和本文链接
嗨,我是波波。曾经创业,有收获也有损失。我积累了丰富教学与编程经验,期待和你互动和进步! 公众号:上海PHP自学中心 付费知识星球:破解面试:程序员的求职导师
讨论数量: 2

Filter(number []int, func(path string, func()) {} 中的 number 参数在 go 中应该称为切片,不够严谨呀,与php还是有区别的,php感觉在这方面挺随意的

1周前 评论
liziyu 1周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
司机 @ 欣昊玉
文章
276
粉丝
341
喜欢
559
收藏
1109
排名:63
访问:12.3 万
私信
所有博文
社区赞助商