03_第一个Qt项目:Hello World 的完整拆解

第一个 Qt 项目:Hello World 的完整拆解------从 main 函数到事件循环

引言

在上一篇文章中,我们成功搭建了 Qt 开发环境,点击运行按钮,看到了一个空白的窗口弹出。

但对于一个 C++ 开发者来说,仅仅"让程序跑起来"是远远不够的。

真正值得思考的问题是:

那几行自动生成的代码,到底在背后做了什么?

cpp 复制代码
#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

这段代码只有短短 7 行。但如果你把这几行代码当做一个"黑盒"来使用,你就错过了 Qt 中最核心、最基础的设计思想。

在这一篇中,我们不急着去写复杂的界面,而是把这几行代码彻底拆解清楚。

我们会依次回答以下几个问题:

  1. argcargv 能做什么?------ 命令行参数与 Qt 应用
  2. QApplication 到底是什么?它和 QCoreApplication 有什么区别?
  3. w.show() 真的把窗口"显示"出来了吗?
  4. a.exec() 为什么不会让程序立刻退出?事件循环到底在做什么?
  5. 这个过程中,MOC(元对象编译器) 悄然参与了哪一步?

当你理解了这几行代码的每一层含义之后,你才算真正入了 Qt 的门。

一、从 main 函数的参数说起:命令行应用与 GUI 应用

任何 C++ 程序的入口都是 main 函数。它的两个参数------argc(参数个数)和 argv(参数值数组)------经常被初学者忽略。

但在现实工程中,命令行参数非常重要。例如:

bash 复制代码
# 以调试模式启动
./myapp --debug

# 打开特定文件
./myapp D:/Projects/demo.txt

# 无界面模式(用于自动化测试)
./myapp --headless

Qt 提供了非常优雅的方式来处理这些参数,而这就是 QApplication 构造函数的隐藏能力之一。

默认行为:Qt 自动处理标准命令行选项

当我们构造 QApplication 时,Qt 会自动解析一些标准命令行选项。例如:

命令行参数 作用
-style=windows 强制使用 Windows 风格
-style=fusion 强制使用 Fusion 风格
-reverse 设置布局方向为从右到左
-qmljsdebugger=... 启用 QML 调试

这些参数不需要你写任何额外代码,Qt 在构造 QApplication 时就已经帮你处理好了。

一个容易忽略的细节:Qt 可能会修改 argc 和 argv

QApplication 构造时会检查命令行参数,并处理 Qt 自己认识的参数。

因此,传入 QApplicationargcargv 并不一定会保持原样。

这也是为什么 Qt 官方建议:如果程序自己还需要解析命令行参数,通常应该先构造 QApplication,再使用 QCommandLineParser 处理业务参数。

Qt 还提供 QCommandLineParser 处理自定义参数,这部分会在后续工程化章节介绍。

二、QApplication 和它的兄弟姐妹们

接下来,我们来看 QApplication a(argc, argv); 这一行。

很多人在写这一行代码时,只是把它当做"启动 Qt 的魔法咒语"。但实际上,QApplication 在一个 Qt 程序中承担着非常重要的职责。

QApplication 在做什么?

当构造 QApplication 对象时,Qt 会完成 QWidget 应用所需要的初始化工作,包括:

  1. 初始化应用程序相关的 GUI 环境;
  2. 根据系统设置初始化字体、调色板等;
  3. 初始化 QWidget 相关功能;
  4. 创建并准备应用程序级事件处理环境;
  5. 解析 Qt 支持的部分标准命令行参数;
  6. 管理应用程序中的顶层窗口、剪贴板、鼠标指针等全局 GUI 状态。

真正进入主事件循环,则是在后面的 app.exec() 中完成。

Qt 的三种"应用"类型

实际上,Qt 提供了三个层级不同的 Application 类,它们的继承关系如下:

复制代码
QCoreApplication   (无 GUI,纯命令行/服务)
        ↑
QGuiApplication    (GUI 基础设施,但不提供 QWidget)
        ↑
QApplication       (在 QGuiApplication 基础上增加 QWidget 支持)

这三者的选择非常简单:

类名 适用场景
QCoreApplication 纯控制台应用、后台服务、批处理程序(不需要任何 GUI 功能)。
QGuiApplication 使用 Qt Quick / QML 或纯 OpenGL 构建的应用(不依赖 Qt Widgets 模块)。
QApplication 使用 QWidget、QMainWindow 和传统 Qt 控件的桌面应用。

下面这种组合方式能使用吗?

c++ 复制代码
QCoreApplication app(argc, argv);
QWidget widget;

答案当然是不能。Widget 属于 Qt Widgets 模块,需要 QApplication 提供对应的初始化环境。

因此,对于 QWidget 应用程序,应该使用 QApplication,而不是 QCoreApplication

可以简单理解为:

复制代码
QCoreApplication
    ↓
基础事件循环和应用程序环境

QGuiApplication
    ↓
增加 GUI 基础能力

QApplication
    ↓
增加 QWidget 所需要的能力

生命周期问题:为什么通常把 QApplication 放在栈上?

你可能会想到:

复制代码
QApplication *app = new QApplication(argc, argv);

这种写法并不是绝对错误。

但 Qt 示例以及实际工程中通常直接使用:

复制代码
QApplication app(argc, argv);

原因是 C++ 的 RAII 机制可以自动管理它的生命周期。

main() 结束时:

复制代码
app.exec()
    ↓
事件循环退出
    ↓
main() 返回
    ↓
QApplication 自动析构
    ↓
Qt 清理应用程序相关资源

因此,对于生命周期与 main() 一致的对象,栈对象是更加自然的选择

在 Qt 的官方规范中,QCoreApplication 及其子类必须main 函数中构造,并且在 main 函数返回之前不能析构。栈对象自动满足了这一要求。

三、w.show() 到底做了什么?

很多人想当然地以为:w.show() 就是把窗口"画"到屏幕上。

从逻辑上说,没错。但从实现角度来说,show() 做了远比"绘图"复杂得多的事情。

show() 的核心工作流程如下:

  1. 创建底层窗口句柄 (Windows 上的 HWND,Linux X11 上的 Window)。
  2. 将 Qt 窗口(QWidgetQMainWindow)与操作系统原生窗口关联
  3. show() 会使 QWidget 进入可见状态,并在需要时创建/显示对应的原生窗口资源。
  4. 窗口显示后,Qt 的事件系统会根据需要产生并处理绘制事件 ,最终由 QWidget 的绘制机制完成界面绘制

关键认知:show() 只是把窗口映射到屏幕上,并标记为"需要绘制"。真正的绘制动作,是在事件循环运行之后,由 QPaintEvent 触发的。

也就是说,如果在调用 w.show() 之后立即调用 return 0;(不进入事件循环),程序会直接退出,你甚至看不到窗口闪现------因为它还没来得及处理绘制事件。

对于一个正常的 Qt GUI 应用程序来说,show() 通常需要配合 app.exec() 使用。

show() 负责让窗口进入可见状态,而 exec() 负责进入应用程序的主事件循环,使程序能够持续处理用户输入、窗口系统事件、定时器等事件。

四、事件循环:深入理解 a.exec()

这是 Qt 初学者最难理解、但却最重要的概念。

什么是事件循环?

通俗地说,事件循环是一个无限循环,它不断地从"事件队列"中取出事件,并将其分发给对应的对象进行处理。

它的伪代码逻辑大致如下,以下代码只是帮助理解事件循环的伪代码,并不是 Qt exec() 的实际源码。

cpp 复制代码
int exec()
{
    while (!exitRequested) {
        Event event = waitForNextEvent();
        dispatchEvent(event);
    }

    return exitCode;
}

为什么需要事件循环?

如果没有事件循环,我们的程序会瞬间执行完毕并退出。

有了事件循环,程序会一直运行,等待并处理各种事件,例如鼠标点击、键盘输入、窗口缩放、定时器事件以及网络 I/O 相关事件。

用现实世界的例子来理解

例子一:餐厅服务员

想象一个餐厅服务员:

  • 他站在门口,等待客人进来(等待事件);
  • 客人进来后,他引导客人入座,并记录点单(处理事件);
  • 点单完成后,他又回到门口等待下一位客人(回到循环)。

这个服务员不会因为"处理完一桌客人"就下班。他会持续不断地等待、处理、再等待。

操作系统的事件循环(或者说 Qt 的事件循环)就是这样一个"服务员"。它永远不会主动停止,除非你告诉它"打烊了"(调用 quit())。

例子二:邮件客户端

