2. Qt基础回顾
在工业设备上位机开发中,Qt 之所以能长期占据主流位置,不仅因为它跨平台、界面美观,更因为它提供了一套稳定而优雅的底层机制。理解这些机制,是写出可维护、可扩展设备软件的前提。本章将围绕 元对象系统、信号与槽、事件循环 三大核心,结合常用类库,做一次系统性的回顾。
一、元对象系统:Qt 的“反射”基石
标准 C++ 本身没有运行时类型信息和反射能力,而 Qt 通过 元对象系统(Meta-Object System) 弥补了这一空白。它由三部分构成:QObject 基类、Q_OBJECT 宏,以及 moc(元对象编译器)。任何需要使用信号槽、属性系统、动态类型转换的类,都必须继承 QObject 并在类声明首行写上 Q_OBJECT。
moc 会在编译前扫描头文件,为带 Q_OBJECT 的类生成 moc_xxx.cpp,其中包含 metaObject()、qt_metacall() 等函数,从而让对象在运行时能够查询自己的类名、属性、信号与槽。这也是 qobject_cast、Q_PROPERTY、QMetaObject::invokeMethod 能工作的原因。
Q_OBJECT 必须写在类体第一行;若忘记添加,编译可能通过,但运行时会报 “undefined reference to vtable” 或信号槽静默失效。
二、信号与槽:对象间解耦的通信方式
信号与槽是 Qt 最具代表性的机制。信号在特定事件发生时被 emit,槽是接收响应的函数。二者通过 QObject::connect 建立连接,连接关系在运行时维护,因此发送者无需知道接收者的具体类型,实现了松耦合。
// 传统字符串连接(Qt4 风格,运行时检查)
connect(btnStart, SIGNAL(clicked()), this, SLOT(onStart()));
// 函数指针连接(Qt5+ 推荐,编译期检查)
connect(btnStart, &QPushButton::clicked, this, &MainWindow::onStart);
// Lambda 连接,适合临时逻辑
connect(btnStop, &QPushButton::clicked, this, [=](){
plc->writeCoil(0, false);
});
连接类型决定了槽的调用时机,工业场景中尤其要注意跨线程通信:
| 连接类型 | 行为 | 典型场景 |
|---|---|---|
| AutoConnection | 同线程直连,跨线程队列 | 默认选择,最常用 |
| DirectConnection | 立即在发送者线程执行 | 同线程、需同步返回 |
| QueuedConnection | 投递到接收者线程事件队列 | 采集线程更新 UI |
| BlockingQueued | 队列连接并阻塞等待 | 需等待结果再继续 |
三、事件循环:程序运转的心脏
Qt 程序由 QApplication::exec() 启动主事件循环,它不断从事件队列取出事件并分发。鼠标、键盘、定时器、网络、绘制请求,本质上都是事件。信号槽的队列连接、QTimer 超时、deleteLater() 的延迟删除,都依赖事件循环驱动。
工业上位机常见误区是:在槽函数中执行耗时操作(如等待 PLC 响应、读写大文件),会阻塞事件循环,导致界面“假死”。正确做法是把耗时任务放入 QThread 或 QtConcurrent,通过信号把结果回传主线程。
QTimer 不再触发,通常说明主线程被长时间占用。
四、常用类库速览
- 容器类:
QList、QVector、QMap、QHash,隐式共享,适合设备点位表、报警记录。 - 字符串:
QString支持 Unicode,配合QTextStream处理中文日志。 - IO 与网络:
QFile、QSerialPort、QTcpSocket、QModbusTcpClient。 - 模型视图:
QAbstractTableModel+QTableView,适合实时数据表。 - 定时器:
QTimer用于轮询采集,精度约毫秒级。
五、机制协同关系图
下图展示元对象系统、信号槽、事件循环与常用类库之间的协作关系:moc 为类生成元信息,信号经 connect 建立连接,队列连接把调用投递到事件循环,最终由事件循环分发给槽函数处理业务。
六、案例与复盘:采集线程更新界面
假设设备以 100ms 周期采集温度,若直接在采集线程操作 QLabel,会触发 Qt 的跨线程警告甚至崩溃。正确做法是:采集线程 emit 信号,主线程槽函数更新界面,连接类型使用 Qt::QueuedConnection。
class Collector : public QObject {
Q_OBJECT
signals:
void temperatureReady(double value);
public slots:
void poll() {
double v = readSensor();
emit temperatureReady(v); // 跨线程安全
}
};
// 主线程中
connect(collector, &Collector::temperatureReady,
this, &MainWindow::updateTemp,
Qt::QueuedConnection);
1. 在非 GUI 线程创建
QWidget 或调用其方法;2. 忘记
Q_OBJECT 导致信号槽失效;3. 在槽中执行阻塞操作,拖垮事件循环;
4. 使用字符串版 connect 时参数拼写错误,运行时才报错。