宏,(...)(#),(##),(__VA_ARGS__)用法

一直以来用宏定义#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)

对于重定义,如果定义的宏不一样,那么编译器会给出警告并使用最新定义的宏。

通过上面的总结描述,现在就可以轻松看到本文开始的宏定义的含义了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 1,Search Bar 怎样去掉背景的颜色(storyboard里只能设置background颜色,可是发现cl...
    以德扶人阅读 2,323评论 2 50
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • C中的预编译宏定义 2009-02-10 作者: infobillows 来源:网络 在将一个C源程序转换为可执行...
    白水灬煮一切阅读 1,579评论 0 5
  • 宏定义在C系开发中可以说占有举足轻重的作用。底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可...
    你好自己阅读 1,051评论 0 5
  • 文/孟小满 因为懂得你的痴心,所以拒绝这份只是感动的感情。 2017.9.5 星期二 ...
    孟小满阅读 1,454评论 89 76