看懂开源项目,你得熟悉这几个 C++11 新特性

点击蓝字

64386150a54f4f49c6accace54010bc9.png

关注我们

C++11 中增加了许多的新特性。

在本文中,我们来聊一下 lambda 表达式,闭包,std::function以及std::bind。

lambda 表达式

C++11 中新增了 lambda 表达式这一语言特性。lambda 表达式可以让我们快速和便捷的创建一个 “函数”。

下面是lambda表达式的语法:

[ capture-list ] { body } [ capture-list ] ( params ) { body } [ capture-list ] ( params ) -> ret { body } [ capture-list ] ( params ) mutable exception attribute -> ret { body }

这其中:

  • capture-list 是需要捕获的变量列表,用逗号分隔。其详细说明见下文。

  • params 是 lambda 表达式需要的参数列表,写法和函数参数一样,不过这里不支持默认参数。

  • ret 指明了lambda表达式的返回值。通过return语句,如果编译器能够推断出返回值的类型。或者表达式没有返回值,“-> ret” 可以省略。

  • body 函数体。

  • mutable 当捕获列表是以复制(见下文)的形式捕获时,默认这些复制的值是 const 的,除非指定了 mutable。

  • exception 提供了异常的说明。

  • attribute 对于attribute的描述可以参见这里:http://en.cppreference.com/w/cpp/language/attributes,这里不多说明。下面,我们通过经典的 Hello World 示例来看一下 lambda 表达式:

auto lambda1 = [] {std::cout << "Hello, World!\n";};
lambda1();

这个 lambda 表达式将打印出字符串 “Hello, World!” 。同时,我们将这个表达式赋值给 “lambda1” 这个变量,然后像调用函数一样,调用这个 lambda 表达式。

使用 lambda 表达式,可以让我们省却定义函数的麻烦,以 inline 的方式写出代码,这样的代码通常更简洁。并且,由于阅读代码时不用寻找函数定义,这样的代码也更易读。

下面,我们来看另外一个例子。这个例子的需求是:分两次,打印出一个 vector 集合中,所有:

1. 模 5 = 0 2. 大于 20 的数字。

现假设已有这个集合的定义如下:

vector<int> numbers { 1, 2, 3, 4, 5, 10, 15, 20, 25, 35, 45, 50 };

我们最先想到的方法自然是定义两个函数,分别按照上面的要求打印出需要的数字,它们的定义如下:

void printNumber1(vector<int>& numbers) {for (const int& i : numbers) {if (i % 5 == 0) {cout<<i<<endl;}}
}void printNumber1(vector<int>& numbers) {for (const int& i : numbers) {if (i % 5 == 0) {cout<<i<<endl;}}
}

然后,我们在需要的地方,调用它们:

printNumber1(numbers);
printNumber2(numbers);

这里逻辑上并没有问题,但是:

  1. 这里我们必须先定义这个函数,才能使用。而这样的函数,可能实际上我们只会使用一次。

  2. 当工程大到一定程度,我们可能不记得每个函数的实现(所以函数命名很重要,原谅我这里给函数起了很含糊的名字,你在实际上工程中,请不要这样做),为了知道每个函数的实现,我们不得不查看函数的定义,这无疑给代码的阅读造成了一定的麻烦。

下面,我们来看看使用lambda表达式如何改善上面说的问题。使用 lambda 表达式,我们可以这样写:

for_each(numbers.begin(), numbers.end(), [] (int i) {if(i % 5 == 0) {cout<<i<<endl;}
});for_each(numbers.begin(), numbers.end(), [] (int i) {if(i > 20) {cout<<i<<endl;}
});

这里,我们不用单独定义函数,直接以 inline 的方式解决了问题。并且,这段代码一气呵成,你很直观的看到了执行的逻辑。

下面,我们再详细看一下 lambda 表达式中的捕获列表的语法,它可能是以下几种情况中的一种:

  • [] 不捕获任何变量

  • [&] 以引用的方式捕获所有变量

  • [=] 以复制的方式捕获所有变量

  • [=, &foo] 以引用的方式捕获foo变量,但是以复制的方式捕获其他变量

  • [bar] 以复制的方式捕获bar变量,不再捕获任何其他变量

  • [this] 捕获this指针

下面,我们再以一个例子说明捕获列表的用法。这里,我们的需求是:打印出一个 vector的所有数字之和

