你應(yīng)該掌握的#define宏定義

如果你并不是一個new coder ,那你一定是知道#define的。是的,它并不完美回梧,而且被很多人所詬病。但是的的確確會讓你的代碼看起來看簡潔祖搓,更方便閱讀狱意。下面讓我們先來溫習(xí)下宏的基礎(chǔ)。

基礎(chǔ)宏

定義宏標(biāo)示 (Object-like Macros)

如果你想定義一個緩存區(qū)的大小拯欧,又不太想硬編碼详囤,你可能會選擇下面的方式

#define BUFFER_SIZE 1024

如果你在#define標(biāo)識符后面,使用了BUFFER_SIZE代碼塊開辟一塊區(qū)間

 foo = (char *) malloc (BUFFER_SIZE);

C預(yù)處理器將會識別BUFFER_SIZE并且把它替換成你上面寫的1024:

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

按照約定,宏定義需要以大寫的形式呈現(xiàn)镐作。這樣讓人瞟一眼就知道你寫的個啥玩意藏姐。

定義宏函數(shù) (Function-like Macros)

你也可以定義一個宏,它看起來像一個函數(shù)一樣该贾。比如:

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

當(dāng)然你也可以去定義一個A減去B帶參數(shù)的函數(shù):

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

驗證一下: SUB(2, SUB(1,3))

1.png

干的不錯羔杨,再來一個取兩者更小的一個數(shù)的宏

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

驗證一下:

| 表達式 | 結(jié)果 | 是否正確 |錯誤原因|
| -------- | -----: | :----: ||
| 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)

信心爆棚的你再次跑起了測試护姆,仿佛一切都在你的掌握之中,直到遇到了那個它:

| 表達式 | 結(jié)果 | 是否正確 ||
| -------- | -----: | :----: ||
| 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))

說了這么多廢話掏击,只是想提醒大家能加上括號的就一定加括號卵皂。當(dāng)然加了括號也不是萬能了。比如:

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

不再作展開操作了砚亭,更詳細的可以去看喵大的宏定義的黑魔法
實在感覺到很無力灯变。默默的打開XCODE,看看蘋果官方的標(biāo)準(zhǔn)寫法:

#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提供用來構(gòu)造獨立變量名的標(biāo)識符殴玛,在預(yù)編譯過程中從0開始計數(shù),每次被調(diào)用的時候+1).也就是連續(xù)調(diào)用兩次MIN(A,B),兩次的A在預(yù)編譯中會以不同的變量名存在添祸。

#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("因為預(yù)編譯FUNC被調(diào)用了兩次滚粟,所以__COUNTER__打印的值是從2開始");
    }
    return 0;
}

=>打印:
__COUNTER__==2 
__COUNTER__==3 
因為預(yù)編譯FUNC被調(diào)用了兩次刃泌,所以__COUNTER__打印的值是從2開始

__NSX_PASTE__ 是預(yù)編譯的連接符凡壤,宏定義中不能直接寫 AB 來連接參數(shù),需要寫成 A##B耙替。
__typeof__ 是gcc對C語言的一個擴展保留字亚侠,用于聲明變量類型,var可以是數(shù)據(jù)類型(int, char*..),也可以是變量表達式俗扇。
__NSMIN_IMPL__現(xiàn)在多加了個__COUNTER__的參數(shù)生成獨立標(biāo)示符硝烂。
認識了上面的大兄弟,我們現(xiàn)在自己來轉(zhuǎn)換一下铜幽,應(yīng)該就很好讀了:

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

有時候你可能想把一個宏的參數(shù)轉(zhuǎn)成一個字符串常量滞谢。可是你并沒有辦法直接把宏參數(shù)硬塞到字符串里面去除抛,這個時候“#”宏預(yù)處理符會幫助到你狮杨。當(dāng)一個宏參數(shù)前面加上“#”宏預(yù)處理符,你就可以成功的把宏參數(shù)轉(zhuǎn)成字符串了到忽。
來個例子禾酱,我們開發(fā)了一個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

宏串聯(lián)(Concatenation)

在上面的例子我們有提到過“##”宏串聯(lián)符。假如現(xiàn)在你想整理你電腦里面的動漫绘趋,分為HYRZ和SQBB系列颤陶,后面加上數(shù)字代表集數(shù)。

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

宏可以和一個方法一樣能接受一系列的參數(shù)陷遮。這面是個例子:

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

這種宏就叫做variadic滓走。在宏定義(其實也包括函數(shù)定義)的時候,寫為...的參數(shù)被叫做可變參數(shù)(variadic)帽馋〗练剑可變參數(shù)的個數(shù)不做限定。當(dāng)宏被使用下面的這些參數(shù)會替換這個宏里面的__VA_ARGS__绽族。所有姨涡,我們可以有下面的展開:

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

打印出當(dāng)前的文件在你電腦上的路徑,和該行代碼在你當(dāng)前文件的當(dāng)前行吧慢。

再來個相加的加法的例子:

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

printf("結(jié)果為:%d\n", ADD(1, 2, 3));;
=>打印一下

結(jié)果為:6

可以看出你可以在A2宏里面擴展成N個參數(shù)涛漂。這就證明了__VA_ARGS__參數(shù)的個數(shù)不做限定。

標(biāo)準(zhǔn)的預(yù)定義宏(Standard Predefined Macros)

表達式 含義
__FILE__ 當(dāng)前源文件的路徑
__LINE__ 當(dāng)前代碼的在源文件的行數(shù)
__DATE__ 當(dāng)前的日期
__TIME__ 當(dāng)前的時間
__FUNCTION__ 當(dāng)前的方法名

