C++ 11相关新特性(lambda表达式与function包装器)

news2025/1/13 19:53:22

目录

lambda表达式

引入

lambda表达式介绍

lambda表达式捕捉列表的传递形式

lambda表达式的原理

包装器

包装器的基本使用

包装器与重载函数

包装器的使用

绑定


C++ 11 新特性

lambda表达式

引入

在C++ 98中,对于sort函数来说,如果需要根据不同的比较方式实现不同的排序结果,需要写不同的仿函数,而在C++ 11中,可以通过lambda表达式解决这个问题,例如下面的例子:

struct Goods
{
    string _name;  // 名字
    double _price; // 价格
    int _evaluate; // 评价
    Goods(const char* str, double price, int evaluate)
        :_name(str)
        , _price(price)
        , _evaluate(evaluate)
    {}
};

当需要按照商品的价格和评价排序时,则需要写两个仿函数

struct ComparePrice
{
    // 按照价格排序
    bool operator()(const Goods& g1, const Goods& g2)
    {
        return g1._price < g2._price;
    }
};

struct CompareEvaluate
{
    // 按照评价排序
    bool operator()(const Goods& g1, const Goods& g2)
    {
        return g1._evaluate < g2._evaluate;
    }
};

调用时传递仿函数匿名对象

sort(v.begin(), v.end(), ComparePrice());
sort(v.begin(), v.end(), CompareEvaluate());

但是当需要按照其他方式进行比较时,需要再写其他的仿函数,为了简化步骤,可以使用lambda表达式

lambda表达式介绍

lambda表达式基本结构如下:

[捕捉列表](形式参数)mutable->返回值类型
{
    函数体
}
  • 捕捉列表:编译器根据[]来判断接下来的代码是否为lambda函数,用于传递在lambda表达式体内的使用到的参数,一般为lambda表达式所在的直接作用域的变量
  • 形式参数:用于lambda表达式体内的变量,如果不需要传递形式参数,则当前项可以省略不写,如果需要加mutable,则不论是有还是没有形式参数,都需要带上()
  • mutable:默认情况下lambda表达式捕捉列表的参数是被const修饰的,所以捕捉列表的参数是以传值的方式传递时是无法直接在lambda表达式内部进行修改的,但是如果加了mutable,就可以取消const属性
  • ->返回值类型:与普通的函数体一样的返回类型声明,如果lambda表达式的返回值类型比较明确时,该项可以不写
  • 函数体:同普通函数

有了lambda表达式,引入部分的例子中的仿函数可以用lambda表达式进行替换,如下:

// lambda表达式
sort(v.begin(), v.end(), [](Goods& g1, Goods& g2)
    {
        return g1._price < g2._price;
    });
sort(v.begin(), v.end(), [](Goods& g1, Goods& g2)
    {
        return g1._evaluate < g2._evaluate;
    });

lambda表达式捕捉列表的传递形式

如果没有形式参数传递,lambda表达式想使用其所在的直接作用域中的变量(全局除外)需要在捕捉列表中传递,在lambda表达式中,捕捉列表的传递形式一共有4种:

  1. 具体变量值传递[variable]:直接传递变量的值,在lambda表达式中就是对该变量的值进行拷贝,所以lambda表达式内部对variable修改时不影响variable本身的内容,并且在没有mutable的情况下不可以在内部对variable进行修改
  2. 具体变量引用传递[&variable]:以variable引用的方式传递,在lambda表达式中可以对variable内容进行修改,从而达到传址调用的效果
  3. 所有变量值传递[=]:将lambda表达式所在作用域中的变量全部以传值的方式传递给lambda表达式,具体传递了哪些值需要看lambda表达式中使用到了哪些值
  4. 所有变量引用传递[&]:将将lambda表达式所在作用域中的变量全部以传址的方式传递给lambda表达式,具体传递了哪些值需要看lambda表达式中使用到了哪些值,如果lambda内部需要进行修改,需要加mutable
对于第二种情况,如果想在lambda表达式内部修改lambda所在作用域的变量的值,可以在lambda表达式的形式参数部分以引用的方式传递实参,此时就可以不需要添加 mutable关键字,这个方法与普通函数的思路一致

上面4种方法也可以交错使用,例如下面的代码:

