【Qt初识】工程起步:项目创建、代码解读与对象树


大家好,我是Halcyon.平安

欢迎文末添加好友交流,共同进步!


目录

上一章节我们讲完了Qt的基础背景知识,并学会了环境的搭建与验证,本章节我们开始来学习项目的创建

一、项目的创建

  1. 点击文件,选择新建文件或项目

  2. 选择Application------>Qt Widgets

  • 这里我们来看看其他的项目模版分别是什么意思
项目模板 说明
Application Qt 应用程序,包括普通窗体程序和 QtQuick 程序
Library 可以创建动态库、静态库以及 Qt Quick 扩展插件、QtCreator 自身插件
其他项目 可以创建单元测试项目、Qt4 设计师自定义控件、子目录项目等
Non-Qt-Project 非 Qt 项目。可以创建纯 C 或纯 C++ 项目
Import Project 导入项目。从版本控制系统管理的软件项目导入旧的项目
  • 选择Application模板后,我们再来看看它的子模版是什么意思
模板名称 说明
Qt Widgets Application 普通窗体模板,传统基于部件的窗体界面程序
Qt Console Application Qt 控制台应用程序。Qt 主打图形界面开发,该控制台项目模板日常基本很少使用
Qt for Python 基于 Python 开发 Qt 程序,采用 LGPL 许可证,可用于开发闭源 Qt 软件
Qt Quick Application Qt 高级 UI 开发技术,适合移动端、嵌入式设备,可快速开发流畅美观界面;内置 QML 标准库,提供可视化控件、交互、动画、模型视图、粒子、着色等全套界面能力
  1. 点击choose后,输入你的文件/项目名称(不能有中文!!),保存的路径(不能带有中文!!),然后点击下一步
  1. 选择Qt项目的构建系统方式,这么我们选择qmake ,然后点击下一步

我们这里为什么选择qmake?另外两种又是什么意思?

  • qmake: qmake 是⼀个构建⼯具(build tool),⽤于⾃动⽣成 makefile ⽂件。qmake ⽀持跨平台构建。qmake 编辑的是⼀个后缀名为 .pro 的⽂件。

  • CMake:CMake 是⼀个跨平台的构建⼯具。CMake 本⾝不是⼀个编译器,其实就是⽣成⼀个让编译器能读懂编译流程的⽂件⼯具。让 CMake ⾃动⽣成构建系统,例如 Makefile 和 Visual Studio 项⽬⽂件。CMake 是⼀个第三⽅⼯具,有⾃⼰的⽂档。

  • Qbs:Qbs(Qt Build Suite:Qt构建套件)同 qmake、CMake ⼀样都是构建⼯具。Qbs 号称是新⼀代的构建⼯具,⽐ qmake 编译速度更快。Qbs 没有绑定 Qt 版本,它从项⽬⽂件的⾼级项⽬描述中⽣成⼀个正确的依赖表。⽽传统的 MakeFile ⽣成⼯具如 qmake 和 CMake ,其在⽣成 MakeFile ⽂件后将实际的命令交给 Make ⼯具去执⾏。

    Qt 官⽅声明,因市场原因,弃⽤ Qbs 。对于 Qt ⽤⼾来说,qmake 是当前使⽤最⼴泛的构建⼯具, CMake 其次。

  1. 然后我们选择QWidget 基类,和勾选UI文件界面

我们顺便来了解一下其他两个基类,和他们的关系!

基类 说明
QMainWindow 主窗口类,多用于复杂应用;包含中央客户区,配套菜单栏、工具栏、状态栏、可停靠浮动面板等全套窗口组件
QWidget 最基础、最简窗体基类,可承载各类控件自由布局搭建界面
QDialog 对话框类,继承自 QWidget;多用于弹窗,也可作为主界面,支持模态弹窗、获取弹窗返回值等专属功能
  • QMainWindow、QDialog 全部间接 / 直接继承 QWidget,所有可视化控件、窗口都属于 QWidget 子类
  1. 选择语言和翻译文件(这里我们先不考虑国际化问题,所以先直接跳过)
  1. 选择Qt套件

我们这里默认只有第⼀个 "Desktop Qt 5.14.2 MinGW 64-bit",但是如果你安装配置了多个 Qt 套件,就可以都选上。

