版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
第15章 預處理和宏 程序在編譯之前先要經(jīng)過預編譯階段。預編譯的任務是根據(jù)程序中的宏指令補充和完善源代碼。有了宏指令,可以使得某些源代碼編輯任務變得輕松,同時也可以控制哪些源代碼需要編譯,哪些不需要編譯。15.1
預處理概述
預處理器是專門用來處理宏指令的程序。在編譯器運行之前,會先運行預處理器,查找所有的預處理指令。預處理指令以“#”開頭,而且不以“;”結束,以區(qū)別于一般的語句。預處理器根據(jù)預處理指令生成新的源代碼文件(臨時文件,可以通過編譯器的選項輸出到指定目錄中)。
編譯器的作用是把源代碼轉(zhuǎn)換成匯編語言或機器指令。但是,編譯器并不是直接編譯程序員寫成的源文件,而是編譯經(jīng)預處理器處理后所產(chǎn)生的新的源文件。這些新的源文件經(jīng)過編譯器生成目標文件,再經(jīng)過鏈接器生成最終的可執(zhí)行程序。
預處理器的任務就是執(zhí)行源代碼中的預處理指令,并對源代碼進行相應的處理。因此,從預處理指令的類型來講,預處理器的任務有這幾部分:將其他文件包含到當前文件中、定義宏、定義類似函數(shù)的宏、實施條件編譯。接下來,將詳細介紹預處理器的各項任務。15.2
宏
所謂宏,是程序中定義的用于替換復雜文本的簡短文本。宏定義的一般格式如圖15-1所示:圖15-1宏的定義
宏名,即宏的名稱,在預編譯時被可替換文本替換??商鎿Q文本,即宏名所指代的文本內(nèi)容。在#define、宏名和可替換文本之間用空格(制表符)分隔。預編譯程序?qū)?define之后的第一個和第二個空格之間的文本作為宏名,其后所有文本作為“可替換文本”,而不管中間有多少個空格。15.2.1
宏展開
在程序的預編譯期,預編譯程序會解析源代碼文本,執(zhí)行替換源程序的動作,把宏引用的地方替換成定義處的文本。這個動作叫做宏的展開。圖15-2這段程序是宏的一個簡單例子。圖15-2宏的使用
上圖代碼中cout后面的NUMBER在預編譯時會被替換成100。
注意:在源代碼文本中,并不是所有的宏名出現(xiàn)的地方都會進行展開。如果這個宏名出現(xiàn)在一個雙引號中,則該宏名就成了字符串的一部分,也就不會進行展開了。15.2.2替代常量
用宏替代字面常量,其好處是直觀、簡潔、修改方
便。譬如對于圓周率,其值是一個無理常量
3.1415926···。如果在程序中每一處要使用圓周率地方都直接書寫這個常量,那么源代碼修改起來就不大方便。一旦要求改變數(shù)字的精度,則所有使用的地方都要修改。無疑修改量就比較大,而且也不能保證每一處都修改對。
如果使用宏替換字面常量,而程序中使用圓周率的地方都使用宏而非圓周率本身,那么當修改圓周率數(shù)值時,只要修改這個宏定義即可,大大減少了編程人員的工作量。例如圖15-3所示代碼,用宏替換常量3.1415926。圖15-3宏替換字面常量
上圖中使用宏PI替換了常量3.1415926,若程序中的常量需要變化,則只需要更改宏定義即可。
相對于常量,使用宏也有缺點。字面常量在編譯時處理,有類型信息。而宏則是在預編譯時展開,只是進行單純的文本替換,沒有類型信息。因此對于一些類型要求比較嚴格的地方,使用宏有一定的風險。
定義宏時可以使用前面已經(jīng)定義的宏。例如:假設已經(jīng)存在一個宏NUMBER,其替換的文本是100,當定義新的宏時,可以在宏的可替換文本中使用NUMBER,如圖15-4所示。圖15-4宏展開15.2.3替代運算符 除了可以用宏替代字面常量,還可以用宏替代某些運算符,包括加減乘除、邏輯與和邏輯非等,甚至函數(shù)和語句塊的花括號。利用這些宏定義,可以編寫出貌似違反C++語法、但實際上合法的源代碼。例如:如圖15-5所示的代碼。圖15-5宏替代運算符15.3
帶參數(shù)的宏
簡單的宏定義,如上節(jié)例子所示,只能進行簡單的文字替換,擴展能力有限。如果宏能夠像函數(shù)那樣帶有參數(shù),并根據(jù)參數(shù)的不同自動展開成不同的文本,則宏的擴展能力將大大提高。實際上,在C++標準中,可以利用預處理命令#define定義帶參數(shù)的宏。15.3.1
定義帶參數(shù)的宏
有參數(shù)宏的宏名后需要帶參數(shù),其語法格式如圖15-6所示:圖15-6帶參數(shù)宏的定義1
若可替換文本一行寫不完,可以分成多行,并在每一行的末尾加上分行的符號“\”(除了最后一行)。其語法格式如圖15-7所示:圖15-7帶參數(shù)宏的定義2
注意:要定義帶參數(shù)的宏,則在宏名和左括號之間不能有空格(或制表符),否則就成了普通的宏定義,括號及其里面的內(nèi)容也將會成為可替換文本的一部分。但是,在括號中的各個參數(shù)之間可以任意添加空格。
在可替換文本中可以引用括號中的參數(shù),從而根據(jù)參數(shù)的不同,展開成不同的源代碼文本。例如,下例定義了一個宏,接受兩個參數(shù),然后比較兩個參數(shù)的大小,并輸出其中的較小值,示例代碼如圖15-8所示。圖15-8帶參數(shù)的宏
注意:在上述例子中使用了帶參數(shù)的宏
LESS,但是在語句的結尾處并沒有分號。乍一看好像不符合C++的語法規(guī)范,但是實際上并沒有錯。這是因為宏是在預編譯時處理的,而不是編譯期。而且,宏展開后的結果符合C++語法規(guī)范,所以這些語句的結尾處沒有分號并不會出錯。15.3.2
注意宏展開的結果
宏的行為有時候和所認知的行為會有些不同,主要原因是通常認為宏作為語句塊,會優(yōu)先執(zhí)行,但這是不對的,宏并沒有那么“聰明”,或者可以說編譯器并沒有那么“聰明”,預編譯器所做的只是忠實地將宏展開。我們來看圖15-9所示的程序,預編譯器不會自作聰明為“x+x”加括號,因此最后輸出的結果是30。圖15-9帶參數(shù)宏的展開
如果憑借第一印象,很可能會做這樣的計算:5*(5+5)=50。但是,預編譯器并不這么認為,而會做這樣的解釋:5*5+5=30。忠實的將原來的宏展開。所以讀者寫宏的時候一定要小心,預防宏的行為不符合預期。
若想讓結果輸出為50,只要給宏加上括號即可,如圖15-10所示。圖15-10展開宏時注意括號
上圖中的“cout<<5*INT(5)<<endl;”經(jīng)預處理后,轉(zhuǎn)換為“cout<<5*(5+5)<<endl;”。15.3.3帶參數(shù)的宏與函數(shù)的比較帶參數(shù)的宏在定義和使用方面同函數(shù)非常相似,都可以接受參數(shù),并且可以根據(jù)不同的參數(shù)產(chǎn)生不同的結果。但是這兩者畢竟是不同的東西,存在很大差異,例如:間,程序運行時并不存在。次,則其語句也將重復多次。換。息。(1)函數(shù)在運行時依然存在,但宏只存在于預編譯期(2)函數(shù)所占內(nèi)存空間只有一份,但如果宏被調(diào)用多(3)函數(shù)調(diào)用有時間和空間方面的開銷,但宏沒有。(4)函數(shù)接受參數(shù)的傳遞,但宏只是對參數(shù)的簡單替(5)函數(shù)的參數(shù)有類型信息,但宏的參數(shù)沒有類型信(6)函數(shù)可以調(diào)試,宏不可以。15.4
條件編譯
條件編譯指令可以對程序源代碼的各部分有選擇的進行編譯,該過程就稱為條件編譯。常見的條件編譯指令有#if、#elif、#else、#ifndef、#ifdef、#endif等。它們主用來在編譯時進行選擇,屏蔽掉一些代碼。
以下內(nèi)容將對條件編譯內(nèi)容進行相關介紹。15.4.1宏指令
C++中的宏指令都是在ANSI標準中的,表
15-1列出了常見的宏指令。表15-1常見的宏指令
#define在前面已經(jīng)介紹過了,這里就不再討論。#error可以強迫編譯程序停止編譯,用來在編譯期間檢查環(huán)境是否符合要求或
者與約束的條件發(fā)生了沖突。其使用格式
如圖15-11所示:#define#error#include#if#else#elif#endif#ifdef#ifndef#undef#line#pragma圖15-11強迫停止編譯指令#error
當程序遇到這個關鍵字,就會停止編譯,產(chǎn)生一個錯誤信息,并且輸出后面的token-string。例如:#if
!defined(
cplusplus)#error
C++
compiler
required.#endif
上面這段代碼的意思是在預編譯期間檢查當前的程序是否是C++編譯環(huán)境,如果不是,就定義#error,讓編譯器停止編譯。#if和#endif會在后面介紹,這里只需要將#if視為普通的if判斷
語句,將#endif看成if判斷的結束。
#include使編譯程序?qū)?include所指向的源文件導入進當前的源文件,被包含的文件必須被尖括號或者引號包圍起來。#if,#else,#elif,#endif,#ifdef和#ifndef屬于條件編命令,可以對程序的各個部分有選擇地進行編譯。#undef命令用來取消前面定義過的宏名。來看一個宏使用的例子,如圖15-12所示。圖15-12宏指令的使用
在程序的第一行定義了NUMBER宏,在第六行輸出10。在第八行取消了NUMBER宏的定義,所以后面會輸出“找不到
NUMBER的定義”。15.4.2
條件編譯
#if,#else,#elif,#endif,#ifdef和#ifn是條件編譯命令。所謂條件編譯,就是可以將源文件中的代碼分成幾個部分,有選擇的編譯各個部分。對于前三個宏,可以理解為if,else和else
if,#endif表示這個條件編譯選擇的結束??磮D15-13中的代碼:圖15-13用#if、#else和#elif進行條件編譯
程序的第一行定義NUMBER宏,令其等于5。后面的代碼判斷是否等于相應的值,選擇性地編譯后面的語句,由于定義NUMBER等于5,編譯器輸出“Number
is
equal
to
5”
另一種條件編譯的方式就是使用#ifdef和#ifndef。同樣,這兩個命令也用#endif作為作用域的結束。#ifdef判斷后面的標識符是否被定義,通常都是指預定義的宏,#ifndef就是#ifdef的取反。看圖15-14中的程序。圖15-14用#ifdef和#ifndef進行條件編譯
第四行判斷是否定義DEBUG標識符(在第一行定義),然后編譯后面的語句。再來看一個例子,示例代碼如圖15-15所示。圖15-15條件編譯1
代碼第四行的disp函數(shù)中采用了條件編譯指令編寫,因為程序中定義了WINDOWS宏,所以輸出“本程序當前運行在Windows環(huán)境下”,假若將“define
WINDOWS”語句注釋掉,程序?qū)⑤敵觥氨境绦蜻\行在非
WINDOWS環(huán)境下”,如圖15-16所示。圖15-16條件編譯2
代碼第六行“#if
defined(WINDOWS)”語句中的“define(宏名)”的含義是若宏被定義則返回1,否則返回0,defined之間可以進行邏輯運算。15.5
文件包含和頭文件衛(wèi)士15.5.1
包含文件指令
包含文件的預處理指令是“#include”,其作用就是將別的文件包含到當前文件中。
在實際使用中,一般被包含的文件是頭文
件。頭文件中一般是函數(shù)、類等的聲明,
包含到當前文件中后,就可以在當前文件
中引用頭文件中的函數(shù)、類等。例如對于
下圖中的源代碼文件進行預編譯后產(chǎn)生的
臨時源代碼文件如圖15-17所示。圖15-17包含頭文件按照C/C++的語法要求,要使用某個標識符(變量、函數(shù)、類等),必須在使用之前
先聲明。雖然在Some.cpp文件中沒有函數(shù)
SomeFunction的聲明,但是由于包含了頭文件header.h,所以仍然可以使用該函數(shù)。15.5.2
搜索頭文件
使用“#include”指令包含頭文件時,其后的頭文件有兩種方式,一種是使用雙引號,一種是使用尖括號。如圖15-18所示。圖15-18頭文件的兩種包含方式如果文件名用尖括號括起來,表明這個文件是一個工程或C++標準庫頭文件。預編譯器會首先搜索在工程中預定義的目錄,然后搜索C++編譯器的安裝目錄??梢酝ㄟ^設置工程搜索路徑環(huán)境變量或命令行選項來修改。如果文件名用一對引號括起來,則表明該文件是用戶提供的頭文件。預編譯器首先從當前文件目錄開始搜索,如果找不到,就從工程中定義的目錄和編輯器的安裝目錄查找。15.5.3
頭文件衛(wèi)士
當工程中文件眾多是,很有可能出現(xiàn)一個頭文件被多次包含的情況。但是,C++中同一個類型被聲明兩次是非法的,來看一個例子。,如圖15-19所示,文件Animal.h中定義了類CAnimal,文件pig.h中定義了類
CPig,文件horse.h中定義了類CHorse,主函數(shù)中用類CPig和CHorse分別定義了變量
pig和horse。圖15-19重復定義1
如上圖所示,在main函數(shù)中使用了CHorse和CPig兩個類,所以需要包含這兩個類的頭文件,但是,這兩個類都包含CAnimal的定義,編譯器不知道該選擇哪個,會報告重復定義的錯誤。如下圖15-20所示,對上述代碼進行編譯,編譯器給出了錯誤信息。圖15-20 重復定義2為了避免因為重復包含頭文件而重復定義類型,導致編譯失敗,這個時候就需要頭文件衛(wèi)士的幫助。所謂頭文件衛(wèi)士就是用一組宏命令將頭文件包起來,使其不會被重復包含,看Animal.h的例子:#ifndef
ANIMAL_H#define
ANIMAL_H??#endif#ifndef是定義在頭文件所有內(nèi)容之前的,#endif是定義在所有內(nèi)容之后的,用預編譯命令#ifndef和#endif將整個Animal.h頭文件中的內(nèi)容包含起來。這樣便不會有編譯錯誤了,下面來分析一下。
當main函數(shù)所在文件第一次包含pig.h文件時,同時會導入Animal.h中的內(nèi)容,這時預編譯器分析當前文件沒有定義ANIMAL_H,就會在當前文件中定義。當再次包含horse.h文件時,導入Animal.h,發(fā)現(xiàn)文件中已經(jīng)定義了ANIMAL_H,就會查找#else或者
#endif,這時會直接跳轉(zhuǎn)到#endif,不會包含當前文件的任何內(nèi)容。
同理,如果有人想繼續(xù)從Horse上繼承,例如Whitehorse
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2022年大二學年總結自我鑒定5篇
- 【模塊二名篇名句默寫】【高分攻略】高考語文一輪復習學案
- 石河子大學《數(shù)字信號處理》2022-2023學年第一學期期末試卷
- 石河子大學《口腔解剖生理學二》2021-2022學年第一學期期末試卷
- 石河子大學《工程項目管理》2021-2022學年第一學期期末試卷
- 石河子大學《波斯文學史》2023-2024學年第一學期期末試卷
- 沈陽理工大學《數(shù)學物理方法》2022-2023學年第一學期期末試卷
- 沈陽理工大學《英國文學史》2022-2023學年第一學期期末試卷
- 《論語》導讀(2021下)學習通超星期末考試答案章節(jié)答案2024年
- 沈陽理工大學《電子技術基礎》2021-2022學年期末試卷
- 維修電工題庫(300道)
- 期中考試卷(試題)-2024-2025學年蘇教版二年級數(shù)學上冊
- 2023年江蘇省普通高中信息技術學業(yè)水平考試題庫試題7
- 粵教板2019高中信息技術必修一全冊練習附答案
- 研究生學術表達能力培養(yǎng)智慧樹知到答案2024年西安建筑科技大學、清華大學、同濟大學、山東大學、河北工程大學、《環(huán)境工程》英文版和《環(huán)境工程》編輯部
- 中國骨關節(jié)炎診療指南(2024版)解讀
- 職業(yè)院校技能大賽《植物病蟲害防治》賽項賽題及答案
- 第六單元測試卷(單元卷)-2024-2025學年六年級上冊統(tǒng)編版語文
- 2024 中國主要城市群生態(tài)環(huán)境保護營商競爭力指數(shù)研究報告
- 人教版(2024)2024-2025學年七年級數(shù)學上冊期中質(zhì)量評價(含答案)
- 2024年新北師大版七年級上冊數(shù)學教學課件 4.3 多邊形和圓的初步認識
評論
0/150
提交評論