百道网
 您现在的位置:Fun书 > 代码之美
代码之美


代码之美

作  者:(美)奥莱姆(Oram,A),(美)维尔森(Wilson,G) 编;BC Group 译

译  者:聂雪军

出 版 社:机械工业出版社

出版时间:2009年01月

定  价:99.00

I S B N :9787111251330

所属分类: 专业科技  >  计算机/网络  >  程序设计    

标  签:科技  套装书  综合  语言与开发工具  程序语言与软件开发  计算机与互联网  

[查看微博评论]

分享到:

TOP内容简介

本书介绍了人类在一个奋斗领域中的创造性和灵活性:计算机系统的开发领域。在每章中的漂亮代码都是来自独特解决方案的发现,而这种发现是来源于作者超越既定边界的远见卓识,并且识别出被多数人忽视的需求以及找出令人叹为观止的问题解决方案。
  本书33章,有38位作者,每位作者贡献一章。每位作者都将自己心目中对于“美丽的代码”的认识浓缩在一章当中,张力十足。38位大牛,每个人对代码之美都有自己独特的认识,现在一览无余的放在一起,对于热爱程序的每个人都不啻一场盛宴。 虽然本书的涉猎范围很广,但也只能代表一小部分在这个软件开发这个最令人兴奋领域所发生的事情。

TOP作者简介

全球38位顶尖高手、众多语言之父经典之作

TOP目录

译者序

前言
第1章 正则表达式匹配器
1.1 编程实践
1.2 实现
1.3 讨论
1.4 其他的方法
1.5 构建
1.6 小结
第2章 Subversion中的增量编辑器:像本体一样的接口
2.1 版本控制与目录树的转换
2.2 表达目录树的差异
2.3 增量编辑器接口
2.4 但这是不是艺术?
2.5 像体育比赛一样的抽象
2.6 结论
第3章 我编写过的最漂亮代码
3.1 我编写过的最漂亮代码
3.2事倍功半
3.3 观点
3.4 本章的中心思想是什么?
3.5 结论
3.6致谢
第4章 查找
4.1. 耗时
4.2. 问题:博客数据
4.3. 问题:时间,人物,以及对象?
4.4. 大规模尺度的搜索
4.5. 结论
第5章 正确、优美、迅速(按重要性排序):从设计XML验证器中学到的经验
5.1 XML验证器的作用
5.2 问题所在
5.3 版本1:简单的实现
5.4 版本2:模拟BNF语法——复杂度O(N)
5.5 版本3:第一个复杂度O(log N)的优化
5.6 版本4:第二次优化:避免重复验证
5.7 版本5:第三次优化:复杂度 O(1)
5.8 版本 6:第四次优化:缓存(Caching)
5.9 从故事中学到的
第6章 集成测试框架:脆弱之美
6.1. 三个类搞定一个验收测试框架
6.2. 框架设计的挑战
6.3. 开放式框架
6.4. 一个HTML解析器可以简单到什么程度?
6.5. 结论
第7章 美丽测试
7.1 讨厌的二分查找
7.2 JUnit简介
7.3将二分查找进行到底
7.4 结论
第8章 图像处理中的即时代码生成
第9章 自顶向下的运算符优先级
9.1. JavaScript
9.2. 符号表
9.3. 语素
9.4. 优先级
9.5. 表达式
9.6. 中置运算符
9.7. 前置操作符
9.8. 赋值运算符
9.9. 常数
9.10. Scope
9.11. 语句
9.12. 函数
9.13. 数组和对象字面量
9.14. 要做和要思考的事
第 10章 追求加速的种群计数
10.1. 基本方法
10.2. 分治法
10.3. 其他方法
10.4. 两个字种群计数的和与差
10.5. 两个字的种群计数比较
10.6. 数组中的1位种群计数
10.7. 应用
第11章 安全通信:自由的技术
11.1 项目启动之前
11.2剖析安全通信的复杂性
11.3 可用性是关键要素
11.4 基础
11.5 测试集
11.6 功能原型
11.7 清理,插入,继续……
11.8 在喜马拉雅山的开发工作
11.9 看不到的改动
11.10 速度确实重要
11.11 人权中的通信隐私
11.12 程序员与文明
第12章 在BioPerl里培育漂亮代码
12.1. BioPerl和Bio::Graphics模块
12.2. Bio::Graphics的设计流程
12.3. 扩展Bio::Graphics
12.4. 结束语和教训
第13章 基因排序器的设计
13.1 基因排序器的用户界面
13.2 通过Web跟用户保持对话
13.3. 多态的威力
13.4 滤除无关的基因
13.5 大规模美丽代码理论
13.6 结论
第14章 优雅代码随硬件发展的演化
14.1. 计算机体系结构对矩阵算法的影响
14.2 一种基于分解的方法
14.3 一个简单版本
14.4 LINPACK库中的DGEFA子程序
14.5 LAPACK DGETRF
14.6递归LU
14.7 ScaLAPACK PDGETRF
14.8 针对多核系统的多线程设计
14.9 误差分析与操作计数浅析
14.10 未来的研究方向
14.11 进一步阅读
第15章 漂亮的设计会给你带来长远的好处
15.1. 对于漂亮代码的个人看法
15.2. 对于CERN库的介绍
15.3. 外在美(Outer Beauty)
15.4. 内在美(Inner Beauty )
15.5. 结论
第16章,Linux内核驱动模型:协作的好处
16.1 简单的开始
16.2 进一步简化
16.3 扩展到上千台设备
16.4 小对象的松散结合
第17章 额外的间接层
17.1. 从直接代码操作到通过函数指针操作
17.2. 从函数参数到参数指针
17.3. 从文件系统到文件系统层
17.4. 从代码到DSL(Domain-Specific Language)
17.5. 复用与分离
17.6.分层是永恒之道?
第18章 Python的字典类:如何打造全能战士
18.1. 字典类的内部实现
18.2. 特殊调校
18.3. 冲突处理
18.4. 调整大小
18.5. 迭代和动态变化
18.6. 结论
18.7. 致谢
第19章 NumPy中的多维迭代器
19.1 N维数组操作中的关键挑战
19.2 N维数组的内存模型
19.3NumPy迭代器的起源
19.4 迭代器的设计
19.5 迭代器的接口
19.6 迭代器的使用
19.7 结束语
第20章 NASA火星漫步者任务中的高可靠企业系统
20.1 任务与CIP
20.2 任务需求
20.3 系统架构
20.4 案例分析:流服务
20.5 可靠性
20.6 稳定性
20.7 结束语
第21章 ERP5:最大可适性的设计
21.1 ERP的总体目标
21.2 ERP5
21.3 Zope基础平台
21.4 ERP5 Project中的概念
21.5 编码实现ERP5 Project
21.6 结束语
第22章 一匙污水
第23章 MapReduce分布式编程
23.1 激动人心的示例
23.2 MapReduce编程模型
23.3 其他MapReduce示例
23.4 分布式MapReduce的一种实现
23.5 模型扩展
23.6 结论
23.7 进阶阅读
23.8 致谢
23.9 附录:单词计数解决方案
第24章 美丽的并发
24.2 软件事务内存
24.3 圣诞老人问题
24.4 对Haskell的一些思考
24.6 致谢
第25章 句法抽象:syntax-case 展开器
25.1. syntax-case简介
25.2. 展开算法
25.3. 例子
25.4. 结论
第26章 节省劳动的架构:一个面向对象的网络化软件框架
26.1 示例程序:日志服务
26.2 日志服务器框架的面向对象设计
26.3 实现串行化日志服务器
26.4 实现并行日志服务器
26.5 结论
第27章 以REST方式集成业务伙伴
27.1 项目背景
27.2 把服务开放给外部客户
27.3 使用工厂模式转发服务
27.4 用电子商务协议来交换数据
27.5 结束语
第28章 漂亮的调试
28.1 对调试器进行调试
28.2 系统化的过程
28.3 关于查找的问题
28.4 自动找出故障起因
28.5 增量调试
28.6 最小化输入
28.7 查找缺陷
28.8 原型问题
28.9 结束语
28.10 致谢
28.11 进一步阅读
第29章 把代码当作文章
第30章 当你与世界的联系只有一个按钮
30.1 基本的设计模型
30.2 输入界面
30.3 用户界面的效率
30.4 下载
30.5 未来的发展方向
第31章 Emacspeak:全功能音频桌面
31.1 产生语音输出
31.2 支持语音的Emacs
31.3 对于在线信息的简单访问
31.4 小结
31.5 致谢
第32章 变动的代码
32.1 像书本一样
32.2 功能相似的代码在外观上也保持相似
32.3 缩进带来的危险
32.4 浏览代码
32.5 我们使用的工具
32.6 DiffMerge的曲折历史
32.7 结束语
32.8 致谢
32.9 进一步阅读
第33章 为“The Book”编写程序
33.1 没有捷径
33.2 给Lisp初学者的提示
33.3 三点共线
33.4 不可靠的斜率
33.5 三角不等性
33.6 河道弯曲模型
33.7 “Duh!”——我的意思是“Aha!”
33.8 结束语
33.9 进一步阅读
后记
作者简介

