整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

大神总结的Qt开发经验,满满的都是干货

先声明,本文并非原创,纯属搬运,内容来自一位叫做飞扬青春的大神的Gitee主页,主要是为了收藏下面介绍的100多个Qt开发经验。我本身也从事了两年了Qt开发,再转Qt开发以前用的都是MFC,我仔细的看了一遍下面列出的各条经验,只恨看到的太晚了,因为很多都是自己踩过的坑。比如qss的ANSI编码、嵌套窗口中主窗口无法接收鼠标移动事件等,又比如我用qss设置窗口样式,但是项目每次重新构建以后,样式表就会不生效等问题,也花了自己不少时间去解决,所以在这里转发大神的经验,留作以后参考和逐条的研究,也分享给更多正在学习Qt或者正在使用Qt进行程序开发的朋友们。

大神主页:https://gitee.com/feiyangqingyun/qtkaifajingyan?_from=gitee_search

1. 当编译发现大量错误的时候,从第一个看起,一个一个的解决,不要急着去看下一个错误,往往后面的错误都是由于前面的错误引起的,第一个解决后很可能都解决了。

2. 定时器是个好东西,学会好使用它,有时候用QTimer::singleShot可以解决意想不到的问题。

3. 打开creator,在构建套件的环境中增加MAKEFLAGS=-j8,可以不用每次设置多线程编译。珍爱时间和生命。新版的QtCreator已经默认就是j8。

4. 如果你想顺利用QtCreator部署安卓程序,首先你要在AndroidStudio 里面配置成功,把坑全部趟平。

5. 很多时候找到Qt对应封装的方法后,记得多看看该函数的重载,多个参数的,你会发现不一样的世界,有时候会恍然大悟,原来Qt已经帮我们封装好了。

6. 可以在pro文件中写上标记版本号+ico图标(Qt5才支持)

VERSION  = 2020.10.25
RC_ICONS = main0.ico

7. 管理员运行程序,限定在MSVC编译器

QMAKE_LFLAGS += /MANIFESTUAC:"level='requireAdministrator' uiAccess='false'" #以管理员运行
QMAKE_LFLAGS += /SUBSYSTEM:WINDOWS,"5.01" #VS2013 在XP运行

8. 运行文件附带调试输出窗口

CONFIG += console pro

9. 绘制平铺背景QPainter::drawTiledPixmap,绘制圆角矩形QPainter::drawRoundedRect(),而不是QPainter::drawRoundRect();

10. 移除旧的样式

//移除原有样式
style()->unpolish(ui->btn);
//重新设置新的该控件的样式。
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内置图标封装在QStyle中,大概七十多个图标,可以直接拿来用。

SP_TitleBarMenuButton,
SP_TitleBarMinButton,
SP_TitleBarMaxButton,
SP_TitleBarCloseButton,
SP_MessageBoxInformation,
SP_MessageBoxWarning,
SP_MessageBoxCritical,
SP_MessageBoxQuestion,
...

13. 根据操作系统位数判断加载

win32 {
    contains(DEFINES, WIN64) { DESTDIR = $${PWD}/../../bin64
    } else { DESTDIR = $${PWD}/../../bin32 }
}

14. Qt5增强了很多安全性验证,如果出现setGeometry: Unable to set geometry,请将该控件的可见移到加入布局之后。

15. 可以将控件A添加到布局,然后控件B设置该布局,这种灵活性大大提高了控件的组合度,比如可以在文本框左侧右侧增加一个搜索按钮,按钮设置图标即可。

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. 对QLCDNumber控件设置样式,需要将QLCDNumber的segmentstyle设置为flat。

17. 巧妙的使用findChildren可以查找该控件下的所有子控件。findChild为查找单个。

//查找指定类名objectName的控件
QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");
//查找所有QPushButton
QList<QPushButton *> allPButtons = parentWidget.findChildren<QPushButton *>();
//查找一级子控件,不然会一直遍历所有子控件
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. 使用弱属性机制,可以存储临时的值用于传递判断。可以通过widget->dynamicPropertyNames()列出所有弱属性名称,然后通过widget->property("name")取出对应的弱属性的值。

20. 在开发时, 无论是出于维护的便捷性, 还是节省内存资源的考虑, 都应该有一个 qss 文件来存放所有的样式表, 而不应该将 setStyleSheet 写的到处都是。如果是初学阶段或者测试阶段可以直接UI上右键设置样式表,正式项目还是建议统一到一个qss样式表文件比较好,统一管理。

21. 如果出现Z-order assignment: is not a valid widget.错误提示,用记事本打开对应的ui文件,找到<zorder></zorder>为空的地方,删除即可。

22. 善于利用QComboBox的addItem的第二个参数设置用户数据,可以实现很多效果,使用itemData取出来。

23. 如果用了webengine模块,发布程序的时候带上QtWebEngineProcess.exe+translations文件夹+resources文件夹。

24. 默认Qt是一个窗体一个句柄,如果要让每个控件都拥有独立的句柄,设置下 a.setAttribute(Qt::AA_NativeWindows);

25. Qt+Android防止程序被关闭。

#if defined(Q_OS_ANDROID)
QAndroidService a(argc, argv);
return a.exec()
#else
QApplication a(argc, argv);
return a.exec();
#endif

26. 可以对整体的指示器设置样式,例如 *::down-arrow,*::menu-indicator{} *::up-arrow:disabled,*::up-arrow:off{}。

27. 可以执行位置设置背景图片。

QMainWindow > .QWidget {
    background-color: gainsboro;
    background-image: url(:/images/pagefold.png);
    background-position: top right;
    background-repeat: no-repeat
}

28. 嵌入式linux运行Qt程序 Qt4写法:./HelloQt -qws & Qt5写法:./HelloQt --platform xcb

29. Qtcreator软件的配置文件存放在:C:\Users\Administrator\AppData\Roaming\QtProject,有时候如果发现出问题了,将这个文件夹删除后打开creator自动重新生成即可。

30. QMediaPlayer是个壳,依赖本地解码器,视频这块默认基本上就播放个MP4,如果要支持其他格式需要下载k-lite或者LAV Filters安装即可(WIN上,其他系统上自行搜索)。如果需要做功能强劲的播放器,初学者建议用vlc、mpv,终极大法用ffmpeg。

31. 判断编译器类型、编译器版本、操作系统。

//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     //控制台程序

//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 内核版本

#if (_WIN32_WINNT >= 0x0500) // Windows 2000以上

#if (_WIN32_WINNT >= 0x0501) // Windows XP以上

#if (_WIN32_WINNT >= 0x0600) // Windows Vista以上

32. 在pro中判断Qt版本及构建套件位数

#打印版本信息
message(qt version: $$QT_VERSION)

#判断当前qt版本号
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) {
#自己根据需要做一些处理
}
}

#QT_ARCH是Qt5新增的,在Qt4上没效果
#打印当前Qt构建套件的信息
message($$QT_ARCH)

#表示arm平台构建套件
contains(QT_ARCH, arm) {}

#表示32位的构建套件
contains(QT_ARCH, i386) {}

#表示64位的构建套件
contains(QT_ARCH, x86_64) {}

33. Qt最小化后恢复界面假死冻结,加上代码

void showEvent(QShowEvent *e)
{
    setAttribute(Qt::WA_Mapped);
    QWidget::showEvent(e);
}

34. 获取标题栏高度:style()->pixelMetric(QStyle::PM_TitleBarHeight); PM_TitleBarHeight点进去你会发现新大陆。

35. 设置高分屏属性以便支持2K4K等高分辨率,尤其是手机app。必须写在main函数的QApplication a(argc, argv);的前面。

#if (QT_VERSION > QT_VERSION_CHECK(5,6,0))
    QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

36. 如果运行程序出现 Fault tolerant heap shim applied to current process. This is usually due to previous crashes. 错误。

