当前位置: 首页 > news >正文

【QT开发手册】对象模型(对象树) 窗⼝坐标体系

请添加图片描述

文章目录

  • 前言
  • 一、 认识对象模型(对象树)
  • 二、Qt的析构注意现象
  • 三、对象树的调⽤析构函数和释放内存
  • 四、 Qt窗⼝坐标体系
  • 🚩总结


前言


一、 认识对象模型(对象树)

在Qt中创建很多对象的时候会提供⼀个Parent对象指针,下⾯来解释这个parent到底是⼲什么的。

  • QObject是以对象树的形式组织起来的。

当创建⼀个QObject对象时,会看到QObject的构造函数接收⼀个QObject指针作为参数,这个参数就是parent,也就是⽗对象指针。

这相当于,在创建QObject对象时,可以提供⼀个其⽗对象,我们创建的这个QObject对象会⾃动添加到其⽗对象的children()列表。

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

这种机制在GUI程序设计中相当有⽤。例如,⼀个按钮有⼀个QShortcut(快捷键)对象作为其⼦对象。当删除按钮的时候,这个快捷键理应被删除。这是合理的。

Qt引⼊对象树的概念,在⼀定程度上解决了内存问题。

  • 当⼀个QObject对象在堆上创建的时候,Qt会同时为其创建⼀个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。

  • 任何对象树中的QObject对象delete的时候,如果这个对象有parent,则⾃动将其从parent的children() 列表中删除;如果有孩⼦,则⾃动delete每⼀个孩⼦。Qt保证没有QObject会被delete 两次,这是由析构顺序决定的。

二、Qt的析构注意现象

如果QObject在栈上创建,Qt保持同样的⾏为。正常情况下,这也不会发⽣什么问题。来看下⾯的代码⽚段:

在这里插入图片描述
作为⽗组件的window和作为⼦组件的quit都是QObject的⼦类(事实上,它们都是QWidget的⼦类,⽽QWidget是QObject的⼦类)。

这段代码是正确的,quit的析构函数不会被调⽤两次,因为标准C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作⽤域时,会先调⽤quit的析构函数,将其从⽗对象window的⼦对象列表中删除,然后才会再调⽤window的析构函数。

但是,如果我们使⽤下⾯的代码:
在这里插入图片描述
在这里插入图片描述

情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中,作为父对象的window会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说,quit此时就被析构了。

然后,代码继续执行,在window析构之后,quit也会被析构,因为quit也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用quit的析构函数了,C++不允许调用两次析构函数,因此,程序崩溃了。

由此我们看到,Qt的对象树机制虽然在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯

在Qt中,尽量在构造的时候就指定parent对象,并且⼤胆在堆上创建。

Qt对象树如图:
在这里插入图片描述
代码⽰例

  1. 创建⼀个新⼯程并编译运⾏,⽣成如下窗⼝;
    在这里插入图片描述
  2. 选中⼯程名,⿏标右键------->“addnew…”(或"添加新⽂件")

在这里插入图片描述
在这里插入图片描述

  1. 选择"choose…",弹出如下界面;
    在这里插入图片描述

  2. 点击"下一步",弹出如下对话框;
    在这里插入图片描述

  3. 点击"完成"之后,⼿动创建类的头⽂件以及源⽂件会⾃动添加到⽬标⼯程中;
    在这里插入图片描述

  4. 修改头⽂件;

#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H// #include <QWidget>
#include <QPushButton>  //修改头文件class MyPushButton : public QPushButton     //修改所继承的基类
{Q_OBJECTpublic:MyPushButton(QWidget *parent = nullptr);        //默认提供的构造函数~MyPushButton();   //添加手动创建类的析构函数signals:public slots:};#endif // MYPUSHBUTTON_H

在这里插入图片描述
7. 编写源⽂件;

#include "mypushbutton.h"
#include <QDebug>MyPushButton::MyPushButton(QWidget *parent) : QPushButton(parent) {qDebug() << "我的按钮的构造函数被调用";
}MyPushButton::~MyPushButton()
{qDebug() << "我的按钮的析构函数被调用";
}

