回调函数

回调函数

一、什么是回调函数

先看看来自 Stack Overflow 某位大神简洁明了的表述:A "callback" is any function that is called by another function which takes the first function as a parameter(回调函数是由接收一个函数作为参数的另一个函数调用的任何函数)。是不是很抽象?

简单来说,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

下面通过一个小栗子深入理解一下什么事回到函数。

1.1 一个简单的栗子

现在有三个函数 func1()func2(void *funcAddr)func3(),他们之间的关系是:func1 调用 func2,并将 func3 的地址作为参数传递给 fun2。那么在函数 func2 执行的过程中,通过指针调用了函数 func3,这个动作就叫做回调(callback)。而先被当做指针传入、后又被调用的函数 func3 就是回调函数。

#include <stdio.h>

void func3()
{
    printf("func3\n");
}

void func2(void *funcAddr)
{
    // 类型转换
    void (*func)();
    func = funcAddr;

    func();// 通过指针 func 调用 func3(),该动作就是回调
}

void func1()
{
    func2(func3);// 将func3地址传递给func2
}

int main()
{
    func1();

    return 0;
}

二、为什么要用回调函数

在上边的例子中,就算不传递 func3 的地址,也可以在 func2 直接调用,为何要多此一举呢?

要回答这个问题,我们先来了解一下回调函数的好处和作用,那就是解耦。就是因为这个特点,普通函数代替不了回调函数。

下面来看看维基百科上的一张图片:

img

下面以一段不完整的 C 语言代码来呈现上图的意思:

#include <stdio.h>
#include "softwareLib.h" // 包含 Library 的头文件

int Callback() // Callback Function
{
    // TODO
    return 0;
}

// Main program
int main() 
{
    Library(Callback);

    return 0;
}

乍一看,回调似乎只是函数间的调用,和普通函数调用没啥区别,但仔细一看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,并且丝毫不需要修改库函数的实现,这就是解耦。

再仔细看看,主函数和回调函数是在同一层的,而库函数在另外一层,想一想,如果库函数对我们不可见,我们修改不了库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数了,这也就是在日常工作中常见的情况。现在再把main()Library()Callback()函数套回前面 func1、func2 和 func3 函数里面,是不是就更明白了。

三、函数指针

通过上面的讲解,我们很容易知道,在 C 语言中,只能通过函数指针来实现回调函数,接下来我将对函数指针做个简单的总结。

3.1 什么是函数指针

首先它是一个指针,只是这个指针指向的是一个函数。指针变量可以指向变量的地址、数组、字符串、动态分配地址等等,同时也可指向一个函数。每个函数在编译的时候,系统会分配给该函数一个入口地址,而函数名就表示这个入口地址。那么指向函数的指针变量称为函数指针变量。

3.2 怎么用

指向函数的指针包含了函数的地址,可以通过它来调用函数。

声明格式:类型说明符 (*函数名)(形参列表),其实这里不能称为函数名,应该叫做指针的变量名。指针的声明必须和它指向函数的声明保持一致。

小试牛刀

函数声明 函数指针
void func(); void (*funcAddr)();
char *func(); char *(*funcAddr)();
int funcMax(int a, int b); int (*funcAddr)(int, int);

①.c

#include <stdio.h>

void func()
{
    printf("func\n");
}

void test(void (*funcAddr)())
{
    funcAddr();
}

int main()
{
    test(func);

    return 0;
}

②.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *func()
{
    char *buf = (char *)malloc(100);
    strncpy(buf, "hello world", 100 - 1);
    return buf;
}

void test(char *(*funcAddr)())
{
    char *buf = funcAddr();
    printf("buf[%s]\n", buf);
}

int main()
{
    test(func);

    return 0;
}

③.c

#include <stdio.h>

int funcMax(int a, int b)
{
    return a > b ? a : b;
}

void test(int (*funcAddr)(int, int))
{
    int a = 10;
    int b = 20;
    
    int res = funcAddr(a, b);
    printf("max(%d, %d) = %d\n", a, b, res);
}

int main()
{
    test(funcMax);

    return 0;
}

对于函数指针的声明,我们还可以通过 typedef 简化一下,以 ①.c 为例,修改如下:

#include <stdio.h>

typedef void (*pFunc)();

void func()
{
    printf("func\n");
}

void test(pFunc funcAddr)
{
    funcAddr();
}

int main()
{
    test(func);

    return 0;
}

如果想要深入学习,可参考如下文章:

参考资料

posted @ 2022-10-06 13:00  MElephant  阅读(149)  评论(0编辑  收藏  举报