🔥 本文专栏:Qt
🌸作者主页:努力努力再努力wz



💪 今日博客励志语录 :
真正成熟的选择,不是选一条不会失败的路,而是选一条失败了也愿意重新站起来的路。
思维导图

引入
在正式开始编写 Qt 程序之前,我们需要先明确一个基本问题:写 Qt 程序,到底需要依赖哪些东西?
前面我们已经知道,Qt 是一套基于 C++ 的图形化客户端开发框架。也就是说,Qt 程序本质上仍然是 C++ 程序,只不过它并不是只依赖 C++ 标准库来完成开发,而是还会使用 Qt 提供的一系列库和工具。
对于一个普通的 C++ 程序来说,我们通常只需要准备代码编辑器和编译器,就可以完成代码编写、编译和运行。而对于 Qt 程序来说,由于它涉及图形化界面的开发,因此还需要依赖 Qt 提供的相关库文件和开发工具。为了让开发者不必手动一个个配置这些内容,Qt 官方提供了 Qt SDK,它会将 Qt 程序开发过程中常用的内容统一打包在一起。
安装好 Qt SDK 之后,我们后续主要会使用 Qt Creator 来编写 Qt 程序。Qt Creator 可以理解为 Qt 官方提供的集成开发环境,它可以帮助我们完成项目创建、代码编写、编译构建和程序运行等操作。
所以,在真正开始写代码之前,我们可以先建立这样一个认识:Qt 程序并不是脱离 C++ 单独存在的程序,而是在 C++ 的基础上,借助 Qt 提供的库和工具来完成图形化客户端开发。
有了这个基础认识之后,接下来我们就可以正式进入 Qt 程序的编写流程,看看一个最基本的 Qt 程序是如何创建、编译并运行起来的。
创建 Qt Widgets 项目并理解核心配置
从控件对象到 Qt Widgets 项目创建
接下来,我们就正式进入 Qt 程序的编写。
不过在讲解具体代码之前,我们需要先建立一个基本认知:我们编写的 Qt 代码,最终是用来生成一个图形化界面的。
前面我们已经知道,Qt 是一套基于 C++ 的图形化客户端开发框架。因此,Qt 程序本质上仍然是 C++ 程序,只不过它不是单纯依赖 C++ 标准库来完成开发,而是会使用 Qt 提供的一系列类和工具来构建图形化界面。
对于一个图形化界面来说,它并不是一个孤立的整体,而是由许多具体的界面元素组成的。比如一个窗口中可能会包含按钮、文本标签、输入框、菜单栏等内容。这些界面元素共同组成了我们最终看到的图形化界面。
在 GUI 开发中,我们通常会把这类具有独立功能、能够被程序创建和控制的界面组成单位称为控件。
控件不是单纯显示在界面上的一张图片,而是一个可以被代码操作的对象,这里所谓的"控制",指的是程序可以通过代码修改控件的属性,或者决定控件在特定操作下执行什么行为。比如设置按钮显示的文字、调整控件的位置和大小、控制控件是否显示,以及规定按钮被点击之后要执行什么逻辑等。
text
显示什么文字
放在什么位置
大小是多少
是否显示
是否可点击
点击之后执行什么逻辑
而控件并不只指能够点击的按钮。按钮、输入框这类可以和用户交互的元素属于控件,文本标签这类主要用于展示内容的元素也可以理解为控件。所以控件可以先理解为图形化界面中具有独立功能、能够通过代码创建和控制的基本组成单位。而一个完整的图形化界面,往往就是由多个控件按照一定关系组织起来形成的。。
由于 Qt 是基于 C++ 的开发框架,所以 Qt 会将这些常见的界面元素封装成对应的类。比如窗口、按钮、标签等常见的界面元素,Qt 都已经为我们提供了对应的类。也就是说,我们在编写 Qt 程序时,并不需要从零开始手动实现这些界面元素,也不需要自己完整定义一个窗口类、按钮类或者标签类。Qt 已经提前帮我们完成了这些常用组件类的设计与实现:类的声明、使用方式以及相关接口通常放在对应的头文件中,而这些类背后的具体功能实现,则以已经编译好的形式封装在对应的库文件中。
对于程序员来说,我们要做的事情,就是在代码中引入对应类所在的头文件,让编译器能够识别这个类;然后在程序中创建对应类的对象,并通过这些对象来构建图形化界面。
这里也可以结合前面一篇博客讲的环境配置的内容来理解:Qt 的相关头文件和库文件都存放在 Qt SDK 对应的安装目录下。头文件负责告诉编译器这些类如何使用,而 Qt 提供的库文件则保存了这些类背后已经实现好的功能代码。 当程序编译、链接和运行时,就需要能够找到这些对应的文件。
因此,前面我们配置环境变量,其中一个重要目的就是让系统在运行 Qt 程序时,能够找到程序依赖的 Qt 动态库。只有这些依赖能够被正确找到,我们编写的 Qt 程序才能正常运行起来。
因此,在真正写代码之前,我们可以先建立这样一个思路:编写 Qt 程序,本质上就是使用 C++ 代码创建和管理 Qt 提供的控件对象,并最终将这些对象组织成一个可视化界面。
带着这个认知,接下来我们就可以开始创建第一个 Qt 项目。
这里打开 Qt Creator,点击左上角的 File 选项,然后选择 New Project 。由于我们的目标是创建一个图形化界面程序,所以这里进入 Application 相关分类,并选择后续要使用的项目模板。
在创建项目时,我们会看到几个不同的选项,例如 Qt Widgets Application 、Qt Quick Application 和 Console Application 。

