C++11新特性⑤ | 仿函数与lambda表达式

news2024/10/16 18:36:17

目录

1、引言

2、仿函数

3、lambda表达式

3.1、lambda表达式的一般形式

3.2、返回类型说明

3.3、捕获列表的规则

3.4、可以捕获哪些变量

3.5、lambda表达式给编程带来的便利


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html       C++11新特性很重要,作为C++开发人员很有必要去学习,不仅笔试面试时会涉及到,开源代码中也在大规模的使用。以很多视频会议及直播软件都在使用的开源WebRTC项目为例,WebRTC代码中大篇幅地使用了C++11及以上的新特性,要读懂其源码,必须要了解这些C++的新特性。所以,接下来一段时间我将结合工作实践,给大家详细讲解一下C++11的新特性,以供借鉴或参考。

1、引言

       在C++中我们可以使用函数名、函数指针、仿函数去实现函数的调用。C++11引入了lambda表达式,又称匿名函数,给我们引入了一种新的函数调用方式,给我们编程带来了很大的便利。今天我们来讲讲仿函数和lambda表达式。

2、仿函数

       仿函数是一种特殊的类或结构体,不是C++11引入的,之前就有了。它重载了函数调用运算符operator(),并且可以像函数一样被调用,同时它也可以拥有自己的数据成员和成员函数。仿函数通常用于算法(如sort、find、transform等)和容器(如set、map、list等)中,以提供自定义的操作行为。在C++11中,可以使用lambda表达式实现简单的仿函数。

       下面是个类中重载 operator()的实例:

class MyFunctor
{
public:
    MyFunctor(int tmp) : round(tmp) {}
    int operator()(int tmp) { return tmp + round; }
private:
    int round;
};

int main()
{
    int round = 2;
    MyFunctor f(round); //调用构造函数
    cout << "result = " << f(1) << endl; // operator()(int tmp)

    return 0;
}

通过类对象去调用重载的operator()方法。 在C++里,我们通过在一个类中重载operator()运算符的方法,去使用一个函数对象而不是一个普通函数。

       再看一个比较数大小的实例:

class compare_class
{
    public:
    bool operator() (int A, int B) const{return A < B;}
};
 
// Declaration of C++ sorting function.
template<class ComparisonFunctor>
void sort_ints(int* begin_items, int num_items, ComparisonFunctor c);
 
int main()
{
    int items[]={4, 3, 1, 2};
    compare_class functor;
    sort_ints( items, sizeof(items)/sizeof(items[0]), functor);
}

3、lambda表达式

       lambda表达式,又称匿名函数,是一个可调用的代码单元,可以理解为一个未命名(匿名)的内联函数。与一般的函数类似,lambda表示式有一个返回类型、一个参数列表和一个函数体。但和函数不同的是,lambda表达式是直接定义在函数内部。

       lambda表达式的引入,给我们编程带来了很大的便利,可以大大简化我们的编码!

3.1、lambda表达式的一般形式

lambda表达式的一般形式如下:

[capture list]( parameter list ) -> return type { function body }

其中:

1)capture list(捕获列表),是一个本表达式所在函数的局部变量的列表;
2)parameter list(参数列表),是给本lambda表达式传入的参数列表;
3)return type(返回类型),是本lambda表达式的返回值类型(可省略);
4)function body(函数体),是本lambda表达式的内部实现。

       对于普通函数,返回类型位于函数开始处,但lambda表达式因为其形式,返回类型不能放在开始处,必须使用尾置的方式来指定返回类型。我们可以忽略参数列表和返回值类型,但必须包含捕获列表和函数体,比如:

auto f = [] { return 20; };

3.2、返回类型说明

       直接在lambda表达式中指定返回类型,没什么问题。下面我们看看没指定返回类型时,将会发生什么。

       如果lambda表达式的函数体中只包含一个return语句,如果没指定lambda表达式的返回值,则编译时会根据return语句中的内容推导出本表达式的返回值类型,比如实现两个整型数据相加的lambda表达式:

[] ( int a, int b) { return a + b; }

本lambda中没有指定函数返回类型,根据表达式(a+b)可以推断出本lambda返回值类型为int。

       如果lambda表示式中不是只包含单一的return语句(多条语句),编译时编译器认定该lambda返回值类型为void。比如返回一个数绝对值的lambda如下:

[] ( int a ) { if ( a < 0) return -a; else return a; };

因为包含了多条语句,所以编译器认定该lambda返回void,编译时会报错!因为返回类型为void,却使用return返回了int类型,不一致了,所以报错!

3.3、捕获列表的规则

       捕获列表,涉及到访问所在函数哪些局部变量,以及访问变量的方式。访问变量的方式主要有以引用的方式访问,还是以值的方式访问。如果是将lambda要访问的变量在捕获列表中罗列出来,则是显示捕获;如果不罗列,就是隐式捕获。

       下面给出完整的捕获列表规则:

捕获类型描述
[]空捕获列表。lamda表达式内部不使用所在函数中的变量。只有捕获列表不为空才能使用所在函数中的变量。
[names]names是一个逗号分割的名字列表,这些名字是lamda所在函数的局部变量的名称。默认情况下,捕获列表中的变量值被拷贝,变量前面可以添加&,加&则表示采用引用捕获方式。
[&]隐式捕获列表,让编译器根据lamda内部的代码去推断使用了所在函数的哪些局部变量。采用引用捕获方式,lamda体中所使用的来自所在函数的变量都采用引用方式使用。
[=]隐式捕获列表,让编译器根据lamda内部的代码去推断使用了所在函数的哪些局部变量。采用值捕获方式,lamda体中将拷贝所使用的来自所在函数的变量的值。
[&, identifier_list] identifier_list(理解为不使用&引用捕获的特例)是逗号分割的列表,包含0个或多个来自所在函数的变量,这些变量采用值捕获,identifier_list列表中的名字前面不能加&。而除identifier_list列表中指明的变量之外的任何隐式捕获的变量都采用引用捕获方式。
[=, identifier_list]identifier_listt(理解为不使用=值捕获的特例)是逗号分割的列表,包含0个或多个来自所在函数的变量,这些变量采用引用捕获,identifier_list列表中的名字前面必须使用&。而除identifier_list列表中指明的变量之外任何隐式捕获的变量都采用值捕获方式。

关于捕获列表的例子,如下:

int main()
{
    int a = 0, b = 1;
    auto f1 = []{ return a; };      // error, 没有捕获外部变量
    auto f2 = [=]{ return a; };     // ok, 值传递方式捕获所有外部变量
    auto f3 = [=]{ return a++; };   // error, a是以赋值方式捕获的,无法修改
    auto f4 = [=]() mutable { return a++; };   // ok, 加上mutable修饰符后,可以修改按值传递进来的拷贝
    auto f5 = [&]{ return a++; };              // ok, 引用传递方式捕获所有外部变量, 并对a执行自加运算
    auto f6 = [a]{ return a+b; };              // error, 没有捕获变量b
    auto f9 = [a,&b]{ return a+(b++); };       // ok, 捕获a, &b
    auto f8 = [=,&b]{ return a+(b++); };       // ok, 捕获所有外部变量,&b
    auto f9 = [&,&b]{ return a+(b++); };       // error, 捕获所有外部变量,不能使用&b,b是特例,和默认的引用捕获不一样,使用值捕获,所以不能加&
    auto f10 = [=,b]{ return a+(b++); };       // error, 捕获所有外部变量,不能使用b,b是特例,和默认的值引用不一样,使用引用捕获,前面必须加&
    return 0;
}

3.4、可以捕获哪些变量

       一般lambda表达式是放置在一个函数中的,即在函数中嵌入的,lambda函数实现体中可以访问其所在函数的局部变量。如果lambda表达式所在函数是一个类的成员函数,则lambda内部也可以访问当前函数所在的类的成员变量,这一点可能很多人不知道。比如:

class Test
{
public:
    int i = 0;   // 类的成员变量

    void func(int x, int y)
    {
        auto x1 = []{ return i; };          // error, 没有捕获外部变量
        auto x2 = [=]{ return i+x+y; };     // ok, 值传递方式捕获所有外部变量
        auto x3 = [=]{ return i+x+y; };     // ok, 引用传递方式捕获所有外部变量
        auto x4 = [this]{ return i; };      // ok, 捕获this指针
        auto x5 = [this]{ return i+x+y; };  // error, 没有捕获x, y
        auto x6 = [this, x, y]{ return i+x+y; };// ok, 捕获this指针, x, y
        auto x9 = [this]{ return i++; };        // ok, 捕获this指针, 并修改成员的值
    }
};