当你打开 Outlook 或 Foxmail 时:

  • 程序不会运行 1 秒就退出;
  • 它持续运行,等待你点击"收件箱";
  • 它持续运行,等待邮件到达的通知(网络事件);
  • 它持续运行,等待你双击一封邮件。

这种"一直在等着,随时准备响应"的机制,就是事件循环。

事件循环是阻塞的,但不是"死"的

初学者常常会疑惑:a.exec() 阻塞了主线程,那程序为什么还能响应点击和动画?

关键在于:a.exec() 的阻塞是一种"主动等待",而不是"忙等"

  • 忙等while(true) { check(); } ------ 持续占用 CPU,导致 CPU 使用率 100%。
  • 主动等待waitForEvent() ------ 将线程挂起,操作系统在没有事件时不会分配给该线程 CPU 时间片;只有当鼠标移动、键盘敲击或定时器超时等事件发生时,操作系统才会唤醒该线程。

"阻塞"描述的是 main() 这一调用路径没有继续向下执行,并不意味着主线程停止工作。

例如:

c++ 复制代码
return app.exec();

qDebug() << "这里什么时候执行?";

答案是:

exec() 返回之后才会执行。

但在 exec() 内部:

复制代码
主线程
  ↓
事件循环
  ├── 鼠标事件
  ├── 键盘事件
  ├── Paint事件
  ├── Timer事件
  ├── Socket事件
  └── ...

所以:主线程被 exec() 阻塞 ≠ 主线程什么都不干。

这种机制不仅高效,而且让 GUI 程序能够在单线程模式下平滑运行------所有 UI 操作在主线程串行执行,不需要复杂的锁机制,这也是 Qt 要求 UI 操作必须在主线程的根本原因之一。

exec() 什么时候退出?

exec() 会一直运行,直到以下情况发生:

  1. 调用了 QCoreApplication::quit()
  2. 当最后一个可见的顶层窗口关闭时,Qt 默认会根据 quitOnLastWindowClosed 的设置退出应用程序事件循环。;
  3. 用户强制终止进程。

exec() 的返回值是一个 int,代表退出代码。在脚本或自动化测试中,这个退出码可以用来判断程序是正常退出还是异常终止。

五、链接与 MOC:构建过程中看不见的工作

这一节虽然是入门篇,但既然我们第一篇就聊了 Qt 与 C++ 的关系,这里需要提一下构建过程中的幕后工作。

我们在第二篇中提到,使用 CMake 构建时,编译输出中会出现 autogen/moc_compilation.cpp.obj

在 Hello World 中,MOC 参与了吗?

答案是:参与了,但只参与了一点点。

在我们前面使用 Qt Creator 创建的典型 Widgets 项目中,MainWindow 通常包含 Q_OBJECT,因此 CMake 的 AUTOMOC 会在构建过程中调用 MOC。

但需要注意:并不是所有 Qt 程序都必须使用 MOC。

MOC 是针对使用 Qt 元对象扩展的类而存在的,典型判断标志就是类声明中的 Q_OBJECT 等宏

MainWindow 类定义中包含了 Q_OBJECT 宏:

c++ 复制代码
class MainWindow : public QMainWindow
{
    Q_OBJECT
    // ...
};

这个宏告诉 MOC:这个类需要元对象信息。MOC 在预处理阶段读取 mainwindow.h,生成一个额外的 C++ 源文件(例如 moc_mainwindow.cpp),其中包含了 MainWindow 的元对象表(QMetaObject)和信号/槽的索引信息。

但在 Hello World 这个阶段,MainWindow 并没有定义任何自定义信号或槽。为什么还需要 MOC?

因为 QMainWindow 本身从 QObject 继承,而 Q_OBJECT 宏的另一个作用是启用动态属性、运行时类型信息(inherits()metaObject() 等)

有一个需要明确:QObject 本身并不要求你的每一个派生类都写 Q_OBJECT

例如:

c++ 复制代码
class MyObject : public QObject
{
};

这个类仍然是 QObject 派生类。

但是:

c++ 复制代码
class MyObject : public QObject
{
    Q_OBJECT
};

才会让这个派生类本身拥有由 MOC 生成的完整元对象信息,例如:

  • 自己声明的 signals
  • slots
  • properties
  • 自己的元对象信息
  • 动态类型相关能力等

