第一節 QT介紹
?1.?QT概述
? ? ? ? 簡單來說,QT就是一個跨平臺的客戶端技術,HTML畫網頁一樣,而QT就是畫客戶端的,它不僅可以繪制界面而且可以做單機應用開發,還可以做網絡程序的客戶端界面開發
? ? ? ? 更專業的說法是:Qt 是?個 跨平臺的 C++ 圖形??界?應?程序框架 ,它擁有完備的C++ 圖形庫和集成了一系列代碼模塊簡化難度,開發者可以通過簡單的拖拽和組合來實現復雜的應?程序,而且Qt 支持C++,Python,QML,Javascript 等多種語言,適合多種技術、開發方式。同時Qt 也擁有一套完整的設計、開發工具以及豐富的文檔和例程,其開源社區非常活躍,這些能明顯降低開發難度和縮短開發時間
? ? ? ? 其實除了QT是客戶端技術,還有VB,C++、win32API 的 MFC ,Winform ,C# .net framework,Java swing/javaFx ,C++ duilib,Objective-c/swift cocoa等等
? ? ? ? 但他們不是做出來的客戶端界面丑,無法跨平臺,沒有活躍的社區,就是沒有維護了,所以一般公司做客戶端界面都是用的QT;
? ? ? ? 而Qt是很多客戶端跨平臺的首選,因為開源、UI 庫和各種功能的類庫非常豐富,但這里只推薦做pc的客戶端,移動端的客戶端界面還是不推薦使用Qt
2. QT框架
? ? ? ? 一句話:One framework. One codebase. Any platform,這是Qt 官網的一句話,很好的概括了什么是Qt,
口號說明:
- One framework(一個框架):只需要使用一個開發框架
- One codebase(一套代碼):只需要維護一份代碼,而不是針對不同平臺寫不同的代碼
- Any platform(任何平臺):這套代碼可以運行在多個平臺上,比如 Windows、macOS、Linux,甚至移動設備(iOS/Android)或 Web 端
3. QT發展(了解)
-
Qt最早是1991年由挪威的Eirik Chambe-Eng和Haavard Nord開發的,1994年3月4日成立奇趣科技公司(Trolltech);
-
2000年奇趣科技公司為開源社區發布了遵循 GPL(GNU General Public License)許可證的開源版本;
-
2008年諾基亞公司收購了奇趣科技公司,并增加了 LGPL(GNU Lesser General Public License)的授權模式;
-
2011年3月Qt 商業授權業務出售給了芬蘭IT服務公司Digia
- 2013年7月3日發布Qt5,2020年6月12日**Qt5.15** LTS 正式發布,目前已經推出Qt6。
????????經過多年的發展,市面上也有很多基于Qt開發的應用程序:WPS、YY語音、豆瓣電臺、蝦米音樂、淘寶助理、千牛、暴雪的戰網客戶端、VirtualBox、Google地圖、Photoshop等等
4. QT優點
-
跨平臺,?乎?持所有的平臺;
-
接?簡單,容易上?,學習 QT 框架對學習其他框架有參考意義。
-
?定程度上簡化了內存回收機制;
-
開發效率?,能夠快速的構建應?程序。
-
有很好的社區氛圍,市場份額在緩慢上升。
-
可以進?嵌?式開發。
5.?Qt 的應?場景
-
主要:?桌?應?程序,?嵌?式系統
-
次要:?移動應?程序
????????額外多說一下:嵌入式系統指的是日常使用的: 冰箱,洗衣機,路由器,投影儀...之類的,這些設備里面就使用了嵌入式系統
第二節 QT Creator
1. 下載
???https://download.qt.io/archive/qt/5.12/5.12.11/
雖然目前最新的QT版本是QT6,但是我這里使用的是QT5??
- 我使用的是5.12.11版的qt,大家可以根據自己的實際情況進行下載,
- 值得多說一句的是:類似于這些軟件/工具的下載,不要最新的,也不要太老的
- 最新的版本出了問題不好解決,太老的版本可能又會有兼容性問題
?2. 安裝
說明一下:
- 可能第一個界面是需要注冊賬號的,直接使用郵箱+密碼注冊,然后會給你的郵箱中發個郵件,最后你在你的郵箱中確認那個郵件就行了(如果在安裝之間斷網,那么就不需要注冊賬號)
- 下一步,下一步,然后選擇那四個組件就行了,不要全選
3. 配置環境變量?
????????為了讓操作系統/QT Creator工具,能夠找到Qt SDK中提供的exe,也就是運行Qt 程序的時候,能夠找到對應.dll 動態庫,所以我們這里可以先把環境變量配置好
4. 基本介紹
? ? ? ? 剛才其實我們就已經把QT開發環境下好了,可能桌面沒有顯示,按一下win鍵,如下圖
5. 新建項目
- 其實一路next下去也行
6. 項目結構
?6.1 qt_tmp.pro
qt_tmp.pro是項目的配置文件
說明一下:
- 這里該怎么理解QT += 模塊呢,其實就是我們在做大型項目是需要引入的第三方庫,
- 后面我們會需要引入multimedia?模塊,而這個模塊是跟音視頻有關的模塊,簡單理解為第三方庫也行
?6.2?mainwindow.h
說明一下:
- Q_OBJECT其實是個宏,其中有QT中的信號與槽的宏定義,更詳細的后面再說
關于QT中對頭文件的設定:
- 在使用Qt中內置的類時,一般來說包含頭文件的名字就是和類名一致的,但是也不是所有的Qt類都需要顯示包含頭文件
- 畢竟在c++中,一些頭文件都是間接被包含的
關于構造函數中需要穿父類的指針說明:
- QT中為了統一析構父類和子類,引入了對象樹的概念(后面詳細介紹)
- 而創建對象時就需要把這個對象往樹·上掛,而往樹上掛就必須要指明父節點,
- 則構造的時候就需要傳一個父類的指針
6.3 mainwindow.cpp
6.4 main.cpp
?說明一下:
- 是先有應用,再有窗口,窗口是放在應用里的,可能有多個窗口,所以他們2個對象實例化的順序不能顛倒
這里簡單說一下a.exec(),其實可以簡單的理解為一個死循環,但還做了一些其他的東西,
- 如果沒有最后一行代碼,而是寫成了return 0; 那么這個窗口會閃一下,就退出了,
- 簡單解釋:能出現窗口是因為有w.show();// 顯示窗口,而w對象又是棧上開辟的,出了作用域就沒了
順便再說一下:w.show()
- .show()方法表示讓控件顯示處理
- .hide()方法表示讓控件隱藏
- widget的父類時QWidget,則上面的那兩個方法都是QWidget提供的
6.5?mainwindow.ui
? ? ? ? 上面那種格式是xml格式,而這里的xml格式就是去描述這個界面是怎么樣的,進一步的qmake會調用相關的工具,依據這個xml文件生成一些c++代碼,從而把完整的界面構建出來
6.6 臨時文件
?
說明一下:
- 在運行一次程序之后,就會在 項目并列的地方,多出一個"build-xxxx"目錄
- 這個目錄里面就是該項目運行過程中,生成的一些臨時文件
7. 第一個程序hello world
7.1 純圖形化方式
說明一下:?
- ?以圖形化方式實現界面,主要是在qt designer 中拖動控件,調調屬性等
- 而我剛才往界面上拖拽了一個QLabel控件,此時,ui文件的xml中就會多出來這一段代碼
- 進一步的qmake就會在編譯項目的時候,基于這個內容,生成一段C++代碼,通過這個C++代碼構建出界面內容
7.2 純代碼方式
方式一:重建一個cpp文件
#include <iostream>
#include <QMainWindow>
#include <QLabel>
#include <QApplication>
using namespace std;int main(int argc, char *argv[])
{// cout << "Hello World!" << endl;QApplication a(argc, argv);// 因為show這個方法本來就是QMainWindow類的,所以直接實例化這個類的對象就行QMainWindow w;w.setGeometry(0,0,800,600);w.setWindowTitle("第一個程序");QLabel* label = new QLabel(&w);// 對這個控件做相關屬性設置label->setText("hello world");label->setGeometry(160,110,400,71);label->setStyleSheet("font: 75 20pt Consolas;");w.show();return a.exec();
}
?
方式二:重建一個qt設計類文件
說明一下:?
- 一般通過代碼來構造界面的時候,通常會把構造界面放到Widget/MainWindow的構造函數中
- 這里的this指針,是給當前這個label對象,指定一個父對象(以后會說的對象樹)
- 在QT中的字符串和C++/C中的字符串是不一樣的,當時C++/C的字符串不好用,所以QT為了字節的開發能變的順暢,就自己發明了一套輪子,搞了一系列基礎類,來支持QT的開發,包括但不限于
字符串->QString 動態數組->QVector 鏈表->QList 字典->QMap - 在QString中也提供了 C風格字符串作為參數的構造函數,不顯示構造QString,上述代碼中,C風格字符串也會隱式構造成QString對象?
- 每個標簽都有一個同名的頭文件,但有時候也會被其他頭文件間接包含,
比如: QString 對應的頭文件,已經被很多Qt內置的其他類給間接包含了,因此一般不需要顯示包含QString頭文件
這里還有一個值得思考的點: 一個對象new出來了,但是卻沒有delete,這難道不會造成內存泄漏嗎
- ????在上述代碼中,Qt不會產生內存泄漏,label對象會在合適的時候被析構函數釋放~~
- 之所以能夠把對象釋放掉,主要是因為把這個對象掛到了對象樹上了,交給Qt的對象樹統一管理
- 但如果這個對象是在棧上創建的話,就有可能會存在一些"提前釋放"的問題,此時就會導致對應的控件就在界面上不存在了
- 推薦: 在堆上創建對象,并掛在對象樹上
7.3 驗證對象樹會統一析構
- 注: 上面少寫了個delete
- ?自己實現label類,并繼承QLabel,然后再自主實現析構函數
- 則可以在輸出日志中觀察到 自己實現的析構函數被調用了,則對象樹的確會統一析構
- 這里使用qDebug進行輸出日志(不建議使用cout),還有一個好處->可以進行統一關閉
輸出日志一般是在開發階段,調試程序的時候使用
8. 快捷鍵
- 注釋:ctrl + /
- 運?:ctrl + R
- 編譯:ctrl + B
- 字體縮放:ctrl + ?標滑輪
- 查找:ctrl + F
- 整?移動:ctrl + shift + ?/?
- 幫助?檔:F1
- ?動對?:ctrl + i;
- 同名之間的 .h 和 .cpp 的切換:F4
- ?成函數聲明的對應定義: alt + enter
9.?使?幫助?檔?
-
方法一:光標放到要查詢的類名/?法名上, 直接按 F1(推薦)
-
方法二:Qt Creator 左側邊欄中直接??標單擊 "幫助" 按鈕
-
方法三:找到 Qt Creator 的安裝路徑,在 "bin" ?件夾下找到 assistant.exe,雙擊打開;
10.?Qt 中的命名規范
-
類名:?字??寫,單詞和單詞之間?字??寫;MyClass? MyAdd
-
函數名及變量名:?字??寫,單詞和單詞之間?字??寫;studentCount
11.?Qt 窗?坐標體系
計算機中的坐標系和數學中的坐標系是不一樣的,y軸是向下增長的
?
?
說明一下:?
- ?對于嵌套窗口,其坐標是相對于父窗口來說的
第三節?信號與槽
1. 基本概念
信號:信號就是一個通知機制,比如在某個狀態做了一個什么事情,或者狀態發生了改變,需要有別人來處理的時候,就要發出一個信號,就相當于發出一個通知,信號是沒有實現的,信號的成員函數是不需要被定義的
槽函數:信號發出去以后,一定要做一件事情,而這個事情就是由槽函數來做的,簡單來說就是對信號做出的一種響應
? ? ? ? ?在QT中信號和槽函數一定要先進行綁定才可以的,且一定是先關聯信號再進行綁定(順序不能顛倒),后續只要信號觸發了,Qt就會自動執行槽函數
? ? ? ? 信號與槽這種機制是QT框架很重要的機制,它的優點有:松散耦合,但缺點:效率較低
2. 圖形化操作
? ? ? ? 比如我現在要在窗口中放置一個按鈕,當我點擊這個按鈕的時候,這個窗口就會關閉?
? ? ? ? 注意:在ui界面中改了控件名字,是需要重新構建的,否則控件名將不會生效?
2.1 編寫槽函數
? ? ? ? 在ui界面中選中控件,然后右鍵轉到槽,然后選擇對應的信號
? ? ? ? ?之后在頭文件對應的類中就會自動生成一個信號,并把光標跳轉到自動生成的槽函數中
void MainWindow::on_pushButton_clicked()
{// 關閉窗口this->close();
}
說明一下:
- 通過圖形化界面的方式定義槽函數,的確很方便,很多步驟它都幫我們做完了
- 但在真實的開發環境中我們需要自定義信號,自定槽函數,自己去關聯信號與槽函數,
3. 純代碼操作?
?3.1 connect函數
????????在 Qt 中,QObject 類提供了?個靜態成員函數 connect() ,該函數專??來關聯指定的信號函數和槽函數
connect(const QObject * sender,const char* signal,const QObject* receiver
const char* method, Qt::ConnectionType type = Qt::AutoConnection)
參數說明:
- sender: 描述當前信號是那個控件發出的
- signal: 描述信號類型
- receiver:?描述了那個控件負責處理
- method: 描述了這個控件怎么處理(要處理信號的對象提供成員函數)
3.2 深度理解connect函數參數
- 我們上面傳信號和槽都是傳遞的函數指針,而在C++中是不允許使用2個不同指針類型,相互傳參相互賦值的(函數傳參,本質就是賦值)
- 其實這個函數聲明是以前舊版本的QT的connect函數聲明,以前個信號參數傳參,要搭配要給SIGNAL宏,給槽函數傳參要搭配一個SLOT宏(這樣做的目的是 將 傳入參數 轉成char*)
- connect(button,SIGNAL(&QPuhsButton::clicked),this,SLOT(&Widget::close))
但是后來因為書寫起來太麻煩了,在Qt5時進行了改進
- ?使用模板實現泛型編程,重載構造函數??
- ? 此時connect函數就有一定的參數檢查功能:參數1和參數2不匹配 or 參數3和參數4不匹配,
都會編譯出錯 - 則要求參數2必須是參數1的成員函數,參數4必須是參數3的成員函數
4. 查看信號與槽
?自己平時的積累 + 查看文檔
- ?QPushButton自己本身沒有clicked信號,但是它繼承了QAbstractButton
- 在QAbstractButton中就有一個clicked點擊信號
- 注: 這個QAbstractButton又使繼承QWidget
- ?我們寫的widget在定義的時候就是繼承QWidget的,而QWidget就有close這個關閉槽
- 注: QWidget這個類又是繼承自QObject類的
5. 自定義槽
- 在Qt中,除了通過connect來連接信號槽之外,還可以通過函數名字的方式自動連接
5. 自定義信號
- 自定義信號,本質就是一個函數,只需要聲明就行了,而這個函數的定義,是Qt在編譯過程中,自動生成的(我們無法干預)
- 而作為信號參數,這個函數的返回值,必須是void的,有沒有參數都是可以的,甚至可以支持重載
- Qt內置的信號,不需要手動觸發,而自定義信號,需要手動代碼觸發? emit mySignal();
- 這里使用圖形化方式創建一個按鈕,在通過這個按鈕發送 自定義信號
-
發送信號的操作, 也可以在任意合適的代碼中. 不一定非得在構造函數里
6. 帶參數的信號與槽
-
?帶有參數的信號,要求信號的參數和槽的參數要一致
-
類型,個數要滿足要求(信號的參數個數要多于槽的參數個數)
7. 額外說明
7.1 前置條件
- ?Qt 中如果要讓某個類能夠使用信號槽(可以在類中定義信號和槽函數),則必須要在類最開始的地方,寫下Q_OBJECT宏
- 將這個宏展開,會得到一大段代碼,然后而這一大段代碼又可以展開
7.2 設計目的
- 解耦,: 把觸發 用戶操作的控件 和 處理對應用戶的操作邏輯 解耦合
- 實現"多對多"效果
一個信號,可以connect到多個槽函數上
一個槽函數也可以被多個信號connect - 但是多對多的需求實際中并不常見,所以以后圖形化開發框架都沒有支持多對多
7.3 disconnect斷開連接
主動斷開往往是把信號重新綁定到另一個槽函數上
-
如果沒有 disconnect, 就會構成 一個信號綁定了兩個槽函數. 觸發信號的時候, 兩個槽函數都會執行
7.4 lambda表達式?
定義槽函數的時候?也是可以使用lambda表達式的,
- ?為了解決lambda訪問作用域的問題,就需要引入變量捕捉的語法規則
- lambda語法是c++11中引入的,對于Qt5及其更高版本,默認就是按照c++ 11來編譯的
- 如果使用Qt4或者更老的版本,就需要手動在.pro文件中加上C++的編譯選項:CONFIG += c++11
8. 登錄案例(動手)
? ? ? ? 我們要實現的是一個登錄和注冊的頁面,具體參考下圖:
要求說明:
- ?實現一個登錄系統的主頁面,2個功能,一個登錄,一個注冊
- 登錄成功,需要跳轉到歡迎界面,并歡迎這個登錄的用戶
- 注冊成功,需要跳轉到主頁面,并把注冊到的信息填入對于的輸入框中
- .....?
注意: 信號與槽不能嵌套,且電腦分辨率不同,會對qt designer產生影響
4.1 關鍵點:如何新建一個窗口?
?
說明一下:
- 然后選擇對應的控件就行了?
4.2 關鍵點:如何展示子類的頁面
? ? ? ? 推薦:在父類中添加一個成員變量,類型是子類的類型,然后在構造函數中實例化這個對象,如下:
? ? ? ? 而想要展示一個頁面直接調用show方法,關閉一個頁面直接調用hide方法
4.3 關鍵點:如何把父類的數據傳遞給子類
? ? ? ? 可以使用信號與槽,但這里推薦直接傳遞參數就可以了,處理參數的函數在子類中實現就可以了,?畢竟父類中有個成員變量是指向子類的(具體情況如上)
4.4 關鍵點:如何把子類的數據傳遞給父類
? ? ? ? 推薦:使用自定義信號與槽,首先在子類中自定義一個信號,然后再父類的構造函數中connect自定義信號與槽,然后再子類中要傳數據的地方emit自定義信號,對了槽函數需要在父類中實現
第四節?Qt Core
Qt Core也就是Qt的核心部分
?
1. QString常見函數
1.1 append - 字符串拼接
void function1()
{QString str = "123456";str.append("abc");// 字符串拼接qDebug() << str;
}
1.2 prepend - 頭前拼接
void function2()
{QString str = "123456";str.prepend("abc");// 字符串拼接qDebug() << str;
}
1.3 arg - 格式化
void function3()
{QString str = "姓名:%1,年齡:%2,性別:%3";QString format = str.arg("張三").arg(30).arg("男");qDebug() << format;
}
1.4? contains - 判斷是否包含
void function4()
{QString str = "123abc456";if(str.contains("abc")){qDebug() << "包含abc" << endl;}else{qDebug() << "不包含abc" << endl;}
}
1.5?isEmpty && isNull
void function5()
{QString str1 = "";QString str2;if(str1.isEmpty()){qDebug() << "str1是empty的" << endl;}if(str1.isNull()){qDebug() << "str1是null的" << endl;}if(str2.isEmpty()){qDebug() << "str2是empty的" << endl;}if(str2.isNull()){qDebug() << "str2是null的" << endl;}
}
說明一下:
- isEmpty()是用來判斷字符串是否是空字符串的
- isNull()既能判斷是否是NULL,而且還能判斷是不是空字符串
1.6 startWith -- 判斷是否以xxxxx打頭
void function6()
{QString str = "123456";if(str.startsWith("123")){qDebug() << "str是以12打頭的" << endl;}
}
?
1.7 indexOf - 找xxx出現的位置
void function7()
{QString str1 = "www.baidu.com";//找第一次.出現的位置qDebug() << str1.indexOf(".") << endl;
}
?
說明一下:
- 這個函數就類似于c++中的find函數
- 同理下標索引也是從0開始的
1.8 repalce - 替換
QString &QString::replace(int position, int n, const QString &after)
參數說明:
- 第一個參數是被替換字符串的位置
- 第二個參數是被替換字符串的長度
- 第三個參數是新的字符串
void function8()
{QString str1 = "www.baidu.com";//找第一次.出現的位置QString target = ".";int index = str1.indexOf(target);str1.replace(index,target.length(),"//");qDebug() << str1 << endl;
}
?1.9 split - 分割字符串
void function9()
{QString str = "111:222:333:444";// 將str按照:分割成4個子字符串QStringList list = str.split(":");qDebug() << list << endl;
}
說明一下:
- ?split的返回值是QStringList類型,而這個類型就相當于一個只能存放QString的vector
1.10 string 與 QString相互轉換?
- QString::fromStdString(s);// 把std::string 轉換成QString
- s.toStdString();//把QString轉換成std::string
2. QByteArray
多用于處理字節數組?
void function1()
{QString str = "123456";QByteArray data = str.toUtf8();QString str1 = QString(data);qDebug() << str1 << endl;
}
3. QStringList && QVector
?QStringLis等價于QVector<QString>
void function2()
{QStringList v1 = {"ab"};v1.push_back("cd");for(auto e : v1){qDebug() << e << " ";}QVector<QString> v2 = {"ab"};v2.push_back("cd");for(auto e : v2){qDebug() << e << " ";}
}
?
說明一下:
- ?除了QVector還有QList,QStack等等,前面已經說了QT它又搞了一套新的輪子,
- 其實就是又封裝了一遍