问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

【C++】lambda表达式的理解与运用(C++11新特性)

创作时间:
作者:
@小白创作中心

【C++】lambda表达式的理解与运用(C++11新特性)

引用
CSDN
1.
https://m.blog.csdn.net/dhgiuyawhiudwqha/article/details/143587952

🌈 个人主页:**谁在夜里看海.******
🔥 个人专栏:**《C++系列》******《Linux系列》****
⛰️ 天高地阔,欲往观之。

目录
前言
C++11之前的例子
一、lambda的语法
lambda函数示例:
二、lambda的捕获列表
1.传值捕获
mutable修饰
2.传引用捕获
3.当前对象this捕获
4.带初始化的捕获
5.全捕获
按值全捕获 [=]:
按引用全捕获 [&]:
三、等价于仿函数

前言

C++11引入了 lambda 函数这个概念,用来快速地构建一个闭包,闭包是函数式编程的一个概念,在函数式编程中使用闭包来实现一些高阶函数。

闭包是指一个匿名函数以及它所捕获的上下文环境的组合。闭包允许你在函数中捕获并使用周围作用域的变量,从而实现更灵活的函数行为。

使用 lambda 可以带来以下好处:

1.通过创建lambda对象,可以快速的构建比如谓词函数这种短小并局部使用的函数。这样可以使这部分代码局部化,不污染全局命名空间

2.通过lambda对象可以快速地创建一个可调用对象。和传统的可调用对象的场景方式相比(如仿函数),省略了编写类的构造函数以及调用操作符重载等相关代码

这样说可能不是很理解,下面列举一个例子,让我们感受一下:

C++11之前的例子

在C++11之前,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。

#include <algorithm>
int main()
{
    int array[] = {4,1,8,5,3,7,0,9,2,6};
    // 默认按照小于比较,排出来结果是升序
    std::sort(array, array+sizeof(array)/sizeof(array[0]));
    // 如果需要降序,需要改变元素的比较规则
    std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
    return 0;
}  

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

struct Goods
{
    string _name;  // 名字
    double _price; // 价格
    int _evaluate; // 评价
    Goods(const char* str, double price, int evaluate)
        :_name(str)
        , _price(price)
        , _evaluate(evaluate)
    {}
};
struct ComparePriceLess
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price < gr._price;
    }
};
struct ComparePriceGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price > gr._price;
    }
};
int main()
{
    vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
    sort(v.begin(), v.end(), ComparePriceLess());
    sort(v.begin(), v.end(), ComparePriceGreater());
}  

每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这都给我们带来了极大的不便,于是C++11就出现了lambda表达式,下面是使用lambda表达式实现上述比较过程的代码:

int main()
{
    vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price < g2._price; });
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price > g2._price; });
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate < g2._evaluate; });
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate > g2._evaluate; });
}  

如此一来,是不是与上面讲到的优点对应上了:

①:快速构建了不同比较逻辑的函数,使代码局部化;

②:快速创建了可调用对象,省略了类的构造函数等的代码编写

下面我们来具体聊聊 lambda 的使用方法:

一、lambda的语法

lambda 表达式书写格式如下:

[ capture-list ] ( params ) mutable -> return-type { body }  

各部分说明:

  • [ capture-list ]:捕捉列表,能够捕捉上下文中的变量供lambda函数内部使用

  • ( params ):参数列表,与普通函数一致,没有参数可省略

  • mutable:修饰符,lambda函数默认为const属性,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略

  • -> return-type:返回值类型,可以省略(由编译器自行推导)

  • { body }:函数体,可以使用自身参数和捕获变量

注意:

在lambda表达式中,参数列表和返回值类型都可以省略,而捕捉列表和函数体不可省略,但可以为空。因此C++11最简单的lambda表达式为:[]{}; 该表达式没有任何含义。

lambda函数示例:

int main()
{
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
    [] {};
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    int a = 3, b = 4;
    [=] {return a + 3; };
    // 省略了返回值类型,无返回值类型
    auto fun1 = [&](int c) {b = a + c; };
    fun1(10);
    cout << a << " " << b << endl;
    // 各部分都很完善的lambda函数
    auto fun2 = [=, &b](int c)->int {return b += a + c; };
    cout << fun2(10) << endl;
    // 复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable { x *= 2; return a + x; };
    cout << add_x(10) << endl;
    return 0;
}  

通过上述例子我们可以看出,lambda表达式可以理解成无名函数,该函数无法直接调用,如果想直接调用,可以借助auto将其赋值给一个变量。

二、lambda的捕获列表

lambda的捕获方式主要分为两大类:传值捕获和传引用捕获

1.传值捕获

传值捕获时,意味着闭包在创建时会拷贝一份变量的当前值,并在闭包中使用这份拷贝。这种捕获方式使得变量在lambda内变成一个只读变量,不能对其进行修改

