C++ 入門指南 4.01

單元 31 - 完成版的 encrypt_demo 專案

-unit31-

encrypt_demo 專案 (project) 中包括兩個標頭檔 (header file) 、兩個實作檔,以及一個 QML 介面檔案

encrypt_demo
     enctypt.h
     encryptcontroller.h
     encrypt.cpp
     encrypcontroller.cpp
     main.qml

其中 encrypt.hencrypt.cpp 在「軟體開發篇」發展完成, main.qml單元 27 - 使用 QML 設計視窗外觀發展,其餘便是實作按鈕的功能,新建編碼解碼均已完成,現在就剩下儲存載入清除拷貝了。

encryptcontroller.h 要先加入宣告以下四個 Q_INVOKABLE 成員函數 (member function)

Q_INVOKABLE void save();
Q_INVOKABLE bool load();
Q_INVOKABLE void clear();
Q_INVOKABLE void copy();

save() 用作儲存load() 用作載入clear() 用作清除以及 copy() 用作拷貝

然後換到 encryptcontroller.cpp ,這裡需要先引入必要的標頭檔

#include <QDebug>

#include <QFile>
#include <QDataStream>
#include <QClipboard>
#include <QGuiApplication>

QDebugQGuiApplication 都是 Qt 專案需要, QFileQDataStream 為檔案處理相關,至於 QClipboard 則是跟剪貼簿有關。

儲存按鈕的目的是存檔,以下為 save() 的實作

void EncryptController::save()
{
    QFile file("encryptor");
    file.open(QIODevice::WriteOnly);
    QDataStream out(&file);
    out << s2q(encryptObject->get_code_array());
}

先宣告 QFile 型態 (type) 變數 (variable) file參數 (parameter) 字串 (string) 是檔案名稱 encryptor

QFile file("encryptor");

然後 file 呼叫 open() ,並以 QIODevice::WriteOnly 當參數,這是指 file 將以寫入模式開啟

file.open(QIODevice::WriteOnly);

接下來宣告 QDataStream 的變數 out ,並以 file參考 (reference) 當參數

QDataStream out(&file);

out 將會處理寫入的資料串流,這裡使用輸出運算子 (output operator) << 將密碼表寫入檔案 encryptor

out << s2q(encryptObject->get_code_array());
}

回到 main.qml儲存按鈕就是呼叫 save() ,然後在 display 顯示「已存檔」

Button {
    width: 50
    text: "存檔"
    onClicked: {
        controller.save();
        display.text = "已存檔"
    }
}

載入按鈕是讀取 encryptor ,然後利用該內容設定 encryptObject 的密碼表

bool EncryptController::load()
{
    QFile file("encryptor");
    if (file.open(QIODevice::ReadOnly)) {
        QDataStream in(&file);
        QString temp;
        in >> temp;

        if (encryptObject == nullptr) {
            encryptObject = new Encrypt;
        }
        encryptObject->set_code_array(q2s(temp));

        return true;
    }
    else {
        return false;
    }
}

注意 load() 設計成回傳布林值 (Boolean) ,這叫做布林函數,之所以設計成布林函數 (Boolean function) ,待會在 main.qml 的部分解釋原因

bool EncryptController::load()

進入 load() ,同樣是先建立 QFile 型態的變數 file ,並以檔案名稱字串當參數,然後就是 if-else 的部分,

if (file.open(QIODevice::ReadOnly)) {
    QDataStream in(&file);
    QString temp;
    in >> temp;

    if (encryptObject == nullptr) {
        encryptObject = new Encrypt;
    }
    encryptObject->set_code_array(q2s(temp));

    return true;
}
else {
    return false;
}

簡單說,檔案預計以讀取模式 QIODevice::ReadOnly 開啟,如果開啟成功就回傳 true ,反之回傳 false ,也就是檔案不存在的話就回傳 false

如果 file 成功開啟 encryptor 檔案,接下來就是用 QDataStream 設定輸入串流,並用 QString 型態的變數 temp 接收從檔案讀取的內容

QDataStream in(&file);
QString temp;
in >> temp;

接下來先檢查 encryptObject 是不是 nullptr ,如果 encryptObjectnullptr ,代表使用者還沒按過新建按鈕,因此這裡要先新建 Encrypt 型態的 encryptObject ,然後再將檔案儲存的密碼表設定給 encryptObject

if (encryptObject == nullptr) {
    encryptObject = new Encrypt;
}
encryptObject->set_code_array(q2s(temp));

回到 main.qml載入按鈕就是呼叫 load() ,如果載入成功回傳 true ,就會在 display 顯示「已載入」,反之就顯示「無法載入」

Button {
    width: 50
    text: "載入"
    onClicked: {
        if (controller.load()) {
            display.text = "已載入"
        }
        else {
            display.text = "無法載入"
        }
    }
}

由於載入可能會失敗,因此將 load() 設計成布林函數,分成載入成功及失敗兩種情況來處理。