其中,Console Application 表示控制台应用程序,主要通过命令行窗口进行输入和输出,并不是我们当前要学习的图形化界面程序,所以这里先排除。
Qt Quick Application 则是 Qt 提供的另一种图形化界面开发方式,它通常会涉及 QML。这里可以先简单了解一下:在 Qt 中,开发图形化界面大致有两种常见方式。
一种是我们当前准备学习的 Qt Widgets,它主要通过编写 C++ 代码来完成界面构建。也就是说,我们需要在代码中创建对应的控件对象,修改对象的属性,并按照一定的关系将这些对象组织起来,最终得到一个完整的图形化界面。
另一种则是 Qt Quick / QML。QML 是 Qt 提供的一种声明式界面描述语言。所谓声明式,可以先简单理解为:它更关注"界面最终应该长什么样",开发者通过类似描述结构的方式来编写界面。
例如,一个简单的 QML 界面大概可以写成这样:
qml
import QtQuick
import QtQuick.Controls
ApplicationWindow {
width: 400
height: 300
visible: true
title: "Hello Qt"
Button {
text: "点击按钮"
anchors.centerIn: parent
}
}
从这段代码可以直观看到,QML 更像是在描述:创建一个窗口,窗口大小是多少,标题是什么,里面有一个按钮,按钮显示什么文字,并且按钮位于窗口中间。
不过由于我们目前还处于 Qt 入门阶段,并且已经具备一定的 C++ 基础,所以当前没有必要额外引入 QML 这门新的界面描述语言。我们先沿着自己熟悉的 C++ 路线,从 Qt Widgets Application 开始学习即可。
因此,当前我们选择 Qt Widgets Application。它更适合我们现阶段用来学习传统桌面图形化界面的开发,也更方便我们理解窗口、控件以及对象之间的基本关系。
配置 Qt Widgets 项目的核心选项
认识 Widget,并进入项目配置流程
选择 Qt Widgets Application 之后,我们会看到一个比较重要的名词:Widget。后续学习 Qt 时,这个词会经常出现,所以这里需要先建立一个基本认知。
在 Qt Widgets 体系中,QWidget 不能简单理解为"窗口",它更准确地说是很多图形化界面组件的共同基础类型。也就是说,按钮、标签、输入框等界面元素虽然最终呈现出来的效果不同,但它们都需要具备一些共同能力,比如能够显示在界面上、拥有位置和大小、可以被代码创建和控制等。因此,Qt 将这些图形化界面组件共有的能力抽象到了 QWidget 中,而具体的按钮、标签、输入框等控件,则是在 QWidget 的基础上进一步扩展出来的。
从最终显示效果来看,一个 QWidget 或其子类对象,通常可以先理解为界面上的一块可显示、可控制的区域 。不同的子类会在这块区域上呈现出不同的外观和功能。比如 QPushButton 最终呈现出来的是一个按钮,QLabel 可以用来显示文本或图片,QLineEdit 则可以用来接收用户输入。
所以,Widget 并不是特指某一个具体的按钮或者窗口,而是 Qt Widgets 体系中对图形化界面组件的一种基础抽象。对于当前阶段来说,我们可以先这样理解:Qt Widgets Application 就是基于 QWidget 这一套界面组件体系来开发图形化界面程序。
选择项目类型之后,接下来需要设置项目名称,并指定项目存放的目录。继续点击 Next 后,会进入构建系统的选择界面。

理解 Qt 项目的构建系统:qmake、.pro 与 Makefile
所谓构建系统,可以先理解为:用来组织项目编译和链接过程的工具 。如果我们编写的只是一个非常简单的 C++ 程序,比如只有一个 main.cpp 文件,那么我们完全可以手动输入一条编译命令来生成可执行文件。
但是在真实项目中,项目结构通常不会这么简单。一个项目往往会包含多个源文件、多个头文件,还可能依赖额外的库文件。不同文件之间也会存在一定的依赖关系。如果每一次编译都由我们手动输入完整的编译和链接命令,那么整个过程就会非常繁琐,也很容易出错。
在 Linux 阶段的学习中,我们已经接触过 Makefile。Makefile 本质上就是用来描述编译规则和文件依赖关系 的文件。make 工具会读取 Makefile,并根据其中的规则执行对应的编译和链接命令,最终生成可执行文件。
而在 Qt 项目中,如果我们选择使用 qmake ,那么 qmake 会在 Makefile 前面多做一层工作。qmake 会读取项目中的 .pro 文件,并根据 .pro 文件中记录的源文件、头文件、Qt 模块、链接库等信息生成 Makefile。随后,make 工具再根据 Makefile 中的规则完成真正的编译和链接过程。
所以,qmake 的基本流程可以简单理解为:
text
.pro 项目文件
↓ qmake 解析
Makefile
↓ make 执行
编译、链接
↓
可执行文件
这里的 .pro 文件可以理解为 qmake 使用的项目描述文件。它本身并不是 C++ 源代码,也不会被直接编译成可执行程序,而是用来告诉 qmake:当前这个 Qt 项目由哪些文件组成、需要使用哪些 Qt 模块、最终要生成什么类型的程序。
比如一个简单的 .pro 文件中,通常会看到类似这样的内容:
pro
QT += core gui widgets
CONFIG += c++11
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
其中:
text
QT += core gui widgets
表示当前项目需要使用 Qt 的哪些模块。比如 widgets 就表示项目会使用 Qt Widgets 相关功能。
text
SOURCES += main.cpp widget.cpp
表示参与编译的 C++ 源文件有哪些。
text
HEADERS += widget.h
表示项目中包含哪些头文件。
text
FORMS += widget.ui
表示项目中包含哪些界面设计文件。这个 .ui 文件通常是通过 Qt Designer 拖拽界面生成的,后续会由 Qt 工具转换成 C++ 代码参与编译。
所以,.pro 文件的作用可以简单理解为:它不是直接描述"程序怎么运行",而是描述"这个项目应该如何被构建"。 qmake 会读取 .pro 文件中的这些信息,然后生成对应的 Makefile。随后,make 工具再根据 Makefile 中的规则执行真正的编译和链接过程,最终得到可执行文件。
也就是说,整个关系可以理解为:
text
.pro 文件:描述 Qt 项目的组成和构建需求
qmake:读取 .pro 文件,生成 Makefile
Makefile:描述具体的编译、链接规则
make:执行 Makefile 中的规则
最终生成可执行文件
因此,.pro 文件本质上就是 qmake 和整个 Qt 项目之间的"配置说明书"。Qt Creator 表面上帮我们完成了项目构建,但底层仍然是根据 .pro 文件中的信息,配合 qmake 和 make 来完成整个编译流程。Qt 官方文档也把 .pro 项目文件描述为 qmake 用来生成 Makefile 的项目信息来源,其中通常包含源文件、头文件、配置项、额外库和 include 路径等内容。
除了 qmake 之外,Qt 项目也可以使用 CMake。CMake 是一种更通用的跨平台构建工具,现在很多 C++ 项目都会使用它。不过在当前入门阶段,我们先选择 qmake 即可,因为 qmake 和 Qt 项目的结合比较直接,也更方便我们先理解 Qt 项目的基本构建流程。
创建项目之后,我们也可以在项目对应的构建目录中看到生成出来的 Makefile 文件。这样就能说明,Qt Creator 并不是凭空完成编译的,而是通过 qmake、Makefile、make 这些构建工具,帮我们把整个编译流程组织了起来。

