你应该掌握的#define宏定义

如果你并不是一个new coder ,那你一定是知道#define的。是的,它并不完美,而且被很多人所诟病。但是的的确确会让你的代码看起来看简洁,更方便阅读。下面让我们先来温习下宏的基础。

基础宏

定义宏标示 (Object-like Macros)

如果你想定义一个缓存区的大小,又不太想硬编码,你可能会选择下面的方式

#define BUFFER_SIZE 1024

如果你在#define标识符后面,使用了BUFFER_SIZE代码块开辟一块区间

 foo = (char *) malloc (BUFFER_SIZE);

C预处理器将会识别BUFFER_SIZE并且把它替换成你上面写的1024:

 foo = (char *) malloc (1024);
 BUFFER_SIZE 
        =>1024

按照约定,宏定义需要以大写的形式呈现。这样让人瞟一眼就知道你写的个啥玩意。

定义宏函数 (Function-like Macros)

你也可以定义一个宏,它看起来像一个函数一样。比如:

#import <Foundation/Foundation.h>
#define lang_init()  c_init()
void c_init();

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
        lang_init();
    }
    return 0;
}

void c_init(){
    printf("c_init method is called \n");
}

当然你也可以去定义一个A减去B带参数的函数:

#define SUB(A,B) (A-B)

验证一下: SUB(2, SUB(1,3))

1.png

干的不错,再来一个取两者更小的一个数的宏

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

验证一下:

| 表达式 | 结果 | 是否正确 |错误原因|
| -------- | -----: | :----: ||
| MIN(4,5) | 4 | 是 ||
| MIN(-1,5) | -1 | 是 ||
| 2 * MIN(4,5) | 5 | 否 |2*4<5?4:5|
在做乘法的时候我们改变了原先的运算顺序,机智如你肯定很快的给出解决方案,如下:

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

信心爆棚的你再次跑起了测试,仿佛一切都在你的掌握之中,直到遇到了那个它:

| 表达式 | 结果 | 是否正确 ||
| -------- | -----: | :----: ||
| MIN(3, 4 < 5 ? 4 : 5) | 4 | 否 ||
我们先来分解一下:

(3<4<5?4:5?3:4<5?4:5)
// =>(1<5?4:5?3:4<5?4:5)
// =>(1?4:5?3:1?4:5)
// => 4

我们不得不又改了解决方案:

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

说了这么多废话,只是想提醒大家能加上括号的就一定加括号。当然加了括号也不是万能了。比如:

float a = 1.0f;
float b = MIN(a++, 1.5f);

不再作展开操作了,更详细的可以去看喵大的宏定义的黑魔法
实在感觉到很无力。默默的打开XCODE,看看苹果官方的标准写法:

#define __NSX_PASTE__(A,B) A##B
#if !defined(MIN)
    #define __NSMIN_IMPL__(A,B,L) ({ 
    __typeof__(A) __NSX_PASTE__(__a,L) = (A); \ 
    __typeof__(B) __NSX_PASTE__(__b,L) = (B);\ 
    (__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__a,L) : __NSX_PASTE__(__b,L); \ 
    })
    
    #define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
#endif

我们先来认识一下几个不太熟悉的大兄弟:

__COUNTER__ 是一个由GCC提供用来构造独立变量名的标识符,在预编译过程中从0开始计数,每次被调用的时候+1).也就是连续调用两次MIN(A,B),两次的A在预编译中会以不同的变量名存在。

#import <Foundation/Foundation.h>
#import "Header.h"
#define FUNC2(x,y) x##y
#define FUNC1(x,y) FUNC2(x,y)
#define FUNC(x) FUNC1(x,__COUNTER__)

int FUNC(my_unique_prefix);
int FUNC(my_unique_prefix);
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        printf("__COUNTER__==%d \n",__COUNTER__);
        printf("__COUNTER__==%d \n",__COUNTER__);
        printf("因为预编译FUNC被调用了两次,所以__COUNTER__打印的值是从2开始");
    }
    return 0;
}

=>打印:
__COUNTER__==2 
__COUNTER__==3 
因为预编译FUNC被调用了两次,所以__COUNTER__打印的值是从2开始

__NSX_PASTE__ 是预编译的连接符,宏定义中不能直接写 AB 来连接参数,需要写成 A##B。
__typeof__ 是gcc对C语言的一个扩展保留字,用于声明变量类型,var可以是数据类型(int, char*..),也可以是变量表达式。
__NSMIN_IMPL__现在多加了个__COUNTER__的参数生成独立标示符。
认识了上面的大兄弟,我们现在自己来转换一下,应该就很好读了:

#define __NSMIN_IMPL__(A,B,L) ({\
        __typeof__(A) __a##L = (A);\
        __typeof__(B) __b##L = (B);\
        (__a##L < __b##L) ? __a##L : __b##L;\
        })

宏短语 (Stringification)

有时候你可能想把一个宏的参数转成一个字符串常量。可是你并没有办法直接把宏参数硬塞到字符串里面去,这个时候“#”宏预处理符会帮助到你。当一个宏参数前面加上“#”宏预处理符,你就可以成功的把宏参数转成字符串了。
来个例子,我们开发了一个APP,第一个版本的版本号可能是1.0咯

#define VERSION_MAJOR 1
#define VERSION_MINOR 0

#define STRINGSIZE2(s) #s
#define STRINGSIZE(s) STRINGSIZE2(s)
#define VERSION_STRING "v" STRINGSIZE(VERSION_MAJOR) \
"." STRINGSIZE(VERSION_MINOR)
=>打印一下
printf ("%s\n", VERSION_STRING);

v1.0

宏串联(Concatenation)

在上面的例子我们有提到过“##”宏串联符。假如现在你想整理你电脑里面的动漫,分为HYRZ和SQBB系列,后面加上数字代表集数。

#define SERIES1 HYRZ
#define SERIES2 SQBB

#define PPCAT_NX(A, B) A ## B
#define PPCAT(A, B) PPCAT_NX(A, B)
#define STRINGSIZE_NX(A) #A
#define STRINGSIZE(A) STRINGSIZE_NX(A)
=>打印一下
printf("%s \n",STRINGSIZE(PPCAT(SERIES1, 01)));
printf("%s \n",STRINGSIZE(PPCAT(SERIES2, 01)));

HYRZ01 
SQBB01 

可变宏(Variadic Macros)

宏可以和一个方法一样能接受一系列的参数。这面是个例子:

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

这种宏就叫做variadic。在宏定义(其实也包括函数定义)的时候,写为...的参数被叫做可变参数(variadic)。可变参数的个数不做限定。当宏被使用下面的这些参数会替换这个宏里面的__VA_ARGS__。所有,我们可以有下面的展开:

eprintf ("%s:%d: ", __FILE__, __LINE__)
==>  fprintf (stderr, "%s:%d: ", __FILE__, __LINE__)

打印出当前的文件在你电脑上的路径,和该行代码在你当前文件的当前行。

再来个相加的加法的例子:

#define A(a1, a2, a3) ((a1)+(a2)+(a3))
#define ADD(...) A(__VA_ARGS__)

printf("结果为:%d\n", ADD(1, 2, 3));;
=>打印一下

结果为:6

可以看出你可以在A2宏里面扩展成N个参数。这就证明了__VA_ARGS__参数的个数不做限定。

标准的预定义宏(Standard Predefined Macros)

表达式 含义
__FILE__ 当前源文件的路径
__LINE__ 当前代码的在源文件的行数
__DATE__ 当前的日期
__TIME__ 当前的时间
__FUNCTION__ 当前的方法名

标准的预定义宏被相关语言标准所定义,所以他们适用于各种编译器。

表达式 含义
__FILE__ 当前源文件的路径
__LINE__ 当前代码的在源文件的行数
__DATE__ 当前的日期
__TIME__ 当前的时间
__FUNCTION__ 当前的方法名
#define log(x)  printf(" THIS IS A LOG : x = %d \n LINE %d \n FILE = %s \n DATE = %s \n TIME = %s \n  FUNCTION = %s\n    ",x,__LINE__,__FILE__,__DATE__,__TIME__,__FUNCTION__)

=>打印一下
 THIS IS A LOG : x = 3 
 LINE 86 
 FILE = /Users/wyy/Desktop/wyy/code/demo/DefineDemo/DefineDemo/main.m 
 DATE = Nov  7 2016 
 TIME = 15:10:51 
 FUNCTION = main
 Program ended with exit code: 0

宏定义取消(Undefining and Redefining Macros)

如果一个宏不再有用,我们可以直接用#undef来取消它

