C++ 入門指南 4.01

單元 18 - 編碼

-unit18-

編碼 (encoding) 需要用到轉換表格,我們利用陣列 (array) 儲存這個表格,簡單說,就是利用 ASCII 排列順序,對應到表格中該位置的字元

There is no spoon.
×                 

012345678910111213141516171819202122232425
qzirajsbktcludmvenwfoxgpyh

上面是用了如下的表格

code_array = "qzirajsbktcludmvenwfoxgpyh"

我們先來想一想程式如何完成編碼工作,假設是對以下的字串 (string) 進行編碼

"There is no spoon."

首先, 'T' 不是英文小寫字母,因此跳過,然後 'h''e''r''e' 都是英文小寫字母,對照表格,需要轉換為 'b''a''n''a' ,接下來遇到一個空格字元 ' ' ,也跳過,然後 'i''s' 也都是英文小寫字母,需要轉換為 'k''w' ,餘下類推。

所以需要利用一個迴圈 (loop) 進行上述編碼工作,逐一檢查字串中的每一個字元 (character) ,若是屬於英文小寫字母的編碼範圍就是 ASCII 編碼 97122 之間,我們先將該字元轉換為整數,然後減掉 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) ,也回傳一個新字串 rs 就是要編碼的字串,而 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 類別的測試程式
   作者:張凱慶 */

編譯後執行,結果如下

-encrypt_demo02-

接下來,我們繼續加入解碼的功能吧!

中英文術語對照
陣列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 型態, valueint 型態,寫一個程式 exercise1801.cxx ,宣告 map 型態變數如 m ,設定四個 key-value 元素,然後用迴圈以變數成員 first 印出 key , second 印出 value 。 參考程式碼
2. 承上題, map 的字面常數是大括弧,裡頭的 key-value 元素也是用大括弧圍起區隔, key 及 value 以逗號區隔,每個 key-value 元素也是用逗號區隔,寫一個程式 exercise1802.cxx ,將 map 變數的建立方式改成用字面常數。 參考程式碼
3. 承上題, mapempty() 可以判斷是否為空, size() 回傳元素個素,寫一個程式 exercise1803.cxx ,宣告一個 map 變數,然後用 empty() 是否為空,如果為空就用 size() 印出元素個數。 參考程式碼
4. 承上題, mapclear() 可以清空 map 中的元素,寫一個程式 exercise1804.cxx ,先用字面常數建立 map 變數 m ,然後利用迴圈印出所有元素內容,繼續利用 clear() 可以清空,最後印出 size()參考程式碼
5. 承上題, mapinsert() 可以插入元素到 map 中,寫一個程式 exercise1805.cxx ,宣告 map 變數 m 之後利用 insert() 插入 map 值,然後利用迴圈印出所有元素內容。 參考程式碼
6. 標準程式庫中 ctimetime() 回傳自西元 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. 標準程式庫中 threadsleep_until() 可以做暫停計時,寫一個程式 exercise1807.cxx ,利用 chrono 中的 now() 取得現在時間,再加上 seconds(5) 當作 sleep_until() 的參數,讓程式暫停五秒。 參考程式碼
9. 承上題,利用迴圈改成暫停一秒,同時印出現在秒數。 參考程式碼
10. 承上題,繼續利用 chrono 中的其他程式計算程式執行時間。 參考程式碼

相關教學影片

上一頁 單元 17 - 修正後的數學公式
回 C++ 入門指南 4.01 目錄
下一頁 單元 19 - 解碼
回 C++ 教材
回程式語言教材首頁