一群人手把手(其实就是我自己)带了很多个刚接触 Qt 的开发者做项目,发现最容易劝退新人的,往往不是信号槽语法,也不是事件循环,而是“项目能建出来,但编译不过去”这种打击感。明明代码跟例子一模一样,点运行却蹦出一堆看不懂的红色错误,排查半天连问题出在哪个环节都说不清楚。
这篇是“Qt 编程”系列的第一篇,核心就讲透一件事:Qt 项目是怎么创建、怎么编译、怎么变成可执行程序的。我把从安装配置到构建链路的每一步拆开,结合大量实操中踩过的坑来讲,适合刚入门 C++ 想学图形界面开发的人,也适合那些平时只点“运行”按钮、但一旦报错就手足无措的开发者。看完这篇,你会知道 Creator 替你做了什么、底层发生了什么、出问题该去哪一环找。
写之前先说清楚整体思路:我写的不是“照着点一遍”的操作手册,而是把每个选择背后的“为什么”也讲掉。这样你换一台机器、换一个 Qt 版本,甚至换一个构建系统,都不会慌。
1. Qt项目创建前的软件环境准备
很多人喜欢跳过环境准备直接写代码,结果第一个坑就踩在装什么、怎么装上。Qt 不是一个小库,它的安装包里混合了编译器、调试器、构建工具、GUI 库、平台插件和大量示例,配错一个组件,后面全盘皆输。
1.1 版本选择与安装组件的理性决策
打开 Qt 官方下载页面,面对“Qt 5.15”“Qt 6.5”“Qt 6.8”这些版本号,新人通常不知道怎么选。我的建议很直接:如果是纯新手,优先选 6.5 以上的 LTS 版本;如果是接手老项目,跟着项目要求走就好。5.15.2 是 5.x 时代最后一个大版本,如今很多存量项目还在维护,但新开项目再用它,多少有点“新瓶装旧酒”的意思。
6.x 相比 5.x 有不少变化,最明显的是图形架构变了,事件循环内部也有重构。不过对我们这篇讲创建与编译的内容来说,qmake 和 CMake 的基本流程几乎一致。你完全不用担心“学了 5 的创建流程,到 6 就不适用了”,不会的。
选好版本后,安装器会列出一长串组件。这里有几个关键点需要解释一下:
- 编译器组件:Qt 是 C++ 库,必须搭配一个编译器才能用。Windows 上最常见的选择是 MSVC(即 Visual Studio 里的那个编译器)和 MinGW(一个 GCC 在 Windows 上的移植版本)。
- Qt 库本体:每个编译器只对应一份预编译好的 Qt 库,比如“Qt 6.5.3 MSVC 2019 64-bit”和“Qt 6.5.3 MinGW 11.2.0 64-bit”是两份不同的 Qt 二进制包,不能混用。
- 构建工具:建议勾选“CMake”和“Ninja”,因为 Qt 6 默认构建系统就是 CMake + Ninja。即使你后面打算用 qmake,Pre-built 环境里把这些工具备齐也没坏处。
实操中发现,很多人安装时嫌麻烦,只勾了某一个 Qt 库版本,没有勾对应的工具链,结果进入 Qt Creator 后发现“Kit”列表里全是黄色感叹号,编译按钮直接变灰。所以安装时宁可多花费一点磁盘空间,也别漏组件。如果在安装器里看到“Qt Creator”和“Qt 工具链”的分区,全部展开,按需勾选即可。
1.2 构建套件Kit的三件套:编译器、Qt库、调试器
Qt Creator 里的核心概念叫“Kit”(构建套件)。换句话说,一套可用的构建环境至少要有三样东西:
- C++ 编译器:负责把源代码翻译成机器码。Windows 下可能是 MSVC,也可能是 MinGW。
- Qt 版本:负责提供 Qt 的头文件、库文件(.lib / .dll / .a)和界面工具。
- 调试器:负责打断点、看变量,MinGW 配套的是 GDB,MSVC 配套的是 CDB。
这三者必须“口径统一”,不是说随便拉一个就能拼起来。最典型的坑是:你在 Visual Studio 里用 MSVC 写好了静态库,结果新项目套了个 MinGW 的 Kit,链接的时候报一堆“无法解析的外部符号”。这就是二进制接口不兼容。MSVC 和 MinGW 生成的代码结构和符号修饰规则完全不同,Qt 库同样如此,你安装的是 MSVC 版,就必须用 MSVC 版 Kit。
判断一个 Kit 是否正常,打开 Creator 的“工具→选项→Kits”就能看到。图标正常、没有黄色警告,说明编译器、Qt 版本和调试器识别成功了。注意“Qt 版本”标签页里有一个“qmake 路径”,这个路径必须指向 Qt 安装目录下的实际路径,比如C:\Qt\6.5.3\mingw_64\bin\qmake.exe。如果路径不对,哪怕 Creator 显示有 Kit,构建时也会报找不到 Qt 模块。
实践经验里还有一个易错点:安装路径别带中文和空格。很多人喜欢建一个D:\项目代码\QtDemo这样的目录,后面编译时你会发现,编译器或者某些工具链对路径里带空格、中文非常敏感,可能产生奇奇怪怪的“找不到文件”错误。最好统一放到纯英文路径,例如D:\QtWorkspace。
2. 从零创建第一个Qt Widgets项目
环境准备好以后,就可以打开 Qt Creator 新建项目了。我常用的是 Qt Widgets Application 模板,因为它最直观:有窗口、有按钮、有控件,编译完马上能看到东西,成就感来得快。
2.1 新建项目向导全流程与关键选项
进入 Creator 后,点击“文件 → New File or Project”,左侧分类选择“Application”,再选“Qt Widgets Application”。这一步别去选“Qt Quick Application”或者“Empty qmake Project”,前者带 QML 概念,后者没有向导生成的示例代码,新手起步会绕远路。
向导会要求填“项目名称”和“创建路径”。项目名称必须用英文,全小写风格是社区惯例,比如demo_first_qt。路径则选择我们刚建立的干净英文目录。这一步里的“构建系统”有三个选项:qmake、CMake、Qbs。我的建议是,第一个项目先选 qmake,因为 qmake 的工程文件就是一个简单的文本脚本,逐行看下来非常容易理解。跑通了整个流程以后,再换 CMake 去体会另一种组织方式,这样知识结构会比较扎实。
接着向导会让你选择“类名”和“基类”。默认会生成一个MainWindow,基类是QMainWindow,建议保留默认。“Create a .ui file”这个复选框默认是勾选的,保留勾选,因为我们想用拖拽的形式设计界面。
“翻译文件”这一步可以不用选,Qt Linguist 是为多语言界面准备的,第一个项目里引入反而多余。最后的“Kit Selection”里,勾选我们刚才确认过的那个可用 Kit。这里被勾中的 Kit 会决定最终生成 exe 的平台形式:如果勾的是“Desktop Qt 6.5.3 MinGW 64-bit”,那生成的就是 64 位程序。
全部点击完成后,Creator 会自动生成一套完整的项目结构和示例代码。你可以直接点左下角的绿色运行按钮,马上看到一个有菜单栏和状态栏的空窗口出现在屏幕上。到这一步,项目创建的基础流程就已经走通了。
2.2 项目文件结构:谁负责入口、谁负责界面、谁负责业务
新建完的项目会包含几个关键文件,很多教程只告诉你“点运行”,却不解释这些文件是干嘛的。这里逐个说清楚。
目录结构大致是这样:
demo_first_qt/ ├── demo_first_qt.pro ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h └── mainwindow.uimain.cpp是程序入口,里面创建了QApplication,创建了MainWindow窗口对象,调用show()显示窗口,最后进入exec()事件循环。简单说,这一段代码负责让界面“转起来”。mainwindow.h是主窗口的类声明。里面有一个很特别的宏Q_OBJECT,它标志着这个类要接受 Qt 的元对象系统处理(后面会细讲)。mainwindow.cpp是主窗口的具体实现,构造函数里有一行ui->setupUi(this),它的作用是把.ui文件里拖出来的所有控件实例化并挂到当前窗口上。mainwindow.ui是一个 XML 格式的界面描述文件。你在 Creator 的“设计”模式里拖一个按钮、改一个布局,保存后全部写在这个文件里。
.ui文件并不会被编译器直接编译。构建时,Qt 的工具链会先把它翻译成一个 C++ 头文件,名称类似ui_mainwindow.h,里面是具体的布局代码。这个文件是自动生成的,不需要你手动维护。
很多新手会有疑问:我为什么不在代码里手写控件,反而要拖一个 XML 文件?我的理解是,.ui文件和手写代码不是二选一的关系,而是一个强调可视化的分工。界面上控件摆得是否整齐、布局嵌套是否合理,拖拽编辑往往比盲写代码直观得多,清晰的.ui文件也对后续维护很友好。
2.3 工程文件逐行解读:qmake的pro文件
qmake 项目的核心配置文件是.pro文件。Creator 在创建项目时已经帮你写好了基本框架,打开看大概是这个样子:
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = demo_first_qt TEMPLATE = app SOURCES += \ main.cpp \ mainwindow.cpp HEADERS += \ mainwindow.h FORMS += \ mainwindow.ui这六七个条目非常重要,逐行解释:
QT += core gui:链接 Qt 基础模块。core 是核心非界面类,gui 是图形界面基础类。Qt 5 以后界面控件被挪进了 widgets 模块,所以下面需要判断版本再加一次。greaterThan(QT_MAJOR_VERSION, 4):这是 qmake 的版本判断语法。Qt 5 之前 widgets 类还在 gui 模块里,Qt 5 之后必须单独链接 widgets,所以加这一行保证跨版本兼容。TARGET:指定生成的可执行程序名称,这里会生成demo_first_qt.exe。TEMPLATE = app:告诉 qmake 这是一个应用程序项目,不是库项目。如果要生成静态库或动态库,应该写lib。SOURCES、HEADERS、FORMS:三行分别声明项目里有哪些 C++ 源文件、头文件和界面文件。后面新添加文件时,可以用右键项目树里的“添加新文件”,Creator 会自动更新这三行。
很多职场老手接手别人的 qmake 项目时,第一件事就是看.pro文件,确认它链接了哪些模块。模块没写全,最常见的报错就是“找不到 QMainWindow”“找不到 QWidget 头文件”之类。比如你只是想在窗口里显示一张图片,需要用到QAxObject之类的 ActiveX 类,那就必须在QT +=后面追加axcontainer;而如果你用到 OpenGL,要追加opengl。.pro文件决定编译器能看到哪些 Qt 头文件和库,这是创建项目时就要养成的意识。
3. 编译不再是“点一下运行”那么简单
很多人在 Qt Creator 里一直点那个绿色三角,却从没想过点下去的瞬间到底发生了什么。我建议每个学习 Qt 的人都手动走一遍编译流程,只有亲手在命令行敲过 qmake 和 make,才能在 Creator 报错时瞬间定位到故障环节。
3.1 qmake经典手工编译链路的完整演示
在讲解 Creator 的自动化编译前,先做一次全手动演示。打开开始菜单里的“Qt 6.5.3 (MinGW 11.2.0) 64-bit”终端,这个终端的环境变量是预配好的,包含编译器路径和 Qt 工具路径。
在终端里进入项目目录:
cd /d D:\QtWorkspace\demo_first_qt第一步先执行:
qmake这一步的作用是根据.pro文件生成 Makefile。Makefile 是一个“施工图”,里面规定了要用哪些规则来编译所有源文件、哪些文件先生成、哪些文件后链接。
第二步执行:
mingw32-make -j8mingw32-make是 GNU make 的 Windows 版本,-j8表示用 8 个线程并行编译,编译速度会有明显提升。这里的底层逻辑是,make 工具按照刚才生成的 Makefile,逐条执行 g++ 编译指令和链接指令,最终生成目标 exe。
编译完成后,当前目录会多出若干中间产物和一个可执行文件(Debug 模式下会有debug文件夹)。输入程序名直接运行,就能看到那个窗口弹出来:
debug\demo_first_qt.exe这套流程看似绕远路,它的价值在于:你知道了 Creator 的“运行”按钮,本质上是先调用了类似qmake的工具生成构建规则,再调用make或jom来完成编译链接。理解了这一步,当构建报错时,就能判断它到底是在qmake阶段失败(工程文件的配置问题),还是在make阶段失败(源码语法问题或链接问题)。
如果还想指定编译类型,可以在 qmake 时追加CONFIG+=release:
qmake CONFIG+=release mingw32-make -j8这时 exe 会生成在release目录下。Debug 和 Release 的应用场景不同,Debug 版体积大、速度慢但带调试信息,方便打断点排查问题;Release 版做了优化,适合最终交付。日常练习时两边都可以,正式发布优先 Release。
3.2 用CMake构建Qt项目:新主流的配置方式
Qt 6 已经把官方推荐构建系统切换到了 CMake。如果你新建项目时选择了 CMake,生成的内容不再是.pro文件,而是一个CMakeLists.txt。它长这样:
cmake_minimum_required(VERSION 3.16) project(DemoFirstQt VERSION 0.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 REQUIRED COMPONENTS Widgets) qt_standard_project_setup() qt_add_executable(DemoFirstQt main.cpp mainwindow.cpp mainwindow.h mainwindow.ui ) target_link_libraries(DemoFirstQt PRIVATE Qt6::Widgets)这段代码的作用和 qmake 的.pro文件大同小异,但思路有些不同:
find_package(Qt6 REQUIRED COMPONENTS Widgets):CMake 会在系统里寻找 Qt6 的安装位置,找到头文件和库的路径。qt_add_executable(...):声明要生成一个可执行程序,明确包含哪些源文件和头文件。target_link_libraries(... PRIVATE Qt6::Widgets):告诉链接器,链接时要带上 Qt6 的 Widgets 库。
在命令行里也可以手动完成 CMake 构建,流程是两条命令:
cmake -S . -B build cmake --build build第一条命令会创建build目录并在这目录里完成配置,生成构建规则;第二条命令按规则真正编译。这个build目录就是下一步要讲的“影子构建”产物。
CMake 相比 qmake 的优势之一是生态通用性好:你如果用 CMake 组织 Qt 项目,以后想在这个项目里集成一些别的 C++ 库(像 JSON 解析、图像处理库)会顺滑得多,因为那些库通常也提供了 CMake 的支持。qmake 更偏向 Qt 自家封闭生态,简单,够用,但扩展外部依赖时略显吃力。
3.3 Shadow Build:为什么源码目录旁边会多一个奇怪文件夹
用 Qt Creator 构建项目后,你经常会发现工作目录里凭空多出一个像build-demo_first_qt-Desktop_Qt_6_5_3_MinGW_64_bit-Debug的文件夹。很多新手会困惑:我明明没有创建这个文件夹,它是什么?
这个机制叫“影子构建”,翻译成英文是 Shadow Build。含义是:构建生成的中间文件、Makefile、.o 目标文件、最终的 exe,全部放到源码目录之外的一个独立目录里,避免污染漂亮的源码结构。
好处很明显:
- 可以同时构建 Debug 和 Release 两种配置,互不干扰。
- 源码目录永远是干净整洁的状态,版本管理时不用排除一堆中间产物。
- 想清理所有编译产物,直接把 build 文件夹删掉即可,不影响任何源码。
代价也客观存在:如果你不小心把项目拷到新位置,或者改了.pro/CMakeLists.txt后没有重新跑构建,某些旧缓存可能导致“找不到ui_mainwindow.h”“轮不到的 Makefile”之类的诡异错误。这时候别去怀疑代码逻辑,先做一次“清理项目”或直接把 build 目录删掉,重新构建,九成问题会消失。
3.4 moc编译器与信号槽机制的关系
Qt 项目特有的一个流程,新手一定要知道它,不然遇到一堆vtable报错时完全摸不着头脑。
C++ 本身没有“信号槽”这种东西。Qt 为了让对象之间能够方便地通信,在 C++ 基础上做了一层元对象系统扩展。也就是说,你的类如果声明了Q_OBJECT宏,这个类的名称、信号、槽、属性等信息就会被额外记录成一张元信息表。Qt 的强大的connect、emit能力,全都依赖这张表。而生成这张表的过程,就需要一个叫moc(Meta-Object Compiler,元对象编译器)的工具。
在编译链路里,moc被插入在正式编译之前。它扫描所有头文件,找到含有Q_OBJECT的类,自动生成一个moc_xxx.cpp文件,然后在链接阶段,把这个生成的文件一起编译并链接进程序。
关键点在于:这个moc_xxx.cpp是自动生成的,你不要手动加到SOURCES里,编译器工具链会自动处理。如果你在.pro文件里添加了一个新头文件,但忘了把它放进HEADERS变量,或者你的类声明了Q_OBJECT却没被列为头文件,输出端就会看到恶心的链接错误,比如:
undefined reference to `vtable for MainWindow'遇到这种错误不要慌,沿着构建系统配置去排查:头文件有没有加进工程、类里是否写了Q_OBJECT、刚才修改后有没有正确调用 qmake/cmake 重新生成构建规则。理解了moc的原理,类似的元对象错误基本都能解决。
4. 编译运行中的高频问题与排查经验
最后把我在实际带人过程中最常见的几类问题整理出来,基本覆盖了项目创建与编译这条链路上会踩的高频坑。每一条背后都有一段真实踩坑经历。
4.1 中文乱码的正确处理姿势
Windows 下用 MSVC 编译 Qt 项目时,源码里写中文,在界面上经常显示成乱码。这背后是两个世界打架的问题:MSVC 编译器默认把源码按本地代码页(通常是 GBK)来解释,而 Qt 6 内部多种默认编码是 UTF-8;两边解读不一致,中文自然就乱了。
行内最快最稳的方案,是在含中文字符串的源文件顶部加一行预处理指令:
#if defined(_MSC_VER) #pragma execution_character_set("utf-8") #endif但更推荐的做法是从源头养成习惯:中文文本用QStringLiteral("你好")写,或者直接把所有界面上要用的文案放到.ui文件/资源文件里,而不是在代码里裸写字符串。资源文件按 UTF-8 管理,Qt 加载时没问题,后续做多语言翻译也方便,这是一劳永逸的路径。
4.2 找不到头文件、链接失败时的排查方向
错误形如:
mainwindow.h: No such file or directory或者:
cannot find -lQt5Widgets前者多半是头文件路径没包含进来,或者源文件里的#include "mainwindow.h"拼写错了,再不然就是构建系统里没声明这个头文件,需要检查.pro的HEADERS或CMakeLists.txt列表。
后者是链接阶段找不到 Qt 库文件,常见原因是构建套件选错,或者QT +=少加了模块。特别注意:如果项目链接了Qt5Widgets,但 Kit 里选的是 Qt6 版本,路径不匹配,链接器自然找不到库。换个思路:先用一个新建的空窗口项目测试当前 Kit 是否能正常编译,能通过的 Kit 再回过去跑目标项目,可以快速排除套件层面的问题。
4.3 应用程序无法正常启动0xc000007b与DLL问题
Windows 上 Qt 程序最经典的一个报错是“应用程序无法正常启动 0xc000007b”。这个错误码的含义一般是“应用程序的位数与依赖的 DLL 位数不一致”。
比如你编译生成了一个 64 位的 exe,但运行时加载的Qt6Widgets.dll是 32 位版本,系统就会弹这个错。排查思路:
- 确认 Creator 的“构建套件”里选的是 64 位。看构建输出中“配置”里的位数标识。
- 确认你生成的构建文件夹和 Qt 库位数一致。64 位 Qt 安装目录名通常带
_64后缀。 - 正式发布时,用 Qt 提供部署工具把相关 DLL 复制到 exe 目录(这个后面讲到具体工具时再展开)。
还有一个小概率情况是系统缺少 Microsoft Visual C++ 运行库。MSVC 编译的程序依赖 VC 运行库,如果目标电脑没有装,也会报这种启动错误。开发机一般没事,部署到别的电脑时常出现。
4.4 程序运行起来但窗口空白或闪退
窗口能弹出来但一片空白,大多数时候不是运行问题,而是你没给窗口设置中央部件。QMainWindow并不是直接往上面堆控件就行的,它有一个“中央部件区”的概念。在构造函数里先这样写就不会空白:
ui->setupUi(this); setCentralWidget(ui->widget);如果程序一运行就闪退,优先检查调试输出窗口里的崩溃信息,定位到具体的空指针访问。最常见的原因是访问了尚未初始化的对象,或构造函数里触发了某个信号槽,而槽对象还没准备好。ui->setupUi一定要放在构造函数里最前面,不要在它之前去操作ui指针。
还有一个很容易忽略的崩溃原因:Debug 和 Release 混用。比如你的主程序是 Debug 编译,却链接了一个 Release 版的库,或者反过来,都会造成运行时奇奇怪怪的崩溃。保持队列统一是一个铁律:一整套构建环境都要保持一致。
4.5 常见问题速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| Kit 里有黄色感叹号 | 没安装对应编译组件或 Qt 版本 | 重新运行安装器,补齐组件 |
找不到ui_xxx.h | .ui文件未编译,或影子构建缓存异常 | 清理构建目录,重新构建 |
undefined reference to vtable | Q_OBJECT宏缺失,或头文件未加入工程 | 检查类声明,检查 HEADERS/CMakeLists |
| 程序乱码 | 编码问题,MSVC 默认代码页与 Qt 不一致 | 用 UTF-8+BOM 或QStringLiteral |
| 双击 exe 提示缺 DLL | 未使用部署工具或 Qt 库没拷贝 | 运行时把 Qt 的 bin 目录加入 PATH,或做部署 |
| 构建成功但点运行没反应 | Debug 与 Release 混用,或启动目录不对 | 确认构建套件配置,检查运行目录下的依赖 |
排查时有一个很实用的习惯:看构建输出窗口的最上面和最下面。最上面那几行通常能看出 Creator 正在调用的命令到底是qmake还是cmake,最下面那几行是错误摘要。中间那一大堆红色的字,因为编译体系压缩输出的原因,未必精准对应到代码行,反而会误导新手。如果你能快速确认“qmake 这一步过了,make 这一步没过”,问题范围就会缩小一半。
写在最后:环境是软件工程的另一半
我自己带新人时,最喜欢看他们第一次成功弹出一个带菜单栏的窗口时的表情。那个窗口虽然什么都没有,但它背后走通了编译器、元对象系统、事件循环这一整条链路。从那时起,他们就建立起了一种信心:“以后不管报什么错,我至少知道它发生在哪个环节。”
如果你打算系统地学 Qt,我会建议把这篇“创建与编译”当成地基,反复练习到闭着眼都能搭出一套干净环境。然后下一篇文章,我会顺着信号槽机制展开聊,那也是 Qt 最有魅力的部分之一。期望读者看完后能带着自己的小项目来,我们一起把这块硬骨头啃完。