常見的預處理指令:
- #include 包含一個源代碼檔案
- #define 定義宏
- #undef 取消已定義的宏
- #if 如果給定條件為真,則編譯下面代碼
- #ifdef 如果宏已經定義,則編譯下面代碼
- #ifndef 如果宏沒有定義,則編譯下面代碼
- #elif 如果前面的#if給定條件不為真,目前條件為真,則編譯下面代碼
- #endif 結束一個#if……#else條件編譯塊
- #error 停止編譯并顯示錯誤資訊
10.編譯C++程式時,編譯器自動定義了一個預處理器名字__cplusplus(注意前面有兩個下劃線),是以可以根據這個來判斷該程式是否是C++程式,以便有條件地包含一些代碼,如:
#ifndef MYHEAD_H#define MYHEAD_H#ifdef __cplusplus //若是C++程式,則需要包含C的庫extern "C" {#endifint DMpostprocessing();#ifdef __cplusplus}#endif#endif
11.在編譯C程式時,編譯器會自動定義預處理常量__STDC__。當然__cplusplus和__STDC__ 不會同時被定義;
12.另外兩個比較有用的預定義常量是__LINE__(記錄檔案已經被編譯的行數)和__FILE__(包含正在被編譯的檔案名稱)。使用如下:
if(element_count==0) cerr<
13. __DATE__:編譯日期,目前被編譯檔案的編譯日期
14. __TIME__:編譯時間,目前被編譯檔案的編譯時間
什麼是預處理指令?
預處理指令是以#号開頭的代碼行。#号必須是該行除了任何空白字元外的第一個字元。#後是指令關鍵字,在關鍵字和#号之間允許存在任意個數的空白字元。整行語句構成了一條預處理指令,該指令将在編譯器進行編譯之前對源代碼做某些轉換。
以前沒有在意的學者注意了,預處理指令是在編譯器進行編譯之前進行的操作.預處理過程掃描源代碼,對其進行初步的轉換,産生新的源代碼提供給編譯器。可見預處理過程先于編譯器對源代碼進行處理。在很多程式設計語言中,并沒有任何内在的機制來完成如下一些功能:在編譯時包含其他源檔案、定義宏、根據條件決定編譯時是否包含某些代碼(防止重複包含某些檔案)。要完成這些工作,就需要使用預處理程式。盡管在目前絕大多數編譯器都包含了預處理程式,但通常認為它們是獨立于編譯器的。預處理過程讀入源代碼,檢查包含預處理指令的語句和宏定義,并對源代碼進行響應的轉換。預處理過程還會删除程式中的注釋和多餘的空白字元。
#include包含一個源代碼檔案
這個預處理指令,我想是見得最多的一個,簡單說一下,第一種方法是用尖括号把頭檔案括起來。這種格式告訴預處理程式在編譯器自帶的或外部庫的頭檔案中搜尋被包含的頭檔案。第二種方法是用雙引号把頭檔案括起來。這種格式告訴預處理程式在目前被編譯的應用程式的源代碼檔案中搜尋被包含的頭檔案,如果找不到,再搜尋編譯器自帶的頭檔案。采用兩種不同包含格式的理由在于,編譯器是安裝在公共子目錄下的,而被編譯的應用程式是在它們自己的私有子目錄下的。一個應用程式既包含編譯器提供的公共頭檔案,也包含自定義的私有頭檔案。采用兩種不同的包含格式使得編譯器能夠在很多頭檔案中差別出一組公共的頭檔案。
頭檔案中應該寫什麼
通過上面的讨論,我們可以了解到,頭檔案的作用就是被其他的 .cpp 包含進去的。它們本身并不參與編譯,但實際上,它們的内容卻在多個 .cpp 檔案中得到了編譯。通過"定義隻能有一次"的規則,我們很容易可以得出,頭檔案中應該隻放變量和函數的聲明,而不能放它們的定義。因為一個頭檔案的内容實際上是會被引入到多個不同的 .cpp 檔案中的,并且它們都會被編譯。放聲明當然沒事,如果放了定義,那麼也就相當于在多個檔案中出現了對于一個符号(變量或函數)的定義,縱然這些定義都是相同的,但對于編譯器來說,這樣做不合法。
是以,應該記住的一點就是,.h頭檔案中,隻能存在變量或者函數的聲明,而不要放定義。即,隻能在頭檔案中寫形如:extern int a; 和 void f(); 的句子。這些才是聲明。如果寫上 int a;或者 void f() {} 這樣的句子,那麼一旦這個頭檔案被兩個或兩個以上的 .cpp 檔案包含的話,編譯器會立馬報錯。(關于 extern,前面有讨論過,這裡不再讨論定義跟聲明的差別了。)
但是,這個規則是有三個例外的:
- 一,頭檔案中可以寫 const 對象的定義。因為全局的 const 對象預設是沒有 extern 的聲明的,是以它隻在目前檔案中有效。把這樣的對象寫進頭檔案中,即使它被包含到其他多個 .cpp 檔案中,這個對象也都隻在包含它的那個檔案中有效,對其他檔案來說是不可見的,是以便不會導緻多重定義。同時,因為這些 .cpp 檔案中的該對象都是從一個頭檔案中包含進去的,這樣也就保證了這些 .cpp 檔案中的這個 const 對象的值是相同的,可謂一舉兩得。同理,static 對象的定義也可以放進頭檔案。
- 二,頭檔案中可以寫内聯函數(inline)的定義。因為inline函數是需要編譯器在遇到它的地方根據它的定義把它内聯展開的,而并非是普通函數那樣可以先聲明再連結的(内聯函數不會連結),是以編譯器就需要在編譯時看到内聯函數的完整定義才行。如果内聯函數像普通函數一樣隻能定義一次的話,這事兒就難辦了。因為在一個檔案中還好,我可以把内聯函數的定義寫在最開始,這樣可以保證後面使用的時候都可以見到定義;但是,如果我在其他的檔案中還使用到了這個函數那怎麼辦呢?這幾乎沒什麼太好的解決辦法,是以 C++ 規定,内聯函數可以在程式中定義多次,隻要内聯函數在一個 .cpp 檔案中隻出現一次,并且在所有的 .cpp 檔案中,這個内聯函數的定義是一樣的,就能通過編譯。那麼顯然,把内聯函數的定義放進一個頭檔案中是非常明智的做法。
- 三,頭檔案中可以寫類(class)的定義。因為在程式中建立一個類的對象時,編譯器隻有在這個類的定義完全可見的情況下,才能知道這個類的對象應該如何布局,是以,關于類的定義的要求,跟内聯函數是基本一樣的。是以把類的定義放進頭檔案,在使用到這個類的 .cpp 檔案中去包含這個頭檔案,是一個很好的做法。在這裡,值得一提的是,類的定義中包含着資料成員和函數成員。資料成員是要等到具體的對象被建立時才會被定義(配置設定空間),但函數成員卻是需要在一開始就被定義的,這也就是我們通常所說的類的實作。一般,我們的做法是,把類的定義放在頭檔案中,而把函數成員的實作代碼放在一個 .cpp 檔案中。這是可以的,也是很好的辦法。不過,還有另一種辦法。那就是直接把函數成員的實作代碼也寫進類定義裡面。在 C++ 的類中,如果函數成員在類的定義體中被定義,那麼編譯器會視這個函數為内聯的。是以,把函數成員的定義寫進類定義體,一起放進頭檔案中,是合法的。注意一下,如果把函數成員的定義寫在類定義的頭檔案中,而沒有寫進類定義中,這是不合法的,因為這個函數成員此時就不是内聯的了。一旦頭檔案被兩個或兩個以上的 .cpp 檔案包含,這個函數成員就被重定義了。
頭檔案中的保護措施
考慮一下,如果頭檔案中隻包含聲明語句的話,它被同一個 .cpp 檔案包含再多次都沒問題——因為聲明語句的出現是不受限制的。然而,上面讨論到的頭檔案中的三個例外也是頭檔案很常用的一個用處。那麼,一旦一個頭檔案中出現了上面三個例外中的任何一個,它再被一個 .cpp 包含多次的話,問題就大了。因為這三個例外中的文法元素雖然"可以定義在多個源檔案中",但是"在一個源檔案中隻能出現一次"。設想一下,如果 a.h 中含有類 A 的定義,b.h 中含有類 B 的定義,由于類B的定義依賴了��� A,是以 b.h 中也 #include了a.h。現在有一個源檔案,它同時用到了類A和類B,于是程式員在這個源檔案中既把 a.h 包含進來了,也把 b.h 包含進來了。這時,問題就來了:類A的定義在這個源檔案中出現了兩次!于是整個程式就不能通過編譯了。你也許會認為這是程式員的失誤——他應該知道 b.h 包含了 a.h ——但事實上他不應該知道。
使用 "#define" 配合條件編譯可以很好地解決這個問題。在一個頭檔案中,通過 #define 定義一個名字,并且通過條件編譯 #ifndef...#endif 使得編譯器可以根據這個名字是否被定義,再決定要不要繼續編譯該頭文中後續的内容。這個方法雖然簡單,但是寫頭檔案時一定記得寫進去。
#define定義宏
有關#define這個宏定義,在C語言中使用的很多,因為#define存在一些不足,C++強調使用const來定義常量。宏定義了一個代表特定内容的辨別符。預處理過程會把源代碼中出現的宏辨別符替換成宏定義時的值。記住僅僅是進行辨別符的替換。下面列舉一些#define的使用:
- 用#define實作求最大值和最小值的宏
#include #define MAX(x,y) (((x)>(y))?(x):(y))#define MIN(x,y) (((x)