C語言進(jìn)階教程之預(yù)處理
一.代碼運(yùn)行是的兩種環(huán)境
1.翻譯環(huán)境,在這個環(huán)境中源代碼被轉(zhuǎn)換為可執(zhí)行的機(jī)器指令。
2.執(zhí)行環(huán)境,它用于實(shí)際執(zhí)行代碼
下面主要講解翻譯環(huán)境。
二.翻譯環(huán)境
從.c 文件到 .exe 文件需要經(jīng)過編譯器的翻譯,而翻譯又分為 編譯和鏈接兩個部分
編譯又分為三個部分:
1.預(yù)編譯:又叫預(yù)處理,在這個部分主要完成頭文件的包含,#define的替換,注釋的刪除;
2.編譯:主要完成語法分析,詞法分析,詞義分析,符號匯總(符號包括全局性的變量和函數(shù)),生成匯編代碼;
3.匯編:生成二進(jìn)制指令,形成符號表(符號表是由符號和其地址組成的);
鏈接:合并段表,合并符號表(在這個階段會發(fā)現(xiàn)未定義的函數(shù))。
見下圖:
三.預(yù)定義符號
__FILE__ //進(jìn)行編譯的源文件
__LINE__ //文件當(dāng)前的行號
__DATE__ //文件被編譯的日期
__TIME__ //文件被編譯的時間
__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
四.#define
1.define 定義宏
宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一個由逗號隔開的符號表,它們可能出現(xiàn)在stuff中。注意 name 需與后面的括號緊密相連,不可以有空格,如果有任何空白存在,參數(shù)列表就會被解釋為stuff的一部分。
注意當(dāng)我們定義宏的時候,不要吝嗇括號!
來看下面一個例子:
#define MOD(x,y) x*y int main() { int m = MOD(2+3,2); printf("%d\n", m); return 0; }
對初學(xué)者來說,這段代碼的答案很容易被認(rèn)為式10,但事實(shí)并非如此,因?yàn)楹晔窃陬A(yù)處理階段先替換掉,然后在進(jìn)行計(jì)算,所以在沒有括號的情況下,替換后是這樣的:2+3*2=8;所以若是想要得到10這個結(jié)果,就要加上括號,即:
#define MOD(x,y) ((x)*(y))
2.帶有副作用的宏參數(shù)
我們知像是前置++ ,后置++這種的運(yùn)算符是會改變操作數(shù)的值屬性的,那它如果應(yīng)用到#define 定義的宏中會是怎么樣呢?
我們來看下面這個例子:
#define MAX(x,y) ((x)>(y)?(x):(y)) int main() { int a = 4; int b = 6; int m = MAX(a++, b++); printf("m=%d\n", m); printf("a=%d b=%d\n", a, b); return 0; }
最后的答案會是多少呢?
首先完成宏參數(shù)的替換:((a++)>(b++)?(a++):(b++))
后置++是先使用后++,因?yàn)?<6,所以執(zhí)行后面的 b++,經(jīng)過前面的++,此時a=5,b=7,所以先把7賦給m,然后b++,得到b=8;
即m=7 a=5 b=8
總結(jié):1.#define 定義的符號需要先原封不動的替換掉,所以建議在#define 后面不加 ' ; ' ;
2.#define 定義的宏不要吝嗇括號,以免出現(xiàn)出乎意料的結(jié)果;
3.避免使用帶有副作用的運(yùn)算符。
五.#define定義宏 與函數(shù)對比
六.預(yù)處理指令
所有的預(yù)處理指令都是以井號(#)開頭。它必須是第一個非空字符,為了增強(qiáng)可讀性,預(yù)處理指令應(yīng)從第一列開始。下面列出了所有重要的預(yù)處理指令:
七.條件編譯
可以實(shí)現(xiàn)將一條語句(一組語句)編譯或者放棄。
常見的條件編譯指令:
1.
#if 常量表達(dá)式
//...
#endif
//常量表達(dá)式由預(yù)處理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
例:
int main() { #if 1 //如果這個常量表達(dá)式為真,則執(zhí)行后面的語句,反之則不執(zhí)行 printf("haha\n"); #endif return 0; }
運(yùn)行結(jié)果:
2.多個分支的條件編譯
#if 常量表達(dá)式
//...
#elif 常量表達(dá)式 (注意這里是 elif ,而不是else if )
//...
#else
//...
#endif
例:
#define M 10 int main() { #if M==5 printf("mafumafu\n"); #elif M==10 printf("Eve\n"); #elif M==7 printf("Sou\n"); #elif M==2 printf("amatsuki\n"); #else printf("soraru"); #endif return 0; }
運(yùn)行結(jié)果:
3.嵌套指令
#if defined(OS_UNIX) //如果定義了,則往下執(zhí)行
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
八.頭文件包含的方式
1. 雙引號式 #include "test.h" :先在源文件所在目錄下查找,如果該頭文件未找到,編譯器 就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)準(zhǔn)位置查找頭文件。
如果找不到就提示編譯錯誤。2.尖括號式 #include <stdio.h>: 查找頭文件直接去標(biāo)準(zhǔn)路徑下去查找,如果找不到就提示編 譯錯誤。
所以說庫里的頭文件也可以用 雙引號 包含 ,但并不建議這樣做,因?yàn)殡p引號包含沒有尖括號包含的查找的快。
嵌套文件包含
comm.h和comm.c是公共模塊。
test1.h和test1.c使用了公共模塊。
test2.h和test2.c使用了公共模塊。
test.h和test.c使用了test1模塊和test2模塊。
這樣最終程序中就會出現(xiàn)兩份comm.h的內(nèi)容。這樣就造成了文件內(nèi)容的重復(fù)。
如何防止這種問題出現(xiàn)?
有兩種解決方式:
1.利用條件編譯指令
#ifndef __TEST_H__ //如果沒有定義 TEST_H__ 則執(zhí)行下一句代碼 定義 __TEST_H__ #define __TEST_H__ #endif2.利用預(yù)處理指令 #pragma once
《高質(zhì)量C/C++編程指南》中的兩個問題
1. 頭文件中的 ifndef/define/endif是干什么用的?
防止頭文件的重復(fù)引用。
2. #include <filename.h> 和 #include "filename.h"有什么區(qū)別?文件的查找策略不同。
總結(jié)
到此這篇關(guān)于C語言進(jìn)階教程之預(yù)處理的文章就介紹到這了,更多相關(guān)C語言預(yù)處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解C語言中的rename()函數(shù)和remove()函數(shù)的使用方法
這篇文章主要介紹了詳解C語言中的rename()函數(shù)和remove()函數(shù)的使用方法,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-09-09C語言實(shí)現(xiàn)掃雷小游戲(適合初學(xué)者)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)掃雷小游戲,適合初學(xué)者練習(xí),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03