C語(yǔ)言的那些小秘密之預處理
預處理是C語(yǔ)言的一個(gè)重要知識點(diǎn),它能改善程序設計的環(huán)境,有助于編寫(xiě)易移植、易調試的程序。因此,我們有必要掌握好預處理命令,在自己編程的時(shí)候靈活的使用它,使得編寫(xiě)的程序結構優(yōu)良,更加易于調試和閱讀。接下來(lái)我盡可能的把預處理中重要知識點(diǎn)向讀者講解清楚,使讀者能夠在自己以后編程的過(guò)程中熟練的使用預處理命令。
本文引用地址:http://dyxdggzs.com/article/275697.htmC語(yǔ)言的預處理主要有三個(gè)方面:
1、文件的包含
2、宏定義
3、條件編譯
一、文件包含的形式有下面兩種
1、#include "文件名"
2、#include <文件名>
它們之間的區別在于:<文件名>系統到頭文件目錄查找文件, "文件名"則先在當前目錄查找,如果沒(méi)有才到頭文件目錄查找;當然我們也可以使用在命令行來(lái)指定頭文件路徑方法。還要注意就是如果在源文件包含的頭文件之間出現調用的情況,那么被調用的頭文件要出現在調用頭文件的前面。
二、宏定義
宏定義的使用有兩種形式,一種不帶參數,而另外一種帶參數。
1、不帶參數
格式: #define 標識符 字符串
相信上面這個(gè)格式大家并不陌生,下面還是來(lái)看看如何使用吧。當然在講解之前我們的看看使用過(guò)程中的如下幾個(gè)注意要點(diǎn):
(1)預處理不做語(yǔ)法檢查,所以我們選用的時(shí)候要尤其小心
(2)宏定義寫(xiě)在函數的花括號外邊,作用域為其后的程序,通常在文件開(kāi)頭部分,直到用#undef命令終止宏定義的作用域
(3)不要在字符串中使用宏,如果宏名出現在字符串中那么將按照字符串進(jìn)行處理
下面來(lái)看段代碼的使用。
[html] view plaincopy#include
#define N 9
int main ()
{
int i,a[N];
for(i=0;i
{
a[i]=i;
printf("%dt",a[i]);
if((i+1)%3==0)
printf("n");
}
//#undef N
printf("%dn",N);
}
運行結果為:
[html] view plaincopy0 1 2
3 4 5
6 7 8
9
Press any key to continue
我們在此主要是介紹下宏的作用域問(wèn)題,當在以上代碼中注釋掉#undef N時(shí),接下來(lái)的打印語(yǔ)句能夠正常的打印出;但是當我們沒(méi)有注釋掉#undef N的時(shí)候就會(huì )出現error C2065: 'N' : undeclared identifier錯誤,提示N沒(méi)有定義。接下來(lái)看看帶參數的宏的使用。
2、帶參數
#define 宏名(參數表) 字符串
注意要點(diǎn):
(1)宏名和參數的括號間不能有空格
(2)宏替換只作替換,不做計算,不做表達式求解,這點(diǎn)要尤其注意
(3)函數調用在編譯后程序運行時(shí)進(jìn)行,并且分配內存。宏替換在編譯前進(jìn)行,不分配內存
(4)宏的啞實(shí)結合(所謂的啞實(shí)結合類(lèi)似于函數調用過(guò)程中實(shí)參替代形參的過(guò)程)不存在類(lèi)型,也沒(méi)有類(lèi)型轉換。
(5)宏展開(kāi)使源程序變長(cháng),函數調用不會(huì )
下面來(lái)看看linux下一個(gè)典型的應用:
#define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x < _y ? _x : _y; })
#define max(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x > _y ? _x : _y; })
在上面的兩個(gè)宏中我們發(fā)現有這么一句代碼(void) (&_x == &_y);可能不少讀者有點(diǎn)發(fā)懵的感覺(jué),這啥意思呢?!其實(shí)我們細細分析就知道,首先看看“==”,這是一個(gè)邏輯表達式,它要求兩邊的比較類(lèi)型必須一致,如果我們的&x和&y類(lèi)型不一致,如一個(gè)為char*,另一個(gè)為int*,不是同一個(gè)類(lèi)型,當我們使用gcc編譯的時(shí)候就會(huì )出現警告信息,vc6則會(huì )報錯error C2446: '==' : no conversion from 'char *' to 'int *'。這句代碼(void) (&_x == &_y); 在此的功能就相當于執行一個(gè)簡(jiǎn)單的判斷操作,我們用來(lái)判斷x和y的類(lèi)型是否一致。別小看了這句代碼,如果學(xué)會(huì )了使用它會(huì )給你的代碼帶來(lái)不少的便捷。下面給出一個(gè)小小的事例:
[cpp] view plaincopy#include
void print()
{
printf("hello world!!!n");
return ;
}
void main(int argc,char*argv)
{
print();
return ;
}
運行結果為:

[cpp] view plaincopyhello world!!!
Press any key to continue
現在我們來(lái)修改下代碼后看看運行結果:
[cpp] view plaincopy#include
void print()
{
printf("hello world!!!n");
return ;
}
void main(int argc,char*argv)
{
#define print() ((void)(3))
print();
return ;
}
運行結果為:
[cpp] view plaincopyPress any key to continue
這兒的結果沒(méi)有了我們之前的那句hello world!!!,可以看出這個(gè)時(shí)候函數并沒(méi)有被調用,這是因為我們使用了#define print() ((void)(3)),使得之后調用函數print()轉換為了一個(gè)空操作,所以這個(gè)函數在接下來(lái)的代碼中都不會(huì )被調用了,就像被“沖刷掉”了一樣??吹竭@兒你是不是想起我們之前的那篇《C語(yǔ)言的那些小秘密之斷言》了呢,我們同樣可以使用這種方法來(lái)實(shí)現斷言的關(guān)閉,方法與之類(lèi)似,在此就不再講解了,有興趣的讀者可以自己試試。講到這兒似乎應該結束了,但是細心的讀者會(huì )有另外一個(gè)疑惑?在#define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x < _y ? _x : _y; })中,我們?yōu)槭裁匆褂孟駎ypeof(y) _y = (y)這樣的轉換呢?而不直接使用typeof(x)==typeof(y)或者(void) (&x == &y); x < y ? x : y; 呢?如果我們使用typeof(x)==typeof(y)就好比使用了char==int一樣,這是不允許的。我們使用一個(gè)typeof(y) _y = (y)這樣的轉換,這是為了防止x和y為一個(gè)表達式的情況,如x=i++之類(lèi)的,如果不轉換的話(huà)i++就多執行了幾次操作,得到的不是我們想要的結果,但是如果我們使用了typeof(y) _y = (y)這樣的轉換,那就不會(huì )出現這樣的問(wèn)題了。下面我們來(lái)看看如何使用宏定義實(shí)現變參,先看看實(shí)現方法。
#define print(...) printf(__VA_ARGS__)
看看上面的宏,其中“...”指可變參數。實(shí)現的可變參數的實(shí)現方式就是使用“...”所代表的內容替代__VA_ARGS__,看看下面的代碼就知道了。
[cpp] view plaincopy#include
#define print(...) printf(__VA_ARGS__)
int main(int argc,char*argv)
{
print("hello world----%dn",1111);
return 0;
}
運行結果為:
[cpp] view plaincopyroot@ubuntu:/home/shiyan# ./arg
hello world----1111
接著(zhù)往下看。
#define printf (tem, ...) fprintf (stdout, tem, ## __VA_ARGS__)
如有對fprintf不熟悉的讀者可以自己查查函數手冊,在此不再講解。
[cpp] view plaincopy#include
#define print(temp, ...) fprintf(stdout, temp, ##__VA_ARGS__)
int main(int argc,char*argv)
{
print("hello world----%dn",1111);
return 0;
}
運行結果為:
[cpp] view plaincopyroot@ubuntu:/home/shiyan# ./arg
hello world----1111
temp在此的作用為設定輸出字符串的格式,后邊“...”為可變參數?,F在問(wèn)題來(lái)了,我們在宏定義中為什么要使用“##”呢?如果我們沒(méi)有使用##會(huì )怎么樣呢?看看下面的代碼:
[cpp] view plaincopy#include
#define print(temp, ...) fprintf(stdout, temp, __VA_ARGS__)
int main(int argc,char*argv)
{
print("hello worldn");
return 0;
}
編譯時(shí)發(fā)生了如下錯誤:
[cpp] view plaincopyroot@ubuntu:/home/shiyan# gcc arg.c -o arg
arg.c: In function ‘main’:
arg.c:7:2: error: expected expression before ‘)’ token
為什么會(huì )出現上面的錯誤呢,現在我們來(lái)分析下,我們進(jìn)行下宏替換,print("hello worldn")就變?yōu)榱薴printf(stdout, "hello worldn",)這樣我們就發(fā)現了后面出現了一個(gè)逗號,所以導致了錯誤,如果有“##”就不會(huì )出現這樣的錯誤了,這是因為如果可變參數被忽略或為空的時(shí)候,“##”操作將使預處理器去除掉它前面的那個(gè)逗號。如果存在可變參數時(shí)候,它也能正常工作。講了“##”,我們當然也要講講“#”。先來(lái)看看下面一段代碼:
[cpp] view plaincopy#include
#define return_exam(p) if(!(p))
{printf("error: "#p" file_name:%stfunction_name:%stline:%d .n",
__FILE__, __func__, __LINE__); return 0;}
int print()
{
return_exam(0);
}
int main(int argc,char*argv)
{
print();
printf("hello world!!!n");
return 0;
}
運行結果為:
[cpp] view plaincopyroot@ubuntu:/home/shiyan# ./arg
error: 0 file_name:arg.c function_name:print line:9 .
hello world!!!
我們發(fā)現在運行結果中打印出了出錯的文件名、函數名、以及行號。采用宏定義來(lái)檢測函數的返回值是否正確,僅僅是為了體現出我們要講解的宏,所以代碼做了最大的簡(jiǎn)化工作,讀者在自己編寫(xiě)代碼時(shí)候要學(xué)會(huì )這樣的檢測方式。“#”的作用就是將其后面的宏參數進(jìn)行字符串化操作,就是在宏變量進(jìn)行替換之后在其左右各加上一個(gè)上雙引號,這就使得"#p"變味了""p""我們發(fā)現這樣的話(huà)剛好兩邊的“""”就消失了。下面來(lái)看看最后一個(gè)知識點(diǎn)條件編譯。
三、條件編譯
條件編譯命令#if、#else、#elif、#endif、#ifdef、#ifndef,條件編譯指令的意思很簡(jiǎn)單,跟我們學(xué)習的if語(yǔ)句類(lèi)似。
一般格式
#if 常量表達式
程序段1;
[#else
程序段2;]
#endif
功能:當表達式為非0(“邏輯真”)時(shí),編譯程序段1,否則編譯程序段2。
一般格式
#ifdef 標識符
程序段1;
[#else
程序段2;]
#endif
功能:當“標識符”已經(jīng)被#define命令定義過(guò),則編譯程序段1,否則編譯程序段2。
#ifndef 標識符
程序段1;
[#else
程序段2;]
#endif
功能:當“標識符”未被#define命令定義過(guò),則編譯程序段1,否則編譯程序段2。
學(xué)習了條件編譯指令之后,我們在調試代碼的時(shí)候,就不要再隨心所欲的刪減代碼了,如果我們不想某段代碼被編譯就可以使用條件編譯指令來(lái)將其注釋掉。如:
#if (0)
注釋代碼段;
#endif
就可以實(shí)現代碼的注釋了,需要的時(shí)候也可以將其啟用,而不會(huì )為需要重新編輯代碼時(shí),發(fā)現已被刪除而頭疼了。
其中值得注意的地方為,常量表達式在編譯時(shí)求值,所以表達式只能是常量或者已經(jīng)定義過(guò)的標識符,不能為變量,也不可以為那些在編譯時(shí)候求值的操作符,如sizeof。
下面來(lái)看段代碼:
[cpp] view plaincopy#include
#define N 1
int main(int argc,char*argv)
{
int a=3;
#if(a)
printf("#if后面的表達式為變量n");
#endif
#if(N)
printf("#if后面的表達式已定義,且不為0---successn");
#else
printf("#if后面的表達式已定義,且不為0---failn");
#endif
return 0;
}
運行結果為:

