lambda函数
lambda函数是C++11中新增的非常有用的特性,它允许你在需要函数对象的地方,如for_each
、sort
等算法,直接定义一个匿名函数。Lambda函数简洁、高效,也为编写现代C++代码提供了更多的便利。
示例:
[](const int & a)-> void{ cout<< a << endl; };
基本语法
[capture list](parameters) multable noexcept -> return type {statement}
- capture list 是捕获列表。
- parameters 是参数列表。
- mutable 是一个可选的说明符,允许你修改通过值捕获的变量。
- throw-spec 是异常说明。(这里一般使用
noexcept
) - return-type 是返回类型。
- statement 是lambda函数体。
示例:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 普通函数
void print(int x) {
cout << x << " ";
}
// 仿函数 (functor)
struct Print {
void operator()(int x) const {
cout << x << " ";
}
};
int main() {
vector<int> vec = {1, 2, 3, 4, 5};
// 使用普通函数
cout << "Using normal function: ";
for_each(vec.begin(), vec.end(), print);
cout << endl;
// 使用仿函数
cout << "Using functor: ";
for_each(vec.begin(), vec.end(), Print());
cout << endl;
// 使用lambda函数
cout << "Using lambda: ";
for_each(vec.begin(), vec.end(), [](int x) {
cout << x << " ";
});
cout << endl;
return 0;
}
参数列表
Lambda函数的参数列表与普通函数或方法的参数列表非常相似。参数列表包围在()
内。但与普通函数不同的是:
-
没有默认参数:您不能为lambda函数的参数提供默认值。
// 错误!lambda函数不支持默认参数 auto lambda = [](int x = 5) { cout << x; };
-
所有参数必须命名:每个参数都需要一个名字,即使您不打算在函数体中使用它。
-
不支持可变参数:不能使用像
...
这样的可变参数来定义lambda函数。
返回类型
Lambda函数通常会根据函数体内的代码推断其返回类型。例如,如果函数体只包含一个返回语句,那么该返回语句的类型就是lambda函数的返回类型。但在某些情况下,您可能想要显式地指定返回类型,尤其是当函数体有多条返回语句,且它们的返回类型不同的时候。显式指定返回类型使用->
符号。
auto lambda = [](int x) -> double { return 2.0 * x; };
在上面的例子中,尽管2.0 * x
的结果可以是int
或double
,但由于我们显式地指定了返回类型为double
,所以lambda函数的返回类型是double
。
函数体
Lambda函数的函数体与普通函数的函数体非常相似。它包围在{}
内,并可以包含任意数量的语句。
捕获列表
通过捕获列表,lambda函数可以访问父作用域中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。
捕获列表书写在[]
中,与函数参数的传递类似,捕获方式可以是值或引用。
下面是不同捕获列表的方式。
1.值传递
与传递参数类似,采用值捕获的前提是变量可以拷贝。
与传递参数不同,变量的值是在lambda函数创建时拷贝,而不是调用时拷贝。
由于被捕获的值是在lambda函数创建时拷贝,因此在随后对其修改不会影响到lambda内部的值。
默认情况下,如果以传值方式捕获变量,则在lambda函数中不能修改变量的值。
2.引用捕获
和函数引用参数一样,引用变量的值在lambda函数体中改变时,将影响被引用的对象。
如果采用引用方式捕获变量,就必须保证被引用的对象在lambda执行的时候是存在的。
3. 隐式捕获
除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。
隐式捕获有两种方式,分别是[=]
和[&]
。[=]
表示以值捕获的方式捕获外部变量,[&]
表示以引用捕获的方式捕获外部变量。
4.混合方式捕获
lambda函数还支持混合方式捕获,即同时使用显式捕获和隐式捕获。
混合捕获时,捕获列表中的第一个元素必须是 = 或 &,此符号指定了默认捕获的方式是值捕获或引用捕获。
需要注意的是:显式捕获的变量必须使用和默认捕获不同的方式捕获。例如:
int i = 10;
int j = 20;
auto f1 = [ =, &i] () { return j + i; }; // 正确,默认值捕获,显式是引用捕获
auto f2 = [ =, i] () { return i + j; }; // 编译出错,默认值捕获,显式值捕获,冲突了
auto f3 = [ &, &i] () { return i +j; }; // 编译出错,默认引用捕获,显式引用捕获,冲突了
5.修改值捕获变量的值
在lambda函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。
在lambda函数中,如果希望修改值捕获变量的值,可以加mutable选项,但是,在lambda函数的外部,变量的值不会被修改。
int a = 123;
auto f = [a]()mutable { cout << ++a << endl; }; // 不会报错
cout << a << endl; // 输出:123
f(); // 输出:124
cout << a << endl; // 输出:123
6.异常说明
lambda可以抛出异常,用throw(…)指示异常的类型,用noexcept指示不抛出任何异常。
示例:
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 20, c = 30;
// 1. 值捕获
auto value_capture = [a]() {
cout << "Value capture: " << a << endl;
};
a = 100;
value_capture(); // 输出: Value capture: 10
// 2. 引用捕获
auto reference_capture = [&b]() {
b++;
cout << "Reference capture: " << b << endl;
};
reference_capture(); // 输出: Reference capture: 21
cout << "Original b: " << b << endl; // 输出: Original b: 21
// 3. 隐式捕获 (全部以值捕获)
auto implicit_value_capture = [=]() {
cout << "Implicit value capture: " << a << " " << b << endl;
};
implicit_value_capture(); // 输出: Implicit value capture: 100 21
// 4. 隐式捕获 (全部以引用捕获)
auto implicit_reference_capture = [&]() {
a++;
cout << "Implicit reference capture: " << a << " " << b << endl;
};
implicit_reference_capture(); // 输出: Implicit reference capture: 101 21
// 5. 混合方式捕获
auto mixed_capture = [&, c]() { // 默认引用捕获,但c是值捕获
a++;
cout << "Mixed capture: " << a << " " << c << endl;
};
mixed_capture(); // 输出: Mixed capture: 102 30
// 6. 修改值捕获变量
auto modify_value_capture = [c]() mutable {
c++;
cout << "Modify value capture: " << c << endl;
};
modify_value_capture(); // 输出: Modify value capture: 31
cout << "Original c: " << c << endl; // 输出: Original c: 30
}
lambda函数的本质
当我们编写了一个lambda函数之后,编译器将它翻译成一个类,该类中有一个重载了()的函数。捕获的外部变量会被存储为类的成员,并在这个类的构造函数中进行初始化。
-
采用值捕获
考虑以下代码:
int a =10; int b = 20; auto addfun = [=] (const int c ) -> int { return a+c; }; int c = addfun(b); cout << c << endl;
等同于:
class Myclass { int m_a; // 该成员变量对应通过值捕获的变量。 public: Myclass( int a ) : m_a(a){}; // 该形参对应捕获的变量。 // 重载了()运算符的函数,返回类型、形参和函数体都与lambda函数一致。 int operator()(const int c) const { return a + c; } };
默认情况下,由lambda函数生成的类是const成员函数,所以变量的值不能修改。如果加上mutable,相当于去掉const。这样上面的限制就能讲通了。
-
采用引用捕获
考虑以下的lambda函数:
int a = 10; auto printFun = [&a]() { cout << a << endl; };
这个lambda函数可以被转化为以下的类形式:
class PrintClass { int& m_a; // 该成员变量是一个引用,对应通过引用捕获的变量。 public: // 构造函数中的引用初始化 PrintClass(int& a) : m_a(a) {} // 重载()运算符 void operator()() const { cout << m_a << endl; } };
你可以通过以下方式使用这个类:
int a = 10; PrintClass printObj(a); printObj();