C++11中的lambda表达式详解

文章目录


前言

在学习lambda表达式之前,我们所使用的可调用函数对象只有函数指针和仿函数对象,他们的定义都相对麻烦,而使用lambda表达式来定义可调用对象,既方便又简单。


1.lambda表达式基础语法

cpp 复制代码
[capture-list] (parameters) -> ReturnType { FuntionBody }
  • capture-list:捕捉列表(空列表不能省略
  • parameters:参数列表(无参时可以省略)
  • ReturnType:返回值类型(可以省略,编译器自动推导)
  • FuntionBody:捕捉列表(空函数体不能省略

代码示例:

cpp 复制代码
#include <iostream>
using namespace std;

int main()
{
    // 一个简单的lambda表达式
    auto add1 = [](int x, int y)->int {return x + y; };

    cout << add1(1, 2) << endl;


    // 1、捕捉为空也不能省略
    // 2、参数为空可以省略
    // 3、返回值可以省略,可以通过返回对象自动推导
    // 4、函数体不能省略
    auto func1 = [] 
    {
        cout << "hello bit" << endl;
        return 0;
    };
    func1();

    int a = 0, b = 1;
    auto swap1 = [](int& x, int& y)
    {
        int tmp = x;
        x = y;
        y = tmp;
    };
    swap1(a, b);
    cout << a << ":" << b << endl;

    return 0;
}

2.捕捉列表

2.1.捕捉方式分类

  1. 显式捕捉:
  • 值捕捉:[x, y](拷贝变量,不可直接修改
  • 引用捕捉:[&x, &y](引用变量,可修改原变量)
  1. 隐式捕捉
  • 隐式值捕捉:[=](以拷贝方式捕捉所有外部变量)
  • 隐式引用捕捉:[&](以引用方式捕捉所有外部变量)
  1. 混合捕捉
  • [=, &x]:变量x单独用引用捕捉,其余变量用值引用捕捉
  • [&, y]:变量y单独用值引用捕捉,其余变量用引用捕捉

2.2.特殊捕捉规则与mutable修饰

  • 不可捕捉 的变量:全局变量、静态局部变量(直接使用即可)
  • mutable作用:取消值捕捉的const属性 ,允许修改拷贝(不影响原变量)
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
    int a = 10, b = 20;
    // 混合捕捉 + mutable
    auto func = [=, &b]() mutable {
        a++; // 允许修改(拷贝)
        b++; // 引用捕捉,修改原变量
        cout << "内部a: " << a << ", 内部b: " << b << endl;
        };
    func();
    cout << "外部a: " << a << ", 外部b: " << b << endl;
	return 0;
}

测试结果:

2.3.捕捉方式对比

捕捉方式 语法示例 变量访问形式 是否可修改原变量
显式值捕捉 [x, y] 拷贝
显式引用捕捉 [&x, &y] 引用
隐式值捕捉 [=] 拷贝
隐式引用捕捉 [&] 引用
混合捕捉 [=, &x] 混合 x是,其余否
mutable值捕捉 x mutable 拷贝

3.典型应用场景

3.1.sort函数中的自定义比较逻辑

代码示例:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Goods {
    string _name;
    double _price;
    int _id;
    Goods(const char* name, double price,int id)
        :_name(name)
        ,_price(price)
        ,_id(id){ }
};
int main()
{
    vector<Goods> goods = { {"苹果",3.5, 1}, {"橙子",4.67, 2},{"葡萄",3.45, 3} };

    //按价格升序排列
    sort(goods.begin(), goods.end(), [](const Goods& a, const Goods& b) {return a._price < b._price; });
    for (auto g : goods) cout << g._id << " " << g._name << " " << g._price << endl;
    return 0;
}

测试结果:

3.2.简化可调用对象定义

  • 替代函数指针:不需要再单独定义函数
  • 临时逻辑封装:线程执行函数、回调函数等
    代码示例:
cpp 复制代码
#include <iostream>
#include <thread>
using namespace std;

int main()
{
    int num = 0;
    // 线程执行逻辑用Lambda封装
    thread t1([&num]() {
        for (int i = 0; i < 10000; ++i) {
            num++;
        }
        });
    thread t2([&num]() {
        for (int i = 0; i < 10000; ++i) {
            num++;
        }
        });
    t1.join();
    t2.join();
    cout << "num: " << num << endl;
    return 0;
}

4.底层原理

4.1.核心本质

  • 底层逻辑:Lambda表达式编译后会生成一个匿名仿函数类
  • 对应关系:

捕捉列表 --> 仿函数类的成员变量

参数列表 --> 仿函数operator()的参数

函数体 --> 仿函数operator()的函数体

  • Lambda表达式与仿函数的转换:
cpp 复制代码
// Lambda表达式
auto rateCalc = [rate](double money, int year) {
    return money * rate * year;
    };

// 编译器生成的等价仿函数
class Lambda_Generated {
private:
    double _rate; // 捕捉的变量作为成员变量
public:
    Lambda_Generated(double rate) : _rate(rate) {}
    double operator()(double money, int year) const {
        return money * _rate * year; // 函数体与Lambda一致
    }
};

4.2.汇编层面验证

  • Lambda对象的创建:调用生成类的构造函数(传捕捉变量)
  • Lambda的调用:本质是调用生成类(仿函数)的operator()方法
    汇编代码:
cpp 复制代码
// 下面operator()中才能使用
eax, [rate]
ecx,[r1]
00D8295C lea eax,[rate] 
00D8295F push eax 
eax
00D82960 lea ecx,[r2] 
ecx,[r2]
00D82963 call `main '::`2'::<lambda_1>::<lambda_1> (0D81F80h) 

// 函数对象
Rate r1(rate);

00D82968 sub esp, 8
00 D8296B movsd xmm0,mmword ptr [rate] 
00 D82970 movsd mmword ptr [esp],xmm0 

00D82975 lea ecx,[r1] 
00D82978 call Rate::Rate (0D81438h) 
r1(10000, 2);
00D8297D push 2
00D8297F sub esp, 8
00 D82982 movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)] 
00 D8298A movsd mmword ptr [esp],xmm0 
ecx,[r1]
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
00 D8299E movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)] 
00 D829A6 movsd mmword ptr [esp],xmm0 
ecx,[r2]
00D829AB lea ecx,[r2] 
00D829AE call `main '::`2'::<lambda_1>::operator() (0D824C0h) 

