#define对C程序员来说并不是什么新鲜的东西,大多数程序员甚至会脱口而出:不就是宏吗?对,#define确实可以用作宏,减少代码的行数及增加代码的可读性。然而,#define的用途并不仅仅如此:把#define和#ifdef,#endif等预编译指令混合使用后,会大大增强工程的灵活性。
在进入讨论之前,我们先看看相关指令的列表及其各自的作用:
#define DEF_A - 定义DEF_A,使其存在(但并没有特定值)
#define DEF_A 22 - 定义DEF_A,使其值为22
#undef DEF_A - 取消DEF_A的定义
#if DEF_A - 如果DEF_A的条件满足,则编译以下代码
#ifdef DEF_A - 相当于#if defined DEF_A。如果定义了DEF_A,则编译以下代码
#ifndef DEF_A - 如果没有定义DEF_A,则编译以下代码
#if defined DEF_A | defined DEF_B | defined DEF_C
- 如果一定了DEF_A或DEF_B或DEF_C,则编译以下代码
#elif DEF_B - 如果定义了DEF_B,则编译以下代码
#else - 如果以上的if语句都不满足,则编译以下代码
#endif - 一个定义块的结束标志
说到这里,也许有人会提出异议了:#define指令无法在Java里使用。对,Java的编译器确实无法使用#define,但我们只要把其他的外部预编译程序与当前工程连接起来,那就可以享有C++的优势,同时又能编译和调试Java了。这个神奇的外部预编译程序叫cl.exe,在任何版本的Visual Studio里都会自带这个程序。由于关于本程序的使用并不在本文讨论范围,读者可以自行研究该程序,在这里,只给出参考命令参数:
cl *.java /P /EP /C /nologo /FI mydefines.java
本命令把当前目录下所有的.java文件应用mydefines.java里的定义进行预编译,预编译的输出文件为.i扩展名的文本文件,只要再加上一个自动改名的批处理就能把输出文件变回.java。此外,如果读者对cl.exe的预编译功能有所不满――例如它会直接把宏变成了宏的值,降低输出代码的可读性――可以尝试写一个自己的预编译程序。可以的话,请给我一份您的改良版本的cl.exe :-)
一切就绪,现在让我们来看看在以上的预编译指令在实际工程中的作用:
应用一. 在对应多平台的工程,需要对应每个工程实现一种定义,而且需要程序在最小改动的情况下适应各工程――例如针对多种不同手机,屏幕尺寸和各自Native API并不完全相同的情况下,如果对应每种手机都写一种代码,那很明显的是浪费时间,这时,我们可以借助#define来定义工程了。
首先,我们可以把手机的屏幕分辨率归纳成一种定义,例如:
#define RES_176_208 // 176x208的分辨率,例如Nokia 6680和N70
#define RES_240_300 // 240x300的分辨率,例如三星Z510
#define RES_352_416 // 352x416的分辨率,例如Nokia N90
此外,根据各厂家的独特API,可以定义:
#define NOKIA_API
还有,某些手机的内存特别小,针对这些机型可以定义:
#define LOW_MEMORY
现在,根据我们具体的情况来定义各手机的工程了,例如:
#if defined NOKIA_6680 | NOKIA_N70
#define RES_176_208 // 176x208的分辨率,例如Nokia 6680和N70
#define NOKIA_API // Nokia独有的API,部分索爱手机也支持
#elif defined SAMSUNG_Z510 | defined SAMSUNG_Z500
#define RES_240_300 // 240x300的分辨率,例如三星Z510, Z500
#define LOW_MEMORY
#elif defined NOKIA_N90
#define RES_352_416 // 352x416的分辨率,例如Nokia N90
#define NOKIA_API
#else
#define RES_176_208 // 176x208的分辨率,例如Nokia 6680和N70
#define LOW_MEMORY
#endif
在以上的定义完成之后,我们的工程定义就变得相当的简单了:
当需要编译NOKIA_6680的工程时,我们只需要把#define NOKIA_6680前面的注释符“//”去掉,把其他手机工程的定义注释,就能得到相应的工程设置。例如:
#define NOKIA_6680
//#define NOKIA_N70
//#define SAMSUNG_Z510
//#define SAMSUNG_Z500
//#define NOKIA_N90
应用二. 在程序里,有时需要对应不同的平台插入相应的处理语句。例如一个对应MIDP2,我们有时需要做一些额外的处理以取得更多的性能优势--在程序的初始化过程,我们可以把Canvas的Super Class设置成Nokia的FullCanvas:
#ifdef NOKIA_API
public class TheGame extends FullCanvas implements Runnable {
#else
public class TheGame extends Canvas implements Runnable {
#endif
......
}
或在只有小内存的手机上,我们需要Disable一些占用内存太多的部分,这时可以“当没有定义LOW_MEMORY时,程序才加载某些资源”:
#ifndef LOW_MEMORY
// 在这里加载耗内存多,但又不是必须要存在的东西
#endif
应用三. 在工程结束阶段,程序员需要很小心自己加入的代码以避免产生任何新的Bug,这时可以用#define来控制自己的代码段,如果发现新代码无效或者产生Bug,马上可以取消新的代码:
在项目公用的头文件加入:#define FIX_BUG_1024
在修复Bug或加入新的代码段时,用FIX_BUG_1024来括住自己的新代码:
#ifdef FIX_BUG_1024
// 新的代码
#endif // #ifdef FIX_BUG_1024
当发现该段代码无效或产生新的Bug或用途不明时,可以随时注释掉头文件里的#define FIX_BUG_1024,则立即可以把代码改回之前的样子。但要注意:在这个过程中,不要把自己的测试代码#ifdef FIX_BUG_1024等上传至CVS等代码融合的服务器,以免造成同事的混淆和不便。
然而,虽然使用预编译指令能让代码灵活,但不适当的使用却会引起其他不必要的麻烦,甚至大大的影响程序的可读性。例如,在IDE里编辑的时候,预编译的部分会按照正常颜色来显示那些不会被编译的部分(相反,大范围注释/**/的代码,则会改变颜色,使程序员容易辨认):
#if 0
public void toString() {
System.out.println( “We defined 0.” );
}
#else
public void toString() {
System.out.println( “We didn't define 0.” );
}
#endif
以上的所有语句都会以正常的代码颜色来显示,然而,事实上第一个toString函数并不会被编译到最终代码里--如果这个函数的长度超过1个屏幕,程序员很有可能就看不到上下的#if,#else,#endif而不知道该函数不被编译,以致他们浪费大量时间修改第一个toString函数后,却发现输出的信息始终没有改变。
因此,使用预编译指令时,请遵守以下几点原则:
1. 在#if和下一个指令之间,包括尽量少的代码。例如,尽量不要把整个函数都包含进预编译指令中,而采用分散的定义:
public void render() {
#ifdef NOKIA_6680
// render对应Nokia6680的代码
#elif defined SAMSUNG_Z500
// render对应三星Z500的代码
#endif
// 其他渲染代码
}
而不是通过定义2个不同的Render函数来分别Render 6680和Z500。
2. 比较长、而且容易混淆的定义段里,在#else和#endif后面用注释加上它的主#if语句的定义:
#ifdef DEF_AA
// DEF_AA的相关代码
#ifdef DEF_CC
// DEF_CC的相关代码
#ifdef DEF_DD
// DEF_DD的相关代码
#endif // #ifdef DEF_DD
#endif // #ifdef DEF_CC
#elif defined DEF_BB
// DEF_BB的相关代码
#else // #ifdef DEF_AA
// 公用代码
#endif // #ifdef DEF_AA
3. 对于临时性的定义,要在不需要之后,立即用#undef取消。
4. 对于互相冲突的定义,可以使用互斥性的定义方式。例如,定义了一种手机后,另外一种手机的定义不可能同时存在(如果两种手机有公用的属性,请把公用属性定义成子属性),这时需要用互斥性的定义以保证每次只有一种手机被定义:
#define NOKIA_6680
#ifndef NOKIA_6680
#define NOKIA_N70
#endif
#if ! (defined NOKIA_6680 | defined NOKIA_N70)
#define SAMSUNG_Z510
#endif
显然,这种定义并不适合有大量并列定义的时候使用――这时定义的信息会越来越长――不过在元素数目较少的情况下,还是不失为一种好的解决方案。
在本文中,我们粗略的学习了多平台J2ME项目的工程管理方法。但事实上,真正庞大的工程,包含了除代码以外的海量资源,例如图片、文本文档等等,使用#define预编译指令只能控制代码和代码对资源的应用部分,对工程的整体整合和控制还是帮助有限的。对于大型工程的整体控制,由于篇幅的限制,将留在下一篇文章中论述。谢谢大家的关注,有任何疑问,欢迎留言提问。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/kyosukeno1/archive/2006/06/20/816799.aspx