1.第一个Qt项目
1.1 创建项目
创建基于窗口的Qt应用程序

指定项目的存储路径

- 项目名称根据需求自己指定即可
- 在指定项目的存储路径的时候, 路径中不能包含中文
选择构建系统

指定默认的窗口类的名字以及窗口的类型

选择编译套件, 编译套件用于项目文件的编译, 如果安装了多个编译套件, 在这里选择其中一个就可以了。

1.2 项目文件(.pro)
在创建的Qt项目中自动生成了一个后缀为 .pro 的项目文件,该文件中记录着项目的一些属性信息,具体信息如下:
cpp
# 项目编译的时候需要加载哪些底层模块
QT += core gui
# 如果当前Qt版本大于4,会添加一个额外的模块:widgets
# Qt5对gui模块进行了拆分,将widgets独立出来了
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
# 使用c++17
CONFIG += c++17
# 禁止使用废弃函数
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
# 项目中的源文件
SOURCES += \
main.cpp \
mainwindow.cpp
# 项目中的头文件
HEADERS += \
mainwindow.h
# 项目中的窗口界面文件
FORMS += \
mainwindow.ui
# Default rules for deployment.
# 发布程序时用的路径
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
1.3 main.cpp
在这个源文件中有程序的入口函数 main(),下面给大家介绍下这个文件中自动生成的几行代码:
cpp
#include "mainwindow.h" // 生成的窗口类头文件
#include <QApplication> // 应用程序类头文件
int main(int argc, char *argv[])
{
// 创建应用程序对象,在一个Qt项目中实例对象有且仅有一个
// 类的作用:检测触发的事件,进行事件循环并处理
QApplication a(argc, argv);
// 创建窗口类对象
MainWindow w;
// 显示窗口
w.show();
// 应用程序对象开始事件循环,保证应用程序不退出
return a.exec();
}
return a.exec()返回什么?
exec() 执行完后,会返回一个整数 ,然后 return 给操作系统。
返回值只有两种情况:
- 返回 0
- 正常关闭窗口
- 程序正常退出
- 返回非 0(通常是 1)
- 程序异常退出、崩溃、出错
系统怎么看:
- Windows / Linux 收到
0→ 认为程序正常结束 - 收到非 0 → 认为程序出错退出
1.4 mainwindow.ui
在Qt中每一个窗口都对应一个可编辑的可视化界面(*.ui), 这个界面对应的是一个xml格式的文件, 一般情况下不需要在xml格式下对这个文件进行编辑, 关于这个文件结构了解即可。
cpp
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>25</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
1.5 mainwindow.h
这个文件是窗口界面对应的类的头文件。
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow> // Qt标准窗口类头文件
QT_BEGIN_NAMESPACE
// mainwindow.ui 文件中也有一个类叫 MainWindow,将这个类放到命名空间 Ui 中
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT // 这个宏是为了能够使用Qt中的信号槽机制
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui; // 定义指针指向窗口的 UI 对象
};
#endif // MAINWINDOW_H
再解释一下这段:
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
// ... 中间所有代码 ...
#endif // MAINWINDOW_H
如果没定义过 MAINWINDOW_H,就定义它,然后编译中间内容。
作用:防止这个文件被重复包含、重复编译,导致报错。
1.6 maindow.cpp
这个文件是窗口界面对应的类的源文件。
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow) // 基于mainwindow.ui创建一个实例对象
{
// this指向MainWindow窗口本身,ui指向界面
// 将 mainwindow.ui 的实例对象和当前类的对象进行关联
// 这样同名的两个类对象就产生了关联,合二为一了
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
2.Qt中的窗口类
2.1 基础窗口类

- 常用的窗口类有3个
- 在创建Qt窗口的时候, 需要让自己的窗口类继承上述三个窗口类的其中一个
- QWidget
- 所有窗口类的基类
- Qt中的控件(按钮, 输入框, 单选框...)也属于窗口, 基类都是QWidget
- 可以内嵌到其他窗口中: 没有边框
- 可以不内嵌单独显示: 独立的窗口, 有边框
- QDialog
对话框类, 后边会具体介绍这个窗口
不能内嵌到其他窗口中 - QMainWindow
有工具栏, 状态栏, 菜单栏, 后边会具体介绍这个窗口
不能内嵌到其他窗口中
2.2 窗口的显示
- 内嵌窗口
- 依附于某一个大的窗口, 作为了大窗口的一部分
- 大窗口就是这个内嵌窗口的父窗口
- 父窗口显示的时候, 内嵌的窗口也就被显示出来了
- 不内嵌窗口
- 这类窗口有边框, 有标题栏
- 需要调用函数才可以显示
cpp
// QWidget是所有窗口类的基类, 调用这个提供的 show() 方法就可以显示将任何窗口显示出来
// 非模态显示
void QWidget::show(); // 显示当前窗口和它的子窗口
// 对话框窗口的非模态显示: 还是调用show() 方法
// 对话框窗口的模态显示
[virtual slot] int QDialog::exec();
3.坐标体系
在Qt关于窗口的显示是需要指定位置的,这个位置是通过坐标来确定的,所有坐标的选取又都是基于坐标原点来确定的。
3.1 窗口的坐标原点
所有坐标的确定都需要先找到坐标原点, Qt的坐标原点在窗口的左上角
- x轴向右递增
- y轴向下递增
3.2 窗口的相对坐标
在一个Qt窗口中一般都有很多子窗口内嵌到这个父窗口中,其中每个窗口都有自己的坐标原点,子窗口的位置也就是其使用的坐标点就是它的父窗口坐标体系中的坐标点。
- 在Qt的某一个窗口中有可能有若干个控件, 这个控件都是嵌套的关系
- A窗口包含B窗口, B窗口包含C窗口
- 每个窗口都有坐标原点, 在左上角
- 子窗口的位置是基于父窗口的坐标体系来确定的, 也就是说通过父窗口左上角的坐标点来确定自己的位置
- Qt中窗口显示的时候使用的相对坐标, 相对于自己的父窗口
- 将子窗口移动到父窗口的某个位置
cpp
// 所有窗口类的基类: QWidget
// QWidget中提供了移动窗口的 API函数
// 参数 x, y是要移动的窗口的左上角的点, 窗口的左上角移动到这个坐标点
void QWidget::move(int x, int y);
void QWidget::move(const QPoint &);
4. 内存回收机制
在Qt中创建对象的时候会提供一个 Parent对象指针(可以查看类的构造函数),下面来解释这个parent到底是干什么的。
QObject是以对象树的形式组织起来的。当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)
QWidget是能够在屏幕上显示的一切组件的父类。QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
Qt 引入对象树的概念,在一定程度上解决了内存问题。
当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。

综上所述, 我们可以得到一个结论:Qt中有内存回收机制, 但是不是所有被new出的对象被自动回收, 满足条件才可以回收, 如果想要在Qt中实现内存的自动回收, 需要满足以下两个条件:
- 创建的对象必须是QObject类的子类(间接子类也可以)
- QObject类是没有父类的, Qt中有很大一部分类都是从这个类派生出去的
- Qt中使用频率很高的窗口类和控件都是 QObject 的直接或间接的子类
- 其他的类可以自己查阅Qt帮助文档
- 创建出的类对象, 必须要指定其父对象是谁, 一般情况下有两种操作方式:
4.1 指定父对象
cpp
// 方式1: 通过构造函数
// parent: 当前窗口的父对象, 找构造函数中的 parent 参数即可
QWidget::QWidget(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
QTimer::QTimer(QObject *parent = nullptr);
// 方式2: 通过setParent()方法
// 假设这个控件没有在构造的时候指定符对象, 可以调用QWidget的api指定父窗口对象
void QWidget::setParent(QWidget *parent);
void QObject::setParent(QObject *parent);