3.5、lambda表达式给编程带来的便利

       lambda表达式的引入,给我们编程带来了很大的便利,可以大大简化我的编码。比如我们在使用STL容器的find_if、count_if、sort等算法函数时,我们要传入条件函数或者比较函数,这些函数直接用lambda表达式去实现,要方便很多。
       在以前没有lambda表达式时,这些条件函数和比较函数,需要在函数外实现,要不定义成全局函数,要不定义成静态函数,甚至还要定义一些辅助的全部或者静态变量。比如有一个存放设备信息的结构体,然后有个存放设备信息的vector列表,给该列表简单的初始化一下:

// 设备信息结构体
typedef struct tagDeviceInfo
{
    char szDeviceId[64];   // 设备id
    char szDeviceName[64]; // 设备名称
    int nDevType;          // 设备类型

public:
    tagDeviceInfo(){ memset(this, 0, sizeof(tagDeviceInfo)); }
}TDeviceInfo;

// 设备管理类
class CDeviceManage
{
    CDeviceManage();
    ~CDeviceManage();

    void InitDeviceList();

private:
    vector<TDeviceInfo> m_vtDevList;
}

// 初始化设备列表(仅用于测试,随意初始化了一些数据)
void CDeviceManage::InitDeviceList()
{
    for ( int i = 0; i < 10; i++ )
    {
        TDeviceInfo tDevInfo;

        char szBuf[128] = { 0 };
        sprintf(szBuf, "E40CF3E4-CC2B-437F-A4B9-65F2D5BD071%d", i);
                                   strcpy(tDevInfo.szDeviceId, szBuf);

        CUIString strName;
        sprintf(szBuf, "设备%d", i);
                                   strcpy(tDevInfo.szDeviceName, szBuf);

        m_vtDevList;.push_back(tDevInfo);
    }
}

假设我们在CDeviceManage::FindTest成员函数中调用STL的算法函数find_if到m_vtDevList列表中搜索设备Guid(szDeviceId)为E40CF3E4-CC2B-437F-A4B9-65F2D5BD0715的设备信息。如果不使用lambda表达式,则要将条件函数实现在CDeviceManage类外部定义成全局函数,同时要将存放目标id的变量定义成全局的,如下所示:

char* s_lpszTargetDevId = ""; // 定义成静态变量

// 将条件匹配函数定义在类CDeviceManage外部,定义成全局函数
BOOL MatchFunc(TDeviceInfo& tDevInfo)
{
    return strcmp( s_lpszTargetDevId,tDevInfo.szDeviceId ) == 0;
}

bool CDeviceManage::FindTest()
{
     // 对静态变量s_pTargetDevId进行赋值
     s_lpszTargetDevId = "E40CF3E4-CC2B-437F-A4B9-65F2D5BD0715";
     vector<TDeviceInfo>::iterator itor = std::find_if(m_vtDevList.begin(), m_vtDevList.end(), MatchFunc);
     if ( itor != m_vtDevList.end() )
     {
         // 找到对应的设备信息,进行后续处理的代码省略
         // ......

         return true;
     }

     return false;
}

上述代码实现的相对麻烦一些。
       如果使用lambda表达式,代码要简洁很多,不用将条件匹配函数定义成全局函数,也不用定义静态辅助变量s_lpszTargetDevId,用lambda表达式实现的代码如下:

bool CDeviceManage::FindTest()
{
    char* lpszTargetDevId = "E40CF3E4-CC2B-437F-A4B9-65F2D5BD0715";
    vector<TDeviceInfo>::iterator itor = std::find_if(vtDevList.begin(), vtDevList.end(), [=](const TDeviceInfo& tDevInfo){
    return strcmp(lpszTargetDevId, tDevInfo.achDeviceId) == 0; } );

    // 后续代码省略
    // ......
}

        至于为什么要使用STL的算法函数去搜索, 因为STL的算法函数的效率比较高,比直接去for循环遍历效率会高很多!如果STL列表中存放了大量的数据,数据搜索就要讲究效率了,就要使用到STL算法函数,比直接for循环变量要高上几个数量级,这点在项目中对比测试过!关于使用STL算法函数提高搜索效率的文章,可以参见我之前写的文章:

VC++调用STL算法函数有效提升STL列表的搜索速度(附源码)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/123943134VC++如何使用C++ STL标准模板库中的算法函数(附源码)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125486409

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

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

相关文章

Qt QTreeWidge解决setItemWidget后,导致复选框失效

一、问题&#xff1a; QTreeWidget某一项加上itemWidget后&#xff0c;导致复选框失效问题 二、解决方法 将要加上的widget控件加到该项的后续的列&#xff0c;即控件跟复选框不同一列 三、具体代码 QTreeWidget* treeW new QTreeWidget; treeW->setColumnCount(2); /…

2023最全的性能测试种类介绍,这6个种类特别重要!

系统的性能是一个很大的概念&#xff0c;覆盖面非常广泛&#xff0c;包括执行效率、资源占用、系统稳定性、安全性、兼容性、可靠性、可扩展性等&#xff0c;性能测试就是描述测试对象与性能相关的特征并对其进行评价而实施的一类测试。 性能测试是一个统称&#xff0c;它其实包…

微信小程序自动化测试pytest版工具使用方法

-mini https://github.com/zx490336534/pytest-mini 微信小程序自动化测试pytest插件/工具 基于MiniTest进行pytest改造 使用方法 准备测试小程序 根据miniprogram-demo项目介绍运行一次项目 成功运行后关闭 安装&更新 pip install pytest-mini --upgrade引入插件…

5、Nginx 配置实例-负载均衡

文章目录 5、Nginx 配置实例-负载均衡5.1 实现效果5.2 准备工作5.3 实验代码5.3.1、轮询&#xff08;默认&#xff09;5.3.2、weight5.3.3、ip_hash5.3.4、fair&#xff08;第三方&#xff09; 【尚硅谷】尚硅谷Nginx教程由浅入深 志不强者智不达&#xff1b;言不信者行不果。 …

选择直接去外企,结果跟我预想的有点不一样。

作者&#xff1a;阿秀 InterviewGuide大厂面试真题网站&#xff1a;https://top.interviewguide.cn 这是阿秀的第「302」篇原创 小伙伴们大家好&#xff0c;我是阿秀。 一般来说计算机专业的出路有以下几种&#xff1a;互联网、国企、银行等事业编&#xff0c;也有相当一部分人…

Spring 基础概念和核心思想

目录 一、Spring 是什么&#xff1f; 1、认识 loC 2、理解 Spring loC 3、DI 概念说明 一、Spring 是什么&#xff1f; 我们通常所说的 Spring 指的是 Spring Framework&#xff08;Spring 框架&#xff09;&#xff0c;它是⼀个开源框架&#xff0c;有着活跃而庞大的社区…

Java集合(Collection、Iterator、Map、Collections)概述——Java第十三讲

前言 本讲我们将继续来讲解Java的其他重要知识点——Java集合。Java集合框架是Java编程语言中一个重要的部分,它提供了一套预定义的类和接口,供程序员使用数据结构来存储和操作一组对象。Java集合框架主要包括两种类型:一种是集合(Collection),存储一个元素列表,…

Linux系统中驱动框架基本概述

大家好&#xff0c;今天跟大家简单聊聊Linux驱动框架以及概述。 一、驱动程序三种基本类型 字符设备(Char Device) 块设备(Block Device) 网络设备(Net Device) 二、设备驱动程序功能 对设备初始化和释放 把数据从内核传送到硬件和从硬件读取数据 读取应用程序传送给设备…

软件设计模式(四):观察者、组合、享元模式

前言 在这篇文章中&#xff0c;荔枝将会梳理软件设计模式中有关观察者模式、组合模式和享元模式的内容。其中组合模式和享元模式比较简单&#xff0c;重点需要理解观察者模式的机制以及为什么该模式实现了对象之间的松耦合。希望荔枝的梳理能对需要的小伙伴有帮助~~~ 文章目录 …

光伏并网双向计量表ADL400

安科瑞 华楠 ADL400 导轨式多功能电能表&#xff0c;是主要针对电力系统&#xff0c;工矿企业&#xff0c;公用设施的电能统计、 管理需求而设计的一款智能仪表&#xff0c;产品具有精度高、体积小、安装方便等优点。集成常见电 力参数测量及电能计量及考核管理&#xff0c;…

