C++ 入門指南 4.01
單元 18 - 編碼
編碼 (encoding) 需要用到轉換表格,我們利用陣列 (array) 儲存這個表格,簡單說,就是利用 ASCII 排列順序,對應到表格中該位置的字元
×
⇓
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
q | z | i | r | a | j | s | b | k | t | c | l | u | d | m | v | e | n | w | f | o | x | g | p | y | h |
上面是用了如下的表格
code_array = "qzirajsbktcludmvenwfoxgpyh"
我們先來想一想程式如何完成編碼工作,假設是對以下的字串 (string) 進行編碼
"There is no spoon."
首先, 'T' 不是英文小寫字母,因此跳過,然後 'h' 、 'e' 、 'r' 、 'e' 都是英文小寫字母,對照表格,需要轉換為 'b' 、 'a' 、 'n' 、 'a' ,接下來遇到一個空格字元 ' ' ,也跳過,然後 'i' 、 's' 也都是英文小寫字母,需要轉換為 'k' 、 'w' ,餘下類推。
所以需要利用一個迴圈 (loop) 進行上述編碼工作,逐一檢查字串中的每一個字元 (character) ,若是屬於英文小寫字母的編碼範圍就是 ASCII 編碼 97 到 122 之間,我們先將該字元轉換為整數,然後減掉 97 就會是表格字串中對應字元索引值。
這是說,第 0 個字元(索引值為 0 ) 'T' 不在英文小寫字母編碼的範圍,因此程式不會處理,然後到第 1 個字元 'h' ,這是英文小寫字母編碼為 104 ,減去 97 之後為 7 ,對應到上面的表格會是 'b' ,因此得到的新字串第 1 個新字元就是 'b' ,餘下會一直進行重複的工作到字串結束為止。
因此,我們對 ToEncode() 的設計如下
// 進行編碼工作的成員函數 string Encrypt::ToEncode(string s) { // 由參數字串取得字元的暫存變數 char c; // 暫存編碼結果的字串 string r; int i, m; // 利用迴圈走完參數字串的所有字元 for (i = 0; i < s.size(); i++) { // 判斷該字元是否為英文小寫字母,若是英文小寫字母就進行編碼轉換 if (s.at(i) >= DIFF && s.at(i) < DIFF + N) { c = s.at(i); m = c - 97; r += get_code_array().at(m); } else { r += s.at(i); } } // 結束回傳編碼過的字串 return r; }
ToEncode() 接收一個字串 s 當參數 (parameter) ,也回傳一個新字串 r , s 就是要編碼的字串,而 r 則是編碼過的字串。
進行編碼轉換的迴圈
// 利用迴圈走完參數字串的所有字元 for (i = 0; i < s.size(); i++) { // 判斷該字元是否為英文小寫字母,若是英文小寫字母就進行編碼轉換 if (s.at(i) >= DIFF && s.at(i) < DIFF + N) { c = s.at(i); m = c - 97; r += get_code_array().at(m); } else { r += s.at(i); } }
字串的成員函數 (member function) size() 回傳字串長度,也就是字串中所有字元的總數,迴圈的控制變數 (control variable) i 最大不超過 s.size() ,因為字串索引從 0 開始,最後一個元素的索引為 s.size() - 1 。字串的另一個成員函數 at() 則是可以取得該索引值的元素,也就是字元。
留意這一行
r += get_code_array().at(m);
由於字串的加號運算子 (operator) + 被多載 (overload) 過,因此可以直接把字串或字元直接跟字串相加。這裡我們最初建立空字串變數 r ,最後得到的結果同樣為變數 r ,也回傳 r 。我們用的技巧是把字串的變數重新指向自己,因此這裡是用 += 運算子。
道理很簡單,因為 r += get_code_array().at(m) 會得到一個新字串,我們將 r 重新當成這個新字串的變數,因此 r 最初是空字串,進入這個迴圈後,無論 get_code_array().at(m) 或是 s.at(i) 都會加進 r 的最後,這時候產生的新字串會重新指派給 r 。
這裡只增加一個 ToEncode() ,完整的實作檔案可以參考「範例程式碼」的 encrypt.cxx 。
我們在下面的 encrypt_demo02.cxx 加入編碼的測試
// 引入標準程式庫中的 iostream #include <iostream> // 引入 Encrypt 類別的標頭檔 #include "encrypt01.h" // 使用 std 中的兩個名稱 using std::cout; // 標準輸出串流的物件 using std::endl; // 新行符號,等於 '\n' // 程式執行的 main() 函數 int main() { // 印出空白一行 cout << endl; // 建立 Encrypt 物件 Encrypt encryptor; // 印出密碼表字串 cout << encryptor.get_code_array() << endl << endl; // 印出準備編碼的字串 string d = "There is no spoon."; cout << d << endl; // 將字串編碼,然後印出編碼結果 string r1 = encryptor.ToEncode(d); cout << r1 << endl << endl; return 0; } /* 《程式語言教學誌》的範例程式 http://kaiching.org/ 檔名:encrypt_demo02.cxx 功能:實作發展中版本 Encrypt 類別的測試程式 作者:張凱慶 */
編譯後執行,結果如下
接下來,我們繼續加入解碼的功能吧!
中英文術語對照 | |
---|---|
陣列 | array |
字元 | character |
控制變數 | control variable |
編碼 | encoding |
迴圈 | loop |
成員函數 | member function |
運算子 | operator |
多載 | overload |
參數 | parameter |
字串 | string |
重點整理 |
---|
1. 轉換表格利用字串來儲存,字串具有字元陣列的特性,可用索引值存取元素值。 |
2. 編碼時利用迴圈依序取得要編碼字串的所有字元,先判斷是否為英文小寫字母,如果是英文小寫字母就進行編碼,如果不是就直接把該字元附加到暫存變數的最後。 |
3. 加號運算子在字串型態被多載過,可用於字串連結並且重新指派。 |
問題與討論 |
---|
1. 要怎麼判斷一個字元是英文小寫字母或大寫字母? |
2. 多載的意義是什麼?為什麼運算子可以多載? |
練習 |
---|
1. 標準程式庫中的 map 是 key-value 配對型的集合體型態,由 key 來存取或設定 value ,例如 m["Mary"] = 70; ,宣告 map 則是需要在角括弧中宣告 key 及 value 的型態,例如 map<string, int> m; , m 的 key 為 string 型態, value 為 int 型態,寫一個程式 exercise1801.cxx ,宣告 map 型態變數如 m ,設定四個 key-value 元素,然後用迴圈以變數成員 first 印出 key , second 印出 value 。 參考程式碼 |
2. 承上題, map 的字面常數是大括弧,裡頭的 key-value 元素也是用大括弧圍起區隔, key 及 value 以逗號區隔,每個 key-value 元素也是用逗號區隔,寫一個程式 exercise1802.cxx ,將 map 變數的建立方式改成用字面常數。 參考程式碼 |
3. 承上題, map 的 empty() 可以判斷是否為空, size() 回傳元素個素,寫一個程式 exercise1803.cxx ,宣告一個 map 變數,然後用 empty() 是否為空,如果為空就用 size() 印出元素個數。 參考程式碼 |
4. 承上題, map 的 clear() 可以清空 map 中的元素,寫一個程式 exercise1804.cxx ,先用字面常數建立 map 變數 m ,然後利用迴圈印出所有元素內容,繼續利用 clear() 可以清空,最後印出 size() 。 參考程式碼 |
5. 承上題, map 的 insert() 可以插入元素到 map 中,寫一個程式 exercise1805.cxx ,宣告 map 變數 m 之後利用 insert() 插入 map 值,然後利用迴圈印出所有元素內容。 參考程式碼 |
6. 標準程式庫中 ctime 的 time() 回傳自西元 1970 年 1 月 1 日到目前的總秒數,利用 ctime() 可以轉換成字串格式,寫一個程式 exercise1806.cxx ,利用 time() 及 ctime() 印出現在時間。 參考程式碼 |
7. 承上題,利用 localtime() 可以回傳時間格式的結構,寫一個程式 exercise1807.cxx ,利用 tm_year 印出現在年,利用 tm_mon 印出現在月,利用 tm_mday 印出現在日,利用 tm_hour 印出現在時,利用 tm_min 印出現在分,利用 tm_sec 印出現在秒。 參考程式碼 |
8. 標準程式庫中 thread 的 sleep_until() 可以做暫停計時,寫一個程式 exercise1807.cxx ,利用 chrono 中的 now() 取得現在時間,再加上 seconds(5) 當作 sleep_until() 的參數,讓程式暫停五秒。 參考程式碼 |
9. 承上題,利用迴圈改成暫停一秒,同時印出現在秒數。 參考程式碼 |
10. 承上題,繼續利用 chrono 中的其他程式計算程式執行時間。 參考程式碼 |
相關教學影片