突破编程_C++_C++11新特性(function与bind绑定器)

1 可调用对象

C++ 中的可调用对象(Callable Objects)是指那些能够被调用执行的对象。这包括了函数、函数对象(也叫做仿函数,即重载了 operator() 的类或者结构体)、Lambda 表达式以及任何具有 operator() 的成员函数的对象。可调用对象在 C++ 标准库算法(如 std::for_each、std::transform 等)以及回调函数等场景中广泛使用。

1.1 函数作为可调用对象

任何普通的函数都可以作为可调用对象使用。

#include <iostream>  
#include <vector>  
#include <algorithm>  // 普通函数  
void print_number(int num) {  std::cout << num << std::endl;  
}  int main() 
{  std::vector<int> numbers = {1, 2, 3, 4, 5};  std::for_each(numbers.begin(), numbers.end(), print_number);  return 0;  
}

1.2 函数对象(仿函数)

函数对象是通过重载operator()的类或者结构体实现的。

#include <iostream>  
#include <vector>  
#include <algorithm>  // 函数对象(仿函数)  
struct PrintNumber {  void operator()(int num) const {  std::cout << num << std::endl;  }  
};  int main() 
{  std::vector<int> numbers = {1, 2, 3, 4, 5};  PrintNumber print;  std::for_each(numbers.begin(), numbers.end(), print);  return 0;  
}

1.3 Lambda 表达式

C++11 引入了 Lambda 表达式,它可以很方便地定义匿名函数对象。

#include <iostream>  
#include <vector>  
#include <algorithm>  int main() 
{  std::vector<int> numbers = {1, 2, 3, 4, 5};  // Lambda 表达式  auto print_lambda = [](int num) {  std::cout << num << std::endl;  };  std::for_each(numbers.begin(), numbers.end(), print_lambda);  return 0;  
}

1.4 可调用对象的类型

在 C++ 中,可调用对象的类型通常不是直接可以使用的,但是可以使用 std::function 来包装它们,使得可调用对象的类型变得统一。

#include <iostream>  
#include <vector>  
#include <algorithm>  
#include <functional>  // 普通函数  
void print_number(int num) {  std::cout << num << std::endl;  
}  // 函数对象(仿函数)  
struct PrintNumber {  void operator()(int num) const {  std::cout << num << std::endl;  }  
};  // 使用 std::function 包装可调用对象  
void use_callable(const std::function<void(int)>& callable, int num) {  callable(num);  
}  int main() 
{  // 使用 std::function 包装函数  use_callable(print_number, 12);  // 使用 std::function 包装函数对象  use_callable(PrintNumber(), 12);  // 使用 std::function 包装 Lambda 表达式  use_callable([](int num) { std::cout << num << std::endl; }, 12);  return 0;  
}

1.5 注意事项

使用可调用对象的注意事项如下:

  • 当使用std::function时,注意其性能可能稍低于直接使用函数调用,因为它需要进行类型擦除。在性能敏感的代码中,直接使用函数调用可能更合适。
  • Lambda 表达式非常灵活,可以捕获局部变量(通过值或引用),具有不同的返回类型,并且可以有默认参数等。
  • 函数对象(仿函数)可以包含状态,这是普通函数和 Lambda 表达式所不具备的。

2 std::function

std::function 是 C++11 标准库中的一个通用、多态的函数封装器。它允许将任何可调用对象(如函数、Lambda 表达式、函数对象或绑定表达式等)作为参数传递,或者赋值给变量。std::function 的灵活性使其成为实现回调、事件驱动编程以及更高级的编程技术的有力工具。

2.1 基本用法

首先,需要包含 <functional> 头文件来使用 std::function。

#include <iostream>  
#include <functional> // 包含 std::function  // 一个普通的函数  
void print_hello() {  std::cout << "Hello, World!" << std::endl;  
}  int main() 
{  // 创建一个 std::function 对象,它可以调用没有参数的函数  std::function<void()> func = print_hello;  // 调用 func,这将调用 print_hello 函数  func(); // 输出: Hello, World!  return 0;  
}

2.2 使用 Lambda 表达式

std::function 也可以用来封装 Lambda 表达式。

