【Qt】Qt界面构建与对象管理:从 “Hello World“ 到内存释放

发布于:2024-04-25 ⋅ 阅读:(23) ⋅ 点赞:(0)


在软件开发中,构建用户界面是至关重要的一步。Qt作为一个跨平台的C++框架,提供了强大的界面构建工具和对象树管理机制,使得界面开发变得简单高效。本文将介绍两种实现 “Hello World” 界面的方法:一种是通过图形化界面创建控件,另一种是通过纯代码方式创建控件,并探讨了Qt框架中的对象树管理机制与前端开发中的DOM对象树的类比。

1. 通过图形化界面创建控件

Qt提供了可视化设计工具,通过拖拽控件、设置属性和信号槽等方式,可以快速构建用户界面。下面是通过图形化界面创建一个显示 “Hello World” 的控件的步骤:

  1. 打开Qt Creator,创建一个新的Qt Widgets Application项目。
  2. 在设计模式下,从左侧的控件库中拖拽一个Label控件到界面上。
  3. 在属性编辑器中设置Label的文本为 “Hello World”。
  4. 编译运行程序,即可看到界面上显示了 “Hello World”
    在这里插入图片描述

2. 通过纯代码方式创建控件

虽然图形化界面设计工具方便直观,但有时候我们也需要通过纯代码的方式来创建控件。下面是通过纯代码方式在Qt中创建一个显示 “Hello World” 的控件的示例代码:

// widget.cpp
// 一般通过代码来构造界面的时候
// 通常会把构造界面放到 Widget/MainWindow 的构造函数中
#include "widget.h"
#include "ui_widget.h"

#include<QLabel>

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

     // 创建对象的时候可以在栈上创建栈上,也可以在堆上创建
     // QLabel label(this) // 在栈上创建会,一下马上消失
    // 更推荐这种直接在堆上面创建的方式
    QLabel* label = new QLabel(this); //给当前的 label对象,指定一个父对象
    label->setText("hello world"); // 在QString 中也提供了C风格字符串作为参数的构造函数.
    // 不显式构造QString,上述代码中,C风格字符串也会隐式构造成QString对象.
}

Widget::~Widget()
{
    delete ui;
}

Qt在早期确实为了解决C++标准库的不足而引入了自己的一套基础类和容器类,以便支持Qt框架的开发。下面是一些常用的Qt自带容器类及其功能:

  1. QString:用于处理字符串,支持 Unicode 字符串,提供了丰富的字符串操作方法。
  2. QVector:动态数组,类似于std::vector,但提供了更多的功能和操作。
  3. QList:链表,类似于std::list,但也提供了额外的操作和功能。
  4. QMap:字典,类似于std::map,用于键值对的映射。
  5. QSet:集合,类似于std::set,用于存储不重复的元素。

这些Qt自带的容器类在Qt的API中被广泛使用,因此在Qt开发中,通常会见到这些类的使用。虽然这些类与标准库中的类功能类似,但它们通常提供了更多的特性和对Qt框架的更好的支持。

在使用这些Qt容器类时,可以方便地进行与标准库容器类的互操作,例如QString和std::string之间的转换。同时,Qt的容器类内部已经处理了一些字符编码的细节,使得处理
Unicode 字符串更为方便。相比之下,标准库中的std::string可能需要额外的处理来支持特定的字符编码。

总的来说,Qt提供的这些容器类为开发者提供了更多选择,同时也更好地与Qt框架集成,使得开发过程更加高效和便捷。

// 更推荐这种直接在堆上面创建的方式
QLabel* label = new QLabel(this); //给当前的 label对象,指定一个父对象 
// 此处通过new的方式创建对象,也就是为了把这个对象的声明周期,交给Qt的对象树统一管理。

3. 对象树管理与内存管理

Qt框架中的对象树管理机制类似于前端开发中的DOM对象树,Qt中也是类似,也是搞了一个对象树,也是N叉树,把界面上的各种元素组织起来了 。通过将控件添加到父控件中,Qt能够自动管理控件的生命周期,从而避免内存泄漏。在上述示例中,我们给QLabel控件指定了父对象为当前的Widget,这样当Widget被销毁时,其子控件也会被自动销毁,从而确保内存的正常释放。
在这里插入图片描述

