一直以来用宏定义#define也就是定义一些简单的常量,至多也就是定义一个函数,很少关注宏定义的用法。直到看到这样的代码:
[cpp] view plain copy
#define PLAYSOUNDEFFECT(...) \
[[GameManager sharedGameManager] playSoundEffect:@#__VA_ARGS__]
这么强大的用法以前从来没有想过。看一下iOS Framework的一些头文件,发现几乎全部都是宏定义:
不得不说宏定义很强大!宏定义的使用使得程序的编写更加的简便!
作为iOS开发者,有必要深入研究一下宏定义的用法。
最官方的关于宏的使用说明网址是:http://gcc.gnu.org/onlinedocs/cpp/Macros.html#Macros
在Apple的官网上可以找到GNU C 4.2 Preprocessor User Guide,发现和GNU官网的说明一模一样。因为Xcode的编译器就是基于GNU C 4.2预处理器,因此在Objective-C的开发环境中使用宏和在C/C++中使用是一模一样的。
下面的文字是阅读官方使用说明后的总结及翻译。(代码直接从官方使用说明摘录)
1、Macros 宏
官方解释:
A macro is a fragment of code which has been given a name. Whenever the name is used, it is replaced by the contents of the macro. There are two kinds of macros. They differ mostly in what they look like when they are used. Object-like macros resemble data objects when used, function-like macros resemble function calls.
有两种宏的类型,一种是类对象的宏,封装使用的数据对象,另一种是类函数的宏,封装函数的调用。在ObjC里面,那就是可以封装Method的使用,如文章一开始的代码
1.1 类对象的宏
最基本的使用:
[cpp] view plain copy
#define BUFFER_SIZE 1024
foo = (char *) malloc (BUFFER_SIZE);
foo = (char *) malloc (1024);
就是最基本的替换。
通常宏的名称都是用大写字母。
------------------------------------------------------------------------------------------------
[cpp] view plain copy
#define NUMBERS 1, \
2, \
3
int x[] = { NUMBERS };
==> int x[] = { 1, 2, 3 };
在宏定义中,如果要换行,使用“\"符号。然后经预处理后还是在同一行。
------------------------------------------------------------------------------------------------
C预处理器是按顺序读取程序,因此宏定义生效在宏定义之后。
[cpp] view plain copy
foo = X;
#define X 4
bar = X;
ces
foo = X;
bar = 4;
------------------------------------------------------------------------------------------------
宏调用时,预处理器在替换宏的内容时,会继续检测内容本身是否也是宏定义,如果是,会继续替换内容。
[cpp] view plain copy
#define TABLESIZE BUFSIZE
#define BUFSIZE 1024
TABLESIZE
==> BUFSIZE
==> 1024
------------------------------------------------------------------------------------------------
宏定义以最后生效的定义为准,因此下面的代码TABLESIZE对应37
[cpp] view plain copy
#define BUFSIZE 1020
#define TABLESIZE BUFSIZE
#undef BUFSIZE
#define BUFSIZE 37
------------------------------------------------------------------------------------------------
如果宏定义内容包含了名称,则预处理器会终止展开防止无限嵌套(infinite resursion)
1.2 类函数宏
[cpp] view plain copy
#define lang_init() c_init()
lang_init()
==> c_init()
类函数宏的名称后面加了"()"。
[cpp] view plain copy
#define lang_init () c_init()
lang_init()
==> () c_init()()
并且"()"必须紧随在名称后面否则就会认为是类对象宏。
1.3 宏参数
在类函数宏里面可以添加参数使得更像真正的函数
[cpp] view plain copy
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
x = min(a, b); ==> x = ((a) < (b) ? (a) : (b));
y = min(1, 2); ==> y = ((1) < (2) ? (1) : (2));
z = min(a + 28, *p); ==> z = ((a + 28) < (*p) ? (a + 28) : (*p));
基本的使用和函数的定义类似,当然宏里面都是实际参数,用逗号隔开。预处理时,先是将宏展开,然后将参数放进宏的主体中,再检查一遍完整的内容。
------------------------------------------------------------------------------------------------
如何宏里面有字符串的内容,即使与参数名相同,也不会被替换。如下:
[cpp] view plain copy
#define foo(x) x, "x"
foo(bar) ==> bar, "x"
1.4 字符串化
使用”#“预处理操作符来实现将宏中的参数转化为字符串。例子如下:
[cpp] view plain copy
#define WARN_IF(EXP) \
do { if (EXP) \
fprintf (stderr, "Warning: " #EXP "\n"); } \
while (0)
WARN_IF (x == 0);
==> do { if (x == 0)
fprintf (stderr, "Warning: " "x == 0" "\n"); } while (0);
这个字符串化会将参数中的所有字符都实现字符串化,包括引号。如果参数中间有很多空格,字符串化之后将会只用一个空格代替。
然后没有什么方法可以直接将参数转化成单一的字符char
------------------------------------------------------------------------------------------------
[cpp] view plain copy
#define xstr(s) str(s)
#define str(s) #s
#define foo 4
str (foo)
==> "foo"
xstr (foo)
==> xstr (4)
==> str (4)
==> "4"
出现上面的结果是因为在使用str(s)时,s是字符串化,所以宏没有扩展开。而使用xstr(s)时s作为一个参数,因此先把宏完全扩展然后再放进参数。
1.5 连接
使用"##"操作符可以实现宏中token的连接。
[cpp] view plain copy
struct command
{
char *name;
void (*function) (void);
};
struct command commands[] =
{
{ "quit", quit_command },
{ "help", help_command },
...
};
#define COMMAND(NAME) { #NAME, NAME ## _command }
struct command commands[] =
{
COMMAND (quit),
COMMAND (help),
...
};
如上,使参数NAME对应的字符与_command连接起来,而不进行其他转化。当然要注意连接后的字符必须是有意义的,否则只会出现错误或警告。
然后C预处理器会将注释转化成空格,因此在宏中间,参数中间加入注释都是可以的。但不能将"##"放在宏的最后,否则会出现错误。
1.6 多参数宏(Variadic Macros)
[cpp] view plain copy
#define eprintf(...) fprintf (stderr, __VA_ARGS__)
eprintf ("%s:%d: ", input_file, lineno)
==> fprintf (stderr, "%s:%d: ", input_file, lineno)
使用标识符__VA_ARGS_来表示多个参数,在宏的名称中则使用(...)
在C++中也可以使用如下的方式:
[cpp] view plain copy
#define eprintf(args...) fprintf (stderr, args)
结果是一样的。
------------------------------------------------------------------------------------------------
"##"的特殊用法:
[cpp] view plain copy
#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__)
eprintf ("success!\n")
==> fprintf(stderr, "success!\n");
将"##"放在","和参数之间,那么如果参数留空的话,那么"##"前面的","就会删掉,从而防止编译错误。
1.7 取消或重新宏定义
这个看下面的代码就明白:
[cpp] view plain copy
#define FOO 4
x = FOO; ==> x = 4;
#undef FOO
x = FOO; ==> x = FOO;
These definitions are effectively the same:
#define FOUR (2 + 2)
#define FOUR (2 + 2)
#define FOUR (2 /* two */ + 2)
but these are not:
#define FOUR (2 + 2)
#define FOUR ( 2+2 )
#define FOUR (2 * 2)
#define FOUR(score,and,seven,years,ago) (2 + 2)
对于重定义,如果定义的宏不一样,那么编译器会给出警告并使用最新定义的宏。
通过上面的总结描述,现在就可以轻松看到本文开始的宏定义的含义了。