C++ 11中引入了新的lamdba表达式,使用也很简单,我最喜欢的是不用给函数取名称,每次给函数取名称都感觉自己读书太少~
lambda表达式可以理解为一个匿名的内联函数。和函数一样,lambda表达式具有一个返回类型、一个参数列表和一个函数体。与函数不一样的是lambda必须使用尾置返回类型。一个lambda表达式表示一个可调用的代码单元。
语法:[capture list] (parameter list) -> return type {function body}
capture list:表示捕获列表,是一个lambda所在函数中定义的局部变量列表
parameter list:表示参数列表
return type:返回类型
function body:函数体
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体,忽略参数列表等价于指定一个空函数列表,忽略返回类型,lambda会根据函数体中的代码推断出来(如果函数体直接return,则是void类型)。例如:
auto f = [ ] {return 42;};
cout << f() << endl;
lambda的调用方式与普通函数的调用方式相同。
与函数的几点不同在于:
如果没有进行捕获,则lambda表达式函数体内只能使用参数列表中的变量。捕获就是明确的指明lambda能使用的局部变量(指调用lambda的地方局部变量)。
这里记住两点就行了:
例如:
void func()
{
static int i = 10;
int j = 20;
auto f1 = [ ] () { return j; }; //编译出错,没有进行捕获,函数体内不能使用
auto f2 = [ ] () { return i; }; //静态变量无需捕获
auto f3 = [j] () { return j; }; //进行了捕获,函数体内可以使用了
};
与函数参数传递类似,变量的捕获方式也可以是值或者引用。这里列出不同的捕获列表的方式,后面进行解释:
与传递参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,不捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。例如:
void func()
{
size_t v1 = 42;
auto f = [ v1 ] { return v1; }; //使用了值捕获,将v1拷贝到名为f的可调用对象
v1 = 0;
auto j = f(); //j为42,f保存了我们创建它是v1的拷贝
}
由于被捕获的值实在lambda创建时拷贝,因此在随后对其修改不会影响到lambda内部对应的值。
默认情况下:如果以传值方式捕获外部变量,则在Lambda表达式函数体中不能修改该外部变量的值。
和函数引用参数一样,一个引用类型的变量在函数体内改变时,实际上使用的是引用所绑定的对象。
void func()
{
size_t v1 = 42;
auto f = [ &v1 ] { return v1; }; //引用捕获,将v1拷贝到名为f的可调用对象
v1 = 0;
auto j = f(); //j为42,f保存了我们创建它是v1的拷贝
}
如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。lambda捕获的都是局部变量,这些变量在函数结束后就不复存在了。如果lambda可能在函数结束后执行,这里就会出现问题。
有一些不可拷贝对象,只能使用引用捕获的方式,比如ostream对象。
除了显示列出我们希望使用的来自所在函数的局部变量之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。
隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量
int main()
{
int a = 123;
auto f = [ = ] { cout << a << endl; }; //值捕获
f(); // 输出:123
auto f1 = [ & ] { cout << a++ << endl; }; //引用捕获
f1(); //输出:123(采用了后++)
cout << a << endl; //输出 124
}
lambda还支持混合方式捕获,即同时使用显示捕获和隐式捕获。
混合捕获时,捕获列表中的第一个元素必须是 = 或 &,此符号指定了默认捕获的方式是值捕获或引用捕获 。
需要注意的是:显示捕获的变量必须使用和默认捕获不同的方式捕获。例如:
void func()
{
int i = 10;
int j = 20;
auto f1 = [ =, &i] () { return j + i; }; //正确,默认值捕获,显示是引用捕获
auto f2 = [ =, i] () { return i + j; }; //编译出错,默认值捕获,显示值捕获,冲突了
auto f3 = [ &, &i] () { return i +j; }; //编译出错,默认引用捕获,显示引用捕获,冲突了
};
在Lambda表达式中,如果以传值方式捕获外部变量,则函数体中不能修改该外部变量,否则会引发编译错误。
如果你希望被值捕获的值被改变,就必须在参数列表首加上关键字mutable。
语法变为:[capture list] (parameter list) mutable -> return type {function body}
int main()
{
int a = 123;
auto f = [a]()mutable { cout << ++a; }; // 不会报错
cout << a << endl; // 输出:123
f(); // 输出:124
}
在默认的规则下,返回类型如下:
所以,有返回类型时,一定要自己显示的进行说明。
当我们编写了一个lambda之后,编译器将该表达式翻译成一个未命名类的未命名对象。该类含有一个重载的函数调用运算符。
在采用值捕获时,lambda形成的类相当于含有自己的数据成员,同时创建构造函数,令其使用捕获的变量的值来初始化数据成员。例如两个数加法的方法:
int func()
{
int a =10;
int b = 20;
auto addfun = [=] (const int c ) -> int { return a+c; };
int c = addfun(b);
cout << c << endl;
};
就等同于:
class Myclass
{
public:
Myclass( int a ) : m_a(a){}; //该形参对应捕获的变量
//该调用运算符的返回类型、形参和函数体都与lambda一致
int operator()(const int c) const
{
return a + c;
}
private:
int m_a; //该数据对应通过值捕获的变量
};
lambda表达式产生的类不含有默认构造函数、赋值运算符及默认析构函数。因为不含默认构造函数,因此要想使用这个类必须提供一个实参。
默认情况下,由lambda产生类当中的调用运算符是一个const成员函数,所以值捕获的值不能修改。如果加上mutable相当于去掉const。这样上面的很多限制就能讲通了。
如果lambda采用引用捕获的方式,编译器可以直接使用该引用而无须在lambda对象产生的类中将其存储为数据成员。
唯一需要注意的是,变量将由程序负责确保执行时引用的对象确实存在。
感谢大家,我是假装很努力的YoungYangD(小羊)。
参考资料:
《C++ primer 第五版》
https://www.cnblogs.com/lustar/p/7531605.html