C语言进阶之回调函数详解

发布时间:2023-08-22  
一、函数指针

在讲之前,我们需要了解函数指针。

本文引用地址:

我们都知道,的灵魂是指针,我们经常使用整型指针,字符串指针,结构体指针等。

int *p1;
char *p2;
STRUCT *p3; // STRUCT为我们定义的结构体

但是好像我们一般很少使用函数指针,我们一般使用函数都是直接使用函数调用。

下面我们来了解一下函数指针的概念和使用方法。

1. 概念

函数指针是指向函数的指针变量。

通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。

函数指针可以像一般函数一样,用于调用函数、传递参数。

函数指针的定义方式为:

函数返回值类型   (* 指针变量名) (函数参数列表);

“函数返回值类型”表示该指针变量可以指向具有什么返回值类型的函数;“函数参数列表”表示该指针变量可以指向具有什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可。

我们看到,函数指针的定义就是将“函数声明”中的“函数名”改成“(指针变量名)”。但是这里需要注意的是:“(指针变量名)”两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数。

那么怎么判断一个指针变量是指向变量的指针变量还是指向函数的指针变量呢?首先看变量名前面有没有“”,如果有“”说明是指针变量;其次看变量名的后面有没有带有形参类型的圆括号,如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。

最后需要注意的是,指向函数的指针变量没有 ++ 和 -- 运算。

一般为了方便使用,我们会选择:

typedef  函数返回值类型  (* 指针变量名) (函数参数列表);

比如:

typedef int (*Fun1)(int); //声明也可写成int (*Fun1)(int x),但习惯上一般不这样。
typedef int (*Fun2)(intint); //参数为两个整型,返回值为整型
typedef void (*Fun3)(void); //无参数和返回值
typedef void* (*Fun4)(void*); //参数和返回值都为void*指针

2. 如何用函数指针调用函数

给大家举一个例子:

int Func(int x);   /*声明一个函数*/
int (*p) (int x);  /*定义一个函数指针*/
p = Func;          /*将Func函数的首地址赋给指针变量p*/
p = &Func;         /*将Func函数的首地址赋给指针变量p*/

赋值时函数 Func 不带括号,也不带参数。由于函数名 Func 代表函数的首地址,因此经过赋值以后,指针变量 p 就指向函数 Func() 代码的首地址了。

下面来写一个程序,看了这个程序你们就明白函数指针怎么使用了:

#include 
int Max(intint);  //函数声明
int main(void)
{
    int(*p)(intint);  //定义一个函数指针
    int a, b, c;
    p = Max;  //把函数Max赋给指针变量p, 使p指向Max函数
    printf("please enter a and b:");
    scanf("%d%d", &a, &b);
    c = (*p)(a, b);  //通过函数指针调用Max函数
    printf("a = %dnb = %dnmax = %dn", a, b, c);
    return 0;
}
int Max(int x, int y)  //定义Max函数
{
    int z;
    if (x > y)
    {
        z = x;
    }
    else
    {
        z = y;
    }
    return z;
}

特别注意的是,因为函数名本身就可以表示该函数地址(指针),因此在获取函数指针时,可以直接用函数名,也可以取函数的地址。

p = Max  可以改成  p = &Max
c = (*p)(a, b)  可以改成  c = p(a, b)

3. 函数指针作为某个函数的参数

既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。示例:

#include 
#include 
//前加一个typedef关键字,这样就定义一个名为FunType函数指针类型,而不是一个FunType变量。

//形式同 typedef int* PINT;

typedef void(*FunType)(int);

void myFun(int x);
void hisFun(int x);
void herFun(int x);
void callFun(FunType fp,int x);
int main()
{
    callFun(myFun,100);//传入函数指针常量,作为
    callFun(hisFun,200);
    callFun(herFun,300);
    return 0;
}
void callFun(FunType fp,int x)
{
    fp(x);//通过fp的指针执行传递进来的函数,注意fp所指的函数有一个参数
}
void myFun(int x)
{
    printf("myFun: %dn",x);
}
void hisFun(int x)
{
    printf("hisFun: %dn",x);
}
void herFun(int x)
{
    printf("herFun: %dn",x);
}

输出:

捕获.PNG

4. 函数指针作为函数返回类型

有了上面的基础,要写出返回类型为函数指针的函数应该不难了,下面这个例子就是返回类型为函数指针的函数:

void (* func5(intintfloat))(intint)
{
    ...
}

在这里,func5 以(int, int, float) 为参数,其返回类型为void (*)(int, int)。在中,变量或者函数的声明也是一个大学问,想要了解更多关于声明的话题,可以参考《C专家编程》(1-3章)。这本书的第三章花了整整一章的内容来讲解如何读懂的声明。

5. 函数指针数组

在开始讲解前,最后介绍一下函数指针数组。既然函数指针也是指针,那我们就可以用数组来存放函数指针。下面我们看一个函数指针数组的例子:

/* 方法 1 */
void (*func_array_1[5])(intintfloat);
/* 方法 2 */
typedef void (*p_func_array)(intintfloat);
p_func_array func_array_2[5];

上面两种方法都可以用来定义函数指针数组,它们定义了一个元素个数为5,类型是 * void (*)(int, int, float)  * 的函数指针数组。

6. 函数指针总结

