C++ lambda表达式

一、lambda概述

要介绍什么时lambda表达式,首先要认识什么是可调用对象。可调用对象的定义是:对于一个对象或一个表达式,如果可以对其使用调用运算符“()”,则称为可调用的。因此我们所熟知的可调用对象就是函数函数指针,另外还有两种可调用对象就是重载了函数调用运算符的类lambda表达式。根据它的本质,我们可以把它看做成是一个没有名字的函数,也可以称为匿名函数。


二、lambda语法分析

语法:[capture list]    (parameter list)    ->    retur type    { function body }

capture list:为捕获列表,它的功能是捕获lambda所在函数中的局部变量,以便在lambda函数体中使用

parameter list:是参数列表,与普通函数一样

return type:返回类型,与普通函数一样

function body:函数体,与普通函数一样

其中参数列表和返回类型是可以忽略的,但捕获列表和函数体一定要有

 

三、lambda应用场景及用法

1. 基本用法

以下例子忽略了参数列表和返回类型

auto f = [] { cout << "my lambda function" << endl; };
f();

auto f2 = [] { return 100; };
cout << f2() << endl;

/*
output:
my lambda function
100
*/

2. 结合STL使用

当我们使用泛型算法时,有些算法的第三个参数是可以接受一个谓词,谓词是一个可调用的表达式,所以我们可以传递一个函数或者一个lambda表达式,例如以下用法,功能是打印vecrot中元素的值

vector<int> vec= {1,2,3,4,5};
for_each(vec.begin(),vec.end(),
          [] (const int &val) { cout << val << endl;});
/*
output:
1
2
3
4
5
*/

3.捕获列表用法

3.1捕获列表的变量是lambda对象的数据成员

要介绍捕获列表,首先就要介绍以下lambda与函数不一样的地方了,当定义一个lambda时,编译器就会生成一个与lambda对应的类对象,而捕获列表中的变量就是lambda生成类中的数据成员,因此在创建lambda对象时,捕获列表的变量就会被初次化,这就说明捕获列表的变量的值不是在调用时确定的,而是在定义的时候就已经确定了。

3.2 值捕获

捕获列表的作用是使用定义lambda函数中的局部变量,值捕获就对捕获的变量进行拷贝,拷贝完之后生成的副本其值与局部变量的值已经没有关系了

int val = 10;
auto f1 = [val] { return val; };
val = 20;
cout << f1() << endl;

/*
output:
10
*/

如果没有捕获变量就使用该局部变量,编译器会报错

int val = 10;
int v = 2;
auto f1 = [val] { return val + v; };

/*
complie error:
error: 'v' is not captured|
*/

 

3.3 引用捕获

引用捕获顾名思义就是以引用的方式去捕获,不是通过拷贝而是通过绑定的方式,与拷贝的捕获变量不同,对于引用捕获当调用lambda时,其捕获值会与绑定的值关联一起,有些对象无法拷贝,就需要使用引用捕获,例如ostream对象

int val = 10;
auto f1 = [&val] { return val; };
val = 20;
cout << f1() << endl;

ostream &os = cout;
auto f2 = [&os] { os << "ref capture lambda func" <<endl; };
f2();

/*
output:
20
ref capture lambda func
*/


3.4 显式捕获与隐式捕获

显示捕获是指我们在捕获列表中列出想要捕获的局部变量名,而隐式捕获就是不列出变量名,让编译器来推断我们要使用哪些变量,隐式的值捕获是[=],隐式的引用捕获是[&]

int val = 10;
int val2 = 20;
auto f1 = [=] { return val; };        //隐式捕获,其捕获方式是值捕获
auto f2 = [&] { return val2; };       //隐式捕获,其捕获方式是引用捕获
cout << f1() << endl;
cout << f2() << endl;

/*
output:
10
20
*/

我们也可以将显示和隐式混合使用,但需要注意的是当混合使用时,捕获列表的第一个元素必须是&或=,即指定隐式在前显式在后

int val = 10;
int val2 = 20;
int val3 = 30;
auto f1 = [=,&val3] { return val + val3; };
auto f2 = [&,val3] { return val2 + val3; };
cout << f1() << endl;
cout << f2() << endl;

/*
output:
40
50
*/


3.5 mutable关键字可使捕获的变量值改变

一般情况下,当我们使用值捕获后修改了lambda函数体中的捕获变量,编译器是会报错的

int val = 10;
auto f1 = [val] { return ++val; };

/*
error: increment of read-only variable 'val'
*/

因此如果我们想修改其值,可以用mutable关键字,此时参数列表不能忽略,对于引用捕获,如果捕获的变量不是const的话是可以修改的

int val = 10;
int val2 = 20;
auto f1 = [val] () mutable{ return ++val; };        //不能忽略参数列表
auto f2 = [&val2] { return ++val2; };
cout << f1() << endl;
cout << f2() << endl;