TOP书摘

~第1章 正则表达式匹配器
Brian Kernighan

正则表达式是描述文本模式的表示法,它可以有效地构造一种用于模式匹配的专用语言。虽然正则表达式可以有多种不同的形式,但它们都有着共同的特点:模式中的大多数字符都是匹配字符串中的字符本身,但有些元字符(metacharacter)却有着特定的含义,例如*表示某种重复,而[...]表示方括号中字符集合的任何一个字符。
实际上,在文本编辑器之类的程序中,所执行的查找操作都是查找文字,因此正则表达式通常是像“print”之类的字符串,而这类字符串将与文档中所有的“printf”或者“sprintf”或者“printer paper”相匹配。在Unix和Windows中可以使用所谓的通配符来指定文件名,其中字符*可以用来匹配任意数量的字符,因此匹配模式*.c就将匹配所有以.c结尾的文件。此外,还有许许多多不同形式的正则表达式,甚至在有些情况下,这些正则表达式会被认为都是相同的。Jeffrey Friedl编著的《Mastering Regular Expressions》一书对这一方面问题进行了广泛的研究。
Stephen Kleene在20世纪50年代的中期发明了正则表达式,用来作为有限自动机的表示法,事实上,正则表达式与其所表示的有限自动机是等价的。20世纪60年代年代中期,正则表达式最初出现在Ken Thompson版本的QED文本编辑器的程序设置中。1967年Thompson申请了一项基于正则表达式的快速文本匹配机制的专利。这项专利在1971年获得了批准,它是最早的软件专利之一[U.S. Patent 3,568,156, Text Matching Algorithm, March 2, 1971].
后来,正则表达式技术从QED移植到了Unix的编辑器ed中,然后又被移植到经典的Unix工具grep中,而grpe正是由于Thompson对ed进行了彻底地修改而形成的。这些广为应用的程序使得正则表达式为早期的Unix社群所熟知。
Thompson最初编写的匹配器是非常快的,因为它结合了两种独立的思想。一种思想是在匹配过程中动态地生成机器指令,这样就可以以机器指令执行的速度而不是解释执行的速度来运行。另一种思想是在每个阶段中都尽可能地执行匹配操作,这样无需回朔(backtrack)就可以查找可能的匹配。在Thompson后来编写的文本编辑器程序中,例如ed,匹配代码使用了一种更为简单的算法,这种算法将会在必要的时候进行回朔。从理论上来看,这种方法的运行速度要更慢,但在实际情况中,这种模式很少需要进行回朔,因此,ed和grep中的算法和代码足以应付大多数的情况。
在后来的正则表达式匹配器中,例如egrep和fgrep等,都增加了更为丰富的正则表达式类型,并且重点是要使得匹配器无论在什么模式下都能够快速执行。功能更为强大的正则表达式正在被越来越多地使用,它们不仅被包含在用C语言开发的库中,而且还被作为脚本语言如Awk和Perl的语法的一部分。
1.1 编程实践
在1998年,Rob Pike和我还在编写《The Practice of Programming》(Addison-Wesley)一书。书中的最后一章是“记法”,在这一章中收录了许多示例代码,这些示例都很好地说明了良好的记法将会带来更好的程序以及更好的设计。其中包括使用简单的数据规范(例如printf)以及从表中生成代码。
由于我们有着深厚的Unix技术背景以及在使用基于正则表达式记法的工具上有着近30年的经验,我们很自然地希望在本书中包含一个对正则表达式的讨论,当然包含一个实现也是必须的。由于我们强调了工具软件的使用,因此似乎最好应该把重点放在grep中的正则表达式类型上——而不是,比方说,在shell的通配符正则表达式上——这样我们还可以在随后再讨论grep本身的设计。
然而,问题是现有的正则表达式软件包都太庞大了。grep中的代码长度超过500行(大约10页书的长度),并且在代码的周围还有复杂的上下文环境。开源的正则表达式软件包则更为庞大——代码的长度几乎布满整本书——因为这些代码需要考虑通用性,灵活性以及运行速度;因此,所有这些正则表达式都不适合用来教学。
我向Rob建议我们需要一个最小的正则表达式软件包,它可以很好地诠释正则表达式的基本思想,并且能够识别出一组有用的并且重要类的模式。理想的情况是,所需代码长度只有一页就够了。
Rob听了我的提议后就离开了他的办公室。我现在还记得,一,两个小时后他回来了,并且给了我一段大约30行的C代码,在《The Practice of Programming》一书的第9章中包含了这段代码。在这段代码实现了一个正则表达式匹配器,用来处理以下的模型。
字符 含义
c 匹配任意的字母c
.(句点) 匹配任意的单个字符
^ 匹配输入字符串的开头
$ 匹配输入字符串的结尾
* 匹配前一个字符的零个或者多个出现
这是一个非常有用的匹配器,根据我在日常工作中使用正则表达式的经验,它可以轻松解决95%的问题。在许多情况下,解决正确的问题就等于朝着创建漂亮的程序迈进了一大步。Rob值得好好地表扬,因为他从大量可选功能集中选出了一组非常小但却重要的,并且是明确的以及可扩展的功能。
Rob的实现本身就是漂亮代码的一个极佳示例:紧凑,优雅,高效并且实用。这是我所见过的最好的递归示例之一,在这段代码中还展示了C指针的强大功能。虽然当时我们最关心的是通过使程序更易于使用(同时也更易于编写)来体现良好记法的重要性,但正则表达式代码同样也是阐述算法,数据结构,测试,性能增强以及其他重要主题的最好方式。
1.2 实现
在《The Practice of Programming》一书中,正则表达式匹配器是一个模拟grep程序中的一部分,但正则表达式的代码完全可以从编写环境中独立出来。这里我们并不关心主程序;像许多Unix工具一样,这个程序将读取其标准输入或者一组文件,然后输出包含与正则表达式匹配的文本行。
以下是匹配算法的代码:
/* match: search for regexp anywhere in text */
int match(char *regexp, char *text)
{
if (regexp[0] == '^')
return matchhere(regexp+1, text);
do { /* must look even if string is empty */
if (matchhere(regexp, text))
return 1;
} while (*text++ != '\0');
return 0;
}

