Qt01对象树和窗口坐标体系

一、QT基本框架

创建一个项目,先看下main.cpp这个文件

注意:

  1. 每个Qt程序有且只能有一个QApplication对象,没有会报错。
  2. Qt里面的头文件和类名是一致的,知道头文件就知道类名,反之亦然
  3. Qt头文件是没有.h的,基本都是以大写的Q开头

根据以上的分析,我们可以得出Qt的程序框架代码:

1
2
3
4
5
6
7
8
9
#include <QApplication>      
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
/*
在这里写你的代码
*/
return a.exec();
}
  • widget.h和widget.cpp分析

打开头文件里面的widget.h,和sources里面的widget.app,可以看到以下代码:

最上面的MyfirstQt.pro,是管理项目的文件,用来存储项目设置。

后缀为“.pro”的文件是项目的管理文件,文件名就是项目的名称,如本项目中的 MyfirstQt.pro。(类似与VS工程的.sln文件)

💛💛实例(用代码创建一个button):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "widget.h"
#include "ui_widget.h"
#include<QPushButton>//按钮控件的头文件

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

//创建第一个按钮
QPushButton *btn=new QPushButton;
//不能用btn->show();//show是以顶层方式弹出控件
//让btn在widget窗口显示
btn->setParent(this);//this指向当前对象的指针(即widget的地址)
//显示文本
btn->setText("第一个按钮");
//创建第二个按钮
//注意:这种方法是按照按钮的大小创建窗口
QPushButton *btn2=new QPushButton("第二个按钮",this);
//移动btn2的位置(由于创建的两个按钮位置重叠了)
btn2->move(100,100);
//因此需要重置窗口大小
resize(600,400);

//设置窗口标题
setWindowTitle("唯有自己强大");
}

二、对象树

💚💚什么是对象树?

我们常常听到 QObject 会用对象树来组织管理自己,那什么是对象树?

这个概念非常好理解。因为 QObject 类就有一个私有变量 QList<QObject *>,专门存储这个类的子孙后代们。比如创建一个 QObject 对象并指定父对象时,就会把自己加入到父对象的 childre() 列表中,也就是 QList<QObject *> 变量中。

父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这 里的父对象并不是继承意义上的父类!)

举个例子,有一个窗口 Window,里面有 Label标签、TextEdit文本输入框、Button按钮这三个元素,并且都设置 Window 为它们的父对象。这时候我做了一个关闭窗口的操作,作为程序员的你是不是自然想到将所有和窗口相关的对象析构啊?古老的办法就是一个个手动 delete 呗。是不是很麻烦?Qt 运用对象树模式,当父对象被析构时,子对象自动就 delete 掉了,不用再写一大堆的代码了。

QWidget 是能够在屏幕上显示的一切组件的父类(QWidget 继承自 QObject,因此也继承了这种对象树关系。)

💛💛注意构建/析构 QObject 的顺序问题

正常情况下,最后被创建出来的会先被析构掉。就好比我有一个大桌子,上面先摆放一个盘子,再摆放一个碗。当我要把桌子撤掉的时候,会先撤掉碗,再撤掉盘子,最后撤掉桌子。

用代码演示一下:

1
2
3
4
5
int main()
{
QWidget window;
QPushButton quit("Quit", &window);
}

后创建的 quit 对象指定了 window 为其父对象。那么关闭程序时,会先调用它的析构函数,然后调用 window 的析构函数。

这就牵扯到一个特殊情况:

1
2
3
4
5
6
7
int main()
{
QPushButton quit("Quit");
QWidget window;

quit.setParent(&window);
}

如果反过来,由于 window 后创建,程序关闭时先调用 window 的析构函数(此时 quit 被第一次析构)。接着调用 quit 的析构函数(此时 quit 被第二次析构),这时由于被两次析构,所以出问题了。

这种特殊情况在编程中很隐蔽,不容易发现。因为编译的时候不会报错,只有运行时才会产生问题。

解决办法如下:

  1. 栈对象的析构顺序
    两个栈对象 window(父)和 quit(子)会在 main 函数结束时按创建相反的顺序析构:

    • 先析构 window(后创建)
    • 再析构 quit(先创建)
  2. Qt 的父子对象机制
    当父对象(window)被销毁时,Qt 会自动递归销毁其子对象(quit)。由于 window 是栈对象,其析构过程会触发 delete &quit,但 quit 同样是个栈对象(未通过 new 在堆上分配)。

  3. 双重释放错误

    • 第一次析构(错误):通过 window 销毁子对象时,对栈地址 &quit 调用 delete,本质是对非堆内存进行释放。
    • 第二次析构(正常):quit 离开作用域时,触发栈对象的自然析构,再次调用析构函数。

    ❌ 这将引发双重释放(double free)或内存访问冲突,导致程序崩溃。

我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。

三、Qt窗口坐标体系

Qt的窗口坐标系以左上角为原点,X 向右增加,Y 向下增加。

对于嵌套窗口,其坐标是相对于父窗口来说的。




转载自:唯有自己强大 如有侵权,在下方评论 立刻删除。