C奇淫技巧,——宏神奇
⼀个、宏列表
当这个问题⾯临:
有⼀个标志变量。位代表对应的含义。
我们须要提供⼀组函数来訪问设置这些位。可是对于每⼀个标记位的操作函数都是相似的。若有32个位,难道要搞32套相似的操作函数么?
你或许会说,⽤⼀套操作函数。依据传⼊的參数来推断对哪个位操作。这样固然可⾏,可是
①不够直观。⽐如訪问Movable标记位,对于⽤户来说,is Movable()是⾮常⾃然的⽅式,⽽我们仅仅能提供这种接⼝isFlag(Movable)
②扩展性差。
若以后添加删改标记位,则须要更改isFlag等函数的代码。
我们想有这种设计:
在头⽂件的宏定义中增删标记位的宏。我们为每⼀个标记位设计的操作函数名就⾃⼰主动更改。添加的标记位也⾃⼰主动添加⼀套操作函数。删除的标记位也⾃⼰主动减去⼀套操作函数。
这种设计就太爽了!
但怎样实现呢?
⾸先,每⼀个标记位的宏名⼀变,我们的操作函数名也要对应改变,这时我们能够想到⽤带參宏。并⽤宏的##符。把两个字符串合在⼀起。
(使它们能被宏替换掉)
#define FLAG_ACCESSOR(flag) \
bool is##flag() const {\
return hasFlags(1 << flag);\
}\
void set##flag() {\
JS_ASSERT(!hasFlags(1 << flag));\
setFlags(1 << flag);\
}\
void setNot##flag() {\
JS_ASSERT(hasFlags(1 << flag));\
removeFlags(1 << flag);\
}
[这⼀步⼀般⼈都能想到的。
]
这样,FLAG_ACCESSOR(Movable)就可得到操作Movable标记位的三个函数:is Movable(),set Movable()。setNot Movable()
可是,难道有多少个标记位,我们就要写多少个FLAG_ACCESSOR(flag)么?
怎样⽤⼀个式⼦来扩展成多个种的FLAG_ACCESSOR(flag),提取共性,因为这多个FLAG_ACCESSOR(flag),flag是不同的,宏函数名是同样的。故⽤宏列表:
#define FLAG_LIST(_)        \
_(InWorklist)                    \
_(EmittedAtUses)            \
_(LoopInvariant)              \
_(Commutative)              \
_(Movable)                      \
_(Lowered)                      \
_(Guard)
这样⼀个式⼦:FLAG_LIST(FLAG_ACCESSOR)就搞定了。
可是,另⼀个问题,我们还未定义InWorklist、EmittedAtUses、LoopInvariant等。须要再⽤宏来定义这些标记位的名字。
⽐如:
#define InWorklist 1
#define EmittedAtUses 2
……
这样以来,若以后我们增改标记位的名字就须要改动两处地⽅了:宏列表、标记名的宏定义。
我们想要的最好的设计是,仅仅改变⼀处处处跟着⼀起改变。
[yang]若是有新的标记位加⼊我们仅仅在#define FLAG_LIST(_) 中加⼊⼀项就好了。⽐如,_(Visited) ⾃⼰主动加⼊#define Visited 8。⾃⼰主动加⼊⼀项宏定义难以实现,那我们考虑有没有替代⽅案。观察发现此宏定义都是定义的数字,⽽枚举也有相同的功能。
这样。我们把这些展开的位标记名放在enum枚举中。让其⾃⼰主动赋上1,2,3……等数值。⽽不必⽤宏定义⼀个⼀个地定义。
如今问题变为:怎样使我们在#defineFLAG_LIST(_) 中加⼊⼀项,enum枚举中就⾃⼰主动加⼊对应的⼀项?
我们仅仅有把FLAG_LIST(_)放⼊enum枚举中,这样才⼲⼀增俱增。
若宏列表:
#define FLAG_LIST(_)        \
_(InWorklist)                    \
_(EmittedAtUses)          \
_(LoopInvariant)
能再变为:
InWorklist
EmittedAtUses
LoopInvariant
就好了。
这样,我们在#defineFLAG_LIST(_) 中加⼊⼀项_(Visited)。
则enum中⾃⼰主动加⼊Visited。
也就是_(InWorklist)怎样展开成InWorklist。这个⾮常easy:#define DEFINE_FLAG(flag)flag,enum c++
其详细实现⽅式例如以下:
#define FLAG_LIST(_)      \
_(InWorklist)                    \
_(EmittedAtUses)          \
_(LoopInvariant)            \
_(Commutative)            \
_(Movable)                      \
_(Lowered)                    \
_(Guard)
它定义了⼀个FLAG_LIST宏。这个宏有⼀个參数称之为 _ ,这个參数本⾝是⼀个宏,它可以调⽤列表中的每⼀个參数。举⼀个实际使⽤的样例可能更能直观地说明问题。
如果我们定义了⼀个宏DEFINE_FLAG,如:
#define DEFINE_FLAG(flag) flag,  //注意flag后有逗号
enum Flag {
None = 0,
FLAG_LIST(DEFINE_FLAG)
Total
};
#undef DEFINE_FLAG
对FLAG_LIST(DEFINE_FLAG)做扩展可以得到例如以下代码:
enum Flag {
None = 0,
DEFINE_FLAG(InWorklist)
DEFINE_FLAG(EmittedAtUses)
DEFINE_FLAG(LoopInvariant)
DEFINE_FLAG(Commutative)
DEFINE_FLAG(Movable)
DEFINE_FLAG(Lowered)
DEFINE_FLAG(Guard)
Total
};
接着,对每⼀个參数都扩展DEFINE_FLAG宏,这样我们就得到了enum例如以下:
enum Flag {
None = 0,
InWorklist,
EmittedAtUses,
LoopInvariant,
Commutative,
Movable,
Lowered,
Guard,
Total
};
接着。我们可能要定义⼀些訪问函数,这样才⼲更好的使⽤flag列表:
#define FLAG_ACCESSOR(flag) \
bool is##flag() const {\
return hasFlags(1 << flag);\
}\
void set##flag() {\
JS_ASSERT(!hasFlags(1 << flag));\
setFlags(1 << flag);\
}\
void setNot##flag() {\
JS_ASSERT(hasFlags(1 << flag));\
removeFlags(1 << flag);\
}
FLAG_LIST(FLAG_ACCESSOR)
#undef FLAG_ACCESSOR
(这样。我们仅仅在宏列表⼀处更改增删位操作就可以。)
【总结:yang】
⼀步步的展⽰其过程是很有启⽰性的,假设对它的使⽤还有不解,能够花⼀些时间在gcc –E上。
【宏列表的长处有:能够把⼀个式⼦扩展成多个式⼦,且⾮常easy扩展。仅仅要再添加列表项就可以。】⼆、指定的初始化
⾮常多⼈都知道像这样来静态地初始化数组:
int fibs[] = {1,2,3,4,5} ;
C99标准实际上⽀持⼀种更为直观简单的⽅式来初始化各种不同的集合类数据(如:结构体。联合体和数组)。
数组的初始化
我们能够指定数组的元素来进⾏初始化。这很实⽤,特别是当我们须要依据⼀组#define来保持某种映射关系的同步更新时。来看看⼀组错误码的定义。如:
/* Entries may not correspond to actualnumbers. Some entries omitted. */
#define EINVAL 1
#define ENOMEM 2
#define EFAULT 3
/* ... */
#define E2BIG  7
#define EBUSY  8
/* ... */
#define ECHILD 12
/* ... */
如今,如果我们想为每⼀个错误码提供⼀个错误描写叙述的字符串。为了确保数组保持了最新的定义,不管头⽂件做了不论什么改动或增补。我们都能够⽤这个数组指定的语法。
char *err_strings[] = {
[0] = "Success",
[EINVAL] = "Invalid argument",
[ENOMEM] = "Not enough memory",
[EFAULT] = "Bad address",
/* ... */
[E2BIG ] = "Argument list too long",
[EBUSY ] = "Device or resource busy",
/* ... */
[ECHILD] = "No child processes"
/* ... */
};
这样就能够静态分配⾜够的空间,且保证最⼤的索引是合法的,同⼀时候将特殊的索引初始化为指定的值,并将剩下的索引初始化为0。结构体与联合体
⽤结构体与联合体的字段名称来初始化数据是很实⽤的。如果我们定义:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。