【C++】C++11 Lambda 表达式

📅 2026/7/28 19:25:27
【C++】C++11 Lambda 表达式
目录1. 直观对比为什么我们需要 Lambda2. 核心语法拆解3. 捕获列表4. Lambda 底层5. 陷阱不能相互赋值在处理复杂数据结构的排序或使用algorithm库时我们经常需要自定义比较逻辑。在 C98 时代如果我们想改变比较规则必须在外部定义一个专门的类或结构体并重载operator()即仿函数。这种做法不仅使得代码极度臃肿还要绞尽脑汁去给这些只用一次的类起名字。为了让逻辑更加内聚C11 引入了 Lambda 表达式它本质上就是一个匿名函数让我们可以把代码直接写在调用的地方。1. 直观对比为什么我们需要 Lambda我们直接看一个实际场景对一个商品列表按照价格排序。【痛点C98 的传统写法】structGoods{string _name;double_price;int_evaluate;};// 必须在外部单独写一个结构体来实现比较逻辑structComparePriceLess{booloperator()(constGoodsg1,constGoodsg2){returng1._priceg2._price;}};intmain(){vectorGoodsv{{苹果,2.1,5},{香蕉,3.0,4}};// 传入仿函数对象sort(v.begin(),v.end(),ComparePriceLess());return0;}【优化C11 Lambda 的极简写法】intmain(){vectorGoodsv{{苹果,2.1,5},{香蕉,3.0,4}};// 直接在参数位置手写 Lambda逻辑一目了然sort(v.begin(),v.end(),[](constGoodsg1,constGoodsg2){returng1._priceg2._price;});return0;}2. 核心语法拆解Lambda 的完整语法格式如下[capture-list](parameters)mutable-return-type{statement}[capture-list](捕捉列表)标志 Lambda 的开始用于把外部变量“抓”到 Lambda 内部使用。(parameters)(参数列表)和普通函数一样。如果没有参数可以连同()一起省略。mutable默认情况下值捕获进来的变量是const的加上它才能修改。注意用了它就绝对不能省略()。- return-type(返回值类型)明确指定返回值。如果没有返回值或编译器能自己推导出来可以省略。{ statement }(函数体)执行的代码逻辑。代码示例inta3,b4;// 1. 最简单的 Lambda什么都不做语法允许[]{};// 2. 只有捕获列表和函数体省略参数和返回值类型编译器自动推导为 intautofun1[]{returna3;};// 3. 包含 mutable 和完整返回值的 Lambda// 注意x 被值拷贝进去了要想修改副本必须加 mutableintx10;autoadd_x[x](intval)mutable-int{x*2;// 如果没有 mutablex 为const类型x不能修改这里会编译报错returnvalx;};coutadd_x(10)endl;// 输出 303. 捕获列表捕获列表决定了外部变量如何进入 Lambda 内部[var]按值传递捕获变量var。[]按值传递捕获父作用域中的所有变量包括this。[var]按引用传递捕获变量var。[]按引用传递捕获父作用域中的所有变量。避坑警告禁止重复捕获[, a]是错的因为已经值捕获了所有变量再写a就是重复。正确写法如[, a]表示除了a用引用其他都用值捕获。作用域限制块作用域内的 Lambda 只能捕获当前父作用域的局部变量捕获全局变量会报错。4. Lambda 底层不要把 Lambda 当作魔法。在底层编译器处理 Lambda 的方式完全就是按照“函数对象仿函数”来处理的当编译器遇到一个 Lambda 表达式时它会自动在底层生成一个匿名类并在里面重载operator()。你捕获的变量实际上变成了这个匿名类的私有成员变量。看下面这段理解 Lambda 底层最直观的例子// 传统仿函数类classRate{public:Rate(doublerate):_rate(rate){}// 构造函数初始化成员doubleoperator()(doublemoney,intyear){returnmoney*_rate*year;}private:double_rate;// 捕获的变量本质上就是成员变量};intmain(){doublerate0.015;// 方式一使用仿函数对象Rater1(rate);r1(10000,2);// 方式二使用 Lambda// 编译器看到这里会在底层自动生成一个类似上面 Rate 的类autor2[rate](doublemoney,intyear)-double{returnmoney*rate*year;};r2(10000,2);return0;}5. 陷阱不能相互赋值既然底层是类那么 Lambda 的类型是什么答案是每个 Lambda 表达式的类型都是独一无二的。autof1[]{couthelloendl;};autof2[]{couthelloendl;};// f1 f2; // 编译报错找不到 operator()即使f1和f2的代码一模一样编译器也会为它们生成两个名字完全不同的底层类。类型不同自然无法相互赋值。正确的传递方式拷贝构造语法允许用一个已有的 Lambda 去拷贝构造出一个新的副本auto f3(f2);。退化为函数指针如果一个 Lambda 的捕获列表为空即没有捕获任何变量它可以被赋值给签名一致的普通函数指针。void(*PF)();// 定义函数指针PFf2;// 成功赋值前提是 f2 没有捕获任何外部变量