C/C++ 基础 - 回调函数
目录
前言
回调函数预备知识
函数指针
什么是函数指针
函数指针的语法
如何用函数指针调用函数
函数指针作为函数的参数
函数指针作为函数返回类型
函数指针数组
回调函数
什么是回调函数
为什么要用回调函数
怎么使用回调函数
总结
前言
在写项目的时候,对于回调函数一知半解,这次将重新学习一下,重新理解一下 回调函数 的魅力所在
回调函数预备知识
在讲回调函数 回调函数 回调函数之前,我们需要了解函数指针。
我们都知道,C语言的灵魂是指针,我们经常使用整型指针,字符串指针,结构体指针等
函数指针
int *p1; // p1是一个指向整数(int)类型的指针变量,可以存储一个int类型数据的地址
char *p2; // p2是一个指向字符(char)类型的指针变量,可以存储一个char类型数据的地址
STRUCT *p3; // p3是一个指向结构体类型STRUCT的指针变量,STRUCT是我们定义的结构体类型
但是好像我们一般很少使用函数指针,我们一般使用函数都是直接使用函数调用。
下面我们来了解一下函数指针的概念和使用方法。
什么是函数指针
函数指针也是个指针,但是和通常的指针不一样,通常的指针指向的是整型、字符型或数组等变量
而函数指针,指向的是函数
函数指针的语法
返回类型 (*指针变量名)(参数类型列表);
-
返回类型: 函数返回的数据类型(如 int double void 等)。
-
指针变量名: 你给这个函数指针起的名字。
-
参数类型列表: 函数接受的参数类型(如果没有参数,可以留空或写 void )。
这里需要注意的是:(*指针变量名)两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针。而是一个函数声明了,即声明了一个返回值类型为指针型的函数。
那么怎么判断一个指针变量是指向变量的还是指向函数呢?
- 首先看变量名前面有没有 “*”,如果有 “*” 说明是指针变量;
- 其次看变量名有没有带 (),如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。
最后需要注意的是:指向函数的指针变量没有 ++ 和 – 运算。
一般为了方便使用,我们会选择用typedef进行函数指针类型的别名定义
// 定义一个函数指针类型 别名为:Fun1,它指向返回 int 类型、接受一个 int 参数的函数
typedef int (*Fun1)(int);// 定义一个函数指针类型 别名为:Fun2,它指向返回 int 类型、接受两个参数(int 和 int)的函数
typedef int (*Fun2)(int, int);// 定义一个函数指针类型 别名为:Fun3,它指向返回 void 类型、无参数的函数
typedef void (*Fun3)(void);// 定义一个函数指针类型 别名为:Fun4,它指向返回 void* 类型、接受一个 void* 参数的函数
typedef void* (*Fun4)(void*);
为什么要使用typedef呢?

如何用函数指针调用函数
举个例子
int Func(int x); /*声明一个函数*/
int (*p) (int x); /*定义一个函数指针*/
p = Func; /*将Func函数的首地址赋给指针变量p*/
p = &Func; /*将Func函数的首地址赋给指针变量p*/
赋值时函数 Func 不带括号,也不带参数。由于函数名 Func 代表函数的首地址,因此经过赋值以后,指针变量 p 就指向函数 Func() 代码的首地址了。
下面来写一个程序,看了这个程序你们就明白函数指针怎么使用了:
特别注意的是,因为函数名本身就可以表示该函数地址(指针),因此在获取函数指针时,可以直接用函数名,也可以用&取函数的地址。
函数指针作为函数的参数
我们见过,函数的形参是指针的
// 函数接受一个整数指针作为参数,并修改该值
void modifyValue(int *ptr) {*ptr = 20; // 修改指针指向的值
}
那么函数指针作为指针,肯定也能放到函数的形参中的
#include <stdio.h>
// 定义一个函数类型:别名为operation,返回类型是int,参数类型是int和int
typedef int (*operation)(int, int);
// 一个加法函数
int add(int a, int b) {return a + b;
}
// 一个减法函数
int sub(int a, int b) {return a - b;
}
// 函数 modifyValue 接受一个函数指针作为参数,并调用它
void modifyValue(int *ptr, operation op) {*ptr = op(*ptr, 5); // 使用传入的函数指针 op 来修改 ptr 指向的值
}int main() {int num = 10;printf("Before: %d\n", num); // 输出修改前的值// 传递加法函数的指针modifyValue(&num, add);printf("After add: %d\n", num); // 输出加法操作后的值// 传递减法函数的指针modifyValue(&num, sub);printf("After sub: %d\n", num); // 输出减法操作后的值return 0;
}
运行结果

