回调函数
在这里插入图片描述

1.概述

1.特征

  1. 1.作为参数传递给另一个函数
  2. 2.在被调用函数执行完毕后被调用

2.使用实例:

  1. 1.事件处理:鼠标点击,键盘输入,网络请求等
  2. 2.异步编程:读取文件,发送邮件,下载文件等
  3. 3.数据处理:对数组进行排序,过滤,映射等
  4. 4.开发插件:WordPress 插件,jQuery 插件等
  5. 5.处理操作系统
  6. 6.框架的API

3.基本概念解释

1.回调:

被传入另一个函数的函数

2.异步编程:

在代码执行时不会阻塞程序运行的方式

3.事件驱动:

程序执行时由外部事件触发而不是顺序执行的方式

2.回调函数的实现

通过函数指针或函数对象来实现

1.函数指针

概念:

存储了一个函数的地址的一个变量

当将函数指针作为参数传递给另一个函数时,另一个函数就可以使用这个指针来调用该函数

返回类型 (*函数指针名称)(参数列表)

实例:

一个回调函数需要接收两个整数参数并返回一个整数值

int (*callback)(int, int);

将一个实际的函数指针赋值给它

int add(int a, int b) {
   return a + b;
}
callback = add;

2.函数对象

概念:

  1. 1.函数对象是一个类的实例,其中重载了函数调用运算符 ()
  2. 2.当将一个函数对象作为参数传递给另一个函数时,另一个函数就可以使用这个对象来调用其重载的函数调用运算符
class callback {
public:
   返回类型 operator()(参数列表) {
       // 函数体
  }
};

实例:

一个回调函数需要接收两个整数参数并返回一个整数值

class Add {
public:
   int operator()(int a, int b) {
       return a + b;
  }
};
Add add;

然后,可以将这个函数对象传递给其他函数,使得其他函数可以使用这个对象来调用其重载的函数调用运算符

3.匿名函数(ambda表达式)

概念:

1.使用匿名函数/lambda表达式是一种常见的回调函数的实现方式

2.Lambda表达式是可以一种作为参数传递给其他函数或对象的匿名函数

2.lambda可以简化c++传递一个函数作为参数传递时使用函数指针或者对象的时候显式的定义函数或者类的繁琐操作

3.提高代码可读性

实例:

#include <iostream>
#include <vector>
#include <algorithm>

void print(int i) {
   std::cout << i << " ";
}

void forEach(const std::vector<int>& v, const void(*callback)(int)) {
   for(auto i : v) {
       callback(i);
  }
}

int main() {
   std::vector<int> v = {1,2,3,4,5};
   forEach(v, [](int i){std::cout << i << " ";});
}
  1. 定义了一个forEach函数,接受一个vector和一个回调函数作为参数
  2. 回调函数的类型是void()(int):一个接受一个整数参数并且返回void的函数指针
  3. 在main函数中,我们使用了Lambda表达式来作为回调函数的实现:{std::cout << i << ” “;}
  4. Lambda表达式的语法:{/ lambda body */}
    1. 1.[]表示Lambda表达式的捕获列表:可以在Lambda表达式中访问的外部变量
    2. 2.{}表示Lambda函数体:Lambda表达式所要执行的代码块
  5. 使用forEach函数:传递了一个Lambda表达式作为回调函数,用于输出vector中的每个元素
  6. forEach函数调用回调函数:实际上是调用Lambda表达式来处理vector中的每个元素(相比传递函数指针或者函数对象更加简洁和易读)
  7. Lambda表达式可以方便地实现回调函数,使得代码更加简洁和易读
  8. 注意Lambda表达式可能会影响代码的性能,需要根据具体情况进行评估和选择

3.回调函数的应用举例

1.异步编程中的回调函数

网络编程中,当某个连接收到数据后,可以使用回调函数来处理数据

void onDataReceived(int socket, char* data, int size);

int main() {
 int socket = connectToServer();
 startReceivingData(socket, onDataReceived);
 // ...
}

void onDataReceived(int socket, char* data, int size) {
 // 处理数据...
}

2.回调函数在GUI编程中的应用

GUI编程中,当用户触发了某个操作时,可以使用回调函数来处理该操作

void onButtonClicked(Button* button);

int main() {
 Button* button = createButton("Click me");
 setButtonClickHandler(button, onButtonClicked);
 // ...
}

void onButtonClicked(Button* button) {
 // 处理按钮点击事件...
}

3.事件处理程序中的回调函数

多线程编程中,当某个线程完成了一次任务后,可以使用回调函数来通知主线程

void onTaskCompleted(int taskId);

int main() {
for (int i = 0; i < numTasks; i++) {
startBackgroundTask(i, onTaskCompleted);
}
// ...
}

void onTaskCompleted(int taskId) {
// 处理任务完成事件...
}

4.回调函数的优点缺点

优点:

  1. 提高代码的复用性和灵活性:回调函数可以将一个函数作为参数传递给另一个函数,从而实现模块化编程,提高代码的复用性和灵活性
  2. 解耦合:回调函数可以将不同模块之间的关系解耦,使得代码更易于维护和扩展。
  3. 可以异步执行:回调函数可以在异步操作完成后被执行,这样避免了阻塞线程,提高应用程序的效率

