C++ 入門指南 4.01

單元 14 - Encrypt 類別

-unit14-

類別 (class) 為 C++ 開發軟體 (software) 的要角,因為類別用來設計物件,軟體的實際運作則是藉由物件與物件的互動。我們接下來進入實際開發軟體的階段,最後會發展出一個 GUI 軟體

Encrypt → encrypt_demo

encrypt_demo GUI 篇所要建立的 Qt Quick 專案名稱。

我們打算發展的一個替英文句子編密碼的軟體,主要功能是做小寫字母的替換,例如 "There is no spoon." 可能變成以下任一個

Tfqdq ki jo itooj.
Tcnan hf gl fqllg.
Tczmz dn ij nkjji.
Tgfsf pb ir barri.
Tdcpc my fo yxoof.

首先,我們要發展 Encrypt 類別,主要功能是建立一個英文小寫字母的對換表格,藉由這個表格,我們可以將英文句子中的小寫英文字母進行對換,然後開發圖形使用者介面 (graphical user interface) main.qml

GUI 的外觀如下圖

-encryptwindow-

我們的 GUI 設計會採用第三方程式庫 (third party library) Qt ,然後利用 Qt Creator 的 IDE 寫程式,並利用 Qt Quick 專案中的 QML 設計 GUI , Qt 為跨平台的程式庫,同樣 Qt Creator 也是跨平台的整合開發環境,因此在各平台的使用都大致相同。

有兩個可供輸入的文字欄位 (text field) ,其中一個我們作為輸出的顯示訊息之用,另有三個標籤 (label) ,顯示文字的提示訊息,七個按鈕 (button) ,提供「新建」、「開啟」、「儲存」 Encrypt 物件,與「編碼」、「解碼」所輸入的英文句子,「清除」所有輸入欄位,以及「拷貝」輸出結果等的功能。

現在我們先來看看所有功能的核心,也就是 Encrypt 類別,我們的目的是,建立一個小寫英文字母的轉換表格,然後編碼、解碼都可直接依據這個表格。我們打算用下面的數學公式建立表格

y = a * x + b
m = y % n
r = m + diff

這裡的概念是利用字元的 ASCII 編碼順序,假設 x 為字元的原始編碼, ASCII 編碼中 'a'97 ,然後將 x 乘上變數 (variable) a , 再加上變數 b ,假設兩者均是 09 的隨機整數,這樣便得到 y 的值。

然後將 y 除以 n 取得餘數 mn 為所要轉換的字元數量,英文小寫字母共有 26 個,所以這裡 n 等於 26 ,因此 m 等於 025 之間的整數值。最後將 m 加上 diffdiff 也就是編碼系統的差值,由於 ASCII 中 'a'97 ,所以這裡 diff 要以 97 代入。

因此,餘數 0 的字元會替換成 'a' ,餘數 1 的字元會被替換成 'b' ,餘數 2 的字元會被替換成 'c' ,餘下 23 個字元類推。這樣的計算需要進行 n 次,也就是 26 次,我們最後得到一組餘數與相對應字元的表格,這就是我們需要的表格了。

重複 n 次,我們需要一個迴圈 (loop) ,由於重複次數確定,因此 for 迴圈 (for loop) 很適合,那我們要用什麼東西來儲存這個表格呢?標準程式庫 (standard library) 中有許多的資料結構 (data structure) ,可以依資料特性有效率的處理資料,這裡,我們利用字串 (string) 就可以了。

字串為類似字元陣列 (character array) 物件 (object) ,因此字串裡是由字元 (character) 當成元素 (element) ,也保有陣列 (array) 的特性,例如利用從 0 開始的索引值存取每個字元,這也完全符合我們計算餘數從 0 開始的需求。

因此,我們要替 Encrypt 宣告一個字串型態的的資料成員 (data member) code_array 為二十六個英文小寫字母的密碼對照表格

private:
    // 密碼表字串
    string code_array;

因此就需要存取函數 (accessor) 修改函數 (mutator) ,也就是 get_code_array()set_code_array() ,另外加上建構函數 (constructor) 跟編碼、解碼用的 ToEncode()ToDecode() 兩個成員函數 (member function) ,我們先寫一個發展中的 encrypt01.h ,如下

// 從標準程式庫中引入 string
#include <string>

// 使用 std 中的 string 名稱
using std::string;

// 宣告 Encrypt 類別
class Encrypt {
public:
    // 宣告建構函數
    Encrypt();
    // 宣告 setter 與 getter 成員函數
    void set_code_array();
    string get_code_array();
    // 宣告編碼、解碼的成員函數
    string ToEncode(string);
    string ToDecode(string);

private:
    // 密碼表字串
    string code_array;
};

/* 《程式語言教學誌》的範例程式
   http://kaiching.org/
   檔名:encrypt01.h
   功能:Encrypt類別的發展中版本
   作者:張凱慶 */

注意這裡,原本我們的範例使用 using 的地方都是連帶 namespace

using namespace std;

這裡改成指定的名稱

// 使用 std 中的 string 名稱
using std::string;

兩者實際的差別不大,因為編譯器 (compiler) 編譯 (compile) 時會將程式作最佳化,因此編譯完成的執行檔只會放進必要的東西,而我們改成指定名稱的寫法,主要的原因是讓我們自己清楚用到標準程式庫中的哪些東西,所以看範例開頭 using 的部份就一目瞭然了。