#include <iostream>  
#include <functional>  int main() 
{  // 创建一个 std::function 对象,它可以调用接受一个 int 参数并返回 void 的函数或 Lambda  std::function<void(int)> func;  // 定义一个 Lambda 表达式,并将其赋值给 func  func = [](int x) {  std::cout << "Value: " << x << std::endl;  };  // 调用 func,传递一个整数参数  func(42); // 输出: Value: 42  return 0;  
}

2.3 使用成员函数

std::function 也可以用来包装类的成员函数,但这需要一些额外的处理,因为成员函数需要一个对象来调用。通常使用 std::bind 或者 Lambda 表达式来包装成员函数。

#include <iostream>  
#include <functional>  class MyClass {  
public:  void print_value(int x) {  std::cout << "Value in MyClass: " << x << std::endl;  }  
};  int main() 
{  MyClass obj;  // 使用 Lambda 表达式包装成员函数  std::function<void(int)> func = [&obj](int x) {  obj.print_value(x);  };  // 调用 func,这将调用 MyClass 的成员函数  func(100); // 输出: Value in MyClass: 100  return 0;  
}

2.4 std::function 的类型擦除

std::function 的类型擦除是 std::function 能够封装任意可调用对象(如函数、Lambda 表达式、函数对象等)并统一调用的关键机制。类型擦除意味着在运行时,std::function 对象隐藏了它所包含的可调用对象的实际类型,使得可以以一种统一的方式处理它们。

(1)类型擦除的实现原理

std::function 内部通常使用小对象优化(Small Object Optimization, SOO)和类型擦除技术来存储和调用可调用对象。小对象优化是一种减少内存分配开销的技术,它允许 std::function 在内部直接存储较小的对象,避免动态内存分配。对于较大的对象,std::function 则会使用动态内存分配。

类型擦除则是通过虚函数和函数指针来实现的。std::function 内部维护了一个指向一个函数对象的指针,这个函数对象负责调用实际的可调用对象。这个函数对象通常是一个模板类,它根据封装的可调用对象的类型进行特化。当调用 std::function 对象时,它会通过这个函数对象的虚函数表找到正确的调用方式,然后间接调用实际的可调用对象。

(2)类型擦除的优缺点

优点:

  • 统一接口:类型擦除使得 std::function 可以提供一个统一的接口来调用不同类型的可调用对象,简化了代码。
  • 灵活性:由于可以封装任意可调用对象,std::function 提供了极大的灵活性,使得可以轻松地传递回调函数、实现策略模式等。

缺点:

  • 性能开销:类型擦除涉及到虚函数表查找和可能的动态内存分配,这会导致一定的性能开销。在性能敏感的代码中,这可能会成为一个问题。
  • 类型安全减弱:由于类型信息在运行时被隐藏,类型安全在一定程度上被削弱。这意味着在编译时无法检查 std::function 对象所封装的可调用对象的类型是否正确。

(3)使用注意事项

  • 避免不必要的类型擦除:如果代码的性能非常关键,且可调用对象的类型已知且固定,那么最好直接使用具体的可调用对象类型,而不是使用 std::function 进行类型擦除。
  • 注意性能影响:在使用 std::function 时,要意识到它可能带来的性能开销,并在必要时进行性能测试和优化。
  • 确保可调用对象兼容性:传递给 std::function 的可调用对象必须满足其期望的签名(即参数类型和返回类型)。否则,在运行时调用时可能会出现错误。

(4)示例代码

下面是一个简单的示例代码,展示了 std::function 的类型擦除特性:

#include <iostream>  
#include <functional>  void print_int(int x) {std::cout << "Integer: " << x << std::endl;
}void print_double(double x) {std::cout << "Double: " << x << std::endl;
}int main() 
{std::function<void(int)> func_int = print_int; // 封装一个接受 int 的函数  std::function<void(double)> func_double = print_double; // 封装一个接受 double 的函数  func_int(12); // 输出: Integer: 12  func_double(3.14); // 输出: Double: 3.14  // 尽管 func_int 和 func_double 的类型不同,但它们都是 std::function 对象,  // 这体现了类型擦除:可以以统一的方式处理它们。  return 0;
}

2.5 使用 std::function 作为回调函数

std::function 非常适合用作回调函数,允许传递任何满足特定签名的可调用对象。这在异步编程、事件处理以及需要灵活性的场景中特别有用。

