詳解C語言編程中預(yù)處理器的用法
預(yù)處理最大的標(biāo)志便是大寫,雖然這不是標(biāo)準(zhǔn),但請你在使用的時(shí)候大寫,為了自己,也為了后人。
預(yù)處理器在一般看來,用得最多的還是宏,這里總結(jié)一下預(yù)處理器的用法。
- #include <stdio.h>
- #define MACRO_OF_MINE
- #ifdef MACRO_OF_MINE
- #else
- #endif
上述五個(gè)預(yù)處理是最常看見的,第一個(gè)代表著包含一個(gè)頭文件,可以理解為沒有它很多功能都無法使用,例如C語言并沒有把輸入輸入納入標(biāo)準(zhǔn)當(dāng)中,而是使用庫函數(shù)來提供,所以只有包含了stdio.h這個(gè)頭文件,我們才能使用那些輸入輸出函數(shù)。 #define則是使用頻率第二高的預(yù)處理機(jī)制,廣泛用在常量的定義,只不過它和const聲明的常量有所所區(qū)別:
#define MAR_VA 100 const int Con_va = 100; ... /*定義兩個(gè)數(shù)組*/ ... for(int i = 0;i < 10;++i) { mar_arr[i] = MAR_VA; con_arr[i] = Con_va; }
區(qū)別1,定義上MAR_VA可以用于數(shù)組維數(shù),而Con_va則不行
區(qū)別2,在使用時(shí),MAR_VA的原理是在文中找到所有使用本身的地方,用值替代,也就是說Con_va將只有一分真跡,而MAR_VA則會有n份真跡(n為使用的次數(shù)) 剩下三個(gè)則是在保護(hù)頭文件中使用頗多。
幾個(gè)比較實(shí)用的用于調(diào)試的宏,由C語言自帶
- __LINE__和__FILE__ 用于顯示當(dāng)前行號和當(dāng)前文件名
- __DATE__和__TIME__ 用于顯示當(dāng)前的日期和時(shí)間
- __func__(C99) 用于顯示當(dāng)前所在外層函數(shù)的名字
上述所說的五種宏直接當(dāng)成值來使用即可。
__STDC__
如果你想檢驗(yàn)?zāi)悻F(xiàn)在使用的編譯器是否遵循ISO標(biāo)準(zhǔn),用它,如果是他的值為1。
printf("%d\n", __STDC__);
輸出: 1
如果你想進(jìn)一步確定編譯器使用的標(biāo)準(zhǔn)版本是C99還是C89可以使用__STDC__VERSION__,C99(199901)
printf("%d\n", __STDC_VERSION__);
輸出: 199901
對于#define
預(yù)處理器一般只對同一行定義有效,但如果加上反斜杠,也能一直讀取下去
#define err(flag) \ if(flag) \ printf("Correctly")
可以看出來,并沒有在末尾添加;,并不是因?yàn)楹瓴恍枰?,而是因?yàn)?,我們總是將宏近似?dāng)成函數(shù)在使用,而函數(shù)調(diào)用之后總是需要以;結(jié)尾,為了不造成混亂,于是在宏定義中我們默認(rèn)不添加;,而在代碼源文件中使用,防止定義混亂。
預(yù)處理同樣能夠帶來一些便利
#define SWAP1(a, b) (a += b, b = a - b, a -= b) #define SWAP2(x, y) {x ^= y; y ^= x; x ^= y}
引用之前的例子,交換兩數(shù)的宏寫法可以有效避免函數(shù)開銷,由于其是直接在調(diào)用處展開代碼塊,故其比擬直接嵌入的代碼。但,偶爾還是會出現(xiàn)一些不和諧的錯誤,對于初學(xué)者來說:
int v1 = 10; int v2 = 20; SWAP1(v1, v2); SWAP2(v1, v2);//報(bào)錯
對于上述代碼塊的情況,為什么SWAP2報(bào)錯?對于一般的初學(xué)者來說,經(jīng)常忽略諸如, goto do...while等少見關(guān)鍵字用法,故很少見SWAP1的寫法,大多集中于SWAP2的類似錯誤,錯就錯在{}代表的是一個(gè)代碼塊,不需要使用;來進(jìn)行結(jié)尾,這便是宏最容易出錯的地方 宏只是簡單的將代碼展開,而不會做任何處理 對于此,即便是老手也常有失足,有一種應(yīng)用于單片機(jī)等地方的C語言寫法可以在此借鑒用于保護(hù)代碼:
#define SWAP3(x ,y) do{ \ x ^= y; y ^= x; x ^= y; \ }while(0)
如此便能在代碼中安全使用花括號內(nèi)的代碼了,并且如之前所約定的那樣,讓宏的使用看起來像函數(shù)。
但正所謂,假的總是假的,即使宏多么像函數(shù),它依舊不是函數(shù),如果真的把它當(dāng)成函數(shù),你會在某些時(shí)候錯的摸不著頭腦,還是一個(gè)經(jīng)典的例子,比較大小:
#define CMP(x, y) (x > y ? x : y) ... int x = 100, y = 200; int result = CMP(x, y++); printf("x = %d, y = %d, result = %d\n", x, y, result);
執(zhí)行這部分代碼,會輸出什么呢? 答案是,不知道!至少result的值我們無法確定,我們將代碼展開得到
int result = (x > y++ ? x : y++);
看起來似乎就是y遞增兩次,最后result肯定是200。真是如此?C語言標(biāo)準(zhǔn)對于一個(gè)確定的程序語句中,一個(gè)對象只能被修改一次,超過一次那么結(jié)果是未定的,由編譯器決定,除了三目操作符?:外,還有&&, ||或是,之中,或者函數(shù)參數(shù)調(diào)用,switch控制表達(dá)式,for里的控制語句 由此可看出,宏的使用也是有風(fēng)險(xiǎn)的,所以雖然宏強(qiáng)大,但是依舊不能濫用。
對于宏而言,前面說過,它只是進(jìn)行簡單的展開,這有時(shí)候也會帶來一些問題:
#define MULTI(x, y) (x * y) ... int x = 100, y = 200; int result = MULTI(x+y, y);
看出來問題了吧?展開之后會變成: int result = x+y * y; 完全違背了當(dāng)初我們設(shè)計(jì)時(shí)的想法,一個(gè)比較好的修改方法是對每個(gè)參數(shù)加上括號: #define MULTI(x, y) ((x) * (y))如此,展開以后:
int result = ((x+y) * (y));
這樣能在很大程度上解決一部分問題。
如果對自己的宏十分自信,可以嵌套宏,即一個(gè)表達(dá)式中使用宏作為宏的參數(shù),但是宏只展開這一級的宏,對于多級宏另有辦法展開
int result = MULTI(MULTI(x, y), y);
展開成:
int result = ((((x) * (y))) * (y));
對宏的應(yīng)用
由于我們并不明白,在某些情況下宏是否被定義了,(NULL宏是一個(gè)例外,它可以被重復(fù)定義),所以我們可以使用一些預(yù)處理保護(hù)機(jī)制來防止錯誤發(fā)生
- #ifndef MY_MACRO
- #define MY_MACRO 10000
- #endif
如果定義了MY_MACRO那就不執(zhí)行下面的語句,如果沒定義那就執(zhí)行。
在宏的使用中有兩個(gè)有用的操作符,姑且叫它操作符#, ##
對于# 我們可以認(rèn)為#操作符的作用是將宏參數(shù)轉(zhuǎn)化為字符串。
#define HCMP(x, y) printf(#x" is equal to" #y" ? %d\n", (x) == (y)) ... int x = 100, y = 200; HCMP(x, y);
展開以后
printf("x is equal to y ? %d\n", (100) == (200));
注:可以自行添加編譯器選項(xiàng),來查看宏展開之后的代碼,具體可以查詢GCC的展開選項(xiàng),這里不再詳述。特別是在多層宏的嵌套使用情況下,但是我不太推薦,故不做多介紹。
能說的就是如何正確的處理一些嵌套使用,之所以不愿意多說也不愿意多用,是因?yàn)镃預(yù)處理器就是一個(gè)奇葩
舉一個(gè)典型的例子,__LINE__ 和 __FILE__的使用。
/* 下方會說到的 # 預(yù)處理指示器,這里先用,實(shí)在看不懂,可以自己動手嘗試 */ #define WHERE_AM_I #__LINE__ " lines in " __FILE__ ... fputs(WHERE_AM_I, stderr);
這樣能工作嗎?如果能我還講干嘛。
/* 常理上這應(yīng)該能工作,但是編譯器非說這錯那錯的 */ /* 好在有前人踏過了坑,為我們留下了解決方案 */ #define DEPAKEGE(X) #X #define PAKEGE(X) DEPAKEGE(X) #define WHERE_AM_I PAKEGE(__LINE__) " lines in " __FILE__ ... fputs(WHERE_AM_I, stderr);
不要問我為什么,因?yàn)槲乙膊恢繡預(yù)處理器的真正工作機(jī)制是什么。
第一次看見這種解決方案是在 Windows 核心編程 中,這本書現(xiàn)在還能給我許多幫助,雖然已經(jīng)漸漸淡出了書架
總結(jié)起來,即將宏參數(shù)放于#操作符之后便由預(yù)處理器自動轉(zhuǎn)換為字符串常量,轉(zhuǎn)義也由預(yù)處理器自動完成,而不需要我們自行添加轉(zhuǎn)義符號。
對于##
它實(shí)現(xiàn)的是將本操作符兩邊的參數(shù)合并成為一個(gè)完整的標(biāo)記,但需要注意的是,由于預(yù)處理器只負(fù)責(zé)展開,所以程序員必須自己保證這種標(biāo)記的合法性,這里涉及到一些寫法問題,都列出來
#define MERGE(x, y) have_define_ ## (x + y) #define MERGE(x, y) have_define_##(x + y) ... result = MERGE(1, 3);
這里首先說明,上述寫法由于習(xí)慣原因,我使用第二種,但是無論哪種都無傷大雅,效果一樣。上述代碼展開以后是什么呢?
result = have_define_1 + 3;
在我看來,這就有點(diǎn)C++中模版的思想了,雖然十分原始,但是總是有了一個(gè)方向,憑借這種方法我們能夠使用宏來進(jìn)行相似卻不同函數(shù)的調(diào)用,雖然我們可以使用函數(shù)指針數(shù)組來存儲,但需要提前知曉有幾個(gè)函數(shù),并且如果要實(shí)現(xiàn)動態(tài)增長還需要消耗內(nèi)存分配,但宏則不同。
inline int func_0(int arg_1, int arg_2) { return arg_1 + arg_2; } inline int func_1(int arg_1, int arg_2) { return arg_1 - arg_2; } inline int func_2(int arg_1, int arg_2) { return arg_1 * arg_2; } inline int func_3(int arg_1, int arg_2) { return arg_1 / arg_2; } #define CALL(x, arg1, arg2) func_##x(arg1, arg2) ... printf("func_%d return %d\n",0 ,CALL(0, 2, 10)); printf("func_%d return %d\n",1 ,CALL(1, 2, 10)); printf("func_%d return %d\n",2 ,CALL(2, 2, 10)); printf("func_%d return %d\n",3 ,CALL(3, 2, 10));
十分簡便的一種用法,在我們增加減少函數(shù)時(shí)我們不必考慮如何找到這些函數(shù)只需要記下每個(gè)函數(shù)對應(yīng)的編號即可,但還是那句話,不可濫用。
#define CAT(temp, i) (cat##i) //... for(int i = 0;i < 5;++i) { int CAT(x,i) = i*i; printf("x%d = %d \n",i,CAT(x,i)); }
對于宏,在使用時(shí)一定要注意,宏只能展開當(dāng)前層的宏,如果你嵌套使用宏,即將宏當(dāng)作宏的參數(shù),那么將導(dǎo)致宏無法完全展開,即作為參數(shù)的宏只能傳遞名字給外部宏
#define WHERE(value_name, line) #value_name #line ... puts(WHERE(x, __LINE__)); //x = 11
輸出: 11__LINE__
對于其他的預(yù)編譯器指令,如:#program, #line, #error和各類條件編譯并不在此涉及,因?yàn)槭褂蒙喜⑽从邢葳寮半y點(diǎn)。
C和C++混合編程的情況
經(jīng)常能在源代碼中看見 extern "C" 這樣的身影,這是做什么的?
這是為了混合編程而設(shè)計(jì)的,常出現(xiàn)在 C++的源代碼中,目的是為了讓 C++能夠成功的調(diào)用 C 的標(biāo)準(zhǔn)或非標(biāo)準(zhǔn)函數(shù)。
#if defined(__cplusplus) || defined(_cplusplus) extern "C" { #endif /**主體代碼**/ #if defined(__cplusplus) || defined(_cplusplus) } #endif
這樣就能在C++中調(diào)用C的代碼了。
在 C 中調(diào)用 C++ 的函數(shù)需要注意,不能使用重載功能,否則會失敗,原因詳見C++對于重載函數(shù)的實(shí)現(xiàn)。也可以稱為 mangle
上一篇:C/C++程序開發(fā)中實(shí)現(xiàn)信息隱藏的三種類型
欄 目:C語言
下一篇:C++求1到n中1出現(xiàn)的次數(shù)以及數(shù)的二進(jìn)制表示中1的個(gè)數(shù)
本文標(biāo)題:詳解C語言編程中預(yù)處理器的用法
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/2502.html
您可能感興趣的文章
- 01-10求子數(shù)組最大和的解決方法詳解
- 01-10深入二叉樹兩個(gè)結(jié)點(diǎn)的最低共同父結(jié)點(diǎn)的詳解
- 01-10數(shù)據(jù)結(jié)構(gòu)課程設(shè)計(jì)- 解析最少換車次數(shù)的問題詳解
- 01-10數(shù)據(jù)結(jié)構(gòu)課程設(shè)計(jì)-用棧實(shí)現(xiàn)表達(dá)式求值的方法詳解
- 01-10HDOJ 1443 約瑟夫環(huán)的最新應(yīng)用分析詳解
- 01-10使用C++實(shí)現(xiàn)全排列算法的方法詳解
- 01-10如何查看進(jìn)程實(shí)際的內(nèi)存占用情況詳解
- 01-10深入Main函數(shù)中的參數(shù)argc,argv的使用詳解
- 01-10APUE筆記之:進(jìn)程環(huán)境詳解
- 01-10深入第K大數(shù)問題以及算法概要的詳解


閱讀排行
本欄相關(guān)
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達(dá)
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對數(shù)函數(shù)的表達(dá)式 c語言中對
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求
隨機(jī)閱讀
- 04-02jquery與jsp,用jquery
- 01-10delphi制作wav文件的方法
- 01-10C#中split用法實(shí)例總結(jié)
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 08-05織夢dedecms什么時(shí)候用欄目交叉功能?
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改