一直以來用宏定義#define也就是定義一些簡單的常量,至多也就是定義一個函數,很少關注宏定義的用法。直到看到這樣的代碼:
#define PLAYSOUNDEFFECT(...) \[[GameManager sharedGameManager] playSoundEffect:@#__VA_ARGS__]
這麼強大的用法以前從來沒有想過。看一下iOS Framework的一些標頭檔,發現幾乎全部都是宏定義:
不得不說宏定義很強大!宏定義的使用使得程式的編寫更加的簡便!
作為iOS開發人員,有必要深入研究一下宏定義的用法。
最官方的關於宏的使用說明網址是:http://gcc.gnu.org/onlinedocs/cpp/Macros.html#Macros
在Apple的官網上可以找到GNU C 4.2 Preprocessor User Guide,發現和GNU官網的說明一模一樣。因為Xcode的編譯器就是基於GNU C 4.2前置處理器,因此在Objective-C的開發環境中使用宏和在C/C++中使用是一模一樣的。
下面的文字是閱讀官方使用說明後的總結及翻譯。(代碼直接從官方使用說明摘錄)
1、Macros 宏
官方解釋:
A macro is a fragment of code which has been given a name. Whenever the name is used, it is replaced by the contents
of the macro. There are two kinds of macros. They differ mostly in what they look like when they are used. Object-like macros resemble data objects when used, function-like macros
resemble function calls.
有兩種宏的類型,一種是類對象的宏,封裝使用的資料對象,另一種是類函數的宏,封裝函數的調用。在ObjC裡面,那就是可以封裝Method的使用,如文章一開始的代碼
1.1 類對象的宏
最基本的使用:
#define BUFFER_SIZE 1024foo = (char *) malloc (BUFFER_SIZE);foo = (char *) malloc (1024);
就是最基本的替換。
通常宏的名稱都是用大寫字母。
------------------------------------------------------------------------------------------------
#define NUMBERS 1, \ 2, \ 3 int x[] = { NUMBERS }; ==> int x[] = { 1, 2, 3 };
在宏定義中,如果要換行,使用“\"符號。然後經預先處理後還是在同一行。
------------------------------------------------------------------------------------------------
C前置處理器是按順序讀取程式,因此宏定義生效在宏定義之後。
foo = X; #define X 4 bar = X;produces foo = X; bar = 4;
------------------------------------------------------------------------------------------------
宏調用時,前置處理器在替換宏的內容時,會繼續檢測內容本身是否也是宏定義,如果是,會繼續替換內容。
#define TABLESIZE BUFSIZE #define BUFSIZE 1024 TABLESIZE ==> BUFSIZE ==> 1024
------------------------------------------------------------------------------------------------
宏定義以最後生效的定義為準,因此下面的代碼TABLESIZE對應37
#define BUFSIZE 1020 #define TABLESIZE BUFSIZE #undef BUFSIZE #define BUFSIZE 37
------------------------------------------------------------------------------------------------
如果宏定義內容包含了名稱,則前置處理器會終止展開防止無限嵌套(infinite resursion)
1.2 類函數宏
#define lang_init() c_init() lang_init() ==> c_init()
類函數宏的名稱後面加了"()"。
#define lang_init () c_init() lang_init() ==> () c_init()()
並且"()"必須緊隨在名稱後面否則就會認為是類對象宏。
1.3 宏參數
在類函數宏裡面可以添加參數使得更像真正的函數
#define min(X, Y) ((X) < (Y) ? (X) : (Y)) x = min(a, b); ==> x = ((a) < (b) ? (a) : (b)); y = min(1, 2); ==> y = ((1) < (2) ? (1) : (2)); z = min(a + 28, *p); ==> z = ((a + 28) < (*p) ? (a + 28) : (*p));
基本的使用和函數的定義類似,當然宏裡面都是實際參數,用逗號隔開。預先處理時,先是將宏展開,然後將參數放進宏的主體中,再檢查一遍完整的內容。
------------------------------------------------------------------------------------------------
如何宏裡面有字串的內容,即使與參數名相同,也不會被替換。如下:
#define foo(x) x, "x" foo(bar) ==> bar, "x"
1.4 字串化
使用”#“預先處理操作符來實現將宏中的參數轉化為字串。例子如下:
#define WARN_IF(EXP) \ do { if (EXP) \ fprintf (stderr, "Warning: " #EXP "\n"); } \ while (0) WARN_IF (x == 0); ==> do { if (x == 0) fprintf (stderr, "Warning: " "x == 0" "\n"); } while (0);
這個字串化會將參數中的所有字元都實現字串化,包括引號。如果參數中間有很多空格,字串化之後將會只用一個空格代替。
然後沒有什麼方法可以直接將參數轉化成單一的字元char
------------------------------------------------------------------------------------------------
#define xstr(s) str(s) #define str(s) #s #define foo 4 str (foo) ==> "foo" xstr (foo) ==> xstr (4) ==> str (4) ==> "4"
出現上面的結果是因為在使用str(s)時,s是字串化,所以宏沒有擴充開。而使用xstr(s)時s作為一個參數,因此先把宏完全擴充然後再放進參數。
1.5 串連
使用"##"操作符可以實現宏中token的串連。
struct command { char *name; void (*function) (void); }; struct command commands[] = { { "quit", quit_command }, { "help", help_command }, ... }; #define COMMAND(NAME) { #NAME, NAME ## _command } struct command commands[] = { COMMAND (quit), COMMAND (help), ... };
如上,使參數NAME對應的字元與_command串連起來,而不進行其他轉化。當然要注意串連後的字元必須是有意義的,否則只會出現錯誤或警告。
然後C前置處理器會將注釋轉化成空格,因此在宏中間,參數中間加入注釋都是可以的。但不能將"##"放在宏的最後,否則會出現錯誤。
1.6 多參數宏(Variadic Macros)
#define eprintf(...) fprintf (stderr, __VA_ARGS__) eprintf ("%s:%d: ", input_file, lineno) ==> fprintf (stderr, "%s:%d: ", input_file, lineno)
使用標識符__VA_ARGS_來表示多個參數,在宏的名稱中則使用(...)
在C++中也可以使用如下的方式:
#define eprintf(args...) fprintf (stderr, args)
結果是一樣的。
------------------------------------------------------------------------------------------------
"##"的特殊用法:
#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__) eprintf ("success!\n") ==> fprintf(stderr, "success!\n");
將"##"放在","和參數之間,那麼如果參數留空的話,那麼"##"前面的","就會刪掉,從而防止編譯錯誤。
1.7 取消或重新宏定義
這個看下面的代碼就明白:
#define FOO 4 x = FOO; ==> x = 4; #undef FOO x = FOO; ==> x = FOO;These definitions are effectively the same: #define FOUR (2 + 2) #define FOUR (2 + 2) #define FOUR (2 /* two */ + 2)but these are not: #define FOUR (2 + 2) #define FOUR ( 2+2 ) #define FOUR (2 * 2) #define FOUR(score,and,seven,years,ago) (2 + 2)
對於重定義,如果定義的宏不一樣,那麼編譯器會給出警告並使用最新定義的宏。
通過上面的總結描述,現在就可以輕鬆看到本文開始的宏定義的含義了。