1. lambdab表达式组成部分
Lambda表达式的格式为:
1
2
|
[capture list](parameters) -> return type { };
// 捕获列表 ,函数参数, 返回值, 函数体
|
例如:
1
2
3
4
5
6
7
|
#include <iostream>
int main()
{
auto f = [](int a) -> int { return a > 6 && a < 9 ; };
std::cout << f(7) << std::endl;
}
|
- [capture list]: 参数捕获。
- (parameters): 形参。
- -> return type: 返回类型,能够自动推导时,可以省略。
- {}: 函数体,和普通函数一样。
2. 参数捕获
一个Lambda处于一个作用域内部,可以捕获该作用域中的局部变量,而对于静态变量,全局变量直接使用即可。对于局部变量的捕获可以分为以下集中情况:
| 捕获类型 |
– |
| 值捕获 |
[x]: 捕获指定变量 [=]: 捕获所有变量 |
| 引用捕获 |
[&x](显式捕获单个变量的引用) [&](隐式捕获所有变量的引用) |
| 混合捕获 |
[=, &x]:按值捕获所有变量,但x为引用 [&, =x]:按引用捕获所有变量,但x为值 [x, &y]: x使用值捕获,y使用引用捕获 |
| 捕获 this 指针 |
[this] 或 [*this] 在类成员函数中,捕获当前对象的 this 指针([this])或整个对象的副本([*this]) 用于访问类的成员变量和成员函数 |
| 其他 |
… |
3. lambda被编译器翻译成类
上面的代码被翻译成类,并重载函数调用符(),也就是仿函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#include <iostream>
int main()
{
class __lambda_5_12
{
public:
inline /*constexpr */ int operator()(int a) const
{
return static_cast<int>((a > 6) && (a < 9));
}
using retType_5_12 = auto (*)(int) -> int;
inline constexpr operator retType_5_12 () const noexcept
{
return __invoke;
};
private:
static inline /*constexpr */ int __invoke(int a)
{
return __lambda_5_12{}.operator()(a);
}
public:
// /*constexpr */ __lambda_5_12() = default;
};
__lambda_5_12 f = __lambda_5_12{};
std::cout.operator<<(f.operator()(7)).operator<<(std::endl);
return 0;
}
|
从上面代码中可以看出,lambda表达式,会被翻译成一个类,捕获的变量用于在类的构造函数中初始化成员变量。并且该类还重载了函数调用运算符“()”,重载函数的函数体就是lambda的函数体。
4. 参数捕获详解
无论值捕获还是引用捕获,当使用全部捕获时,编译器生成的类只会捕获使用到的变量,这个比较智能。
4.1. 值捕获
编译器在创建类的时候,会定义相应的类成员变量,然后在构造lambda对象时,使用捕获的变量初始化类成员变量。
1
2
3
4
5
6
7
8
9
|
#include <iostream>
int main()
{
int x = 10;
auto f = [x](int a) -> int { return a > 6 && a < 9 ; };
std::cout << f(7) << std::endl;
}
|
上面代码翻译成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#include <iostream>
int main()
{
int x = 10;
class __lambda_7_12
{
public:
inline /*constexpr */ int operator()(int a) const
{
return static_cast<int>((a > 6) && (a < 9));
}
private:
int x;
public:
// 构造函数
__lambda_7_12(int & _x): x{_x}
{}
};
// 调用构造函数时,会将x的值复制给类的成员变量x中。
__lambda_7_12 f = __lambda_7_12{x};
std::cout.operator<<(f.operator()(7)).operator<<(std::endl);
return 0;
}
|
4.2. 引用捕获
如果使用引用捕获,类中会定义对应的引用类型的成员变量,这种情况下,lambda对于自身成员变量的操作会影响对应的外部变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include <iostream>
int main()
{
int x = 10;
auto f = [&x](int a)
{
++x;
return a > x ;
};
std::cout << f(7) << std::endl;
}
|
翻译后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#include <iostream>
int main()
{
int x = 10;
class __lambda_7_12
{
public:
inline /*constexpr */ bool operator()(int a) const
{
++x;
return a > x;
}
private:
int & x; // 引用捕获,就会定义对应的引用类型的成员变量
public:
__lambda_7_12(int & _x): x{_x}
{}
};
__lambda_7_12 f = __lambda_7_12{x};
std::cout.operator<<(f.operator()(7)).operator<<(std::endl);
return 0;
}
|
4.3. this捕获与*this捕获
请看下面案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class str {
auto fun()
{
int a = 10;
auto f = [a, x] ()
{
return a > x; // 这样捕获是错的。
};
};
int x;
};
|
上面的案例中,虽然说fun和x都是类成员,但是fun中的lambda是不能直接捕获x的,fun中的局部变量倒是可以捕获。正确做法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class str {
auto fun()
{
int a = 10;
auto f = [a, this] ()
{
return a > x;
};
};
int x;
};
|
翻译后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class str
{
inline void fun()
{
int a = 10;
class __lambda_6_16
{
public:
inline /*constexpr */ bool operator()() const
{
return a > __this->x; // 通过 Str* 类型的成员变量访问对象的成员
}
private:
int a;
str * __this; // 一个Str* 类型的成员变量
public:
__lambda_6_16(str * _this, int & _a)
: __this{_this}
, a{_a}
{}
};
__lambda_6_16 f = __lambda_6_16{this, a};
}
int x;
};
|
但是,要注意this指向的对象的生命周期,这个很危险,Lambda表示式很多时候用来创建一个可调用对象,但是并不是马上就要调用,所以要确保调用的时候参数都是有效的,所以C++17中提供了*this捕获。
4.4. 初始化捕获
Demo1:
1
2
3
4
5
6
|
int main()
{
int x = 10;
auto f = [y = x] (int a) { return a > y; }; // 类似于值捕获
std::cout << f(11) << std::endl;
}
|
Demo2:
1
2
3
4
5
6
7
8
9
10
11
12
|
int main()
{
std::string str = "Are You OK!";
auto f = [ss = std::move(str)] ()
{
std::cout << ss << std::endl;
};
std::cout << str << std::endl; // str为空
f(); // 打印 Are You OK!
}
|
Demo3:
1
2
3
4
5
6
7
8
9
10
|
int main()
{
int x = 10;
int y = 10;
auto f = [z = y + x] (int a)
{
return a > z; // 其实也是值捕获
};
}
|
5. 修饰符
5.1. mutalbe
1
2
3
4
5
6
7
8
9
|
##include <iostream>
int main()
{
int x = 10;
auto f = [x](int a) { return a > 6 && a < 9 ; };
std::cout << f(7) << std::endl;
}
|
翻译后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
##include <iostream>
int main()
{
int x = 10;
class __lambda_7_12
{
public:
inline /*constexpr */ int operator()(int a) const // 这个const是修饰this的
{
return static_cast<int>((a > 6) && (a < 9));
}
private:
int x;
public:
__lambda_7_12(int & _x)
: x{_x}
{}
};
__lambda_7_12 f = __lambda_7_12{x};
std::cout.operator<<(f.operator()(7)).operator<<(std::endl);
return 0;
}
|
上面的代码中,重载函数的this是const的,所以重载函数中不能修改成员变量,使用mutalbe修饰符。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
##include <iostream>
int main()
{
int x = 10;
auto f = [x](int a) mutable
{
return a > 6 && a < 9 ;
};
std::cout << f(7) << std::endl;
}
|
翻译后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
##include <iostream>
int main()
{
int x = 10;
class __lambda_7_12
{
public:
inline /*constexpr */ bool operator()(int a) // 没有const了
{
return (a > 6) && (a < 9);
}
private:
int x;
public:
__lambda_7_12(int & _x)
: x{_x}
{}
};
__lambda_7_12 f = __lambda_7_12{x};
std::cout.operator<<(f.operator()(7)).operator<<(std::endl);
return 0;
}
|
5.2. constexpr和conxteval
- constexpr: 声明Lambda可以在编译期执行。
- conxteval: 声明表达式不会抛出异常, 这个特性在《 C++异常 》篇章讲解。
5.3. 形参修饰
5.3.1. C++20 模板形参
C++20引入,没用过。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
##include <iostream>
int main()
{
int x = 10;
auto f = [x]<typename T>(T a) // 模板形参
{
return a > 6 && a < 9 ;
};
std::cout << f(7) << std::endl;
}
|
翻译后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
##include <iostream>
int main()
{
int x = 10;
class __lambda_7_12
{
public:
template<typename T> // 函数模板
inline /*constexpr */ auto operator()(T a) const
{
return (a > 6) && (a < 9);
}
##ifdef INSIGHTS_USE_TEMPLATE
template<>
inline /*constexpr */ bool operator()<int>(int a) const
{
return (a > 6) && (a < 9);
}
##endif
private:
int x;
public:
__lambda_7_12(int & _x)
: x{_x}
{}
};
__lambda_7_12 f = __lambda_7_12{x};
std::cout.operator<<(f.operator()(7)).operator<<(std::endl);
return 0;
}
|
5.3.2. const auto&
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
##include <map>
##inclide <iostream>
##include <functional>
int main()
{
std::map<int, int> m{6, 8}
auto lam = [](const auto& x) // 等效于: const std::pair<const int, int>& x
{
return p.first + p.second;
}
std::cout << lam(*m.begin()) << std::endl;
}
|
我们看翻译后的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
##include <map>
##include <iostream>
##include <functional>
int main()
{
std::map<int, int, std::less<int>, std::allocator<std::pair<const int, int> > > m = std::map<int, int, std::less<int>, std::allocator<std::pair<const int, int> > >{std::initializer_list<std::pair<const int, int> >{std::pair<const int, int>{6, 8}}, std::less<int>(), std::allocator<std::pair<const int, int> >()};
class __lambda_8_15
{
public:
template<class type_parameter_0_0>
inline /*constexpr */ auto operator()(const type_parameter_0_0 & x) const
{
return x.first + x.second;
}
##ifdef INSIGHTS_USE_TEMPLATE
// 重点
template<> // 此处形参的形参为 const std::pair<const int, int>& 是由const auto& 推导出来的
inline /*constexpr */ int operator()<std::pair<const int, int> >(const std::pair<const int, int> & x) const
{
return x.first + x.second;
}
##endif
private:
template<class type_parameter_0_0>
static inline /*constexpr */ auto __invoke(const type_parameter_0_0 & x)
{
return __lambda_8_15{}.operator()<type_parameter_0_0>(x);
}
};
__lambda_8_15 lam = __lambda_8_15{};
std::cout.operator<<(lam.operator()(m.begin().operator*())).operator<<(std::endl);
return 0;
}
|
##- 1. lambdab表达式组成部分
- 涉及到函数重载时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
auto fun(int x)
{
return ++x;
}
auto fun(int x)
{
return ++x;
}
int main()
{
auto lam = [](auto x)
{
return fun(x);
};
}
|
根据参数类型来选择对应的函数,内部实现为模板函数。
7. Lambda递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
##include <iostream>
int main()
{
// 使用了两层Lambda
auto factorial = [](int n)
{
// 递归Lambda, 在执行前就定义好
auto fact = [](int n, const auto& f) -> int
{
return n > 1 ? n * f(n - 1, f) : 1;
};
// 开始递归,此时fact的类型已经定义
return fact(n, fact);
};
std::cout << factorial(5) << std::endl;
}
|