「学习笔记」Lambda 表达式
仅入门,主要为 C++11 和 C++14 标准,高版本的 Lambda 表达式更复杂,但对竞赛来说一般用不上很复杂的 Lambda 表达式。
平时我们使用 sort()
函数进行自定义排序,一般都会写一个函数,像下面这样的。
bool cmp(int a, int b) {
return a > b;
}
int main() {
int a[] = {1, 2, 3, 4, 5};
sort(a, a + 5, cmp);
for (int i = 0; i < 5; ++ i) {
cout << a[i] << ' ';
}
return 0;
}
这个 cmp()
函数的作用就是将数组从大到小排序。
Lambda 表达式,可以直接用来代替 cmp()
函数,用 Lambda 表达式改写上面的代码。
int main() {
int a[] = {1, 2, 3, 4, 5};
auto cmp = [&](int a, int b) {
return a > b;
};
sort(a, a + 5, cmp);
// 或者是这样
/*
sort(a, a + 5, [&](int a, int b) {
return a > b;
});
*/
for (int i = 0; i < 5; ++ i) {
cout << a[i] << ' ';
}
work();
return 0;
}
尽管第二份代码看着怎么着也比第一份代码更高级,但是,两份代码最后的和输出都是一样的,Lambda 表达式起的作用与 cmp()
函数是一样的。
Lambda 表达式#
Lambda 表达式的语法结构如下。
[捕获](参数) lambda 说明符 {函数体}
通过上面的例子,我们也会察觉,Lambda 表达式只是可以代替一些函数的工作,即 Lambda 表达式能完成的工作也可以被其他 C++ 语法完成,因此,它只是一种语法糖 但也很甜呀。
捕获#
顾名思义,就是捕获变量。
捕获是一个含有零或多个捕获符的逗号分隔列表,有两种默认捕获符。
-
&
以引用方式捕获变量。 -
=
以复制方式捕获变量。
如果捕获列表中只有一个 &
,则默认是引用捕获,同理,如果捕获列表中只有一个 =
,则默认是复制捕获,如果为空,则表示 Lambda 表达式的主体不会访问封闭范围内的变量。
当默认捕获符是 &
时,后继的简单捕获符不能以 &
开始。这里经过测试,只会弹出警告页面,但不会报错。
当变量以复制捕获时,如果没有 mutable
标识符,该变量是不能修改的。
下面用几份代码来试验一下。
int main() {
int a = 2, b = 1, c = 3;
auto it1 = [&] {
b = a;
a = c;
};
it1();
cout << a << ' ' << b << '\n';
return 0;
}
这份代码里面,默认捕获方式为引用捕获,因此 和 都以引用捕获, 和 的值都被修改了。最后的输出为 3 2
。
int main() {
int a = 2, b = 1;
auto it1 = [&, a] {
b = a;
};
it1();
cout << a << ' ' << b << '\n';
return 0;
}
在这份代码里面,这个捕获的含义为:默认变量以引用捕获,但是 以复制捕获,因此 以引用捕获, 的值被修改了,而 以复制捕获,在这里面作为只读变量,值不会被修改。最后的输出为 2 2
。
int main() {
int a = 2, b = 1;
auto it1 = [&, &a] {
b = a;
};
it1();
cout << a << ' ' << b << '\n';
return 0;
}
这份代码与第一份代码输出是一样的,只是会弹出警告。
int main() {
int a = 2, b = 1;
auto it1 = [=, &a] {
a = b;
};
it1();
cout << a << ' ' << b << '\n';
return 0;
}
这份代码里面,默认是以复制捕获, 以引用捕获, 的值会被修改。最后输出 1 1
。
int main() {
int a = 2, b = 1;
auto it1 = [=, &a] {
a = b;
b = a;
};
it1();
cout << a << ' ' << b << '\n';
return 0;
}
报错,因为 是以复制捕获,没有 mutable
标识符是不可以被修改的。
int main() {
int a = 2, b = 1;
auto it1 = [] {
a = b;
b = a;
};
it1();
cout << a << ' ' << b << '\n';
return 0;
}
报错,it1
不能捕获 和 。
int a = 2, b = 1, c = 3;
int main() {
auto it1 = [] {
a = b;
b = c;
};
it1();
cout << a << ' ' << b << '\n';
return 0;
}
int a = 2, b = 1, c = 3;
int main() {
auto it1 = [=] {
a = b;
b = c;
};
it1();
cout << a << ' ' << b << '\n';
return 0;
}
这两份代码的输出都是 1 3
,因为全局变量默认以引用捕获,而捕获列表是针对捕获封闭空间的变量的,不会影响到全局变量。
参数#
与函数的参数差不多,下面这两份代码中,最后的输出都是一样的。
int main() {
int a = 2, b = 1;
auto it1 = [](int c, int d) {
c = d;
return c;
};
cout << it1(a, b) << '\n';
return 0;
}
int it1(int a, int b) {
a = b;
return a;
}
int main() {
int a = 2, b = 1;
cout << it1(a, b) << '\n';
return 0;
}
lambda 说明符#
lambda 说明符由说明符、异常说明、属性和尾随返回类型按前述顺序组成,每个组分均非必需,这里只会讲解说明符和尾随返回类型。
说明符
在 CCF 给我们开 C++17 之前,我们能用的说明符有 mutable
,另外的几个说明符都需要更高版本的 C++,constexpr
从 C++17 起可以用,consteval
从 C++20 起可以用(constexpr
和 consteval
不能同时使用),static
从 C++23 起才能使用(mutable
和 static
不能同时使用,且使用 static
时,捕获列表必须为空)。
前面我们提到了,当变量以复制捕获时,如果没有 mutable
标识符,该变量是不能修改的,不提供说明符时复制捕获的对象在 Lambda 体内是 const
的。
mutable
:允许函数体修改以复制捕获的对象,以及调用它们的非 const
成员函数。
使用了 mutable
说明符,我们就可以修改以复制捕获的变量了,在使用这个标识符时,前面的参数括号不要省略,即使里面为空,否则会弹出警告。
下面的这份代码不加 mutable
是会报错的,因为我们修改了以复制捕获的变量 ,只要我们加上一个 mutable
说明符就可以了通过了。最后输出 3 1
。(这里我们只是可以在函数体内修改以复制捕获的变量,但该变量本身不是以引用捕获的,因此它本身的值并不会改变)。
int main() {
int a = 2, b = 1, c = 3;
auto it1 = [=, &a] () mutable {
a = b;
b = c;
a = b;
};
it1();
cout << a << ' ' << b << '\n';
return 0;
}
尾随返回类型
用于指定 Lambda 表达式的返回类型。若没有指定返回类型,则返回类型将被自动推断。具体的,如果函数体中没有 return
语句,返回类型将被推导为 void
,否则根据返回值推导。
int main() {
int a = 2, b = 1, c = 3;
auto it1 = [=, &a] () mutable {
a = b;
b = c;
a = b;
return a;
};
cout << it1() << '\n';
a = 2, b = 1, c = 3;
auto it2 = [=, &a] () mutable -> int {
a = b;
b = c;
a = b;
return a;
};
cout << it2() << '\n';
return 0;
}
代码中的 it1()
与 it2()
效果是一样的,只是 it1()
是根据返回值自动判断的,it2()
是指定的 int
类型。
函数体#
这个其实就是跟函数一模一样了,将你要运行的操作写在里面即可。
其他#
关于 Lambda 表达式,我们发现上面的代码定义都是 auto
类型的,其实,除了 auto
类型,还可以是 function
类型的。
类模板 std::function
,定义于头文件 <functional>
。std::function
能存储、复制及调用任何可调用目标——函数、Lambda 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。
在 Lambda 表达式中,存储需要表明返回类型和参数,如下。
int main() {
int a = 2, b = 1, c = 3;
function<int()> it1 = [=, &a] () mutable {
return a;
};
cout << it1() << '\n';
function<void(int)> it2 = [&] (int a) {
b = a * a;
cout << b << '\n';
};
it2(a);
function<ll(int, int, int)> it3 = [] (int x, int y, int z) {
return 1ll * x * y * z;
};
cout << it3(a, b, c) << '\n';
return 0;
}
就像这样,在尖括号内,先表明返回类型,再在小括号内表明参数类型,有几个参数就写几个,是 int
就写 int
,是 double
就写 double
。如果没有参数,也不能省略这个小括号。
此外,Lambda 表达式被定义后,只能在该封闭范围内使用,否则会报错。
void work() {
int a = 2, b = 1, c = 3;
cout << it3(a, b, c) << '\n';
}
int main() {
int a = 2, b = 1, c = 3;
function<int()> it1 = [=, &a] {
return a;
};
cout << it1() << '\n';
function<void(int)> it2 = [&] (int a) {
b = a * a;
cout << b << '\n';
};
it2(a);
function<ll(int, int, int)> it3 = [] (int x, int y, int z) {
return 1ll * x * y * z;
};
cout << it3(a, b, c) << '\n';
work();
return 0;
}
这份代码就报错了,因为 it3()
实在 main()
里面定义的,work()
里面不能访问到,同理,在 work()
里面定义的 Lambda 表达式,main()
里面也不能访问到,当然,你也可以定义全局都可以使用的 Lambda 表达式,像这样。其实定义在全局就跟函数几乎一样了。
function<ll(int, int, int)> it3 = [] (int x, int y, int z) {
return 1ll * x * y * z;
};
void work() {
int a = 2, b = 1, c = 3;
cout << it3(a, b, c) << '\n';
}
int main() {
int a = 2, b = 1, c = 3;
function<int()> it1 = [=, &a] {
return a;
};
cout << it1() << '\n';
function<void(int)> it2 = [&] (int a) {
b = a * a;
cout << b << '\n';
};
it2(a);
cout << it3(a, b, c) << '\n';
work();
return 0;
}
关于 Lambda 表达式的应用,可以代替一些函数,像 sort()
里面的自定义比较函数。但总归而言,它只是一种语法糖,只是可以让代码更可读并且自带 inline
,更高版本的 C++ 中,Lambda 表达式的使用会更复杂,想了解更多的可以来这里。
参考资料#
作者:yifan0305
出处:https://www.cnblogs.com/yifan0305/p/17539890.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
转载时还请标明出处哟!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2022-07-10 「学习笔记」treap(大根堆)
2022-07-10 「学习笔记」treap(小根堆)