Qt 套件是指 Qt 程序从编译链接到运⾏环境的全部⼯具和 Qt 类库的集合,对于 MinGW 版本 Qt 程序⽣成和调试,⾄少需要 MinGW 中的编译器 g++(⾃动调⽤链接器)、g++ 配套的基础库、调试器 gdb 还有使⽤ MinGW 环境编译⽽成的 Qt 类库⾃⾝。默认情况下,在上⾯ Kit Selection ⾥选中全部套件。

  1. 下一步后,我们来选择版本控制系统,下面方框内的内容就是我们项目创建好后,自动添加的文件!然后点击完成!

如果你想代码提交到码云或者github,这里的版本控制系统可以选择git

  1. 如下就是一个新的项目文件创建好的状态,和每块区域的名字。

二、项目的代码解释

2.1 widget.h

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

首先是访问头文件被重复包含

cpp 复制代码
#ifndef WIDGET_H:判断是否未定义 WIDGET_H
#define WIDGET_H:如果未定义,立刻定义这个宏
文件末尾 #endif // WIDGET_H 结束保护

然后引入我们的QWidget头文件,然后我们利用Qt标准命名空间包裹,以便于兼容不同版本的Qt

cpp 复制代码
QT_BEGIN_NAMESPACE
...
QT_END_NAMESPACE

然后我们告诉编译器存在一个 Ui 命名空间下的 Widget 类,而且这个类不是我们手写的,是 Qt UIC 工具根据 .ui 界面文件自动生成的,并且只声明不定义,这样还能减少头文件编译依赖,提升编译速度

cpp 复制代码
namespace Ui { class Widget; }

然后我们自己定义窗口类,它是共有继承QWidget 的,并拥有它的所有功能!

cpp 复制代码
class Widget : public QWidget{}

我们必须带上Qt信号槽必备的宏,至于信号槽和这个宏我们后续章节再讲!

cpp 复制代码
Q_OBJECT

接下来是我们的构造函数,QWidget *parent Qt 采用对象树内存管理;传入父窗口:父窗口销毁时自动释放本窗口内存,不用手动delete;= nullptr:默认参数,不传父对象时表示顶层独立窗口。析构函数窗口销毁时执行,一般用来释放资源,ui 界面指针会在这里释放。

cpp 复制代码
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

最后是私有成员UI 界面对象指针,它是整个UI控件管理的核心!且它是自动生成的,并且所有在 Qt Designer 拖拽的按钮、输入框、标签等控件,全部存放在 ui 对象中

cpp 复制代码
private:
    Ui::Widget *ui;
    
使用示例:ui->pushButton->setText("按钮");

这里面的两个Widget并不是一样的,我们自己写的窗口类:class Widget : public QWidget,这是业务窗口类,继承 Qt 原生控件 QWidget; UI 命名空间里的类:namespace Ui { class Widget; },是 Qt UIC 工具自动生成的代码,存储我们在 Qt Designer 里拖出来的所有控件(按钮、标签、输入框等),包含所有控件的指针、布局、界面初始化代码。

Ui::Widget *ui; 指向的就是我们后续实例化对象下的全部控件


2.2 main.cpp

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

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

我们先引入窗口类的头文件,我们就可以直接拖曳一系列的控件;Qt 图形程序必备头文件,QApplication 是我们创建文件选的项目类型,用来管理整个应用程序的生命周期、系统资源、事件循环、界面渲染等。

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

接下来是程序的入口函数,第一个是argc,它是命令行参数个数;第二个是argv,它是命令行参数字符串数组,Qt 程序需要传入这两个参数给应用对象。

cpp 复制代码
int main(int argc, char *argv[]){}

接下来我们创建QApplication,这个有且只能有一个!用来接收命令行参数,处理系统环境、窗口系统、样式、字体等底层初始化;我们接下来实例化自定义窗口Wigget,然后调用 Widget 的构造函数,就自动加载 .ui 界面(就是项目文件管理区的widget.ui)、创建所有拖拽控件,但是构造后此时窗口只是创建完成,不会显示在屏幕上,我们需要调用窗口基类 QWidget 的 show() 方法来将窗口设置为可见,渲染界面到屏幕。最后启动 Qt事件循环a.exec() ,程序停在这里等待用户的操作,当用户关闭所有窗口,事件循环结束,exec() 返回退出码;return 将退出码交给操作系统,程序正常结束。

cpp 复制代码
 QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();

2.3 widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

先引入当前窗口类的头文件,才能实现 Widget 类的构造、析构函数。然后引入 UIC 工具根据 .ui 界面文件自动生成的代码文件;

里面定义了 Ui::Widget 类,包含所有拖拽控件、布局、setupUi 界面初始化函数。

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

