C++ lambda 匿名函数
1、基本介绍
C++11 引入的 lambda 匿名函数(Lambda Expression)是一种轻量级的函数对象,可在需要函数的地方直接定义,无需单独声明,极大简化了代码编写(尤其是回调函数、算法谓词等场景)。
基本语法:
[capture-list] (parameter-list) mutable noexcept(optional) -> return-type { function-body }
| 组成部分 | 说明 |
|---|---|
capture-list |
捕获列表:指定如何捕获 lambda 所在作用域的局部变量(值捕获、引用捕获等),不可省略。 |
parameter-list |
参数列表:与普通函数的参数列表一致(可省略,若无形参)。 |
mutable |
可选关键字:允许在 lambda 内部修改值捕获的变量(默认值捕获变量为 const)。 |
noexcept |
可选:指定 lambda 是否可能抛出异常(C++11 起)。 |
-> return-type |
返回类型:可选,若函数体仅有一条 return 语句,编译器可自动推导返回类型。 |
function-body |
函数体:lambda 的执行逻辑。 |
简单的例子
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// 使用 Lambda 表达式作为 std::sort 的比较准则
// 按降序排序
std::sort(v.begin(), v.end(),
[](int a, int b) { return a > b; } // Lambda 表达式
);
for (int i : v) {
std::cout << i << " ";
}
// 输出: 9 6 5 4 3 2 1 1
return 0;
}
2、捕获列表
捕获列表定义了 Lambda 表达式如何从其所在的作用域中访问外部变量。
2.1 值捕获
将外部变量的值拷贝到 Lambda 中。Lambda 内部修改不会影响外部变量。
int main() {
int x = 10;
int y = 20;
// 值捕获:将 x 和 y 的当前值拷贝到 Lambda 中
auto lambda_val = [x, y]() {
std::cout << "Inside lambda (by value): " << x << ", " << y << std::endl;
// x++; // 错误!默认情况下,值捕获的变量是 const 的。
};
x = y = 100; // 修改外部变量
lambda_val(); // 调用 Lambda
// 输出: Inside lambda (by value): 10, 20
return 0;
}
2.2 引用捕获
捕获外部变量的引用。Lambda 内部修改会直接影响外部变量。
int main() {
int x = 10;
int y = 20;
// 引用捕获:捕获 x 和 y 的引用
auto lambda_ref = [&x, &y]() {
std::cout << "Inside lambda (by ref): " << x << ", " << y << std::endl;
x++; y++; // 修改会影响外部变量
};
lambda_ref(); // 调用 Lambda
std::cout << "After lambda: " << x << ", " << y << std::endl;
// 输出:
// Inside lambda (by ref): 10, 20
// After lambda: 11, 21
return 0;
}
2.3 隐式捕获
让编译器根据 Lambda 体内的代码自动推断需要捕获哪些变量。
[=]:以值捕获的方式捕获所有使用到的外部变量。[&]:以引用捕获的方式捕获所有使用到的外部变量。
int a = 1, b = 2, c = 3;
// 隐式值捕获:自动捕获所有使用到的外部变量 (a, b)
auto lambda1 = [=]() { std::cout << a + b << std::endl; };
// 注意:c 没有被使用,所以不会被捕获
// 隐式引用捕获:自动捕获所有使用到的外部变量 (a, c)
auto lambda2 = [&]() { std::cout << a + c << std::endl; c = 100; };
注意:应谨慎使用隐式捕获,尤其是 [&],因为它可能让你无意中修改外部变量或引入悬空引用。
2.4 混合捕获
int a = 1, b = 2, c = 3, d = 4;
// a 显式值捕获,b 显式引用捕获,其他使用到的变量按值捕获(但这里没有其他变量了)
auto lambda1 = [=, &b]() { /* a by value, b by ref */ };
// a 显式引用捕获,b 显式值捕获,其他使用到的变量按引用捕获(但这里没有其他变量了)
auto lambda2 = [&, b]() { /* a by ref, b by value */ };
// 错误!不能混合相同的捕获模式: [=, a] 或 [&, &b]
2.5 捕获 this 指针
在类的成员函数中,Lambda 可以通过值 [this] 或引用 [&] 捕获 this 指针,从而访问类的成员变量和函数。
class MyClass {
public:
void doSomething() {
// 捕获 this,从而可以访问成员变量 value
auto lambda = [this]() {
std::cout << "Value: " << value << std::endl;
memberFunction();
};
lambda();
}
private:
int value = 42;
void memberFunction() { std::cout << "Member func called\n"; }
};
2.6 初始化捕获(C++14)
允许在捕获时初始化变量(类似变量声明),解决 “移动捕获” 等场景
#include <vector>
#include <utility> // for std::move
int main() {
vector<int> v = {1, 2, 3};
// 初始化捕获:将v移动到lambda内部的vec(避免拷贝大容器)
auto func = [vec = move(v)] {
cout << "vec size: " << vec.size() << endl;
};
func(); // 输出:vec size: 3
// cout << v.size() << endl; // 错误:v已被移动,处于无效状态
return 0;
}
3、mutable 关键字
默认情况下,对于值捕获的变量,Lambda 的 operator() 是 const 的,这意味着你不能在 Lambda 体内修改这些拷贝。
使用 mutable 关键字可以移除这个 const 限制。
int main() {
int count = 0;
// 没有 mutable: 错误!不能修改值捕获的变量。
// auto lambda = [count]() { count++; };
// 使用 mutable
auto lambda = [count]() mutable {
count++; // 现在可以修改了
std::cout << "Count inside lambda: " << count << std::endl;
};
lambda(); // 输出: Count inside lambda: 1
lambda(); // 输出: Count inside lambda: 2
std::cout << "Count outside: " << count << std::endl; // 输出: Count outside: 0
// 注意:修改的是 Lambda 内部的副本,不影响外部变量。
return 0;
}
重要:mutable 允许你修改的是 Lambda 内部副本的值,对外部变量毫无影响。引用捕获不需要 mutable。
4、返回类型
编译器通常可以自动推导 Lambda 的返回类型。但如果函数体中有多个返回语句且类型不同,或者你想要更明确的代码,可以显式指定。
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 编译器自动推导返回类型为 bool
auto is_even = [](int n) { return n % 2 == 0; };
// 显式指定返回类型为 double (使用尾置返回类型语法)
auto divide = [](int a, int b) -> double {
if (b == 0) {
return 0.0; // 返回 double
}
return static_cast<double>(a) / b; // 返回 double
};
5、常见用法与示例
5.1 与 STL 算法结合
std::vector<int> vec = {5, 3, 8, 1, 9};
// 计算大于 5 的元素数量
int count = std::count_if(vec.begin(), vec.end(),
[](int n) { return n > 5; });
// 将所有元素翻倍
std::for_each(vec.begin(), vec.end(),
[](int& n) { n *= 2; }); // 注意:需要引用才能修改原值
vector<int> nums = {3, 1, 4, 1, 5, 9};
// 用lambda作为sort的比较函数(降序排序)
sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b;
}); // nums变为:9,5,4,3,1,1
// 用lambda作为find_if的条件(查找偶数)
auto it = find_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0;
});
if (it != nums.end()) {
cout << "找到偶数:" << *it << endl; // 输出:4
}
5.2 并发编程中的任务
线程或异步任务(std::thread、std::async)需要执行函数,lambda 可直接定义任务逻辑
#include <thread>
#include <future>
int main() {
// 线程任务用lambda定义
thread t([] {
cout << "线程执行中..." << endl;
});
t.join();
// 异步任务用lambda定义
future<int> fut = async([] {
return 1 + 2;
});
cout << "异步结果:" << fut.get() << endl; // 输出:3
return 0;
}
5.3 自定义比较器
std::map<std::string, int> name_age;
// 按值(年龄)排序,而不是键(名字)
std::vector<std::pair<std::string, int>> vec(name_age.begin(), name_age.end());
std::sort(vec.begin(), vec.end(),
[](const auto& a, const auto& b) { return a.second < b.second; });
6、常见问题
1. Lambda 表达式中的捕获列表 [=] 和 [&] 有什么区别?
[=] 表示隐式值捕获,Lambda 体内使用的所有外部变量都会将其当前值拷贝一份到 Lambda 对象中。[&] 表示隐式引用捕获,Lambda 体内使用的所有外部变量都会以其引用被捕获,在 Lambda 内部修改它们会影响外部变量。应谨慎使用 [&],以免造成意外的副作用或悬空引用。
2. 什么是“初始化捕获”(Init Capture)?它解决什么问题?
初始化捕获(C++14)允许在捕获列表中直接初始化一个新的成员变量。它主要解决了移动捕获的问题。例如,你不能用普通捕获移动一个 std::unique_ptr(因为无法拷贝),但可以用 [p = std::move(unique_ptr)] 将其所有权移动到 Lambda 内部。它也允许你以任意表达式初始化捕获的变量。
3. mutable 关键字在 Lambda 中起什么作用?
默认情况下,对于值捕获的变量,Lambda 的函数调用运算符 (operator()) 是 const 的,这意味着你不能修改这些捕获的副本。mutable 关键字移除了这个 const 限制,允许你修改 Lambda 内部的值捕获变量。需要注意的是,这修改的只是副本,不影响外部原始变量。
4. Lambda 表达式的类型是什么?如何存储或传递一个 Lambda?
每个 Lambda 表达式都会生成一个唯一的、编译器生成的、未命名的类型(闭包类型)。存储和传递它的最佳方式是:
- 使用
auto进行初始化(auto lambda = [...](){...};)。 - 使用
std::function(如std::function<void()>),这会带来一些类型擦除的开销,但非常灵活。 - 在模板中使用(
template<typename F> void foo(F func)),这是零开销的方式。
5. 在类的成员函数中,Lambda 如何访问类的成员变量?
需要通过捕获 this 指针。使用 [this] 或 [&](隐式捕获)可以捕获当前对象的 this 指针,从而在 Lambda 内部访问类的成员变量和成员函数。需要注意的是,如果 Lambda 的生命周期可能比对象更长(例如,被放入一个全局队列),这会导致悬空 this 指针。C++17 的 [\*this] 可以按值捕获整个对象的副本,避免这个问题。
C++ lambda 匿名函数的更多相关文章
- lambda匿名函数透析
lambda匿名函数透析 目录 1 匿名函数的作用... 1 2 匿名函数的格式... 1 3 匿名函数实例代码... 3 1 匿名函数的作用 ...
- lambda 匿名函数
# 普通python函数 def func(a,b,c): return a+b+c print func(1,2,3) # 返回值为6 # lambda匿名函数 f = lambda a,b,c:a ...
- lambda 形参:返回值 lambda 匿名函数 格式:
lambda 匿名函数 格式: lambda 形参:返回值 e.g f = lambda n:n**2 print(f(10))
- Python 进阶 之 lambda 匿名函数
lambda 是个匿名函数,通常用于简单判断或者处理,例如判断一个数的奇偶性,过滤字符串,逻辑运算等等. lambda表达式: >>>lambda x:x*x >>> ...
- xpinyin-函数返回多个值-lambda匿名函数-列表生成式-三元表达式
import xpinyinp=xpinyin.Pinyin() #实例化print(p.get_pinyin('小白','')) 函数返回多个值:1.函数如果返回多个值的话,它会把这几个值放到一个元 ...
- lambda匿名函数和他的小伙伴(处理大量数据的时候用到)
lambda匿名函数 主要是为了解决一些简单的需求而设计的一句话函数 #计算n的n次方 def func(n): return n**n f = lambda n : n ** n 语法: 函数名 = ...
- lambda匿名函数,sorted(),filter(),map(),递归函数
1.lambda匿名函数 为了解决一些简单的需求而设计的一句话函数 #计算n的n次方 def func(n): return n**n print(func(10)) f = lambda n: n* ...
- python基础-4 函数参数引用、lambda 匿名函数、内置函数、处理文件
上节课总结 1.三元运算 name=“name1”if 条件 else “name2” 2.深浅拷贝 数字.字符串 深浅,都一样 2.其他 浅拷贝:只拷贝第一层 深拷贝:不拷贝最后一层 3.set集合 ...
- Python 之父为什么嫌弃 lambda 匿名函数?
Python 支持 lambda 匿名函数,其扩展的 BNF 表示法是lambda_expr ::= "lambda" [parameter_list] ":" ...
- Java中的lambda匿名函数使用
Java中的lambda匿名函数使用 lambda匿名函数的使用是为了满足某些情况下需要临时定义函数,或者事先定义,需要时才使用.在python里面,lambda表达式的表达方式为:lambda 参数 ...
随机推荐
- C# WinForm 自定义控件绑定属性DataBindings
https://www.cnblogs.com/jizhongfong/p/4384689.html var bind = new Binding("Enabled", Order ...
- 【7】Manacher算法学习笔记
前言 Manacher 算法是最好写的字符串算法.--教练 暴力求法 给出一个只由小写英文字符 \(\texttt a,\texttt b,\texttt c,\ldots\texttt y,\tex ...
- 堆排序 使用 algorithm
简介 show code code #include <set> #include <vector> #include <list> #include <io ...
- opengl 学习 之 17 lesson
opengl 学习 之 17 lesson 简介 这个教程有点超出了opengl的范围了,但是解决了一个非常公共的问题:如何去表示旋转? 在第3个教程,我们学会了矩阵可以表示一个点绕着一个特定的轴旋转 ...
- unable to find string literal operator ‘operator""format’ with ‘const char [10]’, ‘long unsigned int’ arguments
简介 遇到问题 unable to find string literal operator 'operator""format' with 'const char [10]', ...
- iga 入门之 强解表达式和 弱解表达式
简介 摘自 流体力学数值方法 弱解几分表达式 对Galerkin几分表达式(1-76)式进行分布几分,然后将自然边界条件带入表达式中,由此所获得的几分表达式,将作为Galerkin法求解的出发点.此时 ...
- POLIR-Laws-民法典: 第 5 章: 民事权利
POLIR-Laws-民法典: 第 5 章: 民事权利 第五章 民事权利 第一百零九条 自然人的人身自由.人格尊严受法律保护. 第一百一十条 自然人享有: 生命权.身体权.健康权. 姓名权.肖像 ...
- SciTech-BigDataAIML- Python Data Science Handbook 以及 HTML源码 转Markdown源码 的办法:
以下文为例: Copy HTML Source code from the web page. Transform the HTML code to Markdown code: https://co ...
- SciTech-EE-无线路由模块: 4G/5G全网通转WiFi/RJ45网口 + DTU: 无线传送设备 (Data Transfer unit)是将 UART(串口)数据 通过GPRS手机网络“透明的”与互联网Socket(IP套接字)数据 双向透明桥接互转的通用电子模块或无线终端设备
https://baike.baidu.com/item/DTU/183474?fr=ge_ala 各大电商平台搜"DTU"例如"亿伯特的一款DTU" 和 &q ...
- Coze Studio:字节跳动 Coze 的开源版本来了!第一时间深度解析
一早起来,看到字节跳动把他们的 AI Agent 开发平台 Coze 开源了,取名 Coze Studio(项目地址:https://github.com/coze-dev/coze-studio). ...