Objective-C 宏定義詳細(xì)介紹
喜歡讀一些開源項(xiàng)目源碼的人,總是會發(fā)現(xiàn),大神的代碼中總是有那么一些簡短而高效的宏定義,點(diǎn)擊進(jìn)去一看,發(fā)現(xiàn)晦澀難懂,別說學(xué)習(xí)了,有時(shí)候理解都是一種困難,但是宏定義本身并沒有那么難,但是寫出一個(gè)好的宏當(dāng)然還是需要豐富的經(jīng)驗(yàn)和技術(shù),接下來就說一說宏定義,看懂大神的宏是第一步,偶爾寫一個(gè)也是裝逼的好辦法~
定義:
宏定義分為兩種:一種是對象宏(object-like macro)另一種就是函數(shù)宏(function-like macro)
根據(jù)名字也可以理解到,對象宏就是用來定義一個(gè)量,通過這個(gè)宏可以拿到這個(gè)變量,比如我們定義一個(gè)π值: #define PI 3.1415926在這里如果用到π值時(shí),就不需要再寫出一個(gè)浮點(diǎn)數(shù)了,而直接使用PI就相當(dāng)寫入了這個(gè)常量浮點(diǎn)數(shù),其本質(zhì)的意義在于把代碼中的PI在編譯階段替換為真正的常量,一般用來定義一些常用的常量,比如屏幕的寬高、系統(tǒng)版本號等。但是需要注意的是,但你定義一個(gè)表達(dá)式為宏的時(shí)候,需要透過宏的表面,看到器編譯的本質(zhì),例如
#define MARGIN 10 + 20
但你用它來計(jì)算一個(gè)寬度時(shí),如果用到了MARGIN * 2,結(jié)果將會非你所愿,你得到的會是一個(gè)50而并非60,展開表達(dá)式就可以看到
MARGIN * 2 // 展開可以得到
// 10 + 20 * 2 = 50
我們需要考慮到它的運(yùn)算優(yōu)先級,解決的方式很簡單,再它的外層加上一個(gè)小括號
#define MARGIN (10 + 20) // MARGIN * 2 // (10 + 20) * 2 = 60
函數(shù)宏的作用就類似于一個(gè)函數(shù)一樣,它可以傳遞參數(shù),通過參數(shù)進(jìn)行一系列的操作,比如我們常用的計(jì)算兩個(gè)數(shù)的最大值,我們可以這樣來定義
#define MAX(A,B) A > B ? A : B
這樣寫看起來是沒有問題的,進(jìn)行簡單的比較MAX(1,2)發(fā)現(xiàn)也是沒有什么問題,但是當(dāng)有人使用你的宏進(jìn)行更加復(fù)雜的計(jì)算時(shí)就回出現(xiàn)新的問題,比如進(jìn)行三個(gè)數(shù)值的計(jì)較時(shí),可能會這樣寫
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ò),我們可以展開宏來一探究竟,下面是宏的展開
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)先級 // 帶入值可以看出 //( 3 > (2 > 1 ? 2 : 1 ) ? 3 : 2) > 1 ? 2 : 1 // (3 > 2 ? 3 : 2) > 1 ? 2 : 1 // 3 > 1 ? 2 : 1
想必大家都看出來了問題所在,還是由于優(yōu)先級的問題,所以在此謹(jǐn)記,反正多寫兩個(gè)括號也不會累著,不管會不會出現(xiàn)問題, 寫上小括號終究是保險(xiǎn)一些~
可是總有寫奇葩的寫法會出現(xiàn),而且看開起來還很有道理的樣子~
c = MAX(a++,b); // **我直接展開給你看就得了** // c = a++ > b ? a++ : b // c = 3++ > 2 ? 3++ : 2 // c = 4 // a = 5
不管這樣寫的那個(gè)人是有多欠揍,但是畢竟看起來是沒有任何問題的,所有我們要處理這樣的情況,但是使用我們普通的小括號已經(jīng)無法解決,我們需要使用賦值擴(kuò)展({...})相信有朋友已經(jīng)認(rèn)出來了這種用法了,我們可以使用這樣的方法來計(jì)算出一個(gè)對象,而不用浪費(fèi)變量名,可以形成小范圍的作用域來計(jì)算特殊的值
int a = ({ int b = 10; int c = 20; b + c; }) // a = 30; int b; // 繼續(xù)使用b和c當(dāng)變量名也沒有問題 int c;
再回到現(xiàn)在這個(gè)問題上,我們該如何改裝這個(gè)宏來讓其適應(yīng)這個(gè)坑爹的寫法呢
#define MAX(A,B) ({__typeof(A) __a = (A);__typeof(B) __b = (B); __a > __b ? __a : __b; })
__typeof()就是轉(zhuǎn)換為相同類型的變量值,就完美的解決了這個(gè)問題,但是還有一個(gè)不怎么會發(fā)生的意外,通過上面也可以知道,我們生成了新的變量__a, __b,如何有人使用了__a,__b,就會應(yīng)為變量名重復(fù)而編譯錯(cuò)誤,如果有人這樣用了,你可以拿起你的鍵盤砸他一臉,原因當(dāng)然不是__a使你的宏錯(cuò)誤了,而是__a到底是什么意思,變量名的重要性不言而喻,除非你和看代碼的人有仇,否則請使用有意義的變量名,接下來讓我們看一看官方的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定義,我么來一步一步的解析它,首先看見的是
#define __NSX_PASTE__(A,B) A##B
// 將A和B連接到一塊
它的作用是將A和B連接到一塊,用來生成一個(gè)的字符串,比如A##12就成了A12
接下來我們看到了一個(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
我們先來解釋__COUNTER__是什么,__COUNTER__是一個(gè)預(yù)編譯宏,它將會在每次編譯時(shí)加1,這樣的話可以保證__NSX_PASTE__(__b,__CONNTER__)生成的變量名不易重復(fù),但是這樣還是有那么點(diǎn)危險(xiǎn),就是你要是起變量名叫__a20,那就真的真的沒有辦法了~
可變參數(shù)宏
說起可變參數(shù),我們用的最多的一個(gè)方法NSLog(...)就是可變參數(shù)了,可變參數(shù)意味著參數(shù)的個(gè)數(shù)是不定的,而NSLog作為我們調(diào)試時(shí)一個(gè)重要的工具實(shí)在時(shí)太廢物了,只能打印對應(yīng)的時(shí)間和參數(shù)信息,而文件名,行數(shù),方法名等重要的信息都沒有給出,今天我們就借此來實(shí)現(xiàn)一個(gè)超級版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ù),接下來我們發(fā)現(xiàn)使用了一個(gè)do{}while(0)循環(huán),說明這個(gè)循環(huán)只執(zhí)行一便就回停止,感覺廢話啊,我們的目的就是只執(zhí)行一遍啊,但這樣寫又是為了進(jìn)行防御式編程,如果有人這樣寫的話
if (100 > 99)
NSLog(@"%@",@"Fuck");
就會出現(xiàn)無論如何都會執(zhí)行后兩個(gè)打印,出現(xiàn)的問題想必大家也都知道,那我們直接使用{}給擴(kuò)起來不就行了,實(shí)際操作后確實(shí)是解決了這個(gè)問題,但是再擴(kuò)展一下,當(dāng)我們使用了if{} else if{}時(shí)又會出現(xiàn)新的問題
if (100 > 99) NSLog(@"%@",@"Fuck"); else { } // 展開后可得 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后面會跟上;,如果我么直接使用了{(lán)}后,會在編譯時(shí)在外面加上;,導(dǎo)致編譯錯(cuò)誤,而使用了do{} while(0)循環(huán)后就不會出現(xiàn)這個(gè)問題了
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 { }
到此位置問題解決的差不多了,看一下內(nèi)部的結(jié)構(gòu),__FILE__是編譯的文件路徑,__LINE__是行數(shù),__func__是編譯的方法名,下面我們又看見了
(NSLog)((format), ##__VA_ARGS__);
##上面已經(jīng)看見過了,在這里的作用差不多,也是連接的意思,__VA_ARGS__是剩下的所有參數(shù),使用##連接起來后就時(shí)NSLog(format,__VA_ARGS__)了,這就是NSLog的方法了,但是不知道有沒有人發(fā)現(xiàn)一個(gè)細(xì)節(jié),如果__VA_ARGS__為空的話,那豈不是成了NSLog(format,)這樣肯定會編譯報(bào)錯(cuò)的,但是蘋果的大神們早就想到了解決的方法,如果__VA_ARGS__為空的話,在這里##將會吞掉前面的,,這樣一來就不會出問題了。然后我們就可以使用這個(gè)強(qiáng)大的NSLog()了。
接下說一下多參數(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來定義多參數(shù)變量args,然后通過va_start(args, code)來開始取值,code是第一個(gè)值,va_arg(args, NSString *)來定義取出的值類型,取值方式有點(diǎn)像生成器,取完之后調(diào)用va_end(args)來關(guān)閉。這就是整個(gè)過程,平時(shí)很少使用這樣的方法,如果你有什么好的實(shí)用方法請?jiān)u論指教~~~
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
iOS應(yīng)用運(yùn)用設(shè)計(jì)模式中的Strategy策略模式的開發(fā)實(shí)例
這篇文章主要介紹了iOS應(yīng)用開發(fā)中對設(shè)計(jì)模式中的Strategy策略模式的運(yùn)用,例子采用傳統(tǒng)的Objective-C語言代碼演示,需要的朋友可以參考下2016-03-03詳解iOS中多個(gè)網(wǎng)絡(luò)請求的同步問題總結(jié)
這篇文章主要介紹了詳解iOS中多個(gè)網(wǎng)絡(luò)請求的同步問題總結(jié),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05iOS自定義UIScrollView的滾動條實(shí)例代碼
本篇文章主要介紹了iOS自定義UIScrollView的滾動條實(shí)例代碼,詳細(xì)的介紹了自定義滾動條的示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03iOS左右滑動標(biāo)簽頁導(dǎo)航的設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了iOS左右滑動標(biāo)簽頁導(dǎo)航的設(shè)計(jì)思路,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06