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

目录

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;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

超网和无类间路由是什么?

​一、超网概述 超网是将多个连续的网络地址组合成一个增加的网络地址的技术。常用于减少路由器的路由表大小&#xff0c;网络的可扩展性。通过合并连续的子网&#xff0c;超网可以减少路由入侵的数量&#xff0c;从而提高网络的效率。 超网的实现基于合并多个具有连续IP地址…

【vue教程】五. Vue 的路由管理

目录 往期列表本章涵盖知识点回顾Vue Router 的基本概念什么是 Vue Router&#xff1f;为什么需要 Vue Router&#xff1f; 路由的配置和使用安装 Vue Router创建路由在 Vue 实例中使用路由模板中的路由链接 动态路由和嵌套路由动态路由嵌套路由 路由守卫什么是路由守卫&#x…

HarmonyOS应用三之组件生命周期和参数传递

目录&#xff1a; 1、生命周期的执行顺序2、页面数据传递3、图片的读取4、数据的备份和恢复5、轮播图6、页面布局图 1、生命周期的执行顺序 /** Copyright (c) 2023 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the "License");* yo…

微服务架构设计中的常见的10种设计模式

微服务架构设计的概念 微服务架构&#xff08;Microservices Architecture&#xff09;是一种用于构建分布式系统的软件设计模式。它将大型应用程序拆分成一组小型、自治的服务&#xff0c;每个服务都运行在其独立的进程中&#xff0c;服务之间通过轻量级的通信机制&#xff08…

Nginx 核心配置详解

章节 1 NGINX 的源码安装 2 NGINX 核心配置详解 3 NGINX 之 location 匹配优先级 4 NGINX 基础参数与功能 目录 1 配置文件说明 1.1 nginx 配置文件格式说明 1.2 Nginx 主配置文件的配置指令方式&#xff1a; 1.3 主配置文件结构&#xff1a;四部分 1.4 nginx 文件作用解…

使用Python编写AI程序,让机器变得更智能

人工智能&#xff08;AI&#xff09;是当今科技领域最热门的话题之一。随着Python编程语言的逐渐流行&#xff0c;它已经成为许多人工智能编程的首选语言。本文将介绍如何使用Python编写AI程序&#xff0c;让机器变得更智能。 首先&#xff0c;Python提供了大量的AI库和工具&a…

10、stm32实现adc读取数据

一、配置 二、代码 /* USER CODE BEGIN 2 */OLED_Init();OLED_Clear();OLED_ShowString(0,0,"Hello adc",12,0);uint16_t adc_number 0;/* USER CODE END 2 *//* USER CODE BEGIN WHILE */while (1){HAL_ADC_Start(&hadc1);HAL_ADC_PollForConversion(&hadc1…

SQL实战

学习视频&#xff1a;【课程2.0】SQL从入门到实战|云端数据库搭建|Excel&Tableau连接数据库_哔哩哔哩_bilibili 由于我学习过SQL&#xff0c;所以直接记录一些函数、特殊用法、刷题等实战的知识&#xff0c;后面教学搭建云端数据库和其他软件连接数据库视频讲解很清晰&…

华为AR1220配置GRE隧道

1.GRE隧道的配置 GRE隧道的配置过程,包括设置接口IP地址、配置GRE隧道接口和参数、配置静态路由以及测试隧道连通性。GRE隧道作为一种标准协议,支持多协议传输,但不提供加密,并且可能导致CPU资源消耗大和调试复杂等问题。本文采用华为AR1220路由器来示例说明。 配置…

C语言家教记录(六)

导语 本次授课的内容如下&#xff1a;指针&#xff0c;指针和数组 辅助教材为 《C语言程序设计现代方法&#xff08;第2版&#xff09;》 指针 指针变量 计算机按字节划分地址&#xff0c;每个地址访问一个字节 指针变量指向变量的地址&#xff0c;指的是变量第一个字节的…

ElasticSearch读写性能调优

文章目录 ES写入数据过程ES读取数据的过程写数据底层原理提升集群读取性能数据建模优化分片 提升写入性能的方法服务器端优化写入性能建模时的优化降低Translog写磁盘的频率&#xff0c;但是会降低容灾能力分片设定调整Bulk 线程池和队列 ES写入数据过程 客户端选择一个node发…

记录一次搭建uniapp-vue3的基础项目

1.使用 HBuilder X 创建uniapp vue3的基础项目 2.安装 自动导包插件 unplugin-auto-import npm install unplugin-auto-import或者 pnpm install unplugin-auto-import2.1 根目录下创建 vite.config.js 复制粘贴以下内容 import { defineConfig } from vite import uni fro…

食品零食小吃商城管理系统-计算机毕设Java|springboot实战项目

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

一文学会本地部署可视化应用JSONCrack并配置公网地址实现远程协作

文章目录 前言1. Docker安装JSONCrack2. 安装Cpolar内网穿透工具3. 配置JSON Crack界面公网地址4. 远程访问 JSONCrack 界面5. 固定 JSONCrack公网地址 前言 本文主要介绍如何在Linux环境使用Docker安装数据可视化工具JSONCrack&#xff0c;并结合cpolar内网穿透工具实现团队在…

网络编程/在哪些场景中不必要进行网络字节序装换? Windows Sockets: Byte Ordering

文章目录 概述字节序必须转换字节序的的情况不必转换字节序的的情况字节序转换的例程字节序转换函数字节序转换可以不生硬字节序和位序 概述 本文主要讲述了在哪些场景下必须要进行大小端字节序转换&#xff0c;在哪些场景下可以不用进行大小端字节序转换&#xff0c;IP和端口…

<数据集>安全帽和安全背心识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;22141张 标注数量(xml文件个数)&#xff1a;22141 标注数量(txt文件个数)&#xff1a;22141 标注类别数&#xff1a;3 标注类别名称&#xff1a;[helmet, vest, head] 序号类别名称图片数框数1helmet15937572402v…

程序员如何写PLC程序

PLC是可编程逻辑控制器的简称&#xff0c;常用的编程语言是IEC61131-3&#xff08;梯形图、结构化文本、指令表、功能块、顺序功能图&#xff09;和西门子的SCL。程序员常用的编程语言是JS、Java、Python、C/C、Go等。PLC广泛采用编程工具有codesys、博图等。程序员常用的编程工…

XSS DOM破坏实战案例

目录 案例一 思考 源码分析 查找问题 实现 案例二 查看源码 问题查找 实现 实验环境&#xff1a;DOM clobbering | Web Security Academy (portswigger.net) 案例一 里面是一篇篇的博客&#xff0c;点击进去里面是一些评论 思考 尝试一些常规的xss 没什么效果... 他将…

为什么穷大方

为什么有些人明明很穷&#xff0c;却非常的大方呢&#xff1f; 因为他们认知太低&#xff0c;根本不懂钱的重要性&#xff0c;总是想着及时享乐&#xff0c;所以一年到头也存不了什么钱。等到家人孩子需要用钱的时候&#xff0c;什么也拿不出来&#xff0c;还到处去求人。 而真…

【Qt】常用控件QCheckBox

常用控件QCheckBox QCheckBox表示复选按钮&#xff0c;可以允许选中多个。 QCheckBox继承自QAbstractButton 例子&#xff1a;获取复选按钮的取值 使用Qt Designer先大体进行设计 代码实现&#xff1a; #include "widget.h" #include "ui_widget.h"Widge…