【QT】第一个 QT程序(对象树)

ops/2025/2/21 17:22:20/

🌈 个人主页:Zfox_
🔥 系列专栏:Qt

目录

  • 一:🔥 QtHelloWorld程序
    • 🦋 使⽤"标签"实现
      • 纯代码⽅式实现
      • 可视化操作实现
    • 🦋 使⽤"按钮"实现
      • 可视化操作实现
      • 纯代码实现
    • 🦋 内存泄露
  • 二:🔥 认识对象模型(对象树)
    • 🦋 什么是对象树
    • 🦋 验证对象树
    • 🦋 utf8 和 gbk
  • 三:🔥 解决编码问题
  • 四:🔥 Qt 编程注意事项
    • 🦋 Qt 中的命名事项
    • 🦋 Qt 中的快捷键
  • 五:🔥 Qt 析构函数执行顺序
  • 六:🔥 共勉
    • 🦋 小结

一:🔥 QtHelloWorld程序

🦋 使⽤"标签"实现

纯代码⽅式实现

📚 我们点击 widget.cpp 里面,会有一个 widget 的构造函数和析构函数,我们一般使用代码进行编辑界面的时候,一般都是在 widget 的构造函数中实现,因为在 main 函数中调用了 widget 类之后就直接 show 了,所以卸载构造函数中的时候,一旦执行到了 show 就一定可以显示出设计的界面。

在这里插入图片描述

📚 实现效果:

在这里插入图片描述

void setText(const QString &);

🦁 这里我们会发下使用字符串的时候并不是我们 C++ 使用的标准库中的 string,而是 Qt 自己包装好的字符串 QString 。这个其实也是历史原因,Qt 诞生于 1991 年,那个时候 C++ 还没有定标准,而 Qt 为了更好的开发就自己包装了一些容器。但是我们也还是可以使用 C++ 的标准库中的容器来使用

  • QString 用起来其实也要比 std::string 好一点,因为 QString 内部已经对于字符编码做了处理,而不像 std::string 啥都没做

可视化操作实现

  1. 双击:"widget.ui"⽂件;
    在这里插入图片描述
  2. 拖拽"标签"⾄UI设计界⾯中,并双击修改标签内容;
    在这里插入图片描述

🔥 此时 ui 界面就会生成对应的 XML 格式代码,这个时候 qmake 就会根据这个 XML 代码生成对应的 C++ 代码,我们也可以在同目录下找到这个 C++ 代码
在这里插入图片描述

  1. 实现效果如下图所⽰:
    在这里插入图片描述

🦋 使⽤"按钮"实现

可视化操作实现

  1. 双击:"widget.ui"⽂件;
    在这里插入图片描述

  2. 拖拽控件⾄ui界⾯窗⼝并修改内容;
    在这里插入图片描述

  3. 构建并运⾏,效果如下所⽰:
    在这里插入图片描述

  • 这里的按钮的确可以点击,但是却没有任何反应,这个就设计到我们后面学的信号槽知识,后面会说的
  • QT 的信号槽机制:本质上就是给按钮的点击操作,关联上一个处理函数,当用户点击的时候,就会执行这个处理函数

这里我们的按钮没有任何功能,假如我们要实现一定的功能,那该怎么做呢?

打开 widget.ui 文件,查看设计的右下角,则有

在这里插入图片描述
📚 如下代码:

在这里插入图片描述

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);// 按钮的点击操作 -- 信号槽// 在 Linux 网络编程那也有个connect 函数,那里用来给 TCP socket 建立连接的,写 TCP 客户端的时候,就需要先建立连接才能读写数据// ui->pushButton:谁发出的信号// &QPushButton::clicked:发出了啥信号,点击按钮的时候自动触发该信号// this: 谁来处理这个信号// Widget::handle:具体怎么处理connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);  // 访问到 form file(ui 文件)中创建的控件}Widget::~Widget()
{delete ui;
}void Widget::handleClick()
{if(ui->pushButton->text() == "Hello World"){ui->pushButton->setText("Hello IsLand");}else{ui->pushButton->setText("Hello World");}
}

在这里插入图片描述

📚 返回上级目录查看 ui_widget.h 文件:

在这里插入图片描述

📚 因此我们也可以把 PushButton 改成其他的,如下:

在这里插入图片描述
🧑‍💻 再次查看 ui_widget.h 文件,如下:
在这里插入图片描述
结论:在 objectName 中,设置成什么值,生成的变量名就叫啥名字就可以根据这个名字来获取到对应的控件的变量了

纯代码实现

widget.h

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QPushButton>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);void handleClick();~Widget();private:Ui::Widget *ui;QPushButton* myButton;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);myButton = new QPushButton(this);myButton->setText("Hello World");connect(myButton, &QPushButton::clicked, this, &Widget::handleClick);  // 访问到 form file(ui 文件)中创建的控件
}Widget::~Widget()
{delete ui;
}void Widget::handleClick()
{if(myButton->text() == QString("Hello World")){myButton->setText("Hello IsLand");}else{myButton->setText("Hello World");}
}

💻 实现效果如下图:
在这里插入图片描述
🆚 两个版本比较:

  • 图形化实现:此时按钮对象不需要咱们自己 new。new 对象的操作已经是被 Qt 自动生成了而且这个按钮对象,已经作为 ui 对象里的一个成员变量了,也无需作为 Widget 的成员
  • 纯代码实现:按钮对象是咱们自己 new 的,为了保证其他函数中能够访问到这个变量,就需要把按钮对象,设定为 Widget 类的成员变量

🦋 内存泄露

📚 在上面的代码实现中,我们使用 new 创建了对象,在栈上开辟了一块空间之后,但是我们没有使用 delete 进行释放控件,这样不就会导致内存泄漏啊

  • 其实上述代码在 Qt 不会产生内存泄露, label 对象会在合适的时候被析构释放,之所以能够把对象释放掉,主要是因为把这个对象挂到了 对象树 上
  • 前端开发 (网页开发) 也涉及到 类似的 对象树 (DOM),本质上也是一个树形结构 (N 又树), 通过树形结构把界面上的各种元素组织起来
  • Qt 中也是类似,也是搞了一个对象树,也是 N 又树,把界面上的各种元素组织起来了

在这里插入图片描述

  • 用对象树把这些内容组织起来,最主要的目的:就是为了能够在合适的时机(窗口关闭和销毁),把这些对象统一进行释放。通过这个树形结构,就把界面上要显示的这些控件对象都组织起来了
  • 这里的树上的这些对象,统一销毁是最好不过的,如果某个对象提前销毁,此时就会导致对应的控件就在界面上不存在了。

二:🔥 认识对象模型(对象树)

🦋 什么是对象树

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

  • QObject 是以对象树的形式组织起来的。
    • 当创建一个 QObject 对象时,会看到 QObject 的构造函数接收一个 QObject 指针作为参数,这个参数就是 parent,也就是父对象指针。
    • 这相当于,在创建 QObject 对象时,可以提供一个其父对象,我们创建的这个 QObject 对象会自动添加到其父对象的 children()列表。
    • 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)

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

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

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

  • 但是一旦我们的代码稍微修改一点就会出错
{QLabel label("hello"); // 指定父类是widowQWidget window;label.setParent(&window);
}

情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说,quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++不允许调用两次析构函数,因此,程序崩溃了。

在这里插入图片描述
🍉 由此我们看到,Qt 的对象树机制虽然在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,即 在 Qt 中,尽量将其开辟在堆上,并指定好其 parent 父类对象

🌰 比如:

🧑‍💻 如果我们把最初的代码改成在栈上开辟的话我们运行程序会发现什么都没有

在这里插入图片描述

💻 Qt对象树如图:
在这里插入图片描述

🦋 验证对象树

🤔 首先我们自定义一个 label 类,并在析构部分打上日志,如下步骤:

  1. 选中工程名,鼠标右键 -------> “add new…”(或 “添加新文件” )… 建立出一个新的项目