5.注意事项

  • 一定不能引用捕捉已销毁的局部变量
  • mutable修饰 后的值捕捉的修改,不会影响原变量
  • 全局 作用域的 Lambda 捕捉列表必须为空

捕捉列表中参数相当于仿函数类中成员变量,设想成员变量中如果声明了一个静态变量,那么这个静态成员显然不和其他成员存储在同一片内存空间上,它的初始化顺序也是不确定的。因此全局作用域中,初始化列表不能传值(全局/静态变量)

  • 大对象最好用引用捕捉,值捕捉拷贝开销太大

总结

Lambda表达式作为C++11的核心特性,极大简化了臃肿的代码:无需定义独立函数或仿函数,可在局部直接封装临时逻辑,让代码更紧凑直观;灵活的捕捉机制能直接复用上下文变量,省去参数传递的冗余,大幅提升了开发效率。

相关推荐
~无忧花开~3 小时前
JavaScript实现PDF本地预览技巧
开发语言·前端·javascript
靠沿3 小时前
Java数据结构初阶——LinkedList
java·开发语言·数据结构
4***99743 小时前
Kotlin序列处理
android·开发语言·kotlin
froginwe113 小时前
Scala 提取器(Extractor)
开发语言
t***D2643 小时前
Kotlin在服务端开发中的生态建设
android·开发语言·kotlin
Elias不吃糖3 小时前
LeetCode每日一练(209, 167)
数据结构·c++·算法·leetcode
Want5953 小时前
C/C++跳动的爱心②
c语言·开发语言·c++
初晴や3 小时前
指针函数:从入门到精通
开发语言·c++
铁手飞鹰4 小时前
单链表(C语言,手撕)
数据结构·c++·算法·c·单链表