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

什么是Lambda?

C++ 11加入了一个非常重要的特性——Lambda表达式。营里(戴维营)的兄弟都对Objective-C很熟悉,许多人多block情有独钟,将各种回调函数、代理通通都用它来实现。甚至有人选择用FBKVOController、BlocksKit等开源框架将KVO、控件事件处理都改为通过block解决。原因就是简单、方便、直观,函数的定义和使用出现在同一个地方。这里的Lambda表达式实际上和block非常类似,当然如果你用它和Swift语言的闭包比较,那就是一回事了。

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

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

从一个实际的应用说起

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

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

假设现在有一个结构称为Student,其中包含了ID与name两项——分别表示学号与姓名。在某个应用中,用户希望对一个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,输出其ID与Name值——这样我们连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将被传入这个表达式。以本程序为例,传入的值是10。Lambda-introducer可以指定变量以值的方式传入,也可以用其它的形式指定其以引用的方式传入。其变型大家就baidu一下吧J
  • l(int k)表示了参数列表,属于lambda-declarator的一部分。你可以把表达式看成一个仿函数(如上文的)。这里指定了仿函数的参数列表。如果函数的参数列表为空,这一部分可以省略。
  • lmutable表示仿函数中的变量能否改变。以前文中compareID这个仿函数为例,注意到其中的operator ()是const的。如果lambda表达式中引入了这个mutable,则对应的仿函数中operator()的定义将不包含这个const——这意味着仿函数中的变量值(Lambda-introducer传入)可以改变。讨论operator() const与operator()的区别已经超出了本文的范围,想了解的话,看看C++相关教程吧
  • l-> int 表示返回类型(这里是int)。如果编译器能从代码中推断出返回类型,或者Lambda表达式的返回类型为void,则该项可省略;
  • l{ return k+n; }是compound-statement:函数体。

通过分析可以看出,这个Lambda表达式相当于一个函数,该函数读入一个int值k,将该值加上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表达式),这个函数接收一个int值k,并打印出k+n。g的使用方法如下:

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

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

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

[](){}();

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

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

(0)