python回调函数之获取jenkins构建结果

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 需求背景&#xff1a; 现在用jenkins构建自动化测试&#xff08;2个job&#xff09;&#xff0c;公司现将自动化纳入到发布系统 要求每次构建成功之后&am…

Neo4j图数据库实践——基于知识图谱方法开发构建猪类养殖疾病问答查询系统

Neo4j是一个开源的、高性能的图形数据库。它被设计用于存储、检索和处理具有复杂关系的大规模数据。与传统的关系型数据库不同&#xff0c;Neo4j使用图形结构来表示数据&#xff0c;其中节点表示实体&#xff0c;边表示实体之间的关系。这使得Neo4j在处理关系密集型数据时非常强…

【C++基础】5. 常量

文章目录 【 1. 常量的分类 】1.1 整型常量1.2 浮点常量1.3 字符常量1.4 字符串常量1.5 布尔常量 【 2. 常量的定义 】2.1 #define 预处理器2.2 const 关键字 常量 是固定值&#xff0c;在程序执行期间不会改变。这些固定的值&#xff0c;又叫做字面量。常量可以是任何的基本数…

【Flutter】Flutter 使用 flutter_dotenv 管理环境变量

【Flutter】Flutter 使用 flutter_dotenv 管理环境变量 文章目录 一、前言二、flutter_dotenv包简介三、安装和基本使用1. 安装flutter_dotenv2. 导入flutter_dotenv3. 使用flutter_dotenv 四、高级使用方法1. 变量引用2. 合并3. 在测试中使用 五、完整示例六、总结 一、前言 …

Codeforces-Round-895-Div-3

A. Two Vessels 题目翻译 你有两个装有水的容器。第一个容器含有 a a a克水&#xff0c;第二个容器含有 b b b克水。这两艘船都非常大&#xff0c;可以容纳任意数量的水。 您还有一个空杯子&#xff0c;最多可容纳 c c c克水。 一次&#xff0c;您可以从任何容器中舀出多 c…

UNet pytorch 胎教级介绍 使用DRIVE眼底血管分割数据集进行入门实战

同门的学妹做语义分割&#xff0c;于是打算稍微研究一下&#xff0c;最后的成果就是这篇文章&#xff0c;包括使用数据集进行测试&#xff0c;以及每一个部分的代码&#xff0c;还有一些思考改动和经验。 充分吸收本文知识你需要有pytorch的基础 U-net U-Net&#xff1a;深度…

您的密码是如何落入坏人之手的?

对于我们大多数人来说&#xff0c;密码只是无数在线服务最常用的身份验证方法。但对于网络犯罪分子而言&#xff0c;它的意义远不止于此——进入他人生活的捷径、至关重要的作案工具以及可以出售的商品。 对于我们大多数人来说&#xff0c;密码只是无数在线服务最常用的身份验证…

【MySql】数据库的聚合查询

写在最前面的话 哈喽&#xff0c;宝子们&#xff0c;今天给大家带来的是MySql数据库的聚合查询。在前面CRUD章节我们学习了表达式查询&#xff0c;表达式查询是针对列和列之间进行运算的&#xff0c;那么如果想在行和行之间进行运算&#xff0c;那么就需要用到聚合查询。聚合查…

计组+系统01:思维导图10分钟复习 I/O系统与中断

&#x1fa99;前言 考研笔记整理&#xff0c;纯复习向&#xff0c;思维导图基本就是全部内容了&#xff0c;不会涉及较深的知识点~~&#x1f95d;&#x1f95d; 第1版&#xff1a;查资料、画思维导图~&#x1f9e9;&#x1f9e9; 编辑&#xff1a; 梅头脑 BING AI 参考用书…

Deep Java Library(六)DJLServing自定义模型,自定义Translator注意事项

DJLServing自定义模型中自定义Translator注意事项需要仔细读一下DJLServing源码中的ServingTranslatorFactory类&#xff0c;&#xff0c;一开始不了解以为DJLServing选择Translator像玄学&#xff0c;后来看了像迷宫一样ServingTranslatorFactory类大致明白了&#xff0c;以下…