引
什么是預(yù)處理器,跟我有什么關(guān)系竣稽?
預(yù)處理器是在OC源文件編譯過程中的一個部分囱怕,而且是第一個處理部分,預(yù)處理器的預(yù)也由此可見丧枪。
整個編譯過程可以大致分為:預(yù)處理器進(jìn)行詞法分析 -> 語法分析 -> 生成代碼和優(yōu)化 -> 生成可執(zhí)行的二進(jìn)制文件光涂。
既然有這么多過程,為什么要關(guān)注預(yù)處理器呢拧烦?因?yàn)樗谖覀兊拈_發(fā)中最常見忘闻,而且每個iOS開發(fā)者一定都見過。
不信的話我們可以列舉一下常見的預(yù)處理指令恋博,預(yù)處理器有其區(qū)別于Objective-C的獨(dú)特語法齐佳,語法形式如下:
#指令名 指令參數(shù)
有點(diǎn)眼熟了?我們再具體地說說包含哪些:
- 頭文件包含(#include债沮、#import)
- 條件編譯(#if炼吴、#elif、#else疫衩、#endif硅蹦、#ifdef和#ifndef)
- 診斷(#error、#warning和#line)
-
pragma指令
這樣列出來就明白了吧闷煤,早說是這些就簡單了嘛童芹,大部分都是熟人,慢著鲤拿,這些熟悉的具體表示什么假褪?有什么區(qū)別?那些不太熟的又是干什么的呢近顷?我們一個個來看生音。
除了上述的指令外,還有一個老熟人也屬于預(yù)處理器的范疇窒升,下文再來說缀遍。
預(yù)處理器指令
頭文件包含
學(xué)C語言的時候就接觸到了#include,學(xué)java也會用到import(注意沒有#號)饱须,都是用來導(dǎo)入頭文件的瑟由,這個作用我們明白,OC中的導(dǎo)入頭文件有#include和#import兩種指令,而且對于頭文件名還分為雙引號包含和尖括號包含兩種方式:
#include "頭文件名"
#include <頭文件名>
#import "頭文件名"
#import <頭文件名>
問題來了歹苦,有啥區(qū)別青伤?
先說雙引號和尖括號的區(qū)別,雙引號封裝頭文件名時殴瘦,會先從存儲要編譯的這個文件的目錄中去搜索包含的頭文件狠角,找不到再去用來搜索系統(tǒng)標(biāo)準(zhǔn)頭文件的默認(rèn)目錄搜索。而尖括號封裝頭文件名時蚪腋,會直接去用來搜索系統(tǒng)標(biāo)準(zhǔn)頭文件的默認(rèn)目錄搜索丰歌。由此可見,要用尖括號封裝標(biāo)準(zhǔn)頭文件屉凯,而自己寫的OC類頭文件立帖,應(yīng)該用雙引號封裝。
而對于#include和#import這兩者悠砚,區(qū)別在于#import可以確保頭文件只被引用一次晓勇,這樣就可以防止遞歸包含,什么叫遞歸包含灌旧,A引用B和C绑咱,B也引用了C,那就都包含了C枢泰,這就重復(fù)包含了描融。因此,如果非要用#include衡蚂,那必須額外地寫指令來判斷有沒有包含過窿克,來避免遞歸包含。
條件編譯
條件編譯特別像我們在所有編程語言中都能看到的 if ... else if ... else 形式毛甲,也就是條件判斷語句年叮。
用法如下
#if(對應(yīng)于if)
// 執(zhí)行內(nèi)容
#elif(對應(yīng)于else if)
// 執(zhí)行內(nèi)容
#else(對應(yīng)于else)
// 執(zhí)行內(nèi)容
#endif
對于各個語句的用法要求也和一般語言相同,特殊的是最底下有一個#endif丽啡,畢竟沒有大括號也沒有縮進(jìn)嘛谋右,而且支持嵌套操作硬猫,那嵌套的界限就更要靠#endif來判斷了對吧补箍。
除了這些以外,還有兩個:
#ifdef 宏名
// 執(zhí)行內(nèi)容
#endif
#ifndef 宏名
// 執(zhí)行內(nèi)容
#endif
其中的def是define的簡寫啸蜜,ndef也就是not define坑雅,很容易猜到意思,分別就是判斷是否定義過后面跟著的宏衬横。同樣的要用#endif來作為結(jié)束的界限裹粤。
診斷
診斷中先說頭兩個:
#ifndef 宏名
#error "發(fā)生錯誤啦"
#endif
#if XXX
#warning "警報(bào)!警報(bào)蜂林!"
#endif
一般都用在條件判斷語句內(nèi)容中遥诉,后面都跟著雙引號帶著的消息拇泣,error指令會直接中止編譯,拋出錯誤消息矮锈,warning也會拋出警告消息霉翔,但不會中止編譯。
第三種診斷指令:
#line 行號 "文件名"
//假設(shè)這里有一行會發(fā)生錯誤的代碼
這個指令理解起來有些復(fù)雜苞笨,首先line定義了一個行號债朵,那么之后每一行都會有一個在此基礎(chǔ)上依次加一的行號,比如下一行的錯誤代碼就是第11行瀑凝。發(fā)生錯誤后序芦,會拋出說"文件名"文件的第11行有錯誤。后面跟著的文件名是一個可選項(xiàng)粤咪,寫了就可以在消息中顯示谚中,不寫也沒關(guān)系。
#pragma指令
這個指令更常見了射窒,我們使用UITableView的時候藏杖,經(jīng)常會用到:
#pragma mark - UITableView DataSource
……
#pragma mark - UITableView Delegate
……
這個#pragma mark指令可以在Xcode 中的該文件的方法列表中插入標(biāo)記,#pragma mark -就可以插入一個分隔線脉顿,后跟文字就可以插入文字標(biāo)簽蝌麸。
除此之外,#pragma指令還包含很多別的選項(xiàng)艾疟,上面的是用的最多的来吩,其他的可以查看文檔。
預(yù)處理器之宏
要知道蔽莱,宏也是預(yù)處理器范疇內(nèi)的內(nèi)容弟疆,我們用的也很多:
// 定義常量值
#define 宏名 值
//定義函數(shù)宏
#define 宏名(參數(shù)) 代碼
// 移除宏
#undef 宏名
宏被定義后,會一直存在盗冷,并且能在整個文件中起作用怠苔,直到被#undef指令移除為止。如果函數(shù)有多個參數(shù)仪糖,用逗號分隔開柑司。
定義函數(shù)宏的時候,有一個細(xì)節(jié)要注意锅劝,就是要多對參數(shù)使用括號:
#defind SQUARE(x) ((x) * (x))
為什么要這么麻煩攒驰?為什么不能直接 x * x?要知道故爵,宏在這個意義上是很“傻”的玻粪,它只會單純的將你輸入的x值拿去替換函數(shù)代碼中的x,并不會做什么處理,所以如果你這樣輸入就會造成沒有意料到的結(jié)果:
#defind SQUARE(x) x * x
int number = SQUARE(4+2);// 你以為會等于36劲室?并不會
// 我們說了伦仍,宏只會簡單替換,所以上面等價于:
int number = 4 + 2 * 4 + 2;// 其實(shí)等于14
知道問題所在了吧很洋,這很嚴(yán)重呢铆,因?yàn)椴恢赖脑捀緹o法理解這個bug為什么會出現(xiàn),所以都應(yīng)該使用括號蹲缠。此外棺克,如果你的代碼有多行,還應(yīng)該使用大括號括起來:
#define FUNC(a, b) {a = a + b; b = a - b;}
此外线定,不要過度使用宏娜谊!宏很強(qiáng)大,也很危險斤讥,出了問題往往難以診斷纱皆,也不好維護(hù)。
結(jié)
以上就是OC編譯中的預(yù)處理器中的一些預(yù)處理語言函數(shù)的內(nèi)容芭商,預(yù)處理器的內(nèi)容當(dāng)然不單單只有這些派草,還有對源文件的一些處理,但這些是我們平常開發(fā)中經(jīng)常遇到的铛楣,了解他們是必須且重要的近迁。