宏——基础
編譯4個過程:預處理,編譯,匯編,連接。宏就是在預處理階段發揮作用。
宏結尾沒有;,因為凡是;結尾的東西,都是由第二階段“編譯”來處理的(a.i->a.s),而所有預編譯的代碼都是在預編譯階段處理的,為了以示區分,所以所有預編譯的代碼都不需要分號結尾。
宏有兩種,一種是有宏體宏,另一種是無宏體宏。
無宏體宏
什么是無宏體宏
只有宏名、沒有宏體。
定義形式
#define 宏名?
舉例
#define X86
預編譯完后,由于這個宏沒有宏體,所以宏直接替換為空,空就是啥也沒有。
有宏體宏
有宏體宏分為兩種,一種是無參宏,另一種是有參宏。
無參宏
定義形式
#define 宏名 宏體
舉例
#define YES 1 #define NO 0 #define PI 3.1415926 #define OUT printf(“Hello,World”); View Code預處理后,宏名會被替換為宏體。
帶參宏
定義形式
#define 宏名(參數表) 宏體
舉例
#define S(a,b) a*b*10int main(void) {int va; va = S(3,2); //3對應a,2對應b printf("va = %d\n", va); return 0; } View Code預編譯處理時,將宏體中的a和b,使用參數中的3和2來替換。va = S(a, b) —> va = 3*2*10
?帶參宏需要注意之處
①宏名和參數列表之間不能有空格
#define S (a,b) a*b*10 View Code由于S和(a, b)之間有空格,宏名變為了S,宏體變為了(a,b) a*b*10,含義發生了變化。
②寫帶參宏的時,不要吝嗇括號
#define S(a,b) a*b*10 View Code其實這個帶參宏是有缺點的,如果參數寫成如下形式的話,替換后結果可能就完全背離了你的本意。
S(x+1, y+2) —> x+1*y+2*10
對于預編譯器來說,它再處理宏定義時,它并不知道你的使用意圖是什么,它只會忠實的進行替換工作,但是替換之后是否能夠達到你要的效果,這個就不一定了。怎么解決?
為了避免這種情況,大家在定義帶參宏時不要吝嗇括號。
#define S(a,b) ((a)*(b)*10) //為了保險起見,對整個宏體最好也加一個()。 View CodeS(x+1, y+2) ——> ((x+1)*(y+2)*10)
帶參宏 與 函數
這兩個玩意兒長得很像,但實際上是兩個完全不同的東西。
例子
#include <stdio.h> #define S(a,b) a*b*10 void s(int a, int b) {return a*b*10; }int main(void) {int va1, va2;va1 = S(3, 2); //引用帶參宏va2 = s(3, 2); //調用函數printf("va1 = %d, va2 = %d\n", va1, va2);return 0; } View Code僅僅從調用來看,這兩個東西確實長得很像,如果將宏也定義為小寫的話,僅看調用的話,很難看出這個到底誰是函數誰是宏定義。為了能夠讓大家快速的區分帶參宏和函數,大家在定義宏的時候,宏名一定要大寫,否則在閱讀代碼時,很容易與函數搞混,非常不利于代碼的閱讀和理解。
二者的區別
二者是有著本質區別的:
帶參宏
處理階段:預編譯
宏只是一個供我們程序員識別的一個符號,一旦預編譯之后帶參宏就會消失了,取而代之的是宏體。
參數列表
帶參宏的形參是沒有類型的,我們使用int 、float等類型只有一個目的,就是使用類型來開辟一個變量空間,變量空間的字節數和存儲格式是由類型來決定的,所以定義變量時必須要有類型說明。而帶參宏的參數僅僅只起到替換說明的作用,不需要開辟空間來存放實參的值,既然不需要開辟空間,那就不需要類型的說明。
函數
處理階段:由編譯、匯編、鏈接階段處理
在“預處理階段”是不會處理函數這個東西的,在預處理前后,函數沒有任何變化。
函數是一個獨立體,有調用的過程
運行函數時涉及調用的過程:
調用時:從當前函數跳轉到被調用的函數,開辟形參和自動局部變量時,涉及壓棧操作。
調用結束:返回到調用函數,釋放函數的形參和自動局部變量的空間時,涉及彈棧操作
函數的參數列表
函數的形參是需要被開辟空間的,所以必須要要有類型說明。
宏的一些值得強調的地方
預處理完之后,宏定義和宏引用都會消失
#define NUM 100 //宏定義,預處理后消失 int main {int a; a = NUM; //宏引用,預處理后被替換為宏體,宏引用消失return 0; } View Code宏名的字母一般習慣大寫,以便與變量名、函數名相區別
如果宏名小寫的話,會很容易和正常的變量和函數混淆。
疑問:難道真的沒有小寫的宏嗎?
其實也不是,在少數某些特殊情況下,還真有定義為小寫的,但是這種情況比較少見。標準IO函數,有三個宏(stdio.h):
stdin:標準輸入(從鍵盤輸入數據)
stdout:標準輸出
stderr:標注出錯輸出
這三個宏其實就是小寫的,之所以寫成小寫,應該是歷史遺留問題。
所有預編譯的代碼都是獨占一行的(不能多行)
#define STUDENT struct student{int a; int b;};
為了獨占一行,我把結構體寫在了一行中,但是這樣子不方便理解,我們往往會把它改成如下形式
#define STUDENT struct student{\int a; \int b;\ }; View Code加了\(連行符)后,其實這幾行在同一行中。
宏的作用域 與 #undef
正常情況下的宏作用域為從定義為位置開始,一直到文件的末尾。如果你希望結束宏的作用域的話,可以使用#undef這個預編譯關鍵字。
#define NUM 100 int fun();int main(void) {int a = NUM;return 0; }#undef NUMint fun() {int a = NUM;//這里將無法使用這個宏 } View Code定義宏時可以嵌套引用其它的宏,但是不能嵌套引用自己
嵌套其它宏
#define WIDTH 80 #define LENGTH (WIDTH)+40 #define AREA WIDTH*(LENGTH)int main(void) {int a = AREA;return 0; } View Code這種形式很顯然是正確的。如下寫法也是正確的
#define AREA WIDTH*(LENGTH) #define WIDTH 80 #define LENGTH (WIDTH)+40int main(void) {int a = AREA; //WIDTH*(LENGTH) —>80*(LENGTH) —>>80*40return 0; } View Code這個寫法是正確的,只要宏引用的位置在定義位置的作用域范圍內就行。顯然AREA的引用都在AREA、WIDTH、LENGTH作用域內,所以AREA的引用在替換時,完全不存在任何問題。如下代碼AREA的引用不再LENGTH作用域內,預處理沒問題,但是編譯時回報未定義符號
#define AREA WIDTH*(LENGTH) #define WIDTH 80int main(void) {int a = AREA; WIDTH*(LENGTH) —>80*(LENGTH) —>>80*40return 0; } #define LENGTH (WIDTH)+40 View Code為什么不能嵌套自己
#define AREA AREA*10 int main(void) {int a = AREA;return 0; } View Code嵌套自己時在預編譯器做完替換后,最后還剩一個宏名,這個宏名無法再被替換,最后留給第二階段編譯時,將變成一個無法識別的符號,從而報錯。所以宏不能嵌套自己,這個和函數不一樣,函數嵌套調用自己是遞歸,宏嵌套引用自己就是犯錯。
只作字符替換,不做正確性檢查
預編譯器處理宏時,預編譯器只關心替換的事情,至于替換的宏體的寫法是否正確,預編譯器本身并不做檢查,因為判斷寫法是否正確的這件事情是由第二階段的編譯來做的。
#define NUM 100WEE int main(void) { int a = NUM;return 0; } View Code整形數100WEE的寫法完全是錯的,但是在預編譯時根本不會提示任何錯誤,預編譯器會忠實的將NUM換為100WEE,但是后續編譯時就會報無法識別100WEE的錯誤。
預定義宏
什么是預定義宏
預定義宏,也可以稱為編譯器內置宏,這個宏并沒有定義在哪個.h文件中,所以不能再哪個.h中找到這些玩意。進行預編譯時,當預編譯器看到這些玩意時,會自動處理這些預定義宏。其實將這些預定義宏稱為預編譯關鍵字,可能更好些。
作用
__DATE__:代表預處理的日期
當預處理器檢測到__DATE__后,會將其替換為"月 日 年"的字符串形式的時間,時間格式是西方人習慣的格式。
__FILE__:代表當前預編譯正在處理的那個源文件的文件名
當預處理器檢測到__FILE__后,會將其替換為"***.c"的文件名。
__LINE__:代表__LINE__當前所在行的行號
當預處理器檢測到__LINE__后,會將其替換為__LINE__當前所在行的行號(整形)。
__TIME__:代表對源文件進行預編譯時的時間
當預處理器檢測到__TIME__后,會將其替換為“hh:mm:ss”格式的時間。
__func__:當前__func__所在函數的函數名?
不過這個在預編譯階段不會被處理,而是留到編譯階段處理。
預定義宏的意義 與 調試宏
意義
常常用于調試打印、跟蹤代碼用。當一個程序寫大了后,在調試程序的功能性錯誤時,往往需要打印信息來跟蹤代碼,看看程序是運行到什么位置時才出現了功能性錯誤,以方便我們調試。
printf("%s %d %s\n", __FILE__, __LINE__, __func__); View Code調試宏
在每個需要打印的地方都寫printf會非常的麻煩,因此我們可以把它寫成調試宏。
#include <stdio.h>//調試宏,DEBUG的名字可以自己隨便起 #define DEBUG printf("%s %d %s\n", __FILE__, __LINE__, __func__);void exchange(int *p1, int *p2) { DEBUGint tmp = 0;DEBUG tmp = *p1;DEBUG*p1 = *p2;DEBUG*p2 = tmp;DEBUG }int main(void) { int a = 10; int b = 30; DEBUG exchange(&a, &b);DEBUGprintf("a=%d, b=%d\n", a, b);DEBUGreturn 0; } View Code通過打印信息來跟蹤程序,其實有些時候比“單步運行調試”更好用,因為單步運行調試在某些情況其實很麻煩,不如打印信息來的好使。
如果你想打印自定義信息的話,我們還可以將調試宏定義為帶參宏
#define DEBUG(s1, s2) printf(s1, s2);
疑問:感覺這么寫,也不比直接寫printf("%d\n", va)方便多少呀?
不直接使用printf,而是寫成DEBUG(s1, s2)帶參宏的形式,可以方便我們使用“條件編譯”來快速打開和關閉調試宏,后面將再介紹這個問題。
轉載于:https://www.cnblogs.com/kelamoyujuzhen/p/9417469.html
總結
- 上一篇: 陈一舟:我们花了大力气找合适团队接力人人
- 下一篇: 我对if(!this.IsPostBac