標(biāo)準(zhǔn)的預(yù)定義宏被相關(guān)語言標(biāo)準(zhǔn)所定義,所以他們適用于各種編譯器匈仗。

表達式 含義
__FILE__ 當(dāng)前源文件的路徑
__LINE__ 當(dāng)前代碼的在源文件的行數(shù)
__DATE__ 當(dāng)前的日期
__TIME__ 當(dāng)前的時間
__FUNCTION__ 當(dāng)前的方法名
#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;
}

所以,那些不用和程序生命周期相綁定的宏悠轩,在使用完后你應(yīng)該取消掉间狂。比如YY系列NSAttributedString+YYText.m中的宏Fail:

2.png

宏的應(yīng)用

溫故了宏的知識后,我們來看看大牛們是怎么高效的使用宏吧火架。

蘋果官方提供的好用的宏

UIKIT_EXTERN       //UIKIT_EXTERN是根據(jù)是否支持C++環(huán)境做的宏定義鉴象,大體類似于extern,使用需要引入<UIKit/UIKit.h> 

NS_REQUIRES_SUPER  //對于一些可以被繼承的類何鸡,需要子類在重某一調(diào)用父類的實現(xiàn)以保證正確的行為炼列,通過在頭文件方法的聲明末尾添加。如果子類沒有調(diào)用[super method],會有警告提示音比。

NS_AVAILABLE_IOS(_ios)  //你可以規(guī)定你自己的方法在iOS版本號適用

NS_DEPRECATED      //過期提醒

NS_DESIGNATED_INITIALIZER //Objective-C中主要通過NS_DESIGNATED_INITIALIZER宏來實現(xiàn)指定構(gòu)造器的。

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

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-相關(guān)命令"
    // your code
#pragma clang diagnostic pop

比如:
方法棄用告警
#pragma clang diagnostic ignored "-Wdeprecated-declarations"    
不兼容指針類型
#pragma clang diagnostic ignored "-Wincompatible-pointer-types"  
循環(huán)引用
#pragma clang diagnostic ignored "-Warc-retain-cycles" 
未使用變量
#pragma clang diagnostic ignored "-Wunused-variable"  

第三方框架的應(yīng)用



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

// 運行時objc_msgSend  轉(zhuǎn)函數(shù)指針
#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")  //利用斷言實現(xiàn)方法必須在主線程調(diào)用

#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有個優(yōu)勢就是沒有類型,所以就算你把下面的ANIMATION_DURATION賦給int編譯器也不會產(chǎn)生警告焰望。從另外一個角度來說骚亿,這也是宏的一個弊端。比如說:
當(dāng)你寫動畫效果的時候需要設(shè)定一個動畫展示的時間熊赖。所以代碼可能是這樣的:

#define ANIMATION_DURATION 0.25

很棒来屠,當(dāng)我們的動畫是組合動畫的時候我們也可以通過多次調(diào)用ANIMATION_DURATION來解決硬編碼的問題。但問題來了震鹉,雖然你的宏命名很規(guī)范俱笛,我可以大概猜出與時間的有關(guān)系,但還是無法確定準(zhǔn)確的類型传趾。所以我們有了更好的選擇:

static const NSTimeInterval kAnimationDuration = 0.25;

#define 最大的優(yōu)勢是它并不需要在你的工程里面占用內(nèi)存(比如上面的ANIMATION_DURATION迎膜,但0.25還是需要分配內(nèi)存,放在常量區(qū))浆兰。當(dāng)你使用了這個定義的宏磕仅,它實際上只是用你的設(shè)定的那個值0.25在編譯的時候去替換。這就造成了簸呈,你使用了多少次這個宏榕订,就需要進行多次替換,占用運行內(nèi)存蜕便。上面講到的__COUNTER__就保證了每一次調(diào)用同樣的宏劫恒,產(chǎn)生的立即數(shù)都不一樣。
const定義的常量儲存在數(shù)據(jù)段轿腺,只有一份copy兼贸,這樣效率更高段直。
更多信息#define VS const請點擊這里

文章如果有不足之處,還望指教溶诞!

參考資料:

GCC_MACROS
宏定義的黑魔法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸯檬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子螺垢,更是在濱河造成了極大的恐慌喧务,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枉圃,死亡現(xiàn)場離奇詭異功茴,居然都是意外死亡,警方通過查閱死者的電腦和手機孽亲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門坎穿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人返劲,你說我怎么就攤上這事玲昧。” “怎么了篮绿?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵孵延,是天一觀的道長。 經(jīng)常有香客問我亲配,道長尘应,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任吼虎,我火速辦了婚禮犬钢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘思灰。我一直安慰自己娜饵,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布官辈。 她就那樣靜靜地躺著箱舞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拳亿。 梳的紋絲不亂的頭發(fā)上晴股,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音肺魁,去河邊找鬼电湘。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寂呛。 我是一名探鬼主播怎诫,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贷痪!你這毒婦竟也來了幻妓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤劫拢,失蹤者是張志新(化名)和其女友劉穎肉津,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舱沧,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡妹沙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了熟吏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片距糖。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牵寺,靈堂內(nèi)的尸體忽然破棺而出悍引,到底是詐尸還是另有隱情,我是刑警寧澤缸剪,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站东亦,受9級特大地震影響杏节,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜典阵,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一奋渔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧壮啊,春花似錦嫉鲸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狸眼,卻和暖如春藤树,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拓萌。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工岁钓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓屡限,卻偏偏與公主長得像品嚣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子钧大,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容