[C++语法基础与基本概念] std::function与可调用对象
std::function与可调用对象
- 函数指针
- lambda表达式
- std::function与std::bind
- 仿函数
- 总结
- std::thread与可调用对象
- std::async与可调用对象
- 回调函数
可调用对象是指那些像函数一样可以直接被调用的对象,他们广泛用于C++的算法,回调,事件处理等机制。
函数指针
函数指针是最简单的一种可调用对象
我们大家应该都用过函数名来作为函数的地址,但是函数名称其实与函数的地址是有一些细微的差别的
void printHello() {std::cout << "Hello, World!" << std::endl;
}
以上面的函数为例,函数的名称是printHello, 但是它的类型其实是void() , 而不是void(*)(),但是它可以被隐式的转化成void( * ),
void (*ptr)() = printHello;
在上面这行代码中,printHello会被隐式转化为void( * )(), 这就跟char [] 能被隐式的转化为char *很类似
如下代码也能完成上述转化,但是是显示的取函数地址
void (*ptr)() = &printHello
显示利用&运算符取地址
言归正传,在得到函数指针之后,我们就可以直接通过函数指针调用函数,并且可以将其作为一些函数的参数
例如:
bool cmp( int a, int b)
{return a < b;
}std::vector<int> vec = {2,1,4,3,7,6};
std::sort(vec.begin(),vec.end(),cmp);
上述例子就会将容器vec中的元素从小到大进行一个排序了。
lambda表达式
lambda是C++11引入的一种匿名函数对象,提供了一种简单的方式用来定义内联函数,它的标准格式如下:
[capture-list] (parameters) -> return-type { body }
[capture-list] 捕获列表,捕获的变量可以在lambda表达式的函数体内使用
(parameters) 参数列表,与函数的参数列表一致
-> return-type 返回值,如果不写,lambda表达式会自动推导返回值
{body} 函数体
既然返回值可以省略,lambda表达式最常见的格式就是
[](){}
其中
[&x] 表示用引用的方式捕获变量x
[x] 表示用值捕获的方式捕获变量x
[=] 按值捕获的方式捕获作用域内所有变量
[&] 按引用捕获的方式捕获作用域内所有变量
[a, &b] 按值捕获a, 按引用捕获b
lambda表达式最简单的使用方式:
auto lambda = [](){std::cout << "Hello World" << std::endl;
};
lambda();
int x = 5;
auto lambda = [x](int a){return x + a;
};
lambda(6);
如上图两个例子所示,lambda表达式可以就像普通函数那样被调用
lambda表达式的类型
auto lambda = [](){std::cout << "Hello World" << std::endl;
};
你知道此时auto是什么类型吗?
编译器会为每一个lambda表达式,生成如下面所示的一个类:
class __lambda_1 {
public:void operator()() const {std::cout << "Hello, Lambda!" << std::endl;}
};
这个类是使用了operator重载了()运算符的一个类,听起来跟隐函数非常像,此时auto的类型就是 __lambda_1
同样,lambda表达式也可以带入到各种以可调用对象为参数的函数之中
auto lambda [](int x,int y){return x < y;
};std::vector<int> vec = {2,3,1,7,6,5};
std::sort(vec.begin(),vec.end(), lambda);
std::function与std::bind
std::funtion也是一个可调用对象,它本质上叫做泛型函数包装器,可以用来包装任何的可调用对象,只要这个可调用对象的调用签名与自己匹配即可。 也可以用来包装另一个std::funtion,因为function也是一个可调用对象
什么是调用签名?
std::function<int(int,int)> func ;
上面定义了一个调用签名为 int (int,int)的function对象,表示这个function只能用来包装返回值为int,参数为(int,int)的可调用对象
绑定普通函数-无参数
void printHello() {std::cout << "Hello, World!" << std::endl;
}std::function<void()> func = printHello;
func();
绑定lambda表达式
auto lambda = [](){std::cout << "Hello World" << std::endl;
};
std::function<void()> func = lambda;
func();
绑定仿函数
class PrintFunctor {
public:void operator()() const {std::cout << "Hello from functor!" << std::endl;}
};PrintFunctor functor;
std::function<void()> func = functor;
func();
-----------------------------------------------------
int add(int a,int b)
{return a + b;
}class PrintFunctor {
public:int operator()(int x) const {return add(x,2);}
};
PrintFunctor functor;
std::function<int(int)> func = functor;
func(1);
绑定另一个function
void printHello() {std::cout << "Hello, World!" << std::endl;
}
std::function<void()> func1 = printHello;
std::function<void()> func2 = func1;
绑定普通函数-带参数
int add(int a,int b)
{return a + b;
}std::function<int(int,int)> func = add;
int res = func(1,2);
以上是一些实用std::function的最简单的例子,但是function还远不止于此,如果我们想让绑定更加灵活呢?例如,我们想绑定上面的add函数,但是其中一个参数是已经确定的,如何绑定呢? 这时候就需要用到std::bind
std::bind是用来生成std::function的一个函数,能让std::function的包装更加灵活, 他可以将所有的可调用对象包装成std::function
std::bind绑定普通函数
int add(int a,int b)
{return a < b ;
}
--------------------------------------------------------固定参数绑定
std::function<int()> func = std::bind(add,1,2);
func(); //调用
--------------------------------------------------------不定参数绑定
std::function<int(int)> func = std::bind(add,1,std::placeholders::_1);
func(2); //调用std::function<int(int,int)> func = std::bind(add,std::placeholders::_1,std::placeholders::_2);
func(1,2);//调用
其中
std::placeholders::_1
表示参数,_1后缀表示第一个不定参数,如果想绑定多个不定参数,只需要让后缀继续加就行
std::bind绑定类成员函数
class MyClass {
public:int add(int x, int y) {return x + y;}
};MyClass myclass;
std::function<int(int,int)> func = std::bind(&MyClass:add,&myclass,add,std::placeholders::_1,std::placeholders::_2);
func(1,2); //调用
绑定lambda表达式
std::function<int(int,int)> func = std::bind([](int x,int y){return x + y;},std::placeholders::_1,std::placeholders::_2);func(1,2); //调用
绑定std::function(套娃)
int add(int a,int b)
{return a + b ;
}
std::function<int(int,int)> func = std::bind(add,std::placeholders::_1,std::placeholders::_2);
std::function<int(int)> func1 = std::bind(func1,1,std::placeholders::_1);
func1(2);
绑定仿函数
class ADDFunctor { //
public:int operator()(int x) const {return add(x,2);}
};
ADDFunctor functor;
std::function<int(int)> func = std::bind(functor,std::placeholders::_1);
func(1);
上面介绍了这么多,其实都一样,只要是可调用对象,绑定的方式都相同,只有类成员函数的绑定方式要特殊一些,需要指定对象。
同样,std::function也可以带入到std::sort中作为比较子:
std::function<iint(int,int)> func = std::bind([](int x,int y){return x < y;},std::placeholders::_1,std::placeholders::_2);std::vector<int> vec = {5,2,3,7,1,4};std::sort(vec.begin(),vec.end(),func);---------------------------------------------std::sort(vec.begin(),vec.end(),std::bind([](int x,int y){return x < y;
},std::placeholders::_1,std::placeholders::_2)); //lambda表达式比较多余,这里直接用lambda表达式就行
//仅用来展示语法
在使用std::bind绑定时,经常要用到std::placeholders::_1,这就会导致单行代码过于长,为了处理好看的问题,经常使用宏定义的方式处理。
#define PHS std::placeholders// 绑定普通函数(当然也可以用于绑定其他可调用对象)
#define BIND_FUNC_0(_f) std::bind(_f)
#define BIND_FUNC_1(_f) std::bind(_f, PHS::_1)
#define BIND_FUNC_2(_f) std::bind(_f, PHS::_2)//绑定类成员函数
#define BIND_CLASS_FUNC_0(_c, _f, _p) std::bind(&_c::_f, _p)
#define BIND_CLASS_FUNC_1(_c, _f, _p) std::bind(&_c::_f, _p, PHS::_1)
#define BIND_CLASS_FUNC_2(_c, _f, _p) std::bind(&_c::_f, _p, PHS::_1,PHS::_2)
BIND_FUNC_0 表示绑定一个参数为0的可调用对象 _f表示函数名称
BIND_FUNC_1 表示绑定一个参数为1的可调用对象,以此类推
BIND_CLASS_FUNC_0 表示绑定一个参数为0的类成员函数_c表示类名,_f表示函数名,_p表示对象名称
仿函数
仿函数就是使用 operator重载了()运算符的类
class PrintFunctor {
public:int operator()(int x) const {return add(x,2);}
};
PrintFunctor functor;
functor(1);
一个类在重载了()运算符之后,就可以像函数那样被直接调用,但是它本质上又不是函数,所以吧叫做仿函数
class Functor {
public:int operator()(int x, int y) const {return x < y;}
};
Functor functor;
std::vector<int> vec = {5,2,3,7,1,4};
std::sort(vec.begin(),vec.end(), functor);
总结
除了上述std::sort的例子以外,还有一些用到可调用对象的函数
std::thread与可调用对象
普通函数
void Run(int x, int y, int z)
{.......
}std::thread t(Run,1,2,3);
类成员函数
class Myclass{public:void Run(int x,int y,int z){..........}
};
MyClass myclass;
std::thread t(&Myclass::Run,&myclass,1,2,3);
lambda表达式,std::function, 仿函数绑定方式也都和普通函数一样
auto lambda = [](int x,int y,int z){};
std::thread t(lambda,1,2,3);
----------------------------------------------
std::function<void(int,int,int)> func = std::bind(lambda, std::placeholders::_1,std::placeholders::_2, std::placeholders::_3);
std::thread t(func,1,2,3);
----------------------------------------------
class Myclass{public:void Run(int x,int y, int z){.............}
};
MyClass myclass;
std::thread t(myclass,1,2,3);
std::async与可调用对象
绑定std::function(其余就不展示了,因为都一样)
void Run(int x)
{.............
}
std::function<void(int)> func = std::bind(Run,std::placeholders::_1);
std::async(std::launch::async, func,10); //开启一个异步任务
绑定类成员函数
class Myclass{public:void Run(int x,int y,int z){..........}
};
MyClass myclass;
std::async(std::launch::async,&Myclass::Run,&myclass,1,2,3);
回调函数
除了上面的例子之外,可调用对象还经常作为回调函数使用
什么是回调函数?
回调函数就是将一个可调用对象,通过函数或以其它方式传递过去并存储起来,然后在合适的时机被调用,通常是在某些事件发生之后被调用,例如在网络通信中,收到消息事件,如收到其他套接字发送来的消息,也或是在MQ,RPC通信时收到消息时被调用。
下面举一个简单的例子:
class Base
{
public: virtual void Notify(){};
};
class Base1
{public:virtual void SetFunc(std::function<void()> func){};
};
class Derived:pulibc Base, pulibc Base1
{public:Derive(){};void Notify(); //通常是某个事件的触发函数,例如收到某些信息时触发,读到某些数据时被调用触发void SetFunc(std::function<void()> func);//设置std::functionprivate:std::function<void()> func_;}
void Derived::Notify()
{// ..........// 对接受/读取的数据进行处理 .......//执行回调函数if(func_){func_();}
}void Derived::SetFunc(std::function<void()> func)
{func_ = func;
}
Base1* base1 = new Derived();void PrintReceive()
{std::cout << "receive data!"<< std::endl;
}
std::function<void()> func = PrintReceive;base1->SetFunc(func);
Base* base = base1;//base指针也可能被传递给其他对象,在其他对象内部使用,当收到消息时,base指针的Notify函数被调用
//在Notify函数中触发了我们的回调函数,实现了当收到数据时,打印收到数据的日志。
相关文章:
[C++语法基础与基本概念] std::function与可调用对象
std::function与可调用对象 函数指针lambda表达式std::function与std::bind仿函数总结std::thread与可调用对象std::async与可调用对象回调函数 可调用对象是指那些像函数一样可以直接被调用的对象,他们广泛用于C的算法,回调,事件处理等机制。…...
强化学习-NPG
NPG来源于PG算法,是TRPO算法的前身。 随机梯度策略算法的输入输出问题(不得不考虑的): PG算法用函数表示策略,该表示方法不直接输出动作(有别于“确定性梯度策略”),那么就有两种可以…...
JVM学习与理解
目录 JVM介绍: 解释: 特点: 整体构成: 执行过程: 运行时数据区: Java堆剖析: 堆内存区域划分 为什么要分代呢? 内存分配: 新生区与老年区配置比例:…...
ArrayList、LinkedList、Vector
ArrayList 和 LinkedList 的区别是什么? 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实 现。 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 Linked…...
数据守护者:备份文件的重要性及自动化备份实践
在信息化社会,数据已成为企业运营和个人生活的重要组成部分。无论是企业的核心业务数据,还是个人的珍贵照片、重要文档,数据的丢失或损坏都可能带来无法估量的损失。因此,备份文件的重要性愈发凸显,它不仅是数据安全的…...
【LLM强化学习】Deep使用的强化学习方法 GRPO 理论理解与实践
一、写在前面 预训练 赋予了 LLMs 广阔的知识和强大的语言能力,但模型本身并不知道 什么是好的,什么是坏的,什么是符合人类偏好的。我们需要 引导 LLMs 学习人类的价值观,理解指令背后的意图,并生成更安全、更可靠、更符合人类期望的文本。强化学习(Reinforcement Learn…...
初阶c语言(练习题,猜随机数,关机程序)
目录 第一题,使用函数编写一个随机数,然后自己猜,猜随机数 第二道题(关机程序) 实现代码(关机程序) 实现代码(猜数字) 前言: 学习c语言,学习…...
TypeScript 与后端开发Node.js
文章目录 一、搭建 TypeScript Node.js 项目 (一)初始化项目并安装相关依赖 1、创建项目目录并初始化2、安装必要的依赖包 (二)配置 TypeScript 编译选项(如模块解析方式适合后端) 二、编写服务器代码 &a…...
基于SSM+uniapp的鲜花销售小程序+LW示例参考
1.项目介绍 系统角色:管理员、商户功能模块:用户管理、商户管理、鲜花分类管理、鲜花管理、订单管理、收藏管理、购物车、充值、下单等技术选型:SSM,Vue(后端管理web),uniapp等测试环境&#x…...
数据开放共享和平台整合优化取得实质性突破的智慧物流开源了
智慧物流视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本可通过边缘计算技术…...
2025互联网医院系统源码解析:AI陪诊问诊APP的未来发展
2025年,AI陪诊问诊APP将如何在技术上创新,如何推动互联网医院的进一步发展,成为了我们今天探讨的核心内容。在本文中,我们将通过源码解析,深入分析这一前沿技术的未来发展趋势,帮助广大从业者更好地理解这一…...
【NLP 22、语言模型 language model】
有时候我也想听听,我在你心里,是什么样子 —— 25.1.12 一、什么是语言模型 语言是灵活的,也是有规律的 了解一门语言的人可以判断一句话是否“合理” 通俗来讲,语言模型用来评价一句话(句子可以看作是字的组合)是否“合理”或…...
(萌新入门)如何从起步阶段开始学习STM32 —— 0.碎碎念
目录 前言与导论 碎碎念 所以,我到底需要知道哪些东西呢 从一些基础的概念入手 常见的工具和说法 ST公司 MDK5 (Keil5) CubeMX 如何使用MDK5的一些常用功能 MDK5的一些常见的设置 前言与导论 非常感谢2301_77816627-CSDN博客的提问,他非常好奇…...
Eclipse:关闭多余的工具条
Eclipse默认的工具条非常多,可以通过如下方法选择关闭一些不常用的: 1.选择菜单Window -> Perspective -> Customize Perspective 2.根据需要勾选Toolbar Visbility下面的工具条项...
Git标签管理:从基础到高阶自动化实践
引言 在软件发布过程中,88%的生产事故与版本标记错误相关。Git标签(Tag)作为版本控制的关键锚点,不仅是发布流程的里程碑,更是代码审计和问题追溯的重要依据。本文将深入Git标签的底层机制,揭示企业级标签…...
【第3章:卷积神经网络(CNN)——3.6 CNN的高级特性与优化策略】
在2012年ImageNet竞赛的颁奖现场,当AlexNet以超出第二名10%的惊人准确率夺冠时,整个计算机视觉界都意识到:这个叫CNN的架构正在重写游戏规则。十年后的今天,当我们站在YOLOv8、Vision Transformer等新架构的肩膀上回望,会发现经典CNN的进化史就是一部浓缩的深度学习发展史…...
【R语言】非参数检验
一、Mann-Whitney检验 在R语言中,Mann-Whitney U检验(也称为Wilcoxon秩和检验)用于比较两个独立样本的中位数是否存在显著差异。它是一种非参数检验,适用于数据不满足正态分布假设的情况。 1、独立样本 # 创建两个独立样本数据…...
250214-java类集框架
单列集合是list和set,list的实现类有ArrayList和LinkedList,前者是数组实现,后者是链表实现。list和set,前者有序、可重复,后者无序不可重复。 1.单列集合 1.1. list java.util.List接口继承自Collection接口&#…...
集成测试总结文档
1. 集成测试的定义 集成测试(Integration Testing)是在单元测试之后,将多个独立的软件模块或组件组合在一起进行测试的过程,目的是验证这些模块之间的接口、数据传递、协作逻辑是否符合设计要求,并发现因集成引发的缺…...
POI 的 Excel 读写操作教程
POI 的 Excel 读写操作教程 一、POI 简介 Apache POI 是一款在 Java 开发中广受欢迎的开源库,主要用于处理各种 Microsoft Office 文件格式,Excel 文件便是其中之一。凭借其功能强大的 API,POI 不仅支持对 Excel 文件的读取、写入和修改&am…...
ROS2 话题通信
1. 基本概念 发布-订阅模型:节点间通过话题(Topic)异步通信,发布者(Publisher)发送消息,订阅者(Subscriber)接收消息。 话题(Topic):…...
【学习资源】时间序列数据分析方法(1)
时间序列数据分析是一个有趣的话题,让我们多花一些时间来研究。此篇为第一篇文章。主要介绍特征提取方法、深度学习时序数据分析模型、参考资源。期望能帮助大家解决工业领域的相关问题。 1 特征提取方法:信号处理 (来源:INTELLIGENT FAULT DIAGNOSIS A…...
Hadoop集群安装与配置指南(CentOS 7)
Hadoop集群安装与配置指南(CentOS 7) 一、虚拟机准备 安装虚拟机软件 下载VMware或VirtualBox,完成安装并激活。 注意:选择NAT模式,配置子网IP(如192.168.10.0)。 CentOS 7安装 下载CentOS 7…...
Streamlit与Qlib:量化投资策略可视化实战
Streamlit与Qlib:量化投资策略可视化实战 1. 项目背景 在量化投资领域,数据可视化是理解和展示投资策略的关键。本文将详细介绍如何使用Streamlit和Qlib构建一个交互式的量化投资策略可视化应用。 2. 环境准备 2.1 安装依赖 # 安装必要的库 pip ins…...
Ceph集群搭建2025(squid版)
squid版本维护年限 apt install -y cephadmecho >> "deb http://mirrors.163.com/ceph/debian-squid/ bookworm main" echo >> "deb-src http://mirrors.163.com/ceph/debian-squid/ bookworm main"#安装源 cephadm install #开始初始化一个最…...
机器学习实战(3):线性回归——预测连续变量
第3集:线性回归——预测连续变量 在机器学习的世界中,线性回归是最基础、最直观的算法之一。它用于解决回归问题,即预测连续变量(如房价、销售额等)。尽管简单,但线性回归却是许多复杂模型的基石。今天我们…...
【AI-34】机器学习常用七大算法
以下是对这七大常用算法的浅显易懂解释: 1. k 邻近算法(k - Nearest Neighbors,KNN) 想象你在一个满是水果的大广场上,现在有个不认识的水果,想知道它是什么。k 邻近算法就是去看离这个水果最近的 k 个已…...
【漫话机器学习系列】093.代价函数和损失函数(Cost and Loss Functions)
代价函数和损失函数(Cost and Loss Functions)详解 1. 引言 在机器学习和深度学习领域,代价函数(Cost Function)和损失函数(Loss Function)是核心概念,它们决定了模型的优化方向。…...
ThreadLocal为什么会内存溢出
每个线程(Thread 对象)内部维护一个 ThreadLocalMap,用于存储该线程的所有 ThreadLocal 变量的键值对: ThreadLocalMap虽然是ThreadLocal的静态内部类,但是Thread 对象的属性,当线程存活时ThreadLocalMap不会被回收。 Key:ThreadLocal 实例的 弱引用(WeakReference)。…...
LabVIEW 天然气水合物电声联合探测
天然气水合物被认为是潜在的清洁能源,其储量丰富,预计将在未来能源格局中扮演重要角色。由于其独特的物理化学特性,天然气水合物的探测面临诸多挑战,涉及温度、压力、电学信号、声学信号等多个参数。传统的人工操作方式不仅效率低…...