/* matchhere: search for regexp at beginning of text */
int matchhere(char *regexp, char *text)
{
if (regexp[0] == '\0')
return 1;
if (regexp[1] == '*')
return matchstar(regexp[0], regexp+2, text);

if (regexp[0] == '$' && regexp[1] == '\0')
return *text == '\0';
if (*text!='\0' && (regexp[0]=='.' || regexp[0]==*text))
return matchhere(regexp+1, text+1);
return 0;
}

/* matchstar: search for c*regexp at beginning of text */
int matchstar(int c, char *regexp, char *text)
{
do { /* a * matches zero or more instances */
if (matchhere(regexp, text))
return 1;
} while (*text != '\0' && (*text++ == c || c == '.'));
return 0;
}

1.3 讨论
函数match(regexp,text)用来判断文本中是否出现正则表达式;如果找到了一个匹配的正则表达式则返回1,否则返回0。如果有多个匹配的正则表达式,那么函数将找到文本中最左边的并且最短的那个。
match函数中的基本操作简单明了。如果正则表达式中的第一个字符是^(固定位置的匹配),那么匹配就一定要出现在字符串的开头。也就是说,如果正则表达式是^xyz,那么仅当xyz出现在文本的开头而不是中间的某个位置时才会匹配成功。在代码中通过把正则表达式的剩余部分与文本的起始位置而不是其他地方进行匹配来判断。如果第一个字符不是^,那么正则表达式就可以在字符串中的任意位置上进行匹配。在代码中通过把模式依次与文本中的每个字符位置进行匹配来判断。如果存在多个匹配,那么代码只会识别第一个(最左边的)匹配。也就是说,如果则在表达式是xyz,那么将会匹配第一次出现的xyz,而且不考虑这个匹配出现在什么位置上。
注意,对输入字符串的推进操作是在一个do-while循环中进行的,这种结构在C程序中使用相对较少。在代码中使用do-while而不是while通常会带来疑问:为什么不在循环的起始处判断循环条件,而是在循环末尾当执行完了某个操作之后才进行判断呢?不过,这里的判断是正确的:由于*运算符允许零长度的匹配,因此我们首先需要判断是否存在一个空的匹配。
大部分的匹配工作是在matchhere(regexp,text)函数中完成的,这个函数将判断正则表达式与文本的开头部分是否匹配。函数matchhere把正则表达式的第一个字符与文本的第一个字符进行匹配。如果匹配失败,那么在这个文本位置上就不存在匹配,因此matchhere将返回0。然而,如果匹配成功了,函数将推进到正则表达式的下一个字符和文本的下一个字符继续进行匹配。这是通过递归地调用matchhere函数来实现的。
由于存在着一些特殊的情况,以及需要设置终止递归的条件。因此实际的处理过程要更为复杂些最简单的情况就是,当正则表达式推进到末尾时(regexp[0] == '\0'),所有前面的判断都成功了,那么这个正则表达式就与文本匹配。
如果正则表达式是一个字符后面跟着一个*,那么将会调用matchstar来判断闭包(closure)是否匹配。函数matchstar(c, regexp, text)将尝试匹配重复的文本字符c,从零重复开始并且不断累加,直到匹配text的剩余字符,如果匹配失败,那么函数就认为不存在匹配。这个算法将识别出一个“最短的匹配”,这对简单的模式匹配来说是很好的,例如grep,这种情况下的主要问题是尽可能快地找到一个匹配。而对于文本编辑器来说,“最长的匹配”则是更为直观,且肯定是更好的,因为通常需要对匹配的文本进行替换。在目前许多的正则表达式库中同时提供了这两种方法,在《The Practice of Programming》一书中给出了基于本例中matchstar函数的一种简单变形,我们在后面将给出这种形式。
如果在正则表达式的末尾包含了一个$,那么仅当text此时位于末尾时才会匹配成功:
if (regexp[0] == '$' && regexp[1] == '\0')
return *text == '\0';
如果没有包含$,并且如果当前不是处于text字符串的末尾(也就是说,*text!='\0')并且如果text字符串的第一个字符匹配正则表达式的第一个字符,那么到现在为止都是没有问题的;我们将接着判断正则表达式的下一个字符是否匹配text的下一个字符,这是通过递归调用matchhere函数来实现的。这个递归调用不仅是本算法的核心,也是这段代码如此紧凑和整洁的原因。
如果所有这些匹配尝试都失败了,那么正则表达式和text在这个位置上就不存在匹配,因此函数matchhere将返回0。
在这段代码中大量地使用了C指针。在递归的每个阶段,如果存在某个字符匹配,那么在随后的递归调用中将执行指针算法(例如,regexp+1 and text+1),这样在随后的函数调用中,参数就是正则表达式的下一个字符和text的下一个字符。递归的深度不会超过匹配模式的长度,而通常情况下匹配模式的长度都是很短的,因此不会出现耗尽内存空间的危险。
1.4 其他的方法
这是一段非常优雅并且写得很好的代码,但并不是完美的。我们还可以做哪些其他的工作?我可能对matchhere中的操作进行重新安排,在处理*之前首先处理$。虽然这种安排不会对函数的执行带来影响,但却使得函数看上去要自然一些,而在编程中一个良好的规则就是:在处理复杂的情况之前首先处理容易的情况。
不过,通常这些判断的顺序是非常重要的。例如,在matchstar的这个判断中:
} while (*text != '\0' && (*text++ == c || c == '.'));
无论在什么情况下,我们都必须推进text字符串中的一个或多个字符,因此在text++中的递增运算一定要执行。
该代码对终止条件进行了谨慎的处理。通常,匹配过程的成功与否,是通过判断正则表达式和text中的字符是不是同时处理完来决定的。如果是同时处理完了,那么就表示匹配成功,如果其中一方在另一方之前被处理完了,那么就表示匹配失败。在下面这行代码中很明显地说明了这个判断。
if (regexp[0] == '$' && regexp[1] == '\0')
return *text == '\0';
但在其他的情况下,还有一些微妙的终止条件。
如果在matchstar函数中需要识别最左边的以及最长的匹配,那么函数将首先识别输入字符c的最大重复序列。然后函数将调用matchhere来尝试把匹配延伸到正则表达式的剩余部分和text的剩余部分。每次匹配失败都会将cs的出现次数减1,然后再次开始尝试,包括处理零出现的情况:
/* matchstar: leftmost longest search for c*regexp */
int matchstar(int c, char *regexp, char *text)
{

char *t;

for (t = text; *t != '\0' && (*t == c || c == '.'); t++)
;
do { /* * matches zero or more */
if (matchhere(regexp, t))
return 1;
} while (t-- > text);
return 0;
}

