C++ 入門指南 V2.00 - 單元 18 - 編碼




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



上圖是用了如下的表格


    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() 的設計如下


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 - 97;
069          r += get_code_array().at(m);
070       }
071       else {
072          r += s.at(i);
073       }
074    }
075
076    // 結束回傳編碼過的字串
077    return r;
078 }

ToEncode() 接收一個字串 s 當參數 (parameter) ,也回傳一個新字串 rs 就是要編碼的字串,而 r 則是編碼過的字串。


進行編碼轉換的迴圈


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 - 97;
069       r += get_code_array().at(m);
070    }
071    else {
072       r += s.at(i);
073    }
074 }

字串的成員函數 (member function) size() 回傳字串長度,也就是字串中所有字元的總數,迴圈的控制變數 i 最大不超過 s.size() ,因為字串索引從 0 開始,最後一個元素的索引為 s.size() - 1 。字串的另一個成員函數 at() 則是可以取得該索引值的元素,也就是字元。


留意這一行


069 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.cpp

我們在下面的 encrypt_demo02.cpp 加入編碼的測試


001 // 引入標準程式庫中的 iostream
002 #include <iostream>
003 // 引入 Encrypt 類別的標頭檔
004 #include "encrypt01.h"
005
006 // 使用 std 中的兩個名稱
007 using std::cout// 標準輸出串流的物件
008 using std::endl// 新行符號,等於 '\n'
009
010 // 程式執行的 main() 函數 
011 int main() {
012    // 印出空白一行
013    cout << endl;
014
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    return 0;
030 }
031
032 /* encrypt_demo02.cpp 
033    Kaiching Chang 
034    2014-5 */

編譯後執行,結果如下



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


中英文術語對照


編碼 encoding
陣列 array
字串 string
迴圈 loop
字元 character
參數 parameter
成員函數 member function
運算子 operator
多載 overload

重點整理


  1. 轉換表格利用字串來儲存,字串具有字元陣列的特性,可用索引值存取元素值。
  2. 編碼時利用迴圈依序取得要編碼字串的所有字元,先判斷是否為英文小寫字母,如果是英文小寫字母就進行編碼,如果不是就直接把該字元附加到暫存變數的最後。
  3. 加號運算子在字串型態被多載過,可用於字串連結並且重新指派。

問題與討論


  1. 要怎麼判斷一個字元是英文小寫字母或大寫字母?
  2. 多載的意義是什麼?為什麼運算子可以多載?

練習


  1. 承接上一個單元的猜數字遊戲,將新程式寫在 exercise1801.cpp 中,替 ABCounter() 加入整數參數 length 取代數字 4
  2. 承上題,利用標準程式庫 cstdlibrand()srand() 設計 RandomNumber() 函數,這個新函數取得隨機的四位數當成遊戲答案,將新程式寫在 exercise1802.cpp
  3. 承上題,設計另一個 FindNumber() 函數檢查是否有重複的數字,將新程式寫在 exercise1803.cpp ,使 RandomNumber() 產生的四位數中,四個數字均不重複。

the end

沒有留言: