[C++11] : lambda表达式,函数式编程
C++11 Lambda 表达式与函数式编程
头文件:无需额外头文件,C++11 及以上标准原生支持
Lambda 是 C++11 引入的匿名函数,也是现代 C++ 函数式编程的核心语法,写法简洁、使用灵活,常搭配 STL 算法、线程、回调场景
一、前置认知:什么是函数式编程
1. 基本概念
传统 C++ 以面向对象、命令式编程为主,侧重「怎么做(步骤流程)」;
函数式编程是一种编程思想,核心是把函数当作一等公民:
- 函数可以作为参数传递、作为返回值返回
- 支持匿名函数、表达式式调用,弱化状态、强调逻辑运算
C++ 并非纯函数式语言,但 C++11 借助 Lambda、std::function、std::bind 等特性,完美支持函数式风格写法,Lambda 就是最常用的载体。
2. Lambda 的定位
Lambda = 匿名临时函数,不用写独立函数名、不用单独定义函数,随用随写,完美适配回调、算法遍历、简单逻辑片段。
二、Lambda 完整语法格式
标准语法框架
[捕获列表](参数列表) mutable -> 返回值类型 { 函数体 }
拆解五大组成部分(按顺序,部分可省略):
[]捕获列表:捕获当前作用域的外部变量(核心)()参数列表:和普通函数参数用法一致,无参数可省略mutable关键字:可选,允许修改值捕获的变量-> 类型尾置返回值:指定函数返回类型,可自动推导时省略{}函数体:具体执行逻辑
三、分步讲解 + 基础示例
3.1 最简 Lambda(无参数、无返回值、不捕获变量)
写法
无参数、无返回值,() 和 -> 均可省略:
#include <iostream>
using namespace std;
int main()
{
// 定义并直接调用 Lambda
[](){
cout << "Hello Lambda" << endl;
}();
return 0;
}
- 末尾
():表示立即执行这个匿名函数 - 等价于一个临时的无参无返回函数
把 Lambda 赋值给变量(重复调用)
Lambda 可赋值给变量,像普通函数一样多次调用:
int main()
{
auto func = [](){
cout << "重复执行 Lambda" << endl;
};
func(); // 第一次调用
func(); // 第二次调用
return 0;
}
3.2 带参数的 Lambda
用法和普通函数参数完全一致,() 不能省略:
int main()
{
// 接收两个 int 参数
auto add = [](int a, int b){
cout << a + b << endl;
};
add(10, 20); // 输出 30
add(50, 60); // 输出 110
return 0;
}
3.3 带返回值的 Lambda
分两种场景:显式指定返回值、自动推导返回值
3.3.1显式尾置返回值(-> 类型)
语法固定写在参数列表之后,适合多分支返回、类型复杂场景:
int main()
{
// 明确指定返回 int
auto add = [](int a, int b) -> int {
return a + b;
};
int res = add(3, 4);
cout << res << endl; // 输出 7
return 0;
}
3.3.2自动推导返回值(省略 -> 类型)
满足以下条件可省略返回值声明,编译器自动推导:
- 函数体只有一条 return 语句
- 所有 return 返回同一种类型
// 省略返回值,编译器自动推导为 int
auto sub = [](int a, int b){
return a - b;
};
cout << sub(10, 3); // 输出 7
注意:函数体内有多条
return且返回类型不同,必须显式写尾置返回值。
四、核心重点:捕获列表 []
Lambda 无法直接使用函数体外部的局部变量,必须通过捕获列表把外部变量“抓”进来使用。
捕获列表有 6 种常用规则,逐个讲解。
约定
假设外部变量:
int a = 10;
int b = 20;
4.1 1. 值捕获 [变量名]
- 作用:拷贝一份外部变量到 Lambda 内部,内部是副本
- 特点:默认只读,无法修改原变量,也无法修改副本
int main()
{
int a = 10;
// 值捕获 a
auto func = [a](){
cout << a << endl;
// a = 100; // 编译报错:值捕获默认只读
};
func(); // 输出 10
cout << a; // 外部 a 不变,仍为 10
return 0;
}
4.2 2. 引用捕获 [&变量名]
- 作用:捕获变量引用,内部直接操作原变量
- 特点:读写都生效,修改会影响外部原始变量
int main()
{
int a = 10;
// 引用捕获 a
auto func = [&a](){
a = 100; // 修改原变量
};
func();
cout << a; // 输出 100,外部变量被修改
return 0;
}
4.3 3. 全部值捕获 [=]
- 作用:当前作用域所有局部变量,统一使用值捕获
- 简写:不用逐个写变量名
int main()
{
int a = 10;
int b = 20;
// [=] 全部值捕获
auto func = [=](){
cout << a << " " << b << endl; // 正常使用 a、b
};
func();
return 0;
}
4.4 4. 全部引用捕获 [&]
- 作用:当前作用域所有局部变量,统一使用引用捕获
int main()
{
int a = 10;
int b = 20;
auto func = [&](){
a += 5;
b += 5;
};
func();
cout << a << " " << b; // 输出 15 25
return 0;
}
4.5 5. 混合捕获(常用组合)
同时使用「全部捕获 + 单独指定捕获方式」,优先级:单独捕获 > 全部捕获
示例1:整体值捕获,单独变量改为引用捕获
int a = 10, b = 20;
// [=, &b]:默认所有变量值捕获,唯独 b 用引用捕获
auto func = [=, &b](){
// a 只读,b 可修改
b = 99;
};
示例2:整体引用捕获,单独变量改为值捕获
int a = 10, b = 20;
// [&, a]:默认所有变量引用捕获,唯独 a 用值捕获
auto func = [&, a](){
// b 可修改,a 只读副本
};
4.6 6. this 捕获 [this](类内专用)
如果 Lambda 写在类的成员函数中,想要访问类的成员变量/成员函数,必须捕获 this 指针:
class Test
{
public:
int num = 100;
void run()
{
// 捕获 this,访问类成员
auto func = [this](){
cout << num << endl; // 访问成员变量
};
func();
}
};
int main()
{
Test t;
t.run(); // 输出 100
return 0;
}
五、mutable 关键字(值捕获专属)
前面提到:值捕获的变量默认只读,如果想在 Lambda 内部修改副本,需要加 mutable。
语法规则
(参数列表) mutable { ... }
- 只改变「副本」,不会影响外部原始变量
mutable必须写在参数列表之后
int main()
{
int a = 10;
// 值捕获 + mutable 允许修改副本
auto func = [a]() mutable {
a = 100; // 合法,修改内部副本
cout << a << endl;
};
func(); // 输出 100
cout << a; // 外部 a 仍然是 10,不受影响
return 0;
}
总结:
mutable仅用于值捕获,引用捕获不需要。
六、Lambda 与函数式编程实战场景
Lambda 是 C++ 函数式编程的核心,下面是开发中最常用的场景。
6.1 搭配 STL 算法(遍历、排序、筛选)
std::sort、std::for_each 等算法都接收函数作为参数,Lambda 是首选。
示例1:数组/容器排序
#include <iostream>
#include <vector>
#include <algorithm> // sort 算法
using namespace std;
int main()
{
vector<int> vec = {3, 1, 4, 2, 5};
// 降序排序:Lambda 作为比较函数
sort(vec.begin(), vec.end(), [](int x, int y){
return x > y;
});
// 遍历打印
for (auto v : vec)
{
cout << v << " ";
}
// 输出:5 4 3 2 1
return 0;
}
示例2:遍历容器 for_each
vector<int> vec = {1,2,3};
// Lambda 作为遍历回调
for_each(vec.begin(), vec.end(), [](int v){
cout << v * 2 << " ";
});
6.2 搭配 std::thread 线程(回调函数)
之前讲解线程库时用到过,Lambda 作为线程入口函数,捕获变量极其方便:
#include <iostream>
#include <thread>
using namespace std;
int main()
{
int num = 100;
// 引用捕获变量,创建线程
thread t([&num](){
cout << "子线程:" << num << endl;
});
t.join();
return 0;
}
6.3 存储 Lambda:std::function(函数对象)
Lambda 本质是临时函数,想要保存起来、多次传递,需要搭配 <functional> 头文件的 std::function,这是函数式编程「函数作为变量」的典型用法。
#include <iostream>
#include <functional>
using namespace std;
int main()
{
// 声明:接收两个int、返回int的函数类型
function<int(int, int)> calc;
// 赋值为 Lambda
calc = [](int a, int b){
return a + b;
};
cout << calc(2, 3); // 输出 5
return 0;
}
七、常见误区 & 新手避坑
-
引用捕获悬空风险
如果 Lambda 被延迟执行(线程、异步回调),不要引用局部变量。局部变量销毁后,引用变成野指针,程序崩溃。 -
捕获列表不能混用语法
不允许[=, =]、[&, &]这种重复全部捕获。 -
mutable 不修改外部变量
它只修改值捕获的副本,外部变量始终不变。 -
全局变量无需捕获
Lambda 可以直接使用全局变量,不用写进捕获列表。 -
Lambda 没有函数名
无法递归调用自身(C++11 原生不支持递归 Lambda,高阶用法除外)。
八、知识点总结
1. Lambda 完整结构回顾
[捕获](参数) mutable -> 返回值 { 函数体 }
[]捕获外部变量(值/引用/全部/this)()函数参数,无参可省略mutable允许修改值捕获的副本(可选)->尾置返回值,单return可自动推导(可选)
2. 捕获规则速查表
| 捕获写法 | 含义 | 可修改外部变量 |
|---|---|---|
[a] |
单独值捕获a | ❌ |
[&a] |
单独引用捕获a | ✅ |
[=] |
所有变量值捕获 | ❌ |
[&] |
所有变量引用捕获 | ✅ |
[this] |
捕获类this指针(类内使用) | - |
3. 函数式编程核心体现
- 函数可赋值给变量、可作为参数传递
- 匿名函数简化回调逻辑,代码更紧凑
- 结合 STL 算法、线程、异步回调,是现代 C++ 主流写法
4. 对比与普通函数和仿函数的核心优势
对比传统普通函数、仿函数:
- 无需单独定义函数,代码内聚性强
- 灵活捕获外部变量,使用场景更广
- 写法简洁,STL 算法、回调场景首选
更多推荐

所有评论(0)