C語言預處理詳解

一,預定義符號

__FILE__      //進行編譯的源文件
__LINE__     //文件當前的行號
__DATE__    //文件被編譯的日期
__TIME__    //文件被編譯的時間
__STDC__    //如果編譯器遵循ANSI C,其值為1,否則未定義
 int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	//printf("%d\n", __STDC__);//因為VS不支持ANSI C,其實__STDC__未定義
	//gcc 是支持的
	//gcc對C語言語法的支持非常好
 	return 0;
}

這些預定義符號都是語言內置的,例題如下

 int main()
 {
	 printf("file:%s line:%d\n", __FILE__, __LINE__);
	 return 0;
 }

二,#define

1,#define 定義標識符

#define name stuff
#define MAX 100
#define reg register    //為 register這個關鍵字,創建一個簡短的名字
#define STR "HEHE"
int main()
{
	int a = 3;
	int b = 20;
	if (a == 4)
		b = MAX;
	else
		b = -a;
	register int num = 100;
	reg int num2 = 200;
 	int m = MAX;
	printf("%d\n", MAX);
	printf("%d\n", m);
	printf("%s\n", STR);
 	return 0;
}

在define定義標識符的時候,要不要在最後加上 ; ?

#define MAX 1000;
int main()
{
	int x = 1;
	int max;
	if (x==1)
	{
		max = MAX;//這裡替換為 MAX 1000;;語法錯誤
	}
	else
	{
		max = 0;
	}
	printf("%d", max);
	return 0;
}

2,#define 定義宏

#define 機制包括瞭一個規定,允許把參數替換到文本中,這種實現通常稱為宏(macro)或定義宏(define macro)。

下面是宏的申明方式:

#define name( parament-list ) stuff 其中的 parament-list 是一個由逗號隔開的符號表,它們可能出現在 stuff中。

註意: 參數列表的左括號必須與name緊鄰。 如果兩者之間有任何空白存在,參數列表就會被解釋為stuff的一部 分。

#define SQUARE( x ) x * x

這個宏接收一個參數 x=5.

SQUARE( 5 );

程序會如何表達,如下

5 * 5
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );

觀察上面的代碼,你會認為結果是多少?

替換文本時,參數x被替換成a + 1,所以這條語句實際上變成瞭: printf (“%d\n”,a + 1 * a + 1 );

在宏定義上加上兩個括號,這個問題便輕松的解決瞭:

#define SQUARE(x) (x) * (x)

這樣預處理之後就產生瞭預期的效果:

printf ("%d\n",(a + 1) * (a + 1) );
#define SQUARE(X) ((X)*(X))
 int main()
{
	int a = 5;
	int ret = SQUARE(a+5);
	//int ret = a + 5 * a + 5;
	//int ret = ((a) * (a));
	printf("%d\n", ret);
 	return 0;
}

這裡還有一個宏定義:

#define DOUBLE(x) (x) + (x)

定義中我們使用瞭括號,想避免之前的問題,但是這個宏可能會出現新的錯誤。

int a = 5; 
printf("%d\n" ,10 * DOUBLE(a)); 

好像打印100,但事實上打印的是55. 我們發現替換之後:

printf ("%d\n",10 * (5) + (5));

這個問題,的解決辦法是在宏定義表達式兩邊加上一對括號就可以瞭。

#define DOUBLE(x) ( ( x ) + ( x ) )
#define DOUBLE(X) ((X)+(X))
 int main()
{
	int ret = 10 * DOUBLE(2);
	//int ret = 10 * 2 + 2;
 	printf("%d\n", ret);
 	return 0;
}

提示:所以用於對數值表達式進行求值的宏定義都應該用這種方式加上括號,避免在使用宏時由於參數中的操作符或 鄰近操作符之間不可預料的相互作用。

3,#define 替換規則

在調用宏時,首先對參數進行檢查,看看是否包含任何由#define定義的符號。如果是,它們首先被替換。

替換文本隨後被插入到程序中原來文本的位置。對於宏,參數名被他們的值替換。

最後,再次對結果文件進行掃描,看看它是否包含任何由#define定義的符號。如果是,就重復上述處理過程。

註意:

宏參數和#define 定義中可以出現其他#define定義的變量。但是對於宏,不能出現遞歸。

當預處理器搜索#define定義的符號的時候,字符串常量的內容並不被搜索。

#define PRINT(n) printf("the value of "#n" is %d\n", n)
 int main()
{
	int a = 10;
	PRINT(a);
	int b = 20;
	PRINT(b);
	return 0;
}

用#define連接字符串

#define PRINT(FORMAT, VALUE)
printf("the value is "FORMAT"\n", VALUE);
PRINT("%d", 10);
#define PRINT(n) printf("the value of "#n" is %d\n", n)
 int main()
{
	int a = 10;
	printf("the value of a is %d\n", a);
	int b = 20;
	printf("the value of b is %d\n", b);
	printf("hello world\n");
	printf("hello ""world\n");
	return 0;
}

三,##的作用

1,概念

##可以把位於它兩邊的符號合成一個符號。 它允許宏定義從分離的文本片段創建標識符。

#define ADD_TO_SUM(num, value) 
sum##num += value; 
 ADD_TO_SUM(5, 10);//作用是:給sum5增加10.