Qt 官方对 MOC 的定位也是:读取包含 Q_OBJECT 的类声明,并生成元对象代码;这些元对象代码用于信号槽、运行时类型信息、动态属性系统等。

这也是为什么 Qt 强调:任何从 QObject 派生的类,只要需要信号槽、属性或运行时类型信息,就必须 在类声明中包含 Q_OBJECT 宏,否则 MOC 将跳过它。

六、对象生命周期:从创建到析构

到这里,我们已经知道:

复制代码
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MainWindow w;
    w.show();

    return app.exec();
}

但还有一个非常重要的问题:

app w 是什么时候创建的?什么时候销毁的?

这其实涉及 C++ 中非常基础、也非常重要的一个概念------对象生命周期

QApplication 和 MainWindow 是什么时候创建的?

程序进入 main() 后,按照代码执行顺序:

c++ 复制代码
QApplication app(argc, argv);

首先创建 QApplication 对象。然后:

c++ 复制代码
MainWindow w;

创建 MainWindow 对象。因此,可以简单理解为:

复制代码
进入 main()
    ↓
创建 QApplication
    ↓
创建 MainWindow
    ↓
显示 MainWindow
    ↓
进入事件循环

此时两个对象都处于存活状态:

复制代码
main()
│
├── QApplication app
│
└── MainWindow w

exec() 并不会销毁这些对象

执行:

c++ 复制代码
return app.exec();

以后,程序进入事件循环。此时:

c++ 复制代码
MainWindow w;

这个对象仍然存在。

app.exec() 并不会创建一个新的 MainWindow,也不会销毁 MainWindow

它只是让当前线程进入事件循环,不断处理:

复制代码
鼠标事件
键盘事件
绘制事件
定时器事件
窗口事件
网络 I/O 事件
......

所以在事件循环运行期间,可以理解为:

复制代码
QApplication app  ────────────────┐
                                  │
MainWindow w    ───────────────┐  │
                               │  │
              app.exec()       │  │
              事件循环运行       │  │
                               │  │
              用户不断操作       │  │
                               │  │

exec() 返回以后发生什么?

当应用程序退出时,事件循环结束:

c++ 复制代码
app.exec()

返回一个整数退出码。例如:

c++ 复制代码
int exitCode = app.exec();
return exitCode;

这时候程序才会继续执行 main() 后面的代码。

对于:

c++ 复制代码
return app.exec();

可以理解成:

c++ 复制代码
int exitCode = app.exec();
return exitCode;

return 执行以后,main() 开始结束。

这时 C++ 会自动销毁 main() 中创建的局部对象。

C++ 对象是按照什么顺序析构的?

这里有一个非常重要的 C++ 规则:

局部对象按照创建顺序构造,按照创建顺序的逆序析构。

我们的代码是:

c++ 复制代码
QApplication app(argc, argv);
MainWindow w;

所以:

复制代码
构造:

QApplication
    ↓
MainWindow


析构:

MainWindow
    ↓
QApplication

也就是说,MainWindow 是后创建的,所以它会先析构。

完整过程可以理解为:

复制代码
程序启动
   ↓
创建 QApplication
   ↓
创建 MainWindow
   ↓
w.show()
   ↓
app.exec()
   ↓
事件循环运行
   ↓
用户关闭程序
   ↓
exec() 返回
   ↓
main() 返回
   ↓
MainWindow 析构
   ↓
QApplication 析构
   ↓
程序退出

为什么这个顺序很重要?

因为 MainWindow 属于 Qt GUI 对象,而 QApplication 是整个 Qt GUI 应用程序环境。

因此在正常的生命周期设计中:

复制代码
QApplication
    │
    └── MainWindow

应该保证:

QApplication 的生命周期覆盖整个 GUI 对象的生命周期。

这也是为什么通常把:

c++ 复制代码
QApplication app(argc, argv);

放在 main() 的最前面,并让它一直存活到 main() 结束。

再结合对象树理解

Qt 还有一个非常重要的机制------QObject 父子对象树

例如:

c++ 复制代码
QWidget parent;
QPushButton button(&parent);

这里 button 的父对象是 parent

parent 被销毁时,Qt 的 QObject 对象树机制会自动处理它的子对象。

因此,Qt 中实际上存在两套需要同时理解的生命周期机制:

复制代码
C++ 生命周期
    ↓
局部对象 / 栈对象 / 析构顺序

Qt 对象树
    ↓
QObject 父子关系 / 子对象自动销毁

这两个概念并不是一回事。

理解它们之后,后面学习:

  • QWidget 父子关系
  • QObject 对象树
  • 动态创建控件
  • deleteLater()
  • Qt 内存管理

都会更加容易。

用一句话总结

对于最基本的 Qt Widgets 程序,可以记住这条生命周期:

复制代码
创建 QApplication
        ↓
创建 MainWindow
        ↓
显示窗口
        ↓
进入事件循环
        ↓
用户持续操作
        ↓
事件循环退出
        ↓
MainWindow 析构
        ↓
QApplication 析构
        ↓
程序结束

这就是一个最简单 Qt GUI 程序从启动、运行到退出的完整生命周期。

七、为什么 main() 不能直接 return 0?

到这里,我们已经知道:

c++ 复制代码
MainWindow w;
w.show();

会让窗口进入可见状态。

那么一个很自然的问题来了:

既然窗口已经 show() 了,为什么不能直接 return 0

例如下面这样的代码为什么不行?

c++ 复制代码
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MainWindow w;
    w.show();

    return 0;
}

代码确实可以编译,但程序几乎马上就结束

首先要明确一点:

这段代码本身并不一定编译报错。

真正的问题发生在运行时

程序的执行过程是:

复制代码
进入 main()
    ↓
创建 QApplication
    ↓
创建 MainWindow
    ↓
调用 w.show()
    ↓
窗口进入可见状态
    ↓
执行 return 0
    ↓
main() 结束
    ↓
w 析构
    ↓
app 析构
    ↓
进程退出

也就是说:

show() 并不会让程序一直运行。

它只是让窗口进入可见状态。

为什么 show() 不能让程序一直运行?

因为 show() 和"程序持续运行"其实是两件不同的事情。

例如:

c++ 复制代码
w.show();

解决的是:

窗口应该显示出来。

而:

c++ 复制代码
app.exec();

解决的是:

程序接下来应该持续运行,并不断处理事件。

可以把它们理解成:

复制代码
show()
    ↓
让窗口"出现"

exec()
    ↓
让程序"活着"

因此:

复制代码
w.show();
return 0;

相当于:

复制代码
把窗口打开
	↓
立刻关掉整个程序

那么 return app.exec() 到底解决了什么?

正确的代码是:

c++ 复制代码
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MainWindow w;
    w.show();

    return app.exec();
}

这里最关键的是:

c++ 复制代码
app.exec();

它会进入 Qt 的主事件循环。

可以把它简单理解成:

c++ 复制代码
while (程序没有要求退出)
{
    等待事件;

    获取事件;

    将事件分发给对应对象;

    处理事件;
}

当然,这只是为了帮助理解的伪代码,并不是 Qt 内部实际源码。

运行起来以后到底发生了什么?

假设我们启动程序:

复制代码
main()
 	↓
QApplication
 	↓
MainWindow
 	↓
w.show()
 	↓
app.exec()

进入 exec() 后,程序不会退出。

此时事件循环开始工作:

复制代码
                  app.exec()
                      │
                      ▼
              ┌──────────────┐
              │   等待事件    │
              └──────┬───────┘
                     │
              收到一个事件
                     ↓
              获取这个事件
                     ↓
              分发给目标对象
                     ↓
              对象处理事件
                     ↓
              回到事件循环
					 ↓
              等待下一个事件

例如用户移动鼠标:

复制代码
用户移动鼠标
     ↓
操作系统产生鼠标相关事件
     ↓
Qt 事件循环获取事件
     ↓
找到对应的 QWidget
     ↓
调用相应的事件处理函数
     ↓
继续等待下一个事件

用户点击按钮:

复制代码
用户点击按钮
     ↓
操作系统产生鼠标事件
     ↓
Qt 事件循环
     ↓
QPushButton
     ↓
处理鼠标事件
     ↓
可能发出 clicked() 信号
     ↓
连接的槽函数执行

所以 exec() 运行期间,主线程实际上一直在工作

"exec() 阻塞了主线程",为什么窗口还能够响应?

这是初学者非常容易产生误解的地方。

我们经常说:

app.exec() 会阻塞当前线程。

这里的"阻塞"并不是:

主线程什么都不做。

而是:

main() 后面的代码不会继续执行,直到事件循环退出。

