delphi2007 教程

delphi2007 教程

首页 新随笔 联系 聚合 管理
  1013 Posts :: 0 Stories :: 28 Comments :: 0 Trackbacks

声明:本文乃 熊恒(beta) 原创,如要转载请保持文章完整。 深入研究 case 语句 --- by 熊恒(beta) 不小心在论坛上引发舌战,当中涉及到了 if then else if 的连续使用和直接使 用 case 的情况下,效率孰高孰低的问题。今天的话题就从解决这个争论开始吧, 在最前面,我不想多说什么,实践是检验真理的唯一标准,咱们用例子说话: // 例一 procedure TForm1.Button1Click(Sender: TObject); var i: Integer; begin i := 7; if i = 0 then ShowMessage('0') else if i = 1 then ShowMessage('1') else if i = 5 then ShowMessage('5') else if i = 10 then ShowMessage('10') else if i = 7 then ShowMessage('7') else if i = 9 then ShowMessage('9') else ShowMessage('X'); end; // 例二 procedure TForm1.Button2Click(Sender: TObject); var i: Integer; begin i := 7; case i of 0: ShowMessage('0'); 1: ShowMessage('1'); 5: ShowMessage('5'); 10: ShowMessage('10'); 7: ShowMessage('7'); 9: ShowMessage('9'); else ShowMessage('X'); end; end; 咱们先从例一开始(把精彩的留在后面:-p)。这是典型的 if then else if 使 用,看一下它对应的汇编代码: i := 7; mov eax, $00000007 if i = 0 then test eax, eax // 可以看到编译器很聪明的,和 0 的比较被优化了 jnz +$0b // 若不为 0 则跳转到下一个 if ShowMessage('0') ... // 省略不必要的部分 else if i = 1 then cmp eax, $01 jnz +$0c // 若不为 1 则跳转到下一个 if ShowMessage('1') ... else if i = 5 then cmp eax, $05 jnz +$0c // 若不为 5 则跳转到下一个 if ... // 后面部分不必再看了 可以看到它是一个 if 一个 if 地“规规矩矩”地进行顺序比较的,要是你一共 有 1M 个可能性,并且刚好所有的 if 都不成立(没有找到对应选项),那就要 轮到最后一个 else,可想而知,要进行多少次痛苦的比较。 再看一看例二,采用的是 case 语句,还是先看汇编代码: i := 7; mov eax, $00000007 case i of cmp eax, $0a jnbe +$75 jmp dword ptr [eax * 4 + $0045239d] ... // 后面虽然还有但是由于上面这个 jmp,就不用看了 这最后三句汇编代码非常值得我们仔细推敲!其实 case i of 从决策到跳转一共 只用了三条汇编代码!优美! 先看第一句:cmp eax, $0a 从前面一句可以看到,eax 里面就是我们的 i 的值,那么把它和 $0a 相比 又是什么意思呢?$0a 的十进制值就是 10,看看前面的代码,的确有 10 这个选 项。why 10? 这肯定是首先浮现在你脑子里的问题,如果你仔细看,会发现,10 是几个选项里面最大的一个。(这正是我为什么把 10 这个最大的选项排在中间, 而不是最后,就是为了说明这个 $0a 是指的所有选项的最大值,而不是最后一个 选项的值!) 第二句:jnbe +$75 很好理解,如果 i 大于所有选项中最大的(即找肯定不到对应选项),则直 接跳转到 else 部分。回想前面的例一,要是出现找不到的情况,将是人生(不, 应该说是 CPU 的)一大悲哀 :-) 好了,前面已经排除“异己”了,现在来看它是如何找到“党组织”的。 第三句:jmp dword ptr [eax * 4 + $0045239d] 跳转了。跳到哪里了?以从 eax * 4 + $0045239d 单元里面取出的值作为跳 转的目的。有汇编基础的朋友应该可以看出,$004523f 是一个基地址,eax,我们 的 i,是可变的,也就是说这是一个查表的过程!类似于数组的使用 arr[i]。那 它查出来那么这个神秘的跳转目的地到底在哪里呢?一个单步(Step Over)就看 见光标停留在了 ShowMessage('7') 的位置。啊?就,找到组织了? 看一下它是怎么找到目的的。首先,把 i 作为一个索引,用查表的方式找出一 个数,然后它就说这个数就是我们梦寐以求的入口地址,直接跳过去了(晕~, 欲知详情,往下看)。因此,不管 i 在所有选项中处于什么位置,总是可以通过 这样的一个简单的查表,得到目的地址,只用一条跳转语句就过去了。 因此得出今天的结论:case 语句的效率高!依据是,if then else if 的平均时 间复杂度是 O(n/2),而 case 的时间复杂度是 O(1)。 当然了,如果本文就这样完了,那我肯定被读者的鸡蛋爆头——谜团尚未解开。 别急嘛,下面接着来: 首先当然是为什么能够通过一个简单的查表找到目的地址。我是这样理解的: Delphi 在编译的时候就建立了一个地址表。把所有选项的入口地址存入这个表中 (既然存的是地址,DWord 类型的,所以前面的第三句汇编代码里面有一个乘以 4 的动作)。这个表结构大致是这样的,第 0 项中放的是值为 0 的选项的入口 地址;第 1 项中放的是值为 1 的选项的入口地址。因此,只要有了这个表的基 地址(显然,这可以在编译时确定),事情就变得容易了。 假设 i 等于 7,于是我只要到那个表里面找出第 7 项的值,那自然就是值为 7 的选项的入口地址。呵呵,原来就这么简单。 现在你明白了为什么 case 语句只能用于顺序类型了吧(要不然怎么根据值建 这个表),它不是不能实现对其他类型的使用,而是不愿意,因为这样我们可以 获得极高的效率。我可不想看见 case 语句变成简单的 if then else if 的封装。 其实问题并没有解决完,回头看看刚才的那三行汇编代码的前两行,那仅仅是 排除了 i 大于最大选项值的情况。但是要是 i 为 6 呢?这是它虽然小于最大选 项值 10,但是它仍然不存在于选项表中。那,程序就顺序执行咯,照样根据 i 取出偏移索引值。那这个索引值会指引它跳向何处呢?当然是 else 部分的入口 地址咯(如果有 else 的话,如果没有,则跳出 case 到后面紧接着的代码行呗)。 那但是前面建表的时候,没有 6 这个选项,在表中第 6 项应该没有相应的值啊。 呵呵,这好办,把表里面那些没有对应选项值的“空位”全部填上 else 部分的入 口地址偏移值(若没有 else 部分则填 case 后面紧接着的代码行的地址)就可以 啦。好办法! 还有问题,要是选项里面有负数怎么办?怎么建表?容易,直接以最小的选项 值为基础,所有选项值减去这个负数,于是,选项值又从 0 开始了(当然,查表 的时候 i 也做相应变化)。那要是 i 是负数呢?仔细看那三句汇编的第二句: jnbe +$7c,jnbe 是判断无符号的,i 要是负数,把它看成无符号数,最高位就 是 1,自然大于所有的有符号正数(最高位为 0),而被当成异己,直接排除了。 最后一个问题,这个神秘的表到底建在什么地方呢?如果我的眼神没有退化的 话,它应该紧接着 case i of 即那三行汇编的后面。表后面就是所有选项的入口。 不过,在最大项和最小项之间的差值大于等于 15 的时候,查找地址的动作略有 不同,这应该和表所占空间有关系吧(这种情况没有深入研究,头有点晕了:-p)。 悄悄告诉你,当选项数目很少(小于 5)的时候,不会建立这个表,而是以另 外一种比较有趣的方法实现,你若有兴趣可以自己去看一下。还有,当最小选项 值大于 3 的时候,Delphi 会自动把所有选项的值减去该最小项的值,使最小项 从 0 开始,这样可以节约入口地址表的空间(当然,在用 i 来查表的时候,也 要先减去该值,否则就不对了)。 不管是谁,想到这个办法来实现多分支结构,同时能够极大地提高效率,的确是 个了不起的人。这也进一步让我认识到,编程的确是一项艺术。 差不多应该讲完了(我所知道的),就是不知道讲清楚没有,有问题大家一起探 讨吧。 原文:http://www.delphibbs.com/delphibbs/dispq.asp?lid=1406114

继续阅读《beta 的第三篇心得:深入研究 case 语句》的全文内容...



--------------------------
新闻:鲍尔默:不解Google为何推出两款操作系统
网站导航: 博客园首页  新闻  .NET频道  社区  博问  闪存  找找看
posted on 2009-07-15 10:28 delphi2007 阅读(501) 评论(0)  编辑 收藏 引用
只有注册用户登录后才能发表评论。