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;
}

這些預定義符號都是語言內(nèi)置的,例題如下
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這個關鍵字,創(chuàng)建一個簡短的名字
#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 機制包括了一個規(guī)定,允許把參數(shù)替換到文本中,這種實現(xiàn)通常稱為宏(macro)或定義宏(define macro)。
下面是宏的申明方式:
#define name( parament-list ) stuff 其中的 parament-list 是一個由逗號隔開的符號表,它們可能出現(xiàn)在 stuff中。
注意: 參數(shù)列表的左括號必須與name緊鄰。 如果兩者之間有任何空白存在,參數(shù)列表就會被解釋為stuff的一部 分。
#define SQUARE( x ) x * x
這個宏接收一個參數(shù) x=5.
SQUARE( 5 );
程序會如何表達,如下
5 * 5
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
觀察上面的代碼,你會認為結(jié)果是多少?
替換文本時,參數(shù)x被替換成a + 1,所以這條語句實際上變成了: printf ("%d\n",a + 1 * a + 1 );
在宏定義上加上兩個括號,這個問題便輕松的解決了:
#define SQUARE(x) (x) * (x)
這樣預處理之后就產(chǎn)生了預期的效果:
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)
定義中我們使用了括號,想避免之前的問題,但是這個宏可能會出現(xiàn)新的錯誤。
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));
好像打印100,但事實上打印的是55. 我們發(fā)現(xiàn)替換之后:
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;
}

提示:所以用于對數(shù)值表達式進行求值的宏定義都應該用這種方式加上括號,避免在使用宏時由于參數(shù)中的操作符或 鄰近操作符之間不可預料的相互作用。
3,#define 替換規(guī)則
在調(diào)用宏時,首先對參數(shù)進行檢查,看看是否包含任何由#define定義的符號。如果是,它們首先被替換。
替換文本隨后被插入到程序中原來文本的位置。對于宏,參數(shù)名被他們的值替換。
最后,再次對結(jié)果文件進行掃描,看看它是否包含任何由#define定義的符號。如果是,就重復上述處理過程。
注意:
宏參數(shù)和#define 定義中可以出現(xiàn)其他#define定義的變量。但是對于宏,不能出現(xiàn)遞歸。
當預處理器搜索#define定義的符號的時候,字符串常量的內(nèi)容并不被搜索。
#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,概念
##可以把位于它兩邊的符號合成一個符號。 它允許宏定義從分離的文本片段創(chuàng)建標識符。
#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,帶副作用的宏參數(shù)
當宏參數(shù)在宏的定義中出現(xiàn)超過一次的時候,如果參數(shù)帶有副作用,那么你在使用這個宏的時候就可能出現(xiàn)危險,導 致不可預測的后果。副作用就是表達式求值的時候出現(xiàn)的永久性效果。 例如:
x+1;//不帶副作用 x++;//帶有副作用
MAX宏可以證明具有副作用的參數(shù)所引起的問題。
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++));
//函數(shù)的參數(shù)是計算后再傳進去的
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;
}