#include <iostream>  
#include <functional>  
#include <thread> // 用于线程  
#include <chrono> // 用于休眠  // 模拟一个异步操作,完成后调用回调函数  
void async_operation(std::function<void()> callback) {  // 模拟耗时操作  std::this_thread::sleep_for(std::chrono::seconds(1));  // 异步操作完成后调用回调函数  if (callback) {  callback();  }  
}  void on_operation_complete() {  std::cout << "Operation completed!" << std::endl;  
}  int main() 
{  // 使用普通函数作为回调函数  async_operation(on_operation_complete);  // 或者使用 Lambda 表达式  async_operation([]() {  std::cout << "Operation completed with lambda!" << std::endl;  });  // 等待足够的时间以确保异步操作完成  std::this_thread::sleep_for(std::chrono::seconds(2));  return 0;  
}

在这个例子中,async_operation 函数接受一个 std::function<void()> 类型的回调函数,并在模拟的异步操作完成后调用它。这允许传递任何满足无参数、无返回值要求的可调用对象。

2.6 std::function 与模板

std::function 可以与模板一起使用,以创建更通用的代码。

#include <iostream>  
#include <functional>  
#include <vector>  
#include <algorithm>  // 模板函数,接受一个 std::function 和一个容器  
template <typename Container, typename Function>
void process_container(const Container& c, Function func) {for (const auto& item : c) {func(item);}
}void print_number(int n) {std::cout << n << " ";
}int main() 
{std::vector<int> numbers = { 1, 2, 3, 4, 5 };// 使用 Lambda 表达式作为参数  process_container(numbers, [](int n) {std::cout << n * n << " ";});std::cout << std::endl; // 输出: 1 4 9 16 25   // 使用普通函数作为参数  process_container(numbers, print_number);std::cout << std::endl; // 输出: 1 2 3 4 5   return 0;
}

上面代码的输出为:

1 4 9 16 25
1 2 3 4 5

在这个例子中,process_container 是一个模板函数,它接受一个容器和一个 std::function。这使得可以对任何类型的容器和任何满足特定签名的可调用对象进行操作。

2.9 注意事项

使用 std::function 的注意事项如下:

  • std::function 通常比直接调用函数或成员函数慢,因为它涉及到类型擦除和可能的动态内存分配。
  • 当使用 std::function 传递参数时,请确保传递的可调用对象的类型与 std::function 所期望的类型匹配或兼容。
  • std::function 可以为空(即不包含任何可调用对象)。在调用空 std::function 对象时,将抛出 std::bad_function_call 异常。因此,在调用 std::function 对象之前,通常使用 operator bool 检查其是否包含有效的可调用对象。

3 std::bind

std::bind 是 C++ 标准库中的一个功能,它用于将可调用对象(如函数、函数对象或 Lambda 表达式)与特定的参数绑定在一起,从而生成一个新的可调用对象。这个新的可调用对象可以在稍后被调用,且其调用行为等同于原始可调用对象与绑定参数的结合。

3.1 基本用法

std::bind 的基本语法如下:

auto new_callable = std::bind(callable, arg1, arg2, ...);

其中 callable 是要被绑定的可调用对象(函数、函数对象或 Lambda 表达式),arg1, arg2, … 是要传递给 callable 的参数。std::bind 返回一个新的可调用对象 new_callable,它保存了 callable 和其参数的信息。

std::bind 的一个强大特性是支持占位符 std::placeholders::_1, std::placeholders::_2, …,它们允许指定哪些参数在调用 new_callable 时应该被替换。这使得 std::bind 可以创建灵活的回调函数和延迟计算函数。

针对如下代码:

void print_int(int n) {std::cout << n << " ";
}std::function<void(int)> func_int1 = print_int;
std::function<void(int)> func_int2 = bind(print_int,std::placeholders::_1);

其中对于 std::function<void(int)> func_int1 = print_int; ,这里的 print_int 是一个接受一个 int 参数并且没有返回值的函数或可调用对象。通过直接将 print_int 赋值给 func_int1,创建了一个 std::function 对象,该对象封装了 print_int 的调用。当之后调用 func_int1(some_int) 时,实际上是调用了 print_int(some_int)。

这种方式的优点是简单、直接,并且通常比使用 std::bind 有更好的性能,因为 std::function 可以直接存储对 print_int 的引用或指针,而不需要额外的函数调用或对象来转发调用。

对于 std::function<void(int)> func_int2 = bind(print_int,std::placeholders::_1); ,这里的 std::bind 用于创建一个新的可调用对象,该对象将 print_int 函数的调用与给定的参数绑定在一起。std::placeholders::_1 是一个占位符,表示当新的可调用对象被调用时,它的第一个参数应该传递给 print_int。

3.2 绑定函数和参数

#include <iostream>  
#include <functional>  void print_sum(int a, int b) {  std::cout << "Sum: " << a + b << std::endl;  
}  int main() 
{  // 绑定 print_sum 函数和参数 5, 10  auto bound_func = std::bind(print_sum, 5, 10);  // 调用 bound_func,输出 "Sum: 15"  bound_func();  return 0;  
}

3.3 使用占位符

#include <iostream>  
#include <functional>  
#include <vector>  
#include <algorithm>  bool is_even(int n) {return n % 2 == 0;
}int main() 
{std::vector<int> numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };// 使用 std::bind 和占位符 _1 来创建一个新的可调用对象,该对象接受一个整数并检查它是否是偶数  auto is_even_binder = std::bind(is_even, std::placeholders::_1);// 使用 std::remove_if 和绑定的可调用对象来移除所有偶数  numbers.erase(std::remove_if(numbers.begin(), numbers.end(), is_even_binder), numbers.end());// 输出剩余的数字(奇数)  for (int num : numbers) {std::cout << num << ' ';}std::cout << std::endl; // 输出: 1 3 5 7 9   return 0;
}

在这个例子中,std::bind(is_even, std::placeholders::_1) 创建了一个新的可调用对象 is_even_binder,它接受一个参数(由占位符 std::placeholders::_1 表示)并调用 is_even 函数。然后,将这个新的可调用对象传递给 std::remove_if,它遍历 numbers 容器并移除所有偶数。

3.4 绑定成员函数和对象

#include <iostream>  
#include <functional>  
#include <string>  class Greeter {
public:void greet(const std::string& name) const {std::cout << "Hello, " << name << "!" << std::endl;}
};int main() 
{Greeter greeter;// 绑定 greeter 对象的 greet 成员函数和占位符  auto bound_member_func = std::bind(&Greeter::greet, &greeter, std::placeholders::_1);// 调用 bound_member_func,输出 "Hello, World!"  bound_member_func("World");return 0;
}

这个例子创建了一个 Greeter 类的实例 greeter,并使用 std::bind 将 greet 成员函数和占位符 std::placeholders::_1 绑定到一起。这样,当调用 bound_member_func 时,它会调用 greeter 对象的 greet 成员函数并支持传递一个参数。

3.5 绑定多个参数和占位符

#include <iostream>  
#include <functional>  
#include <vector>  
#include <string>  void print_info(const std::string& name, int age, const std::string& message) {std::cout << "Name: " << name << ", Age: " << age << ", Message: " << message << std::endl;
}int main() 
{std::string name = "Alice";int fixed_age = 30;// 使用 std::bind 绑定 name 和 fixed_age,保留第三个参数为占位符  auto bound_print_info = std::bind(print_info, name, fixed_age, std::placeholders::_1);// 调用 bound_print_info,并传递额外的消息字符串  bound_print_info("Hello, world!"); // 输出: Name: Alice, Age: 30, Message: Hello, world!  return 0;
}

在这个例子中,std::bind 被用来绑定 name 和 fixed_age 参数到 print_info 函数,而第三个参数(消息字符串)则被保留为占位符 std::placeholders::_1。这样,当 bound_print_info 被调用时,就可以传递一个额外的字符串参数来替换占位符。

3.6 注意事项

