柚子快報(bào)邀請(qǐng)碼778899分享:開發(fā)語言 【C語言】預(yù)處理詳解
柚子快報(bào)邀請(qǐng)碼778899分享:開發(fā)語言 【C語言】預(yù)處理詳解
前言
上篇博客我們總結(jié)了編譯與鏈接,有說過編譯里第一步是預(yù)處理,那本篇博客將對(duì)預(yù)處理進(jìn)行進(jìn)一步的詳細(xì)的總結(jié)
個(gè)人主頁:小張同學(xué)zkf
若有問題 評(píng)論區(qū)見
感興趣就關(guān)注一下吧
目錄
1. 預(yù)定義符號(hào)
2. #define 定義常量
3. #define定義宏
4. 帶有副作用的宏參數(shù)
5. 宏替換的規(guī)則
6. 宏和函數(shù)的對(duì)比
?7. #和##
7.1 #運(yùn)算符
7.2 ## 運(yùn)算符
8. 命名約定
9. #undef
10. 命令行定義
11. 條件編譯
12. 頭文件的包含
12.1 頭文件被包含的方式
12.1.1 本地文件包含
12.1.2 庫(kù)文件包含
12.2 嵌套文件包含
13. 其他預(yù)處理指令
1. 預(yù)定義符號(hào)
C語言設(shè)置了一些預(yù)定義符號(hào),
可以直接使用
,預(yù)定義符號(hào)也是在
預(yù)處理期間
處理的。
__FILE__
//
進(jìn)行編譯的源文件
__LINE__
//文
件當(dāng)前的行號(hào)
__DATE__
//文
件被編譯的日期
__TIME__
//文
件被編譯的時(shí)間
__STDC__
//
如果編譯器遵循
ANSI C
,其值為
1
,否則未定義
我們來看一下,在vs2022中是否遵循ANSI C(標(biāo)準(zhǔn)C)
?由此可見,vs2022不遵循ANSI C
注:預(yù)定義符號(hào)在預(yù)處理間就被替換了
2. #define 定義常量
基本語法:
#
define
name stuff
#
define
MAX 1000
#
define
reg register
//
為
register
這個(gè)關(guān)鍵字,創(chuàng)建?個(gè)簡(jiǎn)短的名字
#
define
do_forever for(;;)
//用
更形象的符號(hào)來替換?種實(shí)現(xiàn)
#
define
CASE break;case
//
在寫
case
語句的時(shí)候?動(dòng)把
break
寫上。
//
如果定義的
stuff
過?,可以分成?行寫,除了最后??外,每?的后?都加?個(gè)反斜杠
(
續(xù)?
符
)
。
#
define
DEBUG_PRINT printf(
"file:%s\tline:%d\t \
date:%s\ttime:%s\n"
,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
注:上面“\”是續(xù)行符
?我們想一下,在define定義標(biāo)識(shí)符的時(shí)候,要不要在最后加上---“;”
比如:
#
define
MAX 1000;
#
define
MAX 1000
?建議不要加上 ;? ? ?,這樣容易導(dǎo)致問題。
比如下面的場(chǎng)景:
if
(condition)
max = MAX;
else
max =
0
;
如果是加了分號(hào)的情況,等替換后,if和else之間就是2條語句,而沒有大括號(hào)的時(shí)候,if后邊只能有一條語句。這里會(huì)出現(xiàn)語法錯(cuò)誤。
3. #define定義宏
#define機(jī)制包括了一個(gè)規(guī)定,允許把參數(shù)替換到文本中,這種實(shí)現(xiàn)通常稱為宏(macro)或定義宏(define macro)。
下面是宏的申明方式:
#
define
name( parament-list ) stuff
其中的
parament-list
是一個(gè)由逗號(hào)隔開的符號(hào)表,它們可能出現(xiàn)在stuff中。
注意:
參數(shù)列表的左括號(hào)必須與name緊鄰,如果兩者之間有任何空白存在,參數(shù)列表就會(huì)被解釋為stuff的一部分。
?#define SQUARE( x ) x * x
這個(gè)宏接收一個(gè)參數(shù)
x
.如果在上述聲明之后,你把
SQUARE( 5 );
置于程序中,預(yù)處理器就會(huì)用
下面這個(gè)表達(dá)式替換上面的表達(dá)式:5*5
警告:
這個(gè)宏存在一個(gè)問題:
觀察下面的代碼段:
int
a =
5
;
printf
(
"%d\n"
,SQUARE( a +
1
) );
乍一看,你可能覺得這段代碼將打印36,事實(shí)上它將打印11,為什么呢?
?
替換文本時(shí),參數(shù)x被替換成a + 1,所以這條語句實(shí)際上變成了:
printf
(
"%d\n"
,a +
1
* a +
1
);
這樣就比較清晰了,由替換產(chǎn)生的表達(dá)式并沒有按照預(yù)想的次序進(jìn)行求值。
在宏定義上加上兩個(gè)括號(hào),這個(gè)問題便輕松的解決了:
#
define
SQUARE(x) (x) * (x)
這樣預(yù)處理之后就產(chǎn)生了預(yù)期的效果:
printf
(
"%d\n"
,(a +
1
) * (a +
1
) );
?這里還有一個(gè)宏定義:
#
define
DOUBLE(x) (x) + (x)
?定義中我們使用了括號(hào),想避免之前的問題,但是這個(gè)宏可能會(huì)出現(xiàn)新的錯(cuò)誤。
int
a =
5
;
printf
(
"%d\n"
,
10
* DOUBLE(a));
這將打印什么值呢?看上去,好像打印100,但事實(shí)上打印的是55
我們發(fā)現(xiàn)替換之后:
printf
(
"%d\n"
,
10
* (
5
) + (
5
));
乘法運(yùn)算先于宏定義的加法,所以出現(xiàn)了
55
.
這個(gè)問題的解決辦法是在宏定義表達(dá)式兩邊加上一對(duì)括號(hào)就可以了。
#
define
DOUBLE( x) ( ( x ) + ( x ) )
提示:
所以用于對(duì)數(shù)值表達(dá)式進(jìn)行求值的宏定義都應(yīng)該用這種方式加上括號(hào),避免在使用宏時(shí)由于參數(shù)中的操作符或鄰近操作符之間不可預(yù)料的相互作用。
寫宏一定要考慮優(yōu)先級(jí)和結(jié)合性,這個(gè)很重要?。。。。。。。。。。。?/p>
該加括號(hào)就加括號(hào) ?。。。。。?!
4. 帶有副作用的宏參數(shù)
當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過一次的時(shí)候,如果參數(shù)帶有副作用,那么你在使用這個(gè)宏的時(shí)候就可能出現(xiàn)危險(xiǎn),導(dǎo)致不可預(yù)測(cè)的后果。副作用就是表達(dá)式求值的時(shí)候出現(xiàn)的永久性效果。
1
x+
1
;
//
不帶副作?
2
x++;
//
帶有副作?
?MAX宏可以證明具有副作用的參數(shù)所引起的問題。
#
define
MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x =
5
;
?y
?=
8
;
z = MAX(x++, y++);
?
printf
(
"x=%d y=%d z=%d\n"
, x, y, z);
//
輸出的結(jié)果是什么?
?這里我們得知道預(yù)處理器處理之后的結(jié)果是什么:
z = ( (x++) > (y++) ? (x++) : (y++));
?第一個(gè)x++是5,第一個(gè)y++是8,5<8是假,此刻x是6,y是9,執(zhí)行第二個(gè)y++,那返回的值是9,z就為9,第二個(gè)y++之后,y最后為10,
輸出的結(jié)果是:x=6 y=10 z=9
5. 宏替換的規(guī)則
在程序中擴(kuò)展#define定義符號(hào)和宏時(shí),需要涉及幾個(gè)步驟。
1.
在調(diào)用宏時(shí),首先對(duì)參數(shù)進(jìn)行檢查,看看是否包含任何由#define定義的符號(hào)。如果是,它們?先被替換。
2.
替換文本隨后被插入到程序中原來文本的位置。對(duì)于宏,參數(shù)名被他們的值所替換。
3.
最后,再次對(duì)結(jié)果文件進(jìn)行掃描,看看它是否包含任何由#define定義的符號(hào)。如果是,就重復(fù)上述處理過程。
注意:
1.
宏參數(shù)
和
#define 定義
中可以出現(xiàn)其他#define定義的符號(hào)。但是對(duì)于宏,不能出現(xiàn)遞歸。
2.
當(dāng)預(yù)處理器搜索#define定義的符號(hào)的時(shí)候,字符串常量的內(nèi)容并不被搜索。
6. 宏和函數(shù)的對(duì)比
宏通常被應(yīng)用于執(zhí)行簡(jiǎn)單的運(yùn)算。
比如在兩個(gè)數(shù)中找出較大的一個(gè)時(shí),寫成下面的宏,更有優(yōu)勢(shì)一些。
#
define
MAX(a, b) ((a)>(b)?(a):(b))
?那為什么不用函數(shù)來完成這個(gè)任務(wù)?
原因有二:
1.
用
于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實(shí)際執(zhí)行這個(gè)小型計(jì)算工作所需要的時(shí)間更多。? ? ? 所以宏比函數(shù)在程序的規(guī)模和速度方面更勝一籌。
2.
更為重要的是函數(shù)的參數(shù)必須聲明為特定的類型。所以函數(shù)只能在類型合適的表達(dá)式上使? ? ? 用。反之這個(gè)宏可以適用于整形、長(zhǎng)整型、浮點(diǎn)型等,甚至可以把類型當(dāng)做參數(shù)傳進(jìn)去比?
。宏的參數(shù)是類型無關(guān)的,無需進(jìn)行類型比較。
?和函數(shù)相比宏的劣勢(shì):
1.
每次使用宏的時(shí)候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序的長(zhǎng)度,如果不斷調(diào)用這個(gè)宏,那程序長(zhǎng)度不斷增大,空間也會(huì)增大,反之函數(shù)永遠(yuǎn)調(diào)用的是那一塊空間函數(shù),在這個(gè)方面函數(shù)比較簡(jiǎn)便。
2.
宏是沒法調(diào)試的。
3.
宏由于類型無關(guān),也就不夠嚴(yán)謹(jǐn)。
4.
宏可能會(huì)帶來運(yùn)算符優(yōu)先級(jí)的問題,導(dǎo)致程容易出現(xiàn)錯(cuò)。
宏有時(shí)候可以做函數(shù)做不到的事情。比如:宏的參數(shù)可以出現(xiàn)類型,但是函數(shù)做不到。
#
define
MALLOC(num, type)\
?(type
)malloc(num
sizeof(type))
?...
?
//
使用
?MALLOC(
10
,
int
);
//
類型作為參數(shù)
?
//
預(yù)處理器替換之后:
?(
int
*
)
malloc
(
10
sizeof
(
int
));
宏和函數(shù)的一個(gè)對(duì)比
補(bǔ)充
這里我們補(bǔ)充一個(gè)奇怪的東西,在c++里面有個(gè)內(nèi)聯(lián)函數(shù)(inline)它具有宏的特點(diǎn),也有函數(shù)的特點(diǎn),我們先簡(jiǎn)單了解下,等到c++再詳細(xì)總解
?7. #和##
7.1 #運(yùn)算符
#運(yùn)算符將宏的一個(gè)參數(shù)轉(zhuǎn)換為字符串字面量。它僅允許出現(xiàn)在帶參數(shù)的宏的替換列表中。
#運(yùn)算符所執(zhí)行的操作可以理解為”字符串化“。
當(dāng)我們有一個(gè)變量
int a = 10;
的時(shí)候,我們想打印出:
the value of a is 10
.
就可以寫成:
#
define
PRINT(n) printf(
"the value of "
#n
" is %d"
, n);
當(dāng)我們按照下面的方式調(diào)用的時(shí)候:
PRINT(a);//當(dāng)我們把a(bǔ)替換到宏的體內(nèi)時(shí),就出現(xiàn)了#a,而#a就是轉(zhuǎn)換為"a",時(shí)一個(gè)字符串
?代碼就會(huì)被預(yù)處理為:
printf
(
"the value of ""a" " is %d"
, a);
運(yùn)行代碼就能在屏幕上打?。?/p>
the value of a is
10
7.2 ## 運(yùn)算符
##
可以把位于它兩邊的符號(hào)合成一個(gè)符號(hào),它允許宏定義從分離的文本片段創(chuàng)建標(biāo)識(shí)符。
##
被稱
為記號(hào)粘合
這樣的連接必須產(chǎn)生一個(gè)合法的標(biāo)識(shí)符。否則其結(jié)果就是未定義的。
這里我們想想,寫一個(gè)函數(shù)求2個(gè)數(shù)的較大值的時(shí)候,不同的數(shù)據(jù)類型就得寫不同的函數(shù)。
比如:
int
int_max
(
int
x,
int
y)
{
return
x>y?x:y;
}
float
float_max
(
float
x,
float
y)
{
return
x>yx:y;
}
?但是這樣寫起來太繁瑣了,現(xiàn)在我們這樣寫代碼試試:
//
宏定義
#
define
GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x>y?x:y); \
}
?使用宏,定義不同函數(shù)
GENERIC_MAX(
int
)?
//
替換到宏體內(nèi)后
int##_max
?成了新的符號(hào)
int_max
做函數(shù)名
?
GENERIC_MAX(
float
)
//
替換到宏體內(nèi)后
float##_max
?成了新的符號(hào)
float_max
做函數(shù)名
?
int
main
()
?
{
?
//
調(diào)?函數(shù)
?
int
m = int_max(
2
,
3
);
?
printf
(
"%d\n"
, m);
?
float
fm = float_max(
3.5f
,
4.5f
);
?
printf
(
"%f\n"
, fm);
?
return
0
;
?
}
?輸出:
3
4.500000
?整體代碼如上圖,這個(gè)代碼非常巧妙地用宏來函數(shù)定義,只需將類型傳進(jìn)去,這個(gè)##就是用來將左右兩個(gè)標(biāo)識(shí)符合并成一個(gè)標(biāo)識(shí)符,type改變,那對(duì)應(yīng)的type_max也發(fā)生改變,這樣就能有不同的函數(shù)名字。
假如沒有##,會(huì)發(fā)生什么
如上圖可知,沒有##,這個(gè)type_max本身就是一體的,函數(shù)名不會(huì)隨著你穿進(jìn)去類型的變化而變化,type_max就是簡(jiǎn)簡(jiǎn)單單的一個(gè)標(biāo)識(shí)符。?
?在實(shí)際開發(fā)過程中##使用的很少,很難取出非常貼切的例子。
8. 命名約定
一般來講函數(shù)的宏的使用語法很相似。所以語言本身沒法幫我們區(qū)分二者。
那我們平時(shí)的一個(gè)習(xí)慣是:
把宏名全部大寫
函數(shù)名不要全部大寫
當(dāng)然也有小寫的宏,例如:?
9. #undef
這條指令用于移除一個(gè)宏定義。
#
undef
NAME
?
//
如果現(xiàn)存的?個(gè)名字需要被重新定義,那么它的舊名字?先要被移除。
比如:
這個(gè)MAX不是被定義了嘛,因?yàn)?undef出現(xiàn)所以取消了MAX定義,此刻MAX未定義
10. 命令行定義
許多C 的編譯器提供了一種能力,允許在命令行中定義符號(hào)。用于啟動(dòng)編譯過程。
例如:當(dāng)我們根據(jù)同一個(gè)源文件要編譯出一個(gè)程序的不同版本的時(shí)候,這個(gè)特性有點(diǎn)用處。(假定某個(gè)程序中聲明了一個(gè)某個(gè)長(zhǎng)度的數(shù)組,如果機(jī)器內(nèi)存有限,我們需要一個(gè)很小的數(shù)組,但是另外一個(gè)機(jī)器內(nèi)存大些,我們需要一個(gè)數(shù)組能夠大些。)
#
include
int
main
()
{
int
array
[ARRAY_SIZE];
int
i =
0
;
for
(i =
0
; i< ARRAY_SIZE; i ++)
{
array
[i] = i;
}
for
(i =
0
; i< ARRAY_SIZE; i ++)
{
printf
(
"%d "
,
array
[i]);
}
printf
(
"\n"
);
return
0
;
}
假如我們不用#define定義,我們可以直接在命令行中進(jìn)行如下操作
?編譯指令:
//linux
環(huán)境演?
gcc -D ARRAY_SIZE=
10
programe.c
?用-D命令直接把ARRAY_SIZE賦值成10(這是在gcc編譯器下)
11. 條件編譯
在編譯一個(gè)程序的時(shí)候我們?nèi)绻獙⒁粭l語句(一組語句)編譯或者放棄是很方便的。因?yàn)槲覀冇袟l件編譯指令。
比如說:
調(diào)試性的代碼,刪除可惜,保留又礙事,所以我們可以選擇性的編譯。
#
include
#
define
__DEBUG__
int
main
()
{
int
i =
0
;
int
arr[
10
] = {
0
};
for
(i=
0
; i<
10
; i++)
{
arr[i] = i;
#
ifdef
__DEBUG__
printf
(
"%d\n"
, arr[i]);
//
為了觀察數(shù)組是否賦值成功。
#
endif
//__DEBUG__
}
return
0
;
}
?常見的條件編譯指令:
1.
#
if
常量表達(dá)式
//...
#
endif
//
常量表達(dá)式由預(yù)處理器求值。
如:
#
define
__DEBUG__ 1
#
if
__DEBUG__
//..
#
endif
2.
多個(gè)分支的條件編譯
#
if
常量表達(dá)式
//...
#
elif
常量表達(dá)式
//...
#
else
//...
#
endif
3.
判斷是否被定義
#
if
defined(symbol)
#
ifdef
symbol
#
if
!defined(symbol)
#
ifndef
symbol
4.
嵌套指令
#
if
defined(OS_UNIX)?
?
#
ifdef
OPTION1
?unix_version_option1();
?
#
endif
?
#
ifdef
OPTION2
?unix_version_option2();
?
#
endif
?
#
elif
defined(OS_MSDOS)
?
#
ifdef
OPTION2
?msdos_version_option2();
?
#
endif
?
#
endif
注意:只要是#if指令,那么一定要在用完之后加上#endif !!!
12. 頭文件的包含
12.1 頭文件被包含的方式
12.1.1 本地文件包含
#
include
"filename"
查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)準(zhǔn)位置查找頭文件。
如果找不到就提示編譯錯(cuò)誤
Linux環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:
/usr/include
?VS環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:
C:\Program
Files
(x86)\Microsoft Visual Studio 12.0\VC\include
?
//
這是
VS2013
的默認(rèn)路徑
?注意按照自己的安裝路徑去找。
12.1.2 庫(kù)文件包含
#
include
查找頭文件直接去標(biāo)準(zhǔn)路徑下去查找,如果找不到就提示編譯錯(cuò)誤。
這樣是不是可以說,對(duì)于庫(kù)文件也可以使用?
“”
的形式包含?
答案是肯定的,可以,但是這樣做查找的效率就低些,當(dāng)然這樣也不容易區(qū)分是庫(kù)文件還是本地文件了。
12.2 嵌套文件包含
我們已經(jīng)知道,
#include
指令可以使另外一個(gè)文件被編譯。就像它實(shí)際出現(xiàn)于
#include
指令的
地方一樣。
這種替換的方式很簡(jiǎn)單:預(yù)處理器先刪除這條指令,并用包含文件的內(nèi)容替換。
一個(gè)頭文件被包含10次,那就實(shí)際被編譯10次,如果重復(fù)包含,對(duì)編譯的壓力就比較大。
test.c
#
include
"test.h"
#
include
"test.h"
#
include
"test.h"
#
include
"test.h"
#
include
"test.h"
int
main
()
{
return
0
;
}
test.h
void
test
();
struct
Stu
{
int
id;
char
name[
20
];
};
如果直接這樣寫,test.c文件中將test.h包含5次,那么test.h文件的內(nèi)容將會(huì)被拷貝5份在test.c中。
如果test.h 文件比較大,這樣預(yù)處理后代碼量會(huì)劇增。如果工程比較大,有公共使用的頭文件,被大家都能使用,又不做任何的處理,那么后果真的不堪設(shè)想。
如何解決頭文件被重復(fù)引入的問題?答案:條件編譯。
每個(gè)頭文件的開頭寫:
#
ifndef
__TEST_H__
#
define
__TEST_H__
//
頭?件的內(nèi)容
#
endif
//__TEST_H__
或者
?#pragma once
?就可以避免頭文件的重復(fù)引入。
13. 其他預(yù)處理指令
#
error
#
pragma
#
line
#
pragma
pack()
……
?結(jié)束語
本篇文章小編已經(jīng)盡力在總結(jié)重點(diǎn),但肯定有些地方挖的不夠深,如果想更加詳細(xì)的了解這方面的點(diǎn)點(diǎn)滴滴,我們可以參考《C語言深度解剖》
OK感謝觀看?。?!
柚子快報(bào)邀請(qǐng)碼778899分享:開發(fā)語言 【C語言】預(yù)處理詳解
好文推薦
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。