基于C++ Lambda表达式的程序优化

标签: 编程技术 c++ 编程 表达式 | 发表时间:2011-10-28 18:25 | 作者:editor sunnyboy.xue
出处:http://stblog.baidu-tech.com

这是一个关于C\C++程序员的一个小故事,关于C++11——刚刚通过的新标准的一个小故事

请不要误会,题目中所提及的“优化”并不是提升程序的性能——Lambda表达式干不了这个。从本质上来说,它只是一种“语法糖”而已。不使用这种表达式,我们照样可以写出满足需求的程序。正如放弃C而使用汇编,或者放弃汇编而使用机器语言一样,你能控制的范围就在那里,不增不减。但如果有得选择,我相信大部分人会选择汇编而非机器语言,选择C而非汇编,甚至选择C++而非C语言……。如果你确实是这样选择的,那么我有理由相信,你会选择C++新标准中的Lambda表达式,因为它确实能够简化你的程序,让你写起程序来更容易;让你的程序更易读,更优美;同时也让你有更多向同行炫耀的资本。

从一个实际的应用说起


让我们还是看一个例子吧。

无论是C语言的使用者,还是C++的用户,如果你从事PC程序的算法开发,我有96.57%的把握认为你可能使用过C++标准模板库STL(其中的stringvector之类)。毕竟,STL的抽象不错,不用白不用,是不是。STL中有一大类是算法,这些算法的抽象同样不错,我们就拿排序算法(sort)来说事吧。

假设现在有一个结构称为Student,其中包含了IDname两项——分别表示学号与姓名。在某个应用中,用户希望对一个Student的数组按照ID的从大到小排序,那么程序可能写成如下的形式(本文中的所有程序均在Visual Studio 2010下编译通过):
#include <string>
#include <vector>
#include <iostream>
#include <iterator>
#include <algorithm>
using namespace std;

struct Student {
unsigned ID;
string name;
Student(unsigned i, string n) : ID(i), name(n){}
};

struct compareID {
bool operator ()(const Student& val1, const Student& val2)     const {
return val1.ID < val2.ID;
}
};

int main(int argc, char* argv[]) {
Student a[] = {Student(2, “John”), Student(0, “Tom”), Student(1, “Lily”)};
sort(a, a+3, compareID());
for(int i=0; i<3; ++i)
cout<<a[i].ID<<’ ‘<<a[i].name<<endl;
return 0
}

程序用sort进行排序,之后用一个for循环输出结果。而之所以能完成这个排序,则是由于仿函数compardID的存在。


现在假设用户的需求变了(或者是另一个需求),需要你按照学生的姓名进行排序,那么你需要重新写一个仿函数如下:

struct compareName {

bool operator ()(const Student& val1, const Student& val2) const    {
return val1.name < val2.name;
}
};

然后将sort的调用修改为:

sort(a, a+3, compareName());

问题出现了,你意识到了吗?你只是想表达一个很简单的排序方式,确不得不引入很多的代码行来建相应的仿函数。如果这个函数在很多地方都会用到,那么建立它的价值还相对较大。如果只是用在一个地方,你也不得不中段你流畅是思路,一边骂娘一边写出这么多行代码。另一方面,程序的读者在读到相应部分的时候,也不得不中段他流畅的思路,在工程的某个地方苦苦求索——compareName或者compareID是怎么干的呢?
是的,是的,作为一个C++老鸟,你会说,这样写代码太不专业了。完全可以有不建立仿函数的写法,比如以ID排序时,完全可以通过引入boost库中的bind来实现,比如这样:

sort(a, a+3, bind(less<unsigned>(), bind(&Student::ID, _1), bind(&Student::ID, _2)));

如果你能写出或是读懂这段代码,我承认你的C++水平确实说得过去(如果读不懂,没关系,它不是本文的重点)。但这段代码真的好吗?确实,这样可以省略了仿函数。但问题是代码的复杂性大大增加了——即使如此简单的一个需求,bind表达式也要复杂如斯,更复杂一点的需求要写成何等复杂的形式啊,这对于bind本身,写程序的人,读程序的人都是一种折磨——你hold住吗?

如果用Lambda表达式呢,唔,这个sort语句可以这么写:

sort(a, a+3, [](const Student& val1, const Student& val2){ return val1.ID < val2.ID; });

那个看上去有点奇怪的,sort的第三个函数就是一个Lambda表达式了。如果我们除去开头的“[]”不看,后面的部分很像一个函数——你可以很容易地看出这个函数是干什么的:给定两个Student元素,比较两个元素的ID值,并返回比较结果——这玩意儿比上面那个bind结果容易阅读多了。

事实上,利用Lambda表达式,上述程序可以修改为如下的样子(只列出了main函数):

