剛接觸到MFC編程的人往往會被MFC 向導生成的各種宏定義和預處理指令所嚇倒,但是預處理和宏定義又是C語言的一個強大工具。使用它們可以進行簡單的源代碼控制,版本控制,預警或者完成一些特殊的功能。
一個經典的例子
使用預處理與宏定義最經典的例子莫過於加在一個頭文件中以避免頭文件被兩次編譯。試想這種的情況,有一個文件headerfile.h 它被包含在headerfile1.h中,同時在headerfile2.h 中也被包含了,現在有一個CPP文件,implement.cpp 包含了headerfile1.h 和headerfile2.h:
#include “headerfile1.h”
#include “headerfile2.h”
假設headerfile.h 中定義了一個全局變量 iglobal 。
int iglobal;
在編譯的時候編譯器兩次編譯headerfile,也就會發現iglobal被定義了兩次,這時就會發生變量重定義的編譯錯誤。
傳統的解決辦法是使用#ifdef 以及#endif 來避免頭文件的重復編譯,在上面的例子中,只需要加上這麼幾行:
#ifndef smartnose_2002_6_21_headerfile_h
#define smartnose_2002_6_21_headerfile_h
int iglobal;
#endif
仔細的考慮上面的宏定義,會發現當編譯器編譯過一次headerfile.h以後,smartnose_2002_6_21_headerfile_h 這個宏就被定義了,以後對headerfile.h的編譯都會跳過int iglobal 這一行。當然smartnose_2002_6_21_headerfile_h 這個宏是可以任意定義的,但是這個宏本身不能和其它文件中定義的宏重復,所以MFC在自動生成的文件中總是使用一個隨機產生的長度非常長的宏,但我覺得這沒有必要,我建議在這個宏中加入一些有意義的信息,比方作者,文件名,文件創建時間等等,因為我們有時候會忘記在注釋中加入這些信息。
在VC.Net 中我們不會再看見這些宏定義了,因為在這裡會普遍使用一個預處理指令:
#pragma once
只要在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次,這條指令實際上在VC6中就已經有了,但是考慮到兼容性並沒有太多的使用它。
源代碼版本控制
當我們為許多平台開發多個版本的時候預編譯指令和宏定義也能夠幫我們的忙。假設我們現在為Windows 和Linux開發了一套軟件,由於這兩種系統的不同,我們不得不在程序控制源代碼的版本。比方內存的分配,我們可以在Linux上使用標准C的malloc 函數,但是我們希望在 Windows上使用HeapAlloc API。下面的代碼演示了這種情況:
main()
{
………………..
#ifdef _Windows_PLATFORM
HeapAlloc(5);
#else
malloc(5);
#endif
………………..
}
當我們在WINDOWS 平台上編譯此程序的時候,只需要定義_Windows_PLATFORM這個宏,那麼HeapAlloc這條語句就能夠起作用了。這樣就能夠讓我們在同一個文件中為不同的平台實現不同版本的代碼,同時保持程序的良好結構。在許多情況下,我們還可以為一個方法使用不同的算法,然後用宏定義來針對不同的情況選擇其中的一個進行編譯。這在MFC應用程序中是使用得最多的。最明顯的就是文件中經常存在的
#ifdef _DEBUG
…………………….some code………..
#endif
這樣的代碼,這些代碼在應用程序的調試版(DEBUG)中會發揮其作用。
#Pragma 指令
在所有的預處理指令中,#Pragma 指令可能是最復雜的了,它的作用是設定編譯器的狀態或者是指示編譯器完成一些特定的動作。其格式一般為
#Pragma Para
其中Para 為參數,下面來看一些常用的參數。
message 參數。 Message 參數是我最喜歡的一個參數,它能夠在編譯信息輸出窗口中輸出相應的信息,這對於源代碼信息的控制是非常重要的。其使用方法為:
#Pragma message(“消息文本”)
當編譯器遇到這條指令時就在編譯輸出窗口中將消息文本打印出來。
當我們在程序中定義了許多宏來控制源代碼版本的時候,我們自己有可能都會忘記有沒有正確的設置這些宏,此時我們可以用這條指令在編譯的時候就進行檢查。假設我們希望判斷自己有沒有在源代碼的什麼地方定義了_X86這個宏可以用下面的方法
#ifdef _X86
#Pragma message(“_X86 Macro activated!”)
#endif
當我們定義了_X86這個宏以後,應用程序在編譯時就會在編譯輸出窗口裡顯示“_X86 Macro activated!”。我們就不會因為不記得自己定義的一些特定的宏而抓耳撓腮了。
另一個使用得比較多的pragma參數是code_seg。格式如:
#pragma code_seg( ["section-name"[,"section-class"] ] )
它能夠設置程序中函數代碼存放的代碼段,當我們開發驅動程序的時候就會使用到它。
最後一個比較常用的就是上面所說的#pragma once 指令了。
VC預定義的宏
在VC中有一類宏並不是由用戶用#define語句定義的,而是編譯器本身就能夠識別它們。這些宏的作用也是相當大的。讓我們來看第一個,也是MFC中使用得最頻繁的一個:__FILE__ 。
當編譯器遇到這個宏時就把它展開成當前被編譯文件的文件名。好了,我們馬上就可以想到可以用它來做什麼,當應用程序發生錯誤時,我們可以報告這個錯誤發生的程序代碼在哪個文件裡,比方在文件test.cpp中有這樣的代碼:
try
{
char * p=new(char[10]);
}
catch(CException *e )
{
TRACE(“ there is an error in file: %s ”,__FILE__);
}
在程序運行的時候,如果內存分配出現了錯誤,那麼在調試窗口中會出現there is an error in file: test.cpp 這句話,當然,我們還可以把這個錯誤信息顯示在別的地方。
如果我們還能夠記錄錯誤發生在哪一行就好了,幸運的是,與__FILE__宏定義一樣,還有一個宏記錄了當前代碼所在的行數,這個宏是__LINE__。使用上面的兩個宏,我們可以寫出一個類似於VC提供的ASSERT語句。下面是方法
#define MyAssert(x)
if(!(x))
MessageBox(__FILE__,__LINE__,NULL,MB_OK);
我們在應用程序中可以象使用ASSERT語句一樣使用它,在錯誤發生時,它會彈出一個對話框,其標題和內容告訴了我們錯誤發生的文件和代碼行號,方便我們的調試,這對於不能使用ASSERT語句的項目來說是非常有用的。
除了這兩個宏以外,還有記錄編譯時間的__TIME__,記錄日期的__DATE__,以及記錄文件修改時間的__TIMESTAMP__宏。
使用這些預定義的宏,我們幾乎可以生成和VC能夠生成的一樣完整的源代碼信息報表。
結論
翻開MFC和Linux的源代碼,宏定義幾乎占據了半邊天,消息映射,隊列操作,平台移植,版本管理,甚至內核模塊的拆卸安裝都用宏定義完成。毫不誇張的說,有些文件甚至就只能看見宏定義。所以學習宏定義,熟練的使用宏定義對於學習C語言乃至VC都是非常關鍵的。