我们来看一下正则表达式(.*),它将匹配括号内任意长度的text。假设给定了text:
for (t = text; *t != '\0' && (*t == c || c == '.'); t++)
从开头位置起的最长匹配将会识别整个括号内的表达式,而最短的匹配将会停止在第一次出现右括号的地方。(当然,从第二个左括号开始的最长匹配将会延伸到text的末尾)
1.5 构建
《The Practice of Programming》一书主要讲授良好的程序设计。在编写该书时,Rob和我还在贝尔实验室工作,因此我们知道在课堂上使用这本书有什么样的效果。令人高兴的是,我们发现这本书中的某些内容在课堂上确实有着不错的效果。从2000年教授程序设计中的重点要素时,我们就使用了这段代码。
首先,这段代码以一种全新的形式展示了递归的强大功能及其带来的整洁代码。它既不是另一种版本的快速排序(或者阶乘!)算法,也不是某种树的遍历算法。
这段代码同时还是性能试验的一个很好示例。其性能与系统中的grep并没有太大的差异,这表明递归技术的开销并不是非常大的,因此没有必要对这段代码进行调整。
此外,这段代码还充分说明了优良算法的重要性。如果在模式中包含了几个.*序列,那么在简单的实现中将需要进行大量的回溯操作,并且在某些情况下将会运行得极慢。
在标准的Unix grep中有着同样的回朔操作。例如,下面这个命令:
grep 'a.*a.*a.*a.a'
在普通的机器上处理一个4 MB的文本文件要花费20秒的时间。
如果某个实现是基于把非确定有限自动机转换为确定有限自动机,例如egrep,那么在处理恶劣的情况时将会获得比较好的性能;它可以在不到十分之一秒的时间内处理同样的模式和同样的输入,并且运行时间通常是独立于模式的。
对于正则表达式类型的扩展将形成各种任务的基础。例如:
1.增加其他的元字符,例如+用于表示前面字符的一个或多个出现,或者?用于表示零个或一个字符的匹配。还可以增加一些方式来引用元字符,例如\$表示在模式中的$字符。
2.将正则表达式处理过程分成编译阶段和执行阶段。编译阶段把正则表达式转换为内部形式,使匹配代码更为简单或者使随后的匹配过程运行得更为迅速。对于最初设计中的简单的正则表达式来说,这种拆分并不是必须的,但在像grep这样的程序中,这种拆分是有意义的,因为这种类型的正则表达式要更为丰富,并且同样的正则表达式将会用于匹配大量的输入行。
3.增加像[abc]和[0-9]这样的类型,这在传统的grep中分别匹配a或b或c和一个数字。可以通过几种方式来实现,最自然的方式似乎就是把最初代码中的char*变量用一个结构来代替:

typedef struct RE {
int type; /* CHAR, STAR, etc. */
int ch; /* the character itself */
char *ccl; /* for [...] instead */
int nccl; /* true if class is negated [^...] */
} RE;
并且修改相应的代码来处理一个结构数组而不是处理一个字符数组。在这种情况下,并不一定要把编译阶段从执行阶段中拆分出来,但这里的拆分过程是非常简单的。如果学生们把匹配代码预编译成这个结构,那么总会比那些试图动态地解释一些复杂模式数据结构的学生要做得更好。
为字符类型编写清晰并且无歧义的规范是件有难度的工作,而要用代码完美地实现处理更是难上加难,这需要大量的冗长并且晦涩的编码。随着时间的推移,我简化了这个任务,而现在大多数人会要求像Perl那样的速记,例如\d表示数字,\D表示非数字,而不再像最初那样在方括号内指定字符的范围。
4.使用不透明的类型来隐藏RE结构以及所有的实现细节。这是在C语言中展示面向对象编程技术的好方法,不过除此之外无法支持更多的东西。在实际情况中,将会创建一个正则表达式类,其中类中成员函数的名字像RE_new( )和RE_match( )这样,而不是使用面向对象语言的语法。
5.把正则表达式修改为像各种shell中的通配符那样:匹配模式的两端都被隐含地固定了,*匹配任意数量的字符,而?则匹配任意的单个字符。你可以修改这个算法或者把输入映射到现有的算法中。
6.将这段代码转换成Java。最初的代码很好地使用了C指针,并且把这个算法在不同的语言中实现出来是一个很好的实践过程。在Java版本的代码中将使用String.charA(使用索引而不是指针)或者String.substring(更接近于指针)。这两种方法都没有C代码整洁,并且都不紧凑。虽然在这个练习中性能并不是主要的问题,但有趣的是可以发现了Java版本比C版本在运行速度上要慢6到7倍。
7.编写一个包装类把这种类型的正则表达式转换成Java的Patter类和Matcher类,这些类将以一种与众不同的方式来拆分编译阶段和匹配阶段。这是适配器(Adapter)模式或者外观(Fa?ade)模式的很好示例,这两种模式用来在现有的类或者函数集合外部设置不同的接口。
我曾经大量地使用了这段代码来研究测试技术。正则表达式非常的丰富,而测试也不是无足轻重的,但正则表达式又是很短小的,程序员可以很快地写出一组测试代码来执行。对于前面所列出的各种扩展,我要求学生用一种紧凑的语言写出大量的测试代码(这是“记法”的另一种示例),并且在他们自己的代码中使用这些测试代码;自然我在其他学生的代码中也使用了他们的测试代码。
1.6 小结
当Rob Pike最初写出这段代码时,我被它的紧凑性和优雅性感到惊讶——这段代码比我以前所想像的要更为短小并且功能也更为强大。通过事后分析,我们可以看到为什么这段代码如此短小的众多原因。
首先,我们很好地选择了功能集合,这些功能是最为有用的并且最能从实现中体现出核心思想,而没有多余的东西。例如,虽然固定模式^和$的实现只需要写3~4行代码,但在统一处理普通情况之前,它展示了如何优雅地处理特殊情况。闭包操作*必须出现,因为它是正则表达式中的基本记号,并且是提供处理不确定长度的惟一方式。增加+和?并不会有助于理解,因此这些符号被留作为练习。
其次,我们成功地使用了递归。递归这种基本的编程技巧通常会比明确的循环带来更短、更整洁的以及更优雅的代码,这也正是这里的示例。从正则表达式的开头和tex的开头剥离匹配字符,然后对剩余的字符进行递归的思想,模仿了传统的阶乘或者字符串长度示例的递归结构,但这里是用在了一种更为有趣和更有用的环境中。
第三,这段代码的确使用了基础语言来达到良好的效果。指针也可能被误用,但这里它们被用来创建紧凑的表达式,并且在这个表达式中自然地表达了提取单个字符和推进到下一个字符的过程。数组索引或者子字符串可以达到同样的效果,但在这段代码中,指针能够更好的实现所需的功能,尤其是当指针与C语言中的自动递增运算和到布尔值的隐式转换结合在一起使用时。
我不清楚是否有其他的方法能够在如此少的代码中实现如此多功能,并且同时还要提供丰富的内涵和深层次的思想。~

TOP插图


插图
插图

TOP 其它信息

装  帧:平装

页  数:599

开  本:16开

纸  张:胶版纸

正文语种:中文

加载页面用时:75.2051