清除按鈕的目的是清空所有欄位,用作清除clear() 的實作如下

void EncryptController::clear()
{
    m_userInput = "";
    m_encodeResult = "";
    m_decodeResult = "";
    encryptObject = nullptr;
}

清除就是把所有的設定歸零,也就是將相關資料成員 (data member) 設定成空字串或 nullptr清除按鈕在 main.qml 的實作如下

Button {
    width: 50
    text: "清除"
    onClicked: {
        input.text = ""
        output.text = ""
        controller.clear()
        display.text = "已清除"
    }
}

同樣是輸入、輸出的文字方塊設定為空字串,然後呼叫 clear() ,並在 display 顯示「已清除」。

至於拷貝按鈕則是把編碼 (encoding) 結果拷貝到系統剪貼簿,以下為 copy() 的實作

void EncryptController::copy()
{
    QClipboard *clipboard = QGuiApplication::clipboard();
    clipboard->setText(m_encodeResult);
}

以上是先建立 QClipboard 型態的變數 clipboard ,注意 clipboard指標 (pointer) ,然後 clipboard 利用 setText() 將編碼結果複製到系統剪貼簿,底下繼續看到拷貝按鈕在 main.qml 的實作

Button {
    width: 50
    text: "拷貝"
    onClicked: {
        controller.copy()
        display.text = "已拷貝到系統剪貼簿"
    }
}

就是呼叫 copy() ,然後在 display 顯示「已拷貝到系統剪貼簿」。

來執行測試囉!先按新建,得到下圖的編碼結果,再來存檔

-encryptor_01-

用新的密碼表編碼,然後按下載入

-encryptor_01-

這樣就可以得到同樣的編碼結果

-encryptor_02-

清除就是全部清空

-encryptor_04-

拷貝可以將編碼結果複製貼到其他的軟體

-encryptor_05-

好了, encrypt_demo 專案的所有功能大體完成,下一步是?

中英文術語對照
布林值Boolean
布林函數Boolean function
資料成員data member
編碼encoding
標頭檔header file
成員函數member function
輸出運算子output operator
參數parameter
指標pointer
專案project
參考reference
字串string
型態type
變數variable
重點整理
1. 存檔及載入需要宣告 QFile 型態的檔案物件開啟檔案,然後用 QDataStream 寫入或讀取檔案內容。
2. 布林函數是回傳真假值的函數。
3. 清除按鈕是將所有設定歸零,也就是將變數成員或屬性設定為空字串或 nullptr
4. 拷貝是利用 QClipboard 型態的物件,將字串內容拷貝到系統剪貼簿。
問題與討論
1. 將功能設計成布林函數有何優點?
2. 為什麼檔案處理要分成讀取模式跟寫入模式?
練習
1. 承接上一個單元的猜數字遊戲,利用類別設計猜數字遊戲的計算核心,先以 guess.h 規劃軟體規格,至少要有以下的變數成員:儲存答案的 answer 、對的位置與數字的 A 、錯的位置與對的數字的 B ,累計猜測次數的 times 、猜測數字長度的 digit ,函數成員至少要有 settergetter ,累計猜測次數的 AddTimes() ,計算 AB 值的 ABCounter() ,判斷重複數字的 FindNumber() ,以及做檢測的 Test()參考程式碼
2. 承上題,利用上一個單元建立答案的方式來實作 settergetter ,可以另外設計 guess_demo.cpp 來測試程式碼是否能正確執行。 參考程式碼
3. 承上題,實作將猜測次數加一的 AddTimes()參考程式碼
4. 承上題,實作計算 AB 值的 ABCounter() ,注意 AB 值要先歸零,然後用迴圈逐數字比較答案與猜測數字。 參考程式碼
5. 承上題,實作判斷是否有重複數字的 FindNumber() ,注意這應該是一個布林函數,用來判斷是否有重複數字。 參考程式碼
6. 承上題,實作檢測猜測結果的 Test() ,注意檢測需要是布林函數,猜對回傳 true ,猜錯回傳 false參考程式碼
7. 承上題,將寫好的 guess.hguess.cpp 加入到「單元 29 」練習 8 的 guess_demo 中,另外新增 GuessController 類別,請仿造 encryptcontroller.h 設計 guesscontroller.h ,至少需要一個 Q_PROPERTY 接收使用者輸入,也需要 test() 及判斷重複數字的 findNumber() ,如果答案是用字串,同樣需要 s2q()q2s()參考程式碼
8. 承上題,仿造 encryptcontroller.cpp 定義相關的 settergetter參考程式碼
9. 承上題,定義 test()findNumber() 兩個布林函數。 參考程式碼
10. 承上題,實作 guess_demo 專案中 mail.qml 的按鈕實際功能,使之能用 GUI 進行遊戲。 參考程式碼

相關教學影片

上一頁 單元 30 - QString 的問題
回 C++ 入門指南 4.01 目錄
下一頁 單元 32 - 下一步
回 C++ 教材
回程式語言教材首頁