重新学习一下宏的使用
宏介绍
宏(Macro)是预处理命令的一种,它允许用一个标识符来表示一个字符串。先看一个例子:
1
2
3
4
5
6
7
#include <stdio.h>
#define N 100
int main(){
int sum = 20 + N;
printf("%d\n", sum);
return 0;
}
该示例中的语句int sum = 20 + N;
,N
被100
代替了。
#define N 100
就是宏定义,N
为宏名,100
是宏的内容。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。
宏定义是由源程序中的宏定义命令#define
完成的,宏替换是由预处理程序完成的。
宏定义的一般形式为:
1
#define 宏名 字符串
#
表示这是一条预处理命令,所有的预处理命令都以 # 开头。define
是预处理命令。宏名
是标识符的一种,命名规则和标识符相同。字符串
可以是数字、表达式、if 语句、函数等
项目中用到的列一下
- 日志
1
2
3
4
5
6
7
#define ALLOGGERLEVEL_INFO 3
#define LOG_INFO(content, ...) LOG(ALLOGGERLEVEL_INFO, content, ##__VA_ARGS__)
#define LOG(level, content, ...) do { \
[DDLog logWithLevel:level file:__FILE__ line:__LINE__ prefix:"TEST" log:(content), ##__VA_ARGS__]; \
} while(0)
函数
1 2 3 4 5 6 7
#define EXPORT_HANDLER(handlerName) \ - (void)handlerName:(id)data nativeBidgeResponseCallback:(Callback)responseCallback #define ADAPT_JSHANDLER(JShandlerName, AdaptHandlerName) \ - (void)AdaptJSMethod##JShandlerName:(id)data nativeBidgeResponseCallback:(Callback)responseCallback { \ [self AdaptHandlerName:data nativeBidgeResponseCallback:responseCallback]; \ }
weakify(self) strongify(self)
1 2
#define weakify(self) autoreleasepool{} __weak __typeof__ (self) self_weak_ = self; #define strongify(self) autoreleasepool{} __strong __typeof__(self) self = self_weak_;
ReactiveCocoa中是这样的,最终拆开宏,其实和上面一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#define weakify(...) \ rac_keywordify \ metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__) #define strongify(...) \ rac_keywordify \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wshadow\"") \ metamacro_foreach(rac_strongify_,, __VA_ARGS__) \ _Pragma("clang diagnostic pop") #if DEBUG #define rac_keywordify autoreleasepool {} #else #define rac_keywordify try {} @catch (...) {} #endif
ReactiveCocoa的里面的weak strong宏写的很有意思,下面我们可以单独讲下
Weex中用到的一个例子
他这里用了很多编译器预定义宏。 ` gcc -dM -E - < /dev/null`,用这个命令并没有看到下面的几个宏
1 2 3 4 5 6 7 8 9 10
#if DEBUG #define WXAssert(condition, ...) \ do{\ if(!(condition)){\ WXAssertInternal(@(__func__), @(__FILE__), __LINE__, __VA_ARGS__);\ }\ }while(0) #else #define WXAssert(condition, ...) #endif
可搜索关键字
The Basic Set of Predefined Macros
,找到预定义宏的相关介绍
开始介绍
先介绍下几个宏操作符:#
、##
、@#
、\
、##__VA_ARGS__
#
字符串化操作符
将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串,例如
1
2
3
4
5
6
7
#define LOG(str) NSLog(@"input string is:/t%@/n",@#str)
#define LOG_INFO(str) #str
LOG(ttt);// NSLog(@"input string is:/t%@/n",@"ttt");
NSString *str = @LOG_INFO(abc);// NSString *str = @"abc";
str=LOG_INFO( abc ); //将会被扩展成 str="abc";
str=LOG_INFO( abc def); //将会被扩展成 str="abc def";
- 忽略传入参数名前面和后面的空格
- 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,用每个子字符串中只以一个空格连接,忽略其中多余一个的空格
##
符号链接操作符
是将宏定义的多个形参成一个实际参数名,例如
1
2
3
4
#define NUM_VERIBLE(n) num##n
int num9=9;
int num = NUM_VERIBLE(9); // int num = num9;
当用##连接形参时,##前后的空格可有可无。
如:
#define NUM_VERIBLE(n) num ## n
相当于#define NUM_VERIBLE(n) num##n
连接后的实际参数名,必须为实际存在的参数名或是编译器已知的宏定义
@#
字符化操作符
只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。作用是将传的单字符参数名转换成字符,以一对单引用括起来。例如
1
2
3
#define makechar(x) #@x
char a = makechar(b);// char a = 'b';
\
行继续操作符
当定义的宏不能用一行表达完整时,可以用”/”表示下一行继续此宏的定义。例如:
1
2
3
#define LOG(level, content, ...) do { \
[DDLog logWithLevel:level file:__FILE__ line:__LINE__ prefix:"TEST" log:(content), ##__VA_ARGS__]; \
} while(0)
##__VA_ARGS__
可变参数
1
2
3
4
5
6
7
8
9
10
11
12
13
#define LOG_INFO(content, ...) LOG(ALLOGGERLEVEL_INFO, content, ##__VA_ARGS__)
+ (void)logWithLevel:(int)level file:(const char *)fullpath line:(int)line prefix:(const char*)prefix log:(NSString *)log, ...
{
ALLoggerBlockType block = [self block];
if (block != nil) {
va_list vl;
va_start(vl, log);
NSString* content = [[NSString alloc] initWithFormat:log arguments:vl];
va_end(vl);
block(level, fullpath, line, prefix, content);
}
}
其他预定义宏,如下
Predefined Macros ` gcc -dM -E - < /dev/null` The Complete Reference
预定义宏 | 意义 |
---|---|
__LINE__ | 当前语句所在的行号, 以10进制整数标注 |
__FILE__ | 当前源文件的文件名, 以字符串常量标注 |
__DATE__ | 程序被编译的日期, 以”Mmm dd yyyy”格式的字符串标注 |
__TIME__ | 程序被编译的时间, 以”hh:mm:ss”格式的字符串标注, 该时间由asctime返回. |
__func__ | 获取当前函数名 NSLog(@"%s", __func__); // |
__COUNTER__ | This macro expands to sequential integral values starting from 0. |
1
2
3
4
5
6
7
@implementation Test
- (void)test {
NSLog(@"%s", __func__); // -[Test test]
NSLog(@"%d", __COUNTER__);//0
NSLog(@"%d", __COUNTER__);//1
NSLog(@"%d", __COUNTER__);//2
}
下标访问
可以通过 下标访问 变长参数的 特定元素
1
2
3
4
5
6
7
8
9
10
11
12
#define PP_CONCAT(A, B) PP_CONCAT_IMPL(A, B)
#define PP_CONCAT_IMPL(A, B) A##B
#define PP_GET_N(N, ...) PP_CONCAT(PP_GET_N_, N)(__VA_ARGS__)
#define PP_GET_N_0(_0, ...) _0
#define PP_GET_N_1(_0, _1, ...) _1
#define PP_GET_N_2(_0, _1, _2, ...) _2
// ...
#define PP_GET_N_8(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _8
PP_GET_N(0, foo, bar) // -> foo
PP_GET_N(1, foo, bar) // -> bar
通过上面例子我们对ReactiveCocoa中的Weakify(self)
进行拆解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#define metamacro_concat(A, B) \
metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)
#define metamacro_argcount(...) \
metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
// 拆解 metamacro_at20(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__) // __VA_ARGS__ 代表的就是...部分。
#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST // 取第一个数字,其实最后的出来的就是参数数量
/// -------------
@weakify(self);
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
// 主要拆解 metamacro_foreach_cxt
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
// 拆解1 =
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_foreach_cxt##metamacro_argcount(__VA_ARGS__)(MACRO, SEP, CONTEXT, __VA_ARGS__)
// 拆解2 =
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, __VA_ARGS__)
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
// 拆解3 =
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_foreach_cxt1(rac_weakify_, , __weak, self) rac_weakify_(0,__weak,self)
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
// 最后
__weak __typeof__ (self) self_weak_ = self;
#define weakify(self) autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
其他
根据一些列的转化能做到的事情挺多的,具体可以查看文章C/C++ 宏编程的艺术
Demo地址
文章中用到的demo地址,可以使用Xcode的预处理能力进行宏拆解,如下图:Xcode->Product->Perform action->Preprocess