使用 std::bind 的注意事项如下:

  • 性能开销:std::bind 在运行时可能引入一些额外的性能开销,因为它涉及到函数对象的创建和可能的堆分配(如果绑定的对象很大)。在性能敏感的应用中,直接使用函数指针或 Lambda 表达式可能更为高效。
  • Lambda 表达式的替代:C++11 引入了 Lambda 表达式,它们通常比 std::bind 更加直观和灵活。Lambda 表达式可以直接捕获局部变量,并定义自己的参数列表,这使得它们在很多情况下成为 std::bind 的更好替代。
  • 与 std::function 结合使用:std::bind 常与 std::function 结合使用,因为 std::function 可以存储任何可调用对象,包括通过 std::bind 创建的对象。这种组合允许你创建非常灵活和可重用的回调机制。
  • C++17 之后的替代:在 C++17 及以后的版本中,std::invoke 和结构化绑定提供了更多的灵活性和效率,可以考虑使用这些新特性来替代 std::bind。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/749135.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPF Command

WPF COMMAND在Windows Presentation Foundation&#xff08;WPF&#xff09;框架中是一个设计模式&#xff0c;主要用于实现用户界面&#xff08;UI&#xff09;元素和业务逻辑之间的松耦合交互。具体来说&#xff0c;它是MVVM&#xff08;Model-View-ViewModel&#xff09;架构…

C语言经典面试题目(十一)

1、如何在C语言中进行动态内存分配的错误处理&#xff1f; 在C语言中进行动态内存分配时&#xff0c;需要注意处理内存分配失败的情况。常见的错误处理方式包括&#xff1a; 使用 malloc、calloc 或 realloc 函数进行内存分配后&#xff0c;检查返回的指针是否为 NULL&#x…

PyTorch学习笔记之基础函数篇(十一)

文章目录 7 元素级别的数学运算7.1 torch.abs() 函数7.2 torch.cos() 函数7.3 torch.acos() 函数7.4 torch.add()函数 7 元素级别的数学运算 7.1 torch.abs() 函数 在PyTorch中&#xff0c;torch.abs 函数用于计算张量中每个元素的绝对值。这个函数会返回一个新的张量&#x…

python爬虫实战——抖音

目录 1、分析主页作品列表标签结构 2、进入作品页前 判断作品是视频作品还是图文作品 3、进入视频作品页面&#xff0c;获取视频 4、进入图文作品页面&#xff0c;获取图片 5、完整参考代码 6、获取全部作品的一种方法 本文主要使用 selenium.webdriver&#xff08;Firef…

Java与Go:Map

Map&#xff08;也可以成为字典&#xff0c;映射表&#xff09;是一种数据结构&#xff0c;用于存储键值对&#xff08;key-value pairs&#xff09;。它是一种抽象的数据类型并且允许通过键来快速查找和访问与之相关联的值。在Java和Go中&#xff0c;Map提供了一种非常方便的方…

文章管理AI在架构设计中的关键作用

随着信息技术的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已逐渐成为各行业的创新引擎。在内容管理领域&#xff0c;文章管理AI的崛起不仅改变了传统的内容生产方式&#xff0c;更在架构设计层面展现出其独特的价值和潜力。本文旨在深入探讨文章管理AI在架构设计…

AJAX学习日记——Day 2

一、Bootstrap 1、功能&#xff1a;不离开当前页面&#xff0c;显示单独内容 2、导入&#xff1a; 1、导入bootstrap.css<link href"https://cdn.jsdelivr.net/npm/bootstrap5.3.0-alpha1/dist/css/bootstrap.min.css" rel"stylesheet" integrity&qu…

手机网络连接性能API接口:查询手机网络连接性能状态

手机在网状态查询服务是一项非常方便的服务&#xff0c;可以帮助我们随时了解一个手机号码的在网状态。不论是查询自己的手机号码&#xff0c;还是查询他人的手机号码&#xff0c;这个服务都可以帮助我们获取准确的信息。今天&#xff0c;我想和大家介绍一个非常好用的手机在网…

力扣100题—持续更新

目录 LC141环形列表(easy)题目描述方法1&#xff1a;快慢指针&#xff08;1&#xff09;思路&#xff08;2&#xff09;python代码&#xff08;3&#xff09;复杂度分析 LC881救生艇&#xff08;medium&#xff09;题目描述方法1&#xff1a;双指针-对撞指针&#xff08;1&…

C++三级2021考题

我家的门牌号 #include<bits/stdc.h> using namespace std; int n,m,sum0; int a[100]; void f(int); int x(int); int main() {int s;cin>>n;for(int i1;true;i){s(1i)*i/2;for(int j1;j<i;j){if(s-2*jn){cout<<j<<i;return 0;}}}return 0; } voi…

