基于 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 <iostream>
using namespace std;
#include<string>
#include<algorithm>

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()
{
	Student a[] = {Student(2,"john"),Student(0,"tom"),Student(1,"lili")};
	sort(a,a+3,compareID());
	for (int i = 0;i<3;++i)
	{
		cout<<a[i].name<<endl;
	}
}

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

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

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

问题出现了,你意识到了吗?你只是想表达一个很简单的排序方式,确不得不引入很多的代码行来建相应的仿函数。如果这个函数在很多地方都会用到,那么建立它的价值还相对较大。如果只是用在一个地方,你也不得不中段你流畅是思路,一边骂娘一边写出这么多行代码。另一方面,程序的读者在读到相应部分的时候,也不得不中段他流畅的思路,在工程的某个地方苦苦求索 ——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 语句可以这么写:

Student a[] = {Student(2,"john"),Student(0,"tom"),Student(1,"lili")};
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;});

二、Lambda表达式详解

[capture](parameters) mutable ->return-type{statement}

1.[capture]:捕捉列表。捕捉列表总是出现在 Lambda 函数的开始处。实际上,[] 是 Lambda 引出符。编译器根据该引出符判断接下来的代码是否是 Lambda 函数。捕捉列表能够捕捉上下文中的变量以供 Lambda 函数使用;

2.(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号 “()” 一起省略;

3.mutable:mutable 修饰符。默认情况下,Lambda 函数总是一个 const 函数,mutable 可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);

4.->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->” 一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;

5.{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。

与普通函数最大的区别是,除了可以使用参数以外,Lambda 函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被 Lambda 使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在 “[]” 包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式:

1.[var] 表示值传递方式捕捉变量 var;
2.[=] 表示值传递方式捕捉所有父作用域的变量(包括 this);
3.[&var] 表示引用传递捕捉变量 var;
4.[&] 表示引用传递方式捕捉所有父作用域的变量(包括 this);
5.[this] 表示值传递方式捕捉当前的 this 指针。

上面提到了一个父作用域,也就是包含 Lambda 函数的语句块,说通俗点就是包含 Lambda 的 “{}” 代码块。上面的捕捉列表还可以进行组合,例如:

1.[=,&a,&b] 表示以引用传递的方式捕捉变量 a 和 b,以值传递方式捕捉其它所有变量;
2.[&,a,this] 表示以值传递的方式捕捉变量 a 和 this,引用传递方式捕捉其它所有变量。

不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。例如:

3.[=,a] 这里已经以值传递方式捕捉了所有变量,但是重复捕捉 a 了,会报错的;
4.[&,&this] 这里 & 已经以引用传递方式捕捉了所有变量,再捕捉 this 也是一种重复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coder_Alger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值