標籤:
[精通Objective-C]前置處理器
參考書籍:《精通Objective-C》【美】 Keith Lee
目錄
前置處理器概述
預先處理根據一系列預定義規則,使用一些字元序列替換輸入的字元序列。這些操作主要分為以下三步:
Created with Rapha?l 2.1.0輸入源檔案執行文本翻譯將輸入的源檔案拆分成多個記號將輸入代碼轉換為前置處理器語言
1.文本翻譯:預先處理會將輸入的源檔案拆分成程式碼、使用單個字元替換三字母組合、將被斷開的連續行合并為較長的程式碼和使用單個空格替換注釋。三字母組合是指C語言中用來代表單個字元的三字元序列。
2.記號轉換:前置處理器將上一步驟處理過的代碼轉換為記號序列。
3.基於前置處理器語言的轉換:如果記號序列中含有預先處理語言元素,就根據這些記號進行轉換。
前兩個操作是自動執行的,而最後一個操作是由添加到源檔案中的前置處理器語言函數執行的。
前置處理器語言
前置處理器語言是一門完全獨立的程式設計語言。預先處理語言對源檔案進行的轉換主要包括源檔案的內容,條件編譯和宏展開。前置處理器語言元素會在程式編譯前處理源檔案,但前置處理器不能識別Objective-C代碼。
前置處理器指令
前置處理器指令格式:
#指令名 指令參數
前置處理器指令以#開頭,後面緊跟指令名,之後是相應的參數:
#import "Atom.h"
前置處理器指令會將分行符號號用作結束符號。要使前置處理器指令擴充為多行,可使用反斜線\串連兩行代碼:
#define DegreesToRadians(x) \ ((x)*3,14159/180)
前置處理器指令主要包括了以下4類:標頭檔包含,條件編譯,診斷,#pragma指令
標頭檔包含類指令有#include和#import
#include <Foundation/Foundation.h>#include "Atom.h"#import <Foundation/Foundation.h>#import "Atom.h"
雙引號和角括弧的區別在於,使用雙引號時,編譯器會先從儲存源檔案的目錄中搜尋被包含的標頭檔。如果沒有找到,編譯器會在預設目錄中搜尋標頭檔,預設目錄是預先配置的用於搜尋系統標準標頭檔的目錄;而使用角括弧時,編譯器會直接在預設目錄中搜尋被包含的標頭檔。按慣例,應使用角括弧封裝標準標頭檔,而其他檔案用雙引號封裝。
而#import與#include的區別在於,#import可確保標頭檔僅在源檔案中被包含一次,因而能夠防止遞迴包含。例如之前章節([精通Objective-C]對象和訊息傳遞)中,源檔案main.m包含了標頭檔Hydrogen.h和Atom+Nuclear.h,這兩個檔案又都包含了標頭檔Atom.h,通過#import指令包含標頭檔Hydrogen.h和Atom+Nuclear.h就可以讓main.m只包含標頭檔Atom.h一次,如果使用#include的話,就需要在標頭檔Atom.h中添加包含警衛:
#ifndef ATOM_H#define ATOM_H@interface Atom : NSObject......@end#endif
條件編譯指令有#if、#elif、#else、#endif、#ifdef和#ifndef可以根據條件是否成立,確定包含或不包含部分或者全部源文本。
條件編譯指令#if、#elif和#else的用法與Objective-C中的if、else if和else類似,只是結束整個條件陳述式時要加上#endif。同樣地#if和#endif也可以嵌套使用。
//前置處理器會展開INPUT_ARGS標識符,如果該標識符是一條宏命令,它就會被相應的值替換,如果不是或者這個宏沒有值,則被替換為0#if INPUT_ARGS <= 0#warning "No input arguments defined"#elif INPUT_ARGS > 100#error "Input arguments are too many"#else#define Sum INPUT_ARGS#endif
之前的包含警衛就是使用的#ifdef和#ifndef
#ifndef ATOM_H#define ATOM_H@interface Atom : NSObject......@end#endif
等價於
#if !defined ATOM_H#define ATOM_H@interface Atom : NSObject......@end#endif
診斷類前置處理器指令有#warning、#error和#line。
以下是#warning和#error的用法:
#warning "No input arguments defined"#error "Input arguments are too many"
而#line的文法為
#line 行號 "檔案名稱"
該行號將被賦予下一行代碼,而後續的程式碼都會擁有每行加1的行號,當編譯出錯時,編譯器會顯示含有出錯檔案名稱和相應行號的錯誤信心。由於大多數編譯工具都能夠顯示源檔案的行號,以及錯誤和警告資訊,因此很少需要用到#line。
添加#pragma指令可以在快顯視窗設定標籤
//在快顯視窗中設定建立分割線#pragma mark - //在快顯視窗中設定標籤名稱#pragma mark Mainint main(int argc, const char * argv[]) { @autoreleasepool { } return 0;}#pragma mark -
效果如下所示,可以在標註位置通過選擇標籤跳轉到指定位置,這在大型工程和類中尤為有用:
宏
宏是具有名稱的程式碼片段。當在原始碼中使用某個名稱時,其代表的程式碼片段就會替換它。只用前置處理器巨集指令可以定義常量值,還可以配合輸入參數值提供類似函數的功能。使用#define定義宏,#undef移除宏。
宏只能執行簡單的替換,所以在使用宏時要一定要小心。以下面的一個計算平方的宏為例
#define SQUARE(x) x * xint result = SQUARE(4 + 2);
得到的結果不是36,而是是14,因為int result = SQUARE(4 + 2);等價於int result = 4 + 2 * 4 + 2;。
如果要想得到我們想要的結果應該寫成:
#define SQUARE(x) ((x) * (x))
實際上,函數型宏和對象型宏的定義中都應該使用括弧,另外,應使用花括弧封裝用於執行而非傳回值的多行函數型宏:
#define SWAP(a,b) {a^=b; b^=a; a^=b;}
宏具有強大的功能,但是非常依賴複雜宏的代碼會難以維護,因此,不要過度使用宏!
[精通Objective-C]前置處理器