相关推荐

  • C++中的Lambda表达式详解

    我是搞C++的 一直都在提醒自己,我是搞C++的:但是当C++11出来这么长时间了,我却没有跟着队伍走,发现很对不起自己的身份,也还好,发现自己也有段时间没有写C++代码了.今天看到了C++中的Lambda表达式,虽然用过C#的,但是C++的,一直没有用,也不知道怎么用,就可怜的连Lambda语法都看不懂.好了,这里就对C++中的Lambda进行一个简单的总结,就算是对自己的一个交代,我是搞C++的,我是一个C++ programmer. 一段简单的Code 我也不是文艺的人,对于Lambda的

  • C++ 中的Lambda表达式写法

    小喵的唠叨话: 寒假之后,小喵在家里无所事事,最近用C++写代码的时候,用到了std::sort这个函数,每次用这个函数,小喵似乎都得查一下lambda表达式的写法.正好最近很闲,不如总结一下. 在Bing上搜索 C++ lambda ,第一条记录就是MSDN上的C++ lambda的介绍.本文也是基于这篇文章来写的. 那么接下来,我们分几个部分来介绍. 一.什么是Lambda表达式 MSDN上对lambda表达式的解释: 在 C++ 11 中,lambda 表达式(通常称为 "lambda&q

  • C++ 中lambda表达式的编译器实现原理

    什么是Lambda? C++ 11加入了一个非常重要的特性--Lambda表达式.营里(戴维营)的兄弟都对Objective-C很熟悉,许多人多block情有独钟,将各种回调函数.代理通通都用它来实现.甚至有人选择用FBKVOController.BlocksKit等开源框架将KVO.控件事件处理都改为通过block解决.原因就是简单.方便.直观,函数的定义和使用出现在同一个地方.这里的Lambda表达式实际上和block非常类似,当然如果你用它和Swift语言的闭包比较,那就是一回事了. 现在

  • 浅析C++11新特性的Lambda表达式

    lambda简介 熟悉Python的程序员应该对lambda不陌生.简单来说,lambda就是一个匿名的可调用代码块.在C++11新标准中,lambda具有如下格式: [capture list] (parameter list) -> return type { function body } 可以看到,他有四个组成部分: 1.capture list: 捕获列表 2.parameter list: 参数列表 3.return type: 返回类型 4.function body: 执行代码

  • 浅谈C++11新引入的lambda表达式

    ISO C++ 11 标准的一大亮点是引入Lambda表达式.基本语法如下: [capture list] (parameter list) ->return type { function body } 简单的讲一下各个部分的作用 1.[capture list]捕获列表,捕获到函数体中,使得函数体可以访问 2.(parameter list)参数列表,用来表示lambda表达式的参数列表 3.->return type函数返回值 {function body}就是函数体 lambda表达式

  • 实例讲解C++编程中lambda表达式的使用

    函数对象与Lambdas 你编写代码时,尤其是使用 STL 算法时,可能会使用函数指针和函数对象来解决问题和执行计算.函数指针和函数对象各有利弊.例如,函数指针具有最低的语法开销,但不保持范围内的状态,函数对象可保持状态,但需要类定义的语法开销. lambda 结合了函数指针和函数对象的优点并避免其缺点.lambda 与函数对象相似的是灵活并且可以保持状态,但不同的是其简洁的语法不需要显式类定义. 使用lambda,相比等效的函数对象代码,您可以写出不太复杂并且不容易出错的代码. 下面的示例比较

  • 结合C++11新特性来学习C++中lambda表达式的用法

    在 C++ 11 中,lambda 表达式(通常称为 "lambda")是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象的简便方法. Lambda 通常用于封装传递给算法或异步方法的少量代码行. 本文定义了 lambda 是什么,将 lambda 与其他编程技术进行比较,描述其优点,并提供一个基本示例. Lambda 表达式的各部分 ISO C++ 标准展示了作为第三个参数传递给 std::sort() 函数的简单 lambda: #include <algorit

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

    什么是Lambda? C++ 11加入了一个非常重要的特性--Lambda表达式.营里(戴维营)的兄弟都对Objective-C很熟悉,许多人多block情有独钟,将各种回调函数.代理通通都用它来实现.甚至有人选择用FBKVOController.BlocksKit等开源框架将KVO.控件事件处理都改为通过block解决.原因就是简单.方便.直观,函数的定义和使用出现在同一个地方.这里的Lambda表达式实际上和block非常类似,当然如果你用它和Swift语言的闭包比较,那就是一回事了. 这是

  • Lambda表达式原理及示例

    Lambda表达式   Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性. Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中). 使用 Lambda 表达式可以使代码变的更加简洁紧凑. 1. 需求分析    创建一个新的线程,指定线程要执行的任务 public static void main(String[] args) { // 开启一个新的线程 new Thread(new Runnable() { @Override public voi

  • 利用lambda表达式树优化反射详解

    前言 本节重点不讲反射机制,而是讲lambda表达式树来替代反射中常用的获取属性和方法,来达到相同的效果但却比反射高效. 每个人都知道,用反射调用一个方法或者对属性执行SetValue和GetValue操作的时候都会比直接调用慢很多,这其中设计到CLR中内部的处理,不做深究.然而,我们在某些情况下又无法不使用反射,比如:在一个ORM框架中,你要将一个DataRow转化为一个对象,但你又不清楚该对象有什么属性,这时候你就需要写一个通用的泛型方法来处理,以下代码写得有点恶心,但不妨碍理解意思: //

  • 基于Arrays.sort()和lambda表达式

    目录 Arrays.sort()和lambda表达式 1.对基本数据类型数组的排序 2.给对象数组排序 再谈Comparator-使用lambda表达式 以前 现在 Arrays.sort()和lambda表达式 1.对基本数据类型数组的排序 数字排序: int[] intArray = new int[]{1,34,5,-9}; Arrays.sort(intArray); System.out.println(Arrays.toString(intArray)); 字符串排序(先大写后小写)

  • 深入理解Java中的Lambda表达式

    Java 8 开始出现,带来一个全新特性:使用 Lambda 表达式 (JSR-335) 进行函数式编程.今天我们要讨论的是 Lambda 的其中一部分:虚拟扩展方法,也叫做公共辩护(defender)方法.该特性可以让你在接口定义中提供方法的默认实现.例如你可以为已有的接口(如 List 和 Map)声明一个方法定义,这样其他开发者就无需重新实现这些方法,有点像抽象类,但实际却是接口.当然,Java 8 理论上还是兼容已有的库. 虚拟扩展方法为 Java 带来了多重继承的特性,尽管该团队声称与

  • Java函数式编程(一):你好,Lambda表达式

    第一章 你好,lambda表达式! 第一节 Java的编码风格正面临着翻天覆地的变化. 我们每天的工作将会变成更简单方便,更富表现力.Java这种新的编程方式早在数十年前就已经出现在别的编程语言里面了.这些新特性引入Java后,我们可以写出更简洁,优雅,表达性更强,错误更少的代码.我们可以用更少的代码来实现各种策略和设计模式. 在本书中我们将通过日常编程中的一些例子来探索函数式风格的编程.在使用这种全新的优雅的方式进行设计编码之前,我们先来看下它到底好在哪里. 改变了你的思考方式 命令式风格--

  • Java中Lambda表达式并行与组合行为

    从串行到并行 串行指一个步骤一个步骤地处理,也就是通常情况下,代码一行一行地执行. 如果将我们常用的迭代器式的循环展开的话,就是串行执行了循环体内所定义的操作: sum += arr.get(0); sum += arr.get(1); sum += arr.get(2); //... 在书的一开始,就提到Java需要支持集合的并行计算(而Lambda为这个需求提供了可能). 这些功能将全部被实现于库代码中,对于我们使用者,实现并行的复杂性被大大降低(最低程度上只需要调用相关方法). 另外,关于

  • Java8与Scala中的Lambda表达式深入讲解

    前言 最近几年Lambda表达式风靡于编程界.很多现代编程语言都把它作为函数式编程的基本组成部分.基于JVM的编程语言如Scala.Groovy及Clojure把它作为关键部分集成在语言中.而如今,(最终)Java 8也加入了这个有趣的行列. Java8 终于要支持Lambda表达式!自2009年以来Lambda表达式已经在Lambda项目中被支持.在那时候,Lambda表达式仍被称为Java闭包.在我们进入一些代码示例以前,先来解释下为什么Lambda表达式在Java程序员中广受欢迎. 1.为

  • 基于Python编写一个计算器程序,实现简单的加减乘除和取余二元运算

    方法一: 结合lambda表达式.函数调用运算符.标准库函数对象.C++11标准新增的标准库function类型,编写一个简单的计算器,可实现简单的加.减.乘.除.取余二元运算.代码如下: #include "pch.h" #include <iostream> #include <functional> #include <map> #include <string> using namespace std; int add(int i

随机推荐