QT项目初步认识(对象树)
本博客主要为大家讲解当我们创建一个QT项目,Qt Creator给我们自动生成的文件的作用与联系,以及QT是怎么通过图形化界面生成QT项目的,最后再为大家讲解一下QT中的对象树。
一.创建QT项目
比较简单,如果各位熟悉创建QT项目的创建,可以跳过这一小节。
打开Qt Creator,界面如下:
点击new创建新项目,open可以打开以前的项目,这里我们点击new,出现下面的界面。
这里点击右边第一个创建图形化项目,出现界面如下:
选择合适的名称和路径,要注意的是路径一定要是全英文,不然qt会报错。点击下一步,界面如下:
这里我们要选择一个构建系统,构建系统的主要作用就是帮助应用程序或者项目完成代码的构建,在QT中构建系统的主要作用我们可以看成帮助我们自动生成makefile文件。这里我们选择qmake点击下一步,界面如下:
QMainWindow是一个完整的界面,具有标题栏和菜单栏等,QWidget是一个普通界面,没有标题栏和菜单栏等,QDialog就是一个普通的对话框。这里我们先选择QWidget进行讲解,点击下一步,界面如下:
这个是为了给国际化项目提供多个国家语言的功能,我们不做了解,点击下一步。
选择一个qt编译器的sdk,这里我们直接使用之前安装的就行,点击下一步:
这里是把项目代码提交到GitHub等代码管理平台的,我们直接选择none,点击下一步,项目就创建成功了。界面如下:
这里我们直接点击左下角的第一个三角形,就可以运行项目,结果如下:
是一个空的窗口,第一个项目就创建运行成功了。
二.项目代码讲解
Qt creator 会自动我们之前的选择创建下面的几个文件,我们来一次看看。
1.pro文件
点击pro文件,我们可以看到以上内容,在Qt creator中pro文件主要是为了项目的构建,qmake会自动根据pro文件的内容生成对应makefile,而makefile可以完成编译、链接的功能。通过.pro文件+qmake,我们就可以自动生成makefile。为了验证,我们鼠标右击项目文件夹,点击构建或运行选项:
之后我们打开qt项目所在的文件目录:
点击build文件夹,里面就有生成的makefile文件。
2.widget.ui
这里我们点击widget.ui,可以进入图形化编辑页面:
选择左边的控件拖拽进中间的界面就可以便捷的生成控件,这里我们选择底部的label控件,进行拖拽。
点击控件可以生成文字,右下角可以调整label的属性。创建一个label后我们在点击一下左边的编辑页面:
出现了一个XML文件,这个文件就是qt根据我们刚才的拖拽操作生成的。这个文件有什么作用吗?我们先不解释,直接运行代码。
结果成功,我们在点击之前打开过的build文件夹。
有一个ui_widget.h,我们打开这个文件。
这里有俩个类:Ui_Widget和Widget(后面会用到),widget继承于Ui_Widget。在Ui_Widget中就有我们之前设计的label。
因此我们可以得出ui文件的作用如下:
qt系统通过我们点击ui文件后设计的界面生成一个xml文件,再根据这个xml文件的内容生成ui_widget.h文件,其中ui_widget.h的代码就实现了我们设计的界面。
3.widget.h和widget.cpp
先看widget.h:
再看widget.cpp文件:
这里构造函数为什么要有parent参数和对象树有关,我们最后再讲。
4.main.cpp
QApplication应用程序类
一个qt程序自带的类,用于实现qt图形化程序。
a.exec()
程序进入消息循环,等待对用户输入进行响应。这里a.exec会一直循环运行,直到用户退出,在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
w.show
显示widget窗口
Widget
widget是在栈上定义的,循环结束,栈要销毁时会自动调用构造函数。
三.对象树
在 Qt 中创建很多对象的时候,构造函数会提供一个 Parent 对象指针,这个指针就是用来构建对象树的。
1.什么是对象树
简单的来说就是通过N叉树的结构讲各个qt中的对象组织起来,如:
通过对象树我们就让qt中的类被组织了起来。对象树的作用就是当父对象析构时子对象也应该析构,如上图的QWidget析构释放时,他的子控件也应该全部释放,不允许存在。
2.挂载到对象树的条件
继承自QObject的类才可以参与到对象树中。qt中的控件都是继承自QObject类,都可以产于到对象树中,但是我们自定义实现没有继承QObject的类是无法参与到对象树中的。
3.对象树实现原理
1. 父对象指针与子对象列表:
每个QObject实例包含一个指向父对象的指针(parent)和一个子对象列表(children)。
当创建子对象时,通过构造函数或`setParent()`方法指定父对象,该子对象会被添加到父对象的children列表中。
2. 自动析构机制
父对象在析构时,会遍历其children列表,递归删除所有子对象。
子对象析构时,会从父对象的children列表中移除自身,防止父对象重复删除。
3. 动态父对象变更
调用setParent()时,子对象会从原父对象的列表中移除,并添加到新父对象的列表中。
若新父对象为nullptr,对象成为顶层对象,需手动管理其生命周期。
4. 禁止拷贝构造:
QObject禁用拷贝构造函数和赋值运算符,确保每个对象在树中唯一,维护父子关系的一致性。
上面的这些原理都是在QObject中实现的,所以继承自QObject是先决条件。
4.对象树的作用
1. 自动内存管理,避免内存泄漏
当一个父对象被销毁时(如调用 delete或超出作用域),Qt 会自动递归删除其所有子对象,开发者无需手动释放子对象内存。子对象被删除时,会从其父对象的子对象列表中移除,避免父对象后续误操作已释放的子对象。主要就是对于图形化程序来说父对象没了,位于其上的子对象应该也应自动销毁。
2. 事件传递与信号槽机制的基础
子对象的事件(如鼠标点击)可沿对象树向上传递到父对象,实现事件过滤或统一处理。
如:父对象 window 监听子对象button的事件,button->installEventFilter(window);
3.信号槽自动清理
若对象被删除,Qt 会自动断开与其相关的信号槽连接,避免因对象销毁导致的无效回调。
5.使用例子
这里我们编写一个继承自QLabel的自定义类,学习对象树的使用和验证对象树会自动析构父对象销毁时的子对象。
mylabel.h:
#ifndef MYLABEL_H
#define MYLABEL_H
#include<QLabel>class mylabel:public QLabel
{
public:mylabel(QWidget*parent);~mylabel();
};#endif
mylabel.cpp:
#include "mylabel.h"
#include<iostream>mylabel::mylabel(QWidget*parent):QLabel(parent)
{}
mylabel::~mylabel()
{std::cout<<"~mylabel"<<std::endl;
}
这里我们的mylabel是要建立在窗口widget上,因此构造函数定义如下:
mylabel::mylabel(QWidget*parent):QLabel(parent)
{}
mylabel继承自Qlabel,Qlabel继承自QObject,在QObeject的构造函数中才实现了对象树的功能,因此我们直接调用QLabel的构造函数,QLablel的构造函数又会调用QObject的构造函数,从而将mylabel的parent指针指向我们传入的Qwidget对象,并让mylabel加入Qwidget对象的child列表。实现对象树的挂载。
此外我们也自定义实现了构造函数,来验证对象树会自动析构。
首先要显示控件一般是在Widget.cpp的构造函数中,这样在main函数中调用widget类构造的时候才会将我们的控件也构造出来。widget.cpp代码如下:
#include "widget.h"
#include "ui_widget.h"
#include<mylabel.h>
#include"QLabel"
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QLabel* label1=new mylabel(this);label1->setText("hahahahaha");}Widget::~Widget()
{delete ui;
}
运行结果:
控制台输出:
这里注意几点:
1.在代码中我们new出了一个对象,挂载到对象树后,没有调用delete,验证了在对象树中父对象结束后会自动调用析构函数。
2.对应要挂载到对象树的对象,一定要new出来,new是在堆上申请内存,生命周期在delete才会结束,如果我们在栈上直接定义,栈结束会自动析构,父对象结束也会调用析构,重复析构会报错。
3.QLabel中的析构函数是虚函数,因此QLabel和mylabel的析构函数形成了覆盖,最后析构函数调用的是mylabel的析构函数。
博客就讲到这了,有帮助的话点个赞吧。