简介:本资源是面向C++初学者与Qt进阶开发者的系统性学习材料,聚焦Qt5.9跨平台GUI应用开发全流程,解决从环境搭建、核心机制理解到复杂模块(网络、数据库、多线程、国际化)实战落地的关键问题。压缩包共2000个文件,总计310.67MB,涵盖216个cpp与255个h头文件构成的完整示例源码体系,94个.ui界面文件、74个.pro工程配置、46个.qrc资源定义,以及大量bmp/jpg/png图标资源和可直接运行的exe/dll调试产物,结构清晰、即下即用。已有22193人学习下载,印证其在Qt5.9实践教学中的广泛认可。读者可获得覆盖九大核心章节的配套代码——包括信号槽事件驱动范例、Model/View表格数据绑定、QTcpServer客户端通信实现、QSqlTableModel数据库操作、QThread多线程同步控制及.ts/.qm本地化翻译流程,所有示例均经Qt5.9环境实测验证,是构建扎实Qt工程能力的可靠起点。
1. 这不是一本“翻完就扔”的Qt入门PDF:它配齐了能直接编译、调试、改出新功能的完整C++工程源码
你有没有试过下载一个标着“Qt5.9开发指南”的PDF,点开第3章“信号与槽”,代码片段里只有一行connect(btn, &QPushButton::clicked, this, &MainWindow::onClicked);——然后?没了。没有.pro文件,没有main.cpp入口,没有CMakeLists.txt,更别提能F5跑起来看效果的.ui界面和对应头文件。这种“理论正确、工程失联”的资料,我见过太多,最后都静静躺在收藏夹吃灰。而这份《Qt5.9 C++开发指南.pdf》完全不同:它不是孤立文档,而是一套紧耦合的实战包——PDF讲原理,配套源码是真实可运行的Qt Creator工程(含.pro、.h、.cpp、.ui、资源文件),所有示例均基于Qt5.9.9 LTS版本构建,覆盖QWidget、QML混合、多线程、网络通信、数据库操作等6大高频场景。它适合两类人:刚学完C++语法、想用Qt做第一个桌面工具的新手;以及正在维护遗留Qt5.9项目的工程师——你需要的不是概念复述,而是打开就能编译、断点就能跟踪、改两行就能验证逻辑的“最小可运行证据”。这不是教学幻灯片,这是你本地IDE里能展开、能打断点、能加日志的真实项目。
2. 工程结构与技术栈:从.pro文件开始看清Qt5.9项目的骨架
Qt项目的灵魂不在代码,而在构建配置。这份源码包的每个示例目录下,都包含一个结构清晰、注释到位的.pro文件。它决定了编译器怎么找头文件、链接哪些库、是否启用RTTI、如何处理资源文件——这些细节,恰恰是新手最易卡壳的地方。我们以ch04_signal_slot(第4章信号与槽)为例,拆解其构建逻辑。
2.1 .pro文件核心配置解析:为什么必须写QT += widgets?
# ch04_signal_slot/ch04_signal_slot.pro QT += core widgets gui TARGET = ch04_signal_slot TEMPLATE = app SOURCES += main.cpp \ mainwindow.cpp HEADERS += mainwindow.h FORMS += mainwindow.ui RESOURCES += resources.qrc提示:
QT += widgets不是可选项。Qt5.9中,QWidget类已从gui模块剥离,单独归入widgets模块。若遗漏此行,编译时会报错'QWidget' was not declared in this scope——这是新手最常翻车的第一步。core和gui是基础依赖,但widgets才是GUI控件的真正归属。
这个.pro文件定义了标准Qt Widgets应用的最小结构:TEMPLATE = app表示生成可执行程序;SOURCES/HEADERS/FORMS三者严格对应C++源码、头文件和Qt Designer生成的UI描述;RESOURCES则指向resources.qrc,该文件将图标、图片等二进制资源编译进可执行体,避免运行时路径错误。
2.2 目录结构即开发流程:6个示例目录对应6个能力阶梯
整个源码包按PDF章节组织为6个独立子目录,每个目录都是一个自包含的Qt Creator工程:
| 目录名 | 核心能力 | 关键文件特征 | 典型调试切入点 |
|---|---|---|---|
ch02_helloqt | 基础窗口创建 | main.cpp仅3行,mainwindow.ui含Label控件 | 在MainWindow::MainWindow()构造函数设断点,观察setupUi(this)调用 |
ch03_layout | 布局管理器实战 | mainwindow.ui中嵌套QVBoxLayout+QHBoxLayout | 修改setSpacing(10)参数,实时观察控件间距变化 |
ch04_signal_slot | 信号槽连接方式 | 同时存在connect()宏连接与QObject::connect()函数式连接 | 对比&QPushButton::clicked(新语法)与SIGNAL(clicked())(旧语法)的编译差异 |
ch05_thread | 多线程安全通信 | WorkerThread类继承QThread,含QMutex保护共享数据 | 在WorkerThread::run()中添加qDebug() << "Thread ID:" << QThread::currentThreadId(); |
ch06_network | HTTP请求与JSON解析 | 使用QNetworkAccessManager+QJsonDocument | 拦截QNetworkReply::finished()信号,在槽函数中检查reply->error() |
ch07_database | SQLite增删改查 | QSqlDatabase::addDatabase("QSQLITE")+QSqlQuery执行语句 | 打开QSqlDatabase::database().lastError().text()查看SQL语法错误 |
注意:所有
.ui文件均使用Qt5.9默认的QMainWindow模板生成,未启用QQuickWidget等混合渲染组件——这意味着你无需额外安装Qt Quick模块即可编译运行。这是刻意为之的“降维兼容”设计,确保在老旧开发机上也能零障碍启动。
2.3 Qt5.9特有的ABI兼容性约束:为什么不能混用不同编译器版本?
Qt5.9是一个LTS(长期支持)版本,其二进制接口(ABI)在5.9.0至5.9.9之间保持稳定,但严格禁止跨编译器混用。例如:
- 若你用MinGW 5.3编译的Qt5.9.9库,则所有项目必须使用同一MinGW 5.3工具链;
- 若你用MSVC2015编译的Qt5.9.9,则必须用MSVC2015编译器构建项目。
现象:链接时报错LNK2019: unresolved external symbol "__declspec(dllimport) public: virtual __thiscall
原因:不同编译器对C++名称修饰(name mangling)规则不同,导致Qt库导出的符号与你的代码生成的符号不匹配。
解决:在Qt Creator中,必须手动指定Kit——进入Projects → Build & Run → Kits,确认Compiler与Qt version的编译器标识完全一致(如Desktop Qt 5.9.9 MinGW 5.3 32-bit)。这是血泪经验:曾有同事因Kit选错,花两天排查“Qt库找不到”的假问题。
3. 核心模块代码逐行剖析:从信号槽到多线程的落地实现
Qt的优雅在于抽象,而它的坑往往藏在抽象之下。下面以ch04_signal_slot和ch05_thread两个最具代表性的模块,带你看清Qt5.9 C++代码如何从声明走向执行。
3.1 信号槽机制:不止是connect(),更是对象生命周期的契约
mainwindow.h中声明槽函数:
// ch04_signal_slot/mainwindow.h private slots: void onButtonClicked(); // 传统私有槽 void onSliderValueChanged(int value); // 带参数槽 public slots: void updateStatusLabel(const QString &text); // 公有槽,可被其他对象调用mainwindow.cpp中实现并连接:
// ch04_signal_slot/mainwindow.cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // 方式1:宏连接(Qt5.9仍支持,但已标记为deprecated) connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(onButtonClicked())); // 方式2:函数指针连接(Qt5.9推荐,类型安全) connect(ui->horizontalSlider, &QSlider::valueChanged, this, &MainWindow::onSliderValueChanged); // 方式3:Lambda表达式(需C++11支持,Qt5.9默认开启) connect(ui->pushButton_2, &QPushButton::clicked, [=]() { ui->statusBar->showMessage("Lambda triggered!", 2000); }); }逻辑说明:
SIGNAL/SLOT宏本质是字符串匹配,在编译期无法检查参数类型是否匹配,运行时出错才报Object::connect: No such slot;- 函数指针连接(
&QSlider::valueChanged)在编译期即校验签名,若onSliderValueChanged参数类型写成double而非int,编译直接失败;- Lambda连接最灵活,但需注意捕获列表:
[=]按值捕获,[this]显式捕获当前对象指针,避免悬空引用。
3.2 多线程安全:QThread不是std::thread,moveToThread()才是正解
ch05_thread目录下,workerthread.h定义了一个典型工作线程类:
// ch05_thread/workerthread.h class WorkerThread : public QObject { Q_OBJECT public: explicit WorkerThread(QObject *parent = nullptr); ~WorkerThread(); public slots: void doWork(); // 槽函数,将在目标线程中执行 signals: void resultReady(const QString &result); void finished(); // 线程结束信号 };关键不在继承QThread,而在将WorkerThread对象移动到新线程:
// ch05_thread/mainwindow.cpp void MainWindow::startWork() { // 1. 创建Worker对象(在主线程堆上) m_worker = new WorkerThread(this); // 2. 创建QThread对象(在主线程) m_thread = new QThread(this); // 3. 将Worker对象移动到新线程(核心!) m_worker->moveToThread(m_thread); // 4. 连接信号槽:主线程发信号 → Worker在子线程执行 connect(m_thread, &QThread::started, m_worker, &WorkerThread::doWork); connect(m_worker, &WorkerThread::resultReady, this, &MainWindow::handleResult); connect(m_worker, &WorkerThread::finished, m_thread, &QThread::quit); connect(m_thread, &QThread::finished, m_thread, &QThread::deleteLater); // 5. 启动线程 m_thread->start(); }参数说明:
moveToThread()是Qt事件循环模型的基础:它改变QObject的所属线程,使其后续收到的信号槽调用都在目标线程执行;QThread::started信号由start()触发,用于启动Worker的业务逻辑;m_thread->quit()使线程事件循环退出,deleteLater()在事件循环结束后安全销毁对象,避免内存泄漏;- 绝对不要重写
QThread::run()——这是Qt4时代的反模式,Qt5中应使用moveToThread()+QObject组合。
3.3 数据库操作:SQLite的隐式事务与QSqlQuery的prepare陷阱
ch07_database中,databasehelper.cpp封装了用户表操作:
// ch07_database/databasehelper.cpp bool DatabaseHelper::insertUser(const QString &name, int age) { QSqlQuery query(QSqlDatabase::database()); // 使用默认连接 // 错误写法:每次执行都解析SQL(性能差) // query.exec(QString("INSERT INTO users (name, age) VALUES ('%1', %2)").arg(name).arg(age)); // 正确写法:预编译+绑定参数(防SQL注入+提升性能) query.prepare("INSERT INTO users (name, age) VALUES (?, ?)"); query.addBindValue(name); query.addBindValue(age); if (!query.exec()) { qWarning() << "Insert failed:" << query.lastError().text(); return false; } return true; }逻辑说明:
prepare()将SQL语句发送给SQLite引擎编译为字节码,后续exec()只需传参执行,避免重复解析;addBindValue()自动处理字符串转义,彻底杜绝' OR '1'='1类注入攻击;QSqlDatabase::database()返回默认连接,若需多连接,应显式调用QSqlDatabase::addDatabase("QSQLITE", "connectionName")并传入名称。
4. 编译与运行避坑指南:那些让你怀疑Qt是不是装错了的瞬间
Qt项目编译失败,80%的问题不出在代码,而出在环境配置的“毫米级偏差”。以下是我在多个Windows/macOS/Linux平台实测总结的5条高频踩坑记录,每一条都来自真实翻车现场。
4.1 现象:Qt Creator报错“Cannot find -lGL”,Linux下编译直接中断
原因:Qt5.9默认链接OpenGL库(libGL.so),但Ubuntu/Debian系发行版需单独安装libgl1-mesa-dev,CentOS/RHEL系需mesa-libGL-devel。
解决:
# Ubuntu/Debian sudo apt-get install libgl1-mesa-dev # CentOS/RHEL sudo yum install mesa-libGL-devel # 或 dnf install mesa-libGL-devel(Fedora)注意:安装后需重启Qt Creator,否则Kit检测仍可能失败。若已安装却仍报错,检查
/usr/lib/x86_64-linux-gnu/libGL.so是否存在,不存在则创建软链接:sudo ln -s /usr/lib/x86_64-linux-gnu/mesa/libGL.so.1 /usr/lib/x86_64-linux-gnu/libGL.so
4.2 现象:Windows下运行时报错“无法定位程序输入点ucrtbase.dll”
原因:Qt5.9.9 MinGW版本要求系统安装Visual C++ 2015-2019运行库(vc_redist.x64.exe),但部分精简版Win10未预装。
解决:
- 下载微软官方
vc_redist.x64.exe(2015-2019通用版); - 以管理员身份运行安装;
- 关键一步:在Qt Creator的
Projects → Build & Run → Build Environment中,将PATH变量开头追加C:\Windows\System32(确保系统DLL优先加载)。
4.3 现象:macOS下编译通过,但运行时窗口空白,控制台输出"QCocoaEventDispatcher: Failed to initialize"
原因:Qt5.9对macOS Catalina(10.15)及更高版本支持不完善,且Qt Creator默认使用clang编译器,但Qt5.9.9 SDK需Apple Clang 10.0.1或更低版本。
解决:
- 在Xcode中安装
Command Line Tools for Xcode 10.3(对应Clang 10.0.1); - Qt Creator中切换Kit:
Projects → Build & Run → Kits → Compiler选择Apple Clang 10.0.1; - 清理构建目录:
Build → Clean All,再Build → Rebuild All。
4.4 现象:.ui文件修改后,ui_mainwindow.h未自动生成,编译报'ui_mainwindow.h' file not found
原因:Qt Creator的uic(User Interface Compiler)未被正确触发,常见于项目首次打开时未识别.pro文件中的FORMS变量。
解决:
- 右键点击项目根节点 →
Run qmake(强制重新生成Makefile); - 若无效,手动执行:在项目目录下打开终端,运行
/path/to/Qt5.9.9/5.9.9/gcc_64/bin/uic mainwindow.ui -o ui_mainwindow.h; - 玄学补救:关闭Qt Creator,删除项目目录下的
build-*文件夹和*.pro.user文件,重新打开项目。
4.5 现象:QNetworkAccessManager发起HTTP请求,finished()信号永不触发,reply->isFinished()始终为false
原因:Qt5.9中,QNetworkAccessManager必须在有事件循环的线程中创建。若在无GUI的纯命令行程序中使用,或在QThread::run()中直接创建,事件循环缺失导致网络事件无法分发。
解决:
- 确保
QNetworkAccessManager对象在主线程(GUI线程)创建; - 若需在子线程发请求,采用
moveToThread()方式:QNetworkAccessManager *mgr = new QNetworkAccessManager; mgr->moveToThread(workerThread); // workerThread已启动事件循环 connect(workerThread, &QThread::started, [=]() { QNetworkRequest req(QUrl("https://httpbin.org/get")); mgr->get(req); });
5. 二次开发实战:如何基于现有示例快速构建自己的功能模块
拿到这套源码,你的终极目标不是“跑通示例”,而是“把示例变成你的工具”。下面以一个真实需求为例:为ch06_network示例增加HTTPS证书校验绕过功能,用于测试自签名证书的内网API。这涉及Qt网络模块的底层配置,也是二次开发中最常遇到的“定制化”场景。
5.1 需求分析:为什么默认HTTPS会失败?SSL握手的本质是什么?
Qt5.9的QNetworkAccessManager默认启用严格的SSL证书验证。当访问使用自签名证书的内网服务(如https://192.168.1.100:8443/api)时,reply->sslErrors()会返回非空列表,包含QSslError::SelfSignedCertificateInChain等错误,导致finished()信号后reply->error()为QNetworkReply::SslHandshakeFailedError。绕过验证不是“关掉安全”,而是显式接受特定错误,这正是Qt提供的ignoreSslErrors()接口的设计意图。
5.2 改造步骤:四步完成HTTPS定制,不破坏原有结构
步骤1:在networkmanager.h中扩展接口
// ch06_network/networkmanager.h class NetworkManager : public QObject { Q_OBJECT public: explicit NetworkManager(QObject *parent = nullptr); // 新增:允许设置是否忽略SSL错误 void setIgnoreSslErrors(bool ignore); private slots: void onReplyFinished(); private: QNetworkAccessManager *m_manager; bool m_ignoreSslErrors = false; // 默认不忽略 };步骤2:在networkmanager.cpp中实现SSL错误处理
// ch06_network/networkmanager.cpp NetworkManager::NetworkManager(QObject *parent) : QObject(parent) { m_manager = new QNetworkAccessManager(this); // 关键:连接sslErrors信号,决定是否忽略 connect(m_manager, &QNetworkAccessManager::sslErrors, this, &NetworkManager::onSslErrors); } void NetworkManager::setIgnoreSslErrors(bool ignore) { m_ignoreSslErrors = ignore; } void NetworkManager::onSslErrors(QNetworkReply *reply, const QList<QSslError> &errors) { if (m_ignoreSslErrors) { // 显式忽略所有SSL错误(仅用于测试!) reply->ignoreSslErrors(errors); qDebug() << "SSL errors ignored:" << errors; } else { // 默认行为:不忽略,让请求失败 qDebug() << "SSL errors occurred, not ignored:" << errors; } }步骤3:在mainwindow.cpp中暴露UI开关
// ch06_network/mainwindow.cpp void MainWindow::on_pushButton_clicked() { // ... 原有请求逻辑 // 新增:根据复选框状态设置SSL策略 bool ignoreSsl = ui->checkBoxIgnoreSsl->isChecked(); m_networkManager->setIgnoreSslErrors(ignoreSsl); // 发起GET请求 QNetworkRequest request(QUrl("https://192.168.1.100:8443/api/test")); m_networkManager->get(request); }步骤4:在mainwindow.ui中添加复选框控件
- 打开
mainwindow.ui,拖入QCheckBox; - 设置
objectName为checkBoxIgnoreSsl; - 设置
text为"忽略SSL证书错误(仅测试)"; - 保存后,
ui_mainwindow.h自动更新,ui->checkBoxIgnoreSsl即可在代码中访问。
验证方法:
- 启动内网HTTPS服务(如用
openssl s_server -cert cert.pem -key key.pem -accept 8443);- 在Qt Creator中运行程序,勾选复选框,点击请求按钮;
- 观察控制台输出
SSL errors ignored:,且reply->readAll()成功返回JSON数据;- 取消勾选,再次请求,应看到
SSL errors occurred且请求失败。
5.3 更进一步:如何将此改造封装为可复用的“网络模块”?
若你发现多个项目都需要类似HTTPS定制,可将其抽离为独立模块:
- 创建
src/network/目录,放入sslnetworkmanager.h/cpp; - 在
.pro中添加HEADERS += src/network/sslnetworkmanager.h; - 提供静态工厂方法:
static SslNetworkManager* createWithSslBypass(QObject *parent = nullptr);; - 为避免全局状态污染,绝不使用单例,而是让每个业务模块持有自己的实例。
从那以后我每次接到“对接内网HTTPS服务”的需求,都不再从零写网络层——而是打开ch06_network目录,复制networkmanager.h/cpp,3分钟内完成SSL适配,然后专注业务逻辑。这份源码的价值,从来不是它教了你多少Qt知识,而是它给了你一个可信任的起点:一个结构清晰、注释完整、经得起调试的工程基座。希望帮到你。
本文还有配套的精品资源,点击获取