在这里插入图片描述
9. 编译并运行;

MyPushButton *btn = new MyPushButton;btn->setText("我的按钮");btn->setParent(this); //设置到对象树中,当窗口关闭时就会自动调用其析构函数 

在这里插入图片描述

  1. 当关闭弹出的对话框时,就会⾃动调⽤按钮的析构函数;
    在这里插入图片描述

  2. 观察析构函数的执行顺序;

QPushButton quit ( "Quit" ) ;
QWidget window ;
quit.setParent ( &window) ;

在这里插入图片描述

  1. 执⾏结果:
    在这里插入图片描述

  2. 执⾏结果分析:
    对象树确保的是先释放⼦节点的内存,后释放⽗节点的内存.

⽽析构函数的调⽤顺序则不⼀定遵守上述要求.因此看到⼦节点的析构执⾏顺序反⽽在⽗节点析构顺序之后.

注意:调⽤析构函数和释放内存并⾮是同⼀件事情

三、对象树的调⽤析构函数和释放内存

理解:

我们需要先明确三个核心概念:对象树的内存管理逻辑析构函数的作用“调用析构函数”与“释放内存”的区别。下面分步骤解释:

一、对象树的核心目标:确保内存释放顺序(子先父后)

对象树(如Qt的QObject对象树)是一种“父子关系”的对象管理机制,父对象会主动管理子对象的生命周期。其核心目的是避免内存泄漏和悬空指针,因此严格规定了内存释放的顺序:
必须先释放所有子对象的内存,再释放父对象的内存

原因很简单:如果父对象先释放内存,子对象可能还依赖父对象的资源(比如指针引用),此时子对象就会变成“悬空对象”,操作它会导致程序崩溃。

二、析构函数的作用:不是释放内存,而是“清理资源”
析构函数是对象被销毁前自动调用的成员函数,它的核心作用是清理对象自身占用的“额外资源”(而非释放对象本身的内存)。例如:

  • 关闭对象打开的文件、网络连接;
  • 释放对象内部动态分配的内存(如对象中用new创建的子数据);
  • 解绑与其他对象的关联等。

析构函数的调用仅仅是“执行清理逻辑”,和“释放对象占用的内存”是完全不同的操作。

三、“调用析构函数”与“释放内存”的本质区别
当我们用delete销毁一个对象时,底层会执行两个独立的步骤:

  1. 先调用该对象的析构函数:完成资源清理(如上述的关闭文件、释放内部数据等);
  2. 再释放对象本身的内存:将对象占用的内存归还给操作系统(此时对象才真正“消失”)。

可见:

  • 析构函数调用是“逻辑上的清理”,发生在内存释放之前;
  • 内存释放是“物理上的回收”,是对象生命周期的最后一步。

四、为什么析构函数调用顺序可能“子后父先”?
对象树虽然严格保证“子对象内存先释放,父对象内存后释放”,但析构函数的调用顺序可能与此相反(父先调用,子后调用)。

举一个具体例子(以Qt的QObject为例):

  1. 当父对象被delete时,首先触发父对象的析构函数(第一步:调用父析构);
  2. 父对象的析构函数内部会主动遍历子对象列表,逐个delete子对象:
    • 对每个子对象,先调用子对象的析构函数(第二步:调用子析构);
    • 子析构完成后,释放子对象的内存(第三步:释放子内存);
  3. 所有子对象都被销毁后,父对象的析构函数执行完毕,最后释放父对象的内存(第四步:释放父内存)。

此时的顺序是:

  • 析构函数调用顺序:父 → 子(父先调用,子后调用);
  • 内存释放顺序:子 → 父(子先释放,父后释放)。

这就解释了“子节点的析构执行顺序反而在父节点之后”,但对象树依然保证了内存释放的正确顺序(子先父后)。

四、 Qt窗⼝坐标体系

坐标体系:以左上⻆为原点(0,0),X向右增加,Y向下增加
在这里插入图片描述
对于嵌套窗⼝,其坐标是相对于⽗窗⼝来说的。

⽰例:使⽤Qt中的坐标系设置控件的位置;

#include "widget.h"
#include "ui_widget.h"
// 添加“按钮”相关头文件,用于创建按钮控件
#include <QPushButton> 
// 用于输出调试信息
#include <QDebug>     Widget::Widget(QWidget *parent)// 调用父类构造,传递父部件指针: QWidget(parent) // 初始化 ui 成员(假设 ui 是 Ui::Widget 类型,由 Qt Designer 生成), ui(new Ui::Widget) 
{//  setupUi 用于初始化界面,将界面元素与当前 Widget 关联ui->setupUi(this); // 创建按钮1,父部件指定为当前 Widget,这样按钮会显示在当前界面上QPushButton *btn1 = new QPushButton("按钮1", this); // 设置按钮1在界面上的坐标(以父部件左上角为原点)btn1->move(200, 300); // 创建按钮2,父部件同样指定为当前 WidgetQPushButton *btn2 = new QPushButton("按钮2", this); // 输出按钮1的坐标信息,x() 获取横坐标,y() 获取纵坐标qDebug() << "按钮1的坐标为: [" << btn1->x() << "," << btn1->y() << "]"; // 输出按钮2的坐标信息qDebug() << "按钮2的坐标为: [" << btn2->x() << "," << btn2->y() << "]"; 
}Widget::~Widget()
{// 释放 ui 指针指向的内存,避免内存泄漏delete ui; 
}

在这里插入图片描述
运⾏结果如下图⽰:
在这里插入图片描述


🚩总结

请添加图片描述

http://www.dtcms.com/a/309954.html

相关文章:

  • EXE加密软件(EXE一机一码加密大师) 最新版1.6.0更新 (附2025最新版本CSDN下载地址)
  • windows mamba-ssm环境配置指南
  • 网络层协议IP
  • 运维端口管理闭环:从暴露面测绘到自动化封禁!
  • 【AI问答记录】grafana接收query请求中未携带step参数,后端基于intervalMs和maxDataPoints等参数计算step的逻辑
  • AcWing 897:最长公共子序列 ← 子序列问题(n≤1e3)
  • “数据管理” 一场高风险的游戏
  • 民航领域数据分类分级怎么做?|《民航领域数据分类分级要求》标准解读
  • 第13届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2022年3月13日真题
  • ip去重小脚本
  • uniapp基础 (一)
  • git pull和git fetch的区别
  • Python爬虫实战:研究OpenCV技术构建图像数据处理系统
  • (转)mybatis和hibernate的 缓存区别?
  • (一)React +Ts(vite创建项目)
  • Flask 路由系统:URL 到 Python 函数的映射
  • 嵌入式学习笔记-MCU阶段-DAY10ESP8266模块
  • 第11届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2020年5月30日真题
  • 嵌入式软件 (SW) 设计文件
  • W3D引擎游戏开发----从入门到精通【10】
  • 永洪科技华西地区客户交流活动成功举办!以AI之力锚定增长确定性
  • 视频生成中如何选择GPU或NPU?
  • UE5多人MOBA+GAS 番外篇:同时造成多种类型伤害,以各种属性值的百分比来应用伤害(版本二)
  • 如何理解推理模型
  • 学习:入门uniapp Vue3组合式API版本(17)
  • 2025网络安全指南
  • PyTorch基础——张量计算
  • 考取锅炉司炉工证需要学习哪些专业知识?
  • Altium Designer 22使用笔记(3)---原理图设计
  • Google play上架/更新频繁被拒是什么原因?