为什么函数指针我们用的是别名operation,在下面调用modifyValue的时候,直接传入add,sub就能识别了呢?
在C语言中,函数名(如add或sub)在没有括号时会自动转换为指向该函数的指针。这与operation类型 的期望使用一个函数指针相匹配。
函数指针作为函数返回类型
有了上面的基础,要写出返回类型为函数指针的函数应该不难了,下面这个例子就是返回类型为函数指针的函数:
void (* func5(int, int, float ))(int, int)
{...
}
在这里, func5 以 (int, int, float) 为参数,其返回类型为 void (\*)(int, int) 。
函数指针数组
在开始讲解回调函数前,最后介绍一下函数指针数组。既然函数指针也是指针,那我们就可以用数组来存放函数指针。下面我们看一个函数指针数组的例子:
/* 方法1 */
void (*func_array_1[5])(int, int, float);/* 方法2 */
typedef void (*p_func_array)(int, int, float);
p_func_array func_array_2[5];
回调函数
什么是回调函数
我们先来看看百度百科是如何定义回调函数的:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
这段话比较长,也比较绕口。下面我通过一幅图来说明什么是回调:

假设我们要使用一个排序函数来对数组进行排序,那么在 主程序(Main program) 中,我们先通过库,选择一个 库排序函数(Library function)。但排序算法有很多,有冒泡排序,选择排序,快速排序,归并排序。同时,我们也可能需要对特殊的对象进行排序,比如特定的结构体等。库函数会根据我们的需要选择一种排序算法,然后调用实现该算法的函数来完成排序工作。这个被调用的排序函数就是回调函数(Callback function)。
结合这幅图和上面对回调函数的解释,我们可以发现,要实现回调函数,最关键的一点就是要将函数的指针传递给一个函数(上图中是库函数),然后这个函数就可以通过这个指针来调用回调函数了。注意,回调函数并不是C语言特有的,几乎任何语言都有回调函数。在C语言中,我们通过使用函数指针来实现回调函数。
总结:把一段可执行的代码(函数)像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫 回调 。如果代码立即被执行就称为同步回调,如果过后再执行,则称之为异步回调
回调函数 就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是 回调函数 。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
为什么要用回调函数
因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。
简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。

int Callback() ///< 回调函数
{// TODOreturn 0;
}
int main() ///< 主函数
{// TODOLibrary(Callback); ///< 库函数通过函数指针进行回调// TODOreturn 0;
}
回调似乎只是函数间的调用,和普通函数调用没啥区别。
但仔细看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。
这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,这样有没有觉得很灵活?并且当库函数很复杂或者不可见的时候利用回调函数就显得十分优秀。如果还不明白。看一下下面的例子就会恍然大悟了
怎么使用回调函数
int Callback_1(int a) //回调函数1
{cout << "Hello, this is Callback_1。" << "a=" << a << endl;return 0;
}int Callback_2(int b) //回调函数2
{cout << "Hello, this is Callback_2。" << "b=" << b << endl;return 0;
}int Callback_3(int c) //回调函数3
{cout << "Hello, this is Callback_3。" << "c=" << c << endl;return 0;
}int Handle(int x, int (*Callback)(int x))
{Callback(x); return 0;
}int main()
{Handle(10, Callback_1);Handle(20, Callback_2);Handle(30, Callback_3);return 0;
}
可能同学会有一个疑问:Callback(x); 这里就一个函数是怎么做到识别Callback_1 2 3的呢?
Handle(10, Callback_1); Handle(20, Callback_2); Handle(30, Callback_3);本质上是把Callback_1 2 3函数的地址传给Handle函数。Handle函数里的Callback(x); 它存储的是具体回调函数的内存地址,比如说你传入的是 Handle(10, Callback_1);,那Callback存储的是Callback_1的地址,存储的x变量是10,它就会找到Callback_1并且把10传给它

