由《ISO/IEC 14882:2011》(C++11标准文档),C++从C++11开始支持lambda表达式,俗称“匿名函数”。
因此,如果需要用g++进行编译,编译命令行应形如
$ g++ -o -std=c++11 code code.cpp
或
$ g++ -o -std=c++14 code code.cpp
匿名函数的原型如下
[capture] (parameters) mutable exception attribute -> return_type { body }
必须用方括号括起来的capture列表来开始一个lambda表达式的定义。
lambda函数的形参表比普通函数的形参表多了3条限制:
如果lambda函数没有形参且没有mutable(修饰变量,使其在const定义的函数中仍然可变)、exception(C++中异常的父类)或attribute声明,那么参数的空圆括号可以省略。但如果需要给出mutable、exception或attribute(用于编译器对源代码的优化)声明,那么参数即使为空,圆括号也不能省略。
如果函数体只有一个return语句,或者返回值类型为void,那么返回值类型声明可以被省略:
[capture](parameters){body}
举例如下:
[](int x, int y) {return x < y;} //返回表达式x < y的值
[](int &cxk) {++cxk;} //返回值自动设置为void
[](){++cxk;} //访问全局变量,形参void可被省略即
[]{++cxk;}
如果lambda函数体的形式是return expression
,或者什么也没返回,或者所有返回语句用decltype
都能检测到同一类型(我也不知道这是什么鬼玩意儿),那么返回值类型可以被省略。
如果需要显式指定返回值类型,则
[](int x, int y) -> int {return x + y;}
那么我们在构造sort函数时可以省略cmp函数而直接使用匿名函数
如将数组按降序排列:
不用匿名函数
#include <bits/stdc++.h>
using namespace std;
const int N = 114514;
int ikun[N];
int cmp(int a, int b) {
return a > b;
}
int main() {
int t;
cin >> t;
for(int i = 0; i < t; ++i) cin >> ikun[i];
sort(ikun, ikun + t, cmp);
for(int i = 0; i < t; ++i) cout << (i ? " " : "") << ikun[i];
}
匿名函数
#include <bits/stdc++.h>
using namespace std;
const int N = 114514;
int ikun[N];
int main() {
int t;
cin >> t;
for(int i = 0; i < t; ++i) cin >> ikun[i];
sort(ikun, ikun + t, [](int a, int b) -> int {return a > b;}); // -> int 可省略
for(int i = 0; i < t; ++i) cout << (i ? " " : "") << ikun[i];
}
可以少写个定义(虽然我不知道有什么用orz)
外部变量的捕获
lambda函数可以捕获lambda函数外的具有automatic storage duration的变量,即函数的局部变量与函数形参变量。函数体与这些变量的集合合起来称做闭包
。这些外部变量在声明lambda表达式时列在在方括号[
和]
中。空的方括号表示没有外界变量被capture或者按照默认方式捕获外界变量。这些变量被传值捕获或者引用捕获。对于传值捕获的变量,默认为只读(这是由于lambda表达式生成的为一个函数对象,它的operator()
成员缺省有const属性)。修改这些传值捕获变量将导致编译报错。但在lambda表达式的参数表的圆括号后面使用mutable关键字,就允许lambda函数体内的语句修改传值捕获变量,这些修改与lambda表达式(实际上是用函数对象实现)有相同的生命期,但不影响被传值捕获的外部变量的值。lambda函数可以直接使用具有static存储期的变量。如果在lambda函数的捕获列表中给出了static存储期的变量,编译时会给出警告,仍然按照lambda函数直接使用这些外部变量来处理。因此具有static存储期的变量即使被声明为传值捕获,修改该变量实际上直接修改了这些外部变量。编译器生成lambda函数对应的函数对象时,不会用函数对象的数据成员来保持被“捕获”的static存储期的变量。示例:
[] //未定义变量.试图在Lambda内使用任何外部变量都是错误的.
[x, &y] //x 按值捕获, y 按引用捕获.
[&] //用到的任何外部变量都隐式按引用捕获
[=] //用到的任何外部变量都隐式按值捕获
[&, x] //x显式地按值捕获. 其它变量按引用捕获
[=, &z] //z按引用捕获. 其它变量按值捕获
[this] //只传入this指针
上面是直接从维基百科抄过来的orz
下面是我的理解(举例论证)
例1
#include <bits/stdc++.h>
using namespace std;
int main() {
auto i = 1145141919810;
auto f = [](){cout << i << endl;};
f();
}
此处[]中未定义任何外部变量的捕获方式,编译不通过
例2
#include <bits/stdc++.h>
using namespace std;
int main() {
auto i = 114514;
auto f = [=](){cout << i << endl;};
i = 1919810;
f();
}
由于传入的是i的值,此处输出114514
例3
#include <bits/stdc++.h>
using namespace std;
int main() {
auto i = 114514;
auto f = [&](){cout << i << endl;};
i = 1919810;
f();
}
由于传入的是i的引用,此处输出1919810
例4
比较以下两段代码
代码段1
#include <bits/stdc++.h>
using namespace std;
auto i = 114514;
int main() {
auto f = [=](){cout << i << endl;};
f();
i = 1919810;
f();
}
代码段2
#include <bits/stdc++.h>
using namespace std;
int main() {
auto i = 114514;
auto f = [=](){cout << i << endl;};
f();
i = 1919810;
f();
}
分别输出
114514
1919810
114514
114514
代码段1中全局变量i并不是被捕获而输出,全局变量的输出更随其改动而变动
代码段2中局部变量i被捕获,在匿名函数中被记录为$i=114514$,之后对局部变量的改动不影响i在f中的值
例5
#include <bits/stdc++.h>
using namespace std;
long long i = 114514;
class cxk{
private:
int basketball;
public:
cxk();
void music();
};
cxk::cxk() {
basketball = 114514;
}
void cxk::music() {
int rap = 1919810;
auto f = [this](){cout << this->basketball << rap << endl;};
f();
}
int main() {
cxk nmsl;
nmsl.music();
}
此处出现编译错误,因为在auto f = [this](){cout << this->basketball << rap << endl;};
处引用了不属于类cxk的变量rap虽然他喜欢唱跳rap篮球
如果捕获方式改为=或&,则默认捕获this指针
例6
#include <bits/stdc++.h>
using namespace std;
map<int, int>mp;
int main() {
mp[0] = 1, mp[1] = 2, mp[10] = 99;
for(auto &x : mp) {
auto f = [=](){cout << x.first << " " << x.second << endl;};
f();
}
}
捕获方式可改为&
C++14增加了广义捕获(Generalized capture)。即在捕获子句(capture clause)中增加并初始化新的变量,该变量不需要在lambda表达式所处的闭包域(enclosing scope)中存在;即使在闭包域中存在也会被新变量覆盖(override)。新变量类型由它的初始化表达式推导。一个用途是可以从闭包域中捕获只供移动的变量并使用它。
C++14还允许lambda函数的形参使用auto
关键字作为其类型,这实质上是函数对象的operator()
成员作为模板函数;并且允许可变参数模板。
例1
#include <bits/stdc++.h>
using namespace std;
long long i = 114514;
class cxk{
private:
int basketball;
public:
cxk();
void music();
};
cxk::cxk() {
basketball = 114514;
}
void cxk::music() {
int rap = 1919;
auto f = [this, rap = 1919810](){cout << this->basketball << rap << endl;};
f();
}
int main() {
cxk nmsl;
nmsl.music();
}
例2
#include <bits/stdc++.h>
using namespace std;
long long i = 114514;
class cxk{
private:
int basketball;
public:
cxk();
void music();
};
cxk::cxk() {
basketball = 114514;
}
void cxk::music() {
int rap = 1919;
auto f = [this](auto rap = 1919810){cout << this->basketball << rap << endl;};
f(rap);
}
int main() {
cxk nmsl;
nmsl.music();
}
可变参数模板
因为本人是菜鸡,没写过可变参数模板,略