[C++语法基础与基本概念] std::function与可调用对象

std::function与可调用对象

可调用对象是指那些像函数一样可以直接被调用的对象,他们广泛用于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 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 func ; 上面定义了一个调用签名为 int (int,int)的function对象,表示这个function只能用来包装返回值为int,参数为(int,int)的可调用对象 绑定普通函数-无参数 void printHello() { std::cout << "Hello, World!" << std::endl; } std::function func = printHello; func(); 绑定lambda表达式 auto lambda = [](){ std::cout << "Hello World" << std::endl; }; std::function func = lambda; func(); 绑定仿函数 class PrintFunctor { public: void operator()() const { std::cout << "Hello from functor!" << std::endl; } }; PrintFunctor functor; std::function 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 func = functor; func(1); 绑定另一个function void printHello() { std::cout << "Hello, World!" << std::endl; } std::function func1 = printHello; std::function func2 = func1; 绑定普通函数-带参数 int add(int a,int b) { return a + b; } std::function 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 func = std::bind(add,1,2); func(); //调用 --------------------------------------------------------不定参数绑定 std::function func = std::bind(add,1,std::placeholders::_1); func(2); //调用 std::function 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 func = std::bind(&MyClass:add,&myclass,add,std::placeholders::_1,std::placeholders::_2); func(1,2); //调用 绑定lambda表达式 std::function 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 func = std::bind(add,std::placeholders::_1,std::placeholders::_2); std::function 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 func = std::bind(functor,std::placeholders::_1); func(1); 上面介绍了这么多,其实都一样,只要是可调用对象,绑定的方式都相同,只有类成员函数的绑定方式要特殊一些,需要指定对象。 同样,std::function也可以带入到std::sort中作为比较子: std::function func = std::bind([](int x,int y){ return x < y; },std::placeholders::_1,std::placeholders::_2); std::vector 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 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 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 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 func){}; }; class Derived:pulibc Base, pulibc Base1 { public: Derive(){}; void Notify(); //通常是某个事件的触发函数,例如收到某些信息时触发,读到某些数据时被调用触发 void SetFunc(std::function func);//设置std::function private: std::function func_; } void Derived::Notify() { // .......... // 对接受/读取的数据进行处理 ....... //执行回调函数 if(func_) { func_(); } } void Derived::SetFunc(std::function func) { func_ = func; } Base1* base1 = new Derived(); void PrintReceive() { std::cout << "receive data!"<< std::endl; } std::function func = PrintReceive; base1->SetFunc(func); Base* base = base1; //base指针也可能被传递给其他对象,在其他对象内部使用,当收到消息时,base指针的Notify函数被调用 //在Notify函数中触发了我们的回调函数,实现了当收到数据时,打印收到数据的日志。

相关推荐
weixin_457885822 分钟前
JavaScript智能对话机器人——企业知识库自动化
开发语言·javascript·自动化
机器视觉知识推荐、就业指导17 分钟前
QML 批量创建模块 【Repeater】 组件详解
前端·c++·qml
孤独得猿24 分钟前
Qt常用控件第一部分
服务器·开发语言·qt
慕斯策划一场流浪29 分钟前
fastGPT—nextjs—mongoose—团队管理之团队列表api接口实现
开发语言·前端·javascript·fastgpt env文件配置·fastgpt团队列表接口实现·fastgpt团队切换api·fastgpt团队切换逻辑
时光呢40 分钟前
JAVA常见的 JVM 参数及其典型默认值
java·开发语言·jvm
橙橙子23041 分钟前
c++柔性数组、友元、类模版
开发语言·c++·柔性数组
程序媛学姐1 小时前
SpringKafka错误处理:重试机制与死信队列
java·开发语言·spring·kafka
2401_840192271 小时前
如何学习一门计算机技术
开发语言·git·python·devops
巷北夜未央1 小时前
Python每日一题(14)
开发语言·python·算法
阳光_你好1 小时前
请详细说明opencv/c++对图片缩放
c++·opencv·计算机视觉