C 語言中的預處理

C 語言中以 # 開頭的就是預處理指令,例如 #includeweb

預處理指令的用途

全部的預處理指令都會在 GCC 編譯過程的預處理步驟解析執行,替換爲對應的內容。在下一步編譯過程當中,看不到任何預處理信息,只須要對獨立的程序文件進行操做便可。svg

預處理指令的類型

  • 包含類 #include 頭文件
    經過 #include 引入的頭文件,會在 GCC 編譯過程當中的預處理步驟進行展開,替換爲完整的頭文件內容。
  • 宏定義類 #define 宏名 宏體
    定義的宏在預處理時不進行語法檢查,原樣替換。等到編譯的時候再進行語法檢查。宏體若是有多個組成部分時,一般要加括號,防止發生優先級的問題。
    • 宏變量:支持表達式。例如 #define MY_VAR 3+4,使用的時候若是是這樣 int i = MY_VAR * 5,會在預處理時替換爲 int i = 3+4 * 5,最終獲得的 i 是23。而 #define MY_VAR (3+4)則無次問題
    • 宏函數:能夠將定義的宏當作函數處理。例如 #define MY_FUN(x) (3+x),使用 int i = MY_FUN(4)*5 獲得的 i 是 35。
  • 條件編譯 #ifdef #ifndef #else #endif
  • 預約義宏:所有以兩個下劃線開始和結束,方便調試
    • FILE:文件名
    • FUNCTION:函數名
    • LINE:行號,數值
    • DATE:編譯的日期
    • TIME:編譯的時刻

例如,對於這個 C 文件:函數

#include <stdio.h>

int main()
{
    printf("file is:%s, function is:%s, line is:%d, %s, %s", __FILE__, __FUNCTION__, __LINE__, __DATE__, __TIME__);
}

執行結果以下:ui

file is:define.c, function is:main, line is:5, Jan 19 2019, 15:10:57

宏定義

預處理時,會把全部出現宏名的地方自動替換爲對應的宏值。spa

#define 宏名 宏值

宏定義的規則:debug

  • 宏值中能夠有其餘宏的名字,預處理時也會被替換,例如:
#define PI 3.14
#define PI2 PI * 2
  • 宏值超過一行時,能夠用 \ 換行,例如:
#define PRT printf("%f ", PI); \ printf("%f", PI2)
  • 若是不須要宏值,能夠定義無值的宏:
#define DEBUG
  • 預約義宏能夠在任何地方直接使用。
  • 宏定義結尾不能加分號 ;,不然會被替換致使異常。

綜合示例:調試

#include <stdio.h>

#define PI 3.14
#define PI2 PI*2
#define PRT printf("%f ", PI); \ printf("%f\n", PI2)
#define MIN(a, b) ((a) < (b) ? (a) : (b))

int main()
{
	PRT;
	printf("%d", MIN(2+3*5, 666));
}

有參數的宏

能夠帶一個或多個參數。有參數的宏的原則是:code

一切地方都有加括號,放在由於運算的優先級致使異常xml

  • 整個宏值要加括號
  • 每一個參數都有加括號
#include <stdio.h>

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define FUN1(a) (a * 5)
#define FUN2(a) (a) * 5

int main()
{
	printf("%d\n", MIN(2+3*5, 666));
	printf("%d\n", FUN1(5 + 6));		//5 + 6 * 5 = 35
	printf("%d\n", 100 / FUN2(5));	//100 / 5 * 5 = 100
}

宏定義的優缺點

  • 優勢:有參數的宏定義比函數速度快,無參數的宏定義能夠在C語言的各個版本中使用。
  • 缺點:沒法進行參數類型的檢查,可能編譯經過但運行時報錯。

可使用 inline 內聯函數替代有參數的宏定義,速度快的同時還能夠檢查參數類型。token

對於 C99 以後的版本,可使用常量定義來代替無參數的宏定義:

const double PI 3.14;

條件預處理

經過條件預處理指令,能夠在預處理階段控制要編譯的代碼,從而實現條件預編譯。對編譯器來講這個步驟是透明的,它只須要編譯全部它看見的代碼便可。例如:

#include <stdio.h>
#define DEBUG

int main()
{
#ifdef DEBUG
	printf("debug info");// 只有定義了 DEBUG,纔會執行這段代碼
#endif
	printf("hello world\n");
	return 0;
}

固然,在文件中直接定義宏的話,每次都有修改源文件,不靈活。GCC 提供了一個指定宏定義的選項 gcc -D,例如 gcc -DDEBUG -o build 1.c 會自動在代碼中添加一段定義 DEBUG 的指令 #define DEBUG,這樣就能夠在上面的源代碼中刪除 #define DEBUG 這一行了。

預處理中 # 和 ## 的區別

在宏體中(宏名中不可使用)可使用一個或兩個 # 做爲前綴

  • #:字符串化,若是須要向宏方法中傳字符串參數時可使用
  • ##:鏈接符號,用於鏈接字符串和變量

示例:

#include <stdio.h>
#define MY1(x) #x //傳入的參數轉爲字符串
#define MY2(x) day##x //傳入的參數自動拼接到 day 後面,例如出入1時爲 day1

int main()
{
	int day1 = 666;
	
	printf(MY1(abc\n)); // 打印 abc 字符串後換行
	printf("%d", MY2(1));// 打印 666,即變量 day1
}
相關文章
相關標籤/搜索