1. 程式人生 > >C前處理器和C庫--1

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)中的Xx+2替換,最後成了x+2*x+2*優先順序高,,因此程式執行時先計算2*x,再加上x2,也就成了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