Objective-C宏定義(你真的看懂宏定義了嗎。。。)

喜歡讀一些開(kāi)源項(xiàng)目源碼的人,總是會(huì)發(fā)現(xiàn),大神的代碼中總是有那么一些簡(jiǎn)短而高效的宏定義,點(diǎn)擊進(jìn)去一看,發(fā)現(xiàn)晦澀難懂,別說(shuō)學(xué)習(xí)了,有時(shí)候理解都是一種困難,但是宏定義本身并沒(méi)有那么難,但是寫出一個(gè)好的宏當(dāng)然還是需要豐富的經(jīng)驗(yàn)和技術(shù),接下來(lái)就說(shuō)一說(shuō)宏定義,看懂大神的宏是第一步,偶爾寫一個(gè)也是裝逼的好辦法~

定義

宏定義分為兩種:一種是對(duì)象宏(object-like macro)另一種就是函數(shù)宏(function-like macro)
根據(jù)名字也可以理解到,對(duì)象宏就是用來(lái)定義一個(gè)量,通過(guò)這個(gè)宏可以拿到這個(gè)變量,比如我們定義一個(gè)π值: #define PI 3.1415926在這里如果用到π值時(shí),就不需要再寫出一個(gè)浮點(diǎn)數(shù)了,而直接使用PI就相當(dāng)寫入了這個(gè)常量浮點(diǎn)數(shù),其本質(zhì)的意義在于把代碼中的PI在編譯階段替換為真正的常量,一般用來(lái)定義一些常用的常量,比如屏幕的寬高、系統(tǒng)版本號(hào)等。但是需要注意的是,但你定義一個(gè)表達(dá)式為宏的時(shí)候,需要透過(guò)宏的表面,看到器編譯的本質(zhì),例如

#define MARGIN  10 + 20

但你用它來(lái)計(jì)算一個(gè)寬度時(shí),如果用到了MARGIN * 2,結(jié)果將會(huì)非你所愿,你得到的會(huì)是一個(gè)50而并非60,展開(kāi)表達(dá)式就可以看到

MARGIN * 2 // 展開(kāi)可以得到
//  10 + 20 * 2  = 50

我們需要考慮到它的運(yùn)算優(yōu)先級(jí),解決的方式很簡(jiǎn)單,再它的外層加上一個(gè)小括號(hào)

#define MARGIN (10 + 20)
// MARGIN * 2
// (10 + 20) * 2 = 60

函數(shù)宏的作用就類似于一個(gè)函數(shù)一樣,它可以傳遞參數(shù),通過(guò)參數(shù)進(jìn)行一系列的操作,比如我們常用的計(jì)算兩個(gè)數(shù)的最大值,我們可以這樣來(lái)定義

#define MAX(A,B)  A > B ? A : B

這樣寫看起來(lái)是沒(méi)有問(wèn)題的,進(jìn)行簡(jiǎn)單的比較MAX(1,2)發(fā)現(xiàn)也是沒(méi)有什么問(wèn)題,但是當(dāng)有人使用你的宏進(jìn)行更加復(fù)雜的計(jì)算時(shí)就回出現(xiàn)新的問(wèn)題,比如進(jìn)行三個(gè)數(shù)值的計(jì)較時(shí),可能會(huì)這樣寫

int a = 3;
int b = 2;
int c = 1;
MAX(a, b > c ? b : c)  //
= 2

結(jié)果肯定也不是你想要的,最大值很明顯是3,但是計(jì)算的結(jié)果確實(shí)2,這其中發(fā)生了什么導(dǎo)致計(jì)算出錯(cuò),我們可以展開(kāi)宏來(lái)一探究竟,下面是宏的展開(kāi)

MAX(a,b > c ? b : c);
//a > b > c ? b : c ? a : b > c ? b : c
//(a > (b > c ? b : c) ? a : b) > c ? b : c // 這是運(yùn)算的優(yōu)先級(jí)
// 帶入值可以看出
//( 3 > (2 > 1 ?  2 : 1 ) ? 3 : 2) > 1 ? 2 : 1
// (3 > 2 ? 3 : 2) > 1 ? 2 : 1
// 3 > 1 ? 2 : 1

想必大家都看出來(lái)了問(wèn)題所在,還是由于優(yōu)先級(jí)的問(wèn)題,所以在此謹(jǐn)記,反正多寫兩個(gè)括號(hào)也不會(huì)累著,不管會(huì)不會(huì)出現(xiàn)問(wèn)題,** 寫上小括號(hào)終究是保險(xiǎn)一些~ **
可是總有寫奇葩的寫法會(huì)出現(xiàn),而且看開(kāi)起來(lái)還很有道理的樣子~

