简介《QT学习之路2-2020整理版》是一套完整的QT开发学习资料根据豆子大佬博客内容整理而成在原有七十九章基础上又补齐了后续更新的八十至九十四章合计九十四章面向Qt5与Qt Creator的初学者及希望深入掌握QT框架的开发者。教程从序言与Qt简介等基础概念开始逐步展开信号槽与自定义信号槽、Qt模块组成、主窗口、添加动作、资源文件、对象模型、布局管理器、菜单栏工具栏状态栏以及各类对话框和数据传递、标准对话框、事件系统与事件过滤器等核心内容并延伸到Qt5信号槽新语法、事件总结与自定义事件等进阶主题覆盖从图形控件到事件处理的完整学习路径章节安排由浅入深适合循序渐进学习。资源是一个PDF文件大小约5.79MB已有869人学习下载内部目录清晰便于按需查阅既能作为新手系统学习的教程也能作为日常开发时的参考手册有助于建立整体认知并掌握实际开发技能同时提供理论讲解与实际操作示例相结合的学习体验。1. 为什么这份94章的Qt资料容易读一半就放弃拿到一份更新到94章的Qt学习资料第一反应往往是“这次终于能系统学完了”。但实际场景很可能是前二十章跟着摆按钮、调布局很顺畅到事件循环、多线程就开始掉队最后连一半都没看完。这份2020整理版并不是一本从头讲到尾的教科书而是一套沿着Qt 5时代主干脉络整理的实战文章合集覆盖窗口控件、信号槽、事件、绘制、模型视图以及网络和多线程等模块。它适合刚入门、想把“会拖控件”升级为“能写界面业务”的新手也适合写过一些界面但没系统整理过核心机制的老手。真正的问题是读法而不是资料本身。2. 看目录先画Qt模块地图94章的体系编排与学习主线2.1 先认清这份资料的定位与时间基线这套资料标注“2020整理版”对应的技术生态处于Qt 5.12到5.15这一段普及期。当时桌面开发的主流仍然是Qt WidgetsQML与Qt Quick处于“能用于移动端和嵌入端、但桌面业务还在过渡”的状态。这个时间基线很重要因为它决定了你在阅读时遇到的所有API形态信号槽的新语法已经稳定QRegExp开始被QRegularExpression替代高DPI支持已经默认开启。如果直接拿这套思路去编译Qt 6环境会遇到一批“看似懂、实际编译不过”的代码。比如QRegExp在Qt 6里被移除QTextCodec的默认编码策略也变了还有qreal、QRectF一类的类型推导差异。我建议先判断自己的开发环境如果目标是快速学习和做小工具锁在Qt 5.15系列最省事如果必须用Qt 6就要把这份资料当作“原理参考”每读一章先在Qt 6里编译试一遍再接受报错修正。时间线Qt主线版本对这份资料的适用性2019—2020Qt 5.12 ~ 5.15与资料最匹配编译成功率高建议优先使用2021之后Qt 6.x接口有调整搜索问题时建议带“Qt 6”前缀否则容易被旧回答带偏2.2 用模块地图把94章拆成五个知识块94章听起来吓人但如果按知识块去拆你会发现它其实是在这五个方向上反复展开环境与第一个窗口、控件与布局、核心机制、绘制与视图、能力扩展。核心机制是这套资料里最值得反复读的部分信号槽、事件、对象树、元对象系统几乎决定了你后面写业务代码的上限。知识块在94章里的大致位置读完能做什么环境与第一个窗口开头章节创建项目、编译运行理解main.cpp与.pro的关系控件与布局中前段搭出常见表单、菜单、主窗口界面核心机制中段处理信号槽、事件分发、定时器、内存释放绘制与视图中后段自定义绘制控件、处理鼠标拖拽、做简单图表能力扩展靠近末尾多线程任务、HTTP请求、数据库读写、基础动画按这个地图去读就不会被“第几章到第几章必须顺序读”的思维绑住。比如你已经会用QPushButton了就不用再逐章看按钮属性讲解直接在需要时翻阅即可。2.3 从目录反推优先级哪些章节必须精读、哪些可以速读拿到PDF第一件事我一般不是从头读而是先花半小时做标记。精读章节集中在信号槽、事件系统、对象树与父子关系、QPainter绘制流程、布局管理器这几块。速读章节大多是某个控件的属性罗列、QSS样式表演示、图形效果示例。这种章节不求记住只求“知道有这个东西”遇到需求时能想到回目录里找到对应节。优先级可以这样排第一遍只读能直接提升调试能力的部分比如事件循环、qDebug输出、元对象系统第二遍再做界面布局和控件美化第三遍才碰多线程与网络。这么做的原因是事件循环和对象生命周期是Qt里绕不过去的底层逻辑不理解它们后面多线程崩溃和界面假死都只能靠试错来蒙。3. 跑通第一遍用最小工程把“信号槽”和“对象树”落在你机器上3.1 准备2020年Qt 5时代的干净环境我推荐安装Qt 5.15.2这是最后一个对Windows 7友好的版本也是这份资料对应时期最常用的版本。安装时采用在线安装器组件只勾选“Qt 5.15.2 → MinGW 8.1.0 64-bit”与“Qt Creator”即可不需要全量安装。编译器选择MinGW还是MSVC取决于你习惯用命令行还是Visual Studio对于跟随这份资料学习MinGW更省心因为它不依赖VS环境。安装完成后把编译器路径加入PATH环境变量这样后续在命令行里直接执行qmake与make时不会出现找不到工具链的报错。以MinGW为例需要加入的是Qt安装目录下MinGW中的bin路径具体路径取决于安装位置这里不写死。3.2 建立最小工程目录结构、main.cpp与.pro文件先新建一个文件夹里面放两个文件一个叫learn_qt.pro一个叫main.cpp。.pro是qmake的工程描述文件它告诉构建系统这个项目链接了哪些Qt模块。最小配置只需要一行QT widgets它同时引入QtCore、QtGui与QtWidgets这三个模块覆盖了大部分桌面程序的需求。# learn_qt.pro QT widgets TARGET learn_qt TEMPLATE app SOURCES main.cpp这里的TARGET指定生成的可执行文件名TEMPLATE app表示这是一个应用程序而不是库。SOURCES列出要编译的源文件。如果后续章节里用到网络模块就在QT 这一行追加network用数据库就追加sql。qmake依赖这个文件来生成Makefile所有模块引入都在这一行里控制读代码时优先看这里。main.cpp里写一个带按钮的窗口点击按钮关闭应用。这会同时用到信号槽、对象父子关系与QApplication事件循环三个核心概念是验证环境是否正常的最短路径。#include QApplication #include QPushButton #include QWidget int main(int argc, char *argv[]) { QApplication app(argc, argv); // 创建应用对象管理事件循环 QWidget window; window.setWindowTitle(Qt Learning); QPushButton button(Close, window); // 父对象是 window button.resize(100, 30); button.move(50, 50); window.resize(200, 120); // 新语法连接点击按钮关闭窗口 QObject::connect(button, QPushButton::clicked, window, QWidget::close); window.show(); return app.exec(); // 进入事件循环 }这段代码里有三个关键点。第一QApplication必须在创建任何界面对象之前创建因为它是整个Qt程序的事件循环入口没有它控件无法接收事件。第二QPushButton的构造参数传入了window这个写法建立了父子关系window销毁时button会被自动清理这就是对象树在内存管理上的体现。第三connect使用函数地址而非字符串宏是Qt 5推荐的新语法编译期就能发现连接错误比旧语法更可靠。3.3 命令行编译与运行qmake、make、可执行文件不打开Qt Creator、纯命令行编译能更清楚看到构建的每个阶段。进入工程目录后依次执行以下命令mkdir build cd build qmake ../learn_qt.pro mingw32-makeqmake的作用是根据.pro文件生成Makefilemake则按规则调用g完成编译和链接。整个过程完成后build目录下会出现learn_qt.exe。如果是MSVC环境make命令对应的是nmake原理一样只是工具链名称不同。运行时如果提示找不到Qt5Core.dll说明Qt的bin目录没有进入PATH。临时办法是在命令行里追加Qt bin路径后再执行exe正规做法是把构建出的exe放到单独目录然后用Qt自带的windeployqt工具生成依赖文件这样拷贝给别人时不会缺动态库。我习惯在项目初期就用windeployqt省得每次换目录都要补一遍环境变量。3.4 手动验证信号槽自定义槽函数、Lambda与重载陷阱看懂基础示例后再回头验证信号槽的更多形态。下面这段代码在一个窗口里放两个按钮一个连接自定义成员函数一个连接Lambda表达式。#include QApplication #include QPushButton #include QVBoxLayout #include QDebug class DemoWindow : public QWidget { Q_OBJECT public: DemoWindow() { auto *layout new QVBoxLayout(this); auto *btn1 new QPushButton(Member Slot, this); auto *btn2 new QPushButton(Lambda, this); connect(btn1, QPushButton::clicked, this, DemoWindow::onButtonClicked); connect(btn2, QPushButton::clicked, this, [this]() { qDebug() lambda says clicked; close(); }); layout-addWidget(btn1); layout-addWidget(btn2); } private slots: void onButtonClicked() { qDebug() member slot says clicked; } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); DemoWindow w; w.show(); return app.exec(); }这段代码里需要注意Q_OBJECT宏。只要类里定义了信号或槽就必须在类声明开始处加上它否则元对象编译器不会处理这个类的信号槽信息。小工程里漏掉Q_OBJECT链接时会出现vtable错误报错信息看起来和信号槽无关却是这类资料里最容易踩的坑之一。编译这个文件时Makefile会自动调用moc工具处理Q_OBJECT生成的元对象代码这就是qmake环境下信号槽能够“自省”的原因。lambda连接里如果捕获了this请确保lambda执行期间this指向的对象仍然存活否则一旦按钮触发时窗口已销毁就是典型的悬空指针崩溃这类问题在后面章节会遇到很多。4. 跟着这份PDF实践时的常见问题排查六个最现实的翻车现场4.1 版本错位Qt 6 编译旧章节代码的API差异现象抄写资料里的正则或编码相关代码编译直接报错。原因2020整理版面向Qt 5而Qt 6把QRegExp从Widgets模块中移除部分QVariant和QTextCodec接口也有变化。编译器提示的“找不到成员”往往不是你的拼写错误而是版本API已经迁移。解决先在.pro里固定Qt版本判断比如requested Qt 5路径下才编译某些章节代码如果必须用Qt 6就搜索替代接口。搜索时记得带上“Qt 6”字样不然出来的一堆旧回答只会让你更晕。4.2 环境变量运行时提示找不到Qt5Core.dll现象编译成功双击exe或命令行运行时弹窗提示找不到Qt5Core.dll。原因可执行文件运行时需要加载Qt动态库而这些库位于Qt安装目录的bin文件夹中这个目录没有加入PATH。解决命令行里先执行set PATH你的Qt安装路径\bin;%PATH%再运行程序只能临时生效。更干净的做法是拷贝exe到一个空目录并执行windeployqt exe路径让Qt自动补齐Debug与Release所需的dll之后再运行就不会再出现缺失提示。4.3 中文变乱码源文件编码与MSVC默认行为现象界面按钮上显示的中文变成一串乱码qDebug输出也异常。原因MSVC默认按本地代码页解析源文件而很多编辑环境保存的是UTF-8不带BOM的格式。当源文件没有BOM标记时MSVC会当成GBK处理中文就错位了。解决在Qt Creator里把“文件编码”统一设为UTF-8并在“工具→选项→环境→接口”里勾选“UTF-8 BOM”相关选项如果工程里已有文件乱码就用编辑器转存为UTF-8 with BOM。同时注意界面显示的字符串尽量用tr()包裹或QStringLiteral不要直接把const char*赋值给QString这样在不同编译器和语言环境下都更稳定。4.4 信号槽连接不生效旧语法字符串与类名带命名空间现象connect调用没有编译错误但点击按钮后槽函数就是不执行。原因使用旧语法SIGNAL/SLOT字符串时如果信号是重载函数或类名位于命名空间内字符串匹配会失败。这个匹配是运行期通过字符串比对完成的不报错却悄悄没连上。解决改用新语法connect(obj, 类名::signal, this, 类名::slot)。遇到重载信号时用函数指针转换指定唯一版本例如QComboBox::currentIndexChanged的int重载。更实用的一步是检查connect的返回值它在连接失败时会返回false调试时不要忽略这个返回值。4.5 界面假死exec()、事件循环与阻塞操作现象点击按钮后整个窗口无响应标题栏显示“未响应”过一会儿才恢复。原因槽函数里执行了睡眠、耗时循环或阻塞网络请求把事件循环堵住了。虽然程序没有退出但界面无法处理重绘和鼠标事件看起来就像崩溃。解决耗时不长的任务用QTimer::singleShot分段处理耗时长的放到QThread子线程或改用QNetworkAccessManager的异步接口永远不要在GUI线程里同步等待。遇到反复“卡死又恢复”的场景可以先检查是不是槽函数里出现了while(1)循环再检查是不是调用了sleep函数。4.6 崩溃无日志对象在槽函数里被提前delete现象点击关闭按钮后程序退出报段错误或者删除某控件后整个窗口闪退。原因手动delete了一个仍由父对象管理的控件事件循环在后续处理中又访问了这个已释放对象造成use-after-free。解决遵循一个原则凡是new出来并把父对象指针传入构造的控件一律交给对象树管理不要手动delete。如果确实需要提前释放使用deleteLater()而不是delete它会等到事件循环安全阶段再销毁。调试这类问题用Qt Creator的调试器看崩溃时的调用栈基本都能定位到是哪个槽函数里做了多余的delete。5. 从控件到工程能力把事件、绘制、多线程章节串成主线5.1 事件处理event、eventFilter、installEventFilter读这份资料到中段时你会频繁看到event、eventFilter这类词。它们和信号槽的差别在于信号槽是对象与对象之间的通知事件是系统或框架向对象发出的输入。事件分发顺序大致是事件产生后进入QCoreApplication::notify再经过事件过滤器链最后到达目标对象的event函数。如果重写event()返回true事件就被吞掉不会继续向下分发。常见场景是安装事件过滤器来批量拦截一组控件的输入。比如在主窗口里通过installEventFilter监听某个QLineEdit的按键事件不直接子类化控件就能统一处理键盘回车逻辑。使用过滤器时记得在eventFilter里对QEvent::KeyPress做判断并返回true否则即使你的处理逻辑执行了事件还会继续传播。5.2 绘制体系QPainter、update()与图元视图自定义绘制是这份资料的深水区。核心认知是QPainter绘制窗口的时机不由你决定而是由系统在窗口需要重绘时触发。你调用update()只是给窗口打上“需要重绘”的标记Qt会在合适时机调用paintEvent再在paintEvent里创建QPainter完成绘制。不要在事件处理函数里临时创建QPainter画东西那种画法会在窗口刷新时消失。void MyWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QRectF rect(10, 10, width() - 20, height() - 20); painter.setPen(QPen(Qt::blue, 2)); painter.drawEllipse(rect); }上述代码展示一个自适应绘制圆形的控件。它没有固定圆形坐标而是根据控件当前尺寸动态计算窗口拉伸时圆形跟着变化。调试这类代码时最简单的验证方式是改变窗口尺寸观察paintEvent是否被反复调用。如果发现绘制不更新八成是没有调用update()或者状态变更发生在paintEvent内部导致死循环常见做法是只在事件处理函数里修改数据、在paintEvent里只读数据。5.3 模型视图QListWidget与QListView、Model/View架构很多初学者从QListWidget起步往里面addItem简单直接。但这份资料讲模型视图时会引入QAbstractListModel这类抽象类容易让人困惑。区分点在于QListWidget是“视图和条目数据耦合在一起”的简单控件QListView配合QAbstractListModel则是“数据与显示分离”的架构。当数据量变大、数据结构变复杂或同一份数据要同时显示在表格和列表中时基于Model的写法优势明显。你只需要继承QAbstractListModel实现rowCount和data两个函数再把列表视图setModel即可。改动数据源时调用beginInsertRows和endInsertRows通知视图刷新界面会自动更新不需要手工维护控件条目。对新项目而言数据与界面耦合度越高后期改需求就越费劲。class StringListModel : public QAbstractListModel { Q_OBJECT public: explicit StringListModel(QObject *parent nullptr) : QAbstractListModel(parent) {} int rowCount(const QModelIndex parent QModelIndex()) const override { return m_data.size(); } QVariant data(const QModelIndex index, int role) const override { if (!index.isValid() || index.row() m_data.size()) return {}; if (role Qt::DisplayRole) return m_data.at(index.row()); return {}; } private: QStringList m_data {one, two, three}; };这是一个最小可用的只读模型示例。rowCount告诉视图一共有多少行data返回对应行与角色下的数据。角色相当于“用途标签”DisplayRole表示界面显示文本还有IconRole、ToolTipRole等。初学时不必全部理解先跑通“只读列表显示数据”这一步再逐步研究编辑与增删行。5.4 多线程与网络线程关系和Qt Creator里的并发调试线程章节是这套资料里最容易产生误解的部分。QThread本身不是一个“任务类”它是一个线程的控制器常见惯例是在继承QObject的任务对象里写耗时逻辑再通过moveToThread把对象搬到子线程运行。如果直接继承QThread并在run里写业务逻辑虽然能跑但信号槽的连接方式需要格外小心否则槽函数仍然会在主线程执行。网络请求在这里同样适用异步思维。用QNetworkAccessManager发起get请求后不能阻塞等待响应而是在finished或readyRead信号触发后再读取数据。这样即便请求再慢界面也不会假死。调试并发问题时除了qDebug输出还可以在Qt Creator的调试模式里打开线程面板查看当前断点处在哪个线程然后结合堆栈窗口判断是主线程还是子线程出了问题。6. 用“代码笔记本”消化94章每章留一个最小可运行demo读这份资料最好的归档方式不是记笔记而是建一个“代码笔记本”目录。每学完一章只留下一个能单独编译运行的最小工程按ch01、ch02顺序命名。这样三个月后回看你不需要重新翻PDF只要打开对应目录构建一下就能回忆起这一章的核心。以下是遍历构建所有实例的小脚本。#!/bin/bash for dir in ch*/; do echo building $dir cd $dir mkdir -p build cd build qmake ../*.pro 2/dev/null mingw32-make -j4 2/dev/null cd ../.. done脚本的每一轮循环进入一个章节目录创建build子目录执行qmake生成Makefile再用make并行编译最终二进制文件留在build里。这个做法的价值在于强迫你把每一章代码真实编译一遍编译报错逼你对照PDF重读这比做阅读摘抄的留存率高很多。我的习惯是白天读两章晚上各抽半小时敲完demo敲不出来的章节用红笔在PDF目录上划一道等完成全本后专门回头补这些缺口这六个标记点往往就是你没真正理解的那几块内容。第一遍不追求速度每天两个章节能稳定跑完就足够。到第二遍复习时只需跑一遍我前面给的构建脚本看哪个目录编译失败哪里就是你遗忘的地方。这个习惯让我绕开了很多无谓的重复学习希望帮到你。本文还有配套的精品资源点击获取