C++ 入門指南 4.01
單元 30 - QString 的問題
Qt 的 QString 跟標準程式庫 (standard library) 中的 string 雖然都以字元 (character) 為元素 (element) ,卻是不同的型態 (type)
我們在單元 20 - 型態轉換問題介紹過基本內建型態 (primitive built-in type) 的轉換問題,可是 QString 與 string 都是類別 (class) 定義的型態,因此兩者有各自不同的成員 (member) ,編譯器 (compiler) 認為 QString 是 QString ,而 string 是 string ,像 QString 的前綴 Q 就是標明這是 Qt 程式庫 (library) 的字串 (string) 類別。
基本上 Qt 的視窗介面無論顯示或接收都是 QString 字串,因此當我們想要把 Encrypt 的密碼表用在 Qt Quick 專案的時候就會產生無法處理的問題。
解決這問題有兩種途徑,第一種就是回頭修改 Encrypt 類別,把密碼表從 string 改成 QString ,這樣一來,用到 Qt Quick 專案中就完完全全沒有問題了說。
這是種解決方式,聽起來還不賴!可是會讓 Encrypt 僅限於使用 Qt 程式庫,假如改天要換到另一種 GUI 程式庫的時候,就變成 QString 的地方又要全部改寫,反倒很麻煩,不是嗎?
第二種途徑則是進行型態轉換,利用 QString 提供轉換成字元陣列 (array) 的函數 (function) 來進行,這樣子我們需要宣告兩個新成員函數 (member function) 在 encryptcontroller.h 的 EncryptController 類別宣告中
QString s2q(const string &); string q2s(const QString &);
完整程式請參考範例程式碼的 encryptcontroller.h 。
s2q() 將 string 型態的物件 (object) 轉換成 QString 型態,最後回傳 QString 型態的物件, q2s() 則是相反過來,兩者的參數 (parameter) 都是 const 參考 (reference) ,實作如下
QString EncryptController::s2q(const string &s) { return QString(QString::fromLocal8Bit(s.c_str())); } string EncryptController::q2s(const QString &s) { return string((const char *)s.toLocal8Bit()); }
完整程式請參考範例程式碼的 encryptcontroller.cpp 。
這樣的轉換方式類似把物件轉換成字元陣列,再利用 QString 或 string 的建構函數 (constructor) 回傳 QString 或 string 型態的物件。
回到 getEncryptObject() 的部分,這裡要把 encryptObject->get_code_array() 當作 s2q() 的參數
QString EncryptController::getEncryptObject() { return s2q(encryptObject->get_code_array()); }
這樣一來, Qt Creator 的提示錯誤就會消失
接下來繼續實作 encodeFunction() ,如下
void EncryptController::encodeFunction() { m_encodeResult = s2q(encryptObject->ToEncode(q2s(m_userInput))); qInfo() << m_userInput; qInfo() << m_encodeResult; }
m_userInput 要先用 q2s() 轉換成 string ,然後交給 encryptObject 的 ToEncode() 進行編碼,再用 s2q() 轉換回 QString ,指派給暫存編碼結果的 m_encodeResult 。
底下兩行 qInfo() 是提供給控制台輸出,這是方便除錯的用途。
getEncodeResult() 就是回傳 m_encodeResult
QString EncryptController::getEncodeResult() { return m_encodeResult; }
至於 decodeFunction() 跟 getDecodeResult() 跟以上兩個成員函數及無相似,差別是 ToEncode() 換成 ToDecode() , m_encodeResult 換成 m_decodeResult
void EncryptController::decodeFunction() { m_decodeResult = s2q(encryptObject->ToDecode(q2s(m_userInput))); qInfo() << m_userInput; qInfo() << m_decodeResult; } QString EncryptController::getDecodeResult() { return m_decodeResult; }
繼續看到 main.qml ,這裡要加入 EncryptController 型態的宣告
EncryptController { id: controller }
完整程式碼可以參考「範例程式碼」的 encryptor_demo/main.qml 。
EncryptController 型態的 id 設定為 controller ,在 QML 中可以直接用 controller 呼叫 EncryptController 類別的函數成員,進而實現 Encrypt 類別的功能。
接下來要實作新建、編碼、解碼三個按鈕,先看到新建的部分
Button { id: newButton width: 50 text: "新建" onClicked: { controller.setEncryptObject() display.text = controller.getEncryptObject() } }
按下新建按鈕,首先 controller 呼叫 setEncryptObject() ,設定 Encrypt 型態的資料成員,然後在底下 display 顯示密碼表。
繼續實作編碼按鈕
Button { width: 50 text: "編碼" onClicked: { controller.encodeFunction() output.text = controller.getEncodeResult() display.text = "編碼結果如上" } }
按下編碼按鈕,就是 controller 利用 encodeFunction() 進行編碼,並將結果顯示在 output ,底下 display 顯示「編碼結果如上」。
解碼按鈕按鈕的實作跟編碼按鈕很類似,如下
Button { width: 50 text: "解碼" onClicked: { controller.decodeFunction() output.text = controller.getDecodeResult() display.text = "解碼結果如上" } }
執行後,編碼功能正常運作
解碼功能也可正常運作
同樣是改成相對應的成員函數,下一個單元我們繼續實作存檔、載入、清除、拷貝等四個按鈕,使之具有完整功能。
中英文術語對照 | |
---|---|
類別 | class |
資料成員 | data member |
標頭檔 | header file |
成員函數 | member function |
物件 | object |
參數 | parameter |
指標 | pointer |
專案 | project |
參考 | reference |
標準程式庫 | standard library |
字串 | string |
型態 | type |
重點整理 |
---|
1. QString 與 string 之間的互相轉換為先轉換為字元陣列,再由字元陣列轉換成相對應的 QString 或 string 。 |
2. qInfo() 用來輸出變數值到控制台,這方便用來除錯。 |
3. main.qml 需要加入宣告 EncryptController 型態並設定 id 。 |
問題與討論 |
---|
1. 為什麼 Qt 不直接用標準程式庫中的 string ,而要另外定義 QString ? |
2. 利用 qInfo() 對除錯有哪些幫助呢? |
練習 |
---|
1. 承接「單元 29 」的練習 8 ,替 guess_demo 專案設計猜數字遊戲的計算核心,先將計算核心的演算法寫在程式 guess_demo01.cpp 中,宣告整數 10 以內 int 型態的答案變數 answer ,然後把 answer 印出來。 參考程式碼 |
2. 承上題,將新程式寫在 guess_demo02.cpp ,加入讓使用者猜測的互動程式,使用者猜對就顯示「恭喜猜對」,否則印出遊戲答案。 參考程式碼 |
3. 承上題,參考「單元 16 」練習 6 的取得隨機數方式,將新程式寫在 guess_demo03.cpp ,取得 10 以下的隨機整數當作答案。 參考程式碼 |
4. 承上題,將新程式寫在 guess_demo04.cpp ,把遊戲程式碼放到 while true 迴圈中,並加入計算次數的變數,直到使用者猜對數字才結束程式。 參考程式碼 |
5. 承上題,將新程式寫在 guess_demo05.cpp ,改成猜測四位不重複數字,先設定指定的答案,然後把答案印出來。 參考程式碼 |
6. 承上題,將新程式寫在 guess_demo06.cpp ,利用練習 3 取得隨機整數的方式,也就是用 rand() % 10000 取得的餘數當作答案,然後把答案印出來。 參考程式碼 |
7. 承上題,將新程式寫在 guess_demo07.cpp ,只利用 rand() % 10000 可能會得到非四位數,加入程式碼確保得到的答案一定是四位數。 參考程式碼 |
8. 承上題,將新程式寫在 guess_demo08.cpp ,以上方式取得的答案仍會有重複數字,將四位數分別放入 set 中,如果 set 的 size() 回傳整數 4 就確保得到不重複的四位數。 參考程式碼 |
9. 承上題,利用 rand() 會有效率問題,參考「單元 16 」練習 9 的取得隨機數方式,優化取得不重複四位數的方式,將新程式寫在 guess_demo09.cpp 。 參考程式碼 |
10. 承上題,參考「單元 22 」重構版的 Encrypt 類別建立密碼表的方式,把答案改用字串設定,將新程式寫在 guess_demo10.cpp ,先用字串設定不重複的四位數答案,然後印出答案。 參考程式碼 |
11. 承上題,將新程式寫在 guess_demo11.cpp ,實際實作利用字串建立猜數字遊戲的答案。 參考程式碼 |
相關教學影片