例如:

c++ 复制代码
qDebug() << "A";

int result = app.exec();

qDebug() << "B";

程序运行后:

复制代码
输出 A
    ↓
进入事件循环
    ↓
程序持续运行
    ↓
用户操作窗口
    ↓
处理各种事件
    ↓
......
    ↓
事件循环退出
    ↓
exec() 返回
    ↓
输出 B

所以:

复制代码
main() 的执行流程被暂停 ≠ 主线程停止工作

更准确地说:

复制代码
main()
 │
 ├── 前面的代码正常执行
 │
 ├── app.exec()
 │       │
 │       └── 主线程进入事件循环
 │             ├── 等待事件
 │             ├── 处理事件
 │             ├── 等待事件
 │             └── 处理事件
 │
 └── exec() 返回后继续执行

那么什么时候 exec() 才会返回?

只有当应用程序收到退出请求时,事件循环才会结束。

例如:

复制代码
QApplication::quit();

或者:

复制代码
QCoreApplication::quit();

也可以通过:

复制代码
QApplication::exit(0);

退出事件循环。

对于普通 Qt Widgets 程序,当最后一个顶层窗口关闭时,Qt 默认也会让应用程序退出。

因此:

复制代码
用户关闭窗口
      ↓
关闭事件被处理
      ↓
应用程序请求退出
      ↓
事件循环结束
      ↓
exec() 返回
      ↓
main() 返回
      ↓
局部对象析构
      ↓
   程序退出

用一个最直观的例子理解

错误示例:

c++ 复制代码
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MainWindow w;
    w.show();

    return 0;
}

可以理解成:

复制代码
创建程序
 ↓
创建窗口
 ↓
显示窗口
 ↓
程序立即退出

正确示例:

c++ 复制代码
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MainWindow w;
    w.show();

    return app.exec();
}

则是:

复制代码
创建程序
 ↓
创建窗口
 ↓
显示窗口
 ↓
进入事件循环
 ↓
等待用户操作
 ↓
处理事件
 ↓
继续等待
 ↓
......
 ↓
用户关闭程序
 ↓
退出事件循环
 ↓
main() 返回
 ↓
程序退出

记住 show() 和 exec() 的区别

这是理解 Qt 程序启动流程最重要的一组概念:

代码 主要作用
MainWindow w; 创建窗口对象
w.show(); 让窗口进入可见状态
app.exec(); 进入 Qt 主事件循环
app.quit(); 请求退出事件循环
exec() 返回 事件循环已经结束

因此,一个最基本的 Qt Widgets 程序才会写成:

c++ 复制代码
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MainWindow w;
    w.show();

    return app.exec();
}

这几行代码实际上分别解决了三个不同的问题:

复制代码
QApplication
    ↓
建立 Qt GUI 应用环境

MainWindow + show()
    ↓
创建并显示窗口

app.exec()
    ↓
让程序进入事件循环,持续处理事件

这就是为什么 return 0 不能替代 return app.exec()

完整声明周期流程图

让我们把所有的拼图组合起来,看看一个完整的 Hello World 程序,从启动到窗口关闭,经历了怎样的时序:

从这张图可以看出,QApplicationMainWindow 和事件循环三者之间是如何协同工作的。

八、总结:Hello World 教会我们的三件事

回顾这一篇的内容,一个看似简单的 Hello World 程序,实际上教会了我们三件非常重要的事情。

第一件事:Qt 应用是有"生命"的

QApplication 对象是程序的"心脏"。它在构造时启动整个框架,在析构时清理所有资源。

第二件事:GUI 程序的核心是事件循环

没有事件循环,窗口只是静态的图片;有了事件循环,窗口才有了"生命"------能够响应点击、输入、缩放和网络事件。

这也是为什么 a.exec() 是 Qt 程序与普通 C++ 控制台程序最大的不同之处。

第三件事:MOC 是 Qt 元对象系统的重要组成部分

MOC 为带有 Qt 元对象声明的类生成额外的 C++ 代码,从而支持信号与槽、运行时类型信息、属性系统等机制。

当你理解了这几行代码背后的机制,后续所有的 Qt 学习都会变得顺理成章。

  • 学习信号与槽时,你会知道那是 MOC 生成的元对象在发挥作用;
  • 学习多线程时,你会知道为什么不能在工作线程直接操作 UI(因为事件循环在主线程);
  • 学习网络编程时,你会知道网络事件也是通过事件循环分发到 QTcpSocket 的。