随后我们进行构造函数列表的初识化,调用父类 QWidget 的构造,传入父窗口指针,纳入 Qt 对象树管理(对象树我们等下在讲解);ui(new Ui::Widget)是在堆上创建自动生成的 UI 界面对象,赋值给私有成员 ui 指针。

就刚才这个新对象用setupUi(this) 会完成三件事:给当前窗口 this 搭建布局、创建所有拖拽的按钮 / 标签 / 输入框等控件;设置控件大小、文字、样式、信号槽关联(ui 设计师里绑定的槽);将所有控件挂载到当前 Widget 窗口上。执行完这行,界面上所有控件才真正创建完成。最后窗口销毁时,释放堆上创建的 ui 对象,释放所有界面控件内存,避免内存泄漏。


三、对象树

3.1 什么是对象树

Qt 中所有继承自 QObject 的类(QWidgetQPushButtonQLabelQMainWindow 等)都支持对象树 :创建对象时给它指定父对象 parent,就会把当前对象挂载到父对象的管理链表中;当父对象被销毁 时,会自动遍历链表,delete 所有子对象,自动释放子对象内存,不用手动管理。

核心作用:简化内存管理,避免大量手动 new/delete,减少内存泄漏。

那么他的核心机制是什么?我们往下来看看!

3.2 对象树核心机制

  1. 只有 QObject 子类才能参与对象树

    • 所有窗口控件:QWidget、按钮、输入框、对话框、主窗口 全部继承 QObject,支持对象树;

    • 普通 C++ 类、int/string、自定义非 QObject 类,不受对象树管理。

  2. 指定父对象的两种方式

    • 方式 1:构造函数传参(最常用)

      复制代码
      // this 是当前窗口,作为按钮的父对象
      QPushButton *btn = new QPushButton("按钮", this);
    • 方式 2:动态设置父对象

      复制代码
      QPushButton *btn = new QPushButton("按钮");
      btn->setParent(this); // 后期挂载到父对象
  3. 销毁顺序:父死子先亡

    • 父对象析构时,会自动执行:
      1. 遍历自己保存的全部子对象;
      2. 逐个调用 delete 销毁子对象;
      3. 每个子对象销毁时,又会销毁自己的子对象,递归销毁整棵树。
  4. 栈对象 vs 堆对象 关键区别

    • 栈上创建(局部变量,不加 new)

      复制代码
      Widget w; // main函数内栈对象
      1. 生命周期:函数执行完毕自动销毁;

      2. 销毁时会自动调用自身析构函数,释放自己管理的所有子对象。

    • 堆上创建(new 出来)

      复制代码
      Widget *w = new Widget(nullptr); // 无父对象
      1. 无父对象:必须手动 delete w;,否则内存泄漏;

      2. 有父对象:不用手动 delete,父窗口销毁时自动回收。

整体对象树我们可以通过下面我这张生成的图来整体了解一下

  1. 销毁时会自动调用自身析构函数,释放自己管理的所有子对象。

    外链图片转存中...(img-mHbjhFEE-1783490968145)

    • 堆上创建(new 出来)

      复制代码
      Widget *w = new Widget(nullptr); // 无父对象
      1. 无父对象:必须手动 delete w;,否则内存泄漏;

      2. 有父对象:不用手动 delete,父窗口销毁时自动回收。

整体对象树我们可以通过下面我这张生成的图来整体了解一下

相关推荐
SelectDB1 小时前
Agent 场景动态 JSON 性能拆解:Apache Doris 比 ClickHouse 快 7 倍、比 Elasticsearch 快 2 倍
数据库·json·agent
宠友信息1 小时前
MySQL 租户数据隔离在即时通讯源码后端中的落地方式
java·数据库·spring boot·redis·websocket·mysql
jnrjian2 小时前
PG 切换用户 \c -
数据库·postgresql
Database_Cool_2 小时前
内存数据库持久化方案:阿里云 Tair 持久内存型数据不丢失
数据库·阿里云
汉克老师2 小时前
GESP2026年6月认证C++八级( 第二部分判断题(1-10))精讲
c++·归并排序·dijkstra·二分·互斥·gesp8级·可重复组合
treesforest3 小时前
高精度IP定位怎么选?从企业需求出发的选型指南
网络·数据库·tcp/ip·web安全·ip·高精度ip查询
吴声子夜歌3 小时前
Redis 5.x——pub/sub和Stream
数据库·redis·bootstrap
吴声子夜歌3 小时前
Redis 5.x——多种数据类型使用场景
数据库·redis
nianniannnn3 小时前
c++复习自存--流、异常处理、多线程与C++工程规范
开发语言·c++