C前處理器和C庫--1
目錄
編譯程式之前,先由前處理器檢查程式(因此稱為前處理器)。根據程式中使用的前處理器指令,預處理用符號縮略語所代表的內容替換程式中的縮略語。
前處理器可以根據你的請求包含其他檔案,還可以讓編譯器處理哪些程式碼。前處理器不能理解C,它一般是接受一些文字並將其轉換成其他文字。-- (C Primer Plus中文第五版)
graph TD;
寫好的C檔案 --> 編譯器翻譯,為預處理做準備
編譯器翻譯,為預處理做準備 --> 前處理器尋找肯能存在的預處理指令,開始預處理
預處理符號
明顯常量 #define
#define
定義的作用域從定義出現的位置開始直到檔案的結尾。每個#define
行由三部分組成:
- 指令自身
#define
- 縮略語,即巨集(macro)
巨集的名字中不允許有空格,必須遵循C變數名規則 - 替換列表或者主體(body)
前處理器在程式中發現了巨集的例項後,總會用實體代替該巨集。從巨集變成最終的替換文字的過程稱為巨集展開(macro expansion)。
巨集可以分兩種:
- 類物件巨集 object-like macro
巨集用來代表值 - 類函式巨集 function-like macro
外形和作用都與函式相似
#define TWO 2; #define PX printf("X is: %d\n",x) #define FMT "X+1 is: %d\n" int main() { int x =TWO; PX; printf(FMT,x+1); }
輸出:
X is: 2
X+1 is: 3
上面的程式其實被前處理器改成了:
int x = 2;
printf("X is: %d\n",x);
printf("X+1 is: %d\n",x+1);
//先變成int y = TWO * TWO;
int y = 2*2;
printf("y is: %d\n",y);
注意,巨集展開過程中,是進行替換,並不進行計算。C編譯器在編譯時對所有常量表達式(只包含常量的表示式)求值,所以實際相乘過程發生在編譯階段,而不是預處理階段。前處理器不進行計算,它只是按照指令進行文字替換操作。
巨集展開過程中,會用巨集的等價(即body
)來替換文字,如果巨集的body
重定義常量
假設一個縮略語被定義後又在同文件中被定義,這樣被稱為重定義(redefinng a constant)。有的編譯器會對這樣提出警告,但允許重定義存在,有的則直接報錯。
#define SIX 3 * 3
#define SIX 3 * 3
//上面這樣的重定義會被編譯器認為是重複定義,是相同的
#define SIX 3*3
//這樣的重定義與上面兩種是不同的
在#define
中使用引數
類函式巨集的定義中,用圓括號闊氣一個或多個引數,隨後這些引數出現在替換部分。
#define SQUARE(X) X*X
//一個引數X
//使用
int y = SQUARE(2);
巨集呼叫和函式呼叫存在著區別:
程式執行時,函式呼叫把引數的值傳遞給函式,而編譯前,巨集呼叫把引數的語言符號傳遞給程式,僅僅是替換字元,而不計算。
#define SQUARE(X) X*X
int main() {
int x = 2;
int y = SQUARE(x);
printf("SQUARE(x) is: %d\n",y);
printf("SQUARE(x+2) is: %d\n",SQUARE(x+2));
}
輸出:
SQUARE(x) is: 4
SQUARE(x+2) is: 8
按理說square(2+2)
應該是16啊,怎麼會是8呢?原來像剛才上面說的,預處理只是替換,因此SQUARE(x+2)
中的X
被x+2
替換,最後成了x+2*x+2
。*
優先順序高,,因此程式執行時先計算2*x
,再加上x
和2
,也就成了8
。要想實現平方的效果,需要重新定義:
#define SQUARE(X) ((X)*(X))
即使這樣定義,還是無法避免自增、自減情況下的錯誤:
int x = 3;
int a = SQUARE(++x);
這裡替換成++x
字元後,進行了兩次增量運算,最後結果肯定不是平方了。因此,在巨集中不要使用增量或減量運算子。而且一定要充分的使用圓括號來保證正確的運算順序。
在類函式巨集中使用#
運算子
上面說了在引號表示的字串無法替換掉巨集引數,,但是使用#
預處理運算子,可以把傳入的參量轉化為文字替換到字串裡。
#define SQUARE(X) (X)*(X)
#define PF(X) printf("The square of " #X " is : %d\n",SQUARE(X))
int main() {
int x = 10;
PF(x);
PF(2+4);
}
輸出:
The square of x is : 100
The square of 2+4 is : 36
在巨集中使用##
運算子
##
運算子把兩個語言符號組合成單個語言符號:
#define XVAR(X) x ## X
int XVAR(2) = 11;//聲明瞭一個識別符號為x2的變數
可變巨集:...
和__VA_ARGS__
巨集定義中引數列表的最後一個引數為省略號,預定義巨集__VA_ARGS_
就可以被用在替換部分,以代表省略號省略了什麼。
#define PF(X,...) printf("Result " #X " : " __VA_ARGS__)
int main() {
PF(1,"%d\n",10);
PF(2,"%d's power is %d\n",4,16);
}
輸出:
Result 1 : 10
Result 2 : 4's power is 16
省略號只能代替最後的巨集引數。
#define WRONG(X, ... ,Y) #X #__VA_ARGS__ #Y)//錯誤
這裡有個有趣的現象:一般想要列印字串,字串都得用雙引號括起來,這裡不用:
#define STRING(... ) #__VA_ARGS__
int main() {
printf(STRING(abcdefg));
}
輸出:
abcdefg