關于#ifndef / #define / #endif使用詳解

想必很多人都看過“頭文件中的 #ifndef/#define/#endif 防止該頭文件被重復引用”。但是是否能理解“被重復引用”是什么意思?是不能在不同的兩個文件中使用include來包含這個頭文件嗎?如果頭文件被重復引用了,會產生什么后果?是不是所有的頭文件中都要加入#ifndef/#define/#endif 這些代碼?

其實“被重復引用”是指一個頭文件在同一個cpp文件中被include了多次,這種錯誤常常是由于include嵌套造成的。比如:存在a.h文件#include "c.h"而此時b.cpp文件導入了#include "a.h" 和#include "c.h"此時就會造成c.h重復引用。

頭文件被重復引用引起的后果:

有些頭文件重復引用只是增加了編譯工作的工作量,不會引起太大的問題,僅僅是編譯效率低一些,但是對于大工程而言編譯效率低下那將是一件多么痛苦的事情。

有些頭文件重復包含,會引起錯誤,比如在頭文件中定義了全局變量(雖然這種方式不被推薦,但確實是C規范允許的)這種會引起重復定義。

是不是所有的頭文件中都要加入#ifndef/#define/#endif 這些代碼?

答案:不是一定要加,但是不管怎樣,用ifnde xxx #define xxx#endif或者其他方式避免頭文件重復包含,只有好處沒有壞處。個人覺得培養一個好的編程習慣是學習編程的一個重要分支。

下面給一個#ifndef/#define/#endif的格式:

ifndef A_H意思是"if not define a.h" 如果不存在a.h

接著的語句應該#define A_H 就引入a.h

最后一句應該寫#endif 否則不需要引入

例如:


ifndef GRAPHICS_H // 防止graphics.h被重復引用

define GRAPHICS_H


而關于#define 宏定義也有很多種, 首先介紹下宏的一些基本東西

①程序第一步是在預編譯之前會有一些操作, 例如刪除反斜線和換行符的組合, 將每個注釋用一個空格替代...,

②然后在進入預編譯的時候, 會尋找可能存在的預處理指定(由#開頭), 例如C中常用的#include, 或者oc中的#import, #define...很多(條件編譯語句...)

③處理#define的時候,然后預處理器會從#開始, 一直到執行到第一個換行符(寫代碼的時候換行的作用), 自然, #define只會允許定義一行的宏, 不過正因為上面提到的預處理之前會刪除反斜線和換行符的組合, 所以可以利用反斜線定義多行宏, 在刪除反斜線和換行符的組合后, 邏輯上就成了一行的宏了

④宏作用在預編譯時期, 其真正的效果就是代碼替換, 而且是直接替換(內聯函數!!!), 這個和函數有著很大的區別, 并且正因為是直接替換, 在使用的時候就會有一些的注意點了, 這個在后面會給出例子

⑤宏可以被稱為 類對象宏, 類函數宏

⑥定義宏的語法很簡單, 一個宏定義由三部分組成 , 三分部之間用空格分開, #define, 宏的名字, 主體 例如: 宏#define PI(宏的名字) 3.14(主體), 這里有個注意點就是, 宏的命名和普通的變量命名規則相同

⑦宏在預處理階段只進行文本的替換(相當于把代碼拷貝粘貼), 不會進行具體的計算(發生在編譯時期)

定義宏
下面我們介紹一下,我們基本使用到的宏定義:

①#define PI 3.14 這是宏的最簡單的定義了, 可能也是大家應用最廣的, 就是使用宏來定義一些常量(消除魔法數字)或字符串..., 這一類可以被稱為類對象宏, 方便代碼閱讀和修改, 使用的時候直接使用定義的宏的名字, PI, 那么預處理器就會將代碼中的PI替換為3.14

float computeAreaWithRadius(float r) {

return PI * r * r;

}

②#define log(x) printf("this is test: x = %d", x) 這是宏的第二類定義, 即類函數宏, 這一類的宏和函數類似的寫法, ( )中可以寫變量, 用作函數的參數, 不過, 這個和函數的區別是, 宏的參數不指定類型, 具體的參數類型在調用宏的時候由傳入的參數決定(有點其他語言里的泛型的意思), 這個可以算是和函數相比的優點, 下面測試一些這個宏的使用, 結果你猜對了么?

#define log(x) printf("this is test: x = %d", x)

int main(int argc, const char * argv[]) {

int y = 12;

log(y); // 輸出為  this is test: x = 12

}

③#define log(x) printf("this is test: "#x" = %d", x), 這個定義中和上面的區別是使用了一個#運算符, #運算符被用于利用宏參數創建字符串, 區分一下和上面的結果

#define log(x) printf("this is test: "#x" = %d", x)

int main(int argc, const char * argv[]) {

int y = 12;

log(y);

// 輸出為  this is test: y = 12 (而不是 x = 12, 或者 12 = 12)

// 因為使用#和參數結合可以被替換為宏參數對應的字符串, "#x"表示字符串x, 這里輸入的參數為y, 則替換為y(不是12)

log(2+4)// 輸出為 this is test: 2+4 = 6

}

④#define power(x) xx 這個和上面一樣是一個類函數宏, 這里我原本的意愿是計算 xx即x的平方的值, 不過這樣的定義宏在有些情況下是會出問題的, 這個例子就是告訴大家定義類函數宏的時候就真的要小心, 不然結果并不是我們預期的

#define power(x) x*x

int x = 2;

int pow1 = power(x); // pow1 = 2*2 = 4

int pow2 = power(x+1); //  pow2 = 3 * 3 = 9  ??

// 顯然對于pow1 = 4是沒有問題的

// 不過對于pow2 = 9 這個結果是有問題的, 定義的宏并沒有達到我們預想的效果 結果為 3*3

// 因為: 上面提到過宏是直接的代碼替換, 這里宏展開后就成為了 x+1*x+1 = 2+1*2+1 = 5

// 這里因為運算優先級的原因導致結果的不一樣, 所以pow應該(加上括號)定義為

#define power(x) (x)*(x)

⑤#define RGBA(r, g, b, a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a] 這里是個簡單的多參數的類函數宏的定義, 這個宏在使用OC開發的時候 大家可能都會喜歡使用

⑥這個宏是一個"多行宏"定義的示例, 即在除了最后一行的最后加上反斜線(因為反斜線和換行符的組合在預編譯之前會被系統刪除), 同時這個宏也說明了, 宏的定義是可以嵌套的(有些編譯器可能不支持, xcode中是支持的...)

#define RGB(r, g, b)  {\

RGBA(r, g, b, 1.0f);\

} 

⑦#define print(...) printf(VA_ARGS) 這個宏使用了兩個新的東西...和VA_ARGS, 這兩個是用來定義可變參數宏的, 可以看到是很簡單的, 唯一一個注意點就是, ...要放在參數的最后, 如果你使用C定義可變參數的函數就會發現過程就很復雜了

#define print(...) printf(__VA_ARGS__)

int main(int argc, const char * argv[]) {

print("測試可變參數 ---- %d", 12); // 輸出結果為: 測試可變參數 ---- 12

}

⑧#define weakify( x ) autoreleasepool{} __weak typeof(x) weak##x = x; 最后一個宏介紹另外一個運算符 ## 這個是宏定義中的連接運算符, 例如上面的weak##x 就是將weak和參數x連接在一起, 同時這一個宏在iOS開發中是很有用的, 使用block的時候為了消除循環引用 通常使用weakSelf, 那么就可以定義這樣一個宏, 而不用每次都輸入上面一段重復的代碼 __weak typeof(self) weakself = self, 那么上面定義的宏和這段代碼一樣會生成一個弱引用的新變量, 不過上面定義的時候使用了autoreleasepool{}, 這一個自動釋放池本質上并沒有什么用, 只不過對調用weakify會有影響, 需要使用@weakify(x), ??看上去逼格更高, 不過在RAC中weakify是另外的方式定義的, (開篇給出的第九個宏定義)這個就可以自己下去研究一下了.

#define weakify( x ) autoreleasepool{} __weak typeof(x) weak##x = x;

加上 autoreleasepool{}使用宏的時候就應該加上@

像這樣:

- (void)delay {

@weakify(self)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

[weakself test];

});

}

當然如果你沒有加autoreleasepool{}, 使用宏就不用加上@了

#define weakify( x ) __weak typeof(x) weak##x = x;

像這樣:

- (void)delay {

weakify(self)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

[weakself test];

});
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容