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

目录

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, 没有捕获变量bauto f9 = [a,&b]{ return a+(b++); };       // ok, 捕获a, &bauto f8 = [=,&b]{ return a+(b++); };       // ok, 捕获所有外部变量,&bauto 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, yauto x6 = [this, x, y]{ return i+x+y; };// ok, 捕获this指针, x, yauto 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];   // 设备idchar 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.mzph.cn/news/76923.shtml

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

相关文章

PyTorch实现注意力机制及使用方法汇总,附30篇attention论文

还记得鼎鼎大名的《Attention is All You Need》吗&#xff1f;不过我们今天要聊的重点不是transformer&#xff0c;而是注意力机制。 注意力机制最早应用于计算机视觉领域&#xff0c;后来也逐渐在NLP领域广泛应用&#xff0c;它克服了传统的神经网络的的一些局限&#xff0c…

JAVAEE初阶相关内容第十一弹--多线程(进阶)

目录 一、常见的锁策略 1乐观锁VS悲观锁 1.1乐观锁 1.2悲观锁 2.轻量级锁VS重量级锁 2.1轻量级锁 2.2重量级锁 3.自旋锁VS挂起等待锁 3.1自旋锁 3.2挂起等待锁 4.互斥锁VS读写锁 4.1互斥锁 4.2读写锁 5.公平锁VS非公平锁 5.1公平锁 5.2非公平锁 6.可重入锁VS不…

MemJam: A false Dependency attack against constant-time crypto implementations

作者&#xff1a;A. Moghimi, J. Wichelmann, T. Eisenbarth, and B. Sunar. 发布&#xff1a;International Journal of Parallel Programming 时间&#xff1a;Aug 2019. 笔记&#xff1a; 缓存定时攻击 1、攻击原理 共享缓存存在定时侧信道的风险&#xff08;例如在处理…

设计模式课件

设计模式 创建型设计模式的分类&#xff0c;定义结构型设计模式的分类&#xff0c;定义行为型设计模式的分类&#xff0c;定义 设计模式的分类&#xff0c;在23种设计模式中&#xff0c;每一种属于哪一种的设计模式设计模式的应用场景设计模式的图形&#xff08;考察较少&#…

华为云云耀云服务器L实例评测|带宽,磁盘,CPU,内存以及控制台监控测试

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;AWS/阿里云资深使用…

Python从零到一构建项目

随着互联网的发展&#xff0c;网络上的信息量急剧增长&#xff0c;而获取、整理和分析这些信息对于很多人来说是一项艰巨的任务。而Python作为一种功能强大的编程语言&#xff0c;它的爬虫能力使得我们能够自动化地从网页中获取数据&#xff0c;大大提高了效率。本文将分享如何…

【技术分享】RK Android11系统SD卡启动方法

本文基于Purple Pi OH 3566主板&#xff0c;介绍Android11源码的修改&#xff0c;获得可从SD卡启动的Android11系统镜像。 Purple Pi OH作为一款兼容树莓派的开源主板&#xff0c;采用瑞芯微RK3566 (Cortex-A55) 四核64位超强CPU,主频最高达1.8 GHz,算力高达1Tops&#xff0c;…

海外商城小程序如何开发

随着全球化的发展和人们对跨境购物的需求逐渐增加&#xff0c;海外商城小程序成为了众多电商平台的重要组成部分。本文将深入探讨如何搭建海外商城小程序&#xff0c;从技术实现到用户体验设计&#xff0c;为开发者提供专业且有深度的思考&#xff0c;以帮助他们打造出色的跨境…

手写RPC框架--13.优雅停机

优雅停机 优雅停机a.优雅停机概述b.服务端实现优雅停机c.客户端实现优雅停机d.优雅启动 优雅停机 a.优雅停机概述 当我们快速关闭服务提供方时&#xff0c;注册中心感知、以及通过watcher机制通知调用方一定不能做到实时&#xff0c;一定会有延时&#xff0c;同时我们的心跳检…

