在学习 C 和 Qt 的过程中经常会看到这样的代码auto bitsum [](int x) - int { int res 0; while (x) { res x % 10; x / 10; } return res; };或者在 Qt 的connect()中看到connect(button, QPushButton::clicked, this, [sender]() { sender-sendData(); });刚开始看到[]、[sender]很容易疑惑。其实这两段代码使用的都是同一个 C 特性Lambda 表达式。本文从 Lambda 的基本语法开始重点介绍最容易混淆的捕获列表。一、什么是 Lambda 表达式Lambda 可以简单理解为在代码中直接定义一个匿名函数。例如普通函数int add(int a, int b) { return a b; }如果这个函数只使用一次其实没有必要专门给它起一个函数名。可以直接写成 Lambdaauto add [](int a, int b) - int { return a b; }; cout add(3, 5);输出8这里的add是一个变量它保存了这个 Lambda。因此可以通过add(3, 5);调用它。二、Lambda 的基本语法Lambda 的常见完整形式是[capture](parameters) - return_type { // 函数体 };例如auto add [](int a, int b) - int { return a b; };可以拆成[] (int a, int b) - int │ │ │ 捕获列表 参数列表 返回值类型最后{ return a b; }是 Lambda 的函数体。三、[]是什么Lambda 最前面的[]叫做捕获列表Capture List这是 Lambda 与普通函数一个非常重要的区别。捕获列表主要解决一个问题Lambda 如果需要使用外部作用域中的局部变量该怎么办例如void test() { int x 10; auto func []() { cout x; }; }这段代码会出错。因为x是test()中的局部变量而 Lambda 默认不能直接访问这个外部局部变量。可以通过捕获列表解决void test() { int x 10; auto func [x]() { cout x; }; }这里[x]表示把外面的x捕获到 Lambda 中。四、[x]按值捕获最基本的捕获方式是[x]表示按照值捕获x。例如int x 10; auto func [x]() { cout x; }; func();输出10可以把它简单理解成 Lambda 保存了一份x的副本。因此int x 10; auto func [x]() { cout x; }; x 20; func();输出仍然是10因为 Lambda 中保存的是捕获时的x副本。五、[x]按引用捕获如果写成[x]则表示按引用捕获x。例如int x 10; auto func [x]() { cout x; }; x 20; func();输出20因为 Lambda 访问的是外部原来的x。甚至可以直接修改它int x 10; auto func [x]() { x 100; }; func(); cout x;输出100所以可以简单记忆[x] → 按值捕获 [x] → 按引用捕获六、[]不捕获任何外部变量例如auto func []() { cout Hello; };这里[]表示不捕获任何外部局部变量。前面 LeetCode 中的数位和代码就是这种情况auto bitsum [](int x) - int { int res 0; while (x) { res x % 10; x / 10; } return res; };这里的x并不是外部变量。它来自(int x)也就是说x → Lambda 的参数而不是x → 外部变量所以根本不需要捕获。七、[]默认按值捕获如果外面有多个变量int a 10; int b 20; int c 30;可以写auto func []() { cout a endl; cout b endl; cout c endl; };[]表示Lambda 中使用到的外部局部变量默认按照值捕获。这样就不需要写[a, b, c]八、[]默认按引用捕获同理auto func []() { a; b; c; };表示Lambda 中使用到的外部局部变量默认按照引用捕获。因此[] → 默认按值捕获 [] → 默认按引用捕获这是非常常用的两种写法。九、捕获列表可以混合使用捕获列表并不只能全部按值或者全部按引用。例如int a 10; int b 20; auto func [a, b]() { cout a endl; b; };这里[a, b]表示a → 按值捕获 b → 按引用捕获也可以使用默认捕获方式再单独指定某个变量。例如[, b]表示默认按值捕获但是b按引用捕获。而[, a]表示默认按引用捕获但是a按值捕获。十、Lambda 中的“参数”和“捕获”不要混淆这是学习 Lambda 时非常容易混淆的地方。例如auto func [x](int y) - int { return x y; };这里有三个不同的部分[x] → 捕获列表 (int y) → 参数列表 - int → 返回值类型其中x是外部变量通过捕获列表进入 Lambda。而y是调用 Lambda 时传入的参数。例如int x 10; auto func [x](int y) - int { return x y; }; cout func(5);结果15所以捕获列表解决的是“Lambda 如何使用外部变量”参数列表解决的是“调用 Lambda 时如何传入参数”。十一、Lambda 在 Qtconnect()中的使用Lambda 在 Qt 中非常常见尤其是connect()。例如connect(button, QPushButton::clicked, this, []() { qDebug() 按钮被点击; });这里第四个参数就是一个 Lambda[]() { qDebug() 按钮被点击; }因为这个 Lambda 不需要使用外部局部变量所以捕获列表为空[]十二、Qt 中为什么会出现[sender]再看一个实际例子Sender *sender new Sender; connect(button, QPushButton::clicked, this, [sender]() { sender-sendData(); });这里Sender *sender new Sender;定义了一个局部变量sender而 Lambda 中需要使用这个变量sender-sendData();因此需要在捕获列表中写[sender]表示按值捕获外部的sender变量。这里尤其要注意Sender *sender中的sender是一个指针变量。所以[sender]捕获的是这个指针变量而不是Sender类型。可以简单理解为外部 sender ─────────→ Sender对象 │ │ 按值捕获 ↓ Lambda sender ─────────→ 同一个Sender对象指针本身复制了一份但是两个指针仍然指向同一个对象。十三、[sender]和[sender]的区别假设Sender *sender new Sender;那么[sender]表示按值捕获指针变量。而[sender]表示按引用捕获指针变量。也就是说两者的区别是[sender] ↓ 复制一份指针 [sender] ↓ 引用原来的指针变量在 Qt 的很多场景中如果只是需要 Lambda 使用这个指针常见写法就是[sender]十四、为什么 Lambda 在 Qt 中特别常见因为 Qt 的connect()可以直接接收 Lambda。例如connect(button, QPushButton::clicked, this, [this]() { ui-label-setText(Hello); });这里[this]也是一种捕获。它表示捕获当前对象的this指针。于是 Lambda 中可以使用ui-label等当前对象的成员。因此你在 Qt 项目中看到[this]()[sender]()[data]()本质上都是在使用 Lambda 的捕获列表。十五、Lambda 常见写法总结可以先记住下面这张表写法含义[]不捕获外部变量[x]按值捕获x[x]按引用捕获x[]外部变量默认按值捕获[]外部变量默认按引用捕获[a, b]a按值b按引用[, b]默认按值但b按引用[, a]默认按引用但a按值十六、回到最开始的 LeetCode 代码现在再看这段代码auto bitsum [](int x) - int { int res 0; while (x) { res x % 10; x / 10; } return res; };就很好理解了。auto bitsum ↓ 用一个变量保存 Lambda [] ↓ 不捕获外部变量 (int x) ↓ Lambda 的参数 - int ↓ 返回 int { ... } ↓ Lambda 函数体而 Qt 中[sender]() { sender-sendData(); }则是[sender] ↓ 按值捕获外部的 sender () ↓ 没有参数 { sender-sendData(); } ↓ 使用捕获进来的 sender因此理解 Lambda 最重要的就是先分清楚这三个部分[capture](parameters) - return_type其中捕获列表决定 Lambda 能不能使用外部变量以及如何捕获这些变量参数列表决定调用 Lambda 时接收什么参数返回值类型决定 Lambda 返回什么类型。掌握这几个基本概念后再看 C STL、Qtconnect()中的大量 Lambda 写法就不会再觉得[]、[this]、[sender]很神秘了。
