当前位置: 首页 > news >正文

C++11之lambda及包装器

C++11

  • 一.Lambda
    • 1.1Lambda 表达式
    • 1.2捕捉列表
      • 捕获列表的基本概念
      • 捕获方式
        • (一)值捕获
        • (二)引用捕获
        • (三)默认捕获
        • (四)捕获其他特性
        • (五)在类中捕获私有
    • 1.3lambda的应⽤
    • 1.4lambda的原理
  • 二.包装器
    • 2.1function
      • `std::function`的声明与定义
    • 2.2bind
      • bind的声明与定义
      • std::bind 的原理
      • std::bind的使用

在这里插入图片描述

一.Lambda

lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。

1.1Lambda 表达式

Lambda 表达式是一种匿名函数对象,它可以在代码中直接定义并使用。其基本语法结构如下:

[capture](parameters)->return-type { body }
  • capture:捕获列表,用于捕获 Lambda 表达式外部的变量。捕获方式有值捕获([x],将变量 x 的值复制到 Lambda 中)、引用捕获([&x],通过引用捕获变量 x)等多种方式。
  • parameters:参数列表,与普通函数的参数列表类似,用于接收调用时传入的参数。
  • return-type:返回类型,可以省略,如果 Lambda 表达式只有一条语句且该语句有返回值,则返回类型由该语句的返回类型推导得出。
  • body:函数体,是 Lambda 表达式的核心部分,用于实现具体的功能逻辑。

1.2捕捉列表

捕获列表的基本概念

捕获列表位于 Lambda 表达式的开头,用方括号[]表示。它允许 Lambda 表达式捕获外部作用域中的变量,从而在 Lambda 表达式的函数体内使用这些变量。捕获列表的语法如下:
[capture1, capture2, …]
捕获列表中的每个捕获项可以是一个变量名、一个变量名前加&符号(表示通过引用捕获),或者是一个变量名前加*符号(表示捕获变量的地址)。捕获项之间用逗号分隔。

捕获方式

(一)值捕获

值捕获是将外部变量的值复制到 Lambda 表达式中。这种方式下,Lambda 表达式内部的变量是外部变量的一个副本,对内部变量的修改不会影响外部变量。例如:

int x = 10;
auto lambda = [x]() {x = 20; // 修改的是 Lambda 内部的 x 副本
};
lambda();
std::cout << x << std::endl; // 输出 10,外部 x 的值未改变

值捕获适用于那些不需要修改外部变量,或者希望在 Lambda 表达式中独立操作变量的情况。

(二)引用捕获

引用捕获是通过引用的方式捕获外部变量。这种方式下,Lambda 表达式内部的变量是外部变量的引用,对内部变量的修改会直接影响外部变量。例如:

int x = 10;
auto lambda = [&x]() {x = 20; // 直接修改外部变量 x
};
lambda();
std::cout << x << std::endl; // 输出 20,外部 x 的值被修改

引用捕获适用于那些需要在 Lambda 表达式中修改外部变量的情况,但需要注意的是,如果外部变量的生命周期短于 Lambda 表达式的生命周期,可能会导致悬空引用,引发运行时错误。

(三)默认捕获
在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=,&x]表⽰其他变量隐式值捕捉,
x引⽤捕捉;[&,x,y]表⽰其他变量引⽤捕捉,x和y值捕捉。
当使⽤混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时
后⾯的捕捉变量必须是值捕捉,
同理=混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。

除了显式指定捕获的变量外,Lambda 表达式还支持默认捕获。默认捕获有两种方式:
[=]:表示默认按值捕获所有外部变量。
[&]:表示默认按引用捕获所有外部变量。
默认捕获可以简化捕获列表的书写,但需要谨慎使用,因为过度捕获可能会导致不必要的性能开销或潜在的错误。例如:

int x = 10;
int y = 20;
auto lambda = [=]() {x = 30; // 修改的是 Lambda 内部的 x 副本y = 40; // 修改的是 Lambda 内部的 y 副本
};
lambda();
std::cout << x << " " << y << std::endl; // 输出 10 20,外部 x 和 y 的值未改变

在默认捕获的情况下,如果需要对某个变量采用不同的捕获方式,可以通过显式捕获来覆盖默认捕获。例如:

int x = 10;
int y = 20;
auto lambda = [=, &y]() {x = 30; // 修改的是 Lambda 内部的 x 副本y = 40; // 直接修改外部变量 y
};
lambda();
std::cout << x << " " << y << std::endl; // 输出 10 40,外部 x 的值未改变,y 的值被修改

在这个例子中,x 按值捕获,y 按引用捕获。

(四)捕获其他特性
  1. lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
// 局部的静态和全局变量不能捕捉,也不需要捕捉 static int m = 0;auto func6 = []{int ret = x + m;return ret;};
  1. 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。

i

// 传值捕捉本质是⼀种拷⻉,并且被const修饰了 // mutable相当于去掉const属性,可以修改了 // 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉ auto func7 = [=]()mutable{a++;b++;c++;d++;return a + b + c + d;};cout << func7() << endl;
(五)在类中捕获私有

在类中,若想要使用私有成员变量时,直接捕获私有成员是报错的;这时需要通过捕获this指针来使用私有成员变量。

1.3lambda的应⽤

• 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。

• lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等

struct Goods{string _name; // 名字 double _price; // 价格 int _evaluate; // 评价 // ...Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};struct ComparePriceLess{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};struct ComparePriceGreater{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3 
}, { "菠萝", 1.5, 4 } };// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中 // 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了 sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate < g2._evaluate;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate;});return 0;
}

1.4lambda的原理

• lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会⽣成⼀个对应的仿函数的类。

• 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。

二.包装器

2.1function

std::function是C++标准模板库(STL)中定义在<functional>头文件中的一个类模板。它能够存储、复制,并调用任何可调用对象,如普通函数、Lambda表达式、函数对象、以及其他函数封装器(如std::bind)的结果。这使得std::function成为一种通用的函数封装方式,极大地增强了函数的通用性和可扩展性。

std::function 是⼀个类模板,也是⼀个包装器。std::function 的实例对象可以包装存储其他的可以调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调⽤对象被称为std::function 的⽬标。若std::function 不含⽬标,则称它为空。调⽤空std::function 的⽬标导致抛出std::bad_function_call异常。

std::function的声明与定义

std::function的声明形式如下:

template <class> class function; // 未定义
template <class Ret, class... Args>
class function<Ret(Args...)>; // 定义

这里Ret表示函数返回值的类型,Args...表示函数参数的类型列表。例如,std::function<void()>表示一个没有参数且没有返回值的函数;std::function<int(int, int)>表示一个接受两个int参数并返回一个int值的函数。

函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型,下⾯的第⼆个代码样例展⽰了 std::function 作为map的参数,实现字符串和可调⽤对象的映射表功能。特别要注意的是在调用普通成员函数

#include<functional>int f(int a, int b)
{return a + b;
}struct Functor{public:int operator() (int a, int b){return a + b;}
};class Plus{public:Plus(int n = 10):_n(n){}static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return (a + b) * _n;}private:int _n;
};int main()
{// 包装各种可调⽤对象 function<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b) {return a + b; };cout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;// 包装静态成员函数 // 成员函数要指定类域并且前⾯加&才能获取地址 function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;// 包装普通成员函数 // 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以 function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(pd, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pd), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;return 0;
}

2.2bind

bind的声明与定义

bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。bind 也在这个头⽂件中。

  1. 调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中
    newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。
  2. arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3…这些占位符放到placeholders的⼀个命名空间中。
using placeholders::_1;using placeholders::_2;using placeholders::_3;
simple(1)template <class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);with return type (2)template <class Ret, class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);

std::bind 的原理

std::bind 的原理基于模板和函数对象。它通过模板参数推导,将函数和参数封装成一个函数对象。这个函数对象重载了 operator(),当调用这个函数对象时,它会按照绑定时的参数和函数进行调用。
在实现上,std::bind 使用了完美转发和模板元编程技术。它能够处理各种类型的参数,包括左值、右值、引用等,并且能够正确地将参数转发到目标函数中。

std::bind的使用

#include<functional>using placeholders::_1;using placeholders::_2;using placeholders::_3;int Sub(int a, int b)
{return (a - b) * 10;
}int SubX(int a, int b, int c)
{return (a - b - c) * 10;
}class Plus{public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};int main()
{auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;// bind 本质返回的⼀个仿函数对象 // 调整参数顺序(不常⽤) // _1代表第⼀个实参 // _2代表第⼆个实参 // ...auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;// 调整参数个数 (常⽤) auto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);cout << sub4(5) << endl;// 分别绑死第123个参数 auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 1) << endl;auto sub6 = bind(SubX, _1, 100, _2);cout << sub6(5, 1) << endl;auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 1) << endl;// 成员函数对象进⾏绑死,就不需要每次都传递了 function<double(Plus&&, double, double)> f6 = &Plus::plusd;Plus pd;cout << f6(move(pd), 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;// bind⼀般⽤于,绑死⼀些固定参数 function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);cout << f7(1.1, 1.1) << endl;}

在这里插入图片描述

http://www.dtcms.com/a/302179.html

相关文章:

  • 【Bluedroid】bta_av_sink_media_callback(BTA_AV_SINK_MEDIA_CFG_EVT)流程源码分析
  • 快速了解MySQL
  • 火狐浏览器中国特供版关闭,如何下载 Firefox 国际版?如何备份数据?
  • vue怎么实现导入excel表功能
  • unbuntn 22.04 coreutils文件系统故障
  • 微型化IMU如何突破无人机与机器人的性能边界?
  • 数据处理工具是做什么的?常见数据处理方法介绍
  • Linux 远程连接解析:SSH 协议理论与应用
  • TCP/IP协议栈测试
  • keepalived
  • LNMP架构+wordpress实现动静分离
  • 《UE教程》第八章第一回——光源类型
  • 四、计算机组成原理——第6章:总线
  • Polkadot 的 Web3 哲学:从乔布斯到 Gavin Wood 的数字自由传承
  • 记一次IDEA启动微服务卡住导致内存溢出问题
  • 期货Level2五档委托簿0.25秒高频分钟与日级历史行情数据解析
  • 如何让Word支持Markdown?
  • C#/.NET/.NET Core技术前沿周刊 | 第 48 期(2025年7.21-7.27)
  • 【Linux】Ubuntu上安装.NET 9运行时与ASP.NET Core项目部署入门
  • k8s 1.30 通过helm部署ingress-controller-4.12.1
  • Java面试宝典:MySQL8新特性
  • Caddy服务器指南
  • 架构实战——互联网架构模板(“开发层”和“服务层”技术)
  • 【服务器知识】nginx配置ipv6支持
  • 低代码可视化AR远程协助、巡检、装配、质检新平台-元境智搭平台
  • Odoo:免费开源的需求驱动物料需求计划(DDMRP)解决方案
  • 低轨星座通信路径规划仿真:基于Dijkstra算法的星间链路优化实现
  • Day 24:元组与os模块
  • NAS远程访问新解法:OMV与cpolar的技术协同价值
  • Maven中的bom和父依赖