在这里插入图片描述

  1. 点击"完成"之后,⼿动创建类的头⽂件以及源⽂件

在这里插入图片描述

  • 此时我们可以按 F4 来进行 .h 文件 和 .cpp 文件 来回切换
    在这里插入图片描述
  • 使用快捷键 Alt + Enter 可以实现快速定义

🦭 此时我们的 mylabel.cpp 中就是

#include "mylabel.h"
#include <iostream>MyLabel::MyLabel(QWidget* parent) : QLabel(parent)
{
}MyLabel::~MyLabel()
{std::cout << "MyLabel 被销毁" << std::endl;
}

🦭 midget.cpp 中的代码就是

#include "widget.h"
#include "ui_widget.h"
#include "mylabel.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this); // 将form file生成的界面和我们当前的widget进行关联起来// 使用自己定义的 MyLabel 代替原来的 QLabel,所谓的 “继承” 本质上是扩展,保持原有功能不变的基础上// 给对象扩展出一个析构函数,通过这个析构函数,打印一个自定义日志,方便观察程序运行结果MyLabel *label = new MyLabel(this);label->setText("Hello World");}Widget::~Widget()
{delete ui;
}

🔥 此时我们运行代码,就可以看到窗口上有 Hello World 的字样,只要我们关闭窗口,就会输出我们的日志
在这里插入图片描述

这里也是验证了对象树自动释放对象的能力

  • 这里日志是有的,说明析构函数是执行了,虽然没有 手动 delete,但是由于把 MyLabel 挂到了对象树上,此时窗口被销毁的时候,就会自动销毁对象树中的所有对象!!所以 MyLabel 的析构是执行到了

但是这里似乎出现了乱码的情况

  • 乱码问题出现的原因有且仅有一个 编码方式不匹配(不仅仅局限于 C++)
  • 比如字符串本身是 utf8 编码的,但是终端(控制台)是按照 gbk 的方式来解析显示的,就会出现乱码(相当于拿着 utf8 的数值 去查询 gbk 的 码表)

🦋 utf8 和 gbk

目前,表示汉字字符集, 主要是两种方式

  1. GBK(中国大陆) 使用 2 个字节表示一个汉字!Windows 简体中文版,默认的字符集就是 GBK
  2. UTF-8 / utf8 变长编码,表示一个符号,使用的字节数有变化,2-4但是在 utf8 中。一个汉字一般是 3 个字节
  3. Linux 默认就是 utf8

三:🔥 解决编码问题

💻 我们用文本文件打开 mylabel.cpp 文件,可以看到这个文件的编码方式

在这里插入图片描述

🦁 可看到这个文件的编码方式是 utf8,但是 Qt 的这个终端的编码方式肯定不是 utf8 ,但是 Qt 不支持修改编码方式,所以这里我们就需要借助 Qt 自己提供的打印日志的功能 qDebug,或者使用 QString 来处理编码方式。

#include "mylabel.h"
#include <iostream>#include <QtDebug> // 头文件MyLabel::MyLabel(QWidget* parent) : QLabel(parent)
{}MyLabel::~MyLabel()
{// std::cout << "MyLabel 被销毁" << std::endl;qDebug() << "MyLabel 被销毁"; // qDebug 这个宏封装了 QDebug 对象,使用 qDebug 相当于使用 cout
}
#define qDebug QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug

📚 此时中文就不会出现乱码情况,如下:

在这里插入图片描述

  • 后续在 Qt 中,如果想通过打印日志的方式,输出一些调试信息,都优先使用 qDebug。虽然使用 cout 也行,但是 cout 对于编码的处理不太好,在 windows 上容易出现乱码(如果是 Linux 使用 Qt Creator, 一般就没事了,Linux 默认的编码一般都是 utf8)
  • 使用 qDebug, 还有一个好处:打印的调试日志是可以统一进行关闭的!!
  • 输出的日志,是开发阶段、调试程序的时候使用的。如果你的程序发布给用户,不希望用户看到这些日志的!!
  • qDebug 可以通过编译开关,来实现一键式关闭

🧑‍💻 之前调试程序, 都是用调试器 .VS/gdb,这里为啥要打印日志调试呢??

  • 调试器很多时候是有局限性的,是无法使用的,
  • 假设当前 bug 是一个概率性的 bug。出现的概率是 1% 甚至更小要想调试,无法使用调试器了
  • 使用 日志 就可以很好的解决这种问题
  • 无论是哪种方式本质上都是观察程序执行的中间过程和中间结果~

四:🔥 Qt 编程注意事项

🦋 Qt 中的命名事项

  • 类名:首字母大写,单词和单词之间首字母大写;
  • 函数名及变量名:首字母小写,单词和单词之间首字母大写
  • 起的名字要有描述性,不要使用 abc, xyz 这种比较无规律的名字来描述
  • 如果名字比较长,由多个单词构成的,就需要使用适当的方式来进行区分不同单词
  • 一般可以采用 蛇形命名法 或者 驼峰命名法

🦋 Qt 中的快捷键

在这里插入图片描述
🔧 其里面内置 Vim 插件,因此我们也可以按照使用 Vim 操作来使用

  • 注释:Ctrl + /
  • 运行:Ctrl + R
  • 编译:Ctrl + B
  • 字体缩放:Ctrl + 鼠标滑轮
  • 查找:Ctrl + F
  • 整行移动:Ctrl + Shift + ↑/↓
  • 帮助文档:F1
  • 自动对齐: Ctrl + i
  • 同名之间的.h和.cpp 的切换:F4
  • 生成函数声明的对应定义:Alt + Enter
  • 跳转到控件定义: 鼠标左键 + Ctrl,返回就是:Alt + <-

五:🔥 Qt 析构函数执行顺序

mylabel.cpp

#include "mylabel.h"
#include <iostream>#include <QtDebug>MyLabel::MyLabel(QWidget* parent) : QLabel(parent)
{qDebug() << "mylabel.cpp 构造函数被调用";
}MyLabel::~MyLabel()
{qDebug() << "mylabel.cpp 析构函数被调用";
}

widget.cpp

#include "widget.h" // 创建生成时的文件
#include "ui_widget.h"
#include <QLabel>  // 包含标签的头文件
#include "mylabel.h"
#include <QDebug>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this); // 将form file生成的界面和我们当前的widget进行关联起来// 使用自己定义的 MyLabel 代替原来的 QLabel,所谓的 “继承” 本质上是扩展,保持原有功能不变的基础上// 给对象扩展出一个析构函数,通过这个析构函数,打印一个自定义日志,方便观察程序运行结果MyLabel *label = new MyLabel(this);label->setText("Hello World");}Widget::~Widget()
{qDebug() << "midget 析构函数被调用";delete ui;
}

🔧 运行结果如下:
在这里插入图片描述

  • 对象树确保的是先释放子节点的内存,后释放父节点的内存
  • 而析构函数的调用顺序则不一定遵守上述要求,因此看到子节点的析构执行顺序反而在父节点析构顺序之后

注意:调用析构函数和释放内存并非是同一件事情。

六:🔥 共勉

🦋 小结

  1. 认识 QLabel 类,能够在界面上显示字符串。通过 setText 来设置的,参数 QString(Qt 中把 C++ 里的很多容器类,进行了重新封装,历史原因)
  2. 内存泄露 / 文件资源泄露
  3. 对象树Qt 中通过对象树,来统一的释放界面的控件对象,Qt 还是推荐使用 new 的方式在堆上创建对象,通过对象树, 统一释放对象创建对象的时候,在构造函数中, 指定父对象 (此时才会挂到对象树上),如果你的对象没有挂到对象树上,就必须要记得手动释放 !!
  4. 通过继承自 Qt 内置的类,就可以达到对现有控件进行功能扩展效果 Qt 内置的 QLabel,没法看到销毁过程的。为了看清楚,就创建类 MyLabel, 继承自 QLabel 重写析构函数。在析构函数中加上日志,直观的观察到对象释放的过程了,也可以重写控件中的任何功能。不仅仅是析构函数, 达到功能扩展目的
  5. 乱码问题 和 字符集 ~ MySQL (很多地方都涉及到)
  6. 如何在 Qt 中打印日志,作为调试信息使用 cout 固然可以, 但是并不是上策(字符编码处理的不好,也不方便统一进行关闭)Qt 中推荐使用 qDebug() 完成日志的打印

