C++中Lambda表达式的介绍与使用场景

C++中Lambda表达式的介绍与使用场景

技术背景

在C++中,像std::for_eachstd::transform这类通用函数十分实用,但在使用时若要应用的仿函数是特定函数独有的,使用起来会比较繁琐。在C++03中,若想将仿函数局部化,编写类似局部结构体仿函数的代码是不被允许的,因为该仿函数不能传递给模板函数。

C++11引入了Lambda表达式,它是一种内联的匿名仿函数,能简洁地替代结构体仿函数,让代码更易读和维护。

实现步骤

基本Lambda表达式

最简单的Lambda表达式形式如下:

1
[]{} // lambda with no parameters that does nothing 

其中,[]是捕获列表,{}是函数体。

指定返回类型

在简单情况下,Lambda的返回类型可由编译器推导得出,例如:

1
2
3
4
5
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}

但在复杂情况下,编译器无法推导返回类型,此时需显式指定,使用-> T

1
2
3
4
5
6
7
8
9
10
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) -> double {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}

捕获变量

Lambda表达式可使用捕获列表捕获外部变量,有按值捕获和按引用捕获两种方式:

1
2
3
4
5
6
7
8
9
10
void func5(std::vector<double>& v, const double& epsilon) {
std::transform(v.begin(), v.end(), v.begin(),
[epsilon](double d) -> double {
if (d < epsilon) {
return 0;
} else {
return d;
}
});
}

捕获列表的常见形式有:

  • [&epsilon, zeta]:按引用捕获epsilon,按值捕获zeta
  • [&]:按引用捕获Lambda中使用的所有变量。
  • [=]:按值捕获Lambda中使用的所有变量。
  • [&, epsilon]:按引用捕获Lambda中使用的所有变量,但按值捕获epsilon
  • [=, &epsilon]:按值捕获Lambda中使用的所有变量,但按引用捕获epsilon

C++14的初始化捕获

C++14允许在捕获列表中使用=初始化元素,例如:

1
2
3
4
5
int x = 4;
auto y = [&r = x, x = x+1]()->int {
r += 2;
return x+2;
}(); // Updates ::x to 6, and initializes y to 7.

C++20的模板参数

C++20起,Lambda表达式可拥有模板参数列表:

1
[]<int N>() {};

通用Lambda(C++14)

带有auto参数的Lambda是通用Lambda:

1
[](auto x, auto y) { return x + y; }

核心代码

示例代码1:基本使用

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <algorithm>
#include <vector>
#include <iostream>

void printVector(std::vector<int> v)
{
// lambda expression to print vector
std::for_each(v.begin(), v.end(), [](int i)
{
std::cout << i << " ";
});
std::cout << std::endl;
}

int main()
{
std::vector<int> v {4, 1, 3, 5, 2, 3, 1, 7};

printVector(v);

// below snippet find first number greater than 4
// find_if searches for an element for which
// function(third argument) returns true
std::vector<int>:: iterator p = std::find_if(v.begin(), v.end(), [](int i)
{
return i > 4;
});
std::cout << "First number greater than 4 is : " << *p << std::endl;

// function to sort vector, lambda expression is for sorting in
// non-decreasing order Compiler can make out return type as
// bool, but shown here just for explanation
std::sort(v.begin(), v.end(), [](const int& a, const int& b) -> bool
{
return a > b;
});

printVector(v);

// function to count numbers greater than or equal to 5
int count_5 = std::count_if(v.begin(), v.end(), [](int a)
{
return (a >= 5);
});
std::cout << "The number of elements greater than or equal to 5 is : "
<< count_5 << std::endl;

// function for removing duplicate element (after sorting all
// duplicate comes together)
p = std::unique(v.begin(), v.end(), [](int a, int b)
{
return a == b;
});

// resizing vector to make size equal to total different number
v.resize(std::distance(v.begin(), p));
printVector(v);

// accumulate function accumulate the container on the basis of
// function provided as third argument
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int f = std::accumulate(arr, arr + 10, 1, [](int i, int j)
{
return i * j;
});

std::cout << "Factorial of 10 is : " << f << std::endl;

// We can also access function by storing this into variable
auto square = [](int i)
{
return i * i;
};

std::cout << "Square of 5 is : " << square(5) << std::endl;
return 0;
}

示例代码2:捕获变量

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
#include <bits/stdc++.h>
#include <iostream>
#include <vector>

int main()
{
std::vector<int> v1 = {3, 1, 7, 9};
std::vector<int> v2 = {10, 2, 7, 16, 9};

// access v1 and v2 by reference
auto pushinto = [&] (int m)
{
v1.push_back(m);
v2.push_back(m);
};

// it pushes 20 in both v1 and v2
pushinto(20);

// access v1 by copy
[v1]()
{
for (auto p = v1.begin(); p != v1.end(); p++)
{
std::cout << *p << " ";
}
};

int N = 5;

// below snippet find first number greater than N
// [N] denotes, can access only N by value
std::vector<int>:: iterator p = std::find_if(v1.begin(), v1.end(), [N](int i)
{
return i > N;
});

std::cout << "First number greater than 5 is : " << *p << std::endl;

// function to count numbers greater than or equal to N
// [=] denotes, can access all variable
int count_N = std::count_if(v1.begin(), v1.end(), [=](int a)
{
return (a >= N);
});

std::cout << "The number of elements greater than or equal to 5 is : "
<< count_N << std::endl;
return 0;
}

最佳实践

  • 封装算法:将算法封装在Lambda表达式中,方便传递给其他函数。例如,在使用标准库算法时,可直接使用Lambda作为操作函数。
  • 代码重构:对于复杂函数中的一段代码,可将其封装为Lambda,逐步进行参数化,最终可将其提取为普通函数。
  • 变量初始化:基于算法结果初始化变量,使代码更简洁。

常见问题

  • 返回类型推导问题:当Lambda表达式包含多个返回语句时,编译器可能无法推导返回类型,需显式指定。
  • 捕获变量问题:Lambda表达式只能捕获局部变量,不能捕获对象的成员变量。
  • 与函数指针的转换问题:只有无捕获的Lambda表达式才能隐式转换为函数指针。若需要使用捕获变量的Lambda作为回调函数,可使用std::function

C++中Lambda表达式的介绍与使用场景
https://119291.xyz/posts/introduction-and-use-cases-of-cpp-lambda-expressions/
作者
ww
发布于
2025年5月26日
许可协议