
C++ 程式在進行編譯時,會先檢查所有的前置處理器 (preprocessor) 指令,這些指令在實際編譯前會預先處理,例如引進標頭檔 (header file) 、定義巨集 (macro) 或常數 (constant) 等

前置處理器指令是以井字號開頭的,像是引進標準程式庫 (standard library) 的標頭檔
| #include <cstdlib> | |
| #include <ctime> |
或是引進自己定義的標頭檔
| #include "encrypt.h" |
#include 就是前置處理器指令。
定義巨集則是使用 #define 指令,基本上巨集就是簡單的文字替換,像是一行的程式碼就會直接替換到程式碼內,例如我們之前定義過 PrintInt() 函數 (function)
| 022 | // 印出參數 |
| 023 | void PrintInt(int a) { |
| 024 | cout << a << endl; |
| 025 | } |
完整的 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 如下
| 001 | // 從標準程式庫中引入 string |
| 002 | #include <string> |
| 003 | |
| 004 | // 定義兩個巨集常數 |
| 005 | #define DIFF 97 |
| 006 | #define NUM 26 |
| 007 | |
| 008 | // 使用 std 中的 string 名稱 |
| 009 | using std::string; |
| 010 | |
| 011 | // 宣告 Encrypt 類別 |
| 012 | class Encrypt { |
| 013 | public: |
| 014 | // 宣告建構函數 |
| 015 | Encrypt(); |
| 016 | // 宣告 setter 與 getter 成員函數 |
| 017 | void set_code_array(); |
| 018 | void set_code_array(string); |
| 019 | string get_code_array(); |
| 020 | // 宣告編碼、解碼的成員函數 |
| 021 | string ToEncode(string); |
| 022 | string ToDecode(string); |
| 023 | |
| 024 | private: |
| 025 | // 密碼表字串 |
| 026 | string code_array; |
| 027 | }; |
| 028 | |
| 029 | /* 檔名: encrypt.h |
| 030 | 作者: Kaiching Chang |
| 031 | 時間: 2014-5 */ |
這裡,我們將常數定義的前置處理器指令 #define 以橘色的語法高亮度標示
| 004 | // 定義兩個巨集常數 |
| 005 | #define DIFF 97 |
| 006 | #define NUM 26 |
對照實作如 ToDecode() ,有沒有感覺程式的語意更清楚了點呢?
| 055 | // 進行編碼工作的成員函數 |
| 056 | string Encrypt::ToEncode(string s) { |
| 057 | // 由參數字串取得字元的暫存變數 |
| 058 | char c; |
| 059 | // 暫存編碼結果的字串 |
| 060 | string r; |
| 061 | int i, m; |
| 062 | // 利用迴圈走完參數字串的所有字元 |
| 063 | for (i = 0; i < s.size(); i++) { |
| 064 | // 判斷該字元是否為英文小寫字母 |
| 065 | // 若是英文小寫字母就進行編碼轉換 |
| 066 | if (s.at(i) >= DIFF && s.at(i) < DIFF + N) { |
| 067 | c = s.at(i); |
| 068 | m = c - DIFF; |
| 069 | r += get_code_array().at(m); |
| 070 | } |
| 071 | else { |
| 072 | r += s.at(i); |
| 073 | } |
| 074 | } |
| 075 | |
| 076 | // 結束回傳編碼過的字串 |
| 077 | return r; |
| 078 | } |
完整的實作檔案可以參考 encrypt.cpp 。
我們簡單介紹了前置處理中的引進標頭檔、定義常數及巨集,其實前置處理還可以進行不少工作,例如避免重複引入或除錯等等,不過就目前的發展需求而言,就不詳細討論額外的題材了。
編密碼軟體的核心 Encrypt 類別 (class) 大體上發展完成,同時用了不少標準程式庫的內容,下面我們好好的介紹一下標準程式庫吧!
中英文術語對照
| 前置處理器 | preprocessor |
| 標頭檔 | header file |
| 巨集 | macro |
| 常數 | constant |
| 標準程式庫 | standard library |
| 函數 | function |
| 編譯器 | compiler |
| 前置處理 | preprocess |
| 函數 | function |
| 類別 | class |
重點整理
- 前置處理是編譯器在實際編譯前對程式檔案的處理工作,有引進標頭檔、巨集替換、條件編譯或除錯等。
- #include 為引進標頭檔的前置處理器指令。
- #define 為定義巨集的前置處理器指令。
問題與討論
- 為什麼要做前置處理?前置處理有什麼好處?
- 什麼是巨集?為什麼要設計巨集?
練習
- 承接上一個單元的猜數字遊戲,將類別宣告寫在 exercise2101.h 中,實作檔寫在 exercise2101.cpp 中,將原本的 main() 轉換成 Run() ,注意也要連帶實作 ArrayNumber() 及 ABCounter() 。
- 承上題,另寫一個程式 exercise2102.cpp ,裡頭含有 main() 函數,並建立 GuessGame 物件然後呼叫執行 Run() 。
the end
沒有留言:
張貼留言
0.留言請選擇註冊帳號, Google 或 OpenID 均可
1.歡迎留言交流,但不歡迎垃圾留言及廣告留言
2.文章相關問題歡迎提出,請減少情緒性留言
3.非文章相關內容,請到 G+ 社群或 FB 社團提出
4.問作業之留言會被直接刪除
5.莫忘網路禮節
6.可使用部份HTML標記,如 <b> 、 <i> 、 <a>
7.站長保留刪除留言的權力