int main(int argc, char* argv[]) {
Student a[] = {Student(2, “John”), Student(0, “Tom”), Student(1, “Lily”)};
sort(a, a+3, [](const Student& val1, const Student& val2){ return val1.ID < val2.ID; });
for_each(a, a+3, [](const Student& val){cout<<val.ID<<’ ‘<<val.name<<endl;});
return 0
}

其中的for_each句用于输出——其中的Lambda表达式意味着:对于每一个val,输出其IDName值——这样我们连for循环也省了。

Lambda表达式的引入就是为了更方便地书写程序,更容易地阅读程序。如同STL一样,有什么理由不去用呢?

Lambda表达式的基本语法


有了感性的认识后,我们来分析一下Lambda表达式的语法。

我这里无意把C++标准草案中Lambda表达式的有关章节翻译过来(我也不佩这么做)。只是在这里希望以最通俗的方式将它的语法讲解一二。从结构上说,Lambda表达式可以写成如下的形式:

Lambda-introducer lambda-declarator(opt) compound-statement


其中的Lambda-introducer就是刚刚的那个“[]”它是不能省略的。中括号中也可能出现变量。表示将局部变量传入到Lambda表达式中。lambda-declaratoropt是可选择的,包括了表达式的参数列表,返回值信息, mutable声明(以及一些其它信息,这里不做讨论)。而最后的compound-statement则是表达式的主要内容。

还是看一个例子吧:

int n = 10;
[n](int k) mutable -> int { return k + n; };

程序的第二行是一个lambda表达式,lambda里能出现的东西几乎全了(当然,正如我在前文说的,有一些其它信息这里不做讨论,所以没有加入其中)。让我们对里面的东西一一分析:

l[n]Lambda-introducer,而n是一个变量,表明该表达式作用域中的变量n将被传入这个表达式。以本程序为例,传入的值是10Lambda-introducer可以指定变量以值的方式传入,也可以用其它的形式指定其以引用的方式传入。其变型大家就baidu一下吧J

l(int k)表示了参数列表,属于lambda-declarator的一部分。你可以把表达式看成一个仿函数(如上文的)。这里指定了仿函数的参数列表。如果函数的参数列表为空,这一部分可以省略。

lmutable表示仿函数中的变量能否改变。以前文中compareID这个仿函数为例,注意到其中的operator ()const的。如果lambda表达式中引入了这个mutable,则对应的仿函数中operator()的定义将不包含这个const——这意味着仿函数中的变量值(Lambda-introducer传入)可以改变。讨论operator() constoperator()的区别已经超出了本文的范围,想了解的话,看看C++相关教程吧J

l-> int 表示返回类型(这里是int)。如果编译器能从代码中推断出返回类型,或者Lambda表达式的返回类型为void,则该项可省略;

l{ return k+n; }compound-statement:函数体。

通过分析可以看出,这个Lambda表达式相当于一个函数,该函数读入一个intk,将该值加上n返回。根据上述说明,这个表达式可以简写为:

[n](int k){ return k + n; };

Lambda表达式可以存储在std::function<T> std:: reference_closure<T>类型的变量中。其中的T表示了表达式对应函数的类型。以上述表达式为例,它输入参数为int型变量,输出为int,那么为了保存它,可以写成如下的形式:

function<int(int)> g = [n](int k){ return k + n; };

另一个例子,前文所使用的Lambda表达式:

[](const Student& val1, const Student& val2){ return val1.ID < val2.ID; }

可以存储于function<bool(const Student&, const Student&)>这个类型的变量中。

如果你嫌这么写麻烦,也可以利用C++新标准中另一个新特性:类型推导。即用auto作为变量的类型,让编译器自己推导表达式的类型:

auto g = [n](int k){ return k + n; };

没问题,这样写g还是一个强类型的变量,只不过其类型是由编译器推导的,好处是你不用写太长的变量类型了J


Lambda表达式进阶


作为结尾,我们来看一些C++ Lambda表达式进阶的用法。

Lambda表达式被引入主要是用于函数式编程。有了Lambda表达式,我们也可以做一些函数式编程的东西。比如将一个函数作为返回值的应用:

auto g = [](int n) -> function<void (int)> {
return [n](int k){ cout<<n+k<<’ ‘; };
};

它是一个Lambda表达式,输入一个整型变量n,返回一个函数(lambda表达式),这个函数接收一个intk,并打印出k+ng的使用方法如下:

int a[]={1,2,3,4,5,6,7,8,9,0};
function<void (int)> f = g(2);
for_each(a, a+10, f);

它将输出:3 4 5 6 7 8 9 10 11 2


有一点函数式编程的味道了J

至于其它的东西,比如如下的表达式:

[](){}();

是一个有效的调用。其中[](){}”表示一个Lambda表达式,其输入参数为空,返回void,什么都不干。而最后的()表示调用其求值——虽然什么都不干,但编译能通过,很唬人喔J



好了,就写到这里吧。关于Lambda表达式想说的最后一件事是:它是新标准C++11中定义的。老的编译器不支持(这也是我用VS2010的原因)。想要用它,以及其它新标准带来的好处吗?嘿,你的家伙(指编译器)该升级了J

相关 [lambda 表达式 程序] 推荐:

基于C++ Lambda表达式的程序优化

- sunnyboy.xue - 搜索研发部官方博客
这是一个关于C\C++程序员的一个小故事,关于C++11——刚刚通过的新标准的一个小故事…. 请不要误会,题目中所提及的“优化”并不是提升程序的性能——Lambda表达式干不了这个. 从本质上来说,它只是一种“语法糖”而已. 不使用这种表达式,我们照样可以写出满足需求的程序. 正如放弃C而使用汇编,或者放弃汇编而使用机器语言一样,你能控制的范围就在那里,不增不减.

[Java 8] Lambda 表达式实例

- - Java - 编程语言 - ITeye博客
Java 8 中的 Lambda 表达式,允许将函数作为形参传递给另外的函数. 为了更好地理解,我们用实例的方式来演示如何使用 Lambda 表达式. 1、Lambda 表达式 Hello World. 这是一个最简单的 Lambda 表达式的例子. 首先在 main 方法的上面声明了一个接口 HelloWorld,在 main 方法中实现了这个接口,随后调用了接口的唯一方法.

Lambda表达式现状分析

- - InfoQ cn
距明年Java 8发布还有不到一年时间,Brian Goetz发布了最新的 Lambda表达式现状分析,涵盖了Java集合API的改进. Java 8最受期待的特性之一是引入了 Lambda表达式,Java集合API对它的重点支持是确保该类库被广泛使用的关键所在. 如果你不熟悉Lambda表达式的语法,请查看先前的一篇文章 Lambda表达式现状分析以及之前InfoQ的相关报道,以便了解该语法的详细内容.

Java8集合中的Lambda表达式

- - 四火的唠叨
文章系本人原创,转载请保持完整性并注明出自 《四火的唠叨》. 本文翻译自《 Java 8 Explained: Applying Lambdas to Java Collections》. Lambdas表达式是Java 8的主题,在Java平台上我们期待了很久. 但是,如果如果我们不在集合中使用它的话,就损失了很大价值.

【译】为什么要在Java中加入Lambda表达式

- - Java - 编程语言 - ITeye博客
原文:http://www.lambdafaq.org/why-are-lambda-expressions-being-added-to-java/. 作者:Maurice Naftalin. Lambda表达式(以及闭包)在许多现代编程语言中都很普及. 在为什么要在 Java 平台加入 Lambda 表达式呢.

Java8之使用新JS解释器Nashorn编译Lambda表达式

- - 并发编程网 - ifeve.com
原文链接  作者: Tal Weiss  CEO of  Takipi   译者:踏雁寻花,xbkaishui  校对:方腾飞. 在最近的一篇 文章中,我了解了一下Java8和Scala是如何实现 Lambda 表达式的. 正如我们所知道的,Java8不仅对javac编辑器做了很大改进,它还加入了一个全新的项目—Nashorn.

探索Java语言与JVM中的Lambda表达式(译)

- - BlogJava_首页
探索Java语言与JVM中的Lambda表达式.     Lambda表达式是自Java SE 5引入泛型以来最重大的Java语言新特性,本文是2012年度最后一期 Java Magazine中的 一篇文章,它介绍了Lamdba的设计初衷,应用场景与基本语法. (2013.01.02最后更新).

【外刊IT评论网】Lambda将从根本上改变我们开发Java程序的方式

- - 外刊IT评论网
当今世界主流编程语言无不吸纳强大的闭包概念,但有个例外,它就是Java. 数年来,Java语言中增加闭包特征的工作看起来毫无进展. 早在15年之前,Scala语言和TypeSafe框架的作者Martin Odersky和 Phillip Wadler发布了实验性的 “Pizza”项目,由此,人们开始试图将闭包纳入编程语言的基本特征之一.

大数据Lambda架构

- - CSDN博客云计算推荐文章
1 Lambda架构介绍.          Lambda架构划分为三层,分别是批处理层,服务层,和加速层. 最终实现的效果,可以使用下面的表达式来说明. 1.1 批处理层(Batch Layer, Apache Hadoop).          批处理层主用由Hadoop来实现,负责数据的存储和产生任意的视图数据.

Oryx 2: Lambda architecture on Apache Spark, Apache Kafka for real-time large scale machine learning

- -