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