C++ 入門指南 4.01
單元 14 - Encrypt 類別
類別 (class) 為 C++ 開發軟體 (software) 的要角,因為類別用來設計物件,軟體的實際運作則是藉由物件與物件的互動。我們接下來進入實際開發軟體的階段,最後會發展出一個 GUI 軟體
encrypt_demo 是 GUI 篇所要建立的 Qt Quick 專案名稱。
我們打算發展的一個替英文句子編密碼的軟體,主要功能是做小寫字母的替換,例如 "There is no spoon." 可能變成以下任一個
Tcnan hf gl fqllg.
Tczmz dn ij nkjji.
Tgfsf pb ir barri.
Tdcpc my fo yxoof.
首先,我們要發展 Encrypt 類別,主要功能是建立一個英文小寫字母的對換表格,藉由這個表格,我們可以將英文句子中的小寫英文字母進行對換,然後開發圖形使用者介面 (graphical user interface) 的 main.qml 。
GUI 的外觀如下圖
我們的 GUI 設計會採用第三方程式庫 (third party library) Qt ,然後利用 Qt Creator 的 IDE 寫程式,並利用 Qt Quick 專案中的 QML 設計 GUI , Qt 為跨平台的程式庫,同樣 Qt Creator 也是跨平台的整合開發環境,因此在各平台的使用都大致相同。
有兩個可供輸入的文字欄位 (text field) ,其中一個我們作為輸出的顯示訊息之用,另有三個標籤 (label) ,顯示文字的提示訊息,七個按鈕 (button) ,提供「新建」、「開啟」、「儲存」 Encrypt 物件,與「編碼」、「解碼」所輸入的英文句子,「清除」所有輸入欄位,以及「拷貝」輸出結果等的功能。
現在我們先來看看所有功能的核心,也就是 Encrypt 類別,我們的目的是,建立一個小寫英文字母的轉換表格,然後編碼、解碼都可直接依據這個表格。我們打算用下面的數學公式建立表格
m = y % n
r = m + diff
這裡的概念是利用字元的 ASCII 編碼順序,假設 x 為字元的原始編碼, ASCII 編碼中 'a' 為 97 ,然後將 x 乘上變數 (variable) a , 再加上變數 b ,假設兩者均是 0 到 9 的隨機整數,這樣便得到 y 的值。
然後將 y 除以 n 取得餘數 m , n 為所要轉換的字元數量,英文小寫字母共有 26 個,所以這裡 n 等於 26 ,因此 m 等於 0 到 25 之間的整數值。最後將 m 加上 diff , diff 也就是編碼系統的差值,由於 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 ,將整數陣列改用大括弧設定初值,然後印出索引值 2 及 4 的值。 參考程式碼 |
3. 承上題,陣列可以用索引值更改元素內容,寫一個程式 exercise1403.cxx ,將 a[2] 重新設定為 100 , a[4] 重新設定為 200 ,然後印出 a[2] 與 a[4] 的值。 參考程式碼 |
4. 承上題,陣列可以用迴圈逐一操作每個元素,寫一個程式 exercise1404.cxx ,宣告一個整數陣列 a[5] ,然後先用迴圈設定每個元素值,再用另一個迴圈逐一印出陣列元素。 參考程式碼 |
5. 承上題,陣列可以用相同資料型態的陣列當作元素,這樣的陣列稱之為多維陣列,寫一個程式 exercise1405.cxx ,宣告整數陣列 a[9][9] ,用巢狀迴圈將 a[9][9] 初始化為九九乘法表中的每個數字,並且印出每個數字值。 參考程式碼 |
6. 泛型程式設計是指利用角括弧 < > 設定指定的資料型態,這樣在設計程式庫的時候可以更廣泛地套用不同的資料型態,標準程式庫中 limits 的 numeric_limits 就可以利用角括弧 < > 取得不同基本資料型態的相關程式,例如 numeric_limits<short>::min() 取得 short 型態的最小值, numeric_limits<short>::max() 取得 short 型態的最大值,寫一個程式 exercise1406.cxx 利用這種方式,印出 short 、 int 、 long 、 long long 、 usign long long 、 float 及 double 的最小值與最大值。 參考程式碼 |
7. 如果要知道變數的資料型態,可以利用標準程式庫中的 typeinfo 與關鍵字 typeid , typeid 與小括弧 () 回傳參數的 type_info 物件,再呼叫 name() 函數就會回傳型態名稱的縮寫字串,寫一個程式 exercise1407.cxx 利用這種方式,印出 short 、 int 、 long 混合計算後的型態縮寫字串。 參考程式碼 |
8. 承上題,寫一個程式 exercise1408.cxx 印出 float 、 double 、 long 混合計算後的型態縮寫字串。 參考程式碼 |
9. 承上題,寫一個程式 exercise1409.cxx 印出 char 、 short 、 int 、 float 混合計算後的型態縮寫字串。 參考程式碼 |
10. 承上題,標準程式庫 string 中的 to_string() 可以將參數轉換成字串,寫一個程式 exercise1410.cxx 印出 short 、 int 、 float 轉換字串後的型態字串。 參考程式碼 |
相關教學影片