int main()
{
    int a = 0;
    int b = 0;
    int c = 0;
    // a,b以值传递,c以引用传递
    auto func = [=, &c]()
    {
        // a和b是值传递,不能修改
        // a = 10;
        // b = 20;
        // c是引用传递,可以修改
        c = 30;
    };

    func();

    return 0;
}

lambda表达式的原理

前面通过lambda表达式简化了原本应该使用仿函数改写比较方式的例子展示了lambda表达式的使用,但是lambda表达式实际与仿函数的原理基本一致,所以lambda表达式也被称为匿名函数,观察下面的代码的反汇编代码

class Rate
{
public:
    Rate(double rate)
        : _rate(rate)
    {}

    double operator()(double money, int year)
    {
        return money * _rate * year;
    }
private:
    double _rate;
};


int main()
{
    // 创建普通对象
    double rate = 0.1;
    Rate r(rate);
    // 使用仿函数
    r(10000, 2);

    // 使用lambda表达式
    auto func = [=](double money, int year)
    {
        return money * rate * year;
    };

    func(10000, 2);
    return 0;
}

反汇编如下:

需要注意的是,lambda表达式对象不可以相互转化,尽管完全相同,在底层两个逻辑一模一样的lambda表达式存在不同的lambda+uuid名称

包装器

包装器的基本使用

C++11中引入了function包装器,也叫做适配器,在前面有了lambda表达式后,可以发现如果直接调用lambda表达式的对象,其方式和函数的调用基本相同,但是前面的函数还有可能是仿函数,为了使程序的模版使用效率变高,可以使用包装器

使用包装器需要引入头文件 <functional>

包装器可以根据已有的函数、函数指针、lambda表达式进行包装,基本结构如下:

template <class T> function;     // undefined
template <class Ret, class... Args> 
class function<Ret(Args...)>;

// 其中Ret代表指定的函数的返回值
// Args代表指定的函数的参数
需要注意,使用包装器必须保证包装器中的模版参数(返回值和形式参数)与被包装的对象完全相同

使用方式如下:

#include <functional>

class Rate
{
public:
    Rate(double rate) 
        : _rate(rate)
    {}

    double operator()(double money, int year)
    {
        return money * _rate * year;
    }

    static double calculate(double money, int year)
    {
        return money * 0.1 * year;
    }
private:
    double _rate;
};

double func(double money, int year)
{
    return money * 0.1 * year;
}

int main()
{
    // 包装普通函数
    function<double(double, int)> func1 = func;
    func1(10000, 2);

    // 包装仿函数
    function<double(Rate, double, int)> func2 = &Rate::operator();
    func2(Rate(0.1), 10000, 2);

    function<double(double, int)> func3 = Rate::calculate;
    func3(10000, 2);

    // 包装lambda表达式
    function<double(double, int)> func4 = [=](double money, int year)
    {
        return money * 0.1 * year;
    };

    func4(10000, 2);
    return 0;
}

上面代码中,包装普通函数与包装函数指针类似,包装成员函数需要注意两种形式:1. 静态成员函数 2. 非静态成员函数,对于静态成员函数来说,可以直接取地址,与普通函数类似,但是需要指定类域,而对于非静态成语函数来说,需要加上&,因为非静态成员函数存在一个隐含的参数this,需要依赖对象实例进行调用,所以非静态成员需要传递一个类名(或类指针)代表调用时需要传递同类类型的(匿名/非非匿名)对象(或对象地址),在调用包装后的函数时,实际上是通过对象进行调用,而不是包装器进行调用,对于lambda表达式来说,直接使包装器接受lambda表达式即可

包装器与重载函数

重载函数的根本条件就是必须满足函数名相同,但是此时如果直接使用函数名作为包装器的对象就会产生二义性问题,例如下面的代码:

#include <functional>
int add(int a, int b)
{
    return a + b;
}

double add(double a, double b)
{
    return a + b;
}

int main()
{
    map<int, function<int(int, int)>> m;
    m.insert({ 1, add });
    return 0;
}

报错信息:
'std::_Tree<std::_Tmap_traits<_Kty,_Ty,_Pr,_Alloc,false>>::insert': no overloaded function could convert all the argument types

在上面的代码中,map的模版参数是intfunction<int(int, int)>,代码中也存在对应包装器模版类型的add函数,但是编译器并不会自动选择对应的重载函数,所以在出现重载函数时,推荐使用函数指针对重载函数进行指代,再传入函数指针,避免传入重载函数的函数名,另外也可以使用lambda表达式,从而不使用函数重载,例如下面的代码:

int main()
{
    map<int, function<int(int, int)>> m;
    //m.insert({ 1, add }); 直接插入导致二义性

    // 使用函数指针指代需要插入的函数
    int (*pint)(int, int) = add;
    m.insert({ 1, pint });

    // 使用lambda表达式
    m.insert({ 2, [](int a, int b) {return a + b; } });
    return 0;
}

包装器的使用

C++形式的转移表,以实现简易计算器为例:

下面的代码是用于计算的函数:

int add(int a, int b)
{
    return a + b;
}

int sub(int a, int b)
{
    return a - b;
}

int divide(int a, int b)
{
    return a / b;
}

int multiply(int a, int b)
{
    return a * b;
}

常规写法:

int main()
{
    // 处理操作数和操作符输入
    int num1 = 0;
    int num2 = 0;
    char opt = 0;
    int flag = 1;
    
    // 处理计算
    while (flag && cin >> opt)
    {
        switch (opt)
        {
        case '+':
            cin >> num1 >> num2;
            cout << add(num1, num2) << endl;
            break;
        case '-':
            cin >> num1 >> num2;
            cout << sub(num1, num2) << endl;
            break;
        case '*':
            cin >> num1 >> num2;
            cout << multiply(num1, num2) << endl;
            break;
        case '/':
            cin >> num1 >> num2;
            cout << divide(num1, num2) << endl;
            break;
        default:
            flag = 0;
            break;
        }
        if (flag == 0)
        {
            break;
        }
    }
    
    return 0;
}

使用包装器后:

#include <functional>

int main()
{
    map<char, function<int(int, int)>> m{ {'+', add}, {'-', sub}, {'*', multiply}, {'/', divide} };
    int num1 = 0;
    int num2 = 0;
    char opt = 0;

    while (cin >> opt)
    {
        if (m.count(opt)) // 如果count为1,代表map中存在对应的键值对
        {
            cin >> num1 >> num2;
            cout << m[opt](num1, num2) << endl;// 返回的value是包装器,直接传参即可调用对应
        }
        else
        {
            break;
        }
    }

    return 0;
}

绑定

在C++ 11中,增加了绑定配合包装器的使用,包装器可以实现两种功能:

  1. 改变实参在传参时的顺序
  2. 固定形参中的某一个值
使用 bind时需要展开命名空间 placeholders,因为要使用其中的 _1_2...
placeholders中的内容表示调用绑定函数的实际参数, _1代表第一个实际参数, _2代表第二个实际参数,以此类推
  • 改变实参在传参时的顺序
#include <functional>
using namespace placeholders;

int sub(int a, int b, int c)
{
    return a - b - c;
}

int main()
{
    // 1. 改变实际参数顺序
    function<int(int, int, int)> func = sub;
    cout << func(1, 2, 3) << endl;
    // 绑定改变顺序
    func = bind(func, _2, _3, _1);
    // 改变后的传递顺序为:2, 3, 1
    cout << func(1, 2, 3) << endl;

    return 0;
}

输出结果:
-4
-2

传递顺序改变如下图所示:

注意, bind改变的是实际参数的传递顺序,而不是形参的接收顺序,形参接收还是按照从左到右依次接收传递的实际参数,只是写的第一个实际参数(本应该传递给形参a)被 bind改变作为第三个实际参数,传递给形参 c,依次类推 ab
  • 固定形参中的某一个值

在前面使用function包装器调用非静态成员函数时,需要单独传递一个对象给隐含的this指针,如果每一次传递都需要传递一个对象会显得繁琐,可以考虑将对象参数进行固定,例如下面的代码:

class Rate
{
public:
    Rate(double rate)
        : _rate(rate)
    {}

    double calculate(double money, int year)
    {
        return money * _rate * year;
    }

private:
    double _rate;
};

int main()
{
    // 不使用bind下使用包装器
    function<double(Rate, double, int)> func1 = &Rate::calculate;
    cout << func1(Rate(0.1), 10000, 2) << endl;
    
    
    // 使用bind下使用包装器
    function<double(Rate, double, int)> func2 = &Rate::calculate;
    // 使用bind固定对象Rate(0.1)
    function<double(double, int)> func3 = bind(func2, Rate(0.1), _1, _2);
    cout << func3(10000, 2) << endl;

    return 0;
}