办法:打开注册表,找到HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers\,选中Layers键值,从右侧列表中删除自己的那个程序路径即可。

37. Qt内置了QFormLayout表单布局用于自动生成标签+输入框的组合的表单界面。

38. qml播放视频在linux需要安装 sudo apt-get install libpulse-dev。

39. 可以直接继承QSqlQueryModel实现自定义的QueryModel,比如某一列字体颜色,占位符,其他样式等,重写QVariant CustomSqlModel::data(const QModelIndex &index, int role) const。

40. Qt5以后提供了类QScroller直接将控件滚动。

//禁用横向滚动条
ui->listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

//禁用纵向滚动条
ui->listWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

//设置横向按照像素值为单位滚动
ui->listWidget->setHorizontalScrollMode(QListWidget::ScrollPerPixel);

//设置纵向按照像素值为单位滚动
ui->listWidget->setVerticalScrollMode(QListWidget::ScrollPerPixel);

//设置滚动对象以及滚动方式为鼠标左键拉动滚动
QScroller::grabGesture(ui->listWidget, QScroller::LeftMouseButtonGesture);

//还有个QScrollerProperties可以设置滚动的一些参数

41. 如果使用sqlite数据库不想产生数据库文件,可以创建内存数据库。

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");

42. 清空数据表并重置自增ID,sql = truncate table table_name。

43. Qtchart模块从Qt5.7开始自带,最低编译要求Qt5.4。在安装的时候记得勾选,默认不勾选。使用该模块需要引入命名空间。

#include <QChartView>
QT_CHARTS_USE_NAMESPACE
class CustomChart : public QChartView

44. QPushButton左对齐文字,需要设置样式表QPushButton{text-align:left;}

45. QLabel有三种设置文本的方法,掌握好Qt的属性系统,举一反三,可以做出很多效果。

ui->label->setStyleSheet("qproperty-text:hello;");
ui->label->setProperty("text", "hello");
ui->label->setText("hello");

46. 巧妙的用QEventLoop开启事件循环,可以使得很多同步获取返回结果而不阻塞界面。QEventLoop内部新建了线程执行。

QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();

47. 多种预定义变量 #if (defined webkit) || (defined webengine),去掉生成空的debug和release目录 CONFIG -= debug_and_release。

48. 新版的Qtcreator增强了语法检查,会弹出很多警告提示等,可以在插件列表中关闭clang打头的几个即可,Help》About Plugins。也可以设置代码检查级别,Tools》Options 》C++ 》Code Model。

49. QSqlTableModel的rowCount方法,默认最大返回256,如果超过256,可以将表格拉到底部,会自动加载剩余的,每次最大加载256条数据,如果需要打印或者导出数据,记得最好采用sql语句去查询,而不是使用QSqlTableModel的rowCount方法。不然永远最大只会导出256条数据。

如果数据量很小,也可以采用如下方法:

//主动加载所有数据,不然获取到的行数<=256
while(model->canFetchMore()) {
    model->fetchMore();
}

50. 如果需要指定无边框窗体,但是又需要保留操作系统的边框特性,可以自由拉伸边框,可以使用

 setWindowFlags(Qt::CustomizeWindowHint);

51. 在某些http post数据的时候,如果采用的是&字符串连接的数据发送,中文解析乱码的话,需要将中文进行URL转码。

QString content = "测试中文";
QString note = content.toUtf8().toPercentEncoding();

52. Qt默认不支持大资源文件,比如添加了字体文件,需要pro文件开启。

CONFIG += resources_big

53. Qt中继承QWidget之后,样式表不起作用,解决办法有三个。强烈推荐方法一。

- 方法一:设置属性 this->setAttribute(Qt::WA_StyledBackground, true);

- 方法二:改成继承QFrame,因为QFrame自带paintEvent函数已做了实现,在使用样式表时会进行解析和绘制。

- 方法三:重新实现QWidget的paintEvent函数时,使用QStylePainter绘制。

void Widget::paintEvent(QPaintEvent *)
{
    QStyleOption option;
    option.initFrom(this);
    QPainter painter(this);
    style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
}

54. 有时候在界面上加了弹簧,需要动态改变弹簧对应的拉伸策略,对应方法为changeSize,很多人会选择使用set开头去找,找不到的。

55. 在使用QFile的过程中,不建议频繁的打开文件写入然后再关闭文件,比如间隔5ms输出日志,IO性能瓶颈很大,这种情况建议先打开文件不要关闭,等待合适的时机比如析构函数中或者日期变了需要重新变换日志文件的时候关闭文件。不然短时间内大量的打开关闭文件会很卡,文件越大越卡。

56. 在很多网络应用程序,需要自定义心跳包来保持连接,不然断电或者非法关闭程序,对方识别不到,需要进行超时检测,但是有些程序没有提供心跳协议,此时需要启用系统层的保活程序,此方法适用于TCP连接。

int fd = tcpSocket->socketDescriptor();
int keepAlive = 1;      //开启keepalive属性,缺省值:0(关闭)
int keepIdle = 5;       //如果在5秒内没有任何数据交互,则进行探测,缺省值:7200(s)
int keepInterval = 2;   //探测时发探测包的时间间隔为2秒,缺省值:75(s)
int keepCount = 2;      //探测重试的次数,全部超时则认定连接失效,缺省值: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 一般都是因为platforms插件目录未打包或者打包错了的原因导致的。

58. 非常不建议tr中包含中文,尽管现在的新版Qt支持中文到其他语言的翻译,但是很不规范,也不知道TMD是谁教的,tr的本意是包含英文,然后翻译到其他语言比如中文,现在大量的初学者滥用tr,如果没有翻译的需求,禁用tr,tr需要开销的,Qt默认会认为他需要翻译,会额外进行特殊处理。

59. 很多人Qt和Qt Creator傻傻分不清楚,经常问Qt什么版本结果发一个Qt Creator的版本过来,Qt Creator是使用Qt编写的集成开发环境IDE,和宇宙第一的Visual Studio一样,他可以是msvc编译器的(WIN对应的Qt集成安装环境中自带的Qt Cerator是msvc的),也可以是mingw编译的,还可以是gcc的。如果是自定义控件插件,需要集成到Qt Creator中,必须保证该插件的动态库文件(dll或者so等文件)对应的编译器和Qt版本以及位数和Qt Creator的版本完全一致才行,否则基本不大可能集成进去。特别注意的是Qt集成环境安装包中的Qt版本和Qt Creator版本未必完全一致,必须擦亮眼睛看清楚,有些是完全一致的。

60. 超过两处相同处理的代码,建议单独写成函数。代码尽量规范精简,比如 if(a == 123) 要写成 if (123 == a),值在前面,再比如 if (ok == true) 要写成 if (ok),if (ok == false) 要写成 if (!ok)等。

61. 很多人问Qt嵌入式平台用哪个好,这里统一回答(当前时间节点2018年):imx6+335x比较稳定,性能高就用RK3288 RK3399,便宜的话就用全志H3,玩一玩可以用树莓派香橙派。

62. 对于大段的注释代码,建议用 #if 0 #endif 将代码块包含起来,而不是将该段代码选中然后全部 // ,下次要打开这段代码的话,又需要重新选中一次取消,如果采用的是 #if 0则只要把0改成1即可,效率大大提升。

63. Qt打包发布,有很多办法,Qt5以后提供了打包工具windeployqt(linux上为linuxdeployqt,mac上为macdeployqt)可以很方便的将应用程序打包,使用下来发现也不是万能的,有时候会多打包一些没有依赖的文件,有时候又会忘记打包一些插件尤其是用了qml的情况下,而且不能识别第三方库,比如程序依赖ffmpeg,则对应的库需要自行拷贝,终极大法就是将你的可执行文件复制到Qt安装目录下的bin目录,然后整个一起打包,挨个删除不大可能依赖的组件,直到删到正常运行为止。

