☰
Qt学习之路94章知识地图:信号槽、事件、model/view与线程复习指南
2026/10/24 11:14:27 网站建设 项目流程

简介:这份《QT学习之路2-2020整理版》面向希望系统掌握Qt框架的开发者,从零基础入门到进阶实战均可参考。内容整理自豆子博客,在原有79章基础上补全了2016年3月后更新的15章,共94章,涵盖Qt简介、信号槽机制、自定义信号槽、MainWindow、布局管理器、事件处理、对话框数据传递等核心主题,并延伸至多线程、国际化、数据库集成与网络编程等高级方向,同时涉及Qt Creator使用与调试技巧。资源为1个PDF文件,压缩包约5.79MB,目录结构完整、章节编号清晰,便于按主题顺序学习或跳转查阅。目前已有870人学习,适合需要一份体系化Qt5与Qt Creator学习材料、希望从基础概念一路推进到项目实践的读者收藏研读。

1. Qt 学习之路的完整知识地图:94 章到底覆盖了什么

很多人第一次翻开《Qt 学习之路 2》的目录,第一反应是“怎么这么多章”——94 章、445 页,从 Hello world 一路排到 QML 组件。但真正梳理下来会发现,它并不是零散知识点的堆叠,而是一条从 C++ 框架认知到 GUI 编程、再到模型视图、网络、线程、QML 的完整链路。这份 2020 整理版把原博客 79 章之后的内容补齐到 94 章,等于把 Qt 5 时代最核心的入门到进阶路径一次性收全了。它适合两类人:一类是正在系统学 Qt、需要一张能按图索骥的路线图;另一类是已经写过一些 Qt 代码、但知识点在脑子里还是散的,想借一份笔记把信号槽、事件、model/view、线程这几条主线重新串起来。下面按模块拆开梳理,重点标出复习时最容易卡住的地方。

2. 信号槽与事件系统:Qt 最核心的两套通信机制

2.1 信号槽从旧语法到 Qt5 新语法

信号槽是 Qt 区别于其他 C++ 框架最明显的特征,也是面试和实际开发中绕不开的第一道坎。第 4 章到第 6 章、第 16 章集中讲这块。旧语法用SIGNAL()和SLOT()宏,本质是字符串匹配,编译期不检查参数类型,写错了要到运行时才会发现。Qt 5 引入的新语法用函数指针连接,编译期就能查出签名不匹配的问题。

// 旧语法:字符串匹配,运行时才报错 connect(sender, SIGNAL(valueChanged(int)), receiver, SLOT(updateValue(int))); // Qt5 新语法:编译期检查,支持 lambda connect(sender, &Sender::valueChanged, receiver, &Receiver::updateValue); // 连接 lambda,适合一次性逻辑 connect(button, &QPushButton::clicked, this, [this]() { // 捕获 this 后可直接访问成员 updateStatus("clicked"); });

梳理下来会发现,新语法最大的价值不是“写法更短”,而是把错误从运行时提前到了编译期。参数说明上,connect的第五个参数Qt::ConnectionType决定了调用时机:AutoConnection是默认值,同线程直接调用、跨线程排队;QueuedConnection强制排队,适合跨线程;BlockingQueuedConnection会阻塞发送方,只在同线程外使用,否则会死锁。最容易忽略的是:跨线程用AutoConnection时,如果接收者对象的线程亲和性没设置对,槽函数仍然会在发送者线程执行,这是很多“玄学崩溃”的根源。

2.2 事件循环、event() 与事件过滤器

第 18 章到第 23 章是事件系统,很多人学到这里会觉得“信号槽已经够用了,事件是不是可以跳过”——恰恰相反,事件是信号槽的底层基础。Qt 的事件循环从QApplication::exec()启动,所有窗口消息、定时器、网络回调都在这条循环里分发。QObject::event()是事件分发的总入口,mousePressEvent、keyPressEvent这些虚函数只是event()内部按类型转发的分支。

