一、預(yù)定義符號
在C語言中設(shè)置了許多的預(yù)定義符號,這些預(yù)定義符號是可以直接使用的,預(yù)定義符號也是在預(yù)處理階段進(jìn)行處理的。
常見的預(yù)定義符號:
__FILE__ //進(jìn)?編譯的源文件
__LINE__ //文件當(dāng)前的行號
__DATE__ //文件被編譯的日期
__TIME__ //文件被編譯的時間
__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
【示例】:
#include<stdio.h>
int main()
{
printf("%s\n", __FILE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
printf("%d\n", __LINE__);
return 0;
}
我們在VS上使用 _ _ STDC _ _ 會發(fā)現(xiàn)顯示未定義,這也就說明VS的編譯器是不完全遵循ANSI C的,為了展示效果,我沒可以在gcc的環(huán)境下查看一下。
在gcc環(huán)境下運(yùn)行可以看到它輸出的是1,這表明gcc環(huán)境下的編譯器是遵循ANSI C的。
預(yù)處理之后我們會發(fā)現(xiàn),前面我們就學(xué)過,程序在預(yù)處理之后會把預(yù)定義指令給替換掉,這里結(jié)果也確實(shí)如此。
二、#define定義常量
#define一般有兩種應(yīng)用場景:
- #define定義常量
- #define定義宏
#define定義常量基本語法:
#define name stuff
【示例】:
#include<stdio.h>
#define MAX 100
#define STR "hello world"
int main()
{
int a = MAX;
printf("%d\n", MAX);
printf("%s\n", STR);
return 0;
}
#define定義標(biāo)識符加不加 ; 的區(qū)別:
#include<stdio.h>
#define MAX 100
#define MAX1 100;
int main()
{
int a = MAX;
int b = MAX1;
printf("%d\n", MAX);
printf("%d\n", MAX1);
return 0;
}
可以看到,MAX1加了分號之后, 之后后面使用的MAX1全都加上了分號,這也就導(dǎo)致了在打印MAX1時報錯,在預(yù)處理之后可以清楚的看到原因(#define把;也替換過來了)。所以一般使用 #define 定義常量時,不要加分號。
三、#define定義宏
#define 機(jī)制包括了?個規(guī)定,允許把參數(shù)替換到文本中,這種實(shí)現(xiàn)通常稱為宏(macro)或定義宏(define macro)。
宏的申明方式:
#define name( parament-list ) stuff
parament-list 是?個由逗號隔開的符號表,它們可能出現(xiàn)在stuff中。
注意:參數(shù)列表的左括號必須與name緊鄰,如果兩者之間有任何空白存在,參數(shù)列表就會被解釋為stuff的?部分。
【示例】:利用宏求一個數(shù)的平方
#include<stdio.h>
#define SQURE(x) x * x
int main()
{
int a = 12;
int b = SQURE(a);
printf("%d\n", b);
return 0;
}
因?yàn)閰?shù)是允許替換到文本中的,把a(bǔ)傳給SQURE(a)就相當(dāng)于把程序替換成了a * a, 這里預(yù)處理后也能看到效果。
但是這個宏也存在著一些問題:
int a = 5;
printf("%d\n", SQURE(a + 1));
按照慣性,我們會覺得這個代碼的運(yùn)行結(jié)果會是6 * 6 = 36,但結(jié)果真的會是這樣嗎?
我們來運(yùn)行試一下:
運(yùn)行之后可以發(fā)現(xiàn)結(jié)果等于11,這里就要注意了,宏的參數(shù)是不會參與計(jì)算的,會直接進(jìn)行替換,我們進(jìn)行預(yù)處理生成目標(biāo)文件后可以發(fā)現(xiàn)SQURE(a + 1)替換成了a + 1 * a + 1,而 * 的優(yōu)先級比 + 高,導(dǎo)致會先算 * 再算 + ,a等于5,乘以一還是5,再加上6就等于11。
那怎么讓他得到36呢,其實(shí)這里加個括號就可以了。
#include<stdio.h>
#define SQURE(x) (x) * (x)
int main()
{
int a = 5;
int b = SQURE(a + 1);
printf("%d\n", b);
return 0;
}
當(dāng)然,下面這種方法也是一樣的。
我們只要確保替換之后運(yùn)算順序不發(fā)生改變就可以達(dá)到目的了。
下面是一個宏定義:
#define DOUBLE(x) (x) + (x)
定義中我們使用了括號,雖然這樣可以避免之前的問題,但是這個宏定義可能會出現(xiàn)新的問題:
#include<stdio.h>
#define DOUBLE(x) (x) + (x)
int main()
{
int a = 5;
printf("%d\n", 5 * DOUBLE(a));
return 0;
}
按照慣性思維,我們可能會認(rèn)為打印50,但結(jié)果是否會是50呢?
結(jié)果發(fā)現(xiàn)打印的是30,預(yù)處理之后生成目標(biāo)文件之后可以發(fā)現(xiàn)5會先和a相乘,然后再加a,導(dǎo)致結(jié)果與我們的出現(xiàn)誤差。
這個問題,的解決辦法是在宏定義表達(dá)式兩邊加上?對括號就可以了。
#define DOUBLE(x) ((x) + (x))
提示:所以用于對數(shù)值表達(dá)式進(jìn)行求值的宏定義都應(yīng)該用這種方式加上括號,避免在使用宏時由于參數(shù)中的操作符或鄰近操作符之間不可預(yù)料的相互作用。
四、帶有副作用的宏參數(shù)
當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過?次的時候,如果參數(shù)帶有副作用,那么你在使用這個宏的時候就可能出現(xiàn)危險,導(dǎo)致不可預(yù)測的后果。副作用就是表達(dá)式求值的時候出現(xiàn)的永久性效果。
【示例】:
x+1;//不帶副作用
x++;//帶有副作用
【示例】:MAX宏可以證明具有副作用的參數(shù)所引起的問題。
#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main()
{
int a = 3;
int b = 5;
int m = MAX(a++, b++);
printf("m = %d\n", m);
printf("a = %d\n", a);
printf("b = %d\n", b);
return 0;
}
替換之后是一個三目運(yùn)算符,首先a = 3,b = 5,由于是后置加加,判斷之后才會加一,所以判斷之后a就等于4,b就等于6,因?yàn)楸磉_(dá)式為假,后面那個a++不會執(zhí)行,a還是等于4,后面的b++會執(zhí)行,但由于也是后置加加,先使用后再加一,即m就等于6,b就等于7。
結(jié)論:如果一個帶有副作用的參數(shù)在宏定義中出現(xiàn)兩份,就有可能出現(xiàn)不同的結(jié)果,即帶有副作用的參數(shù)是非常危險的,要盡量避免使用。
五、宏替換的規(guī)則
宏替換是C語言預(yù)處理器的一個重要功能,它在編譯之前進(jìn)行文本替換。宏替換遵循一定的規(guī)則,這些規(guī)則確保了宏能夠在正確的上下文中被替換為定義的文本,需要涉及幾個步驟:
-
文本替換:
宏定義中的所有文本都將被直接替換到宏名出現(xiàn)的任何位置。這意味著宏名在代碼中出現(xiàn)的每個地方,都會用宏定義中的文本替換。
#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define M 10
int main()
{
int a = 3;
int b = 5;
int m = MAX(a, M);
printf("m = %d\n", m);
printf("M = %d\n", M);
printf("b = %d\n", b);
return 0;
}
每個宏名的位置都會用宏定義中的文本替換。
-
宏參數(shù)的保留:
當(dāng)宏名被替換時,宏參數(shù)將保持其原始的括號結(jié)構(gòu)。這是為了避免改變操作符的優(yōu)先級和結(jié)合性,確保代碼的邏輯不變。 -
宏參數(shù)的展開:
宏參數(shù)在替換時會展開,這意味著如果宏參數(shù)本身是一個宏,它也會被展開(替換)。這個過程稱為宏的展開或宏的宏展開。 -
宏展開的順序:
當(dāng)宏參數(shù)中包含其他宏時,預(yù)處理器會按照它們在宏定義中出現(xiàn)的順序進(jìn)行替換。如果宏A中使用了宏B,而宏B又使用了宏C,那么預(yù)處理器首先會替換宏C,然后是宏B,最后是宏A。 -
宏展開的深度:
宏展開的深度是有限的。如果一個宏展開后仍然是一個宏(即宏的宏),這個過程會繼續(xù),但是有一個深度限制,以避免無限循環(huán)。 -
宏定義的順序:
宏定義的順序可能會影響宏替換的結(jié)果。如果兩個宏相互依賴,可能會導(dǎo)致預(yù)處理錯誤。為了解決這個問題,可以使用宏的函數(shù)樣宏形式,或者確保依賴關(guān)系正確。 -
宏定義的優(yōu)先級:
如果兩個宏定義具有相同的名稱,預(yù)處理器將使用最后一個定義。這意味著宏定義可以被后續(xù)的宏定義覆蓋。 -
條件編譯中的宏替換:
在使用#ifdef、#ifndef、#if、#else、#elif和#endif等條件編譯指令時,只有當(dāng)條件為真時,相關(guān)的宏才會被替換。 -
字符串化和標(biāo)記粘貼:
預(yù)處理器提供了特殊的宏操作符,如字符串化運(yùn)算符#和標(biāo)記粘貼運(yùn)算符##。字符串化運(yùn)算符可以將宏參數(shù)轉(zhuǎn)換為字符串字面量,而標(biāo)記粘貼運(yùn)算符可以將兩個宏參數(shù)連接成一個單一的標(biāo)識符。 -
宏展開的最佳實(shí)踐:
為了避免宏展開引起的問題,建議使用括號包圍宏參數(shù),避免宏定義過于復(fù)雜,以及避免宏名與關(guān)鍵字或現(xiàn)有標(biāo)識符沖突。
注意:文章來源:http://www.zghlxwxcb.cn/news/detail-855329.html
- 宏參數(shù)和 #define 定義中可以出現(xiàn)其他 #define 定義的符號。但是對于宏,不能出現(xiàn)遞歸。
#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define M 10
int main()
{
int a = 3;
int b = 5;
int m = MAX(a, MAX(2, 3));
printf("m = %d\n", m);
printf("M = %d\n", M);
printf("b = %d\n", b);
return 0;
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-855329.html
- 當(dāng)預(yù)處理器搜索 #define 定義的符號的時候,字符串常量的內(nèi)容并不被搜索。
到了這里,關(guān)于【C語言基礎(chǔ)】:預(yù)處理詳解(一)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!