64. Qt中的动画,底层用的是QElapsedTimer定时器来完成处理,比如产生一些指定规则算法的数据,然后对属性进行处理。

65. 在绘制无背景颜色只有边框颜色的圆形时候,可以用绘制360度的圆弧替代,效果完全一致。

QRect rect(-radius, -radius, radius * 2, radius * 2);
//以下两种方法二选一,其实绘制360度的圆弧=绘制无背景的圆形
painter->drawArc(rect, 0, 360 * 16);
painter->drawEllipse(rect);

66. 不要把d指针看的很玄乎,其实就是在类的实现文件定义了一个私有类,用来存放局部变量,个人建议在做一些小项目时,没有太大必要引入这种机制,会降低代码可读性,增加复杂性,新手接受项目后会看的很懵逼。

67. 很多人在绘制的时候,设置画笔以为就只可以设置个单调的颜色,其实QPen还可以设置brush,这样灵活性就提高不知道多少倍,比如设置QPen的brush以后,可以使用各种渐变,比如绘制渐变颜色的进度条和文字等,而不再是单调的一种颜色。

68. 很多控件都带有viewport,比如QTextEdit/QTableWidget/QScrollArea,有时候对这些控件直接处理的时候发现不起作用,需要对其viewport()设置才行,比如设置滚动条区域背景透明,需要使用scrollArea->viewport()->setStyleSheet("background-color:transparent;");而不是scrollArea->setStyleSheet("QScrollArea{background-color:transparent;}");

69. 有时候设置了鼠标跟踪setMouseTracking为真,如果该窗体上面还有其他控件,当鼠标移到其他控件上面的时候,父类的鼠标移动事件MouseMove识别不到了,此时需要用到HoverMove事件,需要先设置 setAttribute(Qt::WA_Hover, true);

70. Qt封装的QDateTime日期时间类非常强大,可以字符串和日期时间相互转换,也可以毫秒数和日期时间相互转换,还可以1970经过的秒数和日期时间相互转换等。

QDateTime dateTime;
QString dateTime_str = dateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
//从字符串转换为毫秒(需完整的年月日时分秒)
datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toMSecsSinceEpoch();
//从字符串转换为秒(需完整的年月日时分秒)
datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toTime_t();
//从毫秒转换到年月日时分秒
datetime.fromMSecsSinceEpoch(1315193829218).toString("yyyy-MM-dd hh:mm:ss:zzz");
//从秒转换到年月日时分秒(若有zzz,则为000)
datetime.fromTime_t(1315193829).toString("yyyy-MM-dd hh:mm:ss[:zzz]");

71. 在我们使用QList、QStringList、QByteArray等链表或者数组的过程中,如果只需要取值,而不是赋值,强烈建议使用 at() 取值而不是 [] 操作符,在官方书籍《C++ GUI Qt 4编程(第二版)》的书中有特别的强调说明,此教材的原作者据说是Qt开发的核心人员编写的,所以还是比较权威,至于使用 at() 与使用 [] 操作符速度效率的比较,网上也有网友做过此类对比。原文在书的212页,这样描述的:Qt对所有的容器和许多其他类都使用隐含共享,隐含共享是Qt对不希望修改的数据决不进行复制的保证,为了使隐含共享的作用发挥得最好,可以采用两个新的编程习惯。第一种习惯是对于一个(非常量的)向量或者列表进行只读存取时,使用 at() 函数而不用 [] 操作符,因为Qt的容器类不能辨别 [] 操作符是否将出现在一个赋值的左边还是右边,他假设最坏的情况出现并且强制执行深层赋值,而 at() 函数则不被允许出现在一个赋值的左边。

72. 如果是dialog窗体,需要在exec以后还能让其他代码继续执行,请在dialog窗体exec前增加一行代码,否则会阻塞窗体消息。

QDialog dialog;
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();

73. 安全的删除Qt的对象类,强烈建议使用deleteLater而不是delete,因为deleteLater会选择在合适的时机进行释放,而delete会立即释放,很可能会出错崩溃。如果要批量删除对象集合,可以用qDeleteAll,比如 qDeleteAll(btns);

74. 在QTableView控件中,如果需要自定义的列按钮、复选框、下拉框等其他模式显示,可以采用自定义委托QItemDelegate来实现,如果需要禁用某列,则在自定义委托的重载createEditor函数返回0即可。自定义委托对应的控件在进入编辑状态的时候出现,如果想一直出现,则需要重载paint函数用drawPrimitive或者drawControl来绘制。

75. 将 QApplication::style() 对应的drawPrimitive、drawControl、drawItemText、drawItemPixmap等几个方法用熟悉了,再结合QStyleOption属性,可以玩转各种自定义委托,还可以直接使用paint函数中的painter进行各种绘制,各种牛逼的表格、树状列表、下拉框等,绝对屌炸天。QApplication::style()->drawControl 的第4个参数如果不设置,则绘制出来的控件不会应用样式表。

76. 心中有坐标,万物皆painter,强烈建议在学习自定义控件绘制的时候,将qpainter.h头文件中的函数全部看一遍、试一遍、理解一遍,这里边包含了所有Qt内置的绘制的接口,对应的参数都试一遍,你会发现很多新大陆,会大大激发你的绘制的兴趣,犹如神笔马良一般,策马崩腾遨游代码绘制的世界。

77. 在使用setItemWidget或者setCellWidget的过程中,有时候会发现设置的控件没有居中显示而是默认的左对齐,而且不会自动拉伸填充,对于追求完美的程序员来说,这个可不大好看,有个终极通用办法就是,将这个控件放到一个widget的布局中,然后将widget添加到item中,这样就完美解决了,而且这样可以组合多个控件产生复杂的控件。

//实例化进度条控件
QProgressBar *progress = new QProgressBar;
//增加widget+布局巧妙实现居中
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. 很多时候需要在已知背景色的情况下,能够清晰的绘制文字,这个时候需要计算对应的文字颜色。

//根据背景色自动计算合适的前景色
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. 对QTableView或者QTableWidget禁用列拖动。

#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转到Qt5,有些类的方法已经废弃或者过时了,如果想要在Qt5中启用Qt4的方法,比如QHeadVew的setMovable,可以在你的pro或者pri文件中加上一行即可:DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0

81. Qt中的QColor对颜色封装的很完美,支持各种转换,比如rgb、hsb、cmy、hsl,对应的是toRgb、toHsv、toCmyk、toHsl,还支持透明度设置,颜色值还能转成16进制格式显示。

QColor color(255, 0, 0, 100);
qDebug() << color.name() << color.name(QColor::HexArgb);
//输出 #ff0000 #64ff0000

82. QVariant类型异常的强大,可以说是万能的类型,在进行配置文件的存储的时候,经常会用到QVariant的转换,QVariant默认自带了toString、toFloat等各种转换,但是还是不够,比如有时候需要从QVariant转到QColor,而却没有提供toColor的函数,这个时候就要用到万能办法。

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 *之间转换,最好用toStdString().c_str()而不是toLocal8Bit().constData(),比如在setProperty中如果用后者,字符串中文就会不正确,英文正常。

84. Qt的信号槽机制非常牛逼,也是Qt的独特的核心功能之一,有时候我们在很多窗体中传递信号来实现更新或者处理,如果窗体层级比较多,比如窗体A的父类是窗体B,窗体B的父类是窗体C,窗体C有个子窗体D,如果窗体A一个信号要传递给窗体D,问题来了,必须先经过窗体B中转到窗体C再到窗体D才行,这样的话各种信号关联信号的connect会非常多而且管理起来比较乱,可以考虑增加一个全局的单例类AppEvent,公共的信号放这里,然后窗体A对应信号绑定到AppEvent,窗体D绑定AppEvent的信号到对应的槽函数即可,干净清爽整洁。