选择界面基类,并理解 Form File 的作用
选择构建系统之后,接下来会进入一个选择基类的页面。这里所谓的"基类",指的是 Qt Creator 接下来要为我们生成的初始界面类,应该继承自哪一个 Qt 类。

前面我们已经知道,一个 Qt Widgets 图形化程序运行之后,通常会得到一个图形化界面。这个界面中会包含窗口、按钮、标签、输入框等界面元素。从 Qt 的角度来看,这些界面元素并不是简单的图片,而是一块块可以显示、可以被代码控制的界面区域。它们都可以通过对应的类来描述。
而这些界面类之间并不是完全孤立的。很多界面元素虽然最终呈现出来的效果不同,但它们都具有一些共同特征,比如位置、大小、显示状态、是否可用等。Qt 将这些界面组件共有的基础能力抽象到了一个基础类中,这个基础类就是 QWidget。
因此,QWidget 可以先理解为 Qt Widgets 体系中很多界面组件的共同基础类型。具体的按钮、标签、输入框等控件,都是在这个基础上进一步扩展出来的,拥有各自特定的外观和功能。
在这个基类选择页面中,如果我们选择 Widget ,那么 Qt Creator 会为我们生成一个继承自 QWidget 的自定义界面类。这个类可以作为一个普通的窗口使用,也可以作为承载其他控件的基础界面区域,比较适合当前入门阶段学习。
除了 Widget 之外,这里还可以看到 MainWindow 和 Dialog 两个选项。它们本质上也属于 Qt Widgets 体系,只不过是在 QWidget 的基础上进一步封装出来的特殊窗口类型。
在 Qt Widgets 体系中,QWidget 可以理解为很多可视化界面组件的共同基础类。它抽象出了界面组件共有的一些基础能力,比如位置、大小、显示状态以及被代码控制的能力。而 QMainWindow 和 QDialog 则是在 QWidget 的基础上进一步封装出来的特殊窗口类型。它们本质上也属于 Widget,只不过已经面向特定使用场景提供了更多默认结构和行为:QMainWindow 更适合作为应用程序主窗口,QDialog 更适合作为对话框窗口。
所以,在当前这个最基础的 Qt 入门程序中,我们先选择 Widget 即可。这样可以让我们先从最基础的 QWidget 开始理解 Qt 图形化界面的构建方式,后面再逐步认识 QMainWindow 和 QDialog 这些更具体的窗口类型。
在选择基类的页面中,我们还会看到一个 Form file 选项。这里的 Form file 对应的就是 Qt 项目中的 .ui 文件。
前面我们已经知道,一个 Qt 图形化项目最终运行后,会得到一个可视化界面。而构建这个图形化界面,大致可以有两种方式。
第一种方式,就是我们比较熟悉的 通过 C++ 代码来构建界面。也就是说,我们在代码中创建窗口、按钮、标签等控件对象,设置它们的属性,并按照一定的关系将这些对象组织起来。然后经过项目构建,生成可执行文件,最终运行程序得到对应的图形化界面。
第二种方式,则是通过一种更加直观的 可视化编辑方式 来设计界面。Qt 提供了 Qt Designer 这样的可视化界面设计工具,我们可以像"搭积木"一样,通过鼠标拖拽控件,在窗口中摆放按钮、标签、输入框等界面元素,并设置它们的位置、大小以及相关属性。这样我们在编辑过程中,就可以比较直观地看到界面最终大概会呈现出什么效果。
而这里的 .ui 文件,就是用来保存这种可视化编辑结果 的文件。也就是说,我们在 Qt Designer 中通过拖拽和设置属性设计出来的界面,并不是凭空消失的,而是会被记录到 .ui 文件中。这个文件本质上描述了当前界面中有哪些控件、控件之间是什么关系、控件的名称是什么、大小和位置如何设置等信息。
因此,Form file 可以先理解为:Qt 为可视化界面编辑准备的界面描述文件 。它保存的是我们通过 Qt Designer 编辑出来的界面结构。后续项目构建时,Qt 会再通过对应的工具将 .ui 文件转换成 C++ 代码可以使用的形式,最终参与程序的编译和运行。

