文章

重新学习一下宏的使用

宏介绍

宏(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;N100代替了。

#define N 100就是宏定义,N为宏名,100是宏的内容。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。

宏定义是由源程序中的宏定义命令#define完成的,宏替换是由预处理程序完成的。

宏定义的一般形式为:

1
#define 宏名 字符串

#表示这是一条预处理命令,所有的预处理命令都以 # 开头。define是预处理命令。宏名是标识符的一种,命名规则和标识符相同。字符串可以是数字、表达式、if 语句、函数等

项目中用到的列一下

  1. 日志
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. 函数

    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];  \
    }
    
  2. 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宏写的很有意思,下面我们可以单独讲下

  3. 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,找到预定义宏的相关介绍

    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

图片

参考文章

C语言宏定义

C/C++ 宏编程的艺术

define宏定义中的#,##,@#及/符号

C/C++可变参数,“## VA_ARGS”宏的介绍和使用

本文由作者按照 CC BY 4.0 进行授权