ios宏的简单介绍

学一下如何在 OC 中自定义宏。参考自官方文档

类对象宏

类对象宏是一种会在预处理时被代码片段替代的简单标识。之所以被叫做类对象宏是因为在使用的时候类似于一个对象。最常见的使用方式是用其为一个常量定义一个别名。

基本使用

最基本的使用方式:

// 定义
#define BUFFER_SIZE 1024

// 使用
foo = (char *) malloc (BUFFER_SIZE);
 → foo = (char *) malloc (1024);

一般的,都要使用大写来命名宏。

换行

宏定义在 #define 那一行结束。如果你要书写多行,需要使用 \ 换行。在宏展开的时候,会被自动展开为一行:

#define NUMBERS 1, \
                2, \
                3
int x[] = { NUMBERS };
 → int x[] = { 1, 2, 3 };

顺序替换

预处理器顺序的读取程序,因此只有在定义宏之后,宏才能生效:

foo = X;
#define X 4
bar = X;

//----result----
foo = X;
bar = 4;

宏是一个替换过程,在顺序替换的时候,X 还没有被定义,所以 foo = X,之后宏 X 被定义了,所以之后的读取宏的都会被替换为具体内容。

嵌套宏

在拓展宏的时候,预处理器会检查宏的内容是否还是一个宏,会对其继续替换:

#define TABLESIZE BUFSIZE
#define BUFSIZE 1024
TABLESIZE
 → BUFSIZE
 → 1024

宏定义以最后生效的定义为准,因此下面的代码 TABLESIZE 对应37:

#define BUFSIZE 1020
#define TABLESIZE BUFSIZE
#undef BUFSIZE
#define BUFSIZE 37

类函数宏

宏还可以定义函数:

#define lang_init()  c_init()
lang_init()
 → c_init()

注意,() 一定要跟在名称后面,否则会被认为是一个类对象宏:

#define lang_init ()    c_init()
lang_init()
 → () c_init()()

预处理器会认为 lang_init 是一个整体,() c_init() 是一个整体进行替换。

宏参数

可以在类函数宏中传入参数:

#define min(A, B)  ((A) < (B) ? (A) : (B))

x = min(a, b);          →  x = ((a) < (b) ? (a) : (b));

如果宏的内容中有字符串,那么不会被宏参数替换:

#define foo(x) x, "x"
foo(bar)        → bar, "x"

三个非常重要的注意事项

宏的使用经常会产生错误,下面三点请一定注意,有助于消除大部分的 bug

1.为宏参数添加括号

比如上面的 min 的例子,如果我们在宏的内容中不为 XY 添加括号,会出现什么样的情况?

#define MIN(A,B) (A < B ? A : B)

一般情况下不会有任何问题,但是如果 AB 是一个表达式,就会出错:

int a = MIN(3, 4 < 5 ? 4 : 5);
// => int a = (3 < 4 < 5 ? 4 : 5 ? 3 : 4 < 5 ? 4 : 5);  //希望你还记得运算符优先级
//  => int a = ((3 < (4 < 5 ? 4 : 5) ? 3 : 4) < 5 ? 4 : 5);  //为了您不太纠结,我给这个式子加上了括号
//   => int a = ((3 < 4 ? 3 : 4) < 5 ? 4 : 5)
//    => int a = (3 < 5 ? 4 : 5)
//     => int a = 4

所以,一定要为宏参数添加括号,使其作为一个值被使用

2. 若宏的结果为值,则为整个宏添加括号")若宏的结果为值,则为整个宏添加括号

还是上面 min 的例子,如果此时不在整个红的外面添加括号,会如何呢?

#define min(A, B)  (A) < (B) ? (A) : (B)

同样的,一般不会出现什么问题,但是如果对这个值做其他的操作

int a = 2 * MIN(3, 4);
// => int a = 2 * 3 < 4 ? 3 : 4;
// => int a = 6 < 4 ? 3 : 4;
// => int a = 4;

被替换后,由于优先级的问题,2*3 会被率先执行。所以还上面类似的,要为整个宏添加括号,使其作为一个值被使用

若宏替换的是代码块,则要为这段代码添加 do{...}while(0)

如果宏替换的是某个代码块,比如👇(不用考虑具体代码块表达什么意思):

//A wrong version of NSLog
#define NSLog(format, ...)   ...(语句1);  \
 ...(语句2);

一般使用任然不会出错,比如下面这种情况就能正确替换:

...
NSLog(@"Oops, error happened");
...

但是考虑如果代码块是 if 中单条语句的简写形式

if (errorHappend)
 NSLog(@"Oops, error happened");

展开后可以知道这样一定编译出错:

if (errorHappend)
 ...(语句1);
 ...(语句2);

你可以在宏中为代码块加上 {}。但是如果判断中有 else 语句,那么即使为宏套上了 {},也不能通过编译:

if (errorHappend) {
 ...(语句1);
 ...(语句2);
}; else {
 //Yep, no error, I am happy~ :)
}