mutable修饰

如果想要对传值捕获的变量在lambda函数内部进行修改,可以通过mutable关键字修饰来实现,但是要注意,mutable关键字允许对按值捕获的变量进行内部修改,而且不影响外部的变量:

int main()
{
    int num = 0;
    auto func = [num]() mutable
    {
        return ++num;
    };
    cout << func() << endl; // 此时显示为1
    cout << num << endl; // 显示为0,内部的修改不影响外部
}

2.传引用捕获

按引用捕获会直接引用外部变量,因此在lambda内部对该变量的修改会影响外部变量的值。引用捕获不会创建变量的副本,而是共享相同的内存地址

int main()
{
    int num = 0;
    auto func = [&num]()
    {
        return ++num;
    };
    cout << func() << endl; // 此时显示为1
    cout << num << endl; // 显示为1
}  

但是要注意,在lambda表达式没有被调用时,内部代码不会执行,也不会发生相应的修改

int main()
{
    int num = 10;
    auto algo = [&num](int a = 1, int b = 2)
    {
        num = 5;
        return (a + b) * num;
    };
    cout << num << endl; // 此时显示为10,没有发生修改
    return 0;
}  

只有调用了lambda表达式,才会执行内部的代码:

int main()
{
    int num = 10;
    auto algo = [&num](int a = 1, int b = 2)
    {
        num = 5;
        return (a + b) * num;
    };
    algo();
    cout << num << endl; // 此时显示为5,发生修改
    return 0;
}  

那么当传引用捕获时使用了mutable关键字,会有什么效果呢?mutable关键字失效

mutable关键字的作用是允许修改lambda函数内部的拷贝对象,而引用捕获不涉及拷贝,所以在这种情况下,mutable不会发挥作用,没有实际意义。

3.当前对象this捕获

当前对象捕获 [this] 允许lambda表达式访问器所在类的成员变量和成员函数,它实际上是按引用捕获当前对象指针,因此在lambda内部对成员的任何修改都会直接作用于当前对象本身。

class Counter {
public:
    Counter(int start) : count(start) {}
    void increase(int amount) {
        // 使用 [this] 捕获当前对象
        auto increment = [this, amount]() {
            count += amount; // 访问并修改当前对象的成员变量 count
        };
        increment();  // 调用 Lambda 表达式
    }
    int getCount() const { return count; }
private:
    int count;
};  

4.带初始化的捕获

C++14标准中还引入了带初始化的捕获方式。通过这种方式已进行捕获的时候,可以用捕获到的信息初始化一个新的变量,在函数体内使用这个新的变量。同时,这个捕获方式也是有传值和传引用两种方式:

传值,内部修改不会影响外部

int main()
{
    int num = 10;
    auto algo = [n = num](int a = 1, int b = 2) mutable
    {
        n = 5;
        return (a + b) * n;
    };
    algo();
    cout << num << endl; // 此时显示为10,不发生修改
    return 0;
}  

传引用,内部修改会影响外部

int main()
{
    int num = 10;
    auto algo = [&n = num](int a = 1, int b = 2)
    {
        n = 5;
        return (a + b) * n;
    };
    algo();
    cout << num << endl; // 此时显示为5,发生修改
    return 0;
}  

5.全捕获

全捕获可以通过 [=](按值捕获所有外部变量)或者 [&](按引用捕获所以外部变量)来实现。全捕获非常地便捷,不需要对外部变量一一列举。

按值全捕获

[=]

#include <iostream>
using namespace std;
int main() {
    int x = 10;
    int y = 20;
    auto calculate = [=]() {
        // 按值捕获,内部可以访问 x 和 y,但无法修改它们的值
        return x + y;
    };
    cout << "Sum: " << calculate() << endl; // 输出 30
    // 由于是按值捕获,x 和 y 的值在外部保持不变
    return 0;
}

按引用全捕获

[&]

#include <iostream>
using namespace std;
int main() {
    int x = 10;
    int y = 20;
    auto increment = [&]() {
        // 按引用捕获,允许修改 x 和 y 的值
        x += 5;
        y += 5;
    };
    increment();  // 调用 Lambda,x 和 y 的值将被修改
    cout << "x: " << x << ", y: " << y << endl; // 输出 x: 15, y: 25
    return 0;
}

三、等价于仿函数

lambda 函数对象其实是C++11标准实现的语法糖,其编译器的处理也可以等价于仿函数:根据lambda函数的实现,构造一个等价的仿函数,之后编译处理这个仿函数。

两者是等价的。

以上就是对C++11lambda表达式的介绍与个人理解,欢迎指正~

码文不易,还请多多关注支持,这是我持续创作的最大动力!

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号