85. QTextEdit右键菜单默认英文的,如果想要中文显示,加载widgets.qm文件即可,一个Qt程序中可以安装多个翻译文件,不冲突。

86. Qt中有个全局的焦点切换信号focusChanged,可以用它做自定义的输入法。Qt4中默认会安装输入法上下文,比如在main函数打印a.inputContext会显示值,这个默认安装的输入法上下文,会拦截两个牛逼的信号QEvent::RequestSoftwareInputPanel和QEvent::CloseSoftwareInputPanel,以至于就算你安装了全局的事件过滤器依然识别不到这两个信号,你只需要在main函数执行a.setInputContext(0)即可,意思是安装输入法上下文为空。

87. 在Qt5.10以后,表格控件QTableWidget或者QTableView的默认最小列宽改成了15,以前的版本是0,所以在新版的qt中,如果设置表格的列宽过小,不会应用,取的是最小的列宽。所以如果要设置更小的列宽需要重新设置ui->tableView->horizontalHeader()->setMinimumSectionSize(0);

88. Qt源码中内置了一些未公开的不能直接使用的黑科技,都藏在对应模块的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("");
//解压文件夹到当前目录

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. 理论上串口和网络收发数据都是默认异步的,操作系统自动调度,完全不会卡住界面,网上那些说收发数据卡住界面主线程的都是扯几把蛋,真正的耗时是在运算以及运算后的处理,而不是收发数据,在一些小数据量运算处理的项目中,一般不建议动用线程去处理,线程需要调度开销的,不要什么东西都往线程里边扔,线程不是万能的。只有当真正需要将一些很耗时的操作比如编码解码等,才需要移到线程处理。

90. 在构造函数中获取控件的宽高很可能是不正确的,需要在控件首次显示以后再获取才是正确的,控件是在首次显示以后才会设置好正确的宽高值,记住是在首次显示以后,而不是构造函数或者程序启动好以后,如果程序启动好以后有些容器控件比如QTabWidget中的没有显示的页面的控件,你去获取宽高很可能也是不正确的,万无一失的办法就是首次显示以后去获取。

91. 数据库处理一般建议在主线程,如果非要在其他线程,务必记得打开数据库也要在那个线程,即在那个线程使用数据库就在那个线程打开,不能打开数据库在主线程,执行sql在子线程,很可能出问题。

92. 新版的QTcpServer类在64位版本的Qt下很可能不会进入incomingConnection函数,那是因为Qt5对应的incomingConnection函数参数变了,由之前的int改成了qintptr,改成qintptr有个好处,在32位上自动是quint32而在64位上自动是quint64,如果在Qt5中继续写的参数是int则在32位上没有问题在64位上才有问题,所以为了兼容Qt4和Qt5,必须按照不一样的参数写。

#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
    void incomingConnection(qintptr handle);
#else
    void incomingConnection(int handle);
#endif

93. Qt支持所有的界面控件比如QPushButton、QLineEdit自动关联 on_控件名_信号(参数) 信号槽,比如按钮的单击信号 on_pushButton_clicked(),然后直接实现槽函数即可。

94. QWebEngineView控件由于使用了opengl,在某些电脑上可能由于opengl的驱动过低会导致花屏或者各种奇奇怪怪的问题,比如showfullscreen的情况下鼠标右键失效,需要在main函数启用软件opengl渲染。

#if (QT_VERSION > QT_VERSION_CHECK(5,4,0))
    //下面两种方法都可以,Qt默认采用的是AA_UseDesktopOpenGL
    QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
    //QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
#endif
    QApplication a(argc, argv);

另外一个方法解决 全屏+QWebEngineView控件一起会产生右键菜单无法弹出的bug,需要上移一个像素

QRect rect = qApp->desktop()->geometry();
rect.setY(-1);
rect.setHeight(rect.height());
this->setGeometry(rect);

95. QStyle内置了很多方法用处很大,比如精确获取滑动条鼠标按下处的值。

QStyle::sliderValueFromPosition(minimum(), maximum(), event->x(), width());

96. 用QFile读写文件的时候,推荐用QTextStream文件流的方式来读写文件,速度快很多,基本上会有30%的提升,文件越大性能区别越大。

//从文件加载英文属性与中文属性对照表
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文件默认是ANSI格式,不支持UTF8,如果在QtCreator中打开qss文件来编辑保存,这样很可能导致qss加载以后没有效果。

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读取样式文件不用区分文件编码 带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读取默认支持的是ANSI格式,如果不小心用creator打开编辑过了很可能打不开

        qss = QLatin1String(file.readAll());

#endif

        QString paletteColor = qss.mid(20, 7);

        qApp->setPalette(QPalette(QColor(paletteColor)));

        qApp->setStyleSheet(qss);

        file.close();

    }

}

98. QString内置了很多转换函数,比如可以调用toDouble转为double数据,但是当你转完并打印的时候你会发现精确少了,只剩下三位了,其实原始数据还是完整的精确度的,只是打印的时候优化成了三位,如果要保证完整的精确度,可以调用 qSetRealNumberPrecision 函数设置精确度位数即可。

QString s1, s2;
s1 = "666.5567124";
s2.setNum(888.5632123, 'f', 7);
qDebug() << qSetRealNumberPrecision(10) << s1.toDouble() << s2.toDouble();

99. 用QScriptValueIterator解析数据的时候,会发现总是会多一个节点内容,并且内容为空,如果需要跳过则增加一行代码。

while (it.hasNext()) {
    it.next();    
    if (it.flags() & QScriptValue::SkipInEnumeration)      
       continue;     
    qDebug() << it.name();
}

100. setPixmap是最糟糕的贴图方式,一般只用来简单的不是很频繁的贴图,频繁的建议painter绘制,默认双缓冲,在高级点用opengl绘制,利用GPU。

101. 如果需要在尺寸改变的时候不重绘窗体,则设置属性即可 this->setAttribute(Qt::WA_StaticContents, true); 这样可以避免可以避免对已经显示区域的重新绘制。

102. 默认程序中获取焦点以后会有虚边框,如果看着觉得碍眼不舒服可以去掉,设置样式即可:setStyleSheet("*{outline:0px;}");

103. Qt表格控件一些常用的设置封装,QTableWidget继承自QTableView,所以下面这个函数支持传入QTableWidget。

void QUIHelper::initTableView(QTableView *tableView, int rowHeight, bool headVisible, bool edit)
{
    //奇数偶数行颜色交替
    tableView->setAlternatingRowColors(false);
    //垂直表头是否可见
    tableView->verticalHeader()->setVisible(headVisible);
    //选中一行表头是否加粗
    tableView->horizontalHeader()->setHighlightSections(false);
    //最后一行拉伸填充
    tableView->horizontalHeader()->setStretchLastSection(true);
    //行标题最小宽度尺寸
    tableView->horizontalHeader()->setMinimumSectionSize(0);
    //行标题最大高度
    tableView->horizontalHeader()->setMaximumHeight(rowHeight);
    //默认行高
    tableView->verticalHeader()->setDefaultSectionSize(rowHeight);
    //选中时一行整体选中
    tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
    //只允许选择单个
    tableView->setSelectionMode(QAbstractItemView::SingleSelection);
    //表头不可单击
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
    tableView->horizontalHeader()->setSectionsClickable(false);
#else
    tableView->horizontalHeader()->setClickable(false);
#endif
    //鼠标按下即进入编辑模式
    if (edit) {
        tableView->setEditTriggers(QAbstractItemView::CurrentChanged | QAbstractItemView::DoubleClicked);
    } else {
        tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
    }
}

104. 在一些大的项目中,可能嵌套了很多子项目,有时候会遇到子项目依赖其他子项目的时候,比如一部分子项目用来生成动态库,一部分子项目依赖这个动态库进行编译,此时就需要子项目按照顺序编译。