因此,解决方式是在定义宏的时候为代码块套上 do{...}while(0),使整个代码块形成一个整体,相当于一条语句,并且保证了代码块只会执行一次。这样就可以符合 if 的简写方式了:

if (errorHappend) 
 do {
 ...(语句1);
 ...(语句2);
 } while (0);
else {
 //Yep, no error, I am really happy~ :)
}

为代码块添加 do{...}while(0) 是通用写法

字符串化

可以在宏参数前添加 #,将参数转换为字符串:

#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);

字符串化会将参数中的所有字符(包括引号)都字符串化,如果中间有很多空格,字符串化后将只有一个空格。

那如果想通过 # 来实现参数值的字符串化,而不仅仅是将参数名字符串化呢?这就需要需要使用两层的宏:

#define xstr(s) str(s)
#define str(s) #s
#define foo 4
str (foo)
 → "foo"
xstr (foo)
 → xstr (4)
 → str (4)
 → "4"

在使用 str 的时候,s 会立即被字符串化,而没有被宏展开。但是如果我们使用另外一个宏 xstr 嵌套着,那就会先展开,将值带入后,然后再字符串化。

连接

可以使用 ## 连接两个 token:

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),
 …
};

预处理器会将所有注释转为空格,## 会将左右的空格都忽略。

多参数宏

如果不确定有多少个宏参数,可以使用 代替,这在很多语言中都有类似的做法。相应的,使用 __VA_ARGS__ 在具体的宏中,代替 ...

#define eprintf(…) fprintf (stderr, __VA_ARGS__)

eprintf ("%s:%d: ", input_file, lineno)
 →  fprintf (stderr, "%s:%d: ", input_file, lineno)

上面的例子中,如果宏参数缺失,那么替换后不就成 fprintf (stderr,) 了么?需要稍微修改:

#define eprintf(…) fprintf (stderr, ##__VA_ARGS__)

在加上 ## 之后,预处理器就会在传入空的时候,删掉前面的 , 了。

条件编译

#if/#elif/#else

如果 IOS10 值为真时,输出 It is an iOS10 device!;IOS10 值为假且 IOS9 值为真时,输出It is an iOS9 device!;否则输出It is a device NOT runing iOS10 or iOS9!

#if#endif 配对出现,#endif 用于终止 #if 预处理指令

#if IOS10
 NSLog(@"It is an iOS10 device!");
#elif IOS9
 NSLog(@"It is an iOS9 device!");
#else
 NSLog(@"It is a device NOT runing iOS10 or iOS9!");
#endif

#ifdef

#ifdef 等价于 #if defined,如果后面跟的宏被定义过,则执行下面的代码。

#ifdef RUN
 NSLog(@"defined...");
#endif

#ifndef

和上面相反,如果没有被定义过,则执行下面的代码:

#ifndef RUN
 NSLog(@"not defined...");
#endif

内联函数

在 iOS 的一些框架中,static inline 是经常出现的关键字组合。比如:

// 直接写在文件内,不要作为类方法
static inline CGFloat CGFloatFromPixel(CGFloat value) {
 return value / YYScreenScale();
}

内联函数的标志就是 inline。内联函数直接写在文件内,不要作为类方法,使用的时候导入文件,直接调用即可。

内联函数有什么用呢?内联函数类似于宏,会把代码直接嵌入调用代码中,因此没有普通函数调用产生的性能损耗。如果该函数会被经常调用,那么就可以提高性能。它与 #define 宏的区别在于:

  1. #define 定义的格式要有要求,而使用inline则就行平常写函数那样,只要加上inline即可!
  2. 使用 #define 宏定义的代码,编译器不会对其进行参数有效性检查,仅仅只是对符号表进行替换
  3. #define 宏定义的代码,其返回值不能被强制转换成可转换的适合的转换类型

inline加上static修饰符,只是为了表明该函数只在该文件中可见!也就是说,在同一个工程中,就算在其他文件中也出现同名、同参数的函数也不会引起函数重复定义的错误!

转自 iOS 中的宏

参考文章

官方文档

iOS开发高级:使用宏定义macros

宏定义的黑魔法 - 宏菜鸟起飞手册

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

推荐阅读更多精彩内容

  • http://www.open-open.com/lib/view/open1390651437117.html ...
    Xtuphe阅读 1,255评论 0 10
  • 一. 简介 宏是一种批量处理的称谓,简单来说就是根据定义好的规则替换一定的文本。替换过程在程序编译期,也因此大量使...
    火之玉阅读 1,489评论 0 4
  • 最近新加入了项目组,看到了一个让我很费解的宏定义 #define ServiceTypeMake(_cls, _s...
    奥斯丁1_1阅读 738评论 2 1
  • 写在前面 在开发过程中很多时候需要阅读第三方源码,但是里面有大量的宏。没有换行,没有着色,与平时写的代码完全不同,...
    走进科学阅读 692评论 0 2
  • 宏定义在C系开发中可以说占有举足轻重的作用。底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可...
    你好自己阅读 1,054评论 0 5