Lambda表达式

目录

一、语法

二、参数列表

三、返回类型

四、函数体

五、捕获列表

六、lambda 函数的本质


Lambda 函数是 C++11 标准新增的语法糖,也被称为 lambda 表达式或匿名函数。它具有距离近、简洁、高效和功能强大等特点。

示例:

[](const int& n) -> void { cout << "number:" << n << endl; };

一、语法

以下是一个包含 lambda 函数、普通函数、仿函数语法比较的示例代码:

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

using namespace std;

// 普通函数
void zsshow(const int & no)  {  
    cout << "number:" << no << "\n";
}

// 仿函数
class czs   
{
public:
    void operator()(const int & no) {
            cout << "number:" << no << "\n";

    }
};

int main()
{
    vector<int> vv = { 5,8,3 };   // 存放编号的容器

    // 第三个参数是普通函数
    for_each(vv.begin(), vv.end(), zsshow);  

    // 第三个参数是仿函数
    for_each(vv.begin(), vv.end(), czs());       

    // 第三个参数是 lambda 表达式
    for_each(vv.begin(), vv.end(), 
        [](const int& no) {
                cout << "number:" << no << "\n";

        }
    ); 
}

二、参数列表

参数列表是可选的,类似普通函数的参数列表。如果没有参数列表,()可以省略不写。

与普通函数的不同之处在于:

  • lambda 函数不能有默认参数。

  • 所有参数必须有参数名。

  • 不支持可变参数。

三、返回类型

用后置的方法书写返回类型,类似于普通函数的返回类型。如果不写返回类型,编译器会根据函数体中的代码推断出来。不过,如果有返回类型,建议显式地指定,因为自动推断可能与预期不一致。

四、函数体

类似于普通函数的函数体,和普通函数体的使用一样。

五、捕获列表

通过捕获列表,lambda 函数可以访问父作用域中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。捕获列表书写在[]中,与函数参数的传递类似,捕获方式可以是值和引用。以下列出了不同的捕获列表的方式。

  1. 值捕获

与传递参数类似,采用值捕获的前提是变量可以拷贝。与传递参数不同,变量的值是在 lambda 函数创建时拷贝,而不是调用时拷贝。

例如:

size_t v1 = 42;
auto f = [ v1 ]  { return v1; }; // 使用了值捕获,将 v1 拷贝到名为 f 的可调用对象
v1 = 0;
auto j = f();    // j 为 42,f 保存了我们创建它时 v1 的拷贝

由于被捕获的值是在 lambda 函数创建时拷贝,因此在随后对其修改不会影响到 lambda 内部的值。默认情况下,如果以传值方式捕获变量,则在 lambda 函数中不能修改变量的值。

  1. 引用捕获

和函数引用参数一样,引用变量的值在 lambda 函数体中改变时,将影响被引用的对象。

size_t v1 = 42;
auto f = [ &v1 ]  { return v1; }; // 引用捕获,将 v1 拷贝到名为 f 的可调用对象
v1 = 0;
auto j = f();   // j 为 0

如果采用引用方式捕获变量,就必须保证被引用的对象在 lambda 执行的时候是存在的。

  1. 隐式捕获

除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。

隐式捕获有两种方式,分别是[=][&][=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。

int a = 123;
auto f = [ = ]  { cout << a << endl; };  // 值捕获
f();  // 输出:123

auto f1 = [ & ] { cout << a++ << endl; };  // 引用捕获
f1(); // 输出:123(采用了后++)
cout << a << endl;  // 输出 124
  1. 混合方式捕获

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; };  // 编译出错,默认引用捕获,显式引用捕获,冲突了
  1. 修改值捕获变量的值

在 lambda 函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。

在 lambda 函数中,如果希望修改值捕获变量的值,可以加 mutable 选项,但是,在 lambda 函数的外部,变量的值不会被修改。

int a = 123;
auto f = [a]()mutable { cout << ++a << endl; }; // 不会报错
cout << a << endl;  // 输出:123
f();  // 输出:124
cout << a << endl;  // 输出:123
  1. 异常说明

lambda 可以抛出异常,用 throw(…) 指示异常的类型,用 noexcept 指示不抛出任何异常。

六、lambda 函数的本质

当我们编写了一个 lambda 函数之后,编译器将它翻译成一个类,该类中有一个重载了 () 的函数。

  1. 采用值捕获

采用值捕获时,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。这样上面的限制就能讲通了。

  1. 采用引用捕获

如果 lambda 函数采用引用捕获的方式,编译器直接引用就行了。

唯一需要注意的是,lambda 函数执行时,程序必须保证引用的对象有效。

所以lambda表达式其实就是C++标准委员会把仿函数包装了一下的一个新玩法,只不过这个玩法玩得好玩得秒!使得C++代码可以更加简洁高效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值