柚见第十二期(随机匹配)

随机匹配 目的 为了帮大家更快地发现和自己兴趣相同的朋友 问题 匹配 1 个还是匹配多个&#xff1f; 答&#xff1a;匹配多个&#xff0c;并且按照匹配的相似度从高到低排序 怎么匹配&#xff1f;&#xff08;根据什么匹配&#xff09; 答&#xff1a;标签 tags 还可以根据 us…

分享一下自己总结的7万多字java面试笔记和一些面试视频,简历啥的,已大厂上岸

分享一下自己总结的7万多字java面试笔记和一些面试视频&#xff0c;简历啥的&#xff0c;已大厂上岸 自己总结的面试简历资料&#xff1a;https://pan.quark.cn/s/8b602fe53b58 文章目录 SSMspringspring 的优点&#xff1f;IoC和AOP的理解**Bean 的生命周期****列举一些重要…

20个最佳ChatGPT创业提示

20 Best ChatGPT Prompts for Start-Ups 在初创企业不断变化的生态系统中&#xff0c;利用像 ChatGPT 这样的尖端工具可以成为改变游戏规则的因素。初创企业以其敏捷性和创新性而闻名&#xff0c;总是在寻找提高效率、创造力和竞争力的方法。ChatGPT 凭借其先进的功能&#xf…

leetcode2684--矩阵中移动的最大次数

1. 题意 矩阵中一个位置只能从左上一、左、左下一格子转移而来&#xff0c;且当前值一定大于转移之前的值&#xff1b; 求从第一列开始的最大转移步数。 矩阵中移动的最大次数 2. 题解 思路 由于状态只能从左向右转移&#xff0c;所以同一个位置被搜索到后&#xff0c;第一…

一命通关差分

本章节是前缀和的延申 一命通关前缀和-CSDN博客https://blog.csdn.net/qq_74260823/article/details/136530291?spm1001.2014.3001.5501 一命通关前缀和 公交车 引入 还是利用我们在前缀和中所采用的例子——公交车。 有一辆公交车&#xff0c;一共上下了N批乘客&#xff1a…

【Vue3】源码解析-Runtime

文章目录 系列文章packages/runtime-dom/src/index.ts初始化创建renderermount \src\runtime-core\component.jsh.tspackages/runtime-core/src/renderer.ts挂载及卸载DOM节点render packages/runtime-dom/src/nodeOps.tspackages/runtime-core/src/apiCreateApp.ts创建appmoun…

前端UNIAPP端webview嵌入H5使用说明文档

一、关闭webView窗口 plus.webview.close( id_wvobj, aniClose, duration, extras ); 功能说明 关闭已经打开的Webview窗口&#xff0c;需先获取窗口对象或窗口id&#xff0c;并可指定关闭窗口的动画及动画持续时间。 参数说明 参数是否必须参数类型/固定值说明id_wvobj是…

GPT-3后的下一步:大型语言模型的未来方向

摘要&#xff1a; 本文将概述GPT-3后的下一步&#xff1a;大型语言模型的未来方向&#xff0c;包括技术发展趋势、应用场景、挑战与机遇。 引言&#xff1a; GPT-3是OpenAI于2020年发布的一款大型语言模型&#xff0c;它在自然语言处理领域取得了突破性进展。GPT-3的出现标志…

全国农产品价格分析预测可视化系统设计与实现

全国农产品价格分析预测可视化系统设计与实现 【摘要】在当今信息化社会&#xff0c;数据的可视化已成为决策和分析的重要工具。尤其是在农业领域&#xff0c;了解和预测农产品价格趋势对于农民、政府和相关企业都至关重要。为了满足这一需求&#xff0c;设计并实现了全国农产…

RabbitMQ 面试题及答案整理,最新面试题

RabbitMQ的核心组件有哪些&#xff1f; RabbitMQ的核心组件包括&#xff1a; 1、生产者&#xff08;Producer&#xff09;&#xff1a; 生产者是发送消息到RabbitMQ的应用程序。 2、消费者&#xff08;Consumer&#xff09;&#xff1a; 消费者是接收RabbitMQ消息的应用程序…