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