如何把视频格式转换成mp4?支持的格式种类非常多。

如何把视频格式转换成mp4&#xff1f;随着计算机技术的迅猛发展&#xff0c;我们现在有着各种各样的视频格式可供选择&#xff0c;平时我们都知道的mp4、flv、mov、mkv、avi、wmv等&#xff0c;都是视频格式的种类。其中&#xff0c;MP4是一种具有极佳兼容性的视频格式&#xf…

TikTok魔法:揭秘那个“神奇”的算法

嘿&#xff0c;你是不是每次打开TikTok&#xff0c;都感觉这个应用好像了解你的内心世界一样&#xff1f;没错&#xff0c;背后有一个不为人知、神奇的算法正在起作用&#xff0c;让你欲罢不能。在这篇文章中&#xff0c;我们将揭开TikTok算法的神秘面纱&#xff0c;看看它是如…

车机多用户系统的适配问题

多用户问题出现背景 记录一下多用户的适配问题&#xff1a; 背景是system/app下面新push了两个apk&#xff0c;一个是我们的业务场景apk一个是虚拟车CarService服务的apk&#xff0c;我们的apk需要链接CarService服务通过AIDL通信。 下面这两张图是未roo的情况&#xff08;当…

Python之Xlwings操作excel

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、xlwings简介二、安装与使用1.安装2.使用3.xlwings结构说明 二、xlwings对App常见的操作App基础操作工作簿的基础操作工作表的基础操作工作表其他操作 读取单元格…

MOV导出序列帧并在Unity中播放

MOV导出序列帧并在Unity中播放 前言项目将MOV变成序列帧使用TexturePacker打成一个图集将Json格式精灵表转换为tpsheet格式精灵表导入Unity并播放总结 鸣谢 前言 收集到一批还不错的MG动画&#xff0c;想要在Unity中当特效播放出来&#xff0c;那首先就得把MOV变成序列帧&…

堆排序与TopK问题

一、堆排序 堆排序(升序)&#xff1a;堆排序的思想就是先用数组模拟建大堆&#xff0c;然后把根结点与最后一个结点值交换&#xff0c;最后一个结点的值就是最大值&#xff0c;然后再把前(n-1)个元素重新建大堆&#xff0c;然后根结点与最后一个结点值交换&#xff0c;就找出了…

小红书笔记爬虫

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

LNMP架构搭建论坛

目录 一、LNMP简介&#xff1a; 二、LNMP搭建&#xff1a; 1.前提准备&#xff1a; 关闭防火墙和安全机制&#xff1a; 2.编译安装nginx&#xff1a; 3.编译安装mysql&#xff1a; 3.1 安装依赖环境&#xff1a; 3.2 创建mysql运行用户&#xff1a; 3.3 编译安装&#xff1a…

c语言练习题52:写一个函数判断当前机器是大端还是小端

代码&#xff1a; #include<stdio.h> int check_sys() {int a 1;return *(char*)&a;//小端retrun 1 大端return 0&#xff1b; } int main() {if (check_sys() 1) {printf("小端\n");}elseprintf("大端\n"); } 这里首先取a的地址&#xff0c…

原型链(一定要搞懂啊!!!>-<)

一、概念 1、prototype 习惯称作“显示原型”&#xff0c;只有构造函数才有的属性。 2、构造函数 能用new关键字创建的对象叫做构造函数 3、__proto__ 习惯称作“隐式原型”&#xff0c;每一个实例都有的属性&#xff0c;该属性指向他构造函数的“显示原型”。Function对象…

2.14 PE结构:地址之间的转换

在可执行文件PE文件结构中&#xff0c;通常我们需要用到地址转换相关知识&#xff0c;PE文件针对地址的规范有三种&#xff0c;其中就包括了VA&#xff0c;RVA&#xff0c;FOA三种&#xff0c;这三种该地址之间的灵活转换也是非常有用的&#xff0c;本节将介绍这些地址范围如何…