浅谈#define预编译指令在J2ME项目管理中的作用

#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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值