《Qt从零入门系列(四):Qt项目结构与基础编程详解——从Hello World到对象树》
2026/8/13 17:43:46 网站建设 项目流程

学习Qt,真正让人开始“感觉到门槛”的,往往不是控件有多少,也不是API有多复杂,而是第一次打开Qt Creator后,面对.pro、h、cpp、ui这些文件时,突然发现:一个看似简单的窗口,背后竟然藏着这么多东西。

所以这一篇,我们不急着堆砌控件,也不急着深入复杂的信号与槽,而是从最经典的 Hello World 入手。先用一个按钮、一个标签,把Qt程序真正跑起来;再分别通过纯代码和可视化两种方式构造界面,在动手的过程中理解Qt是怎样创建控件、组织对象以及响应事件的。

当Hello World不再只是“会运行”,而是能够说清楚它为什么这样写、每一个文件分别负责什么、对象为什么能够自动释放,才算真正迈过了Qt入门的第一道门槛。

接下来,我们还会进一步拆解一个完整Qt项目的文件结构,认识.pro、main.cpp、widget.cpp、widget.h和widget.ui各自承担的职责,并在最后补上变量命名、快捷键、文档查询、坐标系以及对象树等开发中极其常见,却又容易被初学者忽略的细节。这一篇不追求“学很多”,而是希望把Qt最基础的那几块砖,一块一块摆正。基础打牢之后,后面的窗口、控件、信号与槽、布局管理,才能真正做到越学越顺。

目录

一、创建Qt的Hello World程序

1.1 使用“按钮”实现Hello World

1.1.1 纯代码方式实现

1.1.2 可视化方式实现

1.2 使用“标签”实现Hello World

1.2.1 纯代码方式实现

1.2.2 可视化方式实现

1.3 Hello World的多种实现方式与最佳实践

1.3.1 使用输入框实现

1.3.2 纯代码方式实现Hello World

1.3.3 使用按钮实现Hello World

1.3.3.1 图形化方式实现按钮版Hello World

1.3.3.2 纯代码方式实现按钮版Hello World

1.4 Qt中的内存泄漏问题

1.5 初识Qt对象树

1.6 Qt中的中文乱码问题

1.7 小结

二、Qt项目文件解析

2.1 .pro工程文件解析

2.2 widget.h头文件解析

2.3 main.cpp主函数文件解析

2.4 widget.cpp源文件解析

2.5 widget.ui界面文件解析

2.6 Qt界面构造方式的最佳实践

2.6.1 纯代码方式实现

2.6.2 按钮方式实现

2.6.3 两种按钮版Hello World实现方式对比

2.6.4 实际开发中,代码构界面与可视化构界面如何选择

三、初识Qt收尾——Qt编程注意事项

3.1 Qt变量命名风格

3.1.1 命名规范与设计思路

3.1.2 命名最佳实践

3.2 Qt常用快捷键

3.3 Qt官方文档查询方法

3.3.1 使用Qt帮助文档

3.3.2 查询文档时的注意事项

3.4 认识Qt坐标系

3.4.1 Qt坐标系

3.4.2 术语扫盲——什么是像素

3.5 深入理解Qt对象树

3.5.1 Qt对象模型与对象树核心知识

3.5.2 Qt对象树的析构机制及注意事项

3.5.3 核心总结与最佳实践

3.5.4 对象树析构过程演示


一、创建Qt的Hello World程序

1.1 使用“按钮”实现Hello World
1.1.1 纯代码方式实现

效果如下所示:

1.1.2 可视化方式实现

(1)双击:"widget.ui"文件;

(2)拖拽控件至ui界面窗口并修改内容;

(3)构建并运行,效果如下所示:

1.2 使用“标签”实现Hello World
1.2.1 纯代码方式实现

实际演示效果:

1.2.2 可视化方式实现

(1)双击:“widget.ui”文件;

(2)拖拽“标签”至UI设计界面中,并双击修改标签内容;

(3)实现效果如下图所示:

1.3 Hello World的多种实现方式与最佳实践

label“标签”:界面上一个用来显示内容的字符串控件。

Qt有自己实现的一套标准库(“自己造轮子”):

1.3.1 使用输入框实现

1.3.2 纯代码方式实现Hello World

1.3.3 使用按钮实现Hello World
1.3.3.1 图形化方式实现按钮版Hello World

1.3.3.2 纯代码方式实现按钮版Hello World

1.4 Qt中的内存泄漏问题

1.5 初识Qt对象树

前端开发(网页开发)中也存在类似的思想,例如DOM(文档对象模型)。浏览器会将网页中的各种元素抽象成一棵树,本质上也是一种N叉树结构,通过这种层级关系将页面中的标签、节点以及它们之间的关系组织起来。

Qt中同样采用了类似的设计思想,引入了对象树机制。Qt会将界面中的各种对象按照父子关系组织成一棵树,通过这种结构统一管理对象的生命周期和层级关系。

1.6 Qt中的中文乱码问题

这里有一个查找字符编码的网站,大家可以尝试一下:utf8

使用qDebug,还有一个好处:

1.7 小结

两张图小结一下:

二、Qt项目文件解析

在使用Qt Creator创建一个新的Qt工程后,工程目录中会自动生成一系列项目文件。其中,.pro文件是Qt工程中最核心的配置文件之一,它负责描述整个项目的基本信息、编译配置以及工程结构。

2.1 .pro工程文件解析

工程创建完成后,在项目目录列表中可以看到一个后缀为.pro的文件,例如:MyProject.pro。其中pro是Project(项目)的缩写,.pro文件就是Qt工程文件。

.pro文件本质上是qmake的工程配置文件,它用于告诉qmake当前项目包含哪些源文件、头文件、资源文件,以及项目需要使用哪些编译选项。在Qt的传统构建流程中,.pro文件并不会直接参与程序编译,而是作为输入文件交给qmake:qmake会读取.pro文件中的配置信息,根据不同平台生成对应的Makefile,之后编译工具(如make、nmake等)再根据Makefile完成源代码的编译和链接。

可以简单理解:.pro文件负责描述“这个项目是什么样的”,qmake负责根据描述生成编译规则,Makefile负责真正执行编译过程。

双击打开这个文件,核心内容如下:

配置项说明
QT += core gui告诉Qt:我们需要core和gui这两个模块。没有它们,窗口都画不出来。
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets一条带条件的语句。如果Qt主版本号大于4,就把widgets模块也捎上。这是Qt4到Qt5的历史遗留问题,widgets在Qt5里从 gui 模块独立出来了,老项目得靠这行兜底。
TARGET = QtFirst最终生成的可执行文件叫什么名。编译完,你会看到QtFirst.exe。
TEMPLATE = app告诉qmake:这是个应用程序,不是库,不是插件,是个能跑起来的程序。
SOURCES += main.cpp\widget.cpp项目里需要编译的源文件清单。新增.cpp文件时别忘了往这加,不然编译器压根不知道它的存在。
HEADERS += widget.h项目里的头文件清单。

.pro文件的写法:

.pro文件本质上就是一张工程清单,告诉qmake这个项目叫什么、用了哪些Qt模块、要编译哪些源文件。写法不复杂,逐条过一遍就清楚了。

1. 注释:从#开始,到这一行结束。跟shell脚本一个习惯。

2. 模块声明:QT += core gui,告诉Qt链接core和gui这两个基础模块。没有它们,窗口都画不出来。

3. 版本兼容:greaterThan(QT_MAJOR_VERSION, 4): QT += widgets,这行是个条件判断。意思是:如果Qt主版本号大于4(也就是Qt5及以上),就额外加上widgets模块。因为widgets在Qt5里从gui模块里拆了出来,老项目需要这行兜底。如果你的项目只打算跑在Qt5上,直接写QT += widgets也行,但为了兼容性,建议保留Qt Creator自动生成的这个写法。

4. 目标文件名:TARGET = QtDemo。编译完输出的可执行程序叫什么,就在这里定。不写的话qmake会用默认名。

5. 模板类型:TEMPLATE = app。告诉qmake要给哪种项目生成Makefile。可选的模板有:

模板说明
app应用程序。这是默认值,不写TEMPLATE就自动按这个来。
lib库文件。
vcapp给Visual Studio用的应用程序工程文件。
vclib给Visual Studio用的库工程文件。
subdirs多目录工程。能进入子目录递归调用make,适合大型项目的分模块管理。

6. 源文件清单:SOURCES += main.cpp widget.cpp。项目里所有.cpp文件都列在这。新增源文件时别忘了补一行,不然编译器根本不知道它的存在。

7. 头文件清单:HEADERS += widget.h。头文件也在这里登记。虽然头文件不参与编译,但列进去后Qt Creator能在项目视图里把它们展示出来,跳转和补全也会更顺畅。

8. 资源文件:RESOURCES += painter.qrc。图片、图标、翻译文件等资源都通过.qrc文件管理,在这里声明。

9. UI 设计文件:FORMS += widget.ui。用Qt Designer拖出来的界面文件,在这里登记。

10. 配置信息:CONFIG += c++11。CONFIG用来告诉qmake 一些全局配置选项。比如c++11就是启用C++11特性支持。还可以用CONFIG控制很多其他编译选项,用到了再查就行。

2.2 widget.h头文件解析

在Qt中,信号与槽这套机制不是默认就开着的。想用上它,类里面必须加上Q_OBJECT宏,它就像给这个类插上了信号槽的“天线”。没有它,后面的connect根本找不到你的信号和槽,程序跑起来也会莫名其妙地报错。

而那个绕不开的指针Ui::Widget *ui;,它的来头也要说清楚。这个指针是用前面声明的namespace Ui里的Widget类定义的,它指向的正是我们用Qt Designer可视化拖拽出来的那个界面。以后想访问界面上的按钮、标签、输入框,全都要通过这个ui指针去够到。简单说,ui就是操作界面的总把手,界面上的每个组件,都攥在它手里。

2.3 main.cpp主函数文件解析

随手新建一个Qt工程,打开main.cpp,你都会看到下面这段自动生成的代码。别急着跳过,它可是整个程序启动逻辑的起点。

新建一个Qt工程后,main.cpp里那几行自动生成的代码,其实是一套固定不变的启动模板。别急着跳过,我们拆开看每一行到底在做什么。

2.4 widget.cpp源文件解析

widget.cpp是Widget类的实现文件。窗体上要实现的所有功能,按钮的响应逻辑、数据的处理流程、界面的动态刷新,几乎都集中在这一个文件里。如果说widget.h是窗体的骨架声明,那么widget.cpp就是血肉所在,所有真正的行为都在这一具躯体里落地。

2.5 widget.ui界面文件解析

widget.ui是窗体界面的定义文件,本质是一个XML文件。窗口上所有组件的属性、布局关系,以及信号与槽的关联,全部以XML的形式记录在这个文件里。你在Qt Designer里拖一拖控件、改一改属性,Qt会自动帮你把这些操作解析并保存成widget.ui。设计界面的时候,你完全不用关心这个文件内部是怎么写的,只要在UI设计器里可视化地拖拽、配置就行。生成的XML交给Qt去处理,你专心把界面搭好。

2.6 Qt界面构造方式的最佳实践

完成一个Hello World,路子不止一条。Qt里的控件琳琅满目,能扛起“显示第一行文字”这个任务的,远比你想象的多,标签、按钮、文本框,甚至一个弹窗,都能成为你亮相的第一个舞台。接下来,我们挑几种最典型的控件,看看它们各自怎么把“Hello World”搬到屏幕上。

2.6.1 纯代码方式实现

2.6.2 按钮方式实现

Qt中的信号槽机制:

2.6.3 两种按钮版Hello World实现方式对比

2.6.4 实际开发中,代码构界面与可视化构界面如何选择

三、初识Qt收尾——Qt编程注意事项

3.1 Qt变量命名风格
3.1.1 命名规范与设计思路

Qt的命名规则,跟我们之前习惯的那套不太一样。直接看表:

名称类型命名规则
类名首字母大写,单词与单词之间首字母也大写
函数名、变量名首字母小写,单词与单词之间首字母大写

没错,Qt 偏爱的是驼峰命名法

这跟我们之前一直用的蛇形命名法(my_function这种)路子完全反着来。习惯了蛇形,刚上手Qt 时总会有那么一点别扭,但没事,写几天手指头就记住了。入乡随俗,写Qt代码就按Qt的规矩来。

3.1.2 命名最佳实践

3.2 Qt常用快捷键

这些快捷键,是你和Qt Creator熟络起来的捷径。刚开始可能记不住几个,但有几个每天都会被按到,用着用着就长在手指上了。

功能快捷键
注释/取消注释当前行或选中行Ctrl + /
运行项目Ctrl + R
编译项目Ctrl + B
缩放编辑器字体Ctrl + 鼠标滚轮
查找文本Ctrl + F
向上 / 向下移动当前行Ctrl + Shift + ↑ / ↓
查看光标所在符号的帮助文档F1
自动对齐代码Ctrl + I
在同名的头文件(.h)与源文件(.cpp)之间切换F4
快速为函数生成对应的定义(在声明处使用)Alt + Enter
3.3 Qt官方文档查询方法
3.3.1 使用Qt帮助文档

打开Qt帮助文档的入口有三个,平时用哪个顺手就选哪个,效果完全一样。

  1. 光标定位,一键直达:把光标停在你想查询的类名或方法名上,直接按F1,对应的帮助文档马上弹出来。这是最快、最不打断思路的方式。

  2. 侧边栏入口:在Qt Creator左侧边栏里,用鼠标点一下“帮助”按钮,进入帮助模式,就会看到下图这样的帮助界面。适合慢慢翻文档、系统学习某个模块时用。点击“帮助”之后,出现如下图示界面:

  3. 独立帮助程序:找到Qt Creator的安装目录,进入bin文件夹,双击里面的assistant.exe。它会启动一个独立的Qt助手程序,不依赖Qt Creator也能查文档。适合在不开 IDE 的时候单独查资料。还可以进行检索:

使用示例:

  1. 新建项目,在新建的项目中使用Qt中的“QpushButton”控件。
  2. 打开帮助手册,在“索引”里面输入“QpushButton”。

3.3.2 查询文档时的注意事项

尽量别依赖中文文档。阅读英文文档是每个程序员绕不开的基本功,这一关迟早要过,没得躲。Qt的官方文档在技术文档里算得上出类拔萃,用词清晰,示例到位,只要耐着性子读下去,看懂个八九成并不难。

我们往后的学习和工作中,翻文档会是家常便饭。与其等到那时候再硬着头皮上,不如从现在就开始练。

3.4 认识Qt坐标系
3.4.1 Qt坐标系

Qt的坐标系,跟我们数学课上习惯的那套笛卡尔坐标系不太一样。它的原点固定在左上角,也就是(0, 0)。从这里出发,X轴向右增长,Y轴向下增长。

换句话说,越往右X越大,越往下Y越大。屏幕上任何一个控件的位置,都由这样一对(x, y)坐标唯一确定。把这点刻在脑子里,后面设置控件位置、处理鼠标点击事件时,就不会因为方向感错乱而犯迷糊了。

这里有个容易忽略的细节:如果窗口是嵌套在另一个窗口里面的,那么它的坐标就不是相对整个屏幕左上角来算的了,而是相对它的父窗口左上角来算的。换句话说,子窗口的(0,0) 点,落在父窗口的坐标系里,而不是整个屏幕的坐标系里。

示例:用 Qt 坐标系设置控件位置

掌握了坐标系,我们来看看代码里怎么用。下面这段代码演示了如何在一个窗口里,用move()函数把一个按钮摆到指定的坐标位置上:

运行结果如下图示:

3.4.2 术语扫盲——什么是像素

3.5 深入理解Qt对象树

3.5.1 Qt对象模型与对象树核心知识

Qt的对象管理,靠的是一棵看不见的“对象树”。这套机制是整个Qt内存管理的地基,也是Qt跟传统C++裸指针管理最不一样的地方。我们把它的核心逻辑拆成几块:

核心概念说明与机制
组织方式Qt中的QObject以对象树的形式组织。一个父对象下面挂着一堆子对象,形成清晰的父子层级。
父对象指针(parent)创建QObject对象时,构造函数里可以传入一个父对象指针,从此确立父子关系。
自动管理子对象一旦出生,就会自动登记到父对象的children()列表里。父对象析构时,会顺手把自己名下所有子对象一并析构掉。
GUI 应用QWidget是所有可视化组件的基类,它也继承自QObject,所以同样吃这套对象树规则。子组件会显示在父组件的坐标系里,还会被父组件的边界裁剪。
内存管理对象树自动处理关联对象的内存释放,内存泄漏的风险因此大幅降低。删除父对象,它下面的所有子对象自动跟着走;单独删除某个子对象,它也会自觉从父对象的子对象列表里除名。
析构保证Qt保证没有任何一个QObject会被删除两次。这个安全底线,正是靠对象树的析构顺序来兜住的。
设计意义这套机制让GUI程序设计更直观、更安全。删除一个窗口,它里面的按钮、标签、输入框等所有子部件,全都会跟着一起被清理干净,不用你手动一个个delete。

简而言之,Qt的对象树就干了一件事:让你在创建对象时给它认个“爹”(parent)。日后你把这个“爹”删掉,它名下的所有“儿子”(children)都会自动跟着一起走,不用你挨个手动清理。省心,还安全。

栈上局部对象的析构顺序与对象树机制

情境析构顺序关键机制结果
父子对象都是栈上的局部对象按创建顺序的逆序来后创建的先死,先创建的后死子对象(比如quit)析构时,会主动把自己从父对象(比如window)的children()名单里摘出去父对象随后析构时,名单里已经查无此子,自然不会重复删除,double delete也就无从谈起

核心要点,拆开揉碎:

一句话记住它:先出生的后离世,后出生的先离世。子对象咽气前,会自觉从父对象的“户口本”上把自己的名字划掉。等父对象走的时候翻开本子一看,那孩子早就不在了,于是挥挥手,不带走一片云彩。整套流程,干净得没有一丝残留。

但是,如果我们使用下面的代码:

3.5.2 Qt对象树的析构机制及注意事项

对象树这套机制,用对了是省心神器,用错了就是崩溃陷阱。下面把正常写法和一种极易踩雷的错误写法摆在一起对比,看完你就知道坑在哪了。

对比项正常情况(前面讲过的安全写法)问题情况(顺序反了,直接崩溃)
创建顺序先创建父对象window,再创建子对象quit,并在quit的构造函数里把window指定为它的父对象。先创建子对象quit,之后才创建父对象window,再通过setParent或后续操作,让window变成quit的父对象。
C++ 析构顺序按创建顺序的逆序走:quit(后创建)先析构,window(先创建)后析构。同样逆序,但这次是window(后创建)先析构,quit(先创建)后析构。
对象树行为1. quit析构时,自己主动从window的children()列表里注销,不给老爹添麻烦。
2. window析构时,翻开名单一看,儿子已经没了,安心闭眼。
1. window先析构,按照对象树规则,它会把children()列表里的所有对象,包括quit,挨个删除一遍。
2. 等quit作为局部变量超出作用域时,它又会被第二次析构。
最终结果安全。每个对象只死一次,符合C++标准,程序平稳落地。崩溃。quit被杀了两次,double delete,程序当场暴毙。

这个坑的根源就一句话:别让父对象比子对象后创建。正常逻辑是“先有爹后有儿子”,析构时儿子先走爹后走,刚好安全;反过来搞,爹先走的时候已经把儿子带走了,儿子自己再走一次,不死才怪。

写Qt代码时,记住这条顺序:先建父,再建子,让父在子前面出生。这样对象树和C++析构顺序就能默契配合,保你平安。

3.5.3 核心总结与最佳实践

问题根源一句话就能说透:Qt对象树能帮你管内存,但有个前提,父对象的命必须比子对象长。一旦父子俩都在栈上,你就得死守一条铁律:父对象必须晚于子对象析构。顺序一乱,子对象就会被删两次,程序当场崩给你看。这类析构顺序问题,调试起来极其头疼。所以Qt开发里,最好养成两个习惯:

  1. 能在出生时认爹,就别拖到后面。创建对象时,直接通过构造函数把父对象传进去,关系一早定死,别等后面再用setParent补救。

  2. 堆上创建,大胆用new。有父子关系的对象,优先扔到堆里,让Qt对象树统一接管它们的生命周期。你只管把父对象删对,它名下的子对象会一个不落地自动释放。

一句话指南:Qt界面对象,用new创建,顺手指定好Parent,剩下的交给对象树。 省心,稳当,不背锅。

3.5.4 对象树析构过程演示

Qt对象树如图所示:

  1. 创建一个新工程并编译运行,生成如下窗口:
  2. 选中工程名,鼠标右键---->“add new…”(或“添加新文件”):
  3. 选择“choose…”,弹出如下界面:
  4. 点击“下一步”,弹出如下对话框:
  5. 点击“完成”之后,手动创建类的头文件以及源文件会自动添加到目标工程中:
  6. 修改头文件:
  7. 编写源文件:
  8. 编译并运行:
  9. 当关闭弹出的对话框时,就会自动调用按钮的析构函数:
  10. 观察析构函数的执行顺序:
  11. 执行结果如下所示:
  12. 执行结果分析:

注意,这里有个很容易绕晕的点:调用析构函数和释放内存,其实是两码事。

(1)内存释放顺序:在对象树里,内存释放永远遵循“先子节点、后父节点”的顺序。先清理孩子,再清理父亲,这条规矩是写死的。

(2)析构函数调用顺序:这个顺序跟内存释放并不一定同步。有时候,子节点的析构函数反而会在父节点之后才执行。别想当然地以为“先释放内存就一定先调析构”,两者之间没有必然的先后绑定。

(3)关键区分:调用析构函数,跟实际把内存归还给系统,是两个独立的过程。一个是对象临终前交代后事,一个是把遗体火化下葬,动作不同,时机也可能不同,不能混为一谈。

一句话总结:对象树中,内存释放总是从子节点走向父节点,但析构函数的调用顺序可能跟它不一致。二者是两套独立操作,谁先谁后,别搞混了。


如果这篇文章对你有帮助,欢迎点赞、收藏、关注三连支持。你的每一次反馈,都是我继续肝下去的最大动力。下一篇见。

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

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

立即咨询