缺点:

  1. 回调函数嵌套过多会导致代码难以维护:如果回调函数嵌套层数过多,代码会变得非常复杂,难以维护
  2. 回调函数容易造成竞态条件:如果回调函数中有共享资源访问,容易出现竞态条件,导致程序出错
  3. 代码可读性差:回调函数的使用可能会破坏代码的结构和可读性,尤其是在处理大量数据时

5.回调函数与其他编程概念的关系

1.回调函数和闭包的关系

闭包概念:

由一个函数及其相关的引用环境组合而成的实体,可以访问函数外部的变量

闭包功能实现的环境:

回调函数需要访问到它所在的父函数的变量

闭包功能的实现:

将回调函数放在闭包内部,可以将父函数的变量保存在闭包的引用环境中,使得回调函数能够访问到这些变量

闭包还可以保证父函数中的变量在回调函数执行时不会被销毁,从而确保了回调函数的正确性。

2.回调函数和Promise的关系

Promise都是异步编程的实现方式

C++中:

回调函数通常使用函数指针或函数对象来实现。当异步操作完成后,会调用注册的回调函数,以便执行相应的处理逻辑。

Promise中:

Promise可以将异步操作封装成一个Promise对象,并通过链式调用then()方法来注册回调函数,以及catch()方法来捕获异常

当异步操作完成后,Promise会自动根据操作结果触发相应的回调函数

3.回调函数和观察者模式的关系

回调函数和观察者模式:用于实现事件驱动编程的技术

  1. 观察者模式是一种设计模式,它通过定义一种一对多的依赖关系,使得一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新
  2. 回调函数则是一种编程技术,它允许将一个函数作为参数传递给另一个函数,在执行过程中调用这个函数来完成特定的任务

观察者模式执行中:

  1. 当一个被观察的对象发生改变时,会遍历所有的观察者对象,调用其定义好的更新方法,以进行相应的操作
  2. 这里的更新方法就可以看做是回调函数,因为它是由被观察对象调用的,并且在执行过程中可能需要使用到一些外部参数或上下文信息
  3. 因此,可以说观察者模式本身就包含了回调函数的概念,并且借助回调函数来实现观察者模式的具体功能

6.编写高质量的回调函数教程

1.原则

  1. 明确函数的目的和作用域。回调函数应该有一个清晰的目的,同时只关注与其作用范围相关的任务
  2. 确定回调函数的参数和返回值。在定义回调函数时,需要明确它所需的参数和返回值类型,这样可以使调用方更容易使用
  3. 谨慎处理错误和异常。回调函数可能会引发一些异常或错误,需要使用 try-catch 块来处理它们,并给出相应的警告
  4. 确保回调函数不会导致死锁或阻塞。回调函数需要尽可能快地执行完毕,以避免影响程序的性能和稳定性
  5. 使用清晰且易于理解的命名规则。回调函数的命名应该清晰、简洁,并尽可能说明其功能和意义
  6. 编写文档和示例代码。良好的文档和示例代码可以帮助其他开发者更容易地使用回调函数,同时也有助于提高代码的可维护性和可重用性
  7. 遵循编码规范和最佳实践。 编写高质量的回调函数需要遵守编码规范和最佳实践,例如使用合适的命名规则、注释代码等

2.命名规范(并非强制)

  1. 使用动词+名词的方式来描述回调函数的作用,例如onSuccess、onError等
  2. 如果回调函数是用于处理事件的,可以以handleEvent或者onEvent作为函数名
  3. 如果回调函数是用于处理异步操作完成后的结果,可以以onComplete或者onResult作为函数名
  4. 在命名时要注意保持简洁明了,不要过于冗长,也不要使用缩写或者不清晰的缩写
  5. 尽量使用有意义的单词或者短语作为函数名,不要使用无意义的字母或数字组合
  6. 与代码中其他的函数名称保持一致,尽量避免出现命名冲突的情况

3.回调函数参数设计

原理:

  1. 一般回调函数需要接收至少一个参数
  2. 通常是处理结果或错误信息
  3. 其他可选参数根据需要添加

例子:

  1. 如果回调函数是用于处理异步请求的,则第一个参数可能是错误信息(如果存在)
  2. 第二个参数则是请求返回的数据
  3. 也可以将回调函数的上下文传递给该函数作为参数,以便在回调函数中使用

实例:

一个函数 process_data 用于处理数据,但是具体的处理方式需要根据不同的情况进行定制化

参数设计:

void process_data(void *data, int len, void (*callback)(void *result));

在 process_data 函数中,首先会对数据进行处理,然后将处理结果传递给回调函数进行处理

void process_data(void *data, int len, void (*callback)(void *result)) {
// 处理数据
void *result = data; // 这里只是举个例子,实际上需要根据实际情况进行处理

// 调用回调函数
callback(result);
}

使用示例:

#include <stdio.h>

void callback_func(void *result) {
printf("processing result: %s\n", (char *)result); // 这里只是举个例子,实际上需要根据实际情况进行处理
}

int main() {
char data[] = "hello world";
process_data(data, sizeof(data), callback_func);
return 0;
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