这里需要注意,.ui 文件并不是在我们每一次拖拽控件、修改属性时,都立刻同步生成最终文件内容。比如我们把一个按钮拖拽到窗口中的某个位置,Qt Designer 会先在编辑器内部更新当前界面的状态,让我们能够直观看到界面的变化。
如果我们继续调整按钮的位置、修改按钮的文字,甚至撤销刚才的操作,这些变化都会先体现在 Qt Designer 当前维护的界面编辑结果中,而没有必要每一步都立即写入 .ui 文件。否则每一次拖拽、修改、撤销都要同步修改文件内容,反而会让整个过程变得复杂。
更准确地说,Qt Designer 会根据我们的可视化编辑操作维护当前界面的结构信息;当我们保存时,才会将当前界面的编辑结果写入 .ui 文件中。 因此,.ui 文件可以理解为对当前可视化界面设计结果的一次保存,它记录了界面中有哪些控件、控件的位置和大小、控件的属性以及控件之间的关系等信息。
至此,一个最基础的 Qt Widgets 项目所需要的核心配置已经完成。后续页面暂时不需要进行额外修改,我们保持默认选项,继续点击 Next ,最后点击 Finish 完成项目创建即可。
Qt Widgets 项目骨架与核心文件分析
项目骨架与 widget.h 文件分析
根据上文,我们已经知道了如何创建一个基本的 Qt Widgets 项目。项目创建完成之后,我们可以看到,Qt Creator 已经根据我们前面选择的项目模板,为我们生成了一个最小可运行程序框架。。
在这个项目中,首先会包含一个带有 main 函数的 main.cpp 源文件。除此之外,Qt Creator 还会为我们生成一个自定义的 Widget 类,其中 widget.h 用来存放这个类的声明,widget.cpp 用来存放这个类的具体实现。如果前面勾选了 Form file ,项目中还会生成一个 widget.ui 文件,用来保存可视化界面的编辑结果。
也就是说,在我们还没有正式手写完整代码之前,Qt Creator 就已经帮我们准备好了一个最基础的 Qt 程序骨架,并且在这些文件中自动生成了一部分基础代码。
从这里我们也可以进一步理解前面博客中提到的"框架"概念。所谓框架,并不是让我们完全从零开始随意编写程序,而是已经提前为程序准备好了一套基本结构和开发流程。对于 Qt 程序来说,项目创建完成后,基础文件、基础类以及最基本的程序启动流程都已经被生成出来了。
因此,我们后续要做的事情,并不是从零开始搭建整个程序,而是在 Qt Creator 已经生成好的项目框架基础上,继续补充和编写自己的界面代码以及业务逻辑。也就是说,Qt 已经帮我们搭好了程序的基本骨架,而我们需要做的,就是在这个骨架中填充自己的内容。
首先,我们先来看 widget.h 文件。这个文件中声明了 Qt Creator 为我们生成的自定义界面类 Widget。
cpp
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget() override;
private:
Ui::Widget *ui;
};
从类的定义中可以看到,Widget 类继承自 QWidget 类:
cpp
class Widget : public QWidget
这里体现的是 C++ 中的继承关系 。也就是说,我们自己定义的 Widget 类,是在 Qt 提供的 QWidget 类基础上扩展出来的。由于 QWidget 本身已经封装了图形化界面组件的一些基础能力,比如位置、大小、显示以及被代码控制等能力,因此 Widget 继承 QWidget 之后,也就具备了作为一个图形化界面组件存在的基础条件。
这里需要注意区分两个概念:继承关系 和所属关系。
继承关系是类与类之间的关系,解决的是"这个类具备什么能力"的问题。例如 Widget 继承自 QWidget,所以它具备了 QWidget 提供的基础界面能力。
而所属关系是对象与对象之间的关系,解决的是"这个对象显示在哪里、属于谁的一部分"的问题。例如后续如果我们在窗口中创建一个按钮,那么这个按钮对象可以属于当前窗口对象,并显示在这个窗口内部,成为窗口界面的一部分。
接下来再看构造函数:
cpp
explicit Widget(QWidget *parent = nullptr);
这里的 parent 是父对象指针 。父类指的是 Widget 继承的 QWidget;而父对象指的是当前这个界面对象属于哪一个具体的界面对象。如果当前 Widget 是一个独立窗口,那么这个 parent 通常就是 nullptr。
所以这里可以简单理解为:创建一个 Widget 对象时,可以指定它的父对象;如果没有指定父对象,它就可以作为一个独立的窗口存在。
类中还包含一个析构函数:
cpp
~Widget() override;
析构函数用于对象销毁时进行资源清理。至于这里的 override,可以先理解为:它表示当前析构函数是在重写父类中的虚函数,这样写可以让编译器帮助我们检查函数重写是否正确。
除此之外,在类中我们还会看到一个特殊的宏:
cpp
Q_OBJECT
Q_OBJECT 不是普通的成员变量,也不是普通的成员函数,而是 Qt 提供的一个特殊宏。当前阶段可以先把它理解为 Qt 为这个类添加的一个辅助标记,表示这个类需要使用 Qt 提供的一些额外机制。
这里暂时不需要展开它的底层原理,只需要知道:这是 Qt Creator 自动生成的代码,先保留即可,不要随意删除。 后续学习信号和槽时,我们还会再次接触到它。
最后还有一个成员变量:
cpp
Ui::Widget *ui;
这个 ui 指针和前面生成的 widget.ui 文件有关。由于我们在创建项目时勾选了 Form file,所以 Qt Creator 会生成一个 .ui 文件来保存可视化界面的编辑结果。而这里的 ui 指针,就是后续 C++ 代码和这个可视化界面文件之间建立联系的重要成员。这个部分后面分析 widget.cpp 时会继续展开。
因此,widget.h 文件的核心作用可以先理解为:声明我们自己的界面类 Widget,并让它继承 Qt 提供的 QWidget,从而具备图形化界面组件的基础能力。
widget.cpp 文件分析:Widget 对象的初始化与资源释放
看完 widget.h 文件之后,接下来我们再来看 widget.cpp 文件。这个文件中存放的是 Qt Creator 为我们生成的自定义界面类 Widget 的具体实现。
在 widget.cpp 中,我们首先会看到 Widget 类的构造函数:
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
这里需要分成几个部分来看。
首先是初始化列表中的这一句:
cpp
QWidget(parent)
由于我们自己定义的 Widget 类继承自 QWidget:
cpp
class Widget : public QWidget
所以在创建 Widget 对象时,需要先调用父类 QWidget 的构造函数,先把继承自 QWidget 的那一部分初始化好。也就是说,QWidget(parent) 首先体现的是 C++ 中的继承关系:子类对象在构造时,需要先构造父类部分,然后再构造子类自己新增的部分。
不过这里还需要注意,parent 这个参数并不是"父类指针",而是父对象指针。
父类指的是 Widget 继承的 QWidget,这是类与类之间的继承关系;而父对象指的是当前这个 Widget 对象属于哪一个具体的界面对象,这是对象与对象之间的所属关系。
所以,QWidget(parent) 可以从两个角度理解:
text
第一,它调用了父类 QWidget 的构造函数,初始化当前 Widget 对象中继承来的 QWidget 部分;
第二,它把 parent 这个父对象指针传给 QWidget,用来告诉 Qt 当前这个界面对象是否属于某个父界面对象。
如果 parent 为 nullptr,说明当前这个 Widget 没有父对象,它通常就可以作为一个独立窗口存在;如果传入了某个具体的父对象,那么当前这个 Widget 就会成为该父对象的一部分。从图形化界面的呈现效果来看,它通常会显示在父对象对应的界面区域内部,而不是单独作为一个窗口出现。这个所属关系后续在学习 Qt 对象树时会进一步展开,这里先建立一个基本认识即可。
接下来是这一句:
cpp
ui(new Ui::Widget)
这里是在创建一个 Ui::Widget 对象,并让成员变量 ui 指向它。前面我们已经知道,如果创建项目时勾选了 Form file ,Qt Creator 会生成一个 .ui 文件,用来保存可视化界面的编辑结果。而 Ui::Widget 这个类型,正是和这个 .ui 文件转换后的内容有关。
再来看构造函数体中的代码:
cpp
ui->setupUi(this);
这里的 this 指针指向当前正在构造的 Widget 对象。setupUi(this) 的作用,可以先理解为:把 .ui 文件中保存的界面结构设置到当前这个 Widget 对象上。
也就是说,前面的 QWidget(parent) 主要负责初始化当前对象作为一个 Qt 界面组件的基础部分,并建立父对象关系;而 ui->setupUi(this) 则负责把可视化界面文件中设计好的内容应用到当前窗口对象上。
所以这两部分的职责可以简单区分为:
text
QWidget(parent):
初始化父类 QWidget 部分,并根据 parent 建立父对象所属关系。
ui->setupUi(this):
把 .ui 文件中设计好的界面内容设置到当前 Widget 对象上。
最后再来看析构函数:
cpp
Widget::~Widget()
{
delete ui;
}
由于前面在构造函数中通过 new Ui::Widget 创建了一个 ui 对象,因此在 Widget 对象销毁时,需要在析构函数中通过 delete ui 释放这部分资源,避免内存泄漏。
因此,widget.cpp 文件的核心作用可以先理解为:完成自定义界面类 Widget 的具体初始化和资源清理。构造函数负责初始化父类部分、创建 ui 对象,并将 .ui 文件描述的界面内容设置到当前窗口上;析构函数则负责释放 ui 对象占用的资源。
widget.ui 文件分析:Qt Designer 与 XML 界面描述
接下来我们再来看 widget.ui 文件。
当我们点击 widget.ui 文件时,Qt Creator 会自动打开一个可视化界面编辑界面,也就是前面提到的 Qt Designer。Qt Designer 可以理解为 Qt 提供的一个图形化界面编辑工具,它允许我们通过鼠标拖拽的方式来设计界面。
在这个界面中,我们可以向窗口中添加按钮、标签、输入框等控件,也可以通过可视化操作来调整控件的位置、大小以及相关属性。也就是说,除了通过 C++ 代码手动创建和组织控件之外,Qt 还提供了一种更加直观的方式,让我们可以像"搭积木"一样设计最终想要呈现的图形化界面。
当我们保存编辑结果时,Qt Designer 会将当前界面的结构信息保存到 .ui 文件中。这里需要注意,.ui 文件保存的并不是程序运行后的界面截图,也不是 C++ 源代码,而是当前界面设计结果对应的结构描述信息。
如果我们以源码形式打开 widget.ui 文件,就会发现它的内容并不是 C++ 代码,而是一种 XML 格式的文本内容。提到 XML,很多读者可能还不太熟悉,但是大家应该都听说过 HTML。XML 和 HTML 有一个相似点:它们都是通过标签来描述内容结构的语言。
不过二者的用途并不相同。HTML 主要用于描述网页页面结构,而 XML 更偏向于描述通用的结构化数据。对于 Qt 的 .ui 文件来说,它就是通过 XML 标签来描述当前界面中有哪些控件、控件的名称是什么、控件的位置和大小如何设置,以及控件之间存在什么样的层级关系。
这里可以简单补充一下 XML 的基本形式。XML 是一种标签化语言,它通常通过一组一组的标签来描述数据结构。一个标签一般会有开始标签和结束标签,中间包裹的内容就是这个标签所描述的数据。
例如:
xml
<string>按钮</string>
这里:
text
<string> 是开始标签
</string> 是结束标签
按钮 是两个标签之间保存的内容
所以这段 XML 的含义就是:这里有一个字符串内容,它的值是"按钮"。
再比如:
xml
<property name="text">
<string>按钮</string>
</property>
这里外层的:
xml
<property name="text">
表示当前描述的是一个名为 text 的属性,而里面的:
xml
<string>按钮</string>
表示这个属性的值是字符串"按钮"。
因此,这段内容可以理解为:
当前控件有一个
text属性,这个属性的值是"按钮"。
放到 Qt 的 .ui 文件中,它就表示:某个控件上显示的文字是"按钮"。
对于一个 .ui 文件中可能会出现类似这样的内容:
xml
<widget class="QWidget" name="Widget">
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>按钮</string>
</property>
</widget>
</widget>
这段内容大致可以理解为:当前界面中有一个 QWidget,它里面包含了一个 QPushButton 按钮,并且这个按钮显示的文本是"按钮"。
所以,widget.ui 文件可以先理解为:Qt Designer 用来保存可视化界面设计结果的文件,它通过 XML 格式描述了当前界面的结构以及控件的相关属性。
后续项目构建时,Qt 会再通过对应的工具将 .ui 文件转换成 C++ 代码可以使用的形式,最终参与程序的编译和运行。当前阶段我们不需要深入展开这个转换过程,只需要先建立一个基本认识:.ui 文件负责保存界面设计结果,而 C++ 代码负责在程序运行时使用这些界面内容。
两种方式实现 Hello World 图形界面
使用 Qt Designer 实现 Hello World 界面
在对 Qt Creator 为我们生成的项目目录结构有了基本了解之后,接下来我们就可以开始实现一个最简单的 Qt 程序。
这次程序的目标非常简单:运行程序后生成一个窗口,并且在窗口中显示一个文本标签,标签内容为 Hello World。
如果从图形化界面的角度来分析,这个界面的结构其实并不复杂。它只包含两个核心元素:一个窗口,以及窗口中的一个标签控件。也就是说,这个界面的基本结构可以理解为:
text
窗口
└── 标签 QLabel
由于这个界面比较简单,所以我们可以先采用一种最直观的方式来完成,也就是通过 Qt Designer 进行可视化编辑。
具体来说,我们可以打开 widget.ui 文件,进入 Qt Designer 的可视化编辑界面,然后在左侧控件栏中找到 Label 标签控件,并将其拖拽到窗口中。这里的 Label 对应 Qt 中的 QLabel 类,主要用于在界面中显示文本或图片。
拖拽完成之后,我们选中这个标签控件,然后修改它显示的文本内容,将其设置为 Hello World。最后保存当前界面的编辑结果。
当我们保存之后,Qt Designer 会将当前可视化编辑的结果写入 widget.ui 文件中。这里需要再次强调:.ui 文件本身并不是 C++ 代码,而是一个使用 XML 格式描述界面结构的文件。它记录了当前界面中有哪些控件、控件的名称是什么、控件的位置和大小如何设置,以及控件的相关属性是什么。
但是,C++ 编译器并不能直接编译 .ui 文件。因此,在 Qt 项目的构建过程中,Qt 会通过一个专门的工具 uic ,将 widget.ui 文件转换成 C++ 代码可以使用的形式,通常会生成一个对应的头文件,例如 ui_widget.h。
而我们前面在 widget.cpp 构造函数中看到的:
cpp
ui->setupUi(this);
就和这个过程有关。
这里的 this 指向当前正在构造的 Widget 窗口对象,而 setupUi(this) 的作用,就是将 .ui 文件中描述的界面结构设置到当前这个 Widget 对象上。也就是说,它会根据可视化编辑结果,在当前窗口中创建对应的控件,并设置这些控件的属性,例如标签的位置、大小以及显示文本等。
因此,整个过程可以简单理解为:
text
在 Qt Designer 中拖拽 Label,并设置文本内容
↓
保存为 widget.ui 文件
↓
构建时由 uic 工具转换成 C++ 可以使用的代码
↓
运行时通过 setupUi(this) 设置到当前 Widget 窗口对象上
↓
最终显示出带有 Hello World 标签的窗口
这样,一个最简单的 Qt 图形化界面程序就实现完成了。
Qt Designer 的核心作用不只是把控件放到窗口上,它还提供了一个属性编辑区域。通常在界面右下角,我们可以看到当前选中控件的属性列表。

比如选中一个 QLabel 标签后,就可以在属性栏中看到它的很多属性,例如:
text
objectName:控件对象名称
text:显示的文本内容
geometry:位置和大小
font:字体
enabled:是否可用
visible:是否显示
这些属性本质上就是控件对象的一些状态。我们在 Qt Designer 中修改这些属性时,Qt Designer 会立刻在可视化界面中展示修改后的效果。比如修改 QLabel 的 text 属性,标签显示的文字就会发生变化;修改控件的大小和位置,界面中的控件位置也会随之改变。
所以,Qt Designer 可以先理解为一个可视化的界面结构与属性编辑工具 。它不仅能通过拖拽方式添加控件,还能直接修改控件的初始属性,并将这些编辑结果一起保存到 .ui 文件中。
这也进一步说明了 .ui 文件保存的内容并不只是"有哪些控件",还包括这些控件的初始属性信息。比如控件的名称、文本、位置、大小等,都会作为界面结构的一部分被记录下来。后续当程序运行并执行 setupUi(this) 时,这些初始属性也会被一起设置到对应的控件对象上。
通过 C++ 代码手动创建 Hello World 界面
除了通过 Qt Designer 可视化编辑界面的方式之外,我们也可以直接通过编写 C++ 代码来实现同样的效果。
根据前面的分析,我们现在要实现的需求非常简单:生成一个窗口,并且在窗口中显示一个标签,标签内容为 Hello World。
如果从图形化界面的结构来分析,这个界面只包含两个核心元素:
text
窗口 Widget
└── 标签 QLabel
也就是说,标签是窗口中的一部分。因此,当我们通过 C++ 代码创建标签对象时,就需要把这个标签对象和窗口对象建立关联。具体来说,就是让这个标签对象的父对象指向当前窗口对象。这样一来,标签就会作为当前窗口界面的一部分,显示在窗口的区域中。
这里的标签类,Qt 已经提前帮我们封装好了,对应的类名就是 QLabel。如果要在代码中使用 QLabel,首先需要包含它对应的头文件:
cpp
#include <QLabel>
然后,我们可以在 Widget 类的构造函数中创建一个 QLabel 对象:
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLabel *label = new QLabel(this);
label->setText("Hello World");
}
这里的:
cpp
QLabel *label = new QLabel(this);
表示创建一个 QLabel 标签对象。其中传入的 this 指针,指向当前正在构造的 Widget 窗口对象。也就是说,我们将当前窗口对象作为这个标签对象的父对象。
这样做之后,从图形化界面的呈现效果来看,这个标签就会显示在当前窗口内部,而不是作为一个独立的窗口存在。
接下来这一句:
cpp
label->setText("Hello World");
表示设置标签对象显示的文本内容。这里需要注意,setText() 并不是像 cout 那样向控制台打印内容,而是修改 QLabel 标签控件显示在界面上的文字。
也就是说,QLabel 负责提供一个可以显示文本的界面区域,而 setText() 则负责设置这个区域中具体显示什么文本。
这里还可以顺便注意一个细节:Qt 中很多和文本相关的接口都会使用 QString。QString 是 Qt 自己提供的字符串类,主要用于处理 Qt 程序中的文本内容。
不过在这里,我们直接传入 "Hello World" 这样的字符串字面量时,Qt 可以完成相应的隐式转换,所以代码可以正常使用。
这也说明,Qt 除了提供 QWidget、QLabel 这类图形化界面相关的类之外,也提供了自己的一套常用数据类型。比如后续我们经常会看到 QString 这样的类型。当前阶段我们不需要深入比较 Qt 类型和 C++ 标准库类型之间的差异,只需要先建立一个基本认识:在 Qt 程序中,很多以 Q 开头的类,通常都是 Qt 框架自己封装好的类型。
因此,通过 C++ 代码实现这个界面的核心流程可以总结为:
text
包含 QLabel 头文件
↓
在 Widget 构造函数中创建 QLabel 对象
↓
将当前 Widget 对象 this 作为 QLabel 的父对象
↓
调用 setText() 设置标签显示内容
↓
运行程序后,窗口中显示 Hello World 标签
这样,我们就没有通过 Qt Designer 拖拽控件,而是完全通过 C++ 代码手动创建了一个标签对象,并将它显示到了窗口中。也就是说,Qt Designer 可视化编辑和 C++ 代码手动创建,本质上都是在构建同一个图形化界面,只是实现方式不同。
Qt Designer 与 C++ 代码的协同关系
通过前面的内容,我们已经认识到,生成一个 Qt 图形化界面大致可以有两种方式:一种是直接编写 C++ 代码来创建和组织控件对象;另一种是通过 Qt Designer 进行可视化编辑,通过拖拽控件的方式设计界面,并将编辑结果保存到 .ui 文件中。
不过这里需要注意,这两种方式并不是完全独立、互不相关的。并不是说使用了 Qt Designer 之后,就完全不需要编写 C++ 代码了。更准确地说,它们之间是一种协同关系。
Qt Designer 更适合用来完成界面的初步搭建,比如在窗口中放置哪些控件、控件的初始位置和大小是多少、控件一开始显示什么文本等。这些可视化编辑结果会被保存到 .ui 文件中。
而在项目构建过程中,Qt 会通过 uic 工具将 .ui 文件转换成 C++ 代码可以使用的形式,通常会生成类似 ui_widget.h 这样的头文件。随后,程序运行时,Widget 构造函数中的:
cpp
ui->setupUi(this);
会根据转换后的界面代码,把 .ui 文件中描述的界面结构设置到当前这个 Widget 窗口对象上。
也就是说,.ui 文件描述的是界面的初始结构 ,而 setupUi(this) 的作用就是将这个初始界面装配到当前窗口对象中。
但是,setupUi(this) 执行完成之后,我们仍然可以继续通过 C++ 代码修改界面。例如:
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->setText("新的文本");
}
这里 setupUi(this) 会先根据 .ui 文件设置界面内容,而后面的:
cpp
ui->label->setText("新的文本");
又会继续修改标签显示的文本。由于这句代码是在 setupUi(this) 之后执行的,所以最终界面中显示的文本会以后面这句 C++ 代码的设置结果为准。
因此,与其说 C++ 代码的"优先级更高",不如说:后执行的代码会覆盖前面已经设置好的界面属性。
所以,Qt Designer 和 C++ 代码之间不是替代关系,而是配合关系:
text
Qt Designer:
负责搭建界面的初始结构,例如控件摆放、大小、初始属性等。
C++ 代码:
负责进一步修改界面属性,并编写按钮点击、输入处理等交互逻辑和业务逻辑。
因此,可以这样理解:.ui 文件负责描述初始界面,setupUi(this) 负责把初始界面设置到窗口对象上,而后续 C++ 代码可以继续修改界面并补充交互逻辑。
这也解释了为什么 Qt Designer 只是帮助我们更直观地完成界面设计,而不是完全替代 C++ 代码。对于一个真正的 Qt 程序来说,可视化界面只是程序的一部分,后续大量的交互行为和业务逻辑,仍然需要我们通过 C++ 代码来完成。
从 QLabel 释放实验到 Qt 对象树机制
为什么 new 出来的 QLabel 不需要手动 delete?
根据上文,我们已经认识了两种生成简单图形化界面的方式:一种是通过 Qt Designer 进行可视化编辑,另一种是通过 C++ 代码 手动创建控件对象。
不过,当我们仔细阅读 C++ 代码时,可能会注意到一个细节:
cpp
QLabel *label = new QLabel(this);
这里的 QLabel 对象是通过 new 创建出来的,也就是说,它是在堆区申请出来的对象。
对于一个 C/C++ 程序员来说,看到 new 这个关键字时,一定要保持敏感。因为只要涉及堆区空间申请,就需要考虑一个非常重要的问题:如果申请出来的对象没有被正确释放,就可能造成内存泄漏。
按照普通 C++ 的思路,如果我们通过 new 创建了一个对象,那么通常需要在合适的位置手动调用 delete 来释放它:
cpp
QLabel *label = new QLabel(this);
// ...
delete label;
但是在前面的 Qt 代码中,我们只是通过 new 创建了一个 QLabel 对象,却并没有手动调用 delete 去释放它。那么这里是否会造成内存泄漏呢?
为了验证这个问题,我们可以做一个简单实验。
这里我自定义一个 MyLabel 类,让它继承自 QLabel。由于 MyLabel 继承了 QLabel,所以它会拥有 QLabel 作为标签控件的基础能力。也就是说,MyLabel 对象本质上仍然可以作为一个标签控件显示在窗口中。
同时,根据 C++ 的继承机制可知,创建一个 MyLabel 对象时,会先调用父类 QLabel 的构造函数,初始化父类部分;然后再执行 MyLabel 自己的构造逻辑。而当对象销毁时,则会调用 MyLabel 的析构函数。
为了观察这个对象是否真的被销毁,我们可以在 MyLabel 的析构函数中打印一条信息:
cpp
#include <QLabel>
#include <QDebug>
class MyLabel : public QLabel
{
public:
explicit MyLabel(QWidget *parent = nullptr)
: QLabel(parent)
{
}
~MyLabel()
{
std::cout << "MyLabel 析构函数被调用"<<std::endl;
}
};
接着,我们在 Widget 类的构造函数中创建这个标签对象:
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
MyLabel *label = new MyLabel(this);
label->setText("Hello World");
}
这里需要注意:
cpp
MyLabel *label = new MyLabel(this);
这行代码中的 this 指向当前正在构造的 Widget 窗口对象。也就是说,我们创建出来的 MyLabel 标签对象,并不是一个完全独立的界面对象,而是将当前 Widget 窗口对象设置成了它的父对象。
从图形化界面的呈现效果来看,这个标签会显示在当前窗口内部,成为当前窗口界面的一部分。
运行程序后,我们可以看到窗口中正常显示了 Hello World 文本。接着,当我们关闭窗口时,程序输出窗口中会打印出 MyLabel 析构函数中的信息。

