
解碼 (decoding) 需要用到與編碼相同的轉換表格

所謂的解碼也就是將編碼 (encoding) 過的小寫英文字母回覆成原來的英文小寫字母,由上圖可以看出,我們儲存表格所用的陣列 (array) ,恰巧依索引值 (index) 可推回原來的英文小寫字母,這是說,索引值 0 為 'q' ,所以是將原本的 'a' 變成 'q' ,因此,解碼就是依索引值重新加上 diff 即可。
實際上我們需要用到巢狀迴圈 (nested loop) ,也就是在迴圈 (loop) 中有其他的迴圈,對單一英文句子而言,我們需要一個迴圈判斷每個字元是否為英文小寫字母,若是英文小寫字母,我們就需要另一個迴圈找出對應的索引值。由這樣的概念設計的解碼成員函數 (member function) ToDecode() ,如下
| 080 | // 進行解碼工作的成員函數 |
| 081 | string Encrypt::ToDecode(string s) { |
| 082 | // 暫存解碼結果的字串 |
| 083 | string r; |
| 084 | int i, j; |
| 085 | // 第一層迴圈逐一取得每一個字元 |
| 086 | for (i = 0; i < s.size(); i++) { |
| 087 | // 判斷該字元是否為英文小寫字母 |
| 088 | // 若是英文小寫字母就進行解碼轉換 |
| 089 | if (s.at(i) >= DIFF && s.at(i) < DIFF + N) { |
| 090 | // 第二層迴圈尋找該字元在密碼表中的索引值 |
| 091 | // 該索引值加上 DIFF 就可轉換回原本的字元 |
| 092 | for (j = 0; j < N; j++) { |
| 093 | if (s.at(i) == get_code_array().at(j)) { |
| 094 | r += (char) j + DIFF; |
| 095 | break; |
| 096 | } |
| 097 | } |
| 098 | } |
| 099 | else { |
| 100 | r += s.at(i); |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | // 結束回傳解碼過的字串 |
| 105 | return r; |
| 106 | } |
巢狀迴圈是迴圈中包含另一個迴圈,由於我們利用縮排的方式編輯程式碼,看起來內層迴圈像是凹陷進去的巢,故稱之為巢狀迴圈。
ToDecode() 與 ToEncode() 相似,同樣需要一個字串 (string) 當參數 (parameter) ,結果也回傳一個字串。
解碼轉換由巢狀迴圈的部份來進行
| 085 | // 第一層迴圈逐一取得每一個字元 |
| 086 | for (i = 0; i < s.size(); i++) { |
| 087 | // 判斷該字元是否為英文小寫字母 |
| 088 | // 若是英文小寫字母就進行解碼轉換 |
| 089 | if (s.at(i) >= DIFF && s.at(i) < DIFF + N) { |
| 090 | // 第二層迴圈尋找該字元在密碼表中的索引值 |
| 091 | // 該索引值加上 DIFF 就可轉換回原本的字元 |
| 092 | for (j = 0; j < N; j++) { |
| 093 | if (s.at(i) == get_code_array().at(j)) { |
| 094 | r += (char) j + DIFF; |
| 095 | break; |
| 096 | } |
| 097 | } |
| 098 | } |
| 099 | else { |
| 100 | r += s.at(i); |
| 101 | } |
| 102 | } |
第一層迴圈,也就是外層迴圈會依序取得英文句子的每個字元,然後判斷是否為英文小寫字母,如果該字元是英文小寫字母,就會啟動另一個迴圈找出表格中對應的索引值出來,相反地,如果該字元不是英文小寫字母,控制變數 i 就會自動遞增,然後判斷下一個字元。
注意,這個巢狀迴圈用了兩個 for 、兩個 if ,依順序 for 、 if 、 for 、 if ,這是我們打算讓程式執行的順序,若是沒有依照這樣的順序,程式可能會跑出無法預期的結果。
這裡只增加一個 ToDecode() ,完整的實作檔案可以參考「範例程式碼」的 encrypt.cpp 。
Encrypt 類別到這邊大致已開發完成,我們用下面的 encrypt_demo.cpp 來測試囉
| 001 | // 引入標準程式庫中的 iostream |
| 002 | #include <iostream> |
| 003 | |
| 004 | // 使用 std 中的兩個名稱 |
| 005 | using std::cout; // 標準輸出串流的物件 |
| 006 | using std::endl; // 新行符號,等於 '\n' |
| 007 | |
| 008 | // 引入 Encrypt 類別的標頭檔 |
| 009 | #include "encrypt.h" |
| 010 | |
| 011 | // 程式執行的 main() 函數 |
| 012 | int main() { |
| 013 | // 印出空白一行 |
| 014 | cout << endl; |
| 015 | // 建立 Encrypt 物件 |
| 016 | Encrypt encryptor; |
| 017 | // 印出密碼表字串 |
| 018 | cout << encryptor.get_code_array() |
| 019 | << endl << endl ; |
| 020 | |
| 021 | // 印出準備編碼的字串 |
| 022 | string d = "There is no spoon."; |
| 023 | cout << d << endl; |
| 024 | |
| 025 | // 將字串編碼,然後印出編碼結果 |
| 026 | string r1 = encryptor.ToEncode(d); |
| 027 | cout << r1 << endl << endl; |
| 028 | |
| 029 | // 重新解碼碼,然後印出解碼結果 |
| 030 | string r2 = encryptor.ToDecode(r1); |
| 031 | cout << r2 << endl << endl; |
| 032 | |
| 033 | return 0; |
| 034 | } |
| 035 | |
| 036 | /* encrypt_demo.cpp |
| 037 | Kaiching Chang |
| 038 | 2014-5 */ |
編譯執行,結果如下

編碼與解碼的結果都正確無誤, Encrypt 類別的發展已經大體完備,不過有個編譯器不會特別提醒的小問題,我們需要稍微倒帶一下,重新檢視 set_code_array() 。
中英文術語對照
| 解碼 | decoding |
| 編碼 | encoding |
| 陣列 | array |
| 索引值 | index |
| 巢狀迴圈 | nested loop |
| 迴圈 | loop |
| 成員函數 | member function |
| 字串 | string |
| 參數 | parameter |
重點整理
- 解碼與編碼用到相同的轉換表格,因為索引值 0 等於 'a' ,索引值 1 等於 'b' ,餘下可類推。
- 巢狀迴圈是指迴圈中有其他迴圈,因為用縮排的方式編排程式碼,凹陷下去的地方看起來像鳥巢,故有此名。
- 解碼利用巢狀迴圈進行,第一層迴圈逐一取得每一個字元,第二層迴圈尋找該字元在密碼表中的索引值。
問題與討論
- 為什麼編碼跟解碼可以用一樣的轉換表格?
- 可以不用巢狀迴圈來解碼嗎?
- 巢狀迴圈可以用一樣名稱的控制變數嗎?
練習
- 承接上一個單元的猜數字遊戲,將新程式寫在 exercise1901.cpp 中,修正第一位數可能為 0 的情況。
- 承上題,將新程式寫在 exercise1902.cpp 中,修正使用者輸入的四位數含有相同數字的情況。
- 承上題,將新程式寫在 exercise1903.cpp 中,修正使用者不是輸入四位數的情況。
the end
沒有留言:
張貼留言