C++ lambda函数用法

Lambda表达式(也叫lambda函数,或简称lambda),是从C++ 11开始引入并不断完善的,是能够捕获作用域中变量的匿名函数对象。因为C++是不能嵌套定义函数的,所以lambda就成了我们构造闭包的主要手段,不过在对象的生命周期上还是有点不同。

Lambda基本语法

lambda基本语法

当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。下面对重要的组成部分进行说明:

捕获列表

值捕获

1
2
3
4
5
6
7
8
void func()
{
int i = 100;//局部变量
//将i拷贝到明位f的可调用对象
auto f = [i] { return i; };
i = 0;
int j = f(); //j=100,因为i是创建时拷贝的
}

引用捕获

1
2
3
4
5
6
7
8
void func()
{
int i = 100;//局部变量
//将i拷贝到明位f的可调用对象
auto f = [&i] { return i; };
i = 0;
int j = f(); //j=0, 因为传递的是引用
}

除了自己列出捕获列表的变量,还可以让编译器根据lambda中代码来推断我们要使用哪些变量(隐式捕获),用过使用&或=指示编译器推断捕获列表。&则采用引用捕获的方式,=则采用值捕获的方式。混合使用隐式捕获和显示捕获,则两者须使用不同的方式,一个为引用捕获,一个为值捕获。

lambda捕获列表:

  • [ ]。空捕获列表,lambda不能使用所在函数中的变量。
  • [=]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
  • [&]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
  • [this]。函数体内可以使用Lambda所在类中的成员变量。
  • [a]。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
  • [&a]。将a按引用进行传递。
  • [=,&a, &b]。除a和b按引用进行传递外,其他参数都按值进行传递。
  • [&, a, b]。除a和b按值进行传递外,其他参数都按引用进行传递。

悬垂引用:

若以引用隐式或显式捕获非引用实体,而在该实体的生存期结束之后调用lambda对象的函数调用运算符,则发生未定义行为。C++ 的闭包并不延长被捕获的引用的生存期。这同样适用于被捕获的this指针所指向的对象的生存期。

形参列表

lambda形参列表和一般的函数形参列表类似,但不允许默认实参(C++14 前)。当以 auto 为形参类型时,该 lambda 为泛型 lambda(C++14 起)。与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//代码在VS2019中测试
void func()
{
int i = 1, j = 2;
auto f = [](int a,int &b) {
a = 10;
b = 20;
//输出:10 20
std::cout << a << " " << b << std::endl;
};
f(i,j);
//输出:1 20
std::cout << i << " " << j << std::endl;
}
1
2
3
4
5
6
7
8
9
//代码在VS2019中测试
void func()
{
auto f = [](auto a,int b=10) {
std::cout << a << " " << b << std::endl;
};
f(1.5, 2);
f(true);
}

说明符

  • mutable:允许函数体修改各个复制捕获的对象,以及调用其非 const 成员函数;
  • constexpr:显式指定函数调用运算符为 constexpr 函数。此说明符不存在时,若函数调用运算符恰好满足针对 constexpr 函数的所有要求,则它也会是 constexpr; (C++17 起)
  • consteval:指定函数调用运算符为立即函数。不能同时使用 consteval 和 constexpr。(C++20 起)

默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。假如我们希望能改变一个被捕获的变量的值,就必须在参数列表后面加上关键字mutable。而一个引用捕获的变量则不受此限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
//代码在VS2019中测试
void func()
{
int i = 10, j = 10;
//加上mutable才可以在lambda函数中改变捕获的变量值
auto f = [i, &j]() mutable {
i = 100, j = 100;
};
i = 0, j = 0;
f();
//输出:0 100
std::cout << i << " " << j << std::endl;
}

返回类型 ->

当我们需要为一个lambda定义返回类型时,需要使用尾置返回类型。返回类型若缺省,则根据函数体中的 return 语句进行推断(如果有多条return语句,需要保证类型一直,否则编译器无法自动推断)。默认情况下,如果一个lambda函数体不包含return语句,则编译器假定返回void。

1
2
3
4
5
6
7
8
9
10
void func()
{
auto f = []() -> double {
if (1 > 2)
return 1;
else
return 2.0;
};
std::cout << f() << std::endl;
}

如果不显示指定返回类型,则int和double两种返回类型会导致推断冲突。

函数体 \(\{ \}\)

略,同普通函数的函数体。


C++ lambda函数用法
https://gstarmin.github.io/2023/02/24/Cpp-lambda用法/
作者
Starmin
发布于
2023年2月24日
许可协议