c = MAX(a++,b); //  **我直接展開(kāi)給你看就得了**
// c = a++ > b ? a++ : b
// c = 3++ > 2 ? 3++ : 2
// c = 4
// a = 5

不管這樣寫的那個(gè)人是有多欠揍,但是畢竟看起來(lái)是沒(méi)有任何問(wèn)題的,所有我們要處理這樣的情況,但是使用我們普通的小括號(hào)已經(jīng)無(wú)法解決,我們需要使用賦值擴(kuò)展({...})相信有朋友已經(jīng)認(rèn)出來(lái)了這種用法了,我們可以使用這樣的方法來(lái)計(jì)算出一個(gè)對(duì)象,而不用浪費(fèi)變量名,可以形成小范圍的作用域來(lái)計(jì)算特殊的值

int a = ({
  int b = 10;
  int c = 20;
  b + c;
})
// a = 30;
int b; // 繼續(xù)使用b和c當(dāng)變量名也沒(méi)有問(wèn)題
int c;

再回到現(xiàn)在這個(gè)問(wèn)題上,我們?cè)撊绾胃难b這個(gè)宏來(lái)讓其適應(yīng)這個(gè)坑爹的寫法呢

#define MAX(A,B) ({__typeof(A) __a = (A);__typeof(B) __b = (B); __a > __b ? __a : __b; })

__typeof()就是轉(zhuǎn)換為相同類型的變量值,就完美的解決了這個(gè)問(wèn)題,但是還有一個(gè)不怎么會(huì)發(fā)生的意外,通過(guò)上面也可以知道,我們生成了新的變量__a, __b,如何有人使用了__a,__b,就會(huì)應(yīng)為變量名重復(fù)而編譯錯(cuò)誤,如果有人這樣用了,你可以拿起你的鍵盤砸他一臉,原因當(dāng)然不是__a使你的宏錯(cuò)誤了,而是__a到底是什么意思,變量名的重要性不言而喻,除非你和看代碼的人有仇,否則請(qǐng)使用有意義的變量名,接下來(lái)讓我們看一看官方的MAX是如何實(shí)現(xiàn)的

#define __NSX_PASTE__(A,B) A##B

#if !defined(MAX)
    #define __NSMAX_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__(__b,L) : __NSX_PASTE__(__a,L); })
    #define MAX(A,B) __NSMAX_IMPL__(A,B,__COUNTER__)
#endif

這是Function框架中的MAX定義,我么來(lái)一步一步的解析它,首先看見(jiàn)的是

#define __NSX_PASTE__(A,B) A##B
// 將A和B連接到一塊

它的作用是將AB連接到一塊,用來(lái)生成一個(gè)的字符串,比如A##12就成了A12
接下來(lái)我們看到了一個(gè)有三個(gè)參數(shù)的宏定義__NSMAX_IMPL__(A,B,__COUNTER__)

#if !defined(MAX)
    #define __NSMAX_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__(__b,L) : __NSX_PASTE__(__a,L); })
    #define MAX(A,B) __NSMAX_IMPL__(A,B,__COUNTER__)
#endif

我們先來(lái)解釋__COUNTER__是什么,__COUNTER__是一個(gè)預(yù)編譯宏,它將會(huì)在每次編譯時(shí)加1,這樣的話可以保證__NSX_PASTE__(__b,__CONNTER__)生成的變量名不易重復(fù),但是這樣還是有那么點(diǎn)危險(xiǎn),就是你要是起變量名叫__a20,那就真的真的沒(méi)有辦法了~

可變參數(shù)宏

說(shuō)起可變參數(shù),我們用的最多的一個(gè)方法NSLog(...)就是可變參數(shù)了,可變參數(shù)意味著參數(shù)的個(gè)數(shù)是不定的,而NSLog作為我們調(diào)試時(shí)一個(gè)重要的工具實(shí)在時(shí)太廢物了,只能打印對(duì)應(yīng)的時(shí)間和參數(shù)信息,而文件名,行數(shù),方法名等重要的信息都沒(méi)有給出,今天我們就借此來(lái)實(shí)現(xiàn)一個(gè)超級(jí)版NSLog宏~~~

#define NSLog(format, ...)  do { fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__); \
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr, "-------\n"); \ } while (0)