[cpp] view plaincopy#if后面的表達式已定義,且不為0---success
Press any key to continue
看看上面的代碼我們的表達式為變量a時(shí)并沒(méi)有打印出來(lái),所以我們不能在其后的表示中使用變量。如果我們使用sizeof操作符會(huì )怎么樣呢?為了加深印象看看下面的代碼后結果吧。
[cpp] view plaincopy#include
int main(int argc,char*argv)
{
int a=9;
#if(sizeof(a))
printf("#if后面的表達式含有sizeof操作符n");
#endif
return 0;
}
編譯出現了如下錯誤:
[cpp] view plaincopyfatal error C1017: invalid integer constant expression
所以我們在使用條件編譯的時(shí)候要牢記這兩點(diǎn),常量表達式不能為變量和含有sizeof等在編譯時(shí)求值的操作符。
接下來(lái)看看這里要講的最后一個(gè)#pragma指令。
一般格式為:
#pragma 參數
下面給出幾種經(jīng)常使用的形式
1、#pragma message("消息")
看看下面一段代碼。
[cpp] view plaincopy#include
#define FDSA
int main(int argc,char*argv)
{
#ifdef FDSA
#pragma message("FDSA 已經(jīng)定義過(guò)了")
#endif
return 0;
}
編譯的時(shí)候我們可以在編譯輸出窗口中看到了輸出“FDSA 已經(jīng)定義過(guò)了”,通過(guò)這種方式我們可以在一些我們想要的地方輸出很多我們需要的信息。
2、#pragma once
如果我們在頭文件的開(kāi)頭部分加入這條指令,那么就能保證我們的頭文件僅僅被編譯一次。
3、#pragma hdrstop
該指令表示編譯頭文件到此為止,后面的無(wú)需在進(jìn)行編譯了。
4、#pragma pack()
設定字節的對齊長(cháng)度,這個(gè)指令我們在《C語(yǔ)言的那些小秘密之字節對齊》中已經(jīng)講解了,在此不再復述。
5、#pragma warning(disable:M N;once:H;error:K)
表示不顯示M和N號的警告信息,H號警告信息只報告一次,把K號警告信息作為一個(gè)錯誤來(lái)處理。
到此關(guān)于預處理的講解就結束了。由于本人水平有限,博客中的不妥或錯誤之處在所難免,殷切希望讀者批評指正。同時(shí)也歡迎讀者共同探討相關(guān)的內容,如果樂(lè )意交流的話(huà)請留下你寶貴的意見(jiàn)。
c語(yǔ)言相關(guān)文章:c語(yǔ)言教程
評論