同样的,我们先以函数的方式来解决这个问题,这个函数的定义可以是这样的:

void printSum(vector<int>& numbers) {int sum = 0;for (const int& i : numbers) {sum += i;}cout<<sum<<endl;
}

然后,我们在需要的地方调用这个函数:

vector<int> numbers { 1, 2, 3, 4, 5, 10, 15, 20, 25, 35, 45, 50 };
printSum (numbers);

而假设我们用 lambda 表达式来写,这样写就可以了:

vector<int> numbers { 1, 2, 3, 4, 5, 10, 15, 20, 25, 35, 45, 50 };
int sum = 0;
std::for_each(numbers.begin(), numbers.end(), [&sum] (const int& i) { sum += i;});
cout<<sum<<endl;

这里,我们用

[&sum] 以引用的形式捕获了 sum 这个变量,并且在 lambda 表达式中修改了这个变量。这样写,是不是比定义函数的方式简洁了很多?对于这种,能够捕获其定义时上下文变量的函数,我们称之为 “闭包”,下文还将提到。

std::function

上文中,对于分两次,打印出一个vector集合中,所有: 1. 模 5 = 0 2. 大于 20 的数字。 这个需求,我们的实现其实还不够好。

回头看一下 printNumber1 和 printNumber2 这两个函数,这两个函数大部分都是重复的:它们都需要遍历集合,都需要做 if 判断,然后打印出结果。实际上,我们在项目中经常遇到这个的问题:两(多)个函数,有大部分的代码都是一样的,其中只有一两行代码有不一样的地方。 其实,我们可以对这个不一样的地方,再做一个抽象,把它们共通起来。

具体到这个例子就是:无论是 “模 5 = 0” 还是 “大于 20” 都是满足“某种条件”。而很自然的会想到,我们是否可以通过一个类似这样的函数来做这个判断:bool func(int i) 然后实现两个函数,通过函数指针的形式来完成判断就好了。但是,我们马上又意识到,这两个函数会很小,并且也是只会用一遍而已,定义一个函数又太“浪费”了。很自然的,我们就会想 lambda。但是,lambda 似乎没法转成函数指针。。。

C++11中,提供了一个通用的描述方法,就是 std::function。std::function可以 hold 住任何可以通过“()”来调用的对象,包括:

  • 普通函数

  • 成员函数

  • lambda

  • std::bind(见下文)后的结果

std::function的语法是这样:

template <class Ret, class... Args> class function<Ret(Args...)>;例如:function<bool (int)> filter

就表达了我们前面需要的那个函数:这个函数接受一个 int 值作为参数,同时返回一个 bool 作为判断的结果。但同时,我们可以用 lambda 表达式直接传递进去。

因此,上面的代码可以改写成这样:

void printNumber(vector<int>& number, function<bool (int)> filter) {for (const int& i : number) {if (filter(i)) {cout<<i<<endl;}}
}

然后在需要的地方,这样调用即可:

printNumber(numbers, [] (int i){ return i % 5 == 0;});
printNumber(numbers, [] (int i){ return i > 20;});

这种做法,是不是又简洁了不少?

闭包

前面提到了 “闭包” 这个词,这里我们来聊一下闭包。

下面是维基百度对于闭包的定义:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

简单来说:闭包可以记忆住创建它时候的那些变量。下面,我们再通过一个例子来说明。现在,假设我们的需求是:获取一个集合中最小和最大值,并在稍后的时候(可能是另外一个函数中)打印它们。

这里,我们常规的做法通常是:通过一个函数获取集合的最大,最小值,然后保存住,最后在需要的时候访问这两个值,然后打印它们。这样做就会需要解决:如果保存和传递最大,最小这两个值。但实际上,这里我们可以考虑用闭包来实现这个功能,让闭包把最大,最小两个值捕获下来,然后在需要的地方调用就可以了。

请看一下下面这段代码:

void getMinMax(vector<int>& number, function<void ()>& printer) {int min = number.front();int max = number.front();for (int i : number) {if (i < min) {min = i;}if (i > max) {max = i;}}printer = [=] () {cout << "min:" <<min<< endl;cout << "max:" << max << endl;};
}

这里,我们通过 function<void ()>& printer(如果你看不懂 function,请看上文)传递出这个闭包。然后,在需要的地方,这样即可:

function<void()> printer;
getMinMax(numbers, printer);
......printer();

