早期的C 編譯器並沒有常數或inl ine 函式。隨著C 的快速發展,人們很快發現,C 需要處理常數、巨集及含括(include)檔。解決的方式是建立一個前置處理器(preprocessor),在將程式送到C 編譯器之前先予以處理。前置處理器算是一種特殊的文字編輯器。它的語法和C 完全不同,它也不需要了解C 語言的架構。它只是一種原始的文字編輯器.
#define 敘述 :
#define 敘述可用來定義常數。例如,下面兩行功能類似 :
- #define SIZE 20 // 陣列大小是20
- const int SIZE = 20; // 陣列大小是20
前置處理器是以行為單位,C++ 的敘述是用分號(;)來結束. 但前置處理器並不是用分號來結束,加入分號的話,會造成奇怪的結果. 前置處理器的指示可在行的尾端放入倒斜線(\)來連接下一行. 一般來說,你可以定義一個取代的巨集,例如 :
- #define FOO bar
Name 可以是任何合法的變數名稱,Substitute-Text 可以是任何內容,只要一行中可以放得下。Subs tit ute-Text 可包含空白字元、運算子和其它字元. 也可以使用下面的定義 :
用法如下 :
- /*
- * 清除陣列的內容
- */
- FOR_ALL {
- data[i] = 0;
- }
FOR_ALL 的定義. 若是濫用這種取代的做法來代替基本的程式架構,會造成反效果。例如,你可以定義 :
- #define BEGIN {
- #define END }
- . . .
- if (index == 0)
- BEGIN
- cout << "Starting\n";
- END
問題是在第1 行的#def ine 敘述,但錯誤訊息會指出第11 行的位置。第1 行的定義會使前置處理器將第11 行展開為 :
- while (index < 10 ** 10)
事實上上面的代碼經過前置處理後換變成下面代碼 :
- main() {
- cout << "The square of all the parts is " <<
- 7 + 5 * 7 + 5 << '\n'; // 而不是預期的 12 * 12
- return (0);
- }
在cons t 之前,#def i ne 是定義常數的唯一方式,因此,早期的程式碼都是使用#define。然而,const 比 #def ine 更理想。首先,C++ 會立即檢查const 敘述的語
法 ; 而#define 指示一直到使用巨集時才會檢查。const 是採用C++ 語法,#define 則有自己的語法;最後,const 適用C++ 變數的有效範圍法則,而用#define 所定義的常數會延伸到整個程式. 在大多數情況下,const 比#define 理想。底下有兩種個方式來定義同樣的常數 :
- #define MAX 10 // 使用前置處理器定義一個值
- // 這很容易出問題
- const int MAX = 10; // 定義一個C++ 的常數整數
- // ( 比較安全)
- struct box {
- int width, height; // 方塊的大小,單位為點
- };
- const box pink_box(1.0, 4.5); // 輸入粉紅色方塊的大小
條件式編譯 :
程式設計師往往必須面對跨平台的問題,就是讓程式在各種機器上執行。理論上,C++ 的可攜性很高;但許多機器具有特殊的架構, 例如 UNIX、MSDOS 及Windows 編譯器,表面上十分類似,但仍有差異存在. 前置處理器允許你透過條件式編譯(condi tional compilat ion),賦予你設計上的彈性. 假設你要在測試時將除錯用的程式碼放進來,然後在正式發行時予以刪除,就可以使用#ifdef-#endif 的方式 :
- #ifdef DEBUG
- cout << "In compute_hash, value " << value << " hash " << hash << "\n";
- #endif /* DEBUG */
- #define DEBUG /* Turn debugging on */
- #undef DEBUG /* Turn debugging off */
- #ifdef DEBUG
- cout << "Test version. Debugging is on\n";
- #else /* DEBUG */
- cout << "Production version\n";
- #endif /* DEBUG */
雖然程式中沒有#define DEBUG,但是編譯prog.c 程式會包含#ifdef DEBUG 到 #endif /* DEBUG */ 之間的程式. 一般形式的選項是-Dsymbol 或 -Dsymbol=value. 例如,下面會設定MAX 為10 :
大部份的C++ 編譯程式會自動定義一些與系統相關的符號. 像是Turbo C++ 定義 __TURBOC__,MS-DOS 則定義__MSDOS__ . ANSI 標準的C 編譯器定義了符號__STDC__. C++ 編譯器定義了__cplusplus . 大多數的UNIX 編譯器都定義了系統的名稱(如Sun、VAX、celer i ty 等等),但很少有文件提到這一點。unix 這個符
號,在所有的UNIX 機器上都有定義.
#include 檔 :
#include 指示允許程式使用另一個程式的原始碼. 例如,程式中使用了這個指示 :
- #include
標準包含檔(incl ude f i le)是用來定義程式庫中函式的資料結構和巨集. 例如,cout 是一個標準類別,可將資料印到到標準輸出裝置. cout 所用ostream 的類別定義和相關程序,都定義在iost ream.h. 另外你可用雙引號括住檔案名稱,來指定含括檔 :
- #include "defs.h"
- #include "data.h"
- #include "io.h"
- #ifndef _CONST_H_INCLUDED_
- /* 定義常數*/
- #define _CONST_H_INCLUDED_
- #endif /* _CONST_H_INCLUDED_ */
參數化的巨集 :
目前為止,討論的只有簡單的#def ine 和巨集. 巨集也可以接受參數,下面的巨集是計算一個數字的平方 :
- #define SQR(x) ((x) * (x)) // 數值的平方計算
上面程式的輸出結果是什麼?(請在你的機器上測試)為什麼會是這個結果?
# 運算子 :
# 運算子用在參數化的巨集中,可以將引數變成字串. 例如 :
- #define STR(data) #data
- STR(hello)
參數化的巨集與inline 函式 :
大部份的情形下,最好使用inl ine 函式而不要用參數化的巨集. 要避免參數化巨集所造成的問題,最好是透過inline 函式. 例如,SQR 巨集可同時處理f loat 及int 兩種資料型態,但你必須寫兩個inline 函式,才能達到同樣的功能 :
- #define SQR(x) ((x) * (x)) // 參數化的巨集
- // 可以處理,但是會有問題
- // 相同作用的inline 函式
- inline int sqr(const int x) {
- return (x * x);
- }
本書沒有包含C++ 前置處理器指示的完整列表. 它包括#if 某些進階的特性,可以處理條件式的編譯 ; 以及#pragma 指示,將特定編譯器的命令放進檔案裡. 關於這些功能的詳細說明,請參考C++ 的技術文件.
補充說明 :
* [C++ 小學堂] pragma comment 的使用
* [C++ 小學堂] 關鍵字 inline 介紹
* [C++ 文章收集] C++中 #define的用法
沒有留言:
張貼留言