C 語言的源代碼到生成可運作的程式(.exe)檔案,需要經過四個階段,分别是:預處理、編譯、彙編以及連結。其中預處理階段會将源代碼檔案中所有的預處理指令、注釋資訊等内容進行處理。
什麼是預處理指令?
即以 # 開頭,加上相應的關鍵字,組合成特殊的指令。
例如最常用的檔案包括指令 #include。将外部的檔案引入到源代碼中,然後我們就可以在源代碼中使用外部檔案中的函數、全局變量等等。
下面聊一聊另一個用的比較多的預處理指令,宏定義指令。
1. 什麼是宏定義?
宏定義又稱為宏代換、宏替換,簡稱為宏。是 C 語言中最為常用的三種預處理指令之一。
宏定義指令,是由 # 和關鍵字 define 組合而成的。
它的作用是文本替換,使用辨別符來代替替換清單中的内容。使用宏定義可以提高程式的通用性、易讀性,減少因為輸入錯誤産生的各種問題,同時還便于修改。
例如:
#define PI 3.1415926
int r = PI * 3 * 3
此處定義了一個宏,PI。我在源代碼中任意地方想要用到 3.141526 這個數值,就可以使用 PI 這個宏來替換。
當我需要圓周率後面更多位的數值,我隻需要在宏定義的地方進行數值修改即可,就不需要一個個去找,去改。
常見的宏定義分為兩種:無參宏定義和帶參宏定義。
2. 無參宏定義
定義格式:
#define 辨別符 替換清單
替換清單裡面可以是數值常數、字元常數、字元串常數等,是以可以了解為使用辨別符來表示常量,是以也被叫作符号常量。
預處理指令不是語句,是以在預處理指令的末尾是無需加上分号的。如果加上分号,可能會獲得超出預期的結果。
#define N 5;
int arr[N];
雖然預處理階段的宏定義沒問題,但在編譯階段是會産生錯誤的。
因為宏定義隻是文本替換,是以預處理的時候,會将所有的 N 替換成 5;,放到源代碼中 arr[5;] 這種寫法不符合 C 語言文法,是以就會産生報錯。
無參宏定義還可以替換表達式,但需要注意的是,表達式需要加上括号,否則就會出現邏輯上的錯誤。
例如:
#define N 3+1
int a = N * N; // 3+1*3+1=7, 不符合預期結果
// 正确方式
#define N (3+1)
int a = N * N ; // (3+1)*(3+1)=16
3. 帶參宏定義
定義格式:
#define 辨別符(參數清單...) 替換清單
此參數清單和函數中定義的參數清單很相似,有多個參數的時候,需要用逗号隔開。不一樣的是,宏定義中的參數是不需要聲明參數的資料類型。
例如:定義一個求兩個值哪個大的帶參宏定義。
#def MAX(a,b) ((a)>(b)?(a):(b))
int r = MAX(3, 2); // r = 2
替換清單中的每個參數以及整個替換清單都需要用括号括起來,否則就可能會出現歧義。例如:
#define MUL(a,b) (a*b)
int r = MUL(3, 3+2);
這裡我們希望的結果是 r = 15,但是實際結果卻是 3*3+2=11。這是因為宏定義隻是文本替換,将替換清單中的内容原封不動地取代宏名。
4. 删除宏定義指令
有時候先定義了宏,後面我又重新定義了和宏名一樣的變量,會發生什麼情況呢?
#define N 5
int N = 3;
這裡編譯器會給變量 N 标出紅色波浪線,給出錯誤提示:應輸入辨別符。
因為宏定義了 N 為 5,那麼在編譯器中有出現的 N 的地方都會被替換成數字 5。是以這裡就變成 5=3,很顯然 5 不符合辨別符的規則,就會報錯。
那麼該怎麼解決這個問題呢?
C 語言中也提供一種預處理指令,#undef。
它的其中一個功能就是删除前面定義過的宏,解放辨別符。
#define N 5
#undef N
int N = 3;
由此可以知道,宏定義的作用域是從定義開始,直到遇到 #undef 指令,或者程式運作完畢時才結束。而在作用域中,就不能再使用宏定義中的辨別符。
5. 宏定義和函數的差別
學到目前,已經知道了 C 語言當中帶參數的有兩個,分别是宏定義和函數。
那麼它們之間有什麼不同之處呢?
5.1 參數類型
前面有提到一點,函數如果有參數,則必須為每一個參數指明資料類型是什麼。而帶參的宏定義是不需要指明參數的資料類型,是以在使用的時候可以填入任何類型的資料。
這是宏定義的優點,同樣也是缺點。沒有指明資料類型,就存在類型安全隐患。是以在設計宏定義的時候,程式員必須確定宏調用參數的類型正确。
5.2 運作時間
宏定義是屬于預處理指令,是以發生在預處理階段,也就是在源代碼編譯之前。
而函數是發生在程式運作期間的。
5.3 記憶體空間
宏定義隻是簡單的文本替換,把替換清單中的内容取代辨別符的位置,替換完就會删除所有對應的辨別符。是以,宏定義的參數是不需要配置設定空間的。
函數每次調用的時候,系統都會給形式參數重新配置設定一塊記憶體空間用來存放。
5.4 執行速度
宏定義是文本替換,是不需要進行任何文法和邏輯檢查,是以速度會快一些。
函數在運作階段,參數需要經曆入棧記憶體,然後再出棧記憶體的操作,是以相對的速度就會慢一些。
5.5 代碼長度
如果宏定義中替換清單裡面的内容是非常的長,然而預處理結束後,替換了辨別符後,代碼長度就會相應的變長。
函數就不會影響代碼的長度。
一般建議,使用頻繁且代碼量小的功能可以使用宏定義。例如,比較兩個數值的大小,或是計算某個數值的平方、三次方等等。
最後
以上就是關于 C 語言預處理指令宏定義的内容,希望對大家有所幫助。
有發現文中存在錯謬之處,或者不了解的地方,歡迎在評論區留言,一起交流學習一下。
如果您覺得本篇文章還不錯,點個贊支援一下呗!