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

C++11新特性lambda的使用详解

得益于C++11的发布,提供了提高效率的语法,C++11以后是现代C++,C++98是传统C++,这里来介绍lambda的使用和原理。

目录

1.lambda

2.列表捕捉

3,lambda的应用

4,lambda原理


1.lambda

lambda表达式本质是一个匿名函数对象,跟普通函数不一样,它们可以定义在函数内部。

lambda使用层而言它们没有类型。是编译器自动去分配的,所以我们用auto去接受lambda对象。

什么意思嘞?就是我们不知道lambda的返回类型,所以我们用auto让编译器自己去推到。

	auto pd = [](int a, int b)->int{ return a + b; };cout << pd(5, 3);

lambda表达形式,

//[]捕捉列表()参数列表->返回类型{}函数体;

[]捕捉列表不能省略,参数列表无参数可省略,返回类型明确,函数体不可以省略。

[]捕捉列表,该表总是出现在lambda开始的位置,编译器根据[]判断下列的函数是否为lambda函数,捕捉列表能够捕捉上下文的变量供lambda使用,分为传值捕捉,传引用捕捉。捕捉列表为空也不可以省略。为啥要捕捉呢?这是因为作用域的限制,普通函数想使用main函数中定义的参数必须进行传参,因为他们的作用域不同,lambda也是函数,如果不传参它不能使用函数作用域外的参数,所以用捕捉列表来捕捉供他使用。

()参数列表,和普通函数传参类似,如果不需要可以省略。

->返回类型,函数返回什么类型就写什么类型。

{}函数体,和普通函数类似,在函数体内除了可以使用参数,还可以使用所有捕捉到的变量,函数体为空也不能省略。

#include<iostream>
using namespace std;
int main()
{auto pd = [](int a, int b)->int{ return a + b; };//[]捕捉列表()参数列表->返回类型{}函数体;cout << pd(5, 3);return 0;
}

2.列表捕捉

lambda函数中我们了解[]是捕捉列表,里面是捕捉的参数,我们还提到分为传引用捕捉和传值捕捉。

lambda函数只能使用lambda函数体中的变量和参数,如果想使用外界的其它变量就需要用捕捉列表进行捕捉。

第一种捕捉方式是在[]中进行显示的传值捕捉和传引用捕捉,捕捉的多个变量用,分割。

[x,&y,z]xz是传值捕捉,传值捕捉的值默认被const修饰不能修改,但是它和外界的a是两份变量地址不一样,传引用捕捉的值可以被修改,修改传引用的值外界的变量也会被修改。

我们用代码来验证上面所说的两份a和传引用捕捉被修改。


int main()
{int c = 5;int d = 10;cout << "被修改前的d" << d<<endl;auto pd = [c, &d](int a, int b)->int {cout << "函数体c地址:" << &c << endl;; d--; return a + b; };//[]捕捉列表()参数列表->返回类型{}函数体;cout << pd(5, 3)<<endl;cout << "外界的c地址" << &c << endl;cout << "被修改后的d" << d;return 0;
}

我们可以看到传值捕捉的c和外界的c地址不一样,说明它两是两份a,类似的,传引用捕捉的b当它在函数体内进行d--后它在函数体外的d也被修改,说明它两的地址一样,同命相连。

第二种捕捉方式是隐式捕捉,这样[=]是传值捕捉,[&]是传引用隐式捕捉,区别是它会自动捕捉所有在函数体中用到的值,传值隐式捕捉不能被修改,传引用隐式捕捉可以被修改,意思是我们在lambda里用了哪些变量,它就会捕捉哪些变量。比如:

我们在函数体没有定义a,但是隐式捕捉之后它会返回5,所以我们看到它会自动捕捉函数体内用到的值。

int a = 5;
auto pd = [=]()->int { return a; };
cout << pd();

当我们修改代码后,在函数体内也创建a,它会返回函数体的a,这是就近原则。

第三种捕捉方式是混合捕捉,即隐式捕捉和显示捕捉一起用,[=,&x]这个的意思是隐式传值捕捉函数体内要用到的值,但是x值是单独传引用捕捉,其它的值都是传值捕捉。需要注意的是,当我们隐式传值捕捉之后就不能再这样写了[=,x]这在逻辑上是矛盾的,已经隐式捕捉要用到的值,又去传值捕捉x,两次传值捕捉,逻辑矛盾,报错。

传引用捕捉类似的[&,x]传引用隐式捕捉所有要用到的值,单独传值捕捉x,也是传引用隐式捕捉所以得值后不可以再次传引用捕捉,造成两次捕捉逻辑上的错误。也就是不能这么写[&,&x];\