#define CAT(X,Y) X##Y
 int main()
{
	int class103 = 100;
	printf("%d\n", CAT(class, 103));
	printf("%d\n", CAT(1, 2));
	return 0;
}

2,帶副作用的宏參數

當宏參數在宏的定義中出現超過一次的時候,如果參數帶有副作用,那麼你在使用這個宏的時候就可能出現危險,導 致不可預測的後果。副作用就是表達式求值的時候出現的永久性效果。 例如:

x+1;//不帶副作用
x++;//帶有副作用

MAX宏可以證明具有副作用的參數所引起的問題。

int main()
{
	int a = 10;
	int b = a + 1;//b得到的是11,a不變
	int b = ++a;//b得到的是11,但是a變瞭,這個表達式是有副作用的
	return 0;
}
int Max(int x, int y)
{
	return x > y ? x : y;
}
 int main()
{
	int a = 5;
	int b = 8;
 	//int m = MAX(a++, b++);
	//int m = ((a++) > (b++) ? (a++) : (b++));
	//函數的參數是計算後再傳進去的
	int m = Max(a++, b++);
 	printf("m=%d\n", m);//8
	printf("a=%d\n", a);//6
	printf("b=%d\n", b);//9
  	return 0;
}

//宏的實現 - 1
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int Max(int x, int y)
{
	return x > y ? x : y;
}
 int main()
{
	int a = 5;
	int b = 8;
	//宏的參數是不計算直接替換進去的
	//替換進去進去後參與運算
 	int m = Max(a++, b++);
 	printf("m=%d\n", m);//8
	printf("a=%d\n", a);//6
	printf("b=%d\n", b);//9
 	return 0;

3,宏和函數對比

宏通常被應用於執行簡單的運算。比如在兩個數中找出較大的一個。

#define MAX(a, b) ((a)>(b)?(a):(b)) 

用於調用函數和從函數返回的代碼可能比實際執行這個小型計算工作所需要的時間更多。所以宏比函數在程序的規模和速度方面更勝一籌。

更為重要的是函數的參數必須聲明為特定的類型。所以函數隻能在類型合適的表達式上使用。反之這個宏怎可 以適用於整形、長整型、浮點型等可以用於>來比較的類型。宏是類型無關的。

宏的劣勢

每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序的長度。

宏是沒法調試的。

宏由於類型無關,也就不夠嚴謹。

宏可能會帶來運算符優先級的問題,導致程容易出現錯。

屬 性 #define定義宏 函數
代 碼 長 度 每次使用時,宏代碼都會被插入到程序中。除瞭非常小的宏 之外,程序的長度會大幅度增長 函數代碼隻出現於一個地方;每次使 用這個函數時,都調用那個地方的同 一份代碼
執 行 速 度 更快 存在函數的調用和返回的額外開銷, 所以相對慢一些
操 作 符 優 先 級 宏參數的求值是在所有周圍表達式的上下文環境裡,除非加 上括號,否則鄰近操作符的優先級可能會產生不可預料的後 果,所以建議宏在書寫的時候多些括號 函數參數隻在函數調用的時候求值一 次,它的結果值傳遞給函數。表達式 的求值結果更容易預測。
帶 有 副 作 用 的 參 數 參數可能被替換到宏體中的多個位置,所以帶有副作用的參 數求值可能會產生不可預料的結果。 函數參數隻在傳參的時候求值一次, 結果更容易控制。
參 數 類 型 宏的參數與類型無關,隻要對參數的操作是合法的,它就可 以使用於任何參數類型。 函數的參數是與類型有關的,如果參 數的類型不同,就需要不同的函數, 即使他們執行的任務是不同的
調 試 宏是不方便調試的 函數是可以逐語句調試的
遞 歸 宏是不能遞歸的 函數是可以遞歸的

四,命名約定

一般來講函數的宏的使用語法很相似。所以語言本身沒法幫我們區分二者。 那我們平時的一個習慣是:

把宏名全部大寫 函數名不要全部大寫

1,#undef

這條指令用於移除一個宏定義。

#undef NAME 
//如果現存的一個名字需要被重新定義,那麼它的舊名字首先要被移除。

命令行定義

許多C 的編譯器提供瞭一種能力,允許在命令行中定義符號。用於啟動編譯過程。 例如:當我們根據同一個源文件要 編譯出不同的一個程序的不同版本的時候,這個特性有點用處。(假定某個程序中聲明瞭一個某個長度的數組,如果 機器內存有限,我們需要一個很小的數組,但是另外一個機器內存大寫,我們需要一個數組能夠大寫。)

#define	MAX 100
 int main()
{
	int m = MAX;
    #undef MAX
	int n = MAX;//err
	return 0;
}
#define M 500
 int main()
{
#if M==100
	printf("haha\n");
#elif M==200
	printf("hehe\n");
#else
	printf("heihei\n");
#endif
 	return 0;
}

2,文件包含

我們已經知道, #include 指令可以使另外一個文件被編譯。就像它實際出現於 #include 指令的地方一樣。 這種替換的方式很簡單: 預處理器先刪除這條指令,並用包含文件的內容替換。 這樣一個源文件被包含10次,那就 實際被編譯10次。

頭文件被包含的方式:

#include "filename" 

VS環境的標準頭文件的路徑:

C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include

庫文件包含

#include <filename.h>

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: