回调函数

0.前言

回调函数是一种在编程中常见的技术,通常在异步编程中使用。简单来说,回调函数是一个被传递给另一个函数的函数,它在该函数的某个时间点被调用,以完成某些特定的操作或任务

在Go语言中,可以将函数直接作为参数传递给另一个函数,并在需要时被调用,这样大大的加强了代码的可定制化,但是也一定程度上减少了代码的可读性,所以在实际使用的时候要学会取舍。

1.使用

  • 异步编程:通常情况下,回调函数在异步操作完成后被调用,以便通知调用方该操作已完成或返回异步操作的结果,回调函数可定制化,所以可以做不同的一些工作。
  • 如果某种逻辑,有几种实现方法,那么可以使用回调函数,将实现和逻辑解耦,提高可复用性和可维护性。例如简易计算器和sort.Slice函数(在下面有代码演示)。

2.示例demo

2.1 异步访问URL

首先定义type callback func(data []byte, err error)作为回调函数的类型,随后写读取URL的逻辑(fetch函数),在主函数中,传入回调函数和URL,并通过channel异步地调用此函数,通过WaitGroup等待协程的返回,其中回调函数定义了读取的内容如何使用,是可定制的。

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"sync"
	"time"
)

type callback6 func(data []byte, err error)

var wg sync.WaitGroup

func fetch(url string, c callback6) {
	go func() {
		// 发送HTTP GET请求
		resp, err := http.Get(url)
		if err != nil {
			c(nil, err)
			return
		}
		defer resp.Body.Close()

		// 读取响应数据
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			c(nil, err)
			return
		}
		time.Sleep(2 * time.Second)
		// 调用回调函数,传递响应数据和错误信息
		c(data, nil)
		wg.Done()
	}()
}

func main() {
	url := "https://www.baidu.com"
	wg.Add(1)
	fetch(url, func(data []byte, err error) {
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
		fmt.Println("data:", string(data))
	})
	fmt.Println("Waiting for response...")
	wg.Wait()
	fmt.Println("Response received!")
}
运行结果
Waiting for response...
data: <!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
                </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

Response received!

2.2 Sort.Slice函数

GO语言的Sort.Slice函数就使用了回调函数的思想,将比较大小的逻辑交给用户来实现,将代码的可定制化大大增强了,以下是一个使用的例子,将字符串按首字母大小进行排序:

package main

import (
	"fmt"
	"sort"
)

func main() {
	strs := []string{"apple", "orange", "banana", "pear"}
	sort.Slice(strs, func(i, j int) bool {
		return strs[i] < strs[j]
	})
	fmt.Println(strs)
}
运行结果
[apple banana orange pear]

2.3 简易计算器

在此例子中,定义了一个type Operation func(int, int) int作为回调函数的类型,随后对此函数做了不同的实现,在主函数中,就可以直接传入函数名,即可完成不同的逻辑运算。

package main

import "fmt"

type Operation func(int, int) int

func main() {
	// 加法运算
	result := calculate(10, 5, add)
	fmt.Println("add:", result) // Output: 15

	// 减法运算
	result = calculate(10, 5, subtract)
	fmt.Println("subtract", result) // Output: 5

	// 乘法运算
	result = calculate(10, 5, multiply)
	fmt.Println("multiply:", result) // Output: 50

	// 除法运算
	result = calculate(10, 5, divide)
	fmt.Println("divide:", result) // Output: 2
}

// 计算函数,接受两个整数和一个运算函数作为参数,返回运算结果
func calculate(a, b int, op Operation) int {
	return op(a, b)
}

// 加法函数,接受两个整数并返回它们的和
func add(a, b int) int {
	return a + b
}

// 减法函数,接受两个整数并返回它们的差
func subtract(a, b int) int {
	return a - b
}

// 乘法函数,接受两个整数并返回它们的积
func multiply(a, b int) int {
	return a * b
}

// 除法函数,接受两个整数并返回它们的商
func divide(a, b int) int {
	return a / b
}
运行结果
add: 15
subtract 5
multiply: 50
divide: 2

3.带参数和不带参数的回调函数的示例