我們看看宣告在 public 的成員函數

public:
    // 宣告建構函數
    Encrypt();
    // 宣告 setter 與 getter 成員函數
    void set_code_array();
    string get_code_array();
    // 宣告編碼、解碼的成員函數
    string ToEncode(string);
    string ToDecode(string);

標頭檔 (header file) 宣告 (declare) 成員函數時,參數列 (parameter list) 只需要宣告型態 (type) 即可,實作檔再補上實際的參數 (parameter) 名稱即可。

這裡我們看到的是未來發展 Encrypt 的規格,編碼由 ToEncode() 負責,解碼則是 ToDecode() ,我們在這裡先宣告函數原型,後續單元再陸續實作。

下面我們先來實作 set_code_array() ,把數學公式寫成程式碼,實際建立 code_array 囉!

中英文術語對照
存取函數accessor
陣列array
按鈕button
字元character
字元陣列character array
類別class
編譯器compiler
編譯compile
建構函數constructor
資料結構data structure
資料成員data member
宣告declare
元素element
for 迴圈for loop
圖形使用者介面graphical user interface
標頭檔header file
標籤label
迴圈loop
成員函數member function
修改函數mutator
物件object
參數parameter
參數列parameter list
標準程式庫standard library
字串string
軟體software
文字欄位text field
第三方程式庫third party library
型態type
變數variable
重點整理
1. Encrypt 類別的主類功能是轉換句子中的英文小寫字母,最後作為 GUI 軟體的核心部分。
2. Encrypt 類別以數學公式建立英文字母的轉換表格,用字串當成員變數儲存表格。
3. ASCII 中, 'a' 的整數值為 97
4. 標準程式庫中有許多有用的資料結構,字串即是一例,字串是由字元所組成的物件,保有字元陣列的特性。
5. 程式的發展通常是逐步來的,標頭檔則作為程式的規格。
問題與討論
1. 為什麼 Encrypt 類別是要當作 GUI 軟體的核心?不能讓 Encrypt 類別直接成為大展神威的 GUI 軟體嗎?
2. 除了數學公式外,有其他的方式可以建立轉換表格嗎?
3. 為什麼程式的發展要逐步來?不能一次到位嗎?
4. 陣列是什麼樣的資料結構?對於儲存資料有什麼方便性?
練習
1. 陣列是 C++ 儲存固定數量的相同資料型態資料結構,宣告陣列在識別字前用型態名稱,後用中括弧圍住陣列元素個數,例如 int a[5] ,寫一個程式 exercise1401.cxx ,宣告整數陣列 a[5] ,然後用索引值設定設定元素值。 參考程式碼
2. 承上題,陣列可以用大括弧設定初值,例如 {1, 2, 3, 4 ,5} ,寫一個程式 exercise1402.cxx ,將整數陣列改用大括弧設定初值,然後印出索引值 24 的值。 參考程式碼
3. 承上題,陣列可以用索引值更改元素內容,寫一個程式 exercise1403.cxx ,將 a[2] 重新設定為 100a[4] 重新設定為 200 ,然後印出 a[2]a[4] 的值。 參考程式碼
4. 承上題,陣列可以用迴圈逐一操作每個元素,寫一個程式 exercise1404.cxx ,宣告一個整數陣列 a[5] ,然後先用迴圈設定每個元素值,再用另一個迴圈逐一印出陣列元素。 參考程式碼
5. 承上題,陣列可以用相同資料型態的陣列當作元素,這樣的陣列稱之為多維陣列,寫一個程式 exercise1405.cxx ,宣告整數陣列 a[9][9] ,用巢狀迴圈將 a[9][9] 初始化為九九乘法表中的每個數字,並且印出每個數字值。 參考程式碼
6. 泛型程式設計是指利用角括弧 < > 設定指定的資料型態,這樣在設計程式庫的時候可以更廣泛地套用不同的資料型態,標準程式庫中 limitsnumeric_limits 就可以利用角括弧 < > 取得不同基本資料型態的相關程式,例如 numeric_limits<short>::min() 取得 short 型態的最小值, numeric_limits<short>::max() 取得 short 型態的最大值,寫一個程式 exercise1406.cxx 利用這種方式,印出 shortintlonglong longusign long longfloatdouble 的最小值與最大值。 參考程式碼
7. 如果要知道變數的資料型態,可以利用標準程式庫中的 typeinfo 與關鍵字 typeidtypeid 與小括弧 () 回傳參數的 type_info 物件,再呼叫 name() 函數就會回傳型態名稱的縮寫字串,寫一個程式 exercise1407.cxx 利用這種方式,印出 shortintlong 混合計算後的型態縮寫字串。 參考程式碼
8. 承上題,寫一個程式 exercise1408.cxx 印出 floatdoublelong 混合計算後的型態縮寫字串。 參考程式碼
9. 承上題,寫一個程式 exercise1409.cxx 印出 charshortintfloat 混合計算後的型態縮寫字串。 參考程式碼
10. 承上題,標準程式庫 string 中的 to_string() 可以將參數轉換成字串,寫一個程式 exercise1410.cxx 印出 shortintfloat 轉換字串後的型態字串。 參考程式碼

相關教學影片

上一頁 軟體開發篇
回 C++ 入門指南 4.01 目錄
下一頁 單元 15 - 實作 set_code_array()
回 C++ 教材
回程式語言教材首頁