这个现象说明:虽然我们没有手动调用 delete label,但是 MyLabel 对象最终确实被销毁了,因此这里并没有发生内存泄漏。
不过这里一定要注意:这并不是 C++ 语言本身自动帮我们回收了堆区对象,而是 Qt 的父子对象机制在发挥作用。
当我们创建 MyLabel 对象时,将 this 作为父对象传入:
cpp
MyLabel *label = new MyLabel(this);
这就表示当前 MyLabel 对象属于当前 Widget 窗口对象。后续当 Widget 对象销毁时,Qt 会自动销毁它所拥有的子对象,因此 MyLabel 的析构函数也会被调用。
所以,这个实验可以证明一个现象:
在 Qt 中,如果一个通过
new创建出来的对象指定了父对象,那么当父对象销毁时,Qt 会负责销毁对应的子对象。
而这个机制,正是接下来要讲的 Qt 对象树。
理解 Qt 对象树:父子关系与自动释放机制
我们知道,一个 Qt 程序运行之后,最终会生成一个可视化的图形界面。而这个图形界面通常并不是由某一个单独的界面元素构成的,而是由窗口、按钮、标签、输入框等多个界面元素共同组成的。
从最终显示效果来看,这些界面元素只是分布在窗口中的不同位置,好像彼此之间没有明显联系。但实际上,它们之间是存在组织关系的。
比如一个窗口中包含一个标签、一个按钮、一个输入框,那么这些控件并不是完全独立存在的。它们在逻辑上属于这个窗口,在界面呈现上也会显示在窗口内部的某一块区域中。也就是说,界面元素之间存在一种父子从属关系。
这里也可以结合我们日常使用图形化客户端程序的经验来理解。比如当我们点击窗口右上角的关闭按钮时,通常期望的是整个窗口都被关闭,而不是窗口消失了,但窗口里面的按钮、标签、输入框等界面元素还单独残留在屏幕上。
这说明,窗口和窗口内部的控件并不是完全独立的关系。窗口可以先理解为一个承载其他控件的容器,而按钮、标签、输入框等控件则是这个窗口内部的组成部分。这个关系不仅体现在程序内部的对象组织上,也体现在最终生成的可视化界面当中。
也就是说,父对象和子对象之间的关系,并不是单纯为了方便写代码,而是和图形化界面的实际呈现方式对应起来的:子对象属于父对象,并且通常显示在父对象对应的界面区域内部。
前面我们已经知道,Qt 中的界面元素通常都是通过类来描述的。当程序中需要某个界面元素时,我们就可以创建对应类的对象。但是,仅仅创建出对象还不够,我们还需要把这些对象组织起来,让程序知道哪个控件属于哪个窗口,哪个对象应该显示在哪个父对象内部。
这种关系就是通过父对象指针来建立的。比如我们创建一个标签对象时:
cpp
MyLabel *label = new MyLabel(this);
这里传入的 this 指向当前窗口对象。也就是说,我们把当前窗口对象设置成了这个标签对象的父对象。
这样一来,从逻辑关系上看,label 属于当前窗口对象;从图形化界面的显示效果来看,这个标签也会显示在当前窗口的界面区域内部,而不是作为一个独立窗口存在。
当多个对象都通过这种父子关系组织起来时,它们在逻辑上就会形成一种树形结构。一个父对象下面可以有多个子对象,而每个子对象下面又可以继续拥有自己的子对象。由于这种结构和树非常类似,所以我们通常称它为 Qt 对象树。
对象树并不只是用来描述界面元素之间的所属关系,它还和对象的生命周期管理有关。
比如一个窗口内部包含多个子控件,那么当这个窗口对象最终被销毁时,它所拥有的子控件对象也应该一起被释放。否则,虽然窗口已经不存在了,但这些子控件对象仍然残留在堆区内存中,就可能造成资源泄漏。
因此,Qt 的父子对象机制提供了一种自动释放子对象的能力:当父对象被销毁时,Qt 会自动销毁它所拥有的子对象。