这里的 printer 其实是我们前面从 getMinMax 函数出传出的闭包,这个闭包捕获了 min 和 max。我们直接传递这个闭包给需要的地方使用,而不用传递裸的两个数值,是不是优雅的不少?

std::bind

下面,我们再改进一下需求,假设我们要

打印出 vector中,20<x<40 范围内的值<="" strong="" style="font-size: inherit; color: inherit; line-height: inherit;">,该怎么办?毕竟,

bool isBetween(int i, int min, int max)

这个函数可没法对应上

function<bool (int)> filter

啊!参数数量就不一样嘛。这个时候,我们可以用 std::bind。

std::bind的语法是这样的:

template <class Fn, class... Args> bind (Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args> bind (Fn&& fn, Args&&... args);

std::bind 可以将调用函数时的部分参数先指定好,留下一部分在真正调用的时候确定。(当然,你也可以直接指定全部参数,在调用时不再指定。)

这里,isBetween中,最小,最大值其实我们是确定了的,即:20 和40。而不确定的,其实是真正待判断的数字本身,那么我们就可以这么做:

std::bind(isBetween, placeholders::_1, 20, 40);

placeholders::_1 的意思是,这里是一个占位符,在调用的时候,将实际传递的第一个参数放到这里。占位符的数量可以是任意多的,像这样:std::placeholders::_1, std::placeholders::_2, …, std::placeholders::_N。

于是乎,对于 打印出vector中,20

这个需求,我们在不修改 printNumber 函数的基础上,通过定义一个 isBetween 函数:

bool isBetween( int i, int min, int max) {return i >= min && i <= max;
}

然后,再这样就搞定了:

function<bool(int)> filter = std::bind(isBetween, placeholders::_1, 20, 40);
printNumber(numbers, filter);

当然,你甚至可以直接把这里的两行写成一行。

如果你不明白这段代码,请再看一下 printNumber 函数的定义:

void printNumber(vector<int>& number, function<bool (int)> filter) {for (const int& i : number) {if (filter(i)) {cout<<i<<endl;}}
}

这里其实调用了 filter(i) 这个函数对象,而这个函数对象只接受一个 int 值作为参数,然后返回一个 bool 值。

function<bool(int)> filter = std::bind(isBetween, placeholders::_1, 20, 40);

绑定之后,只缺一个 int 型参数,所以正好对应得上。

如果不过瘾,我们再来看一个 bind 的例子。我们常常需要在程序中,调用一些用户传过来的回调函数。而在回调函数中,用户常常会需要记录一些状态,于是常常希望通过一个对象的成员函数传过来作为回调函数。

但是在 C++ 中,这样做是很麻烦的一个事情。因为,回调函数的类型我们很难定义。但是,结合 std::function 和 std::bind,一切变得容易多了。

结合前面的例子,现在就假设我们的回调函数是需要打印集合中的最大、最小值。这里假设我们是通过一个类来记录和打印值的,这个类的定义是这样的:

class Printer {
private:int min, max;
public:Printer(int x, int y) {min = x;max = y;}void print() {cout << "min:" << min << endl;cout << "max:" << max << endl;}
};

由于回调函数不需要参数,因此使用回调函数的代码是这样的:

void usingCallback(function<void ()> print) {print();
}

然后,我们可以通过下面的方法来调用 print 函数

Printer printer = Printer(10, 50);
function<void ()> print = bind(&Printer::print, printer);
usingCallback(print);

成员函数其实是类中的方法绑定到一个对象上,然后执行调用。这里的代码很直观的表达了这个关系。

lambda表达式是如何实现的

lambda 表达式是如何实现的呢?

其实是编译器为我们了创建了一个类,这个类重载了(),让我们可以像调用函数一样使用。

而对于捕获变量的 lambda 表达式来说,编译器在创建类的时候,通过成员函数的形式保存了需要捕获的变量。

似乎也没有什么神奇的地方。但正是由于编译器帮我们实现了细节,使我们的代码变得优雅和简洁了许多。

*声明:本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

939f8aaeab7fffe8b0eca5cbfd6e0e8b.png

11cc05bde9e8906bc875b8de6895fc96.gif

戳“阅读原文”我们一起进步

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

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

相关文章

5元素升级android6,【五元素ifive X.7】无障碍升级,ifveX详细升级固件教程,快为爱机升级吧。...

android系统的乐趣就是可以随意的刷机&#xff0c;所以拿到如此高配置的ifiveX也是想随时体验最新的android系统。而很多朋友可能只会用设备&#xff0c;刷机这种比较技术性的动作就不太会弄了。也就在这周&#xff0c;才帮同事刷RUU把已经无法启动的G12刷了回来。所以刷机还是…

昆仑通态复制的程序可以用吗_昆仑通态专题(七):MCGS组态软件的设备窗口...

点击上方蓝色字体&#xff0c;关注我们设备窗口是MCGS嵌入版组态软件系统的重要组成部分&#xff0c;在设备窗口中建立系统与外部硬件设备的连接关系&#xff0c;使系统能够从外部设备读取数据并控制外部设备的工作状态&#xff0c;实现对工业过程设备的实时监控与操作。01 设备…

嵌入式C语言程序调试和宏使用的技巧

点击蓝字关注我们01.调试相关的宏在Linux使用gcc编译程序的时候&#xff0c;对于调试的语句还具有一些特殊的语法。gcc编译的过程中&#xff0c;会生成一些宏&#xff0c;可以使用这些宏分别打印当前源文件的信息&#xff0c;主要内容是当前的文件、当前运行的函数和当前的程序…

matlab中欠定方程组超定方程组_《数值天气预报》:球坐标系中的基本方程组

人们是如何预报天气的&#xff1f;目前的预报方法主要有两种&#xff1a;一种是基于由各种探测资料绘制的天气图&#xff0c;结合历史资料进行分析预测&#xff1b;另一种是基于大气方程组&#xff0c;利用数值解法对其进行求解&#xff0c;从而得到未来时刻的大气状态。后者就…

浏览器总是跳转到缓存界面_跳转到企业缓存之前要考虑的事项

浏览器总是跳转到缓存界面介绍 关系数据库事务是ACID &#xff0c;强大的一致性模型简化了应用程序开发。 由于启用Hibernate缓存是一项配置 &#xff0c;因此&#xff0c;只要数据访问层开始出现性能问题&#xff0c;就转向缓存非常吸引人。 添加缓存层确实可以提高应用程序性…

CryptoTab 服务器_如何架设FTP服务器,如何架设FTP服务器,具体架设方法

FTP服务器&#xff0c;则是在互联网上提供存储空间的计算机&#xff0c;它们依照FTP协议提供服务。 FTP的全称是File Transfer Protocol(文件传输协议)。顾名思义&#xff0c;就是专门用来传输文件的协议。简单地说&#xff0c;支持FTP协议的服务器就是FTP服务器。那么&#xf…

C语言和C++的区别和联系

点击蓝字关注我们C语言和C到底是什么关系&#xff1f;首先C和C语言本来就是两种不同的编程语言&#xff0c;但C确实是对C语言的扩充和延伸&#xff0c;并且对C语言提供后向兼容的能力。对于有些人说的C完全就包含了C语言的说法也并没有错。C一开始被本贾尼斯特劳斯特卢普&#…

hibernate语句_如何优化Hibernate EllementCollection语句

hibernate语句介绍 Hibernate支持三种数据映射类型 &#xff1a; 基本 &#xff08;例如String&#xff0c;int&#xff09;&#xff0c; Embeddable和Entity 。 通常&#xff0c;数据库行被映射到Entity &#xff0c;每个数据库列都与一个基本属性相关联。 当将多个字段映射组…

C++ 虚函数表剖析

点击蓝字关注我们一、概述为了实现C的多态&#xff0c;C使用了一种动态绑定的技术。这个技术的核心是虚函数表&#xff08;下文简称虚表&#xff09;。本文介绍虚函数表是如何实现动态绑定的。二、类的虚表每个包含了虚函数的类都包含一个虚表。我们知道&#xff0c;当一个类&a…

aix pax_通过Pax考试对JBoss Fuse 6.x进行集成测试,第一部分

aix paxJBoss Fuse是一个功能强大的分布式集成平台&#xff0c;具有内置功能&#xff0c;可用于针对集成的微服务部署进行集中式配置管理&#xff0c;服务发现&#xff0c;版本控制&#xff0c;API网关&#xff0c;负载平衡&#xff0c;故障转移等。 JBoss Fuse 6.x构建在Fabri…

android王者调不了界面,王者荣耀登录界面怎么改?登录界面更改教程[多图]

王者荣耀登录界面怎么改&#xff1f;许多玩家都想更改自己登陆的界面&#xff0c;但是都不清楚&#xff0c;下面就让安卓乐园小编为大家带来&#xff0c;登录界面更改教程。王者荣耀登录界面怎么改&#xff1f;1、安卓手机打开文件管理&#xff0c;找到根目录下Android/data/co…

y空间兑换代码_Python爬虫实战:QQ空间全自动点赞工具

QQ空间自动点赞前景提要目标确定分析介绍登陆获取cookie寻找XML寻找可变参数获取第一个空间动态寻找点赞所需的URL寻找可变参数功能提升到秒赞全部代码最后还是希望你们能给我点一波小小的关注。奉上自己诚挚的爱心私信小编01即可获取大量Python学习资料前景提要因为我周围的小…

学点 STL C++ 无序容器和元组

点击蓝字关注我们无序容器我们已经熟知了传统 C 中的有序容器 std::map/std::set&#xff0c;这些元素内部通过红黑树进行实现&#xff0c; 插入和搜索的平均复杂度均为 O(log(size))。在插入元素时候&#xff0c;会根据 < 操作符比较元素大小并判断元素是否相同&#xff0c…

html中的文档格式及举例,跟我一起从零开始学习WebAssembly(三)、最简单的例子hello world(使用自定义HTML模板)...

文章目录创建C代码片创建我们的自定义HTML模板文件编译运行实例有时我们想要使用我们自定义HTML模板。让我们来看看我们如何做到这一点。创建C代码片首先&#xff0c;创建一个名为hello2的目录。其次&#xff0c;在该目录下创建一个名为hello2.c文件。并将以下C代码保存在文件中…

设计模式适配器模式_21世纪的设计模式:适配器模式

设计模式适配器模式这是我的演讲的第三部分&#xff0c;“ 21世纪的设计模式” 。 适配器模式桥接世界。 在一个世界中&#xff0c;我们有一个概念的界面。 在另一个世界&#xff0c;我们有不同的界面。 这两个接口有不同的用途&#xff0c;但有时我们需要进行转移。 在编写良…

excel单元格斜线_掌握这20个Excel技巧,小白轻松变大神

掌握一些Excel小技巧&#xff0c;可以让你的工作效率翻倍&#xff0c;原本半个小时才能搞定的&#xff0c;现在几秒就可以轻松搞定。1、调整单元格大小选中表格&#xff0c;将光标移到表格顶部边框处&#xff0c;等其变成双向箭头即可移动。2、快速插入空行选中行&#xff0c;按…

html鼠标滚动效果代码,JS+CSS实现大气清新的滑动菜单效果代码

本文实例讲述了JSCSS实现大气清新的滑动菜单效果代码。分享给大家供大家参考&#xff0c;具体如下&#xff1a;这是一款比较大气清新的滑动导航菜单&#xff0c;CSS和JavaScript配合完成&#xff0c;鼠标放到一级菜单上&#xff0c;会滑出二级的菜单&#xff0c;兼容性也不错&a…

docker和java容器_使用Docker容器和Java EE进行持续交付

docker和java容器组织需要一种使应用程序交付快速&#xff0c;可预测和安全的方法&#xff0c;而诸如docker之类的容器所提供的敏捷性则可以帮助开发人员实现这一目标。 对于Java EE应用程序&#xff0c;这可以在容器中打包应用程序&#xff0c;应用程序服务器和其他依赖项&…

alientek ministm32液晶显示程序_佳显12864中文字库液晶专业生产液晶显示模块

GDRAM&#xff1a;(Graphic Display RAM)&#xff1a;图形显示RAM&#xff0c;这一块区域用于绘图&#xff0c;往里面写啥&#xff0c;屏幕就会显示啥&#xff0c;它与DDRAM的区别在于&#xff0c;往DDRAM中写的数据是字符的编码&#xff0c;字符的显示先是在CGROM中找到字模&a…

C++ 面试考点(一)

点击蓝字关注我们C 基础1、引用和指针的区别&#xff1f;初始化:引用在定义的时候必须进行初始化&#xff0c;并且不能够改变指针在定义的时候不一定要初始化&#xff0c;并且指向的空间可变访问逻辑不同:通过指针访问对象, 用户需要使用间接访问通过引用访问对象, 用户只需使用…