假设现在有一个问题:对一个给定的数组A,计算其中元素的平方和。
熟悉命令式编程的人很快就能写出这样的代码:
int A[] = { 1, 2, 3, 4, 5 };
int sum = 0;
for (auto it = begin(A); it != end(A); it++) {
sum += (*it) * (*it);
}
cout << sum << endl;
熟悉函数式编程的人可以写出更简单的代码:
A = [1, 2, 3, 4, 5]
S = sum(map(lambda x: x*x, A))
但是当数据操作非常多时,函数的嵌套会严重影响可读性。
笑话一则:
苏联特工九死一生偷到了NASA太空火箭发射程序源代码的最后一页
)))))))))))))))))))))))))))
))))))))))))))))
)))))))))))))))
)))))))))))
))))
)))
))
))
)
语言集成查询(英文:Language Integrated Query,缩写:LINQ),发音”link”,是一项微软技术,新增一种自然查询的SQL语法到.NET Framework的编程语言中,目前可支持Visual Basic .NET以及C#语言。
int[] A = new int[] { 1, 2, 3, 4, 5 };
int S = A.Select(x => x * x)
.Sum();
有没有感觉很简练。
换个稍微复杂点的问题:计算数组A中奇数元素的平方和。
int[] A = new int[] { 1, 2, 3, 4, 5 };
int S = A.Where(x => x % 2 != 0)
.Select(x => x * x)
.Sum();
现在问题来了,C++11已经支持lambda表达式了,能不能写一个库向C++中添加LINQ呢?
首先遇到的第一个困难,C#支持扩展类功能,可以向类中添加自定义的方法,而C++不行。由于不能直接给C++的数组添加方法,我们想到可以用数组做参数调用一个函数来返回一个类,而向这个类中添加LINQ的方法。把这个函数命名为from,把类命名为linq_enumerable。显然函数from接受一个表示容器的参数或两个表示区间的参数,返回类linq_enumerable的对象。
linq_enumerable类应该有怎样的结构呢?它需要支持全部的LINQ方法,包括对全部元素进行选择的方法select、where等,统计的方法contains、count等,以及对所有元素进行折叠处理的aggregate、sum等。
我们知道count、sum只能执行一次然后返回一个值,但是select、where等操作可以执行多次。所以select、where等方法的返回值仍然是一个linq_enumerable类的对象。
linq_enumerable类中并不应该保存元素本身,而是应该保存容器迭代器,这也是一般的函数式语言中的实现方式。因此select返回的对象与where返回的对象的唯一区别就是迭代器不同。我们可以设计一个通用的迭代器,它可以处理多种不同的迭代需求,包括跳过一些元素或者对一些元素做处理,但是更好的方法是对每一个操作都设计一个新类型的迭代器,比如select_iterator和where_iterator等,然后用模板来处理不同的迭代器。
现在linq_enumerable类的完整结构就可以确定了:
template <typename TIterator>
class linq_enumerable {
private:
TIterator _begin;
TIterator _end;
public:
/*一堆方法*/
对于各种TIterator类,它需要接收一个或多个表示不确定类型的迭代器,并且视情况需要接受一个或多个不确定类型的lambda。因此TIterator类也需要用到模板,以select_iterator为例:
template <typename TIterator, typename TFunction>
class select_iterator {
private:
TIterator iterator;
TFunction func;
public:
select_iterator(const TIterator& _iterator, const TFunction& _func)
: iterator(_iterator), func(_func) { }
/*
按需要重载operator++、operator*、operator==、operator!=等
*/
最重要的是重载operator++,在其中调用用户指定的lambda来处理数据或跳过数据。
这时候linq_enumerable类的select方法的只需要一行就能实现,只要用select_iterator做参数构造并返回一个新的linq_enumerable对象就可以了。
linq_enumerable类中的其他方法的实现似乎也没有什么难度,现在已经有了迭代器begin和end,想对其中的元素做什么操作都可以。
最后是from的实现:
template<typename TIterator>
linq_enumerable<TIterator> from(const TIterator& begin, const TIterator& end) {
return linq_enumerable<TIterator>(begin, end);
}
有了这样的一个结构,就可以向其中添枝加叶,实现LINQ的大部分功能应该没问题了。
简单的测试:
int A[] = { 1, 2, 3, 4, 5 };
int sum = from(A)
.where([](int x){return x % 2 == 1; })
.select([](int x){return x * x; })
.sum();
cout << sum << endl;