证明对象是被自动销毁了的:
新建一个C++类:
在这里插入图片描述
在这里插入图片描述
Qt Creator 是帮我们生成了一些代码,但是没有完全生成,头文件没给咱们自动包含,只能自己手动包含了。

在这里插入图片描述

// widget.cpp
#include "widget.h"
#include "ui_widget.h"

#include "mylabel.h"

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

    // 使用自己的定义的 Mylabel 代替原来的 QLabel, 所谓的“继承” 本质上是扩展,保持原来的功能不变的基础上,
    // 给对象扩展出一个析构函数,通过这个析构函数,答应一个自定义日志,方便咱们观察运行效果。
    MyLabel* label = new MyLabel(this);
    label->setText("hello world");
}

Widget::~Widget()
{
    delete ui;
}
// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
// mylabel.cpp
#include "mylabel.h"
#include <iostream>

// QLabel(parent) 就是这个调用父类构造函数,调用这个,才能让咱们自己类的对象加入到Qt对象树中
MyLabel::MyLabel(QWidget* parent) : QLabel(parent)
{

}

MyLabel::~MyLabel()
{
    std::cout << "MyLable 被销毁!" << std::endl;
}
// mylabel.h
#ifndef MYLABEL_H
#define MYLABEL_H

#include <QLabel>

class MyLabel : public QLabel
{
public:
    // 构造函数使用带 QWidget* 版本的
    // 这样才能保证咱们自己的对此昂能够加到对象树上
    MyLabel(QWidget* parent);
    ~MyLabel();
};

#endif // MYLABEL_H

当关闭窗口的时候
在这里插入图片描述
在关闭窗口时,预期输出 “MyLabel 被销毁!”,但实际上输出了乱码。这是因为输出的中文字符在计算机中存储时所占字节数取决于字符集的不同。

在计算机中,一个汉字,占几个字节?
针对这个问题,只要你回答出一个具体的数字,就一定是错的 !!
前提条件:当前中文编码使用的是哪种方式(字符集) 计算机存的其实是二进制数字 英文字母,怎么表示的 ?
ASCII码表,规定了每个字符,都有一个对应的数字来表示 。
目前,表示汉字字符集,主要是两种方式.

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

出现乱码的原因就是:
如果你字符串本身是utf8 编码的,但是终端(控制台)是按照gbk的方式来解析显示的,此时就会出现乱码。
(拿着utf8这里的数值,去查询gbk的码表),此时就会出现乱码了 !!

std::cout << "MyLable 被销毁!" << std::endl;

在这里插入图片描述

Qt中有一个好东西,QString:是可以帮助我们自动的处理编码方式的。
不止是QString,Qt也提供了专门用来打印日志的工具,也能自动处理编码方式,Qt中提供了一个qDebug()工具,借助这个,就可以完成打印日志的过程.很好的处理字符编码。(不需要程序员关注了,内部帮咱们搞好了)

在这里插入图片描述
这样就正常了
在这里插入图片描述

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

小结:

  1. QLabel类:能够在界面上显示字符串,通过setText()方法设置显示内容,参数为QString。
  2. 内存泄漏/资源泄漏:在Qt中需要注意及时释放对象,防止内存泄漏或者文件资源泄漏。
  3. 对象树:Qt通过对象树来管理界面控件对象的释放。推荐使用new方式在堆上创建对象,并指定父对象,在构造函数中将对象挂到对象树上,以便统一释放对象。创建的时候,在构建函数中,指定父对象(此时才会挂到树上),如果你的对象没有挂到对象树上,就必须记得手动释放
  4. 继承扩展:通过继承Qt内置的类,可以对现有控件进行扩展。例如,可以创建自定义类MyLabel,继承自QLabel,并重写析构函数,在其中加入日志记录以观察对象释放过程。继承本质上是对现有代码进行扩展,可以重写控件中的功能以实现个性化需求。
  5. 乱码问题和字符集:在处理中文字符时,需要注意字符集的问题。常见的字符集有GBK和UTF-8,输出乱码可能是因为字符集不匹配导致的。
  6. 日志打印:在Qt中推荐使用qDebug()函数打印日志信息,而不是使用cout。qDebug()能够自动处理字符编码,方便统一进行日志输出管理,同时能够通过编译开关一键式关闭调试信息输出。