那么,接下来我就先来介绍一下常用的宏技巧吧(听起来有点老套,不过请耐心看下去,会有收获的)。
1. 头文件包含守护
出现在某.h或.hpp .hxx中,
#ifndef __HTML_PAGE_BUILDER_H__
#define __HTML_PAGE_BUILDER_H__
// 一大堆声明出现在这里
#endif // #ifndef __HTML_PAGE_BUILDER_H__ (加这样的注释是一种好习惯呕~)
2. 文件域内/外的常量定义
为什么说“文件域内/外”呢?因为宏(Macro)根据它定义所在单元(文件)不同,将拥有不同的有效域,或叫不同的外部视野。
a) 定义在.c/.cpp/.cxx源文件内
在.c类似的源文件编译单元内,宏只能被该单元可见。也就是说在该单元被第一次预编译时,会先找出单元所属所有的宏,然后在该单元可见,在该单元编译结束时,所有这些宏又全部消失掉。
从而,我们可以做个实验,以VC的COMPILER为例:
// my_a.c
#define __AVAIL_ONLY_IN_MY_A_C__
// my_b.c
#if defined(__AVAIL_ONLY_IN_MY_A_C__)
#error Error will never occurred
#endif // #if defined(__AVAIL_ONLY_IN_MY_A_C__)
不管以怎样的编译顺序,在Compile my_b.c时都不会发生错误。
那这时候您想问,“请问宏的名字能不能在单元内被复用?”,我可以回答说,“能,但有限制”,如下面的用法:
// my_a.c
#define SOME_FACTOR (1.0)
// …
{
// …
some_var = calc_with( SOME_FACTOR );
}
#undef SOME_FACTOR
#define SOME_FACTOR (2.0)
// …
{
// …
some_var = calc_with( SOME_FACTOR );
}
#undef SOME_FACTOR
明白了吧,当您的代码结足够好,逻辑足够清楚时,也可以尝试一下这种用法。
宏定义是没有位置限制的,也可以尝试一下下面的BT用法:
void some_func( void )
{
#if defined(SOME_FACTOR)
#error OH MY GOD, it make the codes so confused!
#endif // #if defined(SOME_FACTOR)
#define SOME_FACTOR (1.0)
// …
calc_with( SOME_FACTOR );
// …
some_var = calc2_with( SOME_FACTOR );
some_var = calc3_with( SOME_FACTOR );
// …
#undef SOME_FACTOR
}
这样做的优点就是,在IDE非常弱智的话可以在任意代码处看到宏的值是什么,修改起来比较方便。
如果这些宏通常只要你在自己的模块中调试时所用,并且不想改动任何其他人的头文件时,也是不错的办法。
b) 定义在.h/.hxx/.hpp头文件内
这个就不多说了,被全局引用的宏定义。通常是一些Feature开关的设定。比如:
// xxx_global_features.h
#define __PC_SIDE_DEBUG__
#if defined(__ON_HARDWARE__)
#define __PC_SIDE_DEBUG__
#endif // #if defined(__ON_HARDWARE__)
3. 简写
所以要简写……
// xxx.h
#define XXX_CONTRACT_FLAG_TAG_MUST_BE_PRESENT (0×0000 << 0)
#define XXX_CONTRACT_FLAG_TAG_MUST_BE_PAIRED (0×0000 << 1)
// xxx.c
#include “xxx.h”
#define __CF_PRST XXX_CONTRACT_FLAG_TAG_MUST_BE_PRESENT
#define __CF_PAIR XXX_CONTRACT_FLAG_TAG_MUST_BE_PAIRED
{
// …
xxx_flag = __CF_PRST|__CF_PAIR;
}
如果这招从来没用过……别跟我讲你是用C语言写程序的……。
3. 代码和静态数据分离
先声明,我以确在别人的源代码中见过这种用法,不过在我看到之前我本人就已经在用了…… 不能说是我发明的,但的确是英雄所见。
举个例子,比如我们有一个算法库,里面有定义很多很多很多数组,在一个特殊的OS A(Nucleus)上出于加速及优化方法考虑,一定要定义为静态数组。但同时,由于我要移植到另一个特别OS B上(如Symbian),不允许这样使用。因此,我们要把所有代码里所有的静态数组都找出来,再在栈上定义它们。
这样的话,我们将有两份或多份代码来同时维护,如:
// my_algo.c for OS A
static uint32_t __some_global_datas[] = {
0, 1, 2, 3, 4, 5 ….
0, 1, 2, 3, 4, 5 ….
….
};
// my_algo.c for OS B
{
uint32_t __some_global_datas[] = {
0, 1, 2, 3, 4, 5 ….
0, 1, 2, 3, 4, 5 ….
….
};
}
有一个办法可以只通过一个宏,将两份代码合并到一起,而且不会使代码冗长,最可能不是最好的办法。
1) 先定义一个OS Feature的头文件:
// which_os.h
//#define __OS_A__
#define __OS_B__
2) 再定义一个专门用来存数组数据的头文件:
// array_datas.h
#if defined(__MY_ALGO_C__)
0, 1, 2, 3, 4, 5 ….
0, 1, 2, 3, 4, 5 ….
….
#endif // #if defined(__MY_ALGO_C__)
是的你没有看错,的确在头文件里是这样写的,而且没有任何的包括守护。
3) 再来看看新的my_algo.c:
#define __MY_ALGO_C__
#include “which_os.h”
#if defined(__OS_A__)
static uint32_t __some_global_datas[] = {
#include “array_datas.h”
};
#endif // #if defined(__OS_A__)
// ….
{
#if defined(__OS_B__)
uint32_t __some_global_datas[] = {
#include “array_datas.h”
};
#endif // #if defined(__OS_B__)
}
想想有多份代码的话,有一天老板告诉你说,“我们要真对那些数据的内容做一次修改,然后要对每份代码进行同步。”,如果你真的不知道这种Hack技巧,OH MY GOD,我想我真的要疯掉了。
如果你跟我一样,在代码中的确需要这样的功能,有这样的特殊需求,那么可以试一下。
适应不同的OS的同时,代码整洁不了,不是吗?
4. 数据填充模板
看过之上几招之后,是不是感觉很用法怪异,作者很BT?那我再来告诉你招巨BT,但不太实用的Hack技巧吧。
打个比方,我有n个固定的UI Widget的样式数据,像下面这样写在头文件如:
// x, y, z, w, h, ext
0, 0, 0, 200, 20, 0×11, // Icon bar
0, 200, 0, 200, 20, 0×22, // Soft bar
….
而我要用到这些数据的结构体一共有2个,被定义如下:
struct __struct_UI_widget_coord {
uint32_t x;
uint32_t y;
};
struct __struct_UI_widget_ext {
uint8_t ext;
};
static struct __struct_UI_widget_coord __global_UI_coords[TOTAL_UI_WIDGETS];
static struct __struct_UI_UI_widget_ext __global_UI_exts[TOTAL_UI_WIDGETS];
怎样将我的UI_widget_detail.h中的数据分别填进两个数据中?看看下面的方法:
1) 修改UI_widget_detail.h为如下的格式:
// UI_widget_detail.h
// x, y, z, w, h, ext
__FILL_DATA( 0, 0, 0, 200, 20, 0×11 ),// Icon bar
__FILL_DATA( 0, 200, 0, 200, 20, 0×22 ),// Soft bar
….
2) 填充数组数据
#undef __FILL_DATA
#define __FILL_DATA( x, y, z, w, h, ext ) { x, y }
static struct __struct_UI_widget_coord __global_UI_coords[TOTAL_UI_WIDGETS] = {
#include “UI_widget_detail.h”
};
#undef __FILL_DATA
#define __FILL_DATA( x, y, z, w, h, ext ) { ext }
static struct __struct_UI_UI_widget_ext __global_UI_exts[TOTAL_UI_WIDGETS] = {
#include “UI_widget_detail.h”
};
5. 代码复用模板
我想你一定认为我疯了,不过这些大多不为人知或不常用的技巧的的确确,我一直在用。
不敢说如火纯青,大概也让我少敲击了几下键盘。来看看代码复用模板是怎么一回事?
还是以举个例子:
比如我有一个LIST数据结构,让我感觉烦的是,每次做迭代操作都要写一大堆代码,而且都是一样的。
像下面这个样子:
{
lnode_ptr_t curr;
lnode_ptr_t next;
// …
curr = some_first_node;
while ( NULL != curr ) {
next = curr->_next;
operate1( curr );
operate2( curr );
curr = next;
}
// …
}
如果这样的代码在几百个.c中到处被用到,而且只有中间操作过程不一样,会不会感觉很烦呀?
那我来给你个提示,怎么复用这些框架代码:
#define FOREACH_NODE_FORWARD( __first, __curr, __op ) /
do { /
lnode_ptr_t curr__ = (__first); /
while ( NULL != curr__ ) /
{ /
(__curr) = curr__; /
curr__ = curr__->_next; /
__op /
} /
} while ( 0 );
使用方式如下:
{
lnode_ptr_t curr_ptr = NULL;
// …
FOREACH_NODE_FORWARD
(
some_first_node,
curr_ptr,
{
operate1( curr );
operate2( curr );
}
);
// …
}
看懂了吗?我们真的用宏把这些框架代码给复用了,而且使代码很整洁美观。我们并没有增加一个变量就能使结构如此的完美。
好了,就先说这些多,还有其他更多的技巧请关注我和Dengwei的BLOG。晚安。