输出结果:
2000
2000

上面代码中,需要注意尽管固定了func2的第一个参数,实际参数的指代还是从_1开始,如果固定中间的参数,则最左边的为_1,最右边的为_2(代码如下),以此类推

function<double(Rate, int)> func4 = bind(func2, _1, 10000, _2);
cout << func4(Rate(0.1), 2) << endl;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2045000.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot日志监听功能

目录 1. 概述1.1. 需求1.2. 思路 2. 功能实现2.1 依赖选取2.2 编写logBack.xml2.3 日志拦截2.4 封装请求为HttpServletRequestWrapper2.5 AOP2.6 日志监听 3. 后记 1. 概述 1.1. 需求 背景&#xff1a;拆分支付系统的日志&#xff0c;把每笔单子的日志单独拎出来存库。每笔单…

如何将高清图片修复?3个方法一键还原图片

如何将高清图片修复&#xff1f;高清图片修复是一个涉及图像处理技术的复杂过程&#xff0c;是对图片进行简单的调整或优化。这个过程旨在最大程度地恢复和提升图片的清晰度、细节和整体视觉效果&#xff0c;使其更加逼真、生动。通过高清图片的修复&#xff0c;我们可以让老旧…

稀疏注意力:时间序列预测的局部性和Transformer的存储瓶颈

时间序列预测是许多领域的重要问题&#xff0c;包括对太阳能发电厂发电量、电力消耗和交通拥堵情况的预测。在本文中&#xff0c;提出用Transformer来解决这类预测问题。虽然在我们的初步研究中对其性能印象深刻&#xff0c;但发现了它的两个主要缺点:(1)位置不可知性:规范Tran…

C++_2_ inline内联函数 宏函数(2/3)

C推出了inline关键字&#xff0c;其目的是为了替代C语言中的宏函数。 我们先来回顾宏函数&#xff1a; 宏函数 现有个需求&#xff1a;要求你写一个Add(x,y)的宏函数。 正确的写法有一种&#xff0c;错误的写法倒是五花八门&#xff0c;我们先来“见不贤而自省也。” // …

windows下部署redis3.2

一、下载redis3.2的包 6.2.6的包也有&#xff0c;但无法安装为Windows服务&#xff0c;暂时舍弃。 直接运行&#xff1a; redis-server redis.windows.conf 修改密码, 对应 redis.windows.conf 中的 requirepass 节点&#xff0c;注意去掉前面的# 修改端口&#xff0c;对应…

缺陷检测AI 重要参数解释

一、参数介绍 基本参数 True Positives (TP) True Positives (TP) 是一个用于评估模型性能的术语。它指的是模型正确预测为正例&#xff08;Positive&#xff09;的样本数量&#xff0c;即实际为正例且被正确分类为正例的样本数量。 False Positives (FP) FP (False Posit…

Python 文件目录操作,以及json.dump() 和 json.load()

import os 是用来引入 Python 标准库中的 os 模块的&#xff0c;这个模块提供了与操作系统交互的功能。这个模块常用于文件和目录操作&#xff0c;比如获取文件的目录路径、创建目录等。 如果你在代码中需要使用与操作系统相关的功能&#xff08;例如获取目录名、检查文件是否…

qt-11基本对话框(消息框)

基本对话框--消息框 msgboxdlg.hmsgboxdlg.cppmain.cpp运行图QustionMsgInFormationMsgWarningMsgCriticalMsgAboutMsgAboutAtMsg自定义 msgboxdlg.h #ifndef MSGBOXDLG_H #define MSGBOXDLG_H#include <QDialog> #include <QLabel> #include <QPushButton>…

Cesium模型制作,解决Cesium加载glb/GLTF显示太黑不在中心等问题

Cesium模型制作&#xff0c;解决Cesium加载glb/GLTF显示太黑不在中心等问题 QQ可以联系这里&#xff0c;谢谢

电商搜索新纪元:大模型引领购物体验革新

随着电商行业的蓬勃发展&#xff0c;搜索技术作为连接用户与商品的桥梁&#xff0c;其重要性日益凸显。在技术不断革新的今天&#xff0c;电商搜索技术经历了哪些阶段&#xff1f;面对大模型的飞速发展&#xff0c;企业又将如何把握趋势&#xff0c;应对挑战&#xff1f;为了深…