TEMPLATE = subdirs
#设置ordered参数以后会依次编译 demo designer examples
CONFIG  += ordered
SUBDIRS += demo
SUBDIRS += designer
SUBDIRS += examples

105. MSVC编译器的选择说明

- 如果是32位的Qt则编译器选择x86开头的

- 如果是64位的Qt则编译器选择amd64开头的

- 具体是看安装的Qt构建套件版本以及目标运行平台的系统位数和架构

- 一般现在的电脑默认以64位的居多,选择amd64即可

- 如果用户需要兼容32位的系统则建议选择32位的Qt,这样即可在32位也可以在64位系统运行

- 诸葛大佬补充:x86/x64都是编译环境和运行环境相同,没有或。带下划线的就是交叉编译,前面是编译环境,后面是运行环境。

| 名称 | 说明 |

| ------ | ------ |

|x86|32/64位系统上编译在32/64位系统上运行|

|x86_amd64|32/64位系统上编译在64位系统上运行|

|x86_arm|32/64位系统上编译在arm系统上运行|

|amd64|64位系统上编译在64位系统上运行|

|amd64_x86|64位系统上编译在32/64位系统上运行|

|amd64_arm|64位系统上编译在arm系统上运行|

106. 很多时候用QDialog的时候会发现阻塞了消息,而有的时候我们希望是后台的一些消息继续运行不要终止,此时需要做个设置。

QDialog dialog;
dialog.setWindowModality(Qt::WindowModal);

107. 很多初学者甚至几年工作经验的人,对多线程有很深的误解和滥用,尤其是在串口和网络通信这块,什么都往多线程里面丢,一旦遇到界面卡,就把数据收发啥的都搞到多线程里面去,殊不知绝大部分时候那根本没啥用,因为没找到出问题的根源。

- 如果你没有使用wait***函数的话,大部分的界面卡都出在数据处理和展示中,比如传过来的是一张图片的数据,你需要将这些数据转成图片,这个肯定是耗时的;

- 还有就是就收到的数据曲线绘制出来,如果过于频繁或者间隔过短,肯定会给UI造成很大的压力的,最好的办法是解决如何不要频繁绘制UI比如合并数据一起绘制等;

- 如果是因为绘制UI造成的卡,那多线程也是没啥用的,因为UI只能在主线程;

- 串口和网络的数据收发默认都是异步的,由操作系统调度的,如果数据处理复杂而且数据量大,你要做的是将数据处理放到多线程中;

- 如果没有严格的数据同步需求,根本不需要调用wait***之类的函数来立即发送和接收数据,实际需求中大部分的应用场景其实异步收发数据就足够了;

- 有严格数据同步需求的场景还是放到多线程会好一些,不然你wait***就卡在那边了;

- 多线程是需要占用系统资源的,理论上来说,如果线程数量超过了CPU的核心数量,其实多线程调度可能花费的时间更多,各位在使用过程中要权衡利弊;

108. 在嵌入式linux上,如果设置了无边框窗体,而该窗体中又有文本框之类的,发现没法产生焦点进行输入,此时需要主动激活窗体才行。

//这种方式设置的无边框窗体在嵌入式设备上无法产生焦点
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
//需要在show以后主动激活窗体
w->show();
w->activateWindow();

109. QString的replace函数会改变原字符串,切记,他在返回替换后的新字符串的同时也会改变原字符串,我的乖乖!

110. QGraphicsEffect类的相关效果很炫,可以实现很多效果比如透明、渐变、阴影等,但是该类很耗CPU,如果不是特别需要一般不建议用,就算用也是要用在该部件后期不会发生频繁绘制的场景,不然会让你哭晕在厕所。

111. 在不同的平台上文件路径的斜杠也是不一样的,比如linux系统一般都是 / 斜杠,而在windows上都是 \ 两个反斜杠,Qt本身程序内部无论在win还是linux都支持 / 斜杠的路径,但是一些第三方库的话可能需要转换成对应系统的路径,这就需要用到斜杠转换,Qt当然内置类方法。

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方法可以实现很多效果,包括同步和异步执行,比如有个应用场景是在回调中,需要异步调用一个public函数,如果直接调用的话会发现不成功,此时需要使用 QMetaObject::invokeMethod(obj, "fun", Qt::QueuedConnection); 这种方式来就可以。invokeMethod函数有很多重载参数,可以传入返回值和执行方法的参数等。

113. Qt5中的信号是public的,可以在需要的地方直接emit即可,而在Qt4中信号是protected的,不能直接使用,需要定义一个public函数来emit。

114. Qt5.15版本开始官方不再提供安装包,只提供源码,可以自行编译或者在线安装,估计每次编译各种版本太麻烦,更多的是为了统计收集用户使用信息比如通过在线安装,后期可能会逐步加大商业化力度。

t 面试指南(内含面试题)

QT开发岗位需要掌握哪些技能?

QT开发岗位需要掌握C++语言、QT框架、GUI编程、多线程编程等技能。此外,对于不同的岗位还需要掌握不同的领域知识,如网络编程、数据库编程等。

如何准备QT面试?

首先,需要了解公司的招聘要求和岗位职责,了解面试流程和面试形式。其次,需要准备面试所需的知识和技能,可以通过阅读相关书籍、参加培训课程、做练习题等方式进行。最后,需要进行模拟面试,找到自己的不足之处并加以改进。

面试题:(需要pdf文档的可以进企鹅937552610裙嗱)

一、c++基础知识

1、进程和线程的同步方式

进程:1)管道,是内核里的一串缓存

2)消息队列

3)共享内存

4)信号量机制

5)信号

6)socket

线程:1)等待通知机制

2)共享内存

3)管道

5)并发工具

信号量、读写锁、互斥锁和条件变量

线程的死锁概念 :线程间相互等待临界资源而造成彼此无法继续执行

方式一

1)创建一个线程类的子对象,继承QThread:

2)重写父类的run()方法,在该函数内部编写子线程要处理的具体业务流程

3)在主线程中创建子线程对象,new一个

4)启动子线程,调用start()方法

方式二

1)创建一个新的类,QObject派生

2)类中添加一个公有的自定义成员函数,函数体就是子线程中执行的业务逻辑

3)主线程中创建一个 QThread 对象,就是子线程对象

4)在主线程中创建工作的类对象,不要给创建的对象指定父对象

5)Mywork对象移动到创建的子线程对象中,需要调用QObject类提供的 moveToThread() 方法

注意事项:

业务对象, 构造的时候不能指定父对象

子线程中不能处理ui窗口(ui相关的类)

子线程中只能处理一些数据相关的操作, 不能涉及窗口

2、什么是堆栈

栈区(stack)

堆区(heap)抽象数据结构:后进先出

全局区(静态区)(static)

文字常量区

程序代码区

栈是自动分配释放,一级缓存,类似数组的结构。

堆是由程序员分配释放,二级缓存,速度慢些,先进后出。

3、常用的排序

插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序

冒泡排序:

时间复杂度:最好O(n),最坏O(n的2次方)。

空间复杂度:O(1);

4、数组和链表的区别

1)数组连续储存、固定长度、增删需要移动其他元素。链表动态分配,不连续,需要malloc或

new申请内存,不用时要free或delete.

2)数组访问效率高,链表删插效率高

3)数组利用下标定位,查找的时间复杂度是O(1),链表通过遍历定位元素,查找的时间复杂度是O(N)。

4)数组插入和删除需要移动其他元素,时间复杂度是O(N),链表的插入或删除不需要移动其他元素,时间复杂度是O(1)。

5、回调函数的三种典型使用场景:

1)实现函数功能重定义

2)扩展函数功能

3)实现程序分层设计

6、区分static和const

static :

(1) 修饰全局变量:变量只在本模块内可见,在定义不需要与其他文件共享的全局变量时,加上 static 关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。

(2) 修饰局部变量:变量在全局数据区分配内存空间,编译器自动对其初始化,其作用域为局部作用域,当定义它的函数结束时,其作用域随之结束。

