本章中各种技巧的侧重点在于使程序变得更加短小简洁。常言道,“有得必有失”,这些技巧在带来简洁的同时也会带来混乱。
首先,来看一下#define使用不当时的后果。
例14-11
运行程序之前,先来猜一下输出结果吧,当然希望它是这样的:
那么,运行一下程序,就会发现理想与现实的差距——实际的输出结果是这样的:
这是为什么?仔细回想一下编译宏指令的定义,#define是在编译前处理的。现在程序出了问题,手工处理一下#define看看,就可以找出事实的真相,于是手工替换掉所有的POW2,得到这样的程序段:
这样,输出结果就一目了然了:
找到问题所在,自然也就可以解决——将#define那一行改成:
即可。
再来看一下超级表达式使用不当时的后果:
例14-12 (无意义的程序,仅供测试)
在Dev-C++5.11,DEBUG模式默认设置下编译运行,输入:
虽然期待的输出是这样的:
但是实际的输出相当匪夷所思:
直观看来,问题似乎出在这个表达式上:
回想一下“||”的性质——只要一个操作数非0,结果就是1。一般情况下这么做并不会出现问题,但当“||”的操作数是带有副作用的表达式时就不一样了。所以,一般的解决方式是避免使用这类表达式。
混乱的“简洁”不只发生在使用不当的情况,将一个完全正确的#define和一个完全正确的超级表达式结合起来,有时也会发生不可预料的后果,比如下面这段程序。(www.xing528.com)
例14-13
在Dev-C++5.11中,DEBUG模式默认设置时输出为49 7。在TC++3.0中,默认编译模式时输出为42 7。而事实上,当时期望的结果是36 6。
回想一下编译宏指令的定义,#define是在编译前处理的,也就是说,实际上编译器得到的程序是这样的:
注意到(++x)∗(++x)这个表达式,因为++x的副作用,不同的处理方式会得到不同的结果。但无论如何,都不可能得到最初想要的结果——36 6。
正是这个原因,通常都要大写来将#define出来的“函数”与一般的函数相区分,并且尽量避免将有副作用的表达式传递给这种“函数”。
最后,有一个常见的认识误区:#define或超级表达式可以加快程序的执行速度。实际上,对于超级表达式和对应的一般写法,编译器最终生成的机器代码不会有本质上的区别,因而不能指望有速度上的改善。#define的宏相对于函数而言虽然省去了调用时的消耗,但C++的inline机制完全可以抵消#define的这一优势。而#define因为原理的限制,一旦使用不当,很可能在“参数传递”的过程中造成很不必要的重复运算,请看下例。
例14-14 二分法求乘幂。
输入1.000 000 01 100 000 000,用#define写法和函数写法分别运行一次,即可看到速度差别——#define比函数写法要慢得多。
注意到在使用#define写法时,pow函数会被预处理器变成这样:
于是,pow的递归调用由一次变成了两次,了解复杂度理论的读者可以分析出程序的复杂度由O(log b)变成了O(b),也就是说,#define导致了运行时间数量级的增加。
实际上,导致这种后果的根本原因和例14-13是相同的——用有副作用的表达式作了宏的被使用两次或以上的参数。只要避免这种情况就可以防止此类现象的发生。但即便如此,#define可能导致重复运算这一事实是无法改变的(比如说POW2(x+1)仍然会导致x+1被计算两次),在不能通过编译器的代码优化抵消这一后果,而时间效率又极为重要的场合,此类问题还是需要特别注意的。
1.试用#define和printf函数实现puts函数的功能,并写一个C++程序测试之。
2.试用一个循环体只有一条输出语句的for循环,不定义main以外的函数,输出斐波那契数列的前n项。
3.写一个.h文件,使其包含一个如下的函数:
该文件被#include一次时,调用magic()输出“once”(未出现),#include两次时输出“twice”。并写一个C++程序测试之。
4.写一个程序,使得该程序不能通过编译,但在紧接该程序所有#include之后,加入第1题的参考答案写法1中的#define语句,然后所得程序能够通过编译,并观察运行时输出的结果。该程序中不得使用除#include标准头文件之外的编译宏指令。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。