[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的算法,回调,事件处理等机制。…...

两个实用且热门的 Python 爬虫案例,结合动态/静态网页抓取和反爬策略,附带详细代码和实现说明
在这个瞬息万变的世界里,保持一颗探索的心,永远怀揣梦想前行。即使有时会迷失方向,也不要忘记内心深处那盏指引你前进的明灯。它代表着你的希望、你的信念以及对未来的无限憧憬。每一个不曾起舞的日子,都是对生命的辜负࿱…...
华象新闻 | 2月20日前谨慎升级 PostgreSQL 版本
各位 PostgreSQL 用户,建议近期进行升级 PostgreSQL 版本。 2月20日计划进行非周期性版本发布 PostgreSQL全球开发团队计划于2025年2月20日进行一次非周期性发布,以解决2025年2月13日更新版本中引入的一个回归问题。 2月13日的更新版本包括了17.3、16.7、…...
跳跃游戏 II - 贪心算法解法
问题描述: 给定一个长度为 n 的 0 索引整数数组 nums,我们从数组的第一个元素 nums[0] 开始。每个元素 nums[i] 表示从索引 i 可以跳跃的最大长度,换句话说,从位置 i,你可以跳到位置 i j,其中 0 < j &…...

图像质量评价指标-UCIQE-UIQM
一、评价指标UCIQE 在文章《An underwater color image quality evaluation metric》中,提到的了评价指标UCIQE(Underwater Colour Image Quality Evaluation),是一种无参考图像质量评价指标,主要用于评估水下图像的质…...

CentOS上安装WordPress
在CentOS上安装WordPress是一个相对直接的过程,可以通过多种方法完成,包括使用LAMP(Linux, Apache, MySQL, PHP)栈或使用更现代的LEMP(Linux, Nginx, MySQL, PHP)栈。 我选择的是(Linux, Nginx…...

Spring Boot 原理分析
spring-boot.version:2.4.3.RELEASE Spring Boot 依赖管理 spring-boot-starter-parent 配置文件管理 <resources> <resource> <directory>${basedir}/src/main/resources</directory> <filtering>true&l…...

Git 本地项目上传 GitHub 全指南(SSH Token 两种上传方式详细讲解)
前言:Git 与 GitHub 的区别与联系 在学习如何将本地项目上传到 GitHub 之前,先来弄清楚 Git 和 GitHub 的区别以及它们之间的联系。 对比项GitGitHub定义分布式版本控制系统(DVCS),用于本地和远程管理代码版本托管 G…...

jenkins服务启动-排错
服务状态为active (exited) 且进程不在 查看/etc/rc.d/init.d/jenkins配置 获取配置参数 [rootfy-jenkins-prod jenkins]# cat /etc/rc.d/init.d/jenkins | grep -v #JENKINS_WAR"/usr/lib/jenkins/jenkins.war" test -r "$JENKINS_WAR" || { echo "…...

CF 144A.Arrival of the General(Java实现)
题目分析 一个n个身高数据,问最高的到最前面,最矮的到最后面的最短交换次数 思路分析 首先,如果数据有重复项,例如示例二中,最矮的数据就是最后一个出现的数据位置,最高的数据就是最先出现的数据位置&…...
SAP-ABAP:SAP中REPORT程序和online程序的区别对比
在SAP中,REPORT程序和Online程序(通常指Dialog程序)是两种常见的ABAP程序类型,它们在用途、结构和用户交互方式上有显著区别。以下是它们的详细对比: 1. 用途 REPORT程序Online程序主要用于数据查询、报表生成和批量数…...

Java发展史
JavaEE的由来 语言的诞生 Java的前身是Oak语言,其目的是搞嵌入式开发开发智能面包机 叮~~~🍞🍞🍞 产品以失败告终 巅峰 网景公司需要网景浏览器打开网页,Oak->Java,进行前端开发(相关技…...

vue3--SVG图标的封装与使用
流程 终端输入- -安装下面这个包 npm install vite-plugin-svg-icons -Dvite.config.ts文件中引入 import {createSvgIconsPlugin} from vite-plugin-svg-iconsvite.config.ts文件中配置plugins选项 将下面代码 createSvgIconsPlugin({//用于指定包含 SVG 图标的文件夹路径…...

Datawhale Ollama教程笔记3
小白的看课思路: Ollama REST API 是什么? 想象一下,你有一个智能的“盒子”(Ollama),里面装了很多聪明的“小助手”(语言模型)。如果你想让这些“小助手”帮你完成一些任务&#…...

学习数据结构(10)栈和队列下+二叉树(堆)上
1.关于栈和队列的算法题 (1)用队列实现栈 解法一:(参考代码) 题目要求实现六个函数,分别是栈初始化,入栈,移除并返回栈顶元素,返回栈顶元素,判空࿰…...

洛谷 P3660 USACO17FEB Why Did the Cow Cross the Road III 题解
题意 有一个圆,圆周上按顺时针方向给出 2 n 2n 2n个点。第 i i i个点的颜色是 c o l o r i color_i colori,其中数据保证 1 ≤ c o l o r i ≤ n 1\le color_i\le n 1≤colori≤n,而且每种不同的颜色有且只有两个点。不存在位置重叠的点…...

【数据结构】(9) 优先级队列(堆)
一、优先级队列 优先级队列不同于队列,队列是先进先出,优先级队列是优先级最高的先出。一般有两种操作:返回最高优先级对象,添加一个新对象。 二、堆 2.1、什么是堆 堆也是一种数据结构,是一棵完全二叉树,…...
如何提升爬虫获取数据的准确性?
提升爬虫获取数据的准确性是确保数据分析和后续应用有效性的关键。以下是一些经过验证的方法和最佳实践,可以帮助提高爬虫数据的准确性: 1. 数据清洗 数据清洗是提升数据准确性的重要步骤,主要包括去除重复数据、处理缺失值和异常值。 去除…...
Obsidian及Zotero常用的插件
Obsidian插件 Minimal Theme Settings(Life,zotero)【必需】 界面样式设置所需插件 Style Settings(Life,zotero)【必需】界面样式设置所需插件 Recent Files(Life,zotero…...

闲鱼IP属地是通过电话号码吗?
在闲鱼这样的二手交易平台上,用户的IP属地信息对于维护交易安全、增强用户间的信任至关重要。然而,关于闲鱼IP属地是如何确定的,不少用户存在疑惑,尤其是它与电话号码之间是否存在关联。本文将深入探讨这一问题,揭示闲…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...