(3) 修饰函数:函数的使用方式与全局变量类似,在函数的返回类型前加上 static,就是静态函数,静态函数只能在声明它的文件中可见,其他文件不能引用该函数,不同的文件可以使用相同名字的静态函数,互不影响。

const :

被 const 修饰的变量是只读变量,本质还是变量,有人称其为常变量,和普通变量的区别在于常变量不能用于左值,其余的用法和普通常量一样,变量名不可以改变。

7、工作中有没有使用过动态库和静态库?能不能简单说下两者的区别?

答:静态库:在链接阶段将汇编生成的目标文件.o与引用库一起链接打包到可执行文件中,可简单看成(.o或者.obj文件的集合)。(1)对函数库的链接是放在编译时期完成的(2)程序在运行时与函数库没有瓜葛,移植方便(3)浪费空间和资源

动态库:(1)将库函数的链接载入推迟到程序运行时期(2)可以实现进程间的资源共享(因此也称为共享库)(3)将一些程序升级变得简单(4)可以真正的做到链接载入完全由程序员在程序代码中控制(显示调用)

8、虚函数:

父类型的指针指向其子类的实例

存在虚函数的类都有一个一维的虚函数表叫做虚表,

类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

默认构造函数、初始化构造函数、拷贝构造函数、移动构造函数

构造函数为什么不能被声明为虚函数?

虚函数对应一个vtale,这个表的地址是存储在对象的内存空间的。如果将构造函数设置为虚函数,

就需要到vtable 中调用,可是对象还没有实例化,没有内存空间分配,如何调用

9、STL相关知识

STL由6部分组成:容器(Container)、算法(Algorithm)、 迭代器(Iterator)、

仿函数(Function object)、适配器(Adaptor)、空间配制器(Allocator)

容器、算法、迭代器、仿函数、适配器、空间配置器

容器:数组(array) , 链表(list), tree(树),栈(stack), 队列(queue), 集合(set),映射表(map)

string容器

Vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间

stack是一种先进后出

Queue是一种先进先出

list容器链表是一种物理存储单元上非连续、非顺序的存储结构

set/multiset二叉树

map/multimap二叉树

交换,查找,遍历,复制,修改,反转,排序,合并等

10、智能指针:auto_ptr(17后已遗弃)、unique_ptr、shared_ptr 和 weak_ptr

智能指针本质就是一个类模板,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。

shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:

shared_ptr在内部会维护着一份引用计数,用来记录该份资源被几个对象共享。

当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。

如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。

如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源,否则其他对象就成为野指针。

11、指针和引用的区别

指针是一个实体,而引用仅是个别名。

引用只能在定义时被初始化一次,之后不可变,但指针可以改变。

指针可以有多级,但引用只有一级。

引用不能为空,指针可以为空。

sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小

指针和引用的自增(++)运算意义不一样

引用是类型安全的,而指针不是 ,引用比指针多了类型检查。

引用没有const,指针有const,const的指针不可变。

访问实体方式不同,指针需要显式解引用,引用编译器自己处理

二、网络编程

1、描述QT的TCP通讯流程

服务端:(QTcpServer)

①创建QTcpServer对象

②监听list需要的参数是地址和端口号

③当有新的客户端连接成功回发送newConnect信号

④在newConnection信号槽函数中,调用nextPendingConnection函数获取新连接QTcpSocket对象

⑤连接QTcpSocket对象的readRead信号

⑥在readRead信号的槽函数使用read接收数据

⑦调用write成员函数发送数据

客户端:(QTcpSocket)

①创建QTcpSocket对象

②当对象与Server连接成功时会发送connected 信号

③调用成员函数connectToHost连接服务器,需要的参数是地址和端口号

④connected信号的槽函数开启发送数据

⑤使用write发送数据,read接收数据

1)长连接的保活问题

标准TCP层协议里把对方超时设为2小时,若服务器端超过了2小时还没收到客户的信息,它就发送探测报文段,若发送了10个探测报文段(每一个相隔75S)还没有收到响应,就假定客户出了故障,并终止这个连接。因此应对tcp长连接进行保活。

2)TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;

解决方法二:发送固定长度的消息

解决方法三:把消息的尺寸与消息一块发送

解决方法四:双方约定每次传送的大小

解决方法五:双方约定使用特殊标记来区分消息间隔

解决方法六:标准协议按协议规则处理,如Sip协议

3)粘包问题

TCP产生粘包问题的主要原因是:TCP是面向连接的,所以在TCP看来,并没有把消息看成一条条的,而是全部消息在TCP眼里都是字节流,

因此A、B消息混在一起后,TCP就分不清了。

粘包问题的最本质原因在与接收对等方无法分辨消息与消息之间的边界在哪。我们通过使用某种方案给出边界,例如:

包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对先接收包体长度,依据包体长度来接收包体。

解决方法一:TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;

解决方法二:发送固定长度的消息

解决方法三:把消息的尺寸与消息一块发送

解决方法四:双方约定每次传送的大小

解决方法五:双方约定使用特殊标记来区分消息间隔

解决方法六:标准协议按协议规则处理,如Sip协议

发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。

发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。

可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

指定数据长度的解决方法,主要思路:

我们在数据结构中有个成员代表了长度(消息头),我们准备一个足够大的消息缓冲区(程序中是1024000个字节),循环使用socket中的recv每次读取最多102400个字节,然后把循环接收的消息拼接到消息缓冲区中,直到接收到的消息大于消息头指示的长度,则接收到了一个完整的消息(所以我们的消息缓冲区要比完整的消息还要大才行),进行消息处理。

【文章福利】Qt开发学习资料包、大厂面试题、技术视频和学习路线图,包括(Qt C++基础,数据库编程,Qt项目实战、Qt框架、QML、Opencv、qt线程等等)有需要的可以进企鹅裙937552610领取哦~

4)TCP与UDP区别总结:

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付

3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的

UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP首部开销20字节;UDP的首部开销小,只有8个字节

6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

7、三次握手和四次挥手具体流程

SYN:请求建立连接,FIN:请求断开连接,ACK:确认是否有效, seq:序列号, ack:确认号

1)三次握手

1.客户端向服务端发送⼀个SYN=1(请求建立连接),并生成一个序列号seq=j。

2.服务端接收到SYN=1后,给客户端发送⼀个SYN=1与ACK=1;并将ack置为j+1;同时生成一个序列号seq=k。

3.客户端接收到会检查ack是否为j+1与ACK是否为1,如果是,则会给服务端发送一个ACK=1与ack=k+1,以及自己的序列号seq=j=1; 服务端接收到会检查ACK是否为1与ack是否为k+1,如果是则代表连接建立成功,两者间可以传递数据。

2)四次挥手

1.客户端向服务端发送FIN=1(请求关闭连接),并生成一个序列号seq=x。

2.服务端接收FIN后,向客户端发送ACK=1,ack=x+1,并生成序列号seq=y(客户端无数据发送,但服务器端需发送完最后的数据)。

3.服务端处理完所有数据后,向客户端发送FIN=1与ACK=1,ack=x+1,并生成序列号z,表示服务端现在可以断开连接。

4.客户端收到服务端的数据包后,会向服务端发送ACK=1,seq=x=1,ack=z+1(需要等待2MSL后才可断开连接)。

8、指针和引用的区别

指针是一个实体,而引用仅是个别名。

引用只能在定义时被初始化一次,之后不可变,但指针可以改变。

指针可以有多级,但引用只有一级。

引用不能为空,指针可以为空。

sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小

指针和引用的自增(++)运算意义不一样

引用是类型安全的,而指针不是 ,引用比指针多了类型检查。

引用没有const,指针有const,const的指针不可变。

访问实体方式不同,指针需要显式解引用,引用编译器自己处理