/*
output:
11
21
*/


3.6 结合只接受一元谓词的STL使用

有些泛型算法只接受一元谓词(所谓一元谓词就是意味这可调用对象只接受一个参数),当是我们又想传入两个参数给lambda使用,这时就可以捕获列表就起作用了,以下我们用find_if为例子,此代码功能是找出vector中长度大于等于4的字符串

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

#define FIND_NAME_SIZE  4

using namespace std;

int main()
{
    vector<string> v_str= {"TOM","TONY","JACKY","DAVID"};
    size_t sz = FIND_NAME_SIZE;
    
    auto first_name_it= find_if(v_str.begin(),v_str.end(),
              [sz] (const string &a) { return a.size() >= sz;});

    cout << *first_name_it << endl;

    return 0;
}

/*
TONY
*/

4. 指定lambda的返回类型

返回类型是可以忽略的,那什么时候需要我们指定返回类型呢?当函数体包含return之外的语句,此时编译器假定此lambda返回void,所以需要用尾置返回指定返回类型

int val = 10;
auto f1 = [] (int val) -> int { if(val > 0) return -val; else return val; };
cout << f1(val) << endl;

四、lambda使用注意

不能有默认参数
       lambda函数体中要使用非static的局部变量,捕获列表必须要有
       如果有一个函数返回lambda对象,则此lambda不能包含引用捕获(原因与不能返回一个局部变量的引用类似)
       当混合使用隐式捕获和显式捕获时,捕获列表的显式捕获变量必须使用与隐式捕获不同的方式

 

### C++ Lambda 表达式用法详解 Lambda 表达式C++11 引入的重要特性之一,它允许开发者在调用或传递函数对象时直接定义匿名函数,极大提升了代码的简洁性和可读性。Lambda 表达式广泛应用于 STL 算法、异步编程、事件回调等场景中,是现代 C++ 编程的核心工具之一。 #### 基本语法结构 一个完整的 Lambda 表达式通常由以下几个部分组成: - **捕获列表**(Capture List):指定 Lambda 表达式如何访问外部变量。 - **参数列表**(Parameter List):可选,用于指定 Lambda 接受的参数。 - **可变说明符**(mutable):允许修改按值捕获的变量副本。 - **异常说明符**(Exception Specifier):可选,指定 Lambda 是否抛出异常。 - **返回类型**(Return Type):可选,若未指定,编译器会自动推导。 - **函数体**(Function Body):包含具体的实现逻辑。 示例代码如下: ```cpp auto lambda = [](int a) -> int { return a + 1; }; std::cout << lambda(1) << std::endl; // 输出: 2 ``` #### 捕获机制 Lambda 表达式的捕获机制决定了它如何访问和修改外部变量,主要有以下几种方式: - **按值捕获**([=]):捕获外部变量的副本,Lambda 内部无法直接修改外部变量。若需修改副本,需使用 `mutable` 关键字。 - **按引用捕获**([&]):捕获外部变量的引用,Lambda 内部可直接修改外部变量。 - **显式捕获**:可精确指定捕获的变量及方式,例如 `[x, &y]` 表示按值捕获 `x`,按引用捕获 `y`。 示例代码如下: ```cpp int x = 10; auto lambda = [=]() mutable { x++; std::cout << x << std::endl; // 输出 11 }; lambda(); std::cout << x << std::endl; // 输出 10(外部 x 未变) ``` #### 生命周期管理 使用 Lambda 表达式时,尤其需要注意按引用捕获带来的生命周期问题。如果 Lambda 被存储并在捕获变量的生命周期结束后调用,可能导致未定义行为。因此,开发者需确保引用的变量在 Lambda 调用时仍然有效。 #### 性能考量 按值捕获大对象可能会带来拷贝开销,建议使用按引用捕获或智能指针来避免不必要的性能损耗。此外,隐式捕获(如 [=] 和 [&])仅捕获 Lambda 体内实际使用的外部变量,因此可以有效减少捕获范围。 #### 应用场景 Lambda 表达式在 STL 算法中表现尤为突出,例如 `std::for_each`、`std::transform` 等算法中可以使用 Lambda 来简化函数对象的定义。此外,Lambda 还广泛应用于异步编程、事件回调、闭包管理等场景中。 示例代码如下: ```cpp #include <algorithm> #include <vector> #include <iostream> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; std::for_each(vec.begin(), vec.end(), [](int x) { std::cout << x << " "; }); std::cout << std::endl; return 0; } ``` #### 标准演进与未来趋势 随着 C++ 标准的不断演进,Lambda 表达式的功能也在不断增强。例如,C++20 引入了模式匹配(Pattern Matching),进一步扩展了 Lambda 的应用场景。未来,Lambda 表达式将继续成为现代 C++ 开发的核心工具之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值