4、第一个Qt项目:创建项目、理解 main.cpp、QApplication、窗口组件、对象树机制

4.1 从零开始:创建你的第一个Qt项目

说实话,我当年第一次打开Qt Creator时,看着满屏的选项也有点懵。但别担心,创建Qt项目其实就三步。

打开Qt Creator,点击「New Project」按钮。你会看到这样的选择界面:

  • Qt Widgets Application —— 带窗口界面的桌面程序,我们这节课就用它
  • Qt Console Application —— 纯命令行程序,没有窗口
  • Qt Quick Application —— 用QML写界面,适合移动端和动画场景

选第一个,然后一路下一步。项目名称我建议用英文,比如 MyFirstApp。路径别带中文,否则编译时可能报一些莫名其妙的错——嗯,我踩过这个坑。

构建系统选 qmake 就行,简单够用。最后一步的「Kit Selection」里,确保你勾选了桌面版的编译器(比如 Desktop Qt 5.15.2 MinGW 64-bit)。

点完成,项目就建好了。你会看到自动生成了几个文件:

  • main.cpp —— 程序的入口
  • mainwindow.h / mainwindow.cpp —— 主窗口的声明和实现
  • MyFirstApp.pro —— 项目配置文件

4.2 解剖 main.cpp:Qt程序的起点

双击打开 main.cpp,你会看到类似这样的代码:

#include <QApplication>
#include "mainwindow.h"

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

    return a.exec();
}

这段代码只有七八行,但每一行都值得好好说说。

第一行:#include <QApplication>

这行引入了Qt的核心类库。QApplication 是每个Qt GUI程序都离不开的类。它管理着事件循环、程序设置、全局样式等。

第二行:QApplication a(argc, argv);

创建了一个QApplication对象。注意,每个Qt程序有且只有一个QApplication对象,而且必须在创建任何窗口之前创建它。为什么?因为QApplication负责初始化Qt的运行环境,比如字体、主题、鼠标样式等。你想想看,如果窗口都创建好了,环境还没初始化,那不乱套了吗?

第三行:MainWindow w;

创建主窗口对象。MainWindow 继承自 QMainWindow,它自带菜单栏、工具栏、状态栏等骨架。当然,你也可以直接用 QWidget 创建一个空白窗口。

第四行:w.show();

默认情况下,Qt创建的窗口是隐藏的。必须调用 show() 才能显示出来。我刚开始学的时候,写了个窗口死活看不到,查了半天才发现忘了调用 show()——这种低级错误,谁还没犯过呢?

第五行:return a.exec();

这行启动了Qt的事件循环。程序会一直在这里等待,直到用户关闭窗口。事件循环说白了就是一个无限循环,不断监听鼠标点击、键盘输入、定时器触发等事件,然后分发给对应的处理函数。

核心理解: a.exec() 之后的代码,只有在程序退出时才会执行。所以一般把清理资源的代码放在它后面。

4.3 QApplication:程序的「大管家」

QApplication 不只是创建窗口那么简单。它负责的事情非常多:

功能 说明 常用方法
事件循环 接收并分发所有事件 exec()、processEvents()
全局设置 设置应用名称、版本、图标等 setApplicationName()、setWindowIcon()
样式管理 切换界面风格(Fusion、Windows等) setStyle()
剪贴板 访问系统剪贴板 clipboard()
桌面信息 获取屏幕尺寸、DPI等 desktop()、primaryScreen()

举个例子,如果你想给程序设置一个友好的名称和版本号:

QApplication a(argc, argv);
a.setApplicationName("我的第一个Qt程序");
a.setApplicationVersion("1.0.0");
a.setWindowIcon(QIcon(":/icons/app.png"));

这些信息在后续的关于对话框、系统任务栏等地方都会用到。我个人习惯在项目一开始就把这些设置好,省得后面再补。

4.4 窗口组件:Qt界面的积木

Qt里的窗口组件(Widget),你可以理解成乐高积木。每个组件都是一个独立的小零件,拼在一起就成了完整的界面。

常用的基础组件有:

  • QWidget —— 所有组件的基类,一个空白矩形区域
  • QLabel —— 显示文本或图片
  • QPushButton —— 按钮
  • QLineEdit —— 单行文本输入框
  • QTextEdit —— 多行文本编辑框
  • QComboBox —— 下拉选择框
  • QCheckBox —— 复选框
  • QRadioButton —— 单选按钮

组件之间可以嵌套。比如,一个 QWidget 里面放一个 QPushButton,再放一个 QLabel。这种嵌套关系,就是通过「父子关系」来建立的。

QWidget *window = new QWidget();
QPushButton *btn = new QPushButton("点我", window);  // 指定父对象为 window
QLabel *label = new QLabel("Hello", window);          // 同样指定父对象

这里 window 是父组件,btn 和 label 是子组件。子组件会显示在父组件的区域内,并且会跟随父组件一起移动、显示、隐藏。

小技巧: 在创建子组件时,直接传入父组件指针作为构造参数。这样既建立了父子关系,又省去了手动设置父对象的步骤。

4.5 对象树机制:Qt的内存管理绝活

这是Qt最核心的机制之一,也是很多新手容易忽略的地方。

在C++里,用 new 创建的对象必须手动 delete,否则就会内存泄漏。但Qt引入了一套「对象树」机制,让内存管理变得轻松很多。

原理是这样的:

每个QObject(包括所有窗口组件)都可以有一个父对象。当父对象被销毁时,它会自动销毁所有子对象。这些父子关系构成了一棵树——对象树。

看个例子:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QWidget *window = new QWidget();
    QPushButton *btn = new QPushButton("退出", window);
    QLabel *label = new QLabel("欢迎", window);

    window->show();
    return a.exec();
    // 程序退出时,window 被销毁,btn 和 label 自动被销毁
    // 不需要手动 delete
}

在这个例子里,window 是根节点,btn 和 label 是它的子节点。当 window 被销毁时,Qt会遍历它的子对象列表,逐个调用 delete。你完全不用操心释放内存的事。

注意: 对象树机制只对继承自 QObject 的类有效。如果你用普通的 C++ 类(比如 std::string),该手动释放还是得手动释放。

我曾经在一个项目中,因为没搞清楚对象树,在堆上创建了一堆 QWidget 却不指定父对象,结果程序跑着跑着内存就飙上去了。后来排查了半天,才发现是这些「孤儿组件」没人管,一直没被释放。

那什么时候需要手动 delete?

两种情况:

  1. 对象没有父对象,且是在堆上创建的(用 new 分配的)
  2. 你想在父对象销毁之前提前释放某个子对象

对于第二种情况,Qt 提供了 deleteLater() 方法。它不会立即删除对象,而是等事件循环回到主循环时再删除。这样做的好处是避免在事件处理过程中删除对象导致崩溃。

btn->deleteLater();  // 安全地删除按钮,不会立即执行

4.6 知识体系总览

下面这张图,把本章的核心逻辑串起来了:

第一个Qt项目 · 知识体系 main.cpp QApplication 窗口组件 对象树机制 事件循环 · 全局设置 QWidget · QPushButton · QLabel 父子关系 · 自动析构 三者协作:QApplication驱动事件,组件构建界面,对象树管理内存

说白了,Qt程序就是这三件事:QApplication 驱动事件循环,窗口组件搭建用户界面,对象树自动管理内存。理解了这个三角关系,Qt的大门就算正式打开了。

我的建议: 学Qt不要急着写复杂界面。先把这个最简单的项目跑起来,然后试着改改窗口标题、加个按钮、改改按钮文字。动手试一遍,比看十遍教程都管用。

可可精品资料库