C++ 程式在進行編譯時,會先檢查所有的前置處理器 (preprocessor) 指令,這些指令在實際編譯前會預先處理,例如引進標頭檔 (header file) 、定義巨集 (macro) 或常數 (constant) 等
↓
開始實際編譯工作
前置處理器指令是以井字號開頭的,像是引進標準程式庫 (standard library) 的標頭檔
#include <cstdlib>
#include <ctime>
或是引進自己定義的標頭檔
#include "encrypt.h"
#include 就是前置處理器指令。
定義巨集則是使用 #define 指令,基本上巨集就是簡單的文字替換,像是一行的程式碼就會直接替換到程式碼內,例如我們之前定義過 PrintInt() 函數 (function)
// 印出參數
void PrintInt(int a) {
cout << a << endl;
}
完整的 PrintInt() 定義,請參考單元 9 - 函數。
像 PrintInt() 只有一行、簡單的程式碼,就可以改成巨集,例如
// 定義巨集
#define PrintInt(a) cout << a << endl
為什麼要定義巨集呢?因為編譯器 (compiler) 在實際編譯成執行檔之前,會先進行前置處理 (preprocess) ,這個處理就是直接把程式中有 PrintInt(a) 換成 cout << a << endl ,好處就是少掉額外進行的呼叫函數 (function) 。
呼叫函數的缺點在我們目前的小程式看不出來,倒是可以簡單說,呼叫函數就是要挪出處理器的時間,把函數的程式放到記憶體空間,然後才去執行,整個過程可能只需要幾微秒而已,可是當呼叫的次數越多,累積消耗的時間、空間也就越多囉!
節省時間、空間,就可以讓程式跑得更順,不是嗎?
我們也可以用 #define 指令定義巨集常數,像是 Encrypt 類別中反覆出現的 97 、 26 等數字都應該要定義成常數,最主要的原因不只是這些都是固定的數字,更重要的,假如有一天我們需要對不同的語言文字編碼,雖然很可能只是修改一下 Encrypt 類別,但要把每個 97 及 26 通通找出來修改會是件很麻煩的事情,只在一個地方修改的話,相對就更容易維護程式碼。
常數通常用大寫字母定義在標頭檔裡,我們將 97 定義成 DIFF ,而 26 定義成 NUM ,完整的 encrypt.h 如下
// 從標準程式庫中引入 string
#include <string>
// 使用 std 中的 string 名稱
using std::string;
// 定義兩個巨集常數
#define DIFF 97
#define N 26
// 宣告 Encrypt 類別
class Encrypt {
public:
// 宣告建構函數
Encrypt();
// 宣告 setter 與 getter 成員函數
void set_code_array();
void set_code_array(string);
string get_code_array();
// 宣告編碼、解碼的成員函數
string ToEncode(string);
string ToDecode(string);
private:
// 密碼表字串
string code_array;
};
/* 《程式語言教學誌》的範例程式
http://kaiching.org/
檔名:encrypt.h
功能:Encrypt 類別的標頭檔
作者:張凱慶 */
這裡,我們將常數定義的前置處理器指令 #define 以橘色的語法高亮度標示
// 定義兩個巨集常數
#define DIFF 97
#define N 26
對照實作如 ToDecode() ,有沒有感覺程式的語意更清楚了點呢?
// 進行解碼工作的成員函數
string Encrypt::ToDecode(string s) {
// 暫存解碼結果的字串
string r;
int i, j;
// 第一層迴圈逐一取得每一個字元
for (i = 0; i < s.size(); i++) {
// 判斷該字元是否為英文小寫字母,若是英文小寫字母就進行解碼轉換
if (s.at(i) >= DIFF && s.at(i) < DIFF + N) {
// 第二層迴圈尋找該字元在密碼表中的索引值
// 該索引值加上 DIFF 就可轉換回原本的字元
for (j = 0; j < N; j++) {
if (s.at(i) == get_code_array().at(j)) {
r += (char) j + DIFF;
break;
}
}
}
else {
r += s.at(i);
}
}
// 結束回傳解碼過的字串
return r;
}
完整的實作檔案可以參考 encrypt.cpp 。
我們簡單介紹了前置處理中的引進標頭檔、定義常數及巨集,其實前置處理還可以進行不少工作,例如避免重複引入或除錯等等,不過就目前的發展需求而言,就不詳細討論額外的題材了。
編密碼軟體的核心 Encrypt 類別 (class) 大體上發展完成,同時用了不少標準程式庫的內容,下面我們好好的介紹一下標準程式庫吧!
相關教學影片
中英文術語對照 | |
---|---|
前置處理器 | preprocessor |
標頭檔 | header file |
巨集 | macro |
常數 | constant |
標準程式庫 | standard library |
函數 | function |
編譯器 | compiler |
前置處理 | preprocess |
函數 | function |
類別 | class |
重點整理 |
---|
1. 前置處理是編譯器在實際編譯前對程式檔案的處理工作,有引進標頭檔、巨集替換、條件編譯或除錯等。 |
2. #include 為引進標頭檔的前置處理器指令。 |
3. #define 為定義巨集的前置處理器指令。 |
問題與討論 |
---|
1. 為什麼要做前置處理?前置處理有什麼好處? |
2. 什麼是巨集?為什麼要設計巨集? |
練習 |
---|
1. 承接上一個單元的猜數字遊戲,將類別宣告寫在 exercise2101.h 中,實作檔寫在 exercise2101.cpp 中,將原本的 main() 轉換成 Run() ,注意也要連帶實作 ArrayNumber() 及 ABCounter() 。 |
2. 承上題,另寫一個程式 exercise2102.cpp ,裡頭含有 main() 函數,並建立 GuessGame 物件然後呼叫執行 Run() 。 |