9、HTTP 是一种 超文本传输协议

因特网的协议栈由五个部分组成:物理层、链路层、网络层、运输层和应用层

URL(即网址)

浏览器会向DNS(域名服务器,后面会说)提供网址

HTML 称为超文本标记语言

http协议是应用层协议,主要是解决如何包装数据。而tcp协议是传输层协议,主要解决数据如何在网络中传输。

通俗点说,http的任务是与服务器交换信息,它不管怎么连到服务器和保证数据正确的事情。而tcp的任务是保证连接的可靠,它只管连接,它不管连接后要传什么数据。

http协议是建立在tcp之上的,http是无状态的短链接,而tcp是有状态的长链接。

HTTPS:是以安全为目标的 HTTP 通道,是 HTTP 的安全版。HTTPS 的安全基础是 SSL。

SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。

二、HTTP 与 HTTPS 的区别

1、HTTPS 协议需要到 CA (Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。

(以前的网易官网是http,而网易邮箱是 https 。)

2、HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。

3、HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,

比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。

无连接的意思是指通信双方都不长久的维持对方的任何信息。)

三、QT相关知识

1、什么是元对象系统

元对象系统是一个基于标准C++的扩展,为QT提供了信号与槽机制、实时类型信息、动态属性系统。

元对象系统的三个基本条件:类必须继承自QObject、类声明Q_OBJECT宏(默认私有)、元对象编译器moc。

信号与槽类似观察者模式;

回调函数的本质是基于“你想让别人的代码执行你的代码,而别人的代码你又不能动”这种产生的;

对象树;

信号与槽的实现是借助了Qt 的元对象系统,元对象系统有一个元对象编译器,

程序编译之前会有一个预处理过程,预处理将一个类/对象中的信号,槽的字符串值分别保存在一个容器中,可能是字符串或者其他的有序容器

第5个参数跟线程相关Qt::AutoConnection

1)Qt信号槽机制的优势

(1)类型安全。需要关联的信号和槽的签名必须是等同的,

(2)松散耦合。

(3)信号和槽机制增强了对象间通信的灵活性。一个信号可以关联多个槽,也可以多个信号关联一个槽。

2)Qt信号槽机制的不足

同回调函数相比,信号和槽机制运行速度有些慢。通过传递一个信号来调用槽函数将会比直接调用非虚函数运行速度慢10倍。原因如下:

(1)需要定位接收信号的对象;

(2)安全地遍历所有的关联(如一个信号关联多个槽的情况);

(3)编组/解组传递的参数;

(4)多线程的时候,信号可能需要排队等待。

然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。

知道QT事件机制有几种级别的事件过滤吗?能大致描述下吗?

答:根据对Qt事件机制的分析, 我们可以得到5种级别的事件过滤,处理办法. 以功能从弱到强, 排列如下:

1)重载特定事件处理函数.

最常见的事件处理办法就是重载象mousePressEvent(), keyPressEvent(), paintEvent() 这样的特定事件处理函数.

2)重载event()函数.

通过重载event()函数,我们可以在事件被特定的事件处理函数处理之前(象keyPressEvent())处理它. 比如, 当我们想改变tab键的默认动作时,一般要重载这个函数. 在处理一些不常见的事件(比如:LayoutDirectionChange)时,evnet()也很有用,因为这些函数没有相应的特定事件处理函数. 当我们重载event()函数时, 需要调用父类的event()函数来处理我们不需要处理或是不清楚如何处理的事件.

3) 在Qt对象上安装事件过滤器.

安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件)

首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数. 这样所有发往B的事件都将先由A的eventFilter()处理.

然后, A要重载QObject::eventFilter()函数, 在eventFilter() 中书写对事件进行处理的代码.

4) 给QAppliction对象安装事件过滤器.

一旦我们给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前这个 eventFilter(). 在debug的时候,这个办法就非常有用, 也常常被用来处理失效了的widget的鼠标事件,通常这些事件会被QApplication::notify()丢掉. ( 在QApplication::notify() 中, 是先调用qApp的过滤器, 再对事件进行分析, 以决定是否合并或丢弃)

5) 继承QApplication类,并重载notify()函数.

Qt 是用QApplication::notify()函数来分发事件的.想要在任何事件过滤器查看任何事件之前先得到这些事件,重载这个函数是唯一的办法. 通常来说事件过滤器更好用一些, 因为不需要去继承QApplication类. 而且可以给QApplication对象安装任意个数的事件。

自定义界面

UI设计器集成了Qt样式表的编辑功能

前景色color

背景色background-color

选中后颜色selection-color

背景图片background-image

选择器:QPushButton 、QDialog QPushButton、QPushButton#btnOk

子控件:drop-down、up-button、down-button

伪状态:hover(鼠标移动到条目上方时),active(处于活动的窗体)

属性: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声明了实现接口

Q_PLUGIN_METADATA声明了元数据名称

要把dll和lib放到插件目录下

D:\Qt\Qt5.9.1\Tools\QtCreator\bin\plugins\designer

D:\Qt\Qt5.9.1.9.1\msvc2015\plugins\designer

QWT,是一个基于LGPL版权协议的开源项目, 可生成各种统计图

QT和MFC消息机制比较:

mfc的消息机制其实就是消息映射机制,程序员需要将自定义消息和对应的处理函数添加到消息映射表中。通过PostMessage和SendMessage来实现异步和同步消息。

QT的信号槽机制是信号和槽函数通过QObject::connect动态链接上后存储到元对象系统中,通过emit发送信号,对应的槽函数执行。

比较

Qt的信号槽是动态链接的,而MFC的消息映射是静态的

Qt的信号支持自定义参数,且类型安全

在多线程中,MFC需要向已知线程对象发布消息,而Qt可以不考虑多线程之间的信号槽关系

总结

Qt相比较MFC的消息机制,使用起来更方便,最大的优势是Qt支持动态链接信号槽。

四、项目相关

1、定位问题:

请问下,如果软件除了问题(Bug),如何快速定位?主要方法有哪些?

答:打印输出/代码调试/日志记录/分析工具/找同事讨论。

1)二分法定位技巧

无论是有多复杂的代码,利用二分法定位技巧一般都是可以定位到问题所在。

从二分法定位技巧可以延伸出一些具体的处理bug的方法,比如:对输入数据二分、对代码版本二分、注释掉部分代码、在不同位置插入试探性代码、对运行环境二分。

2)IDE调试

IDE的VS debug的功能简直就是立竿见影。它可以加断点,单步调试。

单步调试可以让我们对代码逻辑,执行顺序,以及各种中间结果更加清晰。

至于本身容易出错的BUG,用IDE调试简直是再合适不过了。

3)重新读一遍程序

相对新手程序员来说,如果代码出现bug,可以重新读一遍程序。这种方法是最有效、最快速的 Debug 方式。

4)必杀,重写一遍

如果你发现无论如何也找不到BUG,而且代码只是复杂,本身不是很长,直接重写代码吧!

5)小黄鸭调试法

小黄鸭调试法是程序员们经常使用的调试代码方法之一。

小黄鸭不懂程序,所以我们可以向他解释每一行程序的作用,以此来激发灵感。

内存泄露及解决办法:

什么是内存泄露?

简单地说就是申请了一块内存空间,使用完毕后没有释放掉。(1)new和malloc申请资源使用后,没有用delete和free释放;(2)子类继承父类时,父类析构函数不是虚函数。(3)Windows句柄资源使用后没有释放。

怎么检测?

第一:良好的编码习惯,使用了内存分配的函数,一旦使用完毕,要记得使用其相应的函数释放掉。

第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。

第三:使用智能指针。

第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky、Valgrind等等。

数据库相关

什么是事务

并发控制

原子性、一致性、隔离性、持续性

valueChanged(int)

QMutex mutex;

QMutexLocker locker(&mutex);

waitForReadyRead()

常用的设计结构:

工厂方法 Factory Method 定义了创建对象的接口,让子类决定实例化哪个类

单例 Singleton 确保一个类只有一个实例,并提供一个访问它的全局访问点

原型 Prototype 通过拷贝原型对象创建新的对象。

适配器 Adapter 将一个类的接口转换成希望的另外一个接口,使得原本不兼容的接口可以协同工作。

观察者 Observer 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

单例:懒汉模式和饿汉模式

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了;线程安全。

而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例;

线程不安全,就只有在实例化之前调用的时候加锁,后面不加锁。

C++新特性主要包括包含语法改进和标准库扩充两个方面,主要包括以下11点:

语法的改进

(1)统一的初始化方法,允许构造函数或其他函数像参数一样使用初始化列表

(2)成员变量默认初始化

(3)类型推导 auto关键字 用于定义变量,编译器可以自动判断的类型(前提:定义一个变量时对其进行初始化)

(4)decltype 求表达式的类型

(5)智能指针 shared_ptr

(6)空指针 nullptr(原来NULL)

nullptr表示空指针,是一个关键词

NULL是老版本的,是一个0的宏

(7)基于范围的for循环

(8)右值引用和move语义 让程序员有意识减少进行深拷贝操作

标准库扩充(往STL里新加进一些模板类,比较好用)

(9)无序容器(哈希表) 用法和功能同map一模一样,区别在于哈希表的效率更高

(10)正则表达式 可以认为正则表达式实质上是一个字符串,该字符串描述了一种特定模式的字符串

(11)Lambda表达式

息队列

“消息队列(MQ)”是在消息的传输过程中保存消息的容器。

消息队列正如同一种先进先出的队列结构,它将发送方的消息推入队列中,并依序推送给接收方。消息队列相关的通信协议都属于应用层协议,位于OSI模型第七层,是基于TCP/IP的通信协议。

与TCP、UDP或是HTTP协议不同,MQ相关协议没有服务端和客户端的概念。原本的客户端和服务端,现在都通过一个中间件服务器(broker)交互,消息的发送方称为生产者,消息的接收方成为消费者,生产者和消费者都可以视同broker的客户端。

通过这种设计,所有消息都被存放于一个中间服务器中,通信的双方不再需要创建服务。这样做带来了几个好处:解耦,异步调用,削峰。

解耦:通过中间件,各个系统之间可以独立运行,不会因为其中一个系统的崩溃影响其他系统,且整个系统的可拓展性也大大加强。

异步:发送方的消息推入了中间件,这条消息可以被所有相关的接收方看到,因此它们可以同时开始处理,这种串联的结构的时间消耗比其他的串行结构小得多。

削峰:在高并发环境下,短时间的大量请求会导致系统和数据库发生很多问题,所以需要对流量进行控制,通过消息队列设置每秒向消费者投递的消息数量,可以控制并发环境下的系统稳定性。

但是,消息队列同样有它的不足。如降低系统可用性,增加系统的复杂性和一致性问题等。因此,是否使用消息队列也必须根据实际应用来决定。

基于消息队列的通信协议有很多,常见的有RabbitMQ,Kafka,还有本文介绍的MQTT。

【领QT开发教程学习资料,点击下方链接免费领取↓↓,先码住不迷路~】

点击→领取「链接」

MQTT

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。

MQTT会构建底层网络传输:它将建立客户端到服务器的连接,提供两者之间的一个有序的、无损的、基于字节流的双向传输。当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(QoS)和主题名(Topic)相关连。

MQTT中的几个重要概念:

订阅(Subscription)

订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。

会话(Session)

每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。

主题名(Topic Name)

连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。

主题筛选器(Topic Filter)

一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。

负载(Payload)

消息订阅者所具体接收的内容。

【领QT开发教程学习资料,点击下方链接免费领取↓↓,先码住不迷路~】

点击→领取「链接」

配置Qt-MQTT环境

默认的Qt环境是不能使用MQTT的,但Qt官方提供了基于MQTT的封装,需要通过源码进行编译。

在dev分支中可以选择MQTT版本,选择最新的下载到本地。

下载下来的是一个Qt项目,在Qt Creator中打开.pro文件,然后使用Release模式,用你所需要的编译器(VS,MinGW…),开始编译。

如果你的系统没有安装过Perl,需要先安装Perl,并加入到系统环境变量中。

完成编译后,可以在你的编译路径的/bin目录中得到所需的动态链接库文件Qt5Mqtt.dll和Qt5Mqttd.dll。前者是release版库,后者是debug版。

为了实现一次配置,所有项目可用的目的,我们可以直接将MQTT配置到系统的Qt环境中去。C++的编译机制是通过头文件和静态链接库编译出动态链接库,再通过头文件和动态链接库运行程序。所以这里我们要将前面编译出的静态链接库和动态链接库都复制到Qt环境中去。

首先,将qtmqtt源码目录下(qtmqtt/src/mqtt)的所有.h头文件拷贝,在Qt安装目录下的include文件夹中创建一个mqtt目录,将拷贝的文件粘贴进去。

然后,将源码编译生成目录下的静态链接库相关文件拷贝到Qt安装目录的/lib下,

依次为Qt5Mqtt.lib(.a) Qt5Mqtt.prl Qt5mqttd.lib(.a) Qt5Mqttd.prl。

再将编译生成的两个动态链接库拷贝到Qt安装目录的/bin下,

依次为Qt5Mqtt.dll Qt5Mqttd.dll。

最后再拷贝模块配置文件到Qt安装目录中。

这样MQTT就已经配置到我们本地的Qt环境中了。后续所有使用此Qt环境的项目都可以直接使用MQTT了。

使用MQTT时,首先要在.pro中添加模块:

QT += mqtt

在使用前引入包:

#include <QtMqtt/qmqttclient.h>

编写代码可以参考Qt官方的MQTT说明文档:

https://doc.qt.io/qt-6/qtmqtt-index.html


搭建EMQ X服务器

为了调试程序,我们需要一台MQTT服务器。EMQ公司官方提供了测试的MQTT服务器,但由于连接数众多,不太稳定,我们需要自己搭建一台MQTT服务器。

EMQ X提供了开源版的EMQ X服务器安装包,支持Windows,Ubuntu等多种使用环境。

安装后,Windows用户使用管理员权限命令行进入安装路径下,进入/emqx/bin/,依次执行命令

#先运行该命令
emqx install
#成功后界面上会ChangeServiceConfig 成功
#再运行
emqx console
#运行成功后会显示emqx is started!
#然后会跳出一个界面,打开emqx运行所需要的各个端口
#最后运行
emqx start
#没有报错就执行成功了

Linux用户在安装路径下执行下述命令即可

emqx start

这样本地就开启了MQTT服务,这里有两个重要的端口号要记住:1883(暴露给外部的MQTT服务端口),18083(服务器控制面板端口)。在本地浏览器输入http://127.0.0.1:18083/,打开服务器控制面板。输入初始用户名admin和用户密码public,即可进入控制面板,并进行MQTT服务器相关配置。

【领QT开发教程学习资料,点击下方链接免费领取↓↓,先码住不迷路~】

点击→领取「链接」

调试软件MQTT X

为了调试程序,我们通常需要一个调试软件来模拟消息的收发,这里推荐使用MQTT X软件进行调试。

MQTT X下载连接:https://mqttx.app/zh

MQTT X使用文档:https://mqttx.app/zh/docs

安装完成后,点击+图标可以添加连接。

这里的Name和Client ID随意,Host填写我们本地配置的MQTT服务器地址127.0.0.1,端口号填1883。点击Connect即可连接到本地。

连接后,点击New Subscription创建topic,然后就可以在该topic下收发消息。


Qt-MQTT编程

这里给出一个Qt-MQTT的程序样例,包含了基础的连接,收,发,断开等功能,读者可以在此基础上二次开发。

.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