1. 預(yù)定義符號
????????C語言設(shè)置了一些預(yù)定義符號,可以直接使用,預(yù)定義符號也是在預(yù)處理期間處理的
????????????????__FILE__ ?進行編譯的源文件
????????????????__LINE__ ?文件當(dāng)前的行號
????????????????__DATE__ ?文件被編譯的日期
????????????????__TIME__ ?文件被編譯的時間
????????????????__STDC__ ?如果編譯器遵循ANSI C,其值為1,否則未定義
? ? ? ? 舉個例子
? ? ? ? 這行代碼的出現(xiàn)位置是在第16行,所以__LINE__的值就是16,注意這個詞是兩個下劃線+LINE+兩個下劃線,文件名,行號,日期,時間都是編譯當(dāng)前文件當(dāng)前位置當(dāng)前時間的信息,注意是編譯時的不是運行時的信息。
2. #define定義常量
????????基本語法:
????????????????#define name stuff
????????#define定義的stuff內(nèi)容可以是數(shù)值,參數(shù),甚至是語句,舉個例子:
????????最后一段定義中我們定義了一個打印的語句,很明顯我是將一行代碼寫成了3行。這是因為寫成一行代碼的時候代碼量太長,因此我們可以借助續(xù)行符進行換行接著寫。前兩行結(jié)尾處的反斜杠就是續(xù)行符,我們可以理解成將反斜杠后面的回車轉(zhuǎn)義掉了,所以回車換行相當(dāng)于沒換,視覺上像是換了行,但實際上還是在這一行上書寫
????????現(xiàn)在我們思考一個問題:在使用#define定義標(biāo)識符的時候要不要在最后加上 ?; ?
????????我建議是不要加上的,因為#define是暴力替換掉常量名,如果不注意的話很容易出問題
? ? ? ? 觀察num被替換之后的樣子我們可以發(fā)現(xiàn)加? ;? 的危害了
3. #define定義宏
????????于定義常量類似,定義宏也是將名暴力替換掉,只不過宏新增了一個參數(shù)的機制
????????下面是宏的聲明方式
????????????????#define name(parament-list) stuff
????????parament-list參數(shù)列表是一個由符號隔開的符號表,其中的符號可能出現(xiàn)在stuff中
????????注意:參數(shù)列表的左括號必須和name緊鄰,如果兩者間由任何空白的存在,參數(shù)列表就會被認(rèn)為是stuff的一部分
????????舉例如何使用定義宏
????????????????????????
? ? ? ? 我用宏實現(xiàn)了一個將參數(shù)變成其二倍的功能
????????但是這么寫宏是有問題的
???????????????????????????????
????????我在宏的參數(shù)部分寫2+1,本意是計算DOUBLE(3),但是因為暴力替換的問題,表達(dá)式并沒有按照我預(yù)想的循序計算,而計算的是2*2+1
????????因此我們在定義宏的時候要將表達(dá)式中的元素用小括號括起來,保證它們的運算順序不會在暴力替換之后改變
????????但是問題還沒有解決
???????????????????????????????
????????很明顯,這段代碼也因為暴力替換的問題本想計算5*4,但是實際上計算的是5*2+2
????????因此我們要將整體也用小括號固定計算順序
??????????????????????????????????
????????這樣才算正確的定義宏
4. 帶有副作用的宏參數(shù)
????????當(dāng)宏參數(shù)在宏定義中出現(xiàn)超過一次的時候,如果參數(shù)帶有副作用,那么你在使用這個宏的時候就可能出現(xiàn)危險,導(dǎo)致不可預(yù)測的后果。副作用就是表達(dá)式求值的時候出現(xiàn)的永久性效果。
????????????????x+1 不帶有副作用
????????????????x++ 帶有副作用
????????舉一個副作用的例子
????????
???????很明顯結(jié)果和我們預(yù)期的不一樣,判斷3和5的大小最后答案竟然是6,而且a和b的值也被改變,這正是因為參數(shù)的副作用帶來的問題,簡單分析一下注釋掉的那條語句就可以知道到底是怎么回事了
5. 宏的替換
????????我們之前已經(jīng)提到過多次了,宏在使用時其實就是將名強行替換掉了,那么具體的替換方案涉及以下幾個步驟
????????1.在調(diào)用宏時,首先對參數(shù)進行檢查,看看是否包含任何由#define定義的符號。如果是,它們首先被替換
????????2.替換文本隨后被插入到程序中原來文本的位置。對于宏,參數(shù)名被它們的值替換。
????????3.最后,再對結(jié)果文件進行掃描,看看它是否包含任何由#define定義的符號。如果是,重復(fù)以上處理步驟
????????注意:
????????1.宏參數(shù)和#define定義中可以出現(xiàn)其他#define定義的符號。但是對于宏,不能出現(xiàn)遞歸
????????2.當(dāng)預(yù)處理器搜索#define定義的符號的時候,字符串常量的內(nèi)容不被搜索
?
????????
6. 宏和函數(shù)的對比
????????宏通常被應(yīng)用于執(zhí)行簡單的運算,函數(shù)用于解決復(fù)雜的功能
????????比如在執(zhí)行上面那個比較兩個數(shù)大小的功能的時候,寫成宏更有優(yōu)勢
????????宏的優(yōu)勢:
? ? ? ? 1. 進行簡單計算的時候需要的計算量比函數(shù)更小,宏沒有像函數(shù)的調(diào)用和返回那樣復(fù)雜的匯編語句需要,因此速度也會比函數(shù)快
? ? ? ? 2. 宏是與類型無關(guān)的,所以參數(shù)想傳什么就傳什么,具體產(chǎn)生什么效果等替換完了再看。甚至宏可以傳遞類型名,這是函數(shù)絕對做不到的
????????宏的劣勢:
? ? ? ? 1. 每次使用宏的時候,一份宏定義的代碼將插到程序當(dāng)中去。除非宏比較短,否則可能大幅增加宏的長度
? ? ? ? 2. 宏是沒法調(diào)試的
? ? ? ? 3. 宏由于類型無關(guān),因此不夠嚴(yán)謹(jǐn)
? ? ? ? 4. 宏可能會帶來運算符優(yōu)先級的問題,導(dǎo)致容易出錯
?
7. #和##
7.1 #運算符
????????#運算符將宏的一個參數(shù)轉(zhuǎn)換成字符串字面量。它僅允許出現(xiàn)在帶參數(shù)的宏的替換宏的替換列表中
????????#執(zhí)行的操作可以理解為字符串化
????????舉個例子:
????????這段代碼中#n在替換時發(fā)生字符串化,將 a 變成了 "a"
????????當(dāng)然在觀察注釋的時候可以注意到printf中包含了3段字符串,這種寫法是允許的,它和第二行注釋是等價的
????????
7.2 ##運算符
????????##可以把位于它兩邊的符號合成一個符號,它允許宏定義從分離的文本片段創(chuàng)建標(biāo)識符。##被稱為記號粘合
????????這樣的連接必須是產(chǎn)生一個合法的標(biāo)識符,否則結(jié)果是未定義的
????????下面我們舉個例子:
????????當(dāng)我們想比較兩個數(shù)的大小的時候不同的類型int、float、double都要寫一個不同名函數(shù)來運算,但其實它們的運算內(nèi)容方法是一樣的,所以能不能有一種方法把它們整合一下
????????##在這段代碼中就起到了粘貼 type 和 _max 的作用,使得type會被替換之后還能與_max形成新的函數(shù)名,用這種方法通過一段代碼生成了3個功能相似,函數(shù)名不同的函數(shù)
8. 命名約定
????????一般來講,函數(shù)和宏的使用語法很相似,所以為了區(qū)分二者,我們有這樣的書寫習(xí)慣
????????????????把宏全部大寫
????????????????函數(shù)名不要全部大寫
9. #undef
????????這條指令用于移除一個宏定義
????????舉個例子
???????????????????????
????????在宏定義移除之后MAX就不能用了,但是我們可以用#define重新啟用他,起到一個修改宏的作用
10. 命令行定義
????????許多C的編譯器提供了一種能力,允許在命令行中定義符號,用于啟動編譯的過程
????????舉個例子,如果我在編寫代碼的時候不能確定一個數(shù)組要分配多大的空間,只能在運行前的時候確定,可以這樣先把程序?qū)懗鰜?/p>
??????????????????????????????????????
????????這樣在程序中并不指定SIZE的具體大小,在啟動程序的時候用命令行
????????????????gcc -D SIZE=10 programme.c
????????這樣程序就能正常跑起來,并且開辟一個10個元素的數(shù)組
????????這個功能vs中不好演示,感興趣可以用Linux嘗試一下
11. 條件編譯
????????在編譯一個程序的時候我們可以使用條件編譯指令來控制要不要編譯這條語句
????????比如說那些調(diào)試性的代碼,刪除可惜,保留又礙事,所以我們可以選擇性編譯
????????常見的條件編譯指令
????????1.#if #endif
????????????????#if 常量表達(dá)式(為真編譯,為假不編譯)
????????????????······文章來源:http://www.zghlxwxcb.cn/news/detail-811053.html
????????????????#endif
????????注意#if后面跟的是常量表達(dá)式
????????????????
????????這么寫就是有錯誤的。第一,#if 后邊要求跟常量表達(dá)式,a是變量。第二,#if 是在預(yù)處理階段進行處理的,這時候a還沒有定義呢,那a算什么?
????????2. 多分支的條件編譯
????????????????#if 常量表達(dá)式
????????????????······
????????????????#elif 常量表達(dá)式
????????????????······
????????????????#else
????????????????······
????????????????#endif
????????3. 判斷是否被定義
????????????????#if defined(symbol)
????????????????#ifdef symbol
????????這兩條語句都是說如果這個symbol被定義了就編譯下面的代碼
????????????????#if !defined(symbol)
????????????????#ifndef symbol
????????這兩條語句是說如果沒定義symbol就編譯下面的代碼
????????當(dāng)然不要忘記 #endif 結(jié)束條件編譯,#endif和距它最近的?#if 或 #if··· 匹配,不管是 #if 還是 #if··· 都要記得用 #endif 結(jié)束
????????4.條件編譯指令是可以嵌套的
12. 頭文件的包含
12.1 頭文件被包含的方式
12.1.1 本地文件包含
????????????????#include "filename.h"
????????查找策略:現(xiàn)在源文件目錄下查找,如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)準(zhǔn)位置查找頭文件,如果還是找不到就報錯
12.1.2 庫文件包含
????????????????#include <filename.h>
????????查找這種類型的頭文件就直接去標(biāo)準(zhǔn)路徑下查找(所以庫函數(shù)所在位置),如果找不到就報錯
????????這樣是不是可以說,對于庫函數(shù)包含的時候可不可以用 ?" " ?的形式?
????????確實是可以的,但是這樣做效率就會低一點,同時這樣也不容易區(qū)分我包含的是庫文件還是本地文件
12.2 嵌套文件包含
????????我們之前已經(jīng)學(xué)到過#include就是把另一個文件復(fù)制到這條語句所在位置,并替換掉這條語句。
????????那么在一個大型的項目中,在所難免的會出現(xiàn)頭文件被重復(fù)包含的現(xiàn)象,這會導(dǎo)致多次把頭文件中的內(nèi)容粘過來,如果頭文件過大,這很影響編譯的速度
????????所以我們可以用條件編譯解決這個問題,在頭文件開頭寫:
??????????????????????????????????????
????????這樣就可以通過判斷__TEST_H__是否被定義來確定這個頭文件有沒有使用過,從而確定要不要把頭文件的內(nèi)容粘過去
????????還有一種方法,在頭文件開頭加上:
??????????????????????????????????????
????????就可以避免頭文件的重復(fù)引用,這個方法是比較常見的
13. 其他預(yù)處理指令
????????????????#error
????????????????#pragma
????????????????#line
????????????????#pragma pack()?? ?修改默認(rèn)對齊數(shù),在結(jié)構(gòu)體中有講
????????????????······
????????更多內(nèi)容可以參考《C語言深度解剖》中預(yù)處理一章文章來源地址http://www.zghlxwxcb.cn/news/detail-811053.html
到了這里,關(guān)于C語言·預(yù)處理詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!