//宏的實現(xiàn) - 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;
//宏的參數(shù)是不計算直接替換進去的
//替換進去進去后參與運算
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,宏和函數(shù)對比
宏通常被應用于執(zhí)行簡單的運算。比如在兩個數(shù)中找出較大的一個。
#define MAX(a, b) ((a)>(b)?(a):(b))
用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實際執(zhí)行這個小型計算工作所需要的時間更多。所以宏比函數(shù)在程序的規(guī)模和速度方面更勝一籌。
更為重要的是函數(shù)的參數(shù)必須聲明為特定的類型。所以函數(shù)只能在類型合適的表達式上使用。反之這個宏怎可 以適用于整形、長整型、浮點型等可以用于>來比較的類型。宏是類型無關的。
宏的劣勢
每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序的長度。
宏是沒法調(diào)試的。
宏由于類型無關,也就不夠嚴謹。
宏可能會帶來運算符優(yōu)先級的問題,導致程容易出現(xiàn)錯。
| 屬 性 | #define定義宏 | 函數(shù) |
| 代 碼 長 度 | 每次使用時,宏代碼都會被插入到程序中。除了非常小的宏 之外,程序的長度會大幅度增長 | 函數(shù)代碼只出現(xiàn)于一個地方;每次使 用這個函數(shù)時,都調(diào)用那個地方的同 一份代碼 |
| 執(zhí) 行 速 度 | 更快 | 存在函數(shù)的調(diào)用和返回的額外開銷, 所以相對慢一些 |
| 操 作 符 優(yōu) 先 級 | 宏參數(shù)的求值是在所有周圍表達式的上下文環(huán)境里,除非加 上括號,否則鄰近操作符的優(yōu)先級可能會產(chǎn)生不可預料的后 果,所以建議宏在書寫的時候多些括號 | 函數(shù)參數(shù)只在函數(shù)調(diào)用的時候求值一 次,它的結(jié)果值傳遞給函數(shù)。表達式 的求值結(jié)果更容易預測。 |
| 帶 有 副 作 用 的 參 數(shù) | 參數(shù)可能被替換到宏體中的多個位置,所以帶有副作用的參 數(shù)求值可能會產(chǎn)生不可預料的結(jié)果。 | 函數(shù)參數(shù)只在傳參的時候求值一次, 結(jié)果更容易控制。 |
| 參 數(shù) 類 型 | 宏的參數(shù)與類型無關,只要對參數(shù)的操作是合法的,它就可 以使用于任何參數(shù)類型。 | 函數(shù)的參數(shù)是與類型有關的,如果參 數(shù)的類型不同,就需要不同的函數(shù), 即使他們執(zhí)行的任務是不同的 |
| 調(diào) 試 | 宏是不方便調(diào)試的 | 函數(shù)是可以逐語句調(diào)試的 |
| 遞 歸 | 宏是不能遞歸的 | 函數(shù)是可以遞歸的 |
四,命名約定
一般來講函數(shù)的宏的使用語法很相似。所以語言本身沒法幫我們區(qū)分二者。 那我們平時的一個習慣是:
把宏名全部大寫 函數(shù)名不要全部大寫
1,#undef
這條指令用于移除一個宏定義。
#undef NAME //如果現(xiàn)存的一個名字需要被重新定義,那么它的舊名字首先要被移除。
命令行定義
許多C 的編譯器提供了一種能力,允許在命令行中定義符號。用于啟動編譯過程。 例如:當我們根據(jù)同一個源文件要 編譯出不同的一個程序的不同版本的時候,這個特性有點用處。(假定某個程序中聲明了一個某個長度的數(shù)組,如果 機器內(nèi)存有限,我們需要一個很小的數(shù)組,但是另外一個機器內(nèi)存大寫,我們需要一個數(shù)組能夠大寫。)
#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,文件包含
我們已經(jīng)知道, #include 指令可以使另外一個文件被編譯。就像它實際出現(xiàn)于 #include 指令的地方一樣。 這種替換的方式很簡單: 預處理器先刪除這條指令,并用包含文件的內(nèi)容替換。 這樣一個源文件被包含10次,那就 實際被編譯10次。
頭文件被包含的方式:
#include "filename"
VS環(huán)境的標準頭文件的路徑:
C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include
庫文件包含
#include <filename.h>
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注腳本之家的更多內(nèi)容!
相關文章
C++?MiniZip實現(xiàn)目錄壓縮與解壓的示例詳解
Zlib是一個開源的數(shù)據(jù)壓縮庫,提供了一種通用的數(shù)據(jù)壓縮和解壓縮算法,本文主要為大家詳細介紹了如何利用Zlib實現(xiàn)目錄壓縮與解壓,需要的小伙伴可以參考下2023-11-11
C語言修煉之路數(shù)據(jù)類型悟正法 解析存儲定風魔下篇
使用編程語言進行編程時,需要用到各種變量來存儲各種信息。變量保留的是它所存儲的值的內(nèi)存位置。這意味著,當您創(chuàng)建一個變量時,就會在內(nèi)存中保留一些空間。您可能需要存儲各種數(shù)據(jù)類型的信息,操作系統(tǒng)會根據(jù)變量的數(shù)據(jù)類型,來分配內(nèi)存和決定在保留內(nèi)存中存儲什么2022-02-02

