【C++ Primer】查缺补漏(三)lambda表达式
前言
本文是笔者回顾复习c++ primer时候对遗忘点的巩固笔记,非详细学习笔记
一、介绍lambda
对于一个对象或表达式,如果可以对其使用调用运算符()
,则称它为可调用对象(callable object)。可以向算法传递任何类别的可调用对象。
一个lambda表达式表示一个可调用的代码单元,类似未命名的内联函数,但可以定义在函数内部。其形式如下:
[capture list] (parameter list) -> return type { function body }
其中,capture list(捕获列表)是一个由lambda所在函数定义的局部变量的列表(通常为空)。return type、parameter list和function body与普通函数一样,分别表示返回类型、参数列表和函数体。但与普通函数不同,lambda必须使用尾置返回类型,且不能有默认实参。
定义lambda时可以省略参数列表和返回类型,但必须包含捕获列表和函数体。省略参数列表等价于指定空参数列表。省略返回类型时,若函数体只是一个return语句,则返回类型由返回表达式的类型推断而来。否则返回类型为void。
auto f = [] { return 42; };
cout << f() << endl; // print 42
lambda可以使用其所在函数的局部变量,但必须先将其包含在捕获列表中。捕获列表只能用于局部非static变量,lambda可以直接使用局部static变量和其所在函数之外声明的名字。
两个例子:
find_if
查找第一个长度大于等于sz的元素。// get an iterator to the first element whose size() is >= sz auto wc = find_if(words.begin(), words.end(), [sz](const string &a) { return a.size() >= sz; });
for_each
函数接受一个输入序列和一个可调用对象,它对输入序列中的每个元素调用此对象。// print words of the given size or longer, each one followed by a space for_each(wc, words.end(), [] (const string &s) { cout << s << " "; });
二、lambda捕获和返回
-
定义
lambda
时会生成一个新的类类型和该类型的一个对象。 -
默认情况下,从
lambda
生成的类都包含一个对应该lambda
所捕获的变量的数据成员,在lambda
对象创建时被初始化(而不是调用时)。 -
值捕获:前提是变量可以拷贝
size_t v1 = 42; // local variable
// copies v1 into the callable object named f
auto f = [v1] { return v1; };
v1 = 0;
auto j = f(); // j is 42; f stored a copy of v1 when we created it
- 引用捕获:必须保证在
lambda
执行时,变量是存在的
size_t v1 = 42; // local variable
// the object f2 contains a reference to v1
auto f2 = [&v1] { return v1; };
v1 = 0;
auto j = f2(); // j is 0; f2 refers to v1; it doesn't store it
- 尽量减少捕获的数据量,尽可能避免捕获指针或引用。
- 隐式捕获:让编译器推断捕获列表,在捕获列表中写一个
&
(引用方式)或=
(值方式)。
// os implicitly captured by reference; c explicitly captured by value
for_each(words.begin(), words.end(),
[&, c] (const string &s) { os << s << c; });
// os explicitly captured by reference; c implicitly captured by value
for_each(words.begin(), words.end(),
[=, &os] (const string &s) { os << s << c; });
这里贴两张侯捷老师c++11课程的图,便于理解:
lambda捕获列表:
捕获列表 | 解释 |
---|---|
[] | 空捕获列表。lambda 不能使用所在函数中的变量。一个lambda 只有在捕获变量后才能使用它们。 |
[names] | names 是一个逗号分隔的名字列表,这些名字都是在lambda 所在函数的局部变量,捕获列表中的变量都被拷贝,名字前如果使用了& ,则采用引用捕获方式。 |
[&] | 隐式捕获列表,采用引用捕获方式。lambda 体中所使用的来自所在函数的实体都采用引用方式使用。 |
[=] | 隐式捕获列表,采用值捕获方式。 |
[&, identifier_list] | identifier_list 是一个逗号分隔的列表,包含0个或多个来自所在函数的变量。这些变量采用值捕获方式,而任何隐式捕获的变量都采用引用方式捕获。identifier_list 中的名字前面不能使用& |
[=, identifier_list] | identifier_list 中的变量采用引用方式捕获,而任何隐式捕获的变量都采用值方式捕获。identifier_list 中的名字不能包括this ,且前面必须使用& |
可变lambda
对于一个值被拷贝的变量,lambda不会改变其值(即默认是const的)。如果希望改变一个被捕获变量的值,就必须在参数列表尾加上关键字mutable
,此时不能省略参数列表,例如:
void fcn3()
{
size_t v1 = 42;// 局部变量
// f可以改变所捕获变量的值
auto f = [v1] () mutable {return ++v1;}
v1 = 0;
auto j = f();
// 此时 j=43, v1=0
}
对于一个引用捕获的变量,是否可以修改取决于该引用指向的类型是否是const。
void fcn4()
{
size_t v1 = 42;// 局部变量
// v1是一个非const变量的引用
// 可以通过f2中的引用来改变它
auto f2 = [&v1] () {return ++v1;}
v1 = 0;
auto j = f2();
// 此时 j=1
}
指定lambda返回类型
// 将vi中每个数替换为其绝对值
transform(vi.begin(), vi.end(), vi.begin(),
[](int i) -> int
{if (i<0) return -i; else return i;}
三、lambda没有默认的构造函数
- link(侯捷老师yyds)
- 这张图看着有点复杂,其实就是使用关联式容器的时候,我们可以自定义排序方法,此时可以使用 Lambda 表达式,同时使用 decltype 关键字可以自动推导出 Lambda 表达式的类型
- 但是需要注意的是,同时需要将 Lambda 表达式作为参数传给 set 容器,否则 set 会自动调用模版参数中传入的函数对象的默认构造函数作为排序的准则,但是对于 Lambda 表达式来说没有默认的构造函数,结果会报错
- 这里相当于是如果没将 cmp 传给 coll,coll 相当于只获得以 cmp 的类型,但是没有获得具体的 cmp,就会调用对于 decltype(cmp) 类型的默认构造函数,但是 Lambda 表达式并没有默认构造函数