bool MyWidget::event(QEvent *e) { if (e->type() == QEvent::KeyPress) { // 先做自定义处理,再交给基类 auto *ke = static_cast<QKeyEvent *>(e); if (ke->key() == Qt::Key_Escape) { return true; // 已处理,不再传递 } } return QWidget::event(e); // 其余交给默认实现 }

事件过滤器installEventFilter是另一个高频考点:它让一个对象可以监视另一个对象的事件,适合做全局快捷键、输入校验。注意点在于,过滤器的eventFilter返回true表示事件被吃掉,返回false才会继续传递。最容易翻车的地方是:在过滤器里对事件对象做了delete,然后又返回false,后续处理就会访问野指针。梳理下来,事件这块的复习顺序应该是:事件循环 →event()分发 → 具体事件函数 → 事件过滤器 → 自定义事件,一层层往上叠,不要跳。

3. GUI 构建主线:从 MainWindow 到布局与对话框

3.1 MainWindow、动作与资源文件

第 7 章到第 12 章是主窗口应用的骨架。QMainWindow比普通QWidget多了菜单栏、工具栏、状态栏和中心部件这几个固定区域。动作QAction是菜单和工具栏共用的抽象,一个动作可以同时挂到菜单项和工具按钮上,改一处两边同步。资源文件.qrc把图片、图标、翻译文件编进二进制,避免运行时路径问题。

<!-- resources.qrc:把图标编进可执行文件 --> <RCC> <qresource prefix="/icons"> <file>new.png</file> <file>open.png</file> </qresource> </RCC>

复习时容易漏的是:.qrc里的路径是相对路径,前缀prefix决定运行时用:/icons/new.png访问。如果资源没显示,先查qmake有没有把.qrc加进RESOURCES,再查前缀拼写。另一个易错点是QAction的triggered和toggled信号:前者只在用户点击时发出,后者在setChecked改变状态时也发出,做可勾选动作时要分清。

3.2 布局管理器与对话框数据传递

第 11 章布局管理器、第 13 到 17 章对话框,这两块是实际写界面时用得最多的。布局管理器解决的是“窗口缩放时控件怎么跟着动”,核心有QHBoxLayout、QVBoxLayout、QGridLayout、QFormLayout和QStackedLayout。很多人习惯用绝对坐标setGeometry,结果换分辨率就乱,这是典型的踩坑。

布局类适用场景关键参数
QHBoxLayout水平排列按钮组stretch 控制拉伸比例
QVBoxLayout垂直表单spacing 控制间距
QGridLayout行列对齐的复杂面板rowSpan/columnSpan 跨格
QFormLayout标签+输入框成对出现labelAlignment 对齐方式
QStackedLayout多页面切换setCurrentIndex 切页

对话框数据传递是另一个高频易错点。模态对话框用exec()阻塞,返回QDialog::Accepted或Rejected;非模态用show(),需要通过信号把数据传回来。标准对话框QMessageBox、QFileDialog、QColorDialog这些在第 15、17 章有专门讲。梳理下来会发现,对话框这块的复习重点不是“怎么弹出来”,而是“数据怎么安全地传回去”——用信号槽传,不要用全局变量,也不要直接访问对话框的内部控件。

4. 模型视图与数据持久化:model/view、文件、数据库、XML/JSON

4.1 model/view 架构与自定义模型

第 41 章到第 51 章是 model/view,这是 Qt 里抽象程度最高、也最容易劝退的一段。核心思想是把数据(model)、展示(view)、编辑(delegate)拆开。QListWidget、QTreeWidget、QTableWidget是便利类,内部已经绑好了 model,适合小数据量;QListView、QTreeView、QTableView配合自定义 model 才能处理大数据和复杂逻辑。

// 自定义只读模型的核心:rowCount 和 data int MyModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; // 扁平结构,无子节点 return m_items.size(); } QVariant MyModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return {}; if (role == Qt::DisplayRole) { return m_items.at(index.row()).name; // 显示文本 } return {}; }

参数说明上,data()的role决定返回什么:DisplayRole是显示文本,EditRole是编辑值,DecorationRole是图标,ToolTipRole是提示。最容易漏的是beginInsertRows/endInsertRows这对调用——不写它们,view 不会知道数据变了,界面就不刷新。QSortFilterProxyModel是排序过滤的中间层,套在源模型和视图之间,不用改源模型就能实现搜索。复习时建议按“便利类 → 自定义只读模型 → 自定义可编辑模型 → 代理模型 → 拖放”的顺序过一遍。

4.2 文件、数据库与 XML/JSON 处理

第 35 到 40 章是文件与容器,第 55 到 64 章是数据库和 XML/JSON。文件部分重点在QFile、QTextStream、QDataStream三者的区别:QTextStream处理文本编码,QDataStream处理二进制且与平台无关。容器部分QList、QVector、QMap、QHash的选型要看访问模式,QHash查找平均 O(1) 但不保证顺序,QMap按 key 有序但 O(log n)。

数据库用QSqlDatabase建立连接,QSqlQuery执行 SQL,配合QSqlTableModel可以直接把表映射到 view 上。XML 有三种处理方式:QXmlStreamReader是流式解析,内存占用小;DOM 把整棵树读进内存,方便随机访问;SAX 是事件回调,适合超大文件。JSON 用QJsonDocument、QJsonObject、QJsonArray,解析和生成都比较直观。

// JSON 解析:从字符串到对象 QJsonParseError err; auto doc = QJsonDocument::fromJson(raw, &err); if (err.error != QJsonParseError::NoError) { // 解析失败,err.errorString() 给出原因 return; } auto obj = doc.object(); auto name = obj.value("name").toString();

梳理下来,这块的易错点集中在:数据库连接名冲突(多线程下每个线程要独立连接)、XML 编码声明与实际字节不一致、JSON 数字精度丢失(大整数用字符串传)。这些在项目里都是血泪经验,复习时值得单独标出来。

5. 网络、进程与线程:跨平台应用的进阶模块

5.1 网络访问与进程间通信

第 65 到 70 章覆盖网络和进程。网络部分QNetworkAccessManager是核心,get()、post()返回QNetworkReply,通过finished信号拿结果。注意QNetworkReply必须手动deleteLater(),否则会内存泄漏。进程部分QProcess可以启动外部程序并读写它的标准输入输出,start()之后用waitForStarted()或信号确认状态。

// 发起 GET 请求并处理响应 auto *manager = new QNetworkAccessManager(this); auto *reply = manager->get(QNetworkRequest(QUrl("https://example.com/api"))); connect(reply, &QNetworkReply::finished, this, [reply]() { if (reply->error() == QNetworkReply::NoError) { auto data = reply->readAll(); // 读取响应体 } reply->deleteLater(); // 必须释放 });

进程间通信除了QProcess,还有共享内存QSharedMemory和本地套接字QLocalSocket。选型上,父子进程用QProcess最直接,同机不同进程用QLocalSocket更灵活。易漏点是QSharedMemory在崩溃后可能残留,需要先attach再detach清理。

5.2 线程模型与 QObject 的线程亲和性

第 71 到 75 章是线程,这是 Qt 里最容易出诡异 bug 的地方。QThread本身是一个线程控制对象,它对象本身属于创建它的线程,真正跑代码的是run()里的内容。推荐用法是moveToThread:把工作对象移到子线程,通过信号槽跨线程调用,而不是继承QThread重写run()。

// 推荐:worker 对象 moveToThread auto *thread = new QThread(this); auto *worker = new Worker(); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Worker::doWork); connect(worker, &Worker::finished, thread, &QThread::quit); connect(thread, &QThread::finished, worker, &QObject::deleteLater); thread->start();

参数说明:moveToThread之后,worker 的槽函数会在子线程执行,但前提是连接类型为QueuedConnection或AutoConnection且跨线程。最容易忽略的是:QObject的子对象必须和父对象在同一线程,跨线程new一个带 parent 的对象会直接报错。线程总结那几章反复强调一句话——不要跨线程直接调用对象方法,一律走信号槽。梳理下来,线程这块的复习清单就是:线程亲和性 → moveToThread → 跨线程信号槽 → 线程安全的数据传递 → 线程退出清理。

6. 易漏点与易错点:复习时最该盯住的五个地方

易漏点一:信号槽连接重复。为什么漏:connect多次调用同一对信号槽会建立多条连接,槽函数被执行多次。怎么补:连接前用disconnect清理,或者用Qt::UniqueConnection标志,或者把连接放在只执行一次的地方。

易漏点二:事件过滤器返回值的语义。为什么漏:eventFilter返回true表示事件被拦截,返回false表示继续传递,很多人记反。怎么补:记住“true 吃掉,false 放行”,在过滤器里打印日志验证一次。

易漏点三:model 数据变更没通知视图。为什么漏:直接改内部容器,没有调用beginInsertRows/dataChanged等信号。怎么补:所有数据变更都走 model 提供的接口,不要绕过 model 直接改底层数据。

易漏点四:跨线程访问 GUI。为什么漏:子线程里直接调setText、update等 GUI 方法,程序随机崩溃。怎么补:GUI 操作一律回到主线程,用信号槽把结果传回来再更新。

易漏点五:QNetworkReply 和 QProcess 对象未释放。为什么漏:只关注功能实现,忘了deleteLater。怎么补:在finished信号里统一加deleteLater(),形成肌肉记忆。

7. 复习清单:怎么用这份 94 章笔记查漏补缺

复习到后期,最怕的是“每章都看过,但合上书就串不起来”。我的做法是把 94 章压成一张三层清单,每层对应不同的复习动作。

第一层是主线层,只列六个词:信号槽、事件、布局与对话框、model/view、网络与进程、线程。合上书,试着对每个词说出它的核心类和典型用法,说不出来的就是薄弱模块,回去翻对应章节。

第二层是接口层,把每个模块的高频类和方法列成表,对照检查自己是否会用。

模块核心类必须会的方法
信号槽QObjectconnect / disconnect
事件QEventevent / installEventFilter
布局QLayoutaddWidget / setStretch
model/viewQAbstractItemModelrowCount / data / index
网络QNetworkAccessManagerget / post / finished
线程QThreadmoveToThread / start / quit

第三层是易错层,就是上一章那五条,每次写新代码前扫一眼,能避开大部分低级问题。

具体操作上,我习惯用“三遍法”:第一遍按目录顺序通读,标记看不懂的章节;第二遍只读标记章节,配合 Qt Creator 写最小可运行示例;第三遍合上资料,对着空白文档默写每个模块的核心代码骨架。默写不出来的地方,就是真正需要补的地方。这份 2020 整理版把 80 到 94 章补齐之后,QML 和 QtQuick 部分也完整了,如果你之前只学到 79 章,建议把后面这十几章单独过一遍,QML 语法、基本元素、组件这三块是 Qt 5 之后界面开发的重要方向。

从那以后我每次开始一个新的 Qt 项目,都会强制走一遍“主线六词 → 接口表 → 易错五条”的检查,确认没有盲区再动手。希望这份梳理能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询