下面是一个四则运算的简单回调函数例子:
typedef float (*Operation)(float, float);float ADD(float a, float b)
{cout << "a+b=" << a + b << endl;return a + b;
}
float SUB(float a, float b)
{cout << "a-b=" << a - b << endl;return a - b;
}
float MUL(float a, float b)
{cout << "a*b=" << a * b << endl;return a * b;
}
float DIV(float a, float b)
{if (b == 0) {printf("Error: Division by zero!\n");return 0;}cout << "a/b=" << a / b << endl;return a / b;
}float add_sub_mul_div(float a, float b, Operation op)
{op(a, b);return 0;
}int main()
{add_sub_mul_div(1.1, 2.2, ADD);add_sub_mul_div(1.1, 2.2, SUB);add_sub_mul_div(1.1, 2.2, MUL);add_sub_mul_div(1.1, 2.2, DIV);return 0;
}
总结
这下对于回调函数是更加理解了,希望各位在今后的学习中能够看见回调函数不再迷惑!
相关文章:
C/C++ 基础 - 回调函数
目录 前言 回调函数预备知识 函数指针 什么是函数指针 函数指针的语法 如何用函数指针调用函数 函数指针作为函数的参数 函数指针作为函数返回类型 函数指针数组 回调函数 什么是回调函数 为什么要用回调函数 怎么使用回调函数 总结 前言 在写项目的时候&#x…...
【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的消息队列:使用 RabbitMQ 实现异步处
<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、开篇整…...
高项第十六章——项目采购管理
什么是采购管理?项目采购管理包括从项目团队外部采购或获取所需产品、服务或成果的各个过程。 项目采购管理包括编制和管理协议所需的管理和控制过程。 16_1 管理基础 什么是协议?协议是用于明确项目初步意向的任何文件或沟通结果,协议的范…...
宽带的带宽
宽带的带宽是指在一定时间内通过网络连接传输数据的能力,通常用**比特率(bit/s,即每秒传输的比特数)**来表示,比如100Mbps(兆比特每秒)。带宽越大,理论上网络传输数据的速度越快&…...
DeepSeek分析仿写选题应该怎么做?
目录 选题分析:AIGC在学术写作中的应用及其与作者背景的关系 1. 选题背景与意义 2. 研究问题 3. 研究方法 4. 主要发现 5. 研究贡献 6. 研究局限与未来方向 7. 结论 8. 未来研究方向 大家好这里是AIWritePaper官方账号,官网👉AIWrit…...
19840 Dijkstra求最短路2
19840 Dijkstra求最短路2 相较于1,数据增强了,要用堆来优化,也就是优先队列。 ⭐️难度:中等 🌟考点:Dijkstra、最短路问题 📖 📚 import java.util.*;public class Main {static…...
docker-compose部署prometheus+grafana+node_exporter
目录 docker-compose文件 配置文件 文件层级关系,docker-compose和配置文件位于同级目录 node_exporter页面json文件 涉及离线包 一.docker-compose文件 [rootsulibao prometheus]# cat docker-compose.yml version: 3services:prometheus:image: registry.c…...
Oracle数据库数据编程SQL<递归函数详解>
递归函数是一种在函数体内直接或间接调用自身的函数。这种函数通过将复杂问题分解为更小的相同问题来解决特定类型的编程任务。 目录 一、递归函数基本概念 1. 递归定义 2. 递归工作原理 二、递归函数示例 1. 经典阶乘函数 2. 斐波那契数列 3. 计算数字位数 三、递归查…...
Redis-08.Redis常用命令-有序集合操作命令
一.有序集合操作命令 ZADD key score 1 member1 [score2 member2]: zadd zset 10.0 a 10.5 b ZRANGE key start stop [WITHSCORES]: zrange zset 0 -1 为何顺序为a,c,b? 因为 zrange zset 0 -1 withscores zrange key start …...
LLaMA-Factory使用实战
LLaMA-Factory使用实战 项目介绍 项目地址:https://github.com/hiyouga/LLaMA-Factory 中文文档:安装 - LLaMA Factory 快速开始文档:https://zhuanlan.zhihu.com/p/695287607(推荐参考) 远程服务器通过本地代理加…...
读一本书,骑行万里路:与维乐 Angel Rise+骑行看世界
最近读到了一本名为《自行车改变的世界:女性骑行的历史》的书,才发现原来女性的骑行自由来得并不轻易,激励着每一位女性勇敢地踏上骑行之路。而我一直在使用的维乐坐垫品牌,除了产品专业之外,也一直都非常关注女性骑行…...
【大模型】SpringBoot整合LangChain4j实现RAG检索实战详解
目录 一、前言 二、LangChain4j 介绍 2.1 什么是LangChain4j 2.2 LangChain4j 主要特点 2.3 Langchain4j 核心组件 三、RAG介绍 3.1 什么是RAG 3.2 RAG工作流程 3.2.1 补充说明 3.3 Embedding模型 3.3.1 RAG实际使用步骤 3.3.2 什么是Embedding 3.3.3 Embedding 技…...
流动的梦境:GPT-4o 的自回归图像生成深度解析
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
北大人工智能研究院朱松纯:“中国的AI叙事” 存在认知偏差
3月29日,在2025中关村论坛通用人工智能论坛上,北京通用人工智能学院院长,北京大学人工智能研究院、智能学院院长朱松纯表示,目前,行业对AI的讨论几乎被大模型能力所占据,而基础学科、原始创新与智能本质的研…...
习题1.26
解释题,说简单也简单,难在如何表达清楚。 首先解释下代码的变化 (defn expmod[base exp m](cond ( exp 0) 1(even? exp) (mod (square (expmod base (/ exp 2) m)) m):else (mod (* base (expmod base (- exp 1) m)) m)))(defn expmod[base exp m](co…...
FPGA调试笔记
XILINX SSTL属性电平报错 错误如下: [DRC BIVRU-1] Bank IO standard Vref utilization: Bank 33 contains ports that use a reference voltage. In order to use such standards in a bank that is not configured to use INTERNAL_VREF, the banks VREF pin mu…...
基于Java(SSM)+Mysql实现移动大厅业务办理(增删改查)
基于 SSM 框架的移动业务大厅 数据库需要自行创建! 一、 整体基本实现情况 对本学期的 Java 作业 1 的 SOSO 移动大厅进行改进, 基于 SSM、JSP、Maven、Tomcat、MySQL 等实现。 二、 实现详情 1、 工程结构图 2、 工程结构各部分实现 (…...
音视频 ColorSpace色彩空间详解
前言 基于前篇介绍YUV格式,本文继续介绍另一个重要概念颜色空间,又叫色彩空间;主要用于在音视频开发中的色彩空间转换。 色彩空间Color Space 色彩空间由色彩模型和色域共同定义。当色彩模型与特定的描述相关联以后,就称为色彩空间。 色彩模型Color Model 色彩模型Col…...
【字符设备驱动开发–IMX6ULL】(一)简介
【字符设备驱动开发–IMX6ULL】(一)简介 一、Linux驱动与裸机开发区别 1.裸机驱动开发回顾 1、底层,跟寄存器打交道,有些MCU提供了库。 spi.c:主机驱动(换成任何一个设备之后只需要调用此文件里面的…...
45 55跳跃游戏解题记录
先是55跳跃游戏,暴力解法会怎样?会超出时间限制,而且有很多细节要注意: func canJump(nums []int) bool {// 处理空数组情况,当nums只剩一个元素时,nums[i:]导致越界。if len(nums) 0 {return false}// 如…...
C++_STL之list篇
一、list的介绍 std::list是C标准模板库(STL)中的一个双向链表容器。与vector和deque不同,list不支持随机访问,但它在任何位置插入和删除元素都非常高效。 1.基本特性 (1)双向链表结构:每个元素都包含指向前驱和后继的指针 (2)非连续存储&…...
Go中的逃逸分析
什么是逃逸? 逃逸是指一个变量本来应该分配在栈(stack)上,但由于某些原因,最终被分配到了堆(heap)上。 类比: 栈就像一个临时的快餐盒,用来存放短期使用的数据。堆就像…...
Spring 声明式事务 万字详解(通俗易懂)
目录 Δ前言 一、声明式事务快速入门 1.为什么需要声明式事务? 2.定义: 3.应用实例: 二、声明式事务的传播机制 1.引出问题: 2.传播机制分类: 3.应用实例: 三、声明式事务的隔离机制 1.四种隔离级别&…...
MySQL 当中的锁
MySQL 当中的锁 文章目录 MySQL 当中的锁MySQL 中有哪些主要类型的锁?请简要说明MySQL 的全局锁有什么用?MySQL 的表级锁有哪些?作用是什么?元数据锁(MetaData Lock,MDL)意向锁(Inte…...
fyrox 2D和3D游戏的制作
目录 fyrox介绍 1. 核心特性 1.1 高性能渲染 1.2 跨平台支持 1.3 物理引擎集成 1.4 脚本系统 1.5 场景管理 2. 架构设计 2.1 渲染器 2.2 资源管理器 2.3 输入系统 2.4 音频引擎 2.5 网络模块 3. 使用场景 3.1 2D游戏 3.2 3D游戏 3.3 模拟与教育应用 4. 在游戏…...
[Linux]基础IO
基础IO C文件IO相关操作磁盘文件与内存文件inode(index node)硬链接与软连接硬链接软连接总结 动静态库静态库动态库总结 C文件IO相关操作 当前路径:进程运行的时候,所处的路径叫做当前路径 打开文件的时候,一定是进…...
力扣刷题-热题100题-第27题(c++、python)
21. 合并两个有序链表 - 力扣(LeetCode)https://leetcode.cn/problems/merge-two-sorted-lists/description/?envTypestudy-plan-v2&envIdtop-100-liked 常规法 创建一个新链表,遍历list1与list2,将新链表指向list1与list2…...
Vue3 其它API Teleport 传送门
Vue3 其它API Teleport 传送门 在定义一个模态框时,父组件的filter属性会影响子组件的position属性,导致模态框定位错误使用Teleport解决这个问题把模态框代码传送到body标签下...
windows下安装sublime
sublime4 alpha 4098 版本 下载 可以根据待破解的版本选择下载 https://www.sublimetext.com/dev crack alpha4098 的licence 在----- BEGIN LICENSE ----- TwitterInc 200 User License EA7E-890007 1D77F72E 390CDD93 4DCBA022 FAF60790 61AA12C0 A37081C5 D0316412 4584D…...
golang 的strconv包常用方法
目录 1. 字符串与整数的转换 2. 字符串与浮点数的转换 3. 布尔值的转换 4. 字符串的转义 5. 补充:rune 类型的使用 方法功能详解 代码示例: 1. 字符串与整数的转换 方法名称功能描述示例Atoi将字符串转换为十进制整数。strconv.Atoi("123&q…...
