0%

lambdaExpression

Lambda表达式使用方法

lambda表达式主要包括如图所示的六个部分,各部分作用如下:

  1. lambda clause,捕获子句,用来表示对表达式外变量的获取形式,[=]表示按值捕获,[&]表示按引用捕获,[]表示啥都没用

  2. parameter list,参数列表,optional,用来声明表达式中使用的参数,

  3. mutable specification,可变声明,optional,对于按值捕获的变量理论上不可修改,如果想要修改需要添加此声明,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // captures_lambda_expression.cpp
    // compile with: /W4 /EHsc
    #include <iostream>
    using namespace std;

    int main()
    {
    int m = 0;
    int n = 0;
    [&, n] (int a) mutable { m = ++n + a; }(4);//修改了n的值,但是由于是按值捕获,所以变化不会反映到外部的变量n上
    cout << m << endl << n << endl; //m = 5 n = 0
    }
  4. exception specification,异常声明,optional,用来声明对于异常的处理,此处可以指定noexcept表示该表达式不抛出异常,注意,如果在lambda体中仍然使用了throw,编译器会警告

  5. trailing-return-value, 返回类型,optional,在只有一个返回值时,lambda表达式会自动推断返回值类型,故无需指定,同样也无需使用auto关键字;然而,当返回语句包含多个时,lambda表达式会将它们的类型推断为void,如下:

    1
    2
    3
    auto x1 = [](int i){ return i; }; // OK: return type is int
    auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
    // return type from braced-init-list isn't valid
  6. lambda body,lambda体,此处是表达式的核心部分,用来指定对数据的操作

Lambda表达式组成

Lambda表达式,函数对象和函数指针的区别

在C++标准库中,函数指针和函数对象的使用非常频繁,但是它们都有各自的局限性

  • 函数指针:语法开销小,但是无法保存上一次执行的状态
  • 函数对象:可以维持状态但是开销大

因此,lambda表达式将两者优点结合并尽可能避免它们的缺点。它可以像函数对象一样,应用灵活且可以保存之前的运行状态,同时它的语法特点使得lambda表达式并不需要显式的类定义,下方是将lambda表达式与STL库结合的一段代码,体现了它相较于函数对象的优势:

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
// even_lambda.cpp
// compile with: cl /EHsc /nologo /W4 /MTd
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main()
{
// Create a vector object that contains 9 elements.
vector<int> v;
for (int i = 1; i < 10; ++i) {
v.push_back(i);
}

// Count the number of even numbers in the vector by
// using the for_each function and a lambda.
int evenCount = 0;
for_each(v.begin(), v.end(), [&evenCount] (int n) {
cout << n;
if (n % 2 == 0) {
cout << " is even " << endl;
++evenCount;
} else {
cout << " is odd " << endl;
}
});

// Print the count of even numbers to the console.
cout << "There are " << evenCount
<< " even numbers in the vector." << endl;
}

但是,如果说之后这个项目还需要进一步改进,建议还是使用函数对象以方便后续维护,lambda表达式,尤其涉及到多层嵌套和高阶lambda函数时,可读性会非常差。

Lambda表达式中需要注意的几个问题

值捕获与引用捕获(capture by value vs. capture by reference)

要点:同一个变量只能以一种形式捕获。对于指针变量,需要区分其指向值的更改(值捕获和引用捕获均会同步改变)和指向地址的更改(只有引用捕获会同步改变)。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// function_lambda_expression.cpp
// compile with: vc19
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

int main()
{
int x = 1;
int* y = &x;
//1.值捕获
auto f1 = [=] {return *y; };
//2.引用捕获
auto f2 = [&] {return *y; };
//3. 指针变量y引用捕获
auto f3 = [=, &y] {return *y; };
//4. 指针变量y值捕获
auto f4 = [&, y] {return *y; };


//5.值捕获
auto f5 = [=] {return x; };
//6.引用捕获
auto f6 = [&] {return x; };
//7. 整型变量x引用捕获
auto f7 = [=, &x] {return x; };
//8. 整型变量x值捕获
auto f8 = [&, x] {return x; };

//更改x y的值
*y = 2;
x = 3;

//y指向值x的更改会全部同步
cout << f1() << endl; //3
cout << f2() << endl; //3
cout << f3() << endl; //3
cout << f4() << endl; //3

//x仅在引用捕获会同步
cout << f5() << endl; //1
cout << f6() << endl; //3
cout << f7() << endl; //3
cout << f8() << endl; //1

//更改y的指向地址
int z = 4;
y = &z;

//y指向地址的更改只会在引用捕获时同步
cout << f1() << endl; //3
cout << f2() << endl; //4
cout << f3() << endl; //3
cout << f4() << endl; //3
return 0;
}

this指针

VS2017后可以对this指针使用值引用,即在捕获子句声明[*this]使用类中的其它变量,在lambda表达式异步或者并行执行时,这样的效率更高(未验证),同时也支持[this]或者[]隐式调用this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// capture "this" by reference
void ApplyScale1(const vector<int>& v) const
{
for_each(v.begin(), v.end(),
[this](int n) { cout << n * _scale << endl; });
}

// capture "this" by value (Visual Studio 2017 version 15.3 and later)
void ApplyScale2(const vector<int>& v) const
{
for_each(v.begin(), v.end(),
[*this](int n) { cout << n * _scale << endl; });
}

void ApplyScale3(const vector<int>& v) const
{
for_each(v.begin(), v.end(),
[=](int n) { cout << n * _scale << endl; });
}

lambda表达式嵌套和高阶lambda函数

lambda表达式支持在一个lambda表达式中定义另一个lambda表达式并传递参数,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// nesting_lambda_expressions.cpp
// compile with: /EHsc /W4
#include <iostream>

int main()
{
using namespace std;

// The following lambda expression contains a nested lambda
// expression.
int timestwoplusthree = [](int x) { return [](int y) { return y * 2; }(x) + 3; }(5);

// Print the result.
cout << timestwoplusthree << endl;
}

同样,lambda表达式也支持使用另一个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
// higher_order_lambda_expression.cpp
// compile with: /EHsc /W4
#include <iostream>
#include <functional>

int main()
{
using namespace std;

// The following code declares a lambda expression that returns
// another lambda expression that adds two numbers.
// The returned lambda expression captures parameter x by value.
auto addtwointegers = [](int x) -> function<int(int)> {
return [=](int y) { return x + y; };
};

// The following code declares a lambda expression that takes another
// lambda expression as its argument.
// The lambda expression applies the argument z to the function f
// and multiplies by 2.
auto higherorder = [](const function<int(int)>& f, int z) {
return f(z) * 2;
};

// Call the lambda expression that is bound to higherorder.
auto answer = higherorder(addtwointegers(7), 8);

// Print the result, which is (7+8)*2.
cout << answer << endl;
}