由 C++ 模板元编程看 F# 对链表的处理,兼谈 C 系语言和 FP 的优劣

       有如下一段 C++ 模板元编程代码,求类型链表中最长类型的长度。

// 求列表中字节最长的类型的字节长度值 // 使用链表传递的思想:1::2::3::4::[] #include <iostream> using namespace std; // 尾结点 struct NullType; // 链表结点 template <class T, class S> struct TypeList; // 定义四结点链表 #define TypeList_1( T1 ) TypeList <T1, NullType> #define TypeList_2( T1, T2 ) TypeList <T1, TypeList_1( T2 )> #define TypeList_3( T1, T2, T3 ) TypeList <T1, TypeList_2( T2, T3 )> #define TypeList_4( T1, T2, T3, T4 ) TypeList <T1, TypeList_3( T2, T3, T4 )> // 结点比较模板 template <class TypeList> struct MaxType; // 尾结点比较模板全特化,令 Max 为 -1 template <> struct MaxType <NullType> { enum { Max = -1 }; }; // 结点比较模板全特化 template <class Head, class Tail> struct MaxType < TypeList<Head, Tail> > { enum { size = sizeof(Head) }; enum { Max = ( size > MaxType<Tail>::Max ) ? size : MaxType<Tail>::Max }; }; int _tmain(int argc, _TCHAR* argv[]) { typedef TypeList_4 (char, float, double, char) Types_4; cout << MaxType<Types_4>::Max << endl; // 求得 double 类型 sizeof 值是最大的为 8 system("pause"); return 0; }

       这段代码很巧,处理过程反映了一种链表结构的思想。这里需要明确解释一下“链表结构”。“链表结构”有两点很关键,一个是非尾结点,一个是尾结点。使用 C 系语言(含 Java)学数据结构的同学,想必多将尾结点理解为“指向 Null 的结点”,不对,这是最后一个非尾结点。数据结构意义上的“尾结点”被 NULL 值给 Rape 掉了,其实这是很“不数学”的,《算法导论》上一般将“尾结点”明确指定为 NIL 结点,这才是负责任的态度;相比较,大陆的很多“流行”的数据结构和算法书都 Rape 掉了尾结点,从这一点上看,就很难说,这些大陆的专家们是否真的理解数据结构……所以说,为何说大陆某知名学府的某位同志的畅销书是垃圾就在这里了……这里不多言,关于 NULL 的讨论我将放到后文展开。

       对于代码的理解,注释已经明了。先关注“非尾结点”,单链表的非尾结点结构可以抽象成如下形式:

struct Node { T head; N tail; };

       此处 head 即为本结点,tail 指的是该结点所引的单链表的后段部分。其实这是一种“嵌套结构”,也就是说每个结点都包括本身和它所引的单链表的后段部分。最后一个非尾结点引的是尾结点——让我们现在关注一下“尾结点”。“尾结点”是自定义的,是与非尾结点不同的。不同的数据结构对其定义和实现不同,《算法导论》上将尾结点或叶结点统称为 NIL 结点。这里,将 NIL 结点表示为 [],也就是空链表,则有如下单链表的嵌套结构示意图:

       此示意图解释了一个含有三个元素的单链表的结构,单链表记为 [ 1; 2; 3 ]。明确了这一嵌套结构后,我们便可以将示意图简化为我们所熟悉的样式:

simple_list

       这段有趣的 C++ 代码本身折射出了严谨的单链表结构思想,但是用 C++ 去描述一个单链表就不能保证这种严谨性了——程序员既可以让最后一个非尾结点指向尾结点,也可以指向 NULL,当然了,由于大陆的某人的努力,绝大多数人都不了解或者反感 NIL 的概念,只知道用 NULL。C# 还好一些,有“独立”的 null 值,VB.net 里有等价的 Nothing,可以在形式上等价于尾结点;但仍显得不伦不类。首先,null 值是普适的,继承于 object 的类型都可以指向 null;再者,值类型无法指向 null,使得程序员用 C# 来写链表时,非尾结点必须是 class 型,这简直就是悲剧;最后,null“很不灵活”,循环链表的尾结点就无法用 null 来做。我在《F# 中的 LOP 之“抽象表述”》这篇文章中的“可选类型”部分讨论了有关 null 的其它缺陷。而 C/C++ 中的 NULL 就显得很尴尬了。也不知道是谁开始教给全世界的人 NULL 就是 0 的。Bjarne Stroustrup 同学就对 NULL 这么个玩儿玩儿很有意见,他曾告诫他的子民们“该是 0 的地方就明明白白地写 0,而不要用 NULL”。NULL 是一个概念,而不是某个值。

       F# 做为一门出色的 FP,对此做了很严谨的处理。见下图:

       输入 1::2::3,将这三个元素连到一起,编译期便会报错,因为没有指定“尾结点”,而第二次输入 1::2::3::[],最后连接上空链表做为尾结点,链表创建成功。是不是很严谨?是不是很数学?——的确做得非常出色。

       用 C# 和 Java 写算法,就好似让厨子去跑赛车。C/C++ 写算法的优势仅仅是性能,除此之外它们的表现有待商榷。为何关于 C/C++ 代码风格的讨论这么多?因为 C/C++ 过于“机械化”,很不便于以人的思维去理解,要是再写得乱七八糟就根本没法儿看了!C/C++ 在这一点上,工程越复杂,问题越大。曾有人自豪地夸耀 C 语言效率之高,“每一行都可以准确地对应汇编码”,精瘦精瘦的,没有一点赘肉——他说得太对了,对于一个复杂的工程,若没有系统的注释,那堆 C/C++ 代码在程序员看来和一坨机器码没什么区别。可以说,当代软件工程学很多思想都是围绕此中心而研究出来的各种解决方案。而 FP 思想在今后软件工业市场的强势崛起,必然会大规模颠覆现有软件工程理论。FP 将以数学化和人性化的方式让程序员思考,这是 FP 无与伦比的优势。

你可能感兴趣的:(编程,C++,c,F#,语言,FP)