#define VERSION_NUM  1

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    #ifdef VERSION_NUM                      //如果定义了 VERSION_NUM
        printf("定义了VERSION_NUM == %d \n",VERSION_NUM);
    #endif
     
    #undef VERSION_NUM                      //取消VERSION_NUM的定义
    
    #ifndef VERSION_NUM                     //如果没有定义VERSION_NUM
        printf("undef取消了VERSION_NUM的定义 \n");
    #endif
       }
    return 0;
}

所以,那些不用和程序生命周期相绑定的宏,在使用完后你应该取消掉。比如YY系列NSAttributedString+YYText.m中的宏Fail:

2.png

宏的应用

温故了宏的知识后,我们来看看大牛们是怎么高效的使用宏吧。

苹果官方提供的好用的宏

UIKIT_EXTERN       //UIKIT_EXTERN是根据是否支持C++环境做的宏定义,大体类似于extern,使用需要引入<UIKit/UIKit.h> 

NS_REQUIRES_SUPER  //对于一些可以被继承的类,需要子类在重某一调用父类的实现以保证正确的行为,通过在头文件方法的声明末尾添加。如果子类没有调用[super method],会有警告提示。

NS_AVAILABLE_IOS(_ios)  //你可以规定你自己的方法在iOS版本号适用

NS_DEPRECATED      //过期提醒

NS_DESIGNATED_INITIALIZER //Objective-C中主要通过NS_DESIGNATED_INITIALIZER宏来实现指定构造器的。

NSLocalizedString(key, comment) \
        [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] //字符串的本地化

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-相关命令"
    // your code
#pragma clang diagnostic pop

比如:
方法弃用告警
#pragma clang diagnostic ignored "-Wdeprecated-declarations"    
不兼容指针类型
#pragma clang diagnostic ignored "-Wincompatible-pointer-types"  
循环引用
#pragma clang diagnostic ignored "-Warc-retain-cycles" 
未使用变量
#pragma clang diagnostic ignored "-Wunused-variable"  

第三方框架的应用



#define MJWeakSelf __weak typeof(self) weakSelf = self;  //弱引用

// 运行时objc_msgSend  转函数指针
#define MJRefreshMsgSend(...) ((void (*)(void *, SEL, UIView *))objc_msgSend)(__VA_ARGS__)
#define MJRefreshMsgTarget(target) (__bridge void *)(target)


#ifndef kSystemVersion
#define kSystemVersion [UIDevice systemVersion]    //版本号
#endif

#define YYAssertMainThread() NSAssert([NSThread isMainThread], @"This method must be called on the main thread")  //利用断言实现方法必须在主线程调用

#define YYCAssertNil(condition, description, ...) NSCAssert(!(condition), (description), ##__VA_ARGS__)    //利用断言保证对象nil,否则crash

#define YYCAssertNotNil(condition, description, ...) NSCAssert((condition), (description), ##__VA_ARGS__)   //利用断言保证对象非nil,否则crash

#define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }           

const

define有个优势就是没有类型,所以就算你把下面的ANIMATION_DURATION赋给int编译器也不会产生警告。从另外一个角度来说,这也是宏的一个弊端。比如说:
当你写动画效果的时候需要设定一个动画展示的时间。所以代码可能是这样的:

#define ANIMATION_DURATION 0.25

很棒,当我们的动画是组合动画的时候我们也可以通过多次调用ANIMATION_DURATION来解决硬编码的问题。但问题来了,虽然你的宏命名很规范,我可以大概猜出与时间的有关系,但还是无法确定准确的类型。所以我们有了更好的选择:

static const NSTimeInterval kAnimationDuration = 0.25;

#define 最大的优势是它并不需要在你的工程里面占用内存(比如上面的ANIMATION_DURATION,但0.25还是需要分配内存,放在常量区)。当你使用了这个定义的宏,它实际上只是用你的设定的那个值0.25在编译的时候去替换。这就造成了,你使用了多少次这个宏,就需要进行多次替换,占用运行内存。上面讲到的__COUNTER__就保证了每一次调用同样的宏,产生的立即数都不一样。
const定义的常量储存在数据段,只有一份copy,这样效率更高。
更多信息#define VS const请点击这里

文章如果有不足之处,还望指教!

参考资料:

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

推荐阅读更多精彩内容