openinstall支持抖音游戏小手柄监测,助力游戏联运生态高效增长

近来&#xff0c;抖音“小手柄”功能风靡游戏广告生态&#xff0c;通过新颖的联运形式成功将游戏广告触达到抖音整个流量池&#xff0c;由于受众较广&#xff0c;小手柄也是目前直播场数、点赞数最高的形式。 为了帮助广告主快速捕捉流量红利&#xff0c;打通抖音小手柄的数据…

【选型指南】大流量停车场和高端停车场如何选择停车场管理系统?

在当今快节奏的城市生活中&#xff0c;大型停车场和高端车场的运营者面临着一系列挑战&#xff0c;尤其是在车辆流量大和客户期望高的情况下。选择一个合适的停车场管理系统&#xff0c;不仅关系到日常运营的效率&#xff0c;更关系到客户的满意度和车场的整体形象。 捷顺科技认…

螺纹钢生产线中测径仪对基圆和负公差的测量和影响

螺纹钢生产线中测径仪的作用 在螺纹钢生产线中&#xff0c;测径仪是一种关键的检测设备&#xff0c;它负责对螺纹钢的基圆直径、横肋和纵肋等尺寸进行实时测量。测径仪的数据对于监控和控制螺纹钢的生产质量至关重要&#xff0c;尤其是在进行负公差轧制时&#xff0c;它能够提供…

K8S中使用英伟达GPU —— 筑梦之路

前提条件 根据不同的操作系统&#xff0c;安装好显卡驱动&#xff0c;并能正常识别出来显卡&#xff0c;比如如下截图&#xff1a; GPU容器创建流程 containerd --> containerd-shim--> nvidia-container-runtime --> nvidia-container-runtime-hook --> libnvid…

【Spring Boot - 注解】@ResponseBody 注解:处理 JSON 响应

文章目录 一、ResponseBody 注解概述1. 注解的功能2. 主要功能 二、ResponseBody 的工作原理1. 接口定义2. 消息转换器3. 自动配置与默认行为 三、ResponseBody 的应用场景1. RESTful API 的实现2. 返回复杂数据结构3. 错误处理和异常处理 四、ResponseBody 的配置和自定义1. 自…

Rabbit的学习——从安装到集群

一、MQ概念 1.1、异步通讯和同步通讯 1.2、同步调用和异步调用 1.2.1、同步调用 1.2.2、异步调用 1.3、消息队列的作用 1.3.1、流量削峰/限流 1.3.2、 应用解耦 1.3.3、异步处理 1.4、消息队列的两种模式 1.4.1、点对点模式 1.4.2、发布/订阅模式 二、RabbitMQ 2.1…

MyBatis Plus批量写入慢?

1. 数据库连接配置 在使用 MyBatis Plus 进行批量插入之前&#xff0c;首先需要配置数据库连接。在连接 URL 中添加 &rewriteBatchedStatementstrue&#xff0c;以提高批量插入的性能。以下是一个示例&#xff1a; spring.datasource.urljdbc:mysql://localhost:3306/your…

路径规划 | 基于改进蝙蝠算法的多无人机路径规划(Matlab)

目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 路径规划 | 基于改进蝙蝠算法的多无人机路径规划&#xff08;Matlab&#xff09; 蝙蝠算法&#xff08;Bat Algorithm&#xff09;是一种基于自然界蝙蝠群体行为的启发式优化算法。该算法模拟了蝙蝠在寻找食物时的行为…

Linux 内核源码分析---内核ICMP协议分析

因特网控制报文协议ICMP&#xff08;Internet Control Message Protocol&#xff09; 是一个差错报告机制&#xff0c;是TCP/IP协议簇中的一个重要子协议&#xff0c;通常被IP层或更高层协议&#xff08;TCP或UDP&#xff09;使用&#xff0c;属于网络层协议&#xff0c;主要用…

论文阅读-Transformer Layers as Painters

1. 摘要 尽管大语言模型现在已经被广泛的应用于各种任务&#xff0c;但是目前对其并没有一个很好的认知。为了弄清楚删除和重组预训练模型不同层的影响&#xff0c;本文设计了一系列的实验。通过实验表明&#xff0c;预训练语言模型中的lower和final layers与中间层分布不一致…