对于本篇的一些关键词非常容易混淆,下表可以先简单进行理解:

概念 负责什么
QApplication QWidget 应用程序环境
show() 让窗口进入可见状态
exec() 进入主事件循环
Event Qt 中传递/处理的事件
Signal Qt 对象之间的通知机制
Slot 接收信号并执行相应逻辑
Q_OBJECT 为类启用 Qt 元对象相关能力
MOC Q_OBJECT 等 Qt 元对象声明生成代码
QObject Qt 对象模型的基础类

这就是"从根源理解"和"只背 API"之间的区别。

从下一篇开始,我们将正式进入 Qt 的核心控件和基础组件学习。

但我们不会像普通教程那样,简单地罗列 "QPushButton 有哪些属性"。

我们会带着一个更深入的问题去学习:

这些控件,是如何与事件循环、对象树和元对象系统协同工作的?


下一篇预告:《Qt 项目构建流程深度解析------MOC、UIC、RCC 的作用与原理》

窗口才有了"生命"------能够响应点击、输入、缩放和网络事件。

这也是为什么 a.exec() 是 Qt 程序与普通 C++ 控制台程序最大的不同之处。

第三件事:MOC 是 Qt 元对象系统的重要组成部分

MOC 为带有 Qt 元对象声明的类生成额外的 C++ 代码,从而支持信号与槽、运行时类型信息、属性系统等机制。

当你理解了这几行代码背后的机制,后续所有的 Qt 学习都会变得顺理成章。

  • 学习信号与槽时,你会知道那是 MOC 生成的元对象在发挥作用;
  • 学习多线程时,你会知道为什么不能在工作线程直接操作 UI(因为事件循环在主线程);
  • 学习网络编程时,你会知道网络事件也是通过事件循环分发到 QTcpSocket 的。

对于本篇的一些关键词非常容易混淆,下表可以先简单进行理解:

概念 负责什么
QApplication QWidget 应用程序环境
show() 让窗口进入可见状态
exec() 进入主事件循环
Event Qt 中传递/处理的事件
Signal Qt 对象之间的通知机制
Slot 接收信号并执行相应逻辑
Q_OBJECT 为类启用 Qt 元对象相关能力
MOC Q_OBJECT 等 Qt 元对象声明生成代码
QObject Qt 对象模型的基础类

这就是"从根源理解"和"只背 API"之间的区别。

从下一篇开始,我们将正式进入 Qt 的核心控件和基础组件学习。

但我们不会像普通教程那样,简单地罗列 "QPushButton 有哪些属性"。

我们会带着一个更深入的问题去学习:

这些控件,是如何与事件循环、对象树和元对象系统协同工作的?


下一篇预告:《Qt 项目构建流程深度解析------MOC、UIC、RCC 的作用与原理》

相关推荐
欧特克_Glodon1 小时前
OpenCV计算机视觉开发入门与实践<十二>:XML 和 YAML 文件读写
xml·c++·opencv·计算机视觉
ryanuo71 小时前
Shadcn/ui × Qt 6/QML:一次从 Web UI 到桌面 UI 的组件化实践
c++·qt·ui·shadcn
余额瞒着我当琳2 小时前
C++--vector第二讲:手写 C++ STL:vector 源码剖析与迭代器失效分析
android·java·c++
郝学胜-神的一滴2 小时前
[简化版 GAMES 104] 现代游戏引擎 06:从Tick时序到邮局模型,拆解确定性世界的底层密码
开发语言·c++·游戏引擎·图形渲染·软件开发·opengl
不会代码的小猴2 小时前
6. Qt网络编程
开发语言·c++·笔记·qt·算法
沐风老师2 小时前
从零开始学3dMax插件开发!
c++·3dmax插件·3dmax·maxscript
wuyk55513 小时前
98.C语言易混难点:字符数组与字符串指针的底层差异
c语言·开发语言·c++·stm32·嵌入式硬件·算法
峥无13 小时前
从0到1手撕红黑树:封装实现 my_map 与 my_set(SGI-STL 源码级深度解析)
开发语言·c++·笔记·算法·stl
波特率11520013 小时前
C++新特性---属性说明符与标准属性
开发语言·c++