C++ 理解函数对象与lambda表达式
参考《21天学通C++》第21与第22章节,对函数对象进行介绍,同时通过lambda表达式这一匿名函数对象的简洁方式加深对函数对象的理解。本篇博文的主要内容是:
(1) 函数对象的概念;
(2) 将函数对象用作谓词;
(3) 如何使用函数对象实现一元、二元谓词;
(4) 如何编写lambda表达式;
(5) 如何将lambda表达式用作谓词;
(6) 如何编写可存储和可操作状态的lambda表达式。
一、 函数对象
1. 函数对象的概念和种类
函数对象是C++实体,从概念上讲,函数对象是用作函数的对象;从实现上说,函数对象是实现了operator()的类的对象。虽然函数和函数指针也可视为函数对象,但是吸纳了operator()的类的对象才能保存状态,才能用于标准模板库算法。C++常用于STL算法的函数对象可分为下面两种类型:(1) 一元函数:接受一个参数的函数,如果一元函数返回一个布尔值,称之为谓词;(2)
二元函数:接受两个参数的函数,若返回bool,则称为二元谓词函数。返回bool值得函数对象常常用来进行判断的算法,组合两个函数对象的函数对象称之为自适应函数对象。
2. 函数对象的典型用途
(1) 一元函数的用途
对于STL算法std::for_each()接受三个参数,第一个参数指定范围的起点,第二个参数指定范围的终点,第三个参数是要对指定范围内的每个元素调用的函数。这个函数就可以通过一元谓词实现,举例如下:
template <typename elementType>
struct DisplayElement
{
void operator () (const elementType& element) const
{
cout<< element << ' ';
}
};
vector<int> vectorElement;
for_each(vectorElement.begin(), vectorElement.end(), DisplayElement<int> ());
这样就可以使用STL算法for_each来对指定范围内的数据执行相同的函数方法。这里也可以使用匿名函数对象,即lambda表达式,如下所示,将上面的for_each改造为相同的功能:
for_each(vectorElement.begin(), vectorElement.end(), [](int& element) { cout << element
<< ' ';});
如果能够使用结构的对象来存储信息,则使用在结构中实现的函数对象的优点将显现出来。
(2) 一元谓词的用途
返回布尔值的一元函数就是谓词,这种函数可用于STL算法的判断。
一元谓词被大量用于STL算法中,例如,std::partition算法使用一元谓词来划分范围,stable_partition算法也使用一元谓词划分范围,但保持元素的相对顺序不变。诸如std::find_if等查找函数以及std::remove_if等删除元素的函数也使用一元谓词,其中std::remove_if删除指定范围内满足谓词条件的元素。
(3) 二元函数的用途
如果函数f(x,y)根据输入参数返回一个值,它将很有用。这种二元函数可用于对两个操作数执行运算,如加减乘除等。同样实现最重要的operator()。在std::transform等算法中,可使用二元乘积函数计算两个容器内容的点乘。
template <typename elementType>
class Multiply
{
public:
elementType operator () (const elmentType& elem1,const
elmentType& elem2)
{ return (elem1 * elem2);}
}
vector<int> vecMultiplicand, vecMultiplier;
vector<int> vecResult;
transform(vecMultiplicand.begin(), vecMultiplicand.end(), vecMultiplier.begin(), vecResult.begin(),
Multiply <int>());
transform将两个范围的内容相乘,并将结果存储在第三个范围中。这里,这三个范围分别存储在类型std::vector的vecMultiplicand、vecMultiplier和vecResult中。乘法运算是通过调用二元函数Multiply::operator
()执行的,对源范围和目标范围内的每个元素都调用了该函数。operator()的返回值保存在vecResult中。
(4)
二元谓词的用途
接受两个参数并返回一个布尔值的函数是二元谓词,这种函数用于诸如std::sort等STL函数中。使用排序二元谓词实现排序,通过实现operator()来动态排序。
很多STL算法都使用二元谓词,比如删除相邻重复元素的std::unique()、排序算法sort、排序并保持相对顺序的std::stable_sort以及两个范围进行操作的std::transform。
(5) 总结
在结构或类中实现函数对象时,它将比简单函数有用的多,因为它也可用于存储与状态相关的信息。谓词是一类特殊的函数对象。
二、 C++ lambda表达式
1. lambda表示概念
可将lambda表达式视为包含公有operator()的匿名结构(或类),从这种意义上说,lambda表达式属于函数对象。从上面所讲到的进行分析:
for_each(vectorElement.begin(),
vectorElement.end(), [](int& element) { cout << element << ' ';});
编译器加到下述lambda表达式时:[](int& element) {
cout << element << ' ';}自动将其展开为类似结构DisplayElement<int>的表示:
struct DisplayElement
{
void operator () (const int& element) const
{
cout<< element << ' ';
}
};
2.
如何定义lambda表达式
以方括号[]打头,告诉编译器,接下来是一个lambda表达式,方括号后面是一个参数列表,该参数列表与不使用lambda表达式时提供给operator()参数列表相同。
3.
一元函数对应的lambda表达式
与一元operator(Type)对应的lambda表达式接收一个参数,定义如下:
[](Type
paramName){ // lambda expression code here}也可用引用来传参:[](Type& paramName){ // lambda expression code here}。
4.
一元谓词对应的lambda表达式
谓词可帮助做出决策,一元谓词是返回bool类型的一元表达式。举例如下:
[](int&
Num){return ((Num % 2) == 0);}
在这里,返回值的性质是为了让编译器知道该lambda表达式的返回类型为bool。在算法中可将lambda表达式用于一元谓词,如可在find_if中使用上述lambda表达式找出集合中的偶数。如果谓词返回true,find_if将返回一个指向相应元素的迭代器,指出找到一个满足条件的元素。
5. 通过捕获列表接受状态变量的lambda表达式
对于上面的一元谓词实现在整数能被2整除时返回true,如果让它更通用,在数字能被用户指定除数整除时返回true,可采用状态变量:
int Divisor = 2;
[Divisor ](int& Num){return ((Num
% Divisor ) == 0);}
一系列以状态变量的方式传递的参数,也被称为lambda表达式的捕获列表。
6. lambda表达式的通用语法
[StateVar1,StateVar1](Type& param) { // }
如果要在lambda表达式中修改这些状态变量,可添加关键字multable:
[StateVar1, StateVar1](Type&
param) multable { // }
这样便可在lambda表达式中修改捕获列表[]中指定的变量,但离开lambda表达式后,这些修改便无效,要确保在lambda表达式内部对状态变量的修改在其外部也有效,应按照引用传递它们:
[&StateVar1, &StateVar1](Type&
param) { // }
lambda表达式还可以接受多个输入参数,为此可用逗号分隔:
[StateVar1, StateVar1](Type1&
param1,Type2& param2) { // }
如果要向编译器明确指明返回类型,可使用->,如下:
[StateVar1, StateVar1](Type1&
param1,Type2& param2)-> ReturnType { return (value or expression); }
最后,复合语句{}可包含多条用分号分割的语句:如下
[StateVar1, StateVar1](Type1&
param1,Type2& param2)-> ReturnType { Statement 1; Statement 2;return (value or expression); }
如果lambda表达式包含多行代码,必须显示地指明返回类型。
7.
二元函数对应的lambda表达式
二元函数接受两个参数,还可返回一个值,与之等价的lambda表达式如下:
[...]
(Type1& param1,Type2& param2){ // }
8.
二元谓词对应的lambda表达式
返回true或false,可帮助决策的二元函数被称为二元谓词函数。这种谓词可用于std::sort等排序算法。
[...]
(Type1& param1,Type2& param2){ return bool expression; }
9.
总结
仅当lambda表达式简洁、高效时才能使用它;
记住lambda表达式总是以[]打头;
除非使用关键字multable进行指定,否则不能修改捕获列表中指定的状态变量;
lambda表达式是实现了operator()的匿名类或结构;
注意使用const对参数进行限定;
lambda表达式的语句块包含多条语句时,要显式的指定返回类型;
不要使用很长的包含多条语句的lambda表达式,而应转而使用函数对象,因为每次使用lambda表达式时,都要重新定义它,这无助于提高代码的重用性。
******************************************************************************************************
2015-8-3
C++ 理解函数对象与lambda表达式的更多相关文章
- 使用Java函数接口及lambda表达式隔离和模拟外部依赖更容易滴单测
概述 单测是提升软件质量的有力手段.然而,由于编程语言上的支持不力,以及一些不好的编程习惯,导致编写单测很困难. 最容易理解最容易编写的单测,莫过于独立函数的单测.所谓独立函数,就是只依赖于传入的参数 ...
- 闭包(Closure)和匿名函数(Anonymous function)/lambda表达式的区别
闭包(Closure)和匿名函数(Anonymous function)/lambda表达式的区别 函数最常见的形式是具名函数(named function): function foo(){ con ...
- C# 匿名委托、匿名方法、匿名对象、Lambda表达式
一.匿名类型可通过使用 new 运算符和对象初始值创建匿名类型.示例:var v = new { Name = "Micro", Message = "Hello&quo ...
- 转载 C#匿名函数 委托和Lambda表达式
转载原出处: http://blog.csdn.net/honantic/article/details/46331875 匿名函数 匿名函数(Anonymous Function)是表示“内联”方法 ...
- 第三天 函数 三元运算 lambda表达式 内置函数 文件操作
面向过程: 直接一行一行写代码,遇到重复的内容复制黏贴. 不利于代码阅读 代码没有复用 面向对象 将代码块定义为函数,以后直接调用函数 增强了复用性 函数的定义方法 def 函数名(传递参数): 函数 ...
- 函数进化到Lambda表达式的三过程
假如我们想要从一个整型数组中取出其中是奇数的选项,其实现方式有很多, 接下来通过三种方法的对比理解Lambda表达式的用途,需要了解的朋友可以参考下 //声明委托类型 public d ...
- 『无为则无心』Python函数 — 34、lambda表达式
目录 1.lambda的应用场景 2.lambda语法 3.快速入门 4.示例:计算a + b 5.lambda的参数形式 6.lambda的应用 lambda表达式的主要作用就是化简代码. 匿名函数 ...
- 匿名函数 =匿名方法+ lambda 表达式
匿名函数的定义和用途 匿名函数是一个"内联"语句或表达式,可在需要委托类型的任何地方使用. 可以使用匿名函数来初始化命名委托[无需取名字的委托],或传递命名委托(而不是命名委托类型 ...
- 如何为CriteriaOperator过滤对象转换为lambda表达式,即:linq to xpo的动态where语句
How to convert the CriteriaOperator to a lambda expression, so, the latter expression can be used in ...
随机推荐
- Linux 的磁盘格式化、挂载、磁盘检验、df、du、fdisk、free命令的使用
df:列出文件系统的整体磁盘使用量du:检查磁盘空间使用量fdisk:用于磁盘分区 free:查看内存占用情况 一.df命令列出系统的整体磁盘使用量 df命令参数功能:检查文件系统的磁盘空间占用情况. ...
- synology git 服务器问题处理
synology git 服务器问题处理 安装 synology 上的 git 套件, 发现使用过程中存在很多问题. permission 问题 ## 将对应的目录设为git所有者 chown git ...
- ehcache 配置说明
- Android中显式意图和隐式意图的区别
1.显式意图 可以直接通过名称开启指定的目标组件: 通过构造方法Intent(Context packageContext,class<?>cls)来实现. button_1 = (But ...
- HTML Entity
1.1 介绍 在编写HTML页面时,需要用到"<".">"."空格"等符号,直接输入这些符号时,会错误的把它们与标记混在一起,非 ...
- SignalR了解
一.概述 一.理解SignalR ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程. 实时 Web 功能是指这样一种 ...
- Git的使用(3) —— 远程版本库的操作(GitHub)
1. 配置SSH (1) GitHub 登陆GitHub后,点击右上角头像,选择 Setting . 在左面栏目中选择"SSH and GPG keys". 打开生成的SSH公钥文 ...
- pyinstaller参数介绍以及总结
最近利用tkinter+python+pyinstaller实现了小工具的项目,在此记录下pyinstaller相关参数以及爬过的坑. 一.pyinstaller相关参数 -F, –onefile 打 ...
- Java性能分析神器-JProfiler详解(转)
前段时间在给公司项目做性能分析,从简单的分析Log(GC log, postgrep log, hibernate statitistic),到通过AOP搜集软件运行数据,再到PET测试,感觉时间花了 ...
- elementUI 列表里面含有多选框,当翻页的时候依然保持之前页多选不变
el-table的type="selection"的使用 场景:el-table,type="selection"时,重新请求后,设置列表更新前的已勾选项 踩坑 ...