C Primer Plus--C預處理器和C庫(1)

編譯程序之前,先由預處理器檢查程序(因此稱爲預處理器)。根據程序中使用的預處理器指令,預處理用符號縮略語所代表的內容替換程序中的縮略語。
預處理器可以根據你的請求包含其他文件,還可以讓編譯器處理哪些代碼。預處理器不能理解C,它一般是接受一些文本並將其轉換成其他文本。-- (C Primer Plus中文第五版)

```mermaid graph TD; 寫好的C文件 --> 編譯器翻譯,爲預處理做準備 編譯器翻譯,爲預處理做準備 --> 預處理器尋找肯能存在的預處理指令,開始預處理 ```

預處理符號

明顯常量 #define

#define定義的作用域從定義出現的位置開始直到文件的結尾。這裏表明在函數裏使用#define,凡是在它定義的後面的所有函數都是可以用的:

#include <stdio.h>
void pp();
int main() {
    #define FF 10
    pp();
}

void pp(){
    printf("FF: %d ",FF);
}

輸出:

FF: 10

要時刻記住:#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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章