C/C++ 基础 - 回调函数

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() ///< 回调函数 { // TODO return 0; } int main() ///< 主函数 { // TODO Library(Callback); ///< 库函数通过函数指针进行回调 // TODO return 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; }

总结

这下对于回调函数是更加理解了,希望各位在今后的学习中能够看见回调函数不再迷惑!

Read more

Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务并全面实现无损语言壁垒交互 前言 在 OpenHarmony 应用向高性能计算领域扩展的过程中,如何优雅地接入已有的 C/C++ 算法库(如加密引擎、重型图像处理、数学模拟)而又不失跨平台的便捷性?传统的 NAPI 虽然稳健,但在 Flutter 生态中,直接利用 WebAssembly (WASM) 配合 FFI(External Function Interface)的语义可以在一定程度上实现代码的高度复用。wasm_ffi 库为 Flutter 开发者提供了一套在 Dart 环境下调用 WASM

By Ne0inhk
三种适用于Web版IM(即时通讯)聊天信息的加密算法实现方案

三种适用于Web版IM(即时通讯)聊天信息的加密算法实现方案

文章目录 * **第一部分:引言与核心密码学概念** * **1.1 为什么IM需要端到端加密(E2EE)?** * **1.2 核心密码学概念与工具** * **第二部分:方案一:静态非对称加密(基础方案)** * **2.1 方案概述与流程** * **2.2 前端Vue实现(使用node-forge)** * **1. 安装依赖** * **2. 核心工具类 `crypto.js`** * **3. Vue组件中使用** * **2.3 后端Java实现(Spring Boot)** * **1. 实体类** * **2. Controller层** * **3. WebSocket配置** * **2.4 密钥管理、注册与登录集成** * **1. 用户注册/登录时生成密钥** * **2. 密钥设置页面** * **2.

By Ne0inhk
前端代码生成的大洗牌:当 GLM 4.7 与 MiniMax 挑战 Claude Opus,谁才是性价比之王?

前端代码生成的大洗牌:当 GLM 4.7 与 MiniMax 挑战 Claude Opus,谁才是性价比之王?

在 AI 辅助编程领域,长期以来似乎存在一条不成文的铁律:如果你想要最好的结果,就必须为最昂贵的模型买单(通常是 Anthropic 或 OpenAI 的旗舰模型)。然而,随着国产大模型如 GLM 4.7 和 MiniMax M2.1 的迭代,这一格局正在发生剧烈震荡。 最近,一场针对Claude Opus 4.5、Gemini 3 Pro、GLM 4.7 和 MiniMax M2.1 的前端 UI生成横向测评,打破了许多人的固有认知。在这场包含落地页、仪表盘、移动端应用等五个真实场景的较量中,不仅出现了令人咋舌的“滑铁卢”,更诞生了性价比极高的“新王”。 本文将深入拆解这场测试的细节,透过代码生成的表象,探讨大模型在工程化落地中的真实效能与成本逻辑。

By Ne0inhk
【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

🌈个人主页: Hygge_Code🔥热门专栏:从0开始学习Java | Linux学习| 计算机网络💫个人格言: “既然选择了远方,便不顾风雨兼程” 文章目录 * JavaScript 正则表达式详解 * 什么是正则表达式🤔 * JavaScript 正则表达式的定义与使用🥝 * 1. 字面量语法 * 2. 常用匹配方法 * test() 方法🍋‍🟩 * exec() 方法🍋‍🟩 * 正则表达式的核心组成部分🐦‍🔥 * 1. 元字符 * 边界符 * 量词 * 字符类 * 2. 修饰符 * 简单示例🍂 JavaScript 正则表达式详解 正则表达式是处理字符串的强大工具,在 JavaScript 中被广泛应用于表单验证、文本处理和数据提取等场景。本文将从正则表达式的基本概念出发,详细介绍其语法规则和实际应用方法。 什么是正则表达式🤔 正则表达式是用于匹配字符串中字符组合的模式,在 JavaScript

By Ne0inhk