先聲明,本文并非原創(chuàng),純屬搬運(yùn),內(nèi)容來(lái)自一位叫做飛揚(yáng)青春的大神的Gitee主頁(yè),主要是為了收藏下面介紹的100多個(gè)Qt開(kāi)發(fā)經(jīng)驗(yàn)。我本身也從事了兩年了Qt開(kāi)發(fā),再轉(zhuǎn)Qt開(kāi)發(fā)以前用的都是MFC,我仔細(xì)的看了一遍下面列出的各條經(jīng)驗(yàn),只恨看到的太晚了,因?yàn)楹芏喽际亲约翰冗^(guò)的坑。比如qss的ANSI編碼、嵌套窗口中主窗口無(wú)法接收鼠標(biāo)移動(dòng)事件等,又比如我用qss設(shè)置窗口樣式,但是項(xiàng)目每次重新構(gòu)建以后,樣式表就會(huì)不生效等問(wèn)題,也花了自己不少時(shí)間去解決,所以在這里轉(zhuǎn)發(fā)大神的經(jīng)驗(yàn),留作以后參考和逐條的研究,也分享給更多正在學(xué)習(xí)Qt或者正在使用Qt進(jìn)行程序開(kāi)發(fā)的朋友們。
大神主頁(yè):https://gitee.com/feiyangqingyun/qtkaifajingyan?_from=gitee_search
1. 當(dāng)編譯發(fā)現(xiàn)大量錯(cuò)誤的時(shí)候,從第一個(gè)看起,一個(gè)一個(gè)的解決,不要急著去看下一個(gè)錯(cuò)誤,往往后面的錯(cuò)誤都是由于前面的錯(cuò)誤引起的,第一個(gè)解決后很可能都解決了。
2. 定時(shí)器是個(gè)好東西,學(xué)會(huì)好使用它,有時(shí)候用QTimer::singleShot可以解決意想不到的問(wèn)題。
3. 打開(kāi)creator,在構(gòu)建套件的環(huán)境中增加MAKEFLAGS=-j8,可以不用每次設(shè)置多線程編譯。珍愛(ài)時(shí)間和生命。新版的QtCreator已經(jīng)默認(rèn)就是j8。
4. 如果你想順利用QtCreator部署安卓程序,首先你要在AndroidStudio 里面配置成功,把坑全部趟平。
5. 很多時(shí)候找到Qt對(duì)應(yīng)封裝的方法后,記得多看看該函數(shù)的重載,多個(gè)參數(shù)的,你會(huì)發(fā)現(xiàn)不一樣的世界,有時(shí)候會(huì)恍然大悟,原來(lái)Qt已經(jīng)幫我們封裝好了。
6. 可以在pro文件中寫(xiě)上標(biāo)記版本號(hào)+ico圖標(biāo)(Qt5才支持)
VERSION = 2020.10.25
RC_ICONS = main0.ico
7. 管理員運(yùn)行程序,限定在MSVC編譯器
QMAKE_LFLAGS += /MANIFESTUAC:"level='requireAdministrator' uiAccess='false'" #以管理員運(yùn)行
QMAKE_LFLAGS += /SUBSYSTEM:WINDOWS,"5.01" #VS2013 在XP運(yùn)行
8. 運(yùn)行文件附帶調(diào)試輸出窗口
CONFIG += console pro
9. 繪制平鋪背景QPainter::drawTiledPixmap,繪制圓角矩形QPainter::drawRoundedRect(),而不是QPainter::drawRoundRect();
10. 移除舊的樣式
//移除原有樣式
style()->unpolish(ui->btn);
//重新設(shè)置新的該控件的樣式。
style()->polish(ui->btn);
11. 獲取類的屬性
const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i = 0; i < count; ++i) {
QMetaProperty metaproperty = metaobject->property(i);
const char *name = metaproperty.name();
QVariant value = object->property(name);
qDebug() << name << value;
}
12. Qt內(nèi)置圖標(biāo)封裝在QStyle中,大概七十多個(gè)圖標(biāo),可以直接拿來(lái)用。
SP_TitleBarMenuButton,
SP_TitleBarMinButton,
SP_TitleBarMaxButton,
SP_TitleBarCloseButton,
SP_MessageBoxInformation,
SP_MessageBoxWarning,
SP_MessageBoxCritical,
SP_MessageBoxQuestion,
...
13. 根據(jù)操作系統(tǒng)位數(shù)判斷加載
win32 {
contains(DEFINES, WIN64) { DESTDIR = $${PWD}/../../bin64
} else { DESTDIR = $${PWD}/../../bin32 }
}
14. Qt5增強(qiáng)了很多安全性驗(yàn)證,如果出現(xiàn)setGeometry: Unable to set geometry,請(qǐng)將該控件的可見(jiàn)移到加入布局之后。
15. 可以將控件A添加到布局,然后控件B設(shè)置該布局,這種靈活性大大提高了控件的組合度,比如可以在文本框左側(cè)右側(cè)增加一個(gè)搜索按鈕,按鈕設(shè)置圖標(biāo)即可。
QPushButton *btn = new QPushButton;
btn->resize(30, ui->lineEdit->height());
QHBoxLayout *layout = new QHBoxLayout(ui->lineEdit);
layout->setMargin(0);
layout->addStretch();
layout->addWidget(btn);
16. 對(duì)QLCDNumber控件設(shè)置樣式,需要將QLCDNumber的segmentstyle設(shè)置為flat。
17. 巧妙的使用findChildren可以查找該控件下的所有子控件。findChild為查找單個(gè)。
//查找指定類名objectName的控件
QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");
//查找所有QPushButton
QList<QPushButton *> allPButtons = parentWidget.findChildren<QPushButton *>();
//查找一級(jí)子控件,不然會(huì)一直遍歷所有子控件
QList<QPushButton *> childButtons = parentWidget.findChildren<QPushButton *>(QString(), Qt::FindDirectChildrenOnly);
18. 巧妙的使用inherits判斷是否屬于某種類。
QTimer *timer = new QTimer; // QTimer inherits QObject
timer->inherits("QTimer"); // returns true
timer->inherits("QObject"); // returns true
timer->inherits("QAbstractButton"); // returns false
19. 使用弱屬性機(jī)制,可以存儲(chǔ)臨時(shí)的值用于傳遞判斷。可以通過(guò)widget->dynamicPropertyNames()列出所有弱屬性名稱,然后通過(guò)widget->property("name")取出對(duì)應(yīng)的弱屬性的值。
20. 在開(kāi)發(fā)時(shí), 無(wú)論是出于維護(hù)的便捷性, 還是節(jié)省內(nèi)存資源的考慮, 都應(yīng)該有一個(gè) qss 文件來(lái)存放所有的樣式表, 而不應(yīng)該將 setStyleSheet 寫(xiě)的到處都是。如果是初學(xué)階段或者測(cè)試階段可以直接UI上右鍵設(shè)置樣式表,正式項(xiàng)目還是建議統(tǒng)一到一個(gè)qss樣式表文件比較好,統(tǒng)一管理。
21. 如果出現(xiàn)Z-order assignment: is not a valid widget.錯(cuò)誤提示,用記事本打開(kāi)對(duì)應(yīng)的ui文件,找到<zorder></zorder>為空的地方,刪除即可。
22. 善于利用QComboBox的addItem的第二個(gè)參數(shù)設(shè)置用戶數(shù)據(jù),可以實(shí)現(xiàn)很多效果,使用itemData取出來(lái)。
23. 如果用了webengine模塊,發(fā)布程序的時(shí)候帶上QtWebEngineProcess.exe+translations文件夾+resources文件夾。
24. 默認(rèn)Qt是一個(gè)窗體一個(gè)句柄,如果要讓每個(gè)控件都擁有獨(dú)立的句柄,設(shè)置下 a.setAttribute(Qt::AA_NativeWindows);
25. Qt+Android防止程序被關(guān)閉。
#if defined(Q_OS_ANDROID)
QAndroidService a(argc, argv);
return a.exec()
#else
QApplication a(argc, argv);
return a.exec();
#endif
26. 可以對(duì)整體的指示器設(shè)置樣式,例如 *::down-arrow,*::menu-indicator{} *::up-arrow:disabled,*::up-arrow:off{}。
27. 可以執(zhí)行位置設(shè)置背景圖片。
QMainWindow > .QWidget {
background-color: gainsboro;
background-image: url(:/images/pagefold.png);
background-position: top right;
background-repeat: no-repeat
}
28. 嵌入式linux運(yùn)行Qt程序 Qt4寫(xiě)法:./HelloQt -qws & Qt5寫(xiě)法:./HelloQt --platform xcb
29. Qtcreator軟件的配置文件存放在:C:\Users\Administrator\AppData\Roaming\QtProject,有時(shí)候如果發(fā)現(xiàn)出問(wèn)題了,將這個(gè)文件夾刪除后打開(kāi)creator自動(dòng)重新生成即可。
30. QMediaPlayer是個(gè)殼,依賴本地解碼器,視頻這塊默認(rèn)基本上就播放個(gè)MP4,如果要支持其他格式需要下載k-lite或者LAV Filters安裝即可(WIN上,其他系統(tǒng)上自行搜索)。如果需要做功能強(qiáng)勁的播放器,初學(xué)者建議用vlc、mpv,終極大法用ffmpeg。
31. 判斷編譯器類型、編譯器版本、操作系統(tǒng)。
//GCC編譯器
#ifdef __GNUC__
#if __GNUC__ >= 3 // GCC3.0以上
//MSVC編譯器
#ifdef _MSC_VER
#if _MSC_VER >=1000 // VC++4.0以上
#if _MSC_VER >=1100 // VC++5.0以上
#if _MSC_VER >=1200 // VC++6.0以上
#if _MSC_VER >=1300 // VC2003以上
#if _MSC_VER >=1400 // VC2005以上
#if _MSC_VER >=1500 // VC2008以上
#if _MSC_VER >=1600 // VC2010以上
#if _MSC_VER >=1700 // VC2012以上
#if _MSC_VER >=1800 // VC2013以上
#if _MSC_VER >=1900 // VC2015以上
//Borland C++
#ifdef __BORLANDC__
//Cygwin
#ifdef __CGWIN__
#ifdef __CYGWIN32__
//mingw
#ifdef __MINGW32__
//windows
#ifdef _WIN32 //32bit
#ifdef _WIN64 //64bit
#ifdef _WINDOWS //圖形界面程序
#ifdef _CONSOLE //控制臺(tái)程序
//Windows(95/98/Me/NT/2000/XP/Vista)和Windows CE都定義了
#if (WINVER >= 0x030a) // Windows 3.1以上
#if (WINVER >= 0x0400) // Windows 95/NT4.0以上
#if (WINVER >= 0x0410) // Windows 98以上
#if (WINVER >= 0x0500) // Windows Me/2000以上
#if (WINVER >= 0x0501) // Windows XP以上
#if (WINVER >= 0x0600) // Windows Vista以上
//_WIN32_WINNT 內(nèi)核版本
#if (_WIN32_WINNT >= 0x0500) // Windows 2000以上
#if (_WIN32_WINNT >= 0x0501) // Windows XP以上
#if (_WIN32_WINNT >= 0x0600) // Windows Vista以上
32. 在pro中判斷Qt版本及構(gòu)建套件位數(shù)
#打印版本信息
message(qt version: $$QT_VERSION)
#判斷當(dāng)前qt版本號(hào)
QT_VERSION = $$[QT_VERSION]
QT_VERSION = $$split(QT_VERSION, ".")
QT_VER_MAJ = $$member(QT_VERSION, 0)
QT_VER_MIN = $$member(QT_VERSION, 1)
#下面是表示 Qt5.5
greaterThan(QT_VER_MAJ, 4) {
greaterThan(QT_VER_MIN, 4) {
#自己根據(jù)需要做一些處理
}
}
#QT_ARCH是Qt5新增的,在Qt4上沒(méi)效果
#打印當(dāng)前Qt構(gòu)建套件的信息
message($$QT_ARCH)
#表示arm平臺(tái)構(gòu)建套件
contains(QT_ARCH, arm) {}
#表示32位的構(gòu)建套件
contains(QT_ARCH, i386) {}
#表示64位的構(gòu)建套件
contains(QT_ARCH, x86_64) {}
33. Qt最小化后恢復(fù)界面假死凍結(jié),加上代碼
void showEvent(QShowEvent *e)
{
setAttribute(Qt::WA_Mapped);
QWidget::showEvent(e);
}
34. 獲取標(biāo)題欄高度:style()->pixelMetric(QStyle::PM_TitleBarHeight); PM_TitleBarHeight點(diǎn)進(jìn)去你會(huì)發(fā)現(xiàn)新大陸。
35. 設(shè)置高分屏屬性以便支持2K4K等高分辨率,尤其是手機(jī)app。必須寫(xiě)在main函數(shù)的QApplication a(argc, argv);的前面。
#if (QT_VERSION > QT_VERSION_CHECK(5,6,0))
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
36. 如果運(yùn)行程序出現(xiàn) Fault tolerant heap shim applied to current process. This is usually due to previous crashes. 錯(cuò)誤。
辦法:打開(kāi)注冊(cè)表,找到HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers\,選中Layers鍵值,從右側(cè)列表中刪除自己的那個(gè)程序路徑即可。
37. Qt內(nèi)置了QFormLayout表單布局用于自動(dòng)生成標(biāo)簽+輸入框的組合的表單界面。
38. qml播放視頻在linux需要安裝 sudo apt-get install libpulse-dev。
39. 可以直接繼承QSqlQueryModel實(shí)現(xiàn)自定義的QueryModel,比如某一列字體顏色,占位符,其他樣式等,重寫(xiě)QVariant CustomSqlModel::data(const QModelIndex &index, int role) const。
40. Qt5以后提供了類QScroller直接將控件滾動(dòng)。
//禁用橫向滾動(dòng)條
ui->listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//禁用縱向滾動(dòng)條
ui->listWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//設(shè)置橫向按照像素值為單位滾動(dòng)
ui->listWidget->setHorizontalScrollMode(QListWidget::ScrollPerPixel);
//設(shè)置縱向按照像素值為單位滾動(dòng)
ui->listWidget->setVerticalScrollMode(QListWidget::ScrollPerPixel);
//設(shè)置滾動(dòng)對(duì)象以及滾動(dòng)方式為鼠標(biāo)左鍵拉動(dòng)滾動(dòng)
QScroller::grabGesture(ui->listWidget, QScroller::LeftMouseButtonGesture);
//還有個(gè)QScrollerProperties可以設(shè)置滾動(dòng)的一些參數(shù)
41. 如果使用sqlite數(shù)據(jù)庫(kù)不想產(chǎn)生數(shù)據(jù)庫(kù)文件,可以創(chuàng)建內(nèi)存數(shù)據(jù)庫(kù)。
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
42. 清空數(shù)據(jù)表并重置自增ID,sql = truncate table table_name。
43. Qtchart模塊從Qt5.7開(kāi)始自帶,最低編譯要求Qt5.4。在安裝的時(shí)候記得勾選,默認(rèn)不勾選。使用該模塊需要引入命名空間。
#include <QChartView>
QT_CHARTS_USE_NAMESPACE
class CustomChart : public QChartView
44. QPushButton左對(duì)齊文字,需要設(shè)置樣式表QPushButton{text-align:left;}
45. QLabel有三種設(shè)置文本的方法,掌握好Qt的屬性系統(tǒng),舉一反三,可以做出很多效果。
ui->label->setStyleSheet("qproperty-text:hello;");
ui->label->setProperty("text", "hello");
ui->label->setText("hello");
46. 巧妙的用QEventLoop開(kāi)啟事件循環(huán),可以使得很多同步獲取返回結(jié)果而不阻塞界面。QEventLoop內(nèi)部新建了線程執(zhí)行。
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
47. 多種預(yù)定義變量 #if (defined webkit) || (defined webengine),去掉生成空的debug和release目錄 CONFIG -= debug_and_release。
48. 新版的Qtcreator增強(qiáng)了語(yǔ)法檢查,會(huì)彈出很多警告提示等,可以在插件列表中關(guān)閉clang打頭的幾個(gè)即可,Help》About Plugins。也可以設(shè)置代碼檢查級(jí)別,Tools》Options 》C++ 》Code Model。
49. QSqlTableModel的rowCount方法,默認(rèn)最大返回256,如果超過(guò)256,可以將表格拉到底部,會(huì)自動(dòng)加載剩余的,每次最大加載256條數(shù)據(jù),如果需要打印或者導(dǎo)出數(shù)據(jù),記得最好采用sql語(yǔ)句去查詢,而不是使用QSqlTableModel的rowCount方法。不然永遠(yuǎn)最大只會(huì)導(dǎo)出256條數(shù)據(jù)。
如果數(shù)據(jù)量很小,也可以采用如下方法:
//主動(dòng)加載所有數(shù)據(jù),不然獲取到的行數(shù)<=256
while(model->canFetchMore()) {
model->fetchMore();
}
50. 如果需要指定無(wú)邊框窗體,但是又需要保留操作系統(tǒng)的邊框特性,可以自由拉伸邊框,可以使用
setWindowFlags(Qt::CustomizeWindowHint);
51. 在某些http post數(shù)據(jù)的時(shí)候,如果采用的是&字符串連接的數(shù)據(jù)發(fā)送,中文解析亂碼的話,需要將中文進(jìn)行URL轉(zhuǎn)碼。
QString content = "測(cè)試中文";
QString note = content.toUtf8().toPercentEncoding();
52. Qt默認(rèn)不支持大資源文件,比如添加了字體文件,需要pro文件開(kāi)啟。
CONFIG += resources_big
53. Qt中繼承QWidget之后,樣式表不起作用,解決辦法有三個(gè)。強(qiáng)烈推薦方法一。
- 方法一:設(shè)置屬性 this->setAttribute(Qt::WA_StyledBackground, true);
- 方法二:改成繼承QFrame,因?yàn)镼Frame自帶paintEvent函數(shù)已做了實(shí)現(xiàn),在使用樣式表時(shí)會(huì)進(jìn)行解析和繪制。
- 方法三:重新實(shí)現(xiàn)QWidget的paintEvent函數(shù)時(shí),使用QStylePainter繪制。
void Widget::paintEvent(QPaintEvent *)
{
QStyleOption option;
option.initFrom(this);
QPainter painter(this);
style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
}
54. 有時(shí)候在界面上加了彈簧,需要?jiǎng)討B(tài)改變彈簧對(duì)應(yīng)的拉伸策略,對(duì)應(yīng)方法為changeSize,很多人會(huì)選擇使用set開(kāi)頭去找,找不到的。
55. 在使用QFile的過(guò)程中,不建議頻繁的打開(kāi)文件寫(xiě)入然后再關(guān)閉文件,比如間隔5ms輸出日志,IO性能瓶頸很大,這種情況建議先打開(kāi)文件不要關(guān)閉,等待合適的時(shí)機(jī)比如析構(gòu)函數(shù)中或者日期變了需要重新變換日志文件的時(shí)候關(guān)閉文件。不然短時(shí)間內(nèi)大量的打開(kāi)關(guān)閉文件會(huì)很卡,文件越大越卡。
56. 在很多網(wǎng)絡(luò)應(yīng)用程序,需要自定義心跳包來(lái)保持連接,不然斷電或者非法關(guān)閉程序,對(duì)方識(shí)別不到,需要進(jìn)行超時(shí)檢測(cè),但是有些程序沒(méi)有提供心跳協(xié)議,此時(shí)需要啟用系統(tǒng)層的保活程序,此方法適用于TCP連接。
int fd = tcpSocket->socketDescriptor();
int keepAlive = 1; //開(kāi)啟keepalive屬性,缺省值:0(關(guān)閉)
int keepIdle = 5; //如果在5秒內(nèi)沒(méi)有任何數(shù)據(jù)交互,則進(jìn)行探測(cè),缺省值:7200(s)
int keepInterval = 2; //探測(cè)時(shí)發(fā)探測(cè)包的時(shí)間間隔為2秒,缺省值:75(s)
int keepCount = 2; //探測(cè)重試的次數(shù),全部超時(shí)則認(rèn)定連接失效,缺省值:9(次)
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(fd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
57. 如果程序打包好以后彈出提示 This application failed to start because it could not find or load the Qt platform plugin 一般都是因?yàn)閜latforms插件目錄未打包或者打包錯(cuò)了的原因?qū)е碌摹?/p>
58. 非常不建議tr中包含中文,盡管現(xiàn)在的新版Qt支持中文到其他語(yǔ)言的翻譯,但是很不規(guī)范,也不知道TMD是誰(shuí)教的,tr的本意是包含英文,然后翻譯到其他語(yǔ)言比如中文,現(xiàn)在大量的初學(xué)者濫用tr,如果沒(méi)有翻譯的需求,禁用tr,tr需要開(kāi)銷的,Qt默認(rèn)會(huì)認(rèn)為他需要翻譯,會(huì)額外進(jìn)行特殊處理。
59. 很多人Qt和Qt Creator傻傻分不清楚,經(jīng)常問(wèn)Qt什么版本結(jié)果發(fā)一個(gè)Qt Creator的版本過(guò)來(lái),Qt Creator是使用Qt編寫(xiě)的集成開(kāi)發(fā)環(huán)境IDE,和宇宙第一的Visual Studio一樣,他可以是msvc編譯器的(WIN對(duì)應(yīng)的Qt集成安裝環(huán)境中自帶的Qt Cerator是msvc的),也可以是mingw編譯的,還可以是gcc的。如果是自定義控件插件,需要集成到Qt Creator中,必須保證該插件的動(dòng)態(tài)庫(kù)文件(dll或者so等文件)對(duì)應(yīng)的編譯器和Qt版本以及位數(shù)和Qt Creator的版本完全一致才行,否則基本不大可能集成進(jìn)去。特別注意的是Qt集成環(huán)境安裝包中的Qt版本和Qt Creator版本未必完全一致,必須擦亮眼睛看清楚,有些是完全一致的。
60. 超過(guò)兩處相同處理的代碼,建議單獨(dú)寫(xiě)成函數(shù)。代碼盡量規(guī)范精簡(jiǎn),比如 if(a == 123) 要寫(xiě)成 if (123 == a),值在前面,再比如 if (ok == true) 要寫(xiě)成 if (ok),if (ok == false) 要寫(xiě)成 if (!ok)等。
61. 很多人問(wèn)Qt嵌入式平臺(tái)用哪個(gè)好,這里統(tǒng)一回答(當(dāng)前時(shí)間節(jié)點(diǎn)2018年):imx6+335x比較穩(wěn)定,性能高就用RK3288 RK3399,便宜的話就用全志H3,玩一玩可以用樹(shù)莓派香橙派。
62. 對(duì)于大段的注釋代碼,建議用 #if 0 #endif 將代碼塊包含起來(lái),而不是將該段代碼選中然后全部 // ,下次要打開(kāi)這段代碼的話,又需要重新選中一次取消,如果采用的是 #if 0則只要把0改成1即可,效率大大提升。
63. Qt打包發(fā)布,有很多辦法,Qt5以后提供了打包工具windeployqt(linux上為linuxdeployqt,mac上為macdeployqt)可以很方便的將應(yīng)用程序打包,使用下來(lái)發(fā)現(xiàn)也不是萬(wàn)能的,有時(shí)候會(huì)多打包一些沒(méi)有依賴的文件,有時(shí)候又會(huì)忘記打包一些插件尤其是用了qml的情況下,而且不能識(shí)別第三方庫(kù),比如程序依賴ffmpeg,則對(duì)應(yīng)的庫(kù)需要自行拷貝,終極大法就是將你的可執(zhí)行文件復(fù)制到Qt安裝目錄下的bin目錄,然后整個(gè)一起打包,挨個(gè)刪除不大可能依賴的組件,直到刪到正常運(yùn)行為止。
64. Qt中的動(dòng)畫(huà),底層用的是QElapsedTimer定時(shí)器來(lái)完成處理,比如產(chǎn)生一些指定規(guī)則算法的數(shù)據(jù),然后對(duì)屬性進(jìn)行處理。
65. 在繪制無(wú)背景顏色只有邊框顏色的圓形時(shí)候,可以用繪制360度的圓弧替代,效果完全一致。
QRect rect(-radius, -radius, radius * 2, radius * 2);
//以下兩種方法二選一,其實(shí)繪制360度的圓弧=繪制無(wú)背景的圓形
painter->drawArc(rect, 0, 360 * 16);
painter->drawEllipse(rect);
66. 不要把d指針看的很玄乎,其實(shí)就是在類的實(shí)現(xiàn)文件定義了一個(gè)私有類,用來(lái)存放局部變量,個(gè)人建議在做一些小項(xiàng)目時(shí),沒(méi)有太大必要引入這種機(jī)制,會(huì)降低代碼可讀性,增加復(fù)雜性,新手接受項(xiàng)目后會(huì)看的很懵逼。
67. 很多人在繪制的時(shí)候,設(shè)置畫(huà)筆以為就只可以設(shè)置個(gè)單調(diào)的顏色,其實(shí)QPen還可以設(shè)置brush,這樣靈活性就提高不知道多少倍,比如設(shè)置QPen的brush以后,可以使用各種漸變,比如繪制漸變顏色的進(jìn)度條和文字等,而不再是單調(diào)的一種顏色。
68. 很多控件都帶有viewport,比如QTextEdit/QTableWidget/QScrollArea,有時(shí)候?qū)@些控件直接處理的時(shí)候發(fā)現(xiàn)不起作用,需要對(duì)其viewport()設(shè)置才行,比如設(shè)置滾動(dòng)條區(qū)域背景透明,需要使用scrollArea->viewport()->setStyleSheet("background-color:transparent;");而不是scrollArea->setStyleSheet("QScrollArea{background-color:transparent;}");
69. 有時(shí)候設(shè)置了鼠標(biāo)跟蹤setMouseTracking為真,如果該窗體上面還有其他控件,當(dāng)鼠標(biāo)移到其他控件上面的時(shí)候,父類的鼠標(biāo)移動(dòng)事件MouseMove識(shí)別不到了,此時(shí)需要用到HoverMove事件,需要先設(shè)置 setAttribute(Qt::WA_Hover, true);
70. Qt封裝的QDateTime日期時(shí)間類非常強(qiáng)大,可以字符串和日期時(shí)間相互轉(zhuǎn)換,也可以毫秒數(shù)和日期時(shí)間相互轉(zhuǎn)換,還可以1970經(jīng)過(guò)的秒數(shù)和日期時(shí)間相互轉(zhuǎn)換等。
QDateTime dateTime;
QString dateTime_str = dateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
//從字符串轉(zhuǎn)換為毫秒(需完整的年月日時(shí)分秒)
datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toMSecsSinceEpoch();
//從字符串轉(zhuǎn)換為秒(需完整的年月日時(shí)分秒)
datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toTime_t();
//從毫秒轉(zhuǎn)換到年月日時(shí)分秒
datetime.fromMSecsSinceEpoch(1315193829218).toString("yyyy-MM-dd hh:mm:ss:zzz");
//從秒轉(zhuǎn)換到年月日時(shí)分秒(若有zzz,則為000)
datetime.fromTime_t(1315193829).toString("yyyy-MM-dd hh:mm:ss[:zzz]");
71. 在我們使用QList、QStringList、QByteArray等鏈表或者數(shù)組的過(guò)程中,如果只需要取值,而不是賦值,強(qiáng)烈建議使用 at() 取值而不是 [] 操作符,在官方書(shū)籍《C++ GUI Qt 4編程(第二版)》的書(shū)中有特別的強(qiáng)調(diào)說(shuō)明,此教材的原作者據(jù)說(shuō)是Qt開(kāi)發(fā)的核心人員編寫(xiě)的,所以還是比較權(quán)威,至于使用 at() 與使用 [] 操作符速度效率的比較,網(wǎng)上也有網(wǎng)友做過(guò)此類對(duì)比。原文在書(shū)的212頁(yè),這樣描述的:Qt對(duì)所有的容器和許多其他類都使用隱含共享,隱含共享是Qt對(duì)不希望修改的數(shù)據(jù)決不進(jìn)行復(fù)制的保證,為了使隱含共享的作用發(fā)揮得最好,可以采用兩個(gè)新的編程習(xí)慣。第一種習(xí)慣是對(duì)于一個(gè)(非常量的)向量或者列表進(jìn)行只讀存取時(shí),使用 at() 函數(shù)而不用 [] 操作符,因?yàn)镼t的容器類不能辨別 [] 操作符是否將出現(xiàn)在一個(gè)賦值的左邊還是右邊,他假設(shè)最壞的情況出現(xiàn)并且強(qiáng)制執(zhí)行深層賦值,而 at() 函數(shù)則不被允許出現(xiàn)在一個(gè)賦值的左邊。
72. 如果是dialog窗體,需要在exec以后還能讓其他代碼繼續(xù)執(zhí)行,請(qǐng)?jiān)赿ialog窗體exec前增加一行代碼,否則會(huì)阻塞窗體消息。
QDialog dialog;
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
73. 安全的刪除Qt的對(duì)象類,強(qiáng)烈建議使用deleteLater而不是delete,因?yàn)閐eleteLater會(huì)選擇在合適的時(shí)機(jī)進(jìn)行釋放,而delete會(huì)立即釋放,很可能會(huì)出錯(cuò)崩潰。如果要批量刪除對(duì)象集合,可以用qDeleteAll,比如 qDeleteAll(btns);
74. 在QTableView控件中,如果需要自定義的列按鈕、復(fù)選框、下拉框等其他模式顯示,可以采用自定義委托QItemDelegate來(lái)實(shí)現(xiàn),如果需要禁用某列,則在自定義委托的重載createEditor函數(shù)返回0即可。自定義委托對(duì)應(yīng)的控件在進(jìn)入編輯狀態(tài)的時(shí)候出現(xiàn),如果想一直出現(xiàn),則需要重載paint函數(shù)用drawPrimitive或者drawControl來(lái)繪制。
75. 將 QApplication::style() 對(duì)應(yīng)的drawPrimitive、drawControl、drawItemText、drawItemPixmap等幾個(gè)方法用熟悉了,再結(jié)合QStyleOption屬性,可以玩轉(zhuǎn)各種自定義委托,還可以直接使用paint函數(shù)中的painter進(jìn)行各種繪制,各種牛逼的表格、樹(shù)狀列表、下拉框等,絕對(duì)屌炸天。QApplication::style()->drawControl 的第4個(gè)參數(shù)如果不設(shè)置,則繪制出來(lái)的控件不會(huì)應(yīng)用樣式表。
76. 心中有坐標(biāo),萬(wàn)物皆painter,強(qiáng)烈建議在學(xué)習(xí)自定義控件繪制的時(shí)候,將qpainter.h頭文件中的函數(shù)全部看一遍、試一遍、理解一遍,這里邊包含了所有Qt內(nèi)置的繪制的接口,對(duì)應(yīng)的參數(shù)都試一遍,你會(huì)發(fā)現(xiàn)很多新大陸,會(huì)大大激發(fā)你的繪制的興趣,猶如神筆馬良一般,策馬崩騰遨游代碼繪制的世界。
77. 在使用setItemWidget或者setCellWidget的過(guò)程中,有時(shí)候會(huì)發(fā)現(xiàn)設(shè)置的控件沒(méi)有居中顯示而是默認(rèn)的左對(duì)齊,而且不會(huì)自動(dòng)拉伸填充,對(duì)于追求完美的程序員來(lái)說(shuō),這個(gè)可不大好看,有個(gè)終極通用辦法就是,將這個(gè)控件放到一個(gè)widget的布局中,然后將widget添加到item中,這樣就完美解決了,而且這樣可以組合多個(gè)控件產(chǎn)生復(fù)雜的控件。
//實(shí)例化進(jìn)度條控件
QProgressBar *progress = new QProgressBar;
//增加widget+布局巧妙實(shí)現(xiàn)居中
QWidget *widget = new QWidget;
QHBoxLayout *layout = new QHBoxLayout;
layout->setSpacing(0);
layout->setMargin(0);
layout->addWidget(progress);
widget->setLayout(layout);
ui->tableWidget->setCellWidget(0, 0, widget);
78. 很多時(shí)候需要在已知背景色的情況下,能夠清晰的繪制文字,這個(gè)時(shí)候需要計(jì)算對(duì)應(yīng)的文字顏色。
//根據(jù)背景色自動(dòng)計(jì)算合適的前景色
double gray = (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255;
QColor textColor = gray > 0.5 ? Qt::black : Qt::white;
79. 對(duì)QTableView或者QTableWidget禁用列拖動(dòng)。
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
ui->tableView->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed);
#else
ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
#endif
80. 從Qt4轉(zhuǎn)到Qt5,有些類的方法已經(jīng)廢棄或者過(guò)時(shí)了,如果想要在Qt5中啟用Qt4的方法,比如QHeadVew的setMovable,可以在你的pro或者pri文件中加上一行即可:DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
81. Qt中的QColor對(duì)顏色封裝的很完美,支持各種轉(zhuǎn)換,比如rgb、hsb、cmy、hsl,對(duì)應(yīng)的是toRgb、toHsv、toCmyk、toHsl,還支持透明度設(shè)置,顏色值還能轉(zhuǎn)成16進(jìn)制格式顯示。
QColor color(255, 0, 0, 100);
qDebug() << color.name() << color.name(QColor::HexArgb);
//輸出 #ff0000 #64ff0000
82. QVariant類型異常的強(qiáng)大,可以說(shuō)是萬(wàn)能的類型,在進(jìn)行配置文件的存儲(chǔ)的時(shí)候,經(jīng)常會(huì)用到QVariant的轉(zhuǎn)換,QVariant默認(rèn)自帶了toString、toFloat等各種轉(zhuǎn)換,但是還是不夠,比如有時(shí)候需要從QVariant轉(zhuǎn)到QColor,而卻沒(méi)有提供toColor的函數(shù),這個(gè)時(shí)候就要用到萬(wàn)能辦法。
if (variant.typeName() == "QColor") {
QColor color = variant.value<QColor>();
QFont font = variant.value<QFont>();
QString nodeValue = color.name(QColor::HexArgb);
}
83. Qt中的QString和const char *之間轉(zhuǎn)換,最好用toStdString().c_str()而不是toLocal8Bit().constData(),比如在setProperty中如果用后者,字符串中文就會(huì)不正確,英文正常。
84. Qt的信號(hào)槽機(jī)制非常牛逼,也是Qt的獨(dú)特的核心功能之一,有時(shí)候我們?cè)诤芏啻绑w中傳遞信號(hào)來(lái)實(shí)現(xiàn)更新或者處理,如果窗體層級(jí)比較多,比如窗體A的父類是窗體B,窗體B的父類是窗體C,窗體C有個(gè)子窗體D,如果窗體A一個(gè)信號(hào)要傳遞給窗體D,問(wèn)題來(lái)了,必須先經(jīng)過(guò)窗體B中轉(zhuǎn)到窗體C再到窗體D才行,這樣的話各種信號(hào)關(guān)聯(lián)信號(hào)的connect會(huì)非常多而且管理起來(lái)比較亂,可以考慮增加一個(gè)全局的單例類AppEvent,公共的信號(hào)放這里,然后窗體A對(duì)應(yīng)信號(hào)綁定到AppEvent,窗體D綁定AppEvent的信號(hào)到對(duì)應(yīng)的槽函數(shù)即可,干凈清爽整潔。
85. QTextEdit右鍵菜單默認(rèn)英文的,如果想要中文顯示,加載widgets.qm文件即可,一個(gè)Qt程序中可以安裝多個(gè)翻譯文件,不沖突。
86. Qt中有個(gè)全局的焦點(diǎn)切換信號(hào)focusChanged,可以用它做自定義的輸入法。Qt4中默認(rèn)會(huì)安裝輸入法上下文,比如在main函數(shù)打印a.inputContext會(huì)顯示值,這個(gè)默認(rèn)安裝的輸入法上下文,會(huì)攔截兩個(gè)牛逼的信號(hào)QEvent::RequestSoftwareInputPanel和QEvent::CloseSoftwareInputPanel,以至于就算你安裝了全局的事件過(guò)濾器依然識(shí)別不到這兩個(gè)信號(hào),你只需要在main函數(shù)執(zhí)行a.setInputContext(0)即可,意思是安裝輸入法上下文為空。
87. 在Qt5.10以后,表格控件QTableWidget或者QTableView的默認(rèn)最小列寬改成了15,以前的版本是0,所以在新版的qt中,如果設(shè)置表格的列寬過(guò)小,不會(huì)應(yīng)用,取的是最小的列寬。所以如果要設(shè)置更小的列寬需要重新設(shè)置ui->tableView->horizontalHeader()->setMinimumSectionSize(0);
88. Qt源碼中內(nèi)置了一些未公開(kāi)的不能直接使用的黑科技,都藏在對(duì)應(yīng)模塊的private中,比如gui-private widgets-private等,比如zip文件解壓類QZipReader、壓縮類QZipWriter就在gui-private模塊中,需要在pro中引入QT += gui-private才能使用。
#include "QtGui/private/qzipreader_p.h"
#include "QtGui/private/qzipwriter_p.h"
QZipReader reader(dirPath);
QString path("");
//解壓文件夾到當(dāng)前目錄
reader.etractAll(path);
//文件夾名稱
QZipReader::FileInfo fileInfo = reader.entryInfoAt(0);
//解壓文件
QFile file(filePath);
file.open(QIODevice::WriteOnly);
file.write(reader.fileData(QString::fromLocal8Bit("%1").arg(filePath)));
file.close();
reader.close();
QZipWriter *writer = new QZipWriter(dirPath);
//添加文件夾
writer->addDirectory(unCompress);
//添加文件
QFile file(filePath);
file.open(QIODevice::ReadOnly);
writer->addFile(data, file.readAll());
file.close();
writer->close();
89. 理論上串口和網(wǎng)絡(luò)收發(fā)數(shù)據(jù)都是默認(rèn)異步的,操作系統(tǒng)自動(dòng)調(diào)度,完全不會(huì)卡住界面,網(wǎng)上那些說(shuō)收發(fā)數(shù)據(jù)卡住界面主線程的都是扯幾把蛋,真正的耗時(shí)是在運(yùn)算以及運(yùn)算后的處理,而不是收發(fā)數(shù)據(jù),在一些小數(shù)據(jù)量運(yùn)算處理的項(xiàng)目中,一般不建議動(dòng)用線程去處理,線程需要調(diào)度開(kāi)銷的,不要什么東西都往線程里邊扔,線程不是萬(wàn)能的。只有當(dāng)真正需要將一些很耗時(shí)的操作比如編碼解碼等,才需要移到線程處理。
90. 在構(gòu)造函數(shù)中獲取控件的寬高很可能是不正確的,需要在控件首次顯示以后再獲取才是正確的,控件是在首次顯示以后才會(huì)設(shè)置好正確的寬高值,記住是在首次顯示以后,而不是構(gòu)造函數(shù)或者程序啟動(dòng)好以后,如果程序啟動(dòng)好以后有些容器控件比如QTabWidget中的沒(méi)有顯示的頁(yè)面的控件,你去獲取寬高很可能也是不正確的,萬(wàn)無(wú)一失的辦法就是首次顯示以后去獲取。
91. 數(shù)據(jù)庫(kù)處理一般建議在主線程,如果非要在其他線程,務(wù)必記得打開(kāi)數(shù)據(jù)庫(kù)也要在那個(gè)線程,即在那個(gè)線程使用數(shù)據(jù)庫(kù)就在那個(gè)線程打開(kāi),不能打開(kāi)數(shù)據(jù)庫(kù)在主線程,執(zhí)行sql在子線程,很可能出問(wèn)題。
92. 新版的QTcpServer類在64位版本的Qt下很可能不會(huì)進(jìn)入incomingConnection函數(shù),那是因?yàn)镼t5對(duì)應(yīng)的incomingConnection函數(shù)參數(shù)變了,由之前的int改成了qintptr,改成qintptr有個(gè)好處,在32位上自動(dòng)是quint32而在64位上自動(dòng)是quint64,如果在Qt5中繼續(xù)寫(xiě)的參數(shù)是int則在32位上沒(méi)有問(wèn)題在64位上才有問(wèn)題,所以為了兼容Qt4和Qt5,必須按照不一樣的參數(shù)寫(xiě)。
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
void incomingConnection(qintptr handle);
#else
void incomingConnection(int handle);
#endif
93. Qt支持所有的界面控件比如QPushButton、QLineEdit自動(dòng)關(guān)聯(lián) on_控件名_信號(hào)(參數(shù)) 信號(hào)槽,比如按鈕的單擊信號(hào) on_pushButton_clicked(),然后直接實(shí)現(xiàn)槽函數(shù)即可。
94. QWebEngineView控件由于使用了opengl,在某些電腦上可能由于opengl的驅(qū)動(dòng)過(guò)低會(huì)導(dǎo)致花屏或者各種奇奇怪怪的問(wèn)題,比如showfullscreen的情況下鼠標(biāo)右鍵失效,需要在main函數(shù)啟用軟件opengl渲染。
#if (QT_VERSION > QT_VERSION_CHECK(5,4,0))
//下面兩種方法都可以,Qt默認(rèn)采用的是AA_UseDesktopOpenGL
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
//QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
#endif
QApplication a(argc, argv);
另外一個(gè)方法解決 全屏+QWebEngineView控件一起會(huì)產(chǎn)生右鍵菜單無(wú)法彈出的bug,需要上移一個(gè)像素
QRect rect = qApp->desktop()->geometry();
rect.setY(-1);
rect.setHeight(rect.height());
this->setGeometry(rect);
95. QStyle內(nèi)置了很多方法用處很大,比如精確獲取滑動(dòng)條鼠標(biāo)按下處的值。
QStyle::sliderValueFromPosition(minimum(), maximum(), event->x(), width());
96. 用QFile讀寫(xiě)文件的時(shí)候,推薦用QTextStream文件流的方式來(lái)讀寫(xiě)文件,速度快很多,基本上會(huì)有30%的提升,文件越大性能區(qū)別越大。
//從文件加載英文屬性與中文屬性對(duì)照表
QFile fle(":/propertyname.txt");
if (file.open(QFile::ReadOnly)) {
//QTextStream方法讀取速度至少快30%
#if 0
while(!file.atEnd()) {
QString line = file.readLine();
appendName(line);
}
#else
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
appendName(line);
}
#endif
file.close();
}
97. 用QFile.readAll()讀取QSS文件默認(rèn)是ANSI格式,不支持UTF8,如果在QtCreator中打開(kāi)qss文件來(lái)編輯保存,這樣很可能導(dǎo)致qss加載以后沒(méi)有效果。
void frmMain::initStyle()
{
//加載樣式表
QString qss;
//QFile file(":/qss/psblack.css");
//QFile file(":/qss/flatwhite.css");
QFile file(":/qss/lightblue.css");
if (file.open(QFile::ReadOnly)) {
#if 1
//用QTextStream讀取樣式文件不用區(qū)分文件編碼 帶bom也行
QStringList list;
QTextStream in(&file);
//in.stCodec("utf-8");
while (!in.atEnd()) {
QString line;
in >> line;
list << line;
}
qss = list.join("\n");
#else
//用readAll讀取默認(rèn)支持的是ANSI格式,如果不小心用creator打開(kāi)編輯過(guò)了很可能打不開(kāi)
qss = QLatin1String(file.readAll());
#endif
QString paletteColor = qss.mid(20, 7);
qApp->setPalette(QPalette(QColor(paletteColor)));
qApp->setStyleSheet(qss);
file.close();
}
}
98. QString內(nèi)置了很多轉(zhuǎn)換函數(shù),比如可以調(diào)用toDouble轉(zhuǎn)為double數(shù)據(jù),但是當(dāng)你轉(zhuǎn)完并打印的時(shí)候你會(huì)發(fā)現(xiàn)精確少了,只剩下三位了,其實(shí)原始數(shù)據(jù)還是完整的精確度的,只是打印的時(shí)候優(yōu)化成了三位,如果要保證完整的精確度,可以調(diào)用 qSetRealNumberPrecision 函數(shù)設(shè)置精確度位數(shù)即可。
QString s1, s2;
s1 = "666.5567124";
s2.setNum(888.5632123, 'f', 7);
qDebug() << qSetRealNumberPrecision(10) << s1.toDouble() << s2.toDouble();
99. 用QScriptValueIterator解析數(shù)據(jù)的時(shí)候,會(huì)發(fā)現(xiàn)總是會(huì)多一個(gè)節(jié)點(diǎn)內(nèi)容,并且內(nèi)容為空,如果需要跳過(guò)則增加一行代碼。
while (it.hasNext()) {
it.next();
if (it.flags() & QScriptValue::SkipInEnumeration)
continue;
qDebug() << it.name();
}
100. setPixmap是最糟糕的貼圖方式,一般只用來(lái)簡(jiǎn)單的不是很頻繁的貼圖,頻繁的建議painter繪制,默認(rèn)雙緩沖,在高級(jí)點(diǎn)用opengl繪制,利用GPU。
101. 如果需要在尺寸改變的時(shí)候不重繪窗體,則設(shè)置屬性即可 this->setAttribute(Qt::WA_StaticContents, true); 這樣可以避免可以避免對(duì)已經(jīng)顯示區(qū)域的重新繪制。
102. 默認(rèn)程序中獲取焦點(diǎn)以后會(huì)有虛邊框,如果看著覺(jué)得礙眼不舒服可以去掉,設(shè)置樣式即可:setStyleSheet("*{outline:0px;}");
103. Qt表格控件一些常用的設(shè)置封裝,QTableWidget繼承自QTableView,所以下面這個(gè)函數(shù)支持傳入QTableWidget。
void QUIHelper::initTableView(QTableView *tableView, int rowHeight, bool headVisible, bool edit)
{
//奇數(shù)偶數(shù)行顏色交替
tableView->setAlternatingRowColors(false);
//垂直表頭是否可見(jiàn)
tableView->verticalHeader()->setVisible(headVisible);
//選中一行表頭是否加粗
tableView->horizontalHeader()->setHighlightSections(false);
//最后一行拉伸填充
tableView->horizontalHeader()->setStretchLastSection(true);
//行標(biāo)題最小寬度尺寸
tableView->horizontalHeader()->setMinimumSectionSize(0);
//行標(biāo)題最大高度
tableView->horizontalHeader()->setMaximumHeight(rowHeight);
//默認(rèn)行高
tableView->verticalHeader()->setDefaultSectionSize(rowHeight);
//選中時(shí)一行整體選中
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
//只允許選擇單個(gè)
tableView->setSelectionMode(QAbstractItemView::SingleSelection);
//表頭不可單擊
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
tableView->horizontalHeader()->setSectionsClickable(false);
#else
tableView->horizontalHeader()->setClickable(false);
#endif
//鼠標(biāo)按下即進(jìn)入編輯模式
if (edit) {
tableView->setEditTriggers(QAbstractItemView::CurrentChanged | QAbstractItemView::DoubleClicked);
} else {
tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
}
}
104. 在一些大的項(xiàng)目中,可能嵌套了很多子項(xiàng)目,有時(shí)候會(huì)遇到子項(xiàng)目依賴其他子項(xiàng)目的時(shí)候,比如一部分子項(xiàng)目用來(lái)生成動(dòng)態(tài)庫(kù),一部分子項(xiàng)目依賴這個(gè)動(dòng)態(tài)庫(kù)進(jìn)行編譯,此時(shí)就需要子項(xiàng)目按照順序編譯。
TEMPLATE = subdirs
#設(shè)置ordered參數(shù)以后會(huì)依次編譯 demo designer examples
CONFIG += ordered
SUBDIRS += demo
SUBDIRS += designer
SUBDIRS += examples
105. MSVC編譯器的選擇說(shuō)明
- 如果是32位的Qt則編譯器選擇x86開(kāi)頭的
- 如果是64位的Qt則編譯器選擇amd64開(kāi)頭的
- 具體是看安裝的Qt構(gòu)建套件版本以及目標(biāo)運(yùn)行平臺(tái)的系統(tǒng)位數(shù)和架構(gòu)
- 一般現(xiàn)在的電腦默認(rèn)以64位的居多,選擇amd64即可
- 如果用戶需要兼容32位的系統(tǒng)則建議選擇32位的Qt,這樣即可在32位也可以在64位系統(tǒng)運(yùn)行
- 諸葛大佬補(bǔ)充:x86/x64都是編譯環(huán)境和運(yùn)行環(huán)境相同,沒(méi)有或。帶下劃線的就是交叉編譯,前面是編譯環(huán)境,后面是運(yùn)行環(huán)境。
| 名稱 | 說(shuō)明 |
| ------ | ------ |
|x86|32/64位系統(tǒng)上編譯在32/64位系統(tǒng)上運(yùn)行|
|x86_amd64|32/64位系統(tǒng)上編譯在64位系統(tǒng)上運(yùn)行|
|x86_arm|32/64位系統(tǒng)上編譯在arm系統(tǒng)上運(yùn)行|
|amd64|64位系統(tǒng)上編譯在64位系統(tǒng)上運(yùn)行|
|amd64_x86|64位系統(tǒng)上編譯在32/64位系統(tǒng)上運(yùn)行|
|amd64_arm|64位系統(tǒng)上編譯在arm系統(tǒng)上運(yùn)行|
106. 很多時(shí)候用QDialog的時(shí)候會(huì)發(fā)現(xiàn)阻塞了消息,而有的時(shí)候我們希望是后臺(tái)的一些消息繼續(xù)運(yùn)行不要終止,此時(shí)需要做個(gè)設(shè)置。
QDialog dialog;
dialog.setWindowModality(Qt::WindowModal);
107. 很多初學(xué)者甚至幾年工作經(jīng)驗(yàn)的人,對(duì)多線程有很深的誤解和濫用,尤其是在串口和網(wǎng)絡(luò)通信這塊,什么都往多線程里面丟,一旦遇到界面卡,就把數(shù)據(jù)收發(fā)啥的都搞到多線程里面去,殊不知絕大部分時(shí)候那根本沒(méi)啥用,因?yàn)闆](méi)找到出問(wèn)題的根源。
- 如果你沒(méi)有使用wait***函數(shù)的話,大部分的界面卡都出在數(shù)據(jù)處理和展示中,比如傳過(guò)來(lái)的是一張圖片的數(shù)據(jù),你需要將這些數(shù)據(jù)轉(zhuǎn)成圖片,這個(gè)肯定是耗時(shí)的;
- 還有就是就收到的數(shù)據(jù)曲線繪制出來(lái),如果過(guò)于頻繁或者間隔過(guò)短,肯定會(huì)給UI造成很大的壓力的,最好的辦法是解決如何不要頻繁繪制UI比如合并數(shù)據(jù)一起繪制等;
- 如果是因?yàn)槔L制UI造成的卡,那多線程也是沒(méi)啥用的,因?yàn)閁I只能在主線程;
- 串口和網(wǎng)絡(luò)的數(shù)據(jù)收發(fā)默認(rèn)都是異步的,由操作系統(tǒng)調(diào)度的,如果數(shù)據(jù)處理復(fù)雜而且數(shù)據(jù)量大,你要做的是將數(shù)據(jù)處理放到多線程中;
- 如果沒(méi)有嚴(yán)格的數(shù)據(jù)同步需求,根本不需要調(diào)用wait***之類的函數(shù)來(lái)立即發(fā)送和接收數(shù)據(jù),實(shí)際需求中大部分的應(yīng)用場(chǎng)景其實(shí)異步收發(fā)數(shù)據(jù)就足夠了;
- 有嚴(yán)格數(shù)據(jù)同步需求的場(chǎng)景還是放到多線程會(huì)好一些,不然你wait***就卡在那邊了;
- 多線程是需要占用系統(tǒng)資源的,理論上來(lái)說(shuō),如果線程數(shù)量超過(guò)了CPU的核心數(shù)量,其實(shí)多線程調(diào)度可能花費(fèi)的時(shí)間更多,各位在使用過(guò)程中要權(quán)衡利弊;
108. 在嵌入式linux上,如果設(shè)置了無(wú)邊框窗體,而該窗體中又有文本框之類的,發(fā)現(xiàn)沒(méi)法產(chǎn)生焦點(diǎn)進(jìn)行輸入,此時(shí)需要主動(dòng)激活窗體才行。
//這種方式設(shè)置的無(wú)邊框窗體在嵌入式設(shè)備上無(wú)法產(chǎn)生焦點(diǎn)
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
//需要在show以后主動(dòng)激活窗體
w->show();
w->activateWindow();
109. QString的replace函數(shù)會(huì)改變?cè)址杏洠诜祷靥鎿Q后的新字符串的同時(shí)也會(huì)改變?cè)址业墓怨裕?/p>
110. QGraphicsEffect類的相關(guān)效果很炫,可以實(shí)現(xiàn)很多效果比如透明、漸變、陰影等,但是該類很耗CPU,如果不是特別需要一般不建議用,就算用也是要用在該部件后期不會(huì)發(fā)生頻繁繪制的場(chǎng)景,不然會(huì)讓你哭暈在廁所。
111. 在不同的平臺(tái)上文件路徑的斜杠也是不一樣的,比如linux系統(tǒng)一般都是 / 斜杠,而在windows上都是 \ 兩個(gè)反斜杠,Qt本身程序內(nèi)部無(wú)論在win還是linux都支持 / 斜杠的路徑,但是一些第三方庫(kù)的話可能需要轉(zhuǎn)換成對(duì)應(yīng)系統(tǒng)的路徑,這就需要用到斜杠轉(zhuǎn)換,Qt當(dāng)然內(nèi)置類方法。
QString path = "C:/temp/test.txt";
path = QDir::toNativeSeparators(path);
//輸出 C:\\temp\\test.txt
QString path = "C:\\temp\\test.txt";
path = QDir::toNativeSeparators(path);
//輸出 C:/temp/test.txt
112. 巧用QMetaObject::invokeMethod方法可以實(shí)現(xiàn)很多效果,包括同步和異步執(zhí)行,比如有個(gè)應(yīng)用場(chǎng)景是在回調(diào)中,需要異步調(diào)用一個(gè)public函數(shù),如果直接調(diào)用的話會(huì)發(fā)現(xiàn)不成功,此時(shí)需要使用 QMetaObject::invokeMethod(obj, "fun", Qt::QueuedConnection); 這種方式來(lái)就可以。invokeMethod函數(shù)有很多重載參數(shù),可以傳入返回值和執(zhí)行方法的參數(shù)等。
113. Qt5中的信號(hào)是public的,可以在需要的地方直接emit即可,而在Qt4中信號(hào)是protected的,不能直接使用,需要定義一個(gè)public函數(shù)來(lái)emit。
114. Qt5.15版本開(kāi)始官方不再提供安裝包,只提供源碼,可以自行編譯或者在線安裝,估計(jì)每次編譯各種版本太麻煩,更多的是為了統(tǒng)計(jì)收集用戶使用信息比如通過(guò)在線安裝,后期可能會(huì)逐步加大商業(yè)化力度。
t 面試指南(內(nèi)含面試題)
QT開(kāi)發(fā)崗位需要掌握C++語(yǔ)言、QT框架、GUI編程、多線程編程等技能。此外,對(duì)于不同的崗位還需要掌握不同的領(lǐng)域知識(shí),如網(wǎng)絡(luò)編程、數(shù)據(jù)庫(kù)編程等。
首先,需要了解公司的招聘要求和崗位職責(zé),了解面試流程和面試形式。其次,需要準(zhǔn)備面試所需的知識(shí)和技能,可以通過(guò)閱讀相關(guān)書(shū)籍、參加培訓(xùn)課程、做練習(xí)題等方式進(jìn)行。最后,需要進(jìn)行模擬面試,找到自己的不足之處并加以改進(jìn)。
面試題:(需要pdf文檔的可以進(jìn)企鵝937552610裙嗱)
1、進(jìn)程和線程的同步方式
進(jìn)程:1)管道,是內(nèi)核里的一串緩存
2)消息隊(duì)列
3)共享內(nèi)存
4)信號(hào)量機(jī)制
5)信號(hào)
6)socket
線程:1)等待通知機(jī)制
2)共享內(nèi)存
3)管道
5)并發(fā)工具
信號(hào)量、讀寫(xiě)鎖、互斥鎖和條件變量
線程的死鎖概念 :線程間相互等待臨界資源而造成彼此無(wú)法繼續(xù)執(zhí)行
方式一
1)創(chuàng)建一個(gè)線程類的子對(duì)象,繼承QThread:
2)重寫(xiě)父類的run()方法,在該函數(shù)內(nèi)部編寫(xiě)子線程要處理的具體業(yè)務(wù)流程
3)在主線程中創(chuàng)建子線程對(duì)象,new一個(gè)
4)啟動(dòng)子線程,調(diào)用start()方法
方式二
1)創(chuàng)建一個(gè)新的類,QObject派生
2)類中添加一個(gè)公有的自定義成員函數(shù),函數(shù)體就是子線程中執(zhí)行的業(yè)務(wù)邏輯
3)主線程中創(chuàng)建一個(gè) QThread 對(duì)象,就是子線程對(duì)象
4)在主線程中創(chuàng)建工作的類對(duì)象,不要給創(chuàng)建的對(duì)象指定父對(duì)象
5)Mywork對(duì)象移動(dòng)到創(chuàng)建的子線程對(duì)象中,需要調(diào)用QObject類提供的 moveToThread() 方法
注意事項(xiàng):
業(yè)務(wù)對(duì)象, 構(gòu)造的時(shí)候不能指定父對(duì)象
子線程中不能處理ui窗口(ui相關(guān)的類)
子線程中只能處理一些數(shù)據(jù)相關(guān)的操作, 不能涉及窗口
2、什么是堆棧
棧區(qū)(stack)
堆區(qū)(heap)抽象數(shù)據(jù)結(jié)構(gòu):后進(jìn)先出
全局區(qū)(靜態(tài)區(qū))(static)
文字常量區(qū)
程序代碼區(qū)
棧是自動(dòng)分配釋放,一級(jí)緩存,類似數(shù)組的結(jié)構(gòu)。
堆是由程序員分配釋放,二級(jí)緩存,速度慢些,先進(jìn)后出。
3、常用的排序
插入排序、希爾排序、選擇排序、冒泡排序、堆排序、快速排序
冒泡排序:
時(shí)間復(fù)雜度:最好O(n),最壞O(n的2次方)。
空間復(fù)雜度:O(1);
4、數(shù)組和鏈表的區(qū)別
1)數(shù)組連續(xù)儲(chǔ)存、固定長(zhǎng)度、增刪需要移動(dòng)其他元素。鏈表動(dòng)態(tài)分配,不連續(xù),需要malloc或
new申請(qǐng)內(nèi)存,不用時(shí)要free或delete.
2)數(shù)組訪問(wèn)效率高,鏈表刪插效率高
3)數(shù)組利用下標(biāo)定位,查找的時(shí)間復(fù)雜度是O(1),鏈表通過(guò)遍歷定位元素,查找的時(shí)間復(fù)雜度是O(N)。
4)數(shù)組插入和刪除需要移動(dòng)其他元素,時(shí)間復(fù)雜度是O(N),鏈表的插入或刪除不需要移動(dòng)其他元素,時(shí)間復(fù)雜度是O(1)。
5、回調(diào)函數(shù)的三種典型使用場(chǎng)景:
1)實(shí)現(xiàn)函數(shù)功能重定義
2)擴(kuò)展函數(shù)功能
3)實(shí)現(xiàn)程序分層設(shè)計(jì)
6、區(qū)分static和const
static :
(1) 修飾全局變量:變量只在本模塊內(nèi)可見(jiàn),在定義不需要與其他文件共享的全局變量時(shí),加上 static 關(guān)鍵字能夠有效地降低程序模塊之間的耦合,避免不同文件同名變量的沖突,且不會(huì)誤使用。
(2) 修飾局部變量:變量在全局?jǐn)?shù)據(jù)區(qū)分配內(nèi)存空間,編譯器自動(dòng)對(duì)其初始化,其作用域?yàn)榫植孔饔糜颍?dāng)定義它的函數(shù)結(jié)束時(shí),其作用域隨之結(jié)束。
(3) 修飾函數(shù):函數(shù)的使用方式與全局變量類似,在函數(shù)的返回類型前加上 static,就是靜態(tài)函數(shù),靜態(tài)函數(shù)只能在聲明它的文件中可見(jiàn),其他文件不能引用該函數(shù),不同的文件可以使用相同名字的靜態(tài)函數(shù),互不影響。
const :
被 const 修飾的變量是只讀變量,本質(zhì)還是變量,有人稱其為常變量,和普通變量的區(qū)別在于常變量不能用于左值,其余的用法和普通常量一樣,變量名不可以改變。
7、工作中有沒(méi)有使用過(guò)動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)?能不能簡(jiǎn)單說(shuō)下兩者的區(qū)別?
答:靜態(tài)庫(kù):在鏈接階段將匯編生成的目標(biāo)文件.o與引用庫(kù)一起鏈接打包到可執(zhí)行文件中,可簡(jiǎn)單看成(.o或者.obj文件的集合)。(1)對(duì)函數(shù)庫(kù)的鏈接是放在編譯時(shí)期完成的(2)程序在運(yùn)行時(shí)與函數(shù)庫(kù)沒(méi)有瓜葛,移植方便(3)浪費(fèi)空間和資源
動(dòng)態(tài)庫(kù):(1)將庫(kù)函數(shù)的鏈接載入推遲到程序運(yùn)行時(shí)期(2)可以實(shí)現(xiàn)進(jìn)程間的資源共享(因此也稱為共享庫(kù))(3)將一些程序升級(jí)變得簡(jiǎn)單(4)可以真正的做到鏈接載入完全由程序員在程序代碼中控制(顯示調(diào)用)
8、虛函數(shù):
父類型的指針指向其子類的實(shí)例
存在虛函數(shù)的類都有一個(gè)一維的虛函數(shù)表叫做虛表,
類的對(duì)象有一個(gè)指向虛表開(kāi)始的虛指針。虛表是和類對(duì)應(yīng)的,虛表指針是和對(duì)象對(duì)應(yīng)的。
默認(rèn)構(gòu)造函數(shù)、初始化構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、移動(dòng)構(gòu)造函數(shù)
構(gòu)造函數(shù)為什么不能被聲明為虛函數(shù)?
虛函數(shù)對(duì)應(yīng)一個(gè)vtale,這個(gè)表的地址是存儲(chǔ)在對(duì)象的內(nèi)存空間的。如果將構(gòu)造函數(shù)設(shè)置為虛函數(shù),
就需要到vtable 中調(diào)用,可是對(duì)象還沒(méi)有實(shí)例化,沒(méi)有內(nèi)存空間分配,如何調(diào)用
9、STL相關(guān)知識(shí)
STL由6部分組成:容器(Container)、算法(Algorithm)、 迭代器(Iterator)、
仿函數(shù)(Function object)、適配器(Adaptor)、空間配制器(Allocator)
容器、算法、迭代器、仿函數(shù)、適配器、空間配置器
容器:數(shù)組(array) , 鏈表(list), tree(樹(shù)),棧(stack), 隊(duì)列(queue), 集合(set),映射表(map)
string容器
Vector容器是單向開(kāi)口的連續(xù)內(nèi)存空間,deque則是一種雙向開(kāi)口的連續(xù)線性空間
stack是一種先進(jìn)后出
Queue是一種先進(jìn)先出
list容器鏈表是一種物理存儲(chǔ)單元上非連續(xù)、非順序的存儲(chǔ)結(jié)構(gòu)
set/multiset二叉樹(shù)
map/multimap二叉樹(shù)
交換,查找,遍歷,復(fù)制,修改,反轉(zhuǎn),排序,合并等
10、智能指針:auto_ptr(17后已遺棄)、unique_ptr、shared_ptr 和 weak_ptr
智能指針本質(zhì)就是一個(gè)類模板,當(dāng)智能指針對(duì)象使用完后,對(duì)象就會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)去釋放該指針?biāo)赶虻目臻g。
shared_ptr采用的是引用計(jì)數(shù)原理來(lái)實(shí)現(xiàn)多個(gè)shared_ptr對(duì)象之間共享資源:
shared_ptr在內(nèi)部會(huì)維護(hù)著一份引用計(jì)數(shù),用來(lái)記錄該份資源被幾個(gè)對(duì)象共享。
當(dāng)一個(gè)shared_ptr對(duì)象被銷毀時(shí)(調(diào)用析構(gòu)函數(shù)),析構(gòu)函數(shù)內(nèi)就會(huì)將該計(jì)數(shù)減1。
如果引用計(jì)數(shù)減為0后,則表示自己是最后一個(gè)使用該資源的shared_ptr對(duì)象,必須釋放資源。
如果引用計(jì)數(shù)不是0,就說(shuō)明自己還有其他對(duì)象在使用,則不能釋放該資源,否則其他對(duì)象就成為野指針。
11、指針和引用的區(qū)別
指針是一個(gè)實(shí)體,而引用僅是個(gè)別名。
引用只能在定義時(shí)被初始化一次,之后不可變,但指針可以改變。
指針可以有多級(jí),但引用只有一級(jí)。
引用不能為空,指針可以為空。
sizeof 引用”得到的是所指向的變量(對(duì)象)的大小,而“sizeof 指針”得到的是指針本身的大小
指針和引用的自增(++)運(yùn)算意義不一樣
引用是類型安全的,而指針不是 ,引用比指針多了類型檢查。
引用沒(méi)有const,指針有const,const的指針不可變。
訪問(wèn)實(shí)體方式不同,指針需要顯式解引用,引用編譯器自己處理
1、描述QT的TCP通訊流程
服務(wù)端:(QTcpServer)
①創(chuàng)建QTcpServer對(duì)象
②監(jiān)聽(tīng)list需要的參數(shù)是地址和端口號(hào)
③當(dāng)有新的客戶端連接成功回發(fā)送newConnect信號(hào)
④在newConnection信號(hào)槽函數(shù)中,調(diào)用nextPendingConnection函數(shù)獲取新連接QTcpSocket對(duì)象
⑤連接QTcpSocket對(duì)象的readRead信號(hào)
⑥在readRead信號(hào)的槽函數(shù)使用read接收數(shù)據(jù)
⑦調(diào)用write成員函數(shù)發(fā)送數(shù)據(jù)
客戶端:(QTcpSocket)
①創(chuàng)建QTcpSocket對(duì)象
②當(dāng)對(duì)象與Server連接成功時(shí)會(huì)發(fā)送connected 信號(hào)
③調(diào)用成員函數(shù)connectToHost連接服務(wù)器,需要的參數(shù)是地址和端口號(hào)
④connected信號(hào)的槽函數(shù)開(kāi)啟發(fā)送數(shù)據(jù)
⑤使用write發(fā)送數(shù)據(jù),read接收數(shù)據(jù)
1)長(zhǎng)連接的保活問(wèn)題
標(biāo)準(zhǔn)TCP層協(xié)議里把對(duì)方超時(shí)設(shè)為2小時(shí),若服務(wù)器端超過(guò)了2小時(shí)還沒(méi)收到客戶的信息,它就發(fā)送探測(cè)報(bào)文段,若發(fā)送了10個(gè)探測(cè)報(bào)文段(每一個(gè)相隔75S)還沒(méi)有收到響應(yīng),就假定客戶出了故障,并終止這個(gè)連接。因此應(yīng)對(duì)tcp長(zhǎng)連接進(jìn)行保活。
2)TCP提供了強(qiáng)制數(shù)據(jù)立即傳送的操作指令push,TCP軟件收到該操作指令后,就立即將本段數(shù)據(jù)發(fā)送出去,而不必等待發(fā)送緩沖區(qū)滿;
解決方法二:發(fā)送固定長(zhǎng)度的消息
解決方法三:把消息的尺寸與消息一塊發(fā)送
解決方法四:雙方約定每次傳送的大小
解決方法五:雙方約定使用特殊標(biāo)記來(lái)區(qū)分消息間隔
解決方法六:標(biāo)準(zhǔn)協(xié)議按協(xié)議規(guī)則處理,如Sip協(xié)議
3)粘包問(wèn)題
TCP產(chǎn)生粘包問(wèn)題的主要原因是:TCP是面向連接的,所以在TCP看來(lái),并沒(méi)有把消息看成一條條的,而是全部消息在TCP眼里都是字節(jié)流,
因此A、B消息混在一起后,TCP就分不清了。
粘包問(wèn)題的最本質(zhì)原因在與接收對(duì)等方無(wú)法分辨消息與消息之間的邊界在哪。我們通過(guò)使用某種方案給出邊界,例如:
包頭加上包體長(zhǎng)度。包頭是定長(zhǎng)的4個(gè)字節(jié),說(shuō)明了包體的長(zhǎng)度。接收對(duì)先接收包體長(zhǎng)度,依據(jù)包體長(zhǎng)度來(lái)接收包體。
解決方法一:TCP提供了強(qiáng)制數(shù)據(jù)立即傳送的操作指令push,TCP軟件收到該操作指令后,就立即將本段數(shù)據(jù)發(fā)送出去,而不必等待發(fā)送緩沖區(qū)滿;
解決方法二:發(fā)送固定長(zhǎng)度的消息
解決方法三:把消息的尺寸與消息一塊發(fā)送
解決方法四:雙方約定每次傳送的大小
解決方法五:雙方約定使用特殊標(biāo)記來(lái)區(qū)分消息間隔
解決方法六:標(biāo)準(zhǔn)協(xié)議按協(xié)議規(guī)則處理,如Sip協(xié)議
發(fā)送端給每個(gè)數(shù)據(jù)包添加包首部,首部中應(yīng)該至少包含數(shù)據(jù)包的長(zhǎng)度,這樣接收端在接收到數(shù)據(jù)后,通過(guò)讀取包首部的長(zhǎng)度字段,便知道每一個(gè)數(shù)據(jù)包的實(shí)際長(zhǎng)度了。
發(fā)送端將每個(gè)數(shù)據(jù)包封裝為固定長(zhǎng)度(不夠的可以通過(guò)補(bǔ)0填充),這樣接收端每次從接收緩沖區(qū)中讀取固定長(zhǎng)度的數(shù)據(jù)就自然而然的把每個(gè)數(shù)據(jù)包拆分開(kāi)來(lái)。
可以在數(shù)據(jù)包之間設(shè)置邊界,如添加特殊符號(hào),這樣,接收端通過(guò)這個(gè)邊界就可以將不同的數(shù)據(jù)包拆分開(kāi)。
指定數(shù)據(jù)長(zhǎng)度的解決方法,主要思路:
我們?cè)跀?shù)據(jù)結(jié)構(gòu)中有個(gè)成員代表了長(zhǎng)度(消息頭),我們準(zhǔn)備一個(gè)足夠大的消息緩沖區(qū)(程序中是1024000個(gè)字節(jié)),循環(huán)使用socket中的recv每次讀取最多102400個(gè)字節(jié),然后把循環(huán)接收的消息拼接到消息緩沖區(qū)中,直到接收到的消息大于消息頭指示的長(zhǎng)度,則接收到了一個(gè)完整的消息(所以我們的消息緩沖區(qū)要比完整的消息還要大才行),進(jìn)行消息處理。
【文章福利】Qt開(kāi)發(fā)學(xué)習(xí)資料包、大廠面試題、技術(shù)視頻和學(xué)習(xí)路線圖,包括(Qt C++基礎(chǔ),數(shù)據(jù)庫(kù)編程,Qt項(xiàng)目實(shí)戰(zhàn)、Qt框架、QML、Opencv、qt線程等等)有需要的可以進(jìn)企鵝裙937552610領(lǐng)取哦~
4)TCP與UDP區(qū)別總結(jié):
1、TCP面向連接(如打電話要先撥號(hào)建立連接);UDP是無(wú)連接的,即發(fā)送數(shù)據(jù)之前不需要建立連接
2、TCP提供可靠的服務(wù)。也就是說(shuō),通過(guò)TCP連接傳送的數(shù)據(jù),無(wú)差錯(cuò),不丟失,不重復(fù),且按序到達(dá);UDP盡最大努力交付,即不保 證可靠交付
3、TCP面向字節(jié)流,實(shí)際上是TCP把數(shù)據(jù)看成一連串無(wú)結(jié)構(gòu)的字節(jié)流;UDP是面向報(bào)文的
UDP沒(méi)有擁塞控制,因此網(wǎng)絡(luò)出現(xiàn)擁塞不會(huì)使源主機(jī)的發(fā)送速率降低(對(duì)實(shí)時(shí)應(yīng)用很有用,如IP電話,實(shí)時(shí)視頻會(huì)議等)
4、每一條TCP連接只能是點(diǎn)到點(diǎn)的;UDP支持一對(duì)一,一對(duì)多,多對(duì)一和多對(duì)多的交互通信
5、TCP首部開(kāi)銷20字節(jié);UDP的首部開(kāi)銷小,只有8個(gè)字節(jié)
6、TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道
7、三次握手和四次揮手具體流程
SYN:請(qǐng)求建立連接,F(xiàn)IN:請(qǐng)求斷開(kāi)連接,ACK:確認(rèn)是否有效, seq:序列號(hào), ack:確認(rèn)號(hào)
1)三次握手
1.客戶端向服務(wù)端發(fā)送?個(gè)SYN=1(請(qǐng)求建立連接),并生成一個(gè)序列號(hào)seq=j。
2.服務(wù)端接收到SYN=1后,給客戶端發(fā)送?個(gè)SYN=1與ACK=1;并將ack置為j+1;同時(shí)生成一個(gè)序列號(hào)seq=k。
3.客戶端接收到會(huì)檢查ack是否為j+1與ACK是否為1,如果是,則會(huì)給服務(wù)端發(fā)送一個(gè)ACK=1與ack=k+1,以及自己的序列號(hào)seq=j=1; 服務(wù)端接收到會(huì)檢查ACK是否為1與ack是否為k+1,如果是則代表連接建立成功,兩者間可以傳遞數(shù)據(jù)。
2)四次揮手
1.客戶端向服務(wù)端發(fā)送FIN=1(請(qǐng)求關(guān)閉連接),并生成一個(gè)序列號(hào)seq=x。
2.服務(wù)端接收FIN后,向客戶端發(fā)送ACK=1,ack=x+1,并生成序列號(hào)seq=y(客戶端無(wú)數(shù)據(jù)發(fā)送,但服務(wù)器端需發(fā)送完最后的數(shù)據(jù))。
3.服務(wù)端處理完所有數(shù)據(jù)后,向客戶端發(fā)送FIN=1與ACK=1,ack=x+1,并生成序列號(hào)z,表示服務(wù)端現(xiàn)在可以斷開(kāi)連接。
4.客戶端收到服務(wù)端的數(shù)據(jù)包后,會(huì)向服務(wù)端發(fā)送ACK=1,seq=x=1,ack=z+1(需要等待2MSL后才可斷開(kāi)連接)。
8、指針和引用的區(qū)別
指針是一個(gè)實(shí)體,而引用僅是個(gè)別名。
引用只能在定義時(shí)被初始化一次,之后不可變,但指針可以改變。
指針可以有多級(jí),但引用只有一級(jí)。
引用不能為空,指針可以為空。
sizeof 引用”得到的是所指向的變量(對(duì)象)的大小,而“sizeof 指針”得到的是指針本身的大小
指針和引用的自增(++)運(yùn)算意義不一樣
引用是類型安全的,而指針不是 ,引用比指針多了類型檢查。
引用沒(méi)有const,指針有const,const的指針不可變。
訪問(wèn)實(shí)體方式不同,指針需要顯式解引用,引用編譯器自己處理
9、HTTP 是一種 超文本傳輸協(xié)議
因特網(wǎng)的協(xié)議棧由五個(gè)部分組成:物理層、鏈路層、網(wǎng)絡(luò)層、運(yùn)輸層和應(yīng)用層
URL(即網(wǎng)址)
瀏覽器會(huì)向DNS(域名服務(wù)器,后面會(huì)說(shuō))提供網(wǎng)址
HTML 稱為超文本標(biāo)記語(yǔ)言
http協(xié)議是應(yīng)用層協(xié)議,主要是解決如何包裝數(shù)據(jù)。而tcp協(xié)議是傳輸層協(xié)議,主要解決數(shù)據(jù)如何在網(wǎng)絡(luò)中傳輸。
通俗點(diǎn)說(shuō),http的任務(wù)是與服務(wù)器交換信息,它不管怎么連到服務(wù)器和保證數(shù)據(jù)正確的事情。而tcp的任務(wù)是保證連接的可靠,它只管連接,它不管連接后要傳什么數(shù)據(jù)。
http協(xié)議是建立在tcp之上的,http是無(wú)狀態(tài)的短鏈接,而tcp是有狀態(tài)的長(zhǎng)鏈接。
HTTPS:是以安全為目標(biāo)的 HTTP 通道,是 HTTP 的安全版。HTTPS 的安全基礎(chǔ)是 SSL。
SSL 協(xié)議位于 TCP/IP 協(xié)議與各種應(yīng)用層協(xié)議之間,為數(shù)據(jù)通訊提供安全支持。
二、HTTP 與 HTTPS 的區(qū)別
1、HTTPS 協(xié)議需要到 CA (Certificate Authority,證書(shū)頒發(fā)機(jī)構(gòu))申請(qǐng)證書(shū),一般免費(fèi)證書(shū)較少,因而需要一定費(fèi)用。
(以前的網(wǎng)易官網(wǎng)是http,而網(wǎng)易郵箱是 https 。)
2、HTTP 是超文本傳輸協(xié)議,信息是明文傳輸,HTTPS 則是具有安全性的 SSL 加密傳輸協(xié)議。
3、HTTP 和 HTTPS 使用的是完全不同的連接方式,用的端口也不一樣,前者是80,后者是443。
4、HTTP 的連接很簡(jiǎn)單,是無(wú)狀態(tài)的。HTTPS 協(xié)議是由 SSL+HTTP 協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議,
比 HTTP 協(xié)議安全。(無(wú)狀態(tài)的意思是其數(shù)據(jù)包的發(fā)送、傳輸和接收都是相互獨(dú)立的。
無(wú)連接的意思是指通信雙方都不長(zhǎng)久的維持對(duì)方的任何信息。)
1、什么是元對(duì)象系統(tǒng)
元對(duì)象系統(tǒng)是一個(gè)基于標(biāo)準(zhǔn)C++的擴(kuò)展,為QT提供了信號(hào)與槽機(jī)制、實(shí)時(shí)類型信息、動(dòng)態(tài)屬性系統(tǒng)。
元對(duì)象系統(tǒng)的三個(gè)基本條件:類必須繼承自QObject、類聲明Q_OBJECT宏(默認(rèn)私有)、元對(duì)象編譯器moc。
信號(hào)與槽類似觀察者模式;
回調(diào)函數(shù)的本質(zhì)是基于“你想讓別人的代碼執(zhí)行你的代碼,而別人的代碼你又不能動(dòng)”這種產(chǎn)生的;
對(duì)象樹(shù);
信號(hào)與槽的實(shí)現(xiàn)是借助了Qt 的元對(duì)象系統(tǒng),元對(duì)象系統(tǒng)有一個(gè)元對(duì)象編譯器,
程序編譯之前會(huì)有一個(gè)預(yù)處理過(guò)程,預(yù)處理將一個(gè)類/對(duì)象中的信號(hào),槽的字符串值分別保存在一個(gè)容器中,可能是字符串或者其他的有序容器
第5個(gè)參數(shù)跟線程相關(guān)Qt::AutoConnection
1)Qt信號(hào)槽機(jī)制的優(yōu)勢(shì)
(1)類型安全。需要關(guān)聯(lián)的信號(hào)和槽的簽名必須是等同的,
(2)松散耦合。
(3)信號(hào)和槽機(jī)制增強(qiáng)了對(duì)象間通信的靈活性。一個(gè)信號(hào)可以關(guān)聯(lián)多個(gè)槽,也可以多個(gè)信號(hào)關(guān)聯(lián)一個(gè)槽。
2)Qt信號(hào)槽機(jī)制的不足
同回調(diào)函數(shù)相比,信號(hào)和槽機(jī)制運(yùn)行速度有些慢。通過(guò)傳遞一個(gè)信號(hào)來(lái)調(diào)用槽函數(shù)將會(huì)比直接調(diào)用非虛函數(shù)運(yùn)行速度慢10倍。原因如下:
(1)需要定位接收信號(hào)的對(duì)象;
(2)安全地遍歷所有的關(guān)聯(lián)(如一個(gè)信號(hào)關(guān)聯(lián)多個(gè)槽的情況);
(3)編組/解組傳遞的參數(shù);
(4)多線程的時(shí)候,信號(hào)可能需要排隊(duì)等待。
然而,與創(chuàng)建對(duì)象的new操作及刪除對(duì)象的delete操作相比,信號(hào)和槽的運(yùn)行代價(jià)只是他們很少的一部分。信號(hào)和槽機(jī)制導(dǎo)致的這點(diǎn)性能損耗,對(duì)實(shí)時(shí)應(yīng)用程序是可以忽略的。
知道QT事件機(jī)制有幾種級(jí)別的事件過(guò)濾嗎?能大致描述下嗎?
答:根據(jù)對(duì)Qt事件機(jī)制的分析, 我們可以得到5種級(jí)別的事件過(guò)濾,處理辦法. 以功能從弱到強(qiáng), 排列如下:
1)重載特定事件處理函數(shù).
最常見(jiàn)的事件處理辦法就是重載象mousePressEvent(), keyPressEvent(), paintEvent() 這樣的特定事件處理函數(shù).
2)重載event()函數(shù).
通過(guò)重載event()函數(shù),我們可以在事件被特定的事件處理函數(shù)處理之前(象keyPressEvent())處理它. 比如, 當(dāng)我們想改變tab鍵的默認(rèn)動(dòng)作時(shí),一般要重載這個(gè)函數(shù). 在處理一些不常見(jiàn)的事件(比如:LayoutDirectionChange)時(shí),evnet()也很有用,因?yàn)檫@些函數(shù)沒(méi)有相應(yīng)的特定事件處理函數(shù). 當(dāng)我們重載event()函數(shù)時(shí), 需要調(diào)用父類的event()函數(shù)來(lái)處理我們不需要處理或是不清楚如何處理的事件.
3) 在Qt對(duì)象上安裝事件過(guò)濾器.
安裝事件過(guò)濾器有兩個(gè)步驟: (假設(shè)要用A來(lái)監(jiān)視過(guò)濾B的事件)
首先調(diào)用B的installEventFilter( const QOject *obj ), 以A的指針作為參數(shù). 這樣所有發(fā)往B的事件都將先由A的eventFilter()處理.
然后, A要重載QObject::eventFilter()函數(shù), 在eventFilter() 中書(shū)寫(xiě)對(duì)事件進(jìn)行處理的代碼.
4) 給QAppliction對(duì)象安裝事件過(guò)濾器.
一旦我們給qApp(每個(gè)程序中唯一的QApplication對(duì)象)裝上過(guò)濾器,那么所有的事件在發(fā)往任何其他的過(guò)濾器時(shí),都要先經(jīng)過(guò)當(dāng)前這個(gè) eventFilter(). 在debug的時(shí)候,這個(gè)辦法就非常有用, 也常常被用來(lái)處理失效了的widget的鼠標(biāo)事件,通常這些事件會(huì)被QApplication::notify()丟掉. ( 在QApplication::notify() 中, 是先調(diào)用qApp的過(guò)濾器, 再對(duì)事件進(jìn)行分析, 以決定是否合并或丟棄)
5) 繼承QApplication類,并重載notify()函數(shù).
Qt 是用QApplication::notify()函數(shù)來(lái)分發(fā)事件的.想要在任何事件過(guò)濾器查看任何事件之前先得到這些事件,重載這個(gè)函數(shù)是唯一的辦法. 通常來(lái)說(shuō)事件過(guò)濾器更好用一些, 因?yàn)椴恍枰ダ^承QApplication類. 而且可以給QApplication對(duì)象安裝任意個(gè)數(shù)的事件。
自定義界面
UI設(shè)計(jì)器集成了Qt樣式表的編輯功能
前景色color
背景色background-color
選中后顏色selection-color
背景圖片background-image
選擇器:QPushButton 、QDialog QPushButton、QPushButton#btnOk
子控件:drop-down、up-button、down-button
偽狀態(tài):hover(鼠標(biāo)移動(dòng)到條目上方時(shí)),active(處于活動(dòng)的窗體)
屬性:min_width、padding-top、border-width、
1、使用Qt Designer
2、qApp->setStyleSheet(“QLineEdit{ background-color: gray}”);
自定義QT控件
1、自定義Widget子類QmyBattery
paintEvent()事件
QmyBattery繼承于QWidget
Q_UNUSED(event)
QPainter/QRect/QColor
提升法
2、自定義Qt Designer插件
編譯器用MSVC2015 32bit
Q_INTERFACES聲明了實(shí)現(xiàn)接口
Q_PLUGIN_METADATA聲明了元數(shù)據(jù)名稱
要把dll和lib放到插件目錄下
D:\Qt\Qt5.9.1\Tools\QtCreator\bin\plugins\designer
D:\Qt\Qt5.9.1.9.1\msvc2015\plugins\designer
QWT,是一個(gè)基于LGPL版權(quán)協(xié)議的開(kāi)源項(xiàng)目, 可生成各種統(tǒng)計(jì)圖
QT和MFC消息機(jī)制比較:
mfc的消息機(jī)制其實(shí)就是消息映射機(jī)制,程序員需要將自定義消息和對(duì)應(yīng)的處理函數(shù)添加到消息映射表中。通過(guò)PostMessage和SendMessage來(lái)實(shí)現(xiàn)異步和同步消息。
QT的信號(hào)槽機(jī)制是信號(hào)和槽函數(shù)通過(guò)QObject::connect動(dòng)態(tài)鏈接上后存儲(chǔ)到元對(duì)象系統(tǒng)中,通過(guò)emit發(fā)送信號(hào),對(duì)應(yīng)的槽函數(shù)執(zhí)行。
比較
Qt的信號(hào)槽是動(dòng)態(tài)鏈接的,而MFC的消息映射是靜態(tài)的
Qt的信號(hào)支持自定義參數(shù),且類型安全
在多線程中,MFC需要向已知線程對(duì)象發(fā)布消息,而Qt可以不考慮多線程之間的信號(hào)槽關(guān)系
總結(jié)
Qt相比較MFC的消息機(jī)制,使用起來(lái)更方便,最大的優(yōu)勢(shì)是Qt支持動(dòng)態(tài)鏈接信號(hào)槽。
1、定位問(wèn)題:
請(qǐng)問(wèn)下,如果軟件除了問(wèn)題(Bug),如何快速定位?主要方法有哪些?
答:打印輸出/代碼調(diào)試/日志記錄/分析工具/找同事討論。
1)二分法定位技巧
無(wú)論是有多復(fù)雜的代碼,利用二分法定位技巧一般都是可以定位到問(wèn)題所在。
從二分法定位技巧可以延伸出一些具體的處理bug的方法,比如:對(duì)輸入數(shù)據(jù)二分、對(duì)代碼版本二分、注釋掉部分代碼、在不同位置插入試探性代碼、對(duì)運(yùn)行環(huán)境二分。
2)IDE調(diào)試
IDE的VS debug的功能簡(jiǎn)直就是立竿見(jiàn)影。它可以加斷點(diǎn),單步調(diào)試。
單步調(diào)試可以讓我們對(duì)代碼邏輯,執(zhí)行順序,以及各種中間結(jié)果更加清晰。
至于本身容易出錯(cuò)的BUG,用IDE調(diào)試簡(jiǎn)直是再合適不過(guò)了。
3)重新讀一遍程序
相對(duì)新手程序員來(lái)說(shuō),如果代碼出現(xiàn)bug,可以重新讀一遍程序。這種方法是最有效、最快速的 Debug 方式。
4)必殺,重寫(xiě)一遍
如果你發(fā)現(xiàn)無(wú)論如何也找不到BUG,而且代碼只是復(fù)雜,本身不是很長(zhǎng),直接重寫(xiě)代碼吧!
5)小黃鴨調(diào)試法
小黃鴨調(diào)試法是程序員們經(jīng)常使用的調(diào)試代碼方法之一。
小黃鴨不懂程序,所以我們可以向他解釋每一行程序的作用,以此來(lái)激發(fā)靈感。
內(nèi)存泄露及解決辦法:
什么是內(nèi)存泄露?
簡(jiǎn)單地說(shuō)就是申請(qǐng)了一塊內(nèi)存空間,使用完畢后沒(méi)有釋放掉。(1)new和malloc申請(qǐng)資源使用后,沒(méi)有用delete和free釋放;(2)子類繼承父類時(shí),父類析構(gòu)函數(shù)不是虛函數(shù)。(3)Windows句柄資源使用后沒(méi)有釋放。
怎么檢測(cè)?
第一:良好的編碼習(xí)慣,使用了內(nèi)存分配的函數(shù),一旦使用完畢,要記得使用其相應(yīng)的函數(shù)釋放掉。
第二:將分配的內(nèi)存的指針以鏈表的形式自行管理,使用完畢之后從鏈表中刪除,程序結(jié)束時(shí)可檢查改鏈表。
第三:使用智能指針。
第四:一些常見(jiàn)的工具插件,如ccmalloc、Dmalloc、Leaky、Valgrind等等。
數(shù)據(jù)庫(kù)相關(guān)
什么是事務(wù)
并發(fā)控制
原子性、一致性、隔離性、持續(xù)性
valueChanged(int)
QMutex mutex;
QMutexLocker locker(&mutex);
waitForReadyRead()
常用的設(shè)計(jì)結(jié)構(gòu):
工廠方法 Factory Method 定義了創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化哪個(gè)類
單例 Singleton 確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)
原型 Prototype 通過(guò)拷貝原型對(duì)象創(chuàng)建新的對(duì)象。
適配器 Adapter 將一個(gè)類的接口轉(zhuǎn)換成希望的另外一個(gè)接口,使得原本不兼容的接口可以協(xié)同工作。
觀察者 Observer 定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。
單例:懶漢模式和餓漢模式
餓漢就是類一旦加載,就把單例初始化完成,保證getInstance的時(shí)候,單例是已經(jīng)存在的了;線程安全。
而懶漢比較懶,只有當(dāng)調(diào)用getInstance的時(shí)候,才回去初始化這個(gè)單例;
線程不安全,就只有在實(shí)例化之前調(diào)用的時(shí)候加鎖,后面不加鎖。
C++新特性主要包括包含語(yǔ)法改進(jìn)和標(biāo)準(zhǔn)庫(kù)擴(kuò)充兩個(gè)方面,主要包括以下11點(diǎn):
語(yǔ)法的改進(jìn)
(1)統(tǒng)一的初始化方法,允許構(gòu)造函數(shù)或其他函數(shù)像參數(shù)一樣使用初始化列表
(2)成員變量默認(rèn)初始化
(3)類型推導(dǎo) auto關(guān)鍵字 用于定義變量,編譯器可以自動(dòng)判斷的類型(前提:定義一個(gè)變量時(shí)對(duì)其進(jìn)行初始化)
(4)decltype 求表達(dá)式的類型
(5)智能指針 shared_ptr
(6)空指針 nullptr(原來(lái)NULL)
nullptr表示空指針,是一個(gè)關(guān)鍵詞
NULL是老版本的,是一個(gè)0的宏
(7)基于范圍的for循環(huán)
(8)右值引用和move語(yǔ)義 讓程序員有意識(shí)減少進(jìn)行深拷貝操作
標(biāo)準(zhǔn)庫(kù)擴(kuò)充(往STL里新加進(jìn)一些模板類,比較好用)
(9)無(wú)序容器(哈希表) 用法和功能同map一模一樣,區(qū)別在于哈希表的效率更高
(10)正則表達(dá)式 可以認(rèn)為正則表達(dá)式實(shí)質(zhì)上是一個(gè)字符串,該字符串描述了一種特定模式的字符串
(11)Lambda表達(dá)式
“消息隊(duì)列(MQ)”是在消息的傳輸過(guò)程中保存消息的容器。
消息隊(duì)列正如同一種先進(jìn)先出的隊(duì)列結(jié)構(gòu),它將發(fā)送方的消息推入隊(duì)列中,并依序推送給接收方。消息隊(duì)列相關(guān)的通信協(xié)議都屬于應(yīng)用層協(xié)議,位于OSI模型第七層,是基于TCP/IP的通信協(xié)議。
與TCP、UDP或是HTTP協(xié)議不同,MQ相關(guān)協(xié)議沒(méi)有服務(wù)端和客戶端的概念。原本的客戶端和服務(wù)端,現(xiàn)在都通過(guò)一個(gè)中間件服務(wù)器(broker)交互,消息的發(fā)送方稱為生產(chǎn)者,消息的接收方成為消費(fèi)者,生產(chǎn)者和消費(fèi)者都可以視同broker的客戶端。
通過(guò)這種設(shè)計(jì),所有消息都被存放于一個(gè)中間服務(wù)器中,通信的雙方不再需要?jiǎng)?chuàng)建服務(wù)。這樣做帶來(lái)了幾個(gè)好處:解耦,異步調(diào)用,削峰。
解耦:通過(guò)中間件,各個(gè)系統(tǒng)之間可以獨(dú)立運(yùn)行,不會(huì)因?yàn)槠渲幸粋€(gè)系統(tǒng)的崩潰影響其他系統(tǒng),且整個(gè)系統(tǒng)的可拓展性也大大加強(qiáng)。
異步:發(fā)送方的消息推入了中間件,這條消息可以被所有相關(guān)的接收方看到,因此它們可以同時(shí)開(kāi)始處理,這種串聯(lián)的結(jié)構(gòu)的時(shí)間消耗比其他的串行結(jié)構(gòu)小得多。
削峰:在高并發(fā)環(huán)境下,短時(shí)間的大量請(qǐng)求會(huì)導(dǎo)致系統(tǒng)和數(shù)據(jù)庫(kù)發(fā)生很多問(wèn)題,所以需要對(duì)流量進(jìn)行控制,通過(guò)消息隊(duì)列設(shè)置每秒向消費(fèi)者投遞的消息數(shù)量,可以控制并發(fā)環(huán)境下的系統(tǒng)穩(wěn)定性。
但是,消息隊(duì)列同樣有它的不足。如降低系統(tǒng)可用性,增加系統(tǒng)的復(fù)雜性和一致性問(wèn)題等。因此,是否使用消息隊(duì)列也必須根據(jù)實(shí)際應(yīng)用來(lái)決定。
基于消息隊(duì)列的通信協(xié)議有很多,常見(jiàn)的有RabbitMQ,Kafka,還有本文介紹的MQTT。
【領(lǐng)QT開(kāi)發(fā)教程學(xué)習(xí)資料,點(diǎn)擊下方鏈接免費(fèi)領(lǐng)取↓↓,先碼住不迷路~】
點(diǎn)擊→領(lǐng)取「鏈接」
MQTT(Message Queuing Telemetry Transport,消息隊(duì)列遙測(cè)傳輸協(xié)議),是一種基于發(fā)布/訂閱(publish/subscribe)模式的"輕量級(jí)"通訊協(xié)議,該協(xié)議構(gòu)建于TCP/IP協(xié)議上,由IBM在1999年發(fā)布。MQTT最大優(yōu)點(diǎn)在于,可以以極少的代碼和有限的帶寬,為連接遠(yuǎn)程設(shè)備提供實(shí)時(shí)可靠的消息服務(wù)。作為一種低開(kāi)銷、低帶寬占用的即時(shí)通訊協(xié)議,使其在物聯(lián)網(wǎng)、小型設(shè)備、移動(dòng)應(yīng)用等方面有較廣泛的應(yīng)用。
實(shí)現(xiàn)MQTT協(xié)議需要客戶端和服務(wù)器端通訊完成,在通訊過(guò)程中,MQTT協(xié)議中有三種身份:發(fā)布者(Publish)、代理(Broker)(服務(wù)器)、訂閱者(Subscribe)。其中,消息的發(fā)布者和訂閱者都是客戶端,消息代理是服務(wù)器,消息發(fā)布者可以同時(shí)是訂閱者。
MQTT會(huì)構(gòu)建底層網(wǎng)絡(luò)傳輸:它將建立客戶端到服務(wù)器的連接,提供兩者之間的一個(gè)有序的、無(wú)損的、基于字節(jié)流的雙向傳輸。當(dāng)應(yīng)用數(shù)據(jù)通過(guò)MQTT網(wǎng)絡(luò)發(fā)送時(shí),MQTT會(huì)把與之相關(guān)的服務(wù)質(zhì)量(QoS)和主題名(Topic)相關(guān)連。
MQTT中的幾個(gè)重要概念:
訂閱(Subscription)
訂閱包含主題篩選器(Topic Filter)和最大服務(wù)質(zhì)量(QoS)。訂閱會(huì)與一個(gè)會(huì)話(Session)關(guān)聯(lián)。一個(gè)會(huì)話可以包含多個(gè)訂閱。每一個(gè)會(huì)話中的每個(gè)訂閱都有一個(gè)不同的主題篩選器。
會(huì)話(Session)
每個(gè)客戶端與服務(wù)器建立連接后就是一個(gè)會(huì)話,客戶端和服務(wù)器之間有狀態(tài)交互。會(huì)話存在于一個(gè)網(wǎng)絡(luò)之間,也可能在客戶端和服務(wù)器之間跨越多個(gè)連續(xù)的網(wǎng)絡(luò)連接。
主題名(Topic Name)
連接到一個(gè)應(yīng)用程序消息的標(biāo)簽,該標(biāo)簽與服務(wù)器的訂閱相匹配。服務(wù)器會(huì)將消息發(fā)送給訂閱所匹配標(biāo)簽的每個(gè)客戶端。
主題篩選器(Topic Filter)
一個(gè)對(duì)主題名通配符篩選器,在訂閱表達(dá)式中使用,表示訂閱所匹配到的多個(gè)主題。
負(fù)載(Payload)
消息訂閱者所具體接收的內(nèi)容。
【領(lǐng)QT開(kāi)發(fā)教程學(xué)習(xí)資料,點(diǎn)擊下方鏈接免費(fèi)領(lǐng)取↓↓,先碼住不迷路~】
點(diǎn)擊→領(lǐng)取「鏈接」
默認(rèn)的Qt環(huán)境是不能使用MQTT的,但Qt官方提供了基于MQTT的封裝,需要通過(guò)源碼進(jìn)行編譯。
在dev分支中可以選擇MQTT版本,選擇最新的下載到本地。
下載下來(lái)的是一個(gè)Qt項(xiàng)目,在Qt Creator中打開(kāi).pro文件,然后使用Release模式,用你所需要的編譯器(VS,MinGW…),開(kāi)始編譯。
如果你的系統(tǒng)沒(méi)有安裝過(guò)Perl,需要先安裝Perl,并加入到系統(tǒng)環(huán)境變量中。
完成編譯后,可以在你的編譯路徑的/bin目錄中得到所需的動(dòng)態(tài)鏈接庫(kù)文件Qt5Mqtt.dll和Qt5Mqttd.dll。前者是release版庫(kù),后者是debug版。
為了實(shí)現(xiàn)一次配置,所有項(xiàng)目可用的目的,我們可以直接將MQTT配置到系統(tǒng)的Qt環(huán)境中去。C++的編譯機(jī)制是通過(guò)頭文件和靜態(tài)鏈接庫(kù)編譯出動(dòng)態(tài)鏈接庫(kù),再通過(guò)頭文件和動(dòng)態(tài)鏈接庫(kù)運(yùn)行程序。所以這里我們要將前面編譯出的靜態(tài)鏈接庫(kù)和動(dòng)態(tài)鏈接庫(kù)都復(fù)制到Qt環(huán)境中去。
首先,將qtmqtt源碼目錄下(qtmqtt/src/mqtt)的所有.h頭文件拷貝,在Qt安裝目錄下的include文件夾中創(chuàng)建一個(gè)mqtt目錄,將拷貝的文件粘貼進(jìn)去。
然后,將源碼編譯生成目錄下的靜態(tài)鏈接庫(kù)相關(guān)文件拷貝到Qt安裝目錄的/lib下,
依次為Qt5Mqtt.lib(.a) Qt5Mqtt.prl Qt5mqttd.lib(.a) Qt5Mqttd.prl。
再將編譯生成的兩個(gè)動(dòng)態(tài)鏈接庫(kù)拷貝到Qt安裝目錄的/bin下,
依次為Qt5Mqtt.dll Qt5Mqttd.dll。
最后再拷貝模塊配置文件到Qt安裝目錄中。
這樣MQTT就已經(jīng)配置到我們本地的Qt環(huán)境中了。后續(xù)所有使用此Qt環(huán)境的項(xiàng)目都可以直接使用MQTT了。
使用MQTT時(shí),首先要在.pro中添加模塊:
QT += mqtt
在使用前引入包:
#include <QtMqtt/qmqttclient.h>
編寫(xiě)代碼可以參考Qt官方的MQTT說(shuō)明文檔:
https://doc.qt.io/qt-6/qtmqtt-index.html
為了調(diào)試程序,我們需要一臺(tái)MQTT服務(wù)器。EMQ公司官方提供了測(cè)試的MQTT服務(wù)器,但由于連接數(shù)眾多,不太穩(wěn)定,我們需要自己搭建一臺(tái)MQTT服務(wù)器。
EMQ X提供了開(kāi)源版的EMQ X服務(wù)器安裝包,支持Windows,Ubuntu等多種使用環(huán)境。
安裝后,Windows用戶使用管理員權(quán)限命令行進(jìn)入安裝路徑下,進(jìn)入/emqx/bin/,依次執(zhí)行命令
#先運(yùn)行該命令
emqx install
#成功后界面上會(huì)ChangeServiceConfig 成功
#再運(yùn)行
emqx console
#運(yùn)行成功后會(huì)顯示emqx is started!
#然后會(huì)跳出一個(gè)界面,打開(kāi)emqx運(yùn)行所需要的各個(gè)端口
#最后運(yùn)行
emqx start
#沒(méi)有報(bào)錯(cuò)就執(zhí)行成功了
Linux用戶在安裝路徑下執(zhí)行下述命令即可
emqx start
這樣本地就開(kāi)啟了MQTT服務(wù),這里有兩個(gè)重要的端口號(hào)要記住:1883(暴露給外部的MQTT服務(wù)端口),18083(服務(wù)器控制面板端口)。在本地瀏覽器輸入http://127.0.0.1:18083/,打開(kāi)服務(wù)器控制面板。輸入初始用戶名admin和用戶密碼public,即可進(jìn)入控制面板,并進(jìn)行MQTT服務(wù)器相關(guān)配置。
【領(lǐng)QT開(kāi)發(fā)教程學(xué)習(xí)資料,點(diǎn)擊下方鏈接免費(fèi)領(lǐng)取↓↓,先碼住不迷路~】
點(diǎn)擊→領(lǐng)取「鏈接」
為了調(diào)試程序,我們通常需要一個(gè)調(diào)試軟件來(lái)模擬消息的收發(fā),這里推薦使用MQTT X軟件進(jìn)行調(diào)試。
MQTT X下載連接:https://mqttx.app/zh
MQTT X使用文檔:https://mqttx.app/zh/docs
安裝完成后,點(diǎn)擊+圖標(biāo)可以添加連接。
這里的Name和Client ID隨意,Host填寫(xiě)我們本地配置的MQTT服務(wù)器地址127.0.0.1,端口號(hào)填1883。點(diǎn)擊Connect即可連接到本地。
連接后,點(diǎn)擊New Subscription創(chuàng)建topic,然后就可以在該topic下收發(fā)消息。
這里給出一個(gè)Qt-MQTT的程序樣例,包含了基礎(chǔ)的連接,收,發(fā),斷開(kāi)等功能,讀者可以在此基礎(chǔ)上二次開(kāi)發(fā)。
.h
#ifndef MY_MQTT_CLIENT_H
#define MY_MQTT_CLIENT_H
#include <QObject>
#include <QDateTime>
#include <QtMqtt/qmqttclient.h>
namespace Ui {
class MyMQTTClient;
}
using namespace std;
class MyMQTTClient : public QObject
{
Q_OBJECT
public:
explicit MyMQTTClient(QObject *parent = nullptr);
~MyMQTTClient(){
};
QMqttClient *m_client = nullptr;
void MyMQTTSubscribe(QString);
void MyMQTTSendMessage(const QString, const QString);
signals:
public slots:
void brokerConnected();
void updateLogStateChange();
void brokerDisconnected();
void receiveMess(const QByteArray &, const QMqttTopicName &);
private:
};
#endif // MY_MQTT_CLIENT_H
.cpp
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。