[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函数中触发了我们的回调函数,实现了当收到数据时,打印收到数据的日志。

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

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

相关文章

解锁机器学习核心算法 | 线性回归:机器学习的基石

在机器学习的众多算法中&#xff0c;线性回归宛如一块基石&#xff0c;看似质朴无华&#xff0c;却稳稳支撑起诸多复杂模型的架构。它是我们初涉机器学习领域时便会邂逅的算法之一&#xff0c;其原理与应用广泛渗透于各个领域。无论是预测房价走势、剖析股票市场波动&#xff0…

Visual Studio Code支持WSL,直接修改linux/ubuntu中的文件

步骤1 开始通过 WSL 使用 VS Code | Microsoft Learn 点击远程开发扩展包。 步骤2 Remote Development - Visual Studio Marketplace 点击install&#xff0c; 允许打开Visual Studio Code。 步骤3 共有4项&#xff0c;一齐安装。 步骤4 在WSL Linux(Ubuntu)中&#xf…

ShenNiusModularity项目源码学习(9:项目结构)

ShenNiusModularity源码主要有11个project&#xff08;其实还有officialweb、test两个文件夹&#xff0c;大致有4、5个project&#xff0c;但看着跟主要项目代码没太大关系&#xff0c;暂时不管&#xff09;&#xff0c;这11个project的依赖关系如下图所示&#xff0c;其中最下…

Rook-ceph(1.92最新版)

安装前准备 #确认安装lvm2 yum install lvm2 -y #启用rbd模块 modprobe rbd cat > /etc/rc.sysinit << EOF #!/bin/bash for file in /etc/sysconfig/modules/*.modules do[ -x \$file ] && \$file done EOF cat > /etc/sysconfig/modules/rbd.modules &l…

力扣 66.加一 (Java实现)

题目分析 给定一个数组&#xff0c;可以组成一个数字&#xff0c;将数字加一后&#xff0c;返回新数组 思路分析 首先跟着题目思路走&#xff0c;将数组按位*10可以得到数字&#xff0c;再加一&#xff0c;加一后按位%10&#xff0c;可以得到新的数组。但是此处数字会过大&…

vue3-03初学vue3中的配置项setup(Composition API (组合API组件中所用到的:数据、方法等,均要配置在setup中)

1.关于setup Vue3.0中一个新的配置项&#xff0c;值为一个函数.setup是所有Composition API (组合API)“表演的舞台”m组件中所用到的:数据、方法等等&#xff0c;均要配置在setup中。 2..setup函数使用 setup函数的两种返回值 1.若返回一个对象&#xff0c;则对象中的属性、…

docker 安装的open-webui链接ollama出现网络错误

# 故事背景 部署完ollama以后&#xff0c;使用谷歌浏览器的插件Page Assist - 本地 AI 模型的 Web UI 可以比较流畅的使用DeepSeek&#xff0c;但是只局限于个人使用&#xff0c;想分享给更多的小伙伴使用&#xff0c;于是打算使用open-webui 来管理用户&#xff0c;经官网推荐…

【进阶】Java设计模式详解

java注解 什么是注解&#xff1f; java中注解(Annotation)&#xff0c;又称java标注&#xff0c;是一种特殊的注释。 可以添加在包&#xff0c;类&#xff0c;成员变量&#xff0c;方法&#xff0c;参数等内容上面&#xff0c;注解会随同代码被编译到字节码文件中&#xff0…

观望=没有!

“兄弟&#xff0c;Java现在学还有前途吗&#xff1f;”“前端是不是饱和了&#xff1f;”——每天打开私信&#xff0c;这类问题能占大半。我的回复永远只有一句&#xff1a; “如果你非要等我说‘行’才敢行动&#xff0c;那答案已经不重要了。” # 技术人总在纠结“能不能”…

百度搜索融合 DeepSeek 满血版,开启智能搜索新篇

百度搜索融合 DeepSeek 满血版&#xff0c;开启智能搜索新篇 &#x1f680; &#x1f539; 一、百度搜索全量接入 DeepSeek &#x1f539; 百度搜索迎来重要升级&#xff0c;DeepSeek 满血版全面上线&#xff01;&#x1f389; 用户在百度 APP 搜索后&#xff0c;点击「AI」即…

【MySQL系列文章】Linux环境下安装部署MySQL

前言 本次安装部署主要针对Linux环境进行安装部署操作,系统位数64 getconf LONG_BIT 64MySQL版本&#xff1a;v5.7.38 一、下载MySQL MySQL下载地址&#xff1a;MySQL :: Download MySQL Community Server (Archived Versions) 二、上传MySQL压缩包到Linuxx环境&#xff0c…

RedisTemplate存储含有特殊字符解决

ERROR信息: 案发时间: 2025-02-18 01:01 案发现场: UserServiceImpl.java 嫌疑人: stringRedisTemplate.opsForValue().set(SystemConstants.LOGIN_CODE_PREFIX phone, code, Duration.ofMinutes(3L)); // 3分钟过期作案动机: stringRedisTemplate继承了Redistemplate 使用的…

C++--STL库-List

目录 1.list 的基本使用 1.1 创建和初始化 1.2. 插入元素 1.3. 删除元素 1.4. 访问元素 1.5 遍历 1.6 总结 list是C标准库&#xff08;STL&#xff09;中的双向链表容器&#xff0c;属于<list>头文件。 它的特点是&#xff1a; 动态大小&#xff1a;可以随时插入…

C语言——深入理解指针(3)

文章目录 字符指针变量数组指针变量数组指针变量是什么&#xff1f;数组指针变量怎么初始化 二维数组传参的本质函数指针变量函数指针变量的创建函数指针变量的使用两段关于函数的有趣代码typedef关键字 函数指针数组转移表第一种写法&#xff1a;第二种写法&#xff08;函数指…

mybatis使用typeHandler实现类型转换

使用mybatis作为操作数据库的orm框架&#xff0c;操作基本数据类型时可以通过内置的类型处理器完成java数据类型和数据库类型的转换&#xff0c;但是对于扩展的数据类型要实现与数据库类型的转换就需要自定义类型转换器完成&#xff0c;比如某个实体类型存储到数据库&#xff0…

基于微信小程序的宿舍报修管理系统设计与实现,SpringBoot(15500字)+Vue+毕业论文+指导搭建视频

运行环境 jdkmysqlIntelliJ IDEAmaven3微信开发者工具 项目技术SpringBoothtmlcssjsjqueryvue2uni-app 宿舍报修小程序是一个集中管理宿舍维修请求的在线平台&#xff0c;为学生、维修人员和管理员提供了一个便捷、高效的交互界面。以下是关于这些功能的简单介绍&#xff1a; …

STM32 HAL库 UART串口发送数据实验

一、实验目标 通过STM32的UART串口发送字符串数据到PC端串口调试助手&#xff0c;验证通信功能。 二、硬件准备 主控芯片&#xff1a;STM32F103C8T6。 串口模块&#xff1a;USB转TTL模块。 接线说明&#xff1a; STM32的USART1_TX&#xff08;PA9&#xff09; → USB-TTL模…

一.AI大模型开发-初识机器学习

机器学习基本概念 前言 本文主要介绍了深度学习基础&#xff0c;包括机器学习、深度学习的概念&#xff0c;机器学习的两种典型任务分类任务和回归任务&#xff0c;机器学习中的基础名词解释以及模型训练的基本流程等。 一.认识机器学习 1.人工智能和机器学习 人工智能&am…

冒险岛079 V8 整合版源码搭建教程+IDEA启动

今天教大家来部署下一款超级怀旧游戏冒险岛&#xff0c;冒险岛源码是开源的&#xff0c;但是开源的代码会有各种&#xff0c;本人进行了加工整合&#xff0c;并且用idea进行了启动测试&#xff0c;经过修改后没有任何问题。 启动截图 后端控制台 前端游戏界面 声明 冒险岛源码…

【操作系统】操作系统概述

操作系统概述 1.1 操作系统的概念1.1.1 操作系统定义——什么是OS&#xff1f;1.1.2 操作系统作用——OS有什么用&#xff1f;1.1.3 操作系统地位——计算机系统中&#xff0c;OS处于什么地位&#xff1f;1.1.4 为什么学操作系统&#xff1f; 1.2 操作系统的历史1.2.1 操作系统…