C++ 入門指南 4.01
單元 21 - 前置處理
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) 在實際編譯成執行檔之前,會先進行前置處理,這個處理就是直接把程式 (program) 中有 PrintInt(a) 換成 cout << a << endl ,好處就是少掉額外進行的呼叫函數。
呼叫函數的缺點在我們目前的小程式看不出來,倒是可以簡單說,呼叫函數就是要挪出處理器的時間,把函數的程式放到記憶體空間,然後才去執行,整個過程可能只需要幾微秒而已,可是當呼叫的次數越多,累積消耗的時間、空間也就越多囉!
節省時間、空間,就可以讓程式跑得更順,不是嗎?
我們也可以用 #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 類別的標頭檔 作者:張凱慶 */
對照實作如 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.cxx 。
我們簡單介紹了前置處理中的引進標頭檔、定義常數及巨集,其實前置處理還可以進行不少工作,例如避免重複引入或除錯等等,不過就目前的發展需求而言,就不詳細討論額外的題材了。
編密碼軟體的核心 Encrypt 類別 (class) 大體上發展完成,同時用了不少標準程式庫的內容,下面我們好好的介紹一下標準程式庫吧!
中英文術語對照 | |
---|---|
類別 | class |
編譯器 | compiler |
常數 | constant |
函數 | function |
標頭檔 | header file |
巨集 | macro |
前置處理器 | preprocessor |
程式 | program |
標準程式庫 | standard library |
重點整理 |
---|
1. 前置處理是編譯器在實際編譯前對程式檔案的處理工作,有引進標頭檔、巨集替換、條件編譯或除錯等。 |
2. #include 為引進標頭檔的前置處理器指令。 |
3. #define 為定義巨集的前置處理器指令。 |
問題與討論 |
---|
1. 為什麼要做前置處理?前置處理有什麼好處? |
2. 什麼是巨集?為什麼要設計巨集? |
練習 |
---|
1. 標準程式庫中的 deque 是雙端佇列的集合體型態,可以在頭尾進行資料操作,字面常數一樣是用大括弧,寫一個程式 exercise2101.cxx ,用字面常數建立一個整數的 deque ,然後用 push_front() 在頭插入整數, push_back() 在尾插入另一個整數,最後印出所有元素內容。 參考程式碼 |
2. C++11 新增了 unordered_map ,這是未排序的配對型集合體型態,使用方式與 map 類似,寫一個程式 exercise2102.cxx ,宣告建立一個 unordered_map ,然後印出元素內容。 參考程式碼 |
3. C++11 新增了 unordered_set ,這是未排序的配對型集合體型態,使用方式與 set 類似,寫一個程式 exercise2103.cxx ,宣告建立一個 unordered_set ,然後印出元素內容。 參考程式碼 |
4. 標準程式庫中的 priority_queue 是預設以由大到小排序的集合體型態,利用 push() 可以加入資料,寫一個程式 exercise2104.cxx ,宣告建立一個整數的 priority_queue ,利用 push() 加入數個不同整數,然後印出元素內容。 參考程式碼 |
5. 標準程式庫中的 list 是雙向鏈結串列的集合體型態,字面常數一樣是用大括弧,寫一個程式 exercise2105.cxx ,用字面常數建立一個整數的 list ,然後用 find() 查找元素, insert() 插入元素, remove() 移除元素,最後印出操作過後的所有元素內容。 參考程式碼 |
6. 標準程式庫中 cmath 的 sin() 用以計算正弦函數,寫一個程式 exercise2106.cxx ,利用 sin() 計算正弦值。 參考程式碼 |
7. 標準程式庫中 cmath 的 cos() 用以計算餘弦函數,寫一個程式 exercise2107.cxx ,利用 cos() 計算餘弦值。 參考程式碼 |
8. 標準程式庫中 cmath 的 tan() 用以計算正切函數,寫一個程式 exercise2108.cxx ,利用 tan() 計算正切值。 參考程式碼 |
9. 標準程式庫中 cmath 的 floor() 、 ceil() 、 round() 用以計算最接近整數,寫一個程式 exercise2109.cxx ,比較三者差異。 參考程式碼 |
10. 標準程式庫中 cmath 的 isgreater() 、 isgreaterequal() 、 isless() 、 islessequal() 、 islessgreater() 用以比較兩個整數參數之間的關係,用法如函數識別字的字面意義,寫一個程式 exercise2110.cxx ,比較五者差異。 參考程式碼 |
相關教學影片