这也解释了前面的实验现象:虽然我们通过 new 创建了 MyLabel 对象,并且没有手动调用 delete,但是因为创建对象时指定了父对象 this,所以当当前窗口对象销毁时,Qt 会自动释放这个标签对象,于是 MyLabel 的析构函数也会被调用。
所以,Qt 对象树可以先这样理解:
对象树是 Qt 用来组织对象父子关系的一种机制。对于界面对象来说,它既能描述控件之间的所属关系,也能在父对象销毁时自动释放子对象,从而减少内存泄漏的风险。
使用 qDebug 输出日志,并理解字符串编码问题
这里还可以顺便补充一个细节:在前面的实验中,我为了观察 MyLabel 对象是否真的被销毁,在析构函数中打印了一条信息。虽然使用 std::cout 也可以完成简单输出,但是在 Qt 程序中,更推荐使用 Qt 提供的 qDebug() 进行日志输出。
cpp
#include <QDebug>
~MyLabel()
{
qDebug() << "MyLabel 析构函数被调用";
}
这里需要先区分一下"调试"和"日志"。调试通常是指我们在开发阶段通过断点、单步执行等方式观察程序某一时刻的状态;而日志则是程序在运行过程中主动记录下来的关键信息,用来帮助我们观察程序的执行流程和中间状态。
在实际开发中,很多问题并不一定能够稳定地通过调试复现。比如某些偶现问题、并发时序问题,甚至死锁问题,可能只会在特定运行条件下触发。这个时候,日志就非常重要。相比于调试器断点,日志更适合观察程序在真实运行状态下的行为。尤其是对于死锁、竞态条件、偶现崩溃等问题,单步调试可能会改变程序原本的运行节奏,导致问题无法稳定复现。因此,日志可以帮助我们在不明显干扰程序运行的情况下,还原程序执行现场。
通过在关键位置输出日志,我们可以知道程序执行到了哪一步、某个对象是否被创建或销毁、某个分支是否被执行,从而在问题出现后根据日志内容反推错误发生的位置和原因。
而日志输出的内容通常是字符串,字符串又会涉及编码问题。因为计算机底层保存的并不是"字符本身",而是字符经过某种编码方式转换后的字节序列。不同编码方式下,同一个字符最终对应的底层字节序列可能不同。如果字符串在写入和显示时使用的编码规则不一致,就可能出现乱码问题。
这里可以简单建立一个模型:
text
字符本身
↓ Unicode 分配统一码点
Unicode 码点
↓ 按具体编码方式转换
底层字节序列
比如同一个中文字符,在 Unicode 中会有一个统一的码点,而真正保存到文件或输出到终端时,还需要根据具体编码方式转换成字节序列。常见的编码方式包括 UTF-8、UTF-16、GBK 等。也就是说,Unicode 更像是给字符提供统一编号,而 UTF-8、UTF-16 这类编码方式,则负责把这些字符编号转换成具体的二进制字节。
对于 Qt 项目来说,Qt Creator 中的源文件通常会使用 UTF-8 编码。UTF-8 是目前非常常见的一种变长编码方式,能够表示中文、英文以及其他多种语言字符。虽然我们也可以手动关注源文件编码、控制台编码以及输出环境是否一致,但在 Qt 程序中,使用 qDebug() 通常会更加方便。
原因在于,qDebug() 是 Qt 提供的日志输出接口,它和 Qt 自己的字符串类型以及 Qt Creator 的程序输出窗口配合得更自然。比如后续我们经常会接触到 QString,它并不是简单保存一串原始字节,而是基于 Unicode 文本模型来管理字符串内容。更准确地说,QString 内部使用 UTF-16 编码单元保存文本,因此在处理中文、英文等多语言字符时,会比直接操作 char* 这类原始字节更加稳定。
所以,qDebug() 的优势并不是"永远不会乱码",而是它和 Qt 自己的字符串体系配合更自然。尤其是在输出 QString 等 Qt 字符串类型时,Qt 可以按照自己的文本处理方式进行转换和输出,相比直接输出一段原始 char* 字节数据,更不容易因为编码理解不一致而出现乱码。
当然,如果源文件本身编码不一致,或者传入的是错误编码的 char*、QByteArray 等原始字节数据,仍然可能出现乱码。因此,更稳妥的做法是:在 Qt 项目中尽量统一使用 UTF-8 编码,并优先使用 QString、qDebug() 这类更贴合 Qt 字符串体系的方式进行文本处理和日志输出。
所以,前面的析构函数打印可以改成:
cpp
~MyLabel()
{
qDebug() << "MyLabel 析构函数被调用";
}
这样既符合 Qt 程序的编码习惯,也方便我们在 Qt Creator 的输出窗口中观察对象销毁的时机。

结语
那么这就是本篇文章的全部内容,带你认识编写一个Qt程序,我会持续更新,希望你能够多多关注,如果本文有帮助到你的话,还请三连加关注,你的支持就是我创作的最大动力!