函数指针常量 :Max;函数指针变量:p;

数名调用如果都得如 (*myFun)(10) 这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们才会设计成又可允许 myFun(10) 这种形式地调用(这样方便多了,并与数学中的函数形式一样)。

在函数指针变量也可以存入一个数组内。数组的声明方法:int (*fArray[10]) (int);

二、回调函数

1. 什么是回调函数

我们先来看看百度百科是如何定义回调函数的:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

这段话比较长,也比较绕口。下面我通过一幅图来说明什么是回调:

捕获.PNG

假设我们要使用一个排序函数来对数组进行排序,那么在主程序(Main program)中,我们先通过库,选择一个库排序函数(Library function)。但排序算法有很多,有冒泡排序,选择排序,快速排序,归并排序。

同时,我们也可能需要对特殊的对象进行排序,比如特定的结构体等。库函数会根据我们的需要选择一种排序算法,然后调用实现该算法的函数来完成排序工作。这个被调用的排序函数就是回调函数(Callback function)。

结合这幅图和上面对回调函数的解释,我们可以发现,要实现回调函数,最关键的一点就是要将函数的指针传递给一个函数(上图中是库函数),然后这个函数就可以通过这个指针来调用回调函数了。注意,回调函数并不是C语言特有的,几乎任何语言都有回调函数。在C语言中,我们通过使用函数指针来实现回调函数。

把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。

如果代码立即被执行就称为同步回调,如果过后再执行,则称之为异步回调。

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

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

2. 为什么要用回调函数?

因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。


简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。

捕获.PNG

int Callback()    // /< 回调函数
{
    // TODO
    return 0;
}
int main()     // /<  主函数
{
    // TODO
    Library(Callback);  // /< 库函数通过函数指针进行回调
    // TODO
    return 0;
}

回调似乎只是函数间的调用,和普通函数调用没啥区别。

但仔细看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。

这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,这样有没有觉得很灵活?并且当库函数很复杂或者不可见的时候利用回调函数就显得十分优秀。

3. 怎么使用回调函数?

int Callback_1(int a)   // /< 回调函数1
{
    printf("Hello, this is Callback_1: a = %d ", a);
    return 0;
}
int Callback_2(int b)  // /< 回调函数2
{
    printf("Hello, this is Callback_2: b = %d ", b);
    return 0;
}
int Callback_3(int c)   // /< 回调函数3
{
    printf("Hello, this is Callback_3: c = %d ", c);
    return 0;
}
int Handle(int x, int (*Callback)(int))  // /< 注意这里用到的函数指针定义
{
    Callback(x);
}
int main()
{
    Handle(4, Callback_1);
    Handle(5, Callback_2);
    Handle(6, Callback_3);
    return 0;
}

如上述代码:可以看到,Handle() 函数里面的参数是一个指针,在  main() 函数里调用 

文章来源于:电子产品世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    单片机C语言程序设计之T0控制LED实现二进制计数;AT89C51单片机是美国ATMEL公司生产的低电压、高性能CMOS 8位单片机,具有丰富的内部资源:4kB闪存、128BRAM、32根I/O口线......
    技术。此外,C语言程序具有完善的模块程序结构,从而为软件开发中采用模块化程序设计方法提供了有力的保障。因此,使用C语言进行程序设计已成为软件开发的一个主流。用C语言......
    第4章 汇编语言程序设计;汇编语言语句格式 一、指令格式 [标号:]助记符 操作数1,操作数2 [;注释] 标号:由1-8个字符组成,且第一个字符必须是字母。用于指示指令的地址。 操作数:可使......
    不像其他大多数的 程序设计语言 一样被广泛用于程序设计。 在今天的实际应用中,它通常被应用在底层,硬件操作和高要求的程序优化的场合。 驱动程序、嵌入式操作系统和实时运行程序都需要汇编语言。 汇编语言特点:汇编语言......
    有志于单片机开发的童鞋能做一个参考 第一步: C语言基础,建议买一本谭浩强的《C语言程序设计》,如果不想买,可以在网上找一个PDF版; 微机原理与接口技术基础,建议去中国大学MOOC上看一下相关课程,也可以看C语言......
    机是一个可编程芯片,类似于电脑的CPU,只不过性能、成本、功耗都比cpu要低很多。 那我们通过编写汇编或者C语言程序,下载到基于单片机设计的电路板里面,就能完成相应的产品功能。 比如说打开/关闭灯、温湿......
    开发板,利用矩阵键盘作为按键输入,将数码管作为显示输出 《汇编语言程序设计实践》是为汇编语言程序设计课程而独立开设的实践性课程。对于巩固和加深理解汇编语言程序设计,加强......
    式系统编程:基础声明              每个函数都是执行特定任务的语句的集合,一个或多个函数的集合称为编程语言。每一种语言都包含一些基本要素和语法规则。C语言程序设计是用字符集、变量、数据......
    烧录到单片机中,复位就运行起来了。 预处理 预处理以#开头,在从.C到 .hex之前,扩展C语言程序设计的环境。 #include 的作用将 reg52.h文件复制到编译的源文件中,使用<>或......
    由经验取100us。 读取10次数和将10次数排列的程序如下,这个循环嵌套程序C语言程序设计的书里很常见。 vu16 Tpad_Default_Val=0;这个vu16代表volatile......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>