預處理指令簡介
1.爲了區分預處理指令和一般的C語句,所有預處理指令都以符號"#"開頭,並且結尾不用分號
2.C語言提供的預處理指令主要有:宏定義、文件包含、條件編譯
一、不帶參數的宏定義
1.一般形式
#define 宏名 字符串
比如#define ABC 10
右邊的字符串也可以省略,比如#define ABC
2.作用
它的作用是在編譯預處理時,將源程序中所有"宏名"替換成右邊的"字符串",常用來定義常量。
接下來寫個程序根據圓的半徑計算周長
1 #include <stdio.h> 2 3 // 源程序中所有的宏名PI在編譯預處理的時候都會被3.14所代替 4 #define PI 3.14 5 6 // 根據圓的半徑計radius算周長 7 float girth(float radius) { 8 return 2 * PI *radius; 9 } 10 11 int main () 12 { 13 float g = girth(2); 14 15 printf("周長爲:%f", g); 16 return 0; 17 }
在第4行定義了一個叫PI的宏,在編譯預處理之後,第8行中的2 * PI *radius就會變成2 * 3.14 * radius。
輸出結果:
3.使用習慣與注意
1> 宏名一般用大寫字母,以便與變量名區別開來,但用小寫也沒有語法錯誤
2> 對程序中用雙引號擴起來的字符串內的字符,不進行宏的替換操作。比如:
1 #define R 10 2 int main () 3 { 4 char *s = "Radio"; 5 return 0; 6 }
在第1行定義了一個叫R的宏,但是第4行中"Radio"裏面的'R'並不會被替換成10
3> 在編譯預處理用字符串替換宏名時,不作語法檢查,只是簡單的字符串替換。只有在編譯的時候纔對已經展開宏名的源程序進行語法檢查
1 #define I 100 2 int main () 3 { 4 int i[3] = I; 5 return 0; 6 }
在做編譯預處理的時候,不管語法對不對,第4行的I都會被替換爲100。不過在編譯的時候就會報第4行的錯。
4> 宏名的有效範圍是從定義位置到文件結束。如果需要終止宏定義的作用域,可以用#undef命令
1 #define PI 3.14 2 /* 3 . 4 . 5 . 6 . 7 */ 8 #undef PI
PI這個宏在第1行到第8行之間是有效的,第8行後就無效了
5> 定義一個宏時可以引用已經定義的宏名
#define R 3.0 #define PI 3.14 #define L 2*PI*R #define S PI*R*R
二、帶參數的宏定義
1.一般形式
#define 宏名(參數列表) 字符串
2.作用
在編譯預處理時,將源程序中所有宏名替換成字符串,並且將 字符串中的參數 用 宏名右邊參數列表 中的參數替換
1 #include <stdio.h> 2 3 #define average(a, b) (a+b)/2 4 5 int main () 6 { 7 int a = average(10, 4); 8 9 printf("平均值:%d", a); 10 return 0; 11 }
第3行中定義了一個帶有2個參數的宏average,第7行其實會被替換成:int a = (10 + 4)/2;,輸出結果爲:。是不是感覺這個宏有點像函數呢?
3.使用注意
1> 宏名和參數列表之間不能有空格,否則空格後面的所有字符串都作爲替換的字符串
1 #define average (a, b) (a+b)/2 2 3 int main () 4 { 5 int a = average(10, 4); 6 return 0; 7 }
注意第1行的宏定義,宏名average跟(a, b)之間是有空格的,於是,第5行就變成了這樣:
int a = (a, b) (a+b)/2(10, 4);
這個肯定是編譯不通過的
2> 帶參數的宏在展開時,只作簡單的字符和參數的替換,不進行任何計算操作。所以在定義宏時,一般用一個小括號括住字符串的參數。
下面定義一個宏D(a),作用是返回a的2倍數值:
如果定義宏的時候不用小括號括住參數
1 #include <stdio.h> 2 3 #define D(a) 2*a 4 5 int main () 6 { 7 int b = D(3+4); 8 9 printf("%d", b); 10 return 0; 11 }
第7行將被替換成int b = 2*3+4;,輸出結果:
如果定義宏的時候用小括號括住參數,把上面的第3行改成:
#define D(a) 2*(a)
注意右邊的a是有括號的,第7行將被替換成int b = 2*(3+4);,輸出結果:
3> 計算結果最好也用括號括起來
下面定義一個宏P(a),作用是返回a的平方:
如果不用小括號括住計算結果
1 #include <stdio.h> 2 3 #define Pow(a) (a) * (a) 4 5 int main(int argc, const char * argv[]) { 6 int b = Pow(10) / Pow(2); 7 8 printf("%d", b); 9 return 0; 10 }
注意第3行,沒有用小括號擴住計算結果,只是括住了參數而已。第6行代碼被替換爲:
int b = (10) * (10) / (2) * (2);
簡化之後:int b = 10 * (10 / 2) * 2;,最後變量b爲:
如果用小括號括住計算結果
將上面的第3行代碼改爲:
#define Pow(a) ( (a) * (a) )
那麼第6行被替換爲:
int b = ( (10) * (10) ) / ( (2) * (2) );
簡化之後:int b = (10 * 10) / (2 * 2);,最後輸出結果:。這個纔是我們想要的結果。
也就意味着前面的#define average(a, b) (a+b)/2應該寫成#define average(a, b) (((a)+(b))/2)
5.與函數的區別
從整個使用過程可以發現,帶參數的宏定義,在源程序中出現的形式與函數很像。但是兩者是有本質區別的:
1> 宏定義不涉及存儲空間的分配、參數類型匹配、參數傳遞、返回值問題
2> 函數調用在程序運行時執行,而宏替換隻在編譯預處理階段進行。所以帶參數的宏比函數具有更高的執行效率
條件編譯的概念
在很多情況下,我們希望程序的其中一部分代碼只有在滿足一定條件時才進行編譯,否則不參與編譯(只有參與編譯的代碼最終才能被執行),這就是條件編譯。
#if defined()和#if !defined()的用法
#if 和 #elif後面的條件不僅僅可以用來判斷宏的值,還可以判斷是否定義過某個宏。比如:
1 #if defined(MAX) 2 ...code... 3 #endif
如果前面已經定義過MAX這個宏,就將code編譯進去。它不會管MAX的值是多少,只要定義過MAX,條件就成立。
條件也可以取反:
1 #if !defined(MAX) 2 ...code... 3 #endif
如果前面沒有定義過MAX這個宏,就將code編譯進去。
2.#ifdef和#ifndef的使用
* #ifdef的使用和#if defined()的用法基本一致
* #ifndef又和#if !defined()的用法基本一致
這講介紹最後一個預處理指令---文件包含
一、基本概念
其實我們早就有接觸文件包含這個指令了, 就是#include,它可以將一個文件的全部內容拷貝另一個文件中。
二、一般形式
1.第1種形式#include <文件名>
直接到C語言庫函數頭文件所在的目錄中尋找文件
2.第2種形式 #include "文件名"
系統會先在源程序當前目錄下尋找,若找不到,再到操作系統的path路徑中查找,最後纔到C語言庫函數頭文件所在目錄中查找
三、使用注意(很少會用,瞭解吧)
1.#include指令允許嵌套包含,比如a.h包含b.h,b.h包含c.h,但是不允許遞歸包含,比如 a.h 包含 b.h,b.h 包含 a.h。
下面的做法是錯誤的
2.使用#include指令可能導致多次包含同一個頭文件,降低編譯效率
比如下面的情況:
在one.h中聲明瞭一個one函數;在two.h中包含了one.h,順便聲明瞭一個two函數。(這裏就不寫函數的實現了,也就是函數的定義)
假如我想在main.c中使用one和two兩個函數,而且有時候我們並不一定知道two.h中包含了one.h,所以可能會這樣做:
編譯預處理之後main.c的代碼是這樣的:
1 void one(); 2 void one(); 3 void two(); 4 int main () 5 { 6 7 return 0; 8 }
第1行是由#include "one.h"導致的,第2、3行是由#include "two.h"導致的(因爲two.h裏面包含了one.h)。可以看出來,one函數被聲明瞭2遍,根本就沒有必要,這樣會降低編譯效率。
爲了解決這種重複包含同一個頭文件的問題,一般我們會這樣寫頭文件內容:
大致解釋一下意思,就拿one.h爲例:當我們第一次#include "one.h"時,因爲沒有定義_ONE_H_,所以第9行的條件成立,接着在第10行定義了_ONE_H_這個宏,然後在13行聲明one函數,最後在15行結束條件編譯。當第二次#include "one.h",因爲之前已經定義過_ONE_H_這個宏,所以第9行的條件不成立,直接跳到第15行的#endif,結束條件編譯。就是這麼簡單的3句代碼,防止了one.h的內容被重複包含。
這樣子的話,main.c中的:
#include "one.h" #include "two.h"
就變成了:
1 // #include "one.h" 2 #ifndef _ONE_H_ 3 #define _ONE_H_ 4 5 void one(); 6 7 #endif 8 9 // #include "two.h" 10 #ifndef _TWO_H_ 11 #define _TWO_H_ 12 13 // #include "one.h" 14 #ifndef _ONE_H_ 15 #define _ONE_H_ 16 17 void one(); 18 19 #endif 20 21 void two(); 22 23 #endif
第2~第7行是#include "one.h"導致的,第10~第23行是#include "two.h"導致的。編譯預處理之後就變爲了:
1 void one(); 2 void two();
這纔是我們想要的結果