lambda如果在函数局部域中,它可以捕捉lambda之前定义过的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda可以直接使用,这也就意味着lambda定义在全局域中[]内必须为空。

这里再解释一下,全局域变量和静态变量它们的作用范围是全局,生命周期是程序运行期间,内存地址固定而普通局部变量的生命周期仅仅是所在的作用域作用域也在当前作用域内,比如函数体内定义一个a变量,当函数栈帧销毁时,a变量也会随着一起销毁。我lambda定义在全局域,当要捕获它的时候要么该变量未创建,要么都被销毁了,全局变量又不用去捕获,所以定义在全局域的变量无需去捕获。

捕获的本质是传值捕获会拷贝一份变量供函数体使用,传引用捕获是直接拿到变量的地址来进行使用,传引用捕获的变量要注意生命周期,避免造成悬空引用。这里我们想一下普通的局部变量和全局变量,全局变量的生命周期是程序的整个运行期间,作用域是全局,局部变量的生命周期是在这个函数区间内,出函数就会被销毁,​​局部域可以访问全局变量,但全局域无法直接访问局部变量​​。如果lambda在全局域定义去捕获局部域变量可能导致未定义的行为,比如变量已经销毁,但是还去使用,这种行为未定义。对于全局域的变量大家都可以使用它无需去捕获。

默认情况下,传值捕捉到的变量是被const修饰的,要想取消常性,需要加mutable。

auto func7 = [=]()mutable{};

3,lambda的应用

在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的
类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ 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;
}

4,lambda原理

lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for
这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个
lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返
回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成
的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕
捉,编译器要看使⽤哪些就传那些对象。
上⾯的原理,我们可以透过汇编层了解⼀下,下⾯第⼆段汇编层代码印证了上⾯的原理。
class Rate
{
public:
Rate(double rate)
: _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 函数对象
Rate r1(rate);
r1(10000, 2);
r2(10000, 2);
auto func1 = [] {
cout << "hello world" << endl;
};
func1();
return 0;
}
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 捕捉列表的rate,可以看到作为lambda_1类构造函数的参数传递了,这样要拿去初始化成员变量
// 下⾯operator()中才能使⽤
00D8295C lea eax,[rate]
00D8295F push eax
00D82960 lea ecx,[r2]
00D82963 call `main'::`2'::<lambda_1>::<lambda_1> (0D81F80h)
// 函数对象
Rate r1(rate);
00D82968 sub esp,8
00D8296B movsd xmm0,mmword ptr [rate]
00D82970 movsd mmword ptr [esp],xmm0
00D82975 lea ecx,[r1]
00D82978 call Rate::Rate (0D81438h)
r1(10000, 2);
00D8297D push 2
00D8297F sub esp,8
00D82982 movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D8298A movsd mmword ptr [esp],xmm0
00D8298F lea ecx,[r1]
00D82992 call Rate::operator() (0D81212h)
// 汇编层可以看到r2 lambda对象调⽤本质还是调⽤operator(),类型是lambda_1,这个类型名
// 的规则是编译器⾃⼰定制的,保证不同的lambda不冲突
r2(10000, 2);
00D82999 push 2
00D8299B sub esp,8
00D8299E movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D829A6 movsd mmword ptr [esp],xmm0
00D829AB lea ecx,[r2]
00D829AE call `main'::`2'::<lambda_1>::operator() (0D824C0h)

相关文章:

  • 2025-05-31 Python深度学习10——模型训练流程
  • 【Dv3Admin】工具请求配置文件解析
  • 计算晶体结构和电子能带的软件
  • Linux命令之ausearch命令
  • 【HW系列】—日志介绍
  • Linux531rsync定时同步 再回忆
  • 一文认识并学会c++模板初阶
  • 随机响应噪声-极大似然估计
  • 每日算法-250531
  • resubmit v1.2.0 新特性支持类级别防止重复提交
  • 深入剖析Java类加载机制:双亲委派模型的突破与实战应用
  • tmux基本原理
  • Mybatis:灵活掌控SQL艺术
  • 探索DeepSeek提示词:关键策略与实用场景
  • ServletComponentScan 注解的作用
  • 某乎x-zse-96 破解(补环境版本)
  • win32相关(临界区)
  • 使用curlconverter网站快速生成requests请求包
  • day28 python训练营 类的定义与方法
  • 「 扑翼飞行器 」悬停飞行的信号串联滤波器设计
  • 整站排名优化品牌/四平网络推广
  • 邯郸大名网站建设/代发百度首页排名
  • asp.net 网站开发实例/搜索引擎app
  • 在百度做网站推广怎么做/查看浏览过的历史记录百度
  • 郑州企业网站建设/厦门seo优化
  • 在闲鱼可以做网站吗/学大教育一对一收费价格表