1. 不带参数的回调函数

不带参数的回调函数是最简单的形式,通常用于简单的事件处理或状态通知。

package main

import "fmt"

// 定义一个不带参数的回调函数类型
type CallbackFunc func()

// 定义一个函数,接受回调函数作为参数
func execute(callback CallbackFunc) {
    fmt.Println("Executing some logic...")
    callback() // 调用回调函数
}

func main() {
    // 定义一个不带参数的回调函数
    myCallback := func() {
        fmt.Println("Callback executed!")
    }

    // 将回调函数传递给 execute 函数
    execute(myCallback)
}
运行结果
Executing some logic...
Callback executed!

2. 带参数的回调函数

带参数的回调函数可以传递数据给回调函数,使其更加灵活。参数的类型和数量可以根据需要定义。

package main

import "fmt"

// 定义一个带参数的回调函数类型
type CallbackFunc func(string, int) string

// 定义一个函数,接受带参数的回调函数
func process(callback CallbackFunc, input string, number int) {
    fmt.Println("Processing input...")
    result := callback(input, number) // 调用回调函数
    fmt.Println("Result:", result)
}

func main() {
    // 定义一个带参数的回调函数
    myCallback := func(input string, number int) string {
        return fmt.Sprintf("Callback received: %s, Number: %d", input, number)
    }

    // 将回调函数传递给 process 函数
    process(myCallback, "Hello, Go!", 42)
}
运行结果
Processing input...
Result: Callback received: Hello, Go!, Number: 42

3. 匿名函数作为回调

无论是带参数还是不带参数的回调函数,都可以使用匿名函数来实现。匿名函数可以在调用时直接定义,而不需要提前声明。

不带参数的匿名回调
package main

import "fmt"

// 定义一个不带参数的回调函数类型
type CallbackFunc func()

// 定义一个函数,接受回调函数作为参数
func execute(callback CallbackFunc) {
    fmt.Println("Executing some logic...")
    callback() // 调用回调函数
}

func main() {
    // 使用匿名函数作为回调
    execute(func() {
        fmt.Println("Anonymous callback executed!")
    })
}
运行结果
Executing some logic...
Anonymous callback executed!
带参数的匿名回调
package main

import "fmt"

// 定义一个带参数的回调函数类型
type CallbackFunc func(string, int) string

// 定义一个函数,接受带参数的回调函数
func process(callback CallbackFunc, input string, number int) {
    fmt.Println("Processing input...")
    result := callback(input, number) // 调用回调函数
    fmt.Println("Result:", result)
}

func main() {
    // 使用匿名函数作为带参数的回调
    process(func(input string, number int) string {
        return fmt.Sprintf("Anonymous callback received: %s, Number: %d", input, number)
    }, "Hello, Go!", 42)
}
运行结果
Processing input...
Result: Anonymous callback received: Hello, Go!, Number: 42

4. 回调函数的高级用法

异步操作中的回调

回调函数常用于异步操作,例如在完成某个耗时任务后通知调用者。

package main

import (
    "fmt"
    "time"
)

// 定义一个带参数的回调函数类型
type CallbackFunc func(result string)

// 模拟一个异步操作
func asyncOperation(callback CallbackFunc) {
    go func() {
        time.Sleep(2 * time.Second) // 模拟耗时操作
        callback("Operation completed!") // 调用回调函数
    }()
}

func main() {
    fmt.Println("Starting async operation...")
    asyncOperation(func(result string) {
        fmt.Println("Callback:", result)
    })

    // 防止程序立即退出
    time.Sleep(3 * time.Second)
}
运行结果
Starting async operation...
Callback: Operation completed!

总结

  • 不带参数的回调函数:适用于简单的事件处理或状态通知。
  • 带参数的回调函数:可以传递数据给回调函数,更加灵活。
  • 匿名函数:可以在调用时直接定义,适合简单的回调逻辑。
  • 异步操作:回调函数常用于异步操作完成后的通知。
posted @ 2025-03-18 02:08  搁浅~浅浅浅  阅读(27)  评论(0)    收藏  举报