😋 以上就是我对 【Qt】新世界大门:概述 的理解, 觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~ 😉
在这里插入图片描述


http://www.ppmy.cn/ops/160294.html

相关文章

【Python项目】文本相似度计算系统

【Python项目】文本相似度计算系统 技术简介&#xff1a;采用Python技术、Django技术、MYSQL数据库等实现。 系统简介&#xff1a;本系统基于Django进行开发&#xff0c;包含前端和后端两个部分。前端基于Bootstrap框架进行开发&#xff0c;主要包括系统首页&#xff0c;文本分…

Windows Docker Desktop部署MaxKB详细教程

MaxKB(Max Knowledge Base)是一款基于大语言模型(LLM)和检索增强生成(RAG)技术的开源知识库问答系统,旨在帮助企业、教育机构及研究组织高效管理知识并提供智能问答服务。 一、前期准备工作 首先,你需要再你的Windows电脑上安装 Docker Desktop。本章教程,不介绍如何安…

设计模式3:代理、适配器、装饰器模式

代理模式&#xff08;Proxy Pattern&#xff09; 代理模式的本质是⼀个中间件&#xff0c;主要⽬的是解耦合服务提供者和使⽤者。使⽤者通过代理间接访问服务提供者&#xff0c;便于后者的封装和控制。是⼀种结构型设计模式。 静态代理和动态代理区别 静态代理&#xff1a;编…

【核心算法篇十六】《DeepSeek强化学习:MuZero算法核心解析》

引言:当强化学习不再需要环境说明书 2016年AlphaGo击败李世石让世界认识了蒙特卡洛树搜索(MCTS)的威力,2017年AlphaZero用通用算法征服围棋、象棋、将棋三大棋类。但它们的共同局限在于:必须预先知道环境的完整规则(比如棋盘如何落子、得分如何计算)。而2019年DeepMind…

WPF7-数据绑定基础

1. WPF数据绑定试验 1.1. 数据绑定的核心实现1.2. {Binding}语法1.3. 理解 DataContext 1. WPF数据绑定试验 以下是一个简单的 WPF 数据绑定示例&#xff0c;使用两个TextBox控件分别表示Name和Age来进行进行数据绑定试验。 数据模型类 创建一个 Person 类&#xff0c;包含…

Android嵌套滑动造成的滑动冲突原理分析

嵌套滑动造成的滑动冲突原理分析 场景复现&#xff1a; CoordinatorLayout AppBarLayout Vertical RecyclerView Horizontal RecycleView Horizontal RecycleView 是Vertical RecyclerView的一个子view, CoordinatorLayout 实现了AppBarLayout 和 RecyclerView的协调联动…

光子神经网络:为人工智能注入 “光” 速动力

光子神经网络&#xff08;Photonic Neural Networks, PNNs&#xff09;是利用光子学技术实现的人工神经网络&#xff0c;旨在通过光的高速传输和并行处理能力&#xff0c;突破传统电子计算在速度和能效上的限制。以下是光子神经网络的关键内容&#xff1a; 1. 光子神经网络的优…

VSCode 中使用 Snippets 设置常用代码块

背景 在开发中&#xff0c;有很多代码片段是重复的&#xff0c;例如&#xff1a;vue文件中的模版&#xff0c;react 中的模版&#xff0c;打印的 log 等等&#xff0c;很多很多。对于这些重复性的工作&#xff0c;vscode 官方提供了解决方案-Snippets in Visual Studio Code&a…