首先看這個(gè)宏的定義NSLog(format,...)發(fā)現(xiàn)它有...,這就是可變參數(shù),而__VA__ARGS__就是除了format外剩下的所有參數(shù),接下來(lái)我們發(fā)現(xiàn)使用了一個(gè)do{}while(0)循環(huán),說(shuō)明這個(gè)循環(huán)只執(zhí)行一便就回停止,感覺(jué)廢話啊,我們的目的就是只執(zhí)行一遍啊,但這樣寫又是為了進(jìn)行防御式編程,如果有人這樣寫的話

if (100 > 99)
  NSLog(@"%@",@"Fuck");

就會(huì)出現(xiàn)無(wú)論如何都會(huì)執(zhí)行后兩個(gè)打印,出現(xiàn)的問(wèn)題想必大家也都知道,那我們直接使用{}給擴(kuò)起來(lái)不就行了,實(shí)際操作后確實(shí)是解決了這個(gè)問(wèn)題,但是再擴(kuò)展一下,當(dāng)我們使用了if{} else if{}時(shí)又會(huì)出現(xiàn)新的問(wèn)題

if (100 > 99)
  NSLog(@"%@",@"Fuck");
else {
}
// 展開(kāi)后可得
if (100 > 99)
{ fprintf(stderr, "<%s : %d> %s\n",
  [[[NSString stringWithUTF8String:__FILE__] lastPathComponent]  UTF8String], __LINE__, __func__);
  (NSLog)((format), ##__VA_ARGS__);
  fprintf(stderr, "-------\n");};
else {
}

編譯錯(cuò)誤,大家也發(fā)現(xiàn)了NSLog后面會(huì)跟上;,如果我么直接使用了{}后,會(huì)在編譯時(shí)在外面加上;,導(dǎo)致編譯錯(cuò)誤,而使用了do{} while(0)循環(huán)后就不會(huì)出現(xiàn)這個(gè)問(wèn)題了

if (100 > 99)
 do { fprintf(stderr, "<%s : %d> %s\n",
  [[[NSString stringWithUTF8String:__FILE__] lastPathComponent]  UTF8String], __LINE__, __func__);
  (NSLog)((format), ##__VA_ARGS__);
  fprintf(stderr, "-------\n");} while(0);
else {
}

到此位置問(wèn)題解決的差不多了,看一下內(nèi)部的結(jié)構(gòu),__FILE__是編譯的文件路徑,__LINE__是行數(shù),__func__是編譯的方法名,下面我們又看見(jiàn)了

(NSLog)((format), ##__VA_ARGS__);

##上面已經(jīng)看見(jiàn)過(guò)了,在這里的作用差不多,也是連接的意思,__VA_ARGS__是剩下的所有參數(shù),使用##連接起來(lái)后就時(shí)NSLog(format,__VA_ARGS__)了,這就是NSLog的方法了,但是不知道有沒(méi)有人發(fā)現(xiàn)一個(gè)細(xì)節(jié),如果__VA_ARGS__為空的話,那豈不是成了NSLog(format,)這樣肯定會(huì)編譯報(bào)錯(cuò)的,但是蘋果的大神們?cè)缇拖氲搅私鉀Q的方法,如果__VA_ARGS__為空的話,在這里##將會(huì)吞掉前面的,,這樣一來(lái)就不會(huì)出問(wèn)題了。然后我們就可以使用這個(gè)強(qiáng)大的NSLog()了。

接下說(shuō)一下多參數(shù)函數(shù)的使用

- (void)say:(NSString *)code,... {    
    va_list args;
    va_start(args, code);
    NSLog(@"%@",code);
    while (YES) {
        NSString *string = va_arg(args, NSString *);
        if (!string) {
            break;
        }
        NSLog(@"%@",string);
    }
    va_end(args);
}

我們可以要先定義一個(gè)va_list args來(lái)定義多參數(shù)變量args,然后通過(guò)va_start(args, code)來(lái)開(kāi)始取值,code是第一個(gè)值,va_arg(args, NSString *)來(lái)定義取出的值類型,取值方式有點(diǎn)像生成器,取完之后調(diào)用va_end(args)來(lái)關(guān)閉。這就是整個(gè)過(guò)程,平時(shí)很少使用這樣的方法,如果你有什么好的實(shí)用方法請(qǐng)?jiān)u論指教~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,202評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,742評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,580評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,297評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,688評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,875評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,438評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,183評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,384評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,612評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,022評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,297評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,093評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,330評(píng)論 2 377

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