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?
两种情况:
- 对象没有父对象,且是在堆上创建的(用
new分配的) - 你想在父对象销毁之前提前释放某个子对象
对于第二种情况,Qt 提供了 deleteLater() 方法。它不会立即删除对象,而是等事件循环回到主循环时再删除。这样做的好处是避免在事件处理过程中删除对象导致崩溃。
btn->deleteLater(); // 安全地删除按钮,不会立即执行
4.6 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
说白了,Qt程序就是这三件事:QApplication 驱动事件循环,窗口组件搭建用户界面,对象树自动管理内存。理解了这个三角关系,Qt的大门就算正式打开了。
我的建议: 学Qt不要急着写复杂界面。先把这个最简单的项目跑起来,然后试着改改窗口标题、加个按钮、改改按钮文字。动手试一遍,比看十遍教程都管用。
可可精品资料库