Qt 验证自动释放 + 乱码问题(6)

简介:上一篇文章写到,当new出一个控件对象并且将它挂到对象树上,无需我们手动释放该对象,是因为在一个合适的时机,会统一释放该对象树上的控件对象。这篇文章主要内容是去验证确实会自动释放该对象树上的控件对象,并且讨论Qt终端输出的乱码问题的缘由,并且如何去解决乱码问题,并且对这两篇文章的内容做个小结,也能由此看出,虽然只是一个简单的hello world程序,却能连续许多的相关知识点(全是干货)

验证自动释放(对象树上的对象)

先创建一个项目,再左上角找到文件,新建一个项目,此时选择新添加类

在这里插入图片描述

这里输入你添加类的名称后,选择它的基类。如果点开三角没看到,就直接手动输入QLabel,其它的就不用管了,直接下一步

在这里插入图片描述

完成后,左边文件就会自动添加mylabel.h与mylabel.cpp

// mylabel.h
#ifndef MYLABEL_H
#define MYLABEL_H


class myLabel : public QLabel
{
public:
    myLabel();
};

#endif // MYLABEL_H

// mylabel.cpp
#include "mylabel.h"

myLabel::myLabel()
{

}

我们会发现添加的mylabel.h,它并没有包含头文件 QLabel.h,这操作其实挺挫的,帮我们生成了一些代码,但好像又没完全生成,连头文件都没有自动包含,那也没啥办法,就只能手动的去包含。不过,它生成的默认构造很明显也不是我们需要的,得手动写 mylabel(QWidget* parent),这样才能确保咱们自己的对象能够加到对象树上

#ifndef MYLABEL_H
#define MYLABEL_H
#include<QLabel>

class myLabel : public QLabel
{
public:
    myLabel(QWidget* parent);
};

#endif // MYLABEL_H

处理完mylabel.h文件,再来看看mylabel.cpp文件。这里分享一个小技巧,在Qt Creator中,可以通过F4快捷键快速切换.h和对应的.cpp文件,这也是C++IDE的常规功能下面构造函数中,QLabel(parent) 它会调用父类的构造函数去初始化,这样才能让咱们自己类的对象加入到Qt 对象树上,具体看Widget.cpp文件的代码

#include "mylabel.h"
myLabel::myLabel(QWidget* parent) 
    : QLabel(parent)
{
}

完成了mylabel.hmylabel.cpp,接下来Widget.cpp文件的操作就是复刻Qt 创建hello world程序的操作了,看看myLabel(this);这就是为啥要手动写构造函数的缘故

#include "widget.h"
#include "ui_widget.h"
#include "mylabel.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    myLabel* label = new myLabel(this);
    label->setText("hello world");
}

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

写到这里界面上已经能显示出hello world了,但如何去观察该对象会自动释放呢?那对象要自动释放,是不是只能从析构函数入手,所有只需要在 myLabel类中写个析构函数即可判断这里再分享一个小技巧,在写完一个函数的声明后,按下 alt + enter就可以自动在对应的cpp文件中添加函数的定义了

class myLabel : public QLabel
{
public:
    myLabel(QWidget* parent);
    ~myLabel();// 先写析构函数的声明
};

它如果会自动释放对象的话,是一定会走析构函数的,可能会遇到点疑问(其实是我自己搞蒙了,嘿嘿)?为啥这析构函数不去delete 对象呢?首先要分清楚咱现在探究的是new出的这个对象我们没有delete,它却会自动释放,那它释放是肯定要去走析构函数的啊,其次它的成员变量也没有指向一块空间的啊,所有不需要在析构函数中delete啥

myLabel::~myLabel()
{
    std::cout << "Mylabel已经被销毁" << std::endl;
}

当你运行程序时,你会发现下面的应用程序输出啥也没有,这是因为你还没有关闭界面,没有自动释放,关闭界面后会看到应用输出程序输出中显示:Mylabel宸茬粡琚攢姣,这有是有了但咋是乱码呢?那这证明了该对象会自动析构,现在迎来了一个新问题:乱码的缘由是什么,如何去解决乱码

在这里插入图片描述

乱码问题的缘由

在这里插入图片描述

通过上述的验证,有日志显示就说明了析构函数是执行了,虽然没有手动delete,但是由于把MyLabel挂到了对象树上,此时窗口销毁的时候,就会自动销毁对象树中的所有对象,MyLabel的析构时执行到了。但实际的显示结果却出现乱码,乱码这个事情咱们以后是会经常遇到的而乱码问题出现的原因有且只有一个(不局限于C++)就是编码方式不匹配

那编码方式不匹配是啥意思呢?就是如果你字符串本身是utf8编码的,但是终端(控制台)却是按照 gbk(后面解释)的方式来解析显示的,此时就会出现乱码。(就好像是拿着 utf8 的数值就查询 gbk 的码表,那能对才见了鬼了,此时就会出现乱码

  • 现在咱提出一个问题,一个汉字占多少个字节。这还用想吗?在C语言中就知道一个汉字占两个字节,但真的是这样的吗?其实针对这个问题来说,只要你回答出一个具体的数字,那就一定是错的!回答这个问题之前,要考虑到它的前提条件:也就是当前中文编码使用的是哪种方式(字符集)
  • 大家都知道计算机中只存储二进制数字,那英文字母怎么表示的呢?就是通过ASCII码表去规定每个字符都有一个对应的数字去表示,而表示英文只需要用到一个字节(也就八个比特位),毕竟英文字母数量就那么几个。而表示中文一个字节根本不够,因为中文常用字就大概四千个,算上生僻字就有差不多六万个左右。
  • 通过ASCII码表去规定英文字母和一些字符,那自然会有一个大表格会给每个汉字去分配一个整数(计算机只要把这六万个左右的整数以及对应的汉字直接存起来,遇到相应的表示汉字的二进制数字,再根据这张表找到对应的汉字输出即可,而对于计算机来说,六万多个符号的表格就是小case)
  • 那具体这个表格是什么样子的呢?具体每个汉字都使用哪个数字表示?这就不一定了,这个表示汉字的字符集有很多种,而不同的字符集表示同一个汉字,所使用的数字各不相同。
  • 目前表示汉字字符集主要有两种方式,第一种:GBK 使用2个字节表示一个汉字,其中Windows简体中文版,默认的字符集就是GBK。第二种:UTF-8 / utf8 变长编码,它表示一个符号,使用的字节数有变化(2-4个)但是在utf8中,一个汉字一般是3个字节,其中Linux默认就是 utf8
  • 一个汉字它具体的utf8 / GBK 编码的数值是多少,可以通过一些在线工具查看

std::cout << "Mylabel已经被销毁" << std::endl;这个字符串使用的编码方式和当前mylabel.pp文件的编码方式是一致的,如何去查看该文件的编码方式呢?以记事本打开.cpp文件后,在将该文件另存为,就可以看到该文件的编码方式了,如果下面显示的是 ANSI,则说明这个文件是GBK编码

在这里插入图片描述

那现在终端出现了乱码,那就证明Qt Creator内置的终端不是以 utf8 的方式来显示字符串的,另外该终端好像无法去设置字符编码,但可以从其它的方式入手

解决乱码问题

1. 使用QString

QString可以去帮助我们自动的处理编码方式,这了解一下就行了,谁会去用这玩意啊。所以着重介绍第二种方法

myLabel::~myLabel()
{
    QString str("Mylabel已经被销毁");
    fprintf(stdout, "%s", str.toLocal8Bit().constData());
}

2. qDebug()

Qt其实提供了专门用来打印日志的工具,也就是qDebug()工具,借助这个就可以完成打印日志的过程,还能很好处理字符编码(就无需我们去关注内部啦),这用起来嘎嘎的香。这里咱阐述一下qDebug(),它是一个宏,里面封装了一个QDebug对象,而只需要直接使用qDebug()就行了,它这里其实就相当于重载了 “<<” 移位运算符去输出(没说它就是重载哈),可以把它当成cout来使用

#include<QDebug>
myLabel::~myLabel()
{
    qDebug() << "Mylabel已经被销毁了";
}

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

小结

  1. 认识QLabel类,能够在界面上显示字符串,通过 setText 来设置参数QString (Qt中把C++里的很多容器类,进行了重新封装以及历史原因)
  2. 内存泄露/文件资源泄露
  3. 对象树,Qt中通过对象树来统一的释放界面的控件对象,Qt 还是推荐使用 new的方式在堆上创建对象,通过对象树去统一释放对象,其次创建对象的时候,在构造函数中要指定父对象(此时才会挂到对象树上),如果你的对象没有挂到对象树上,就必须要记得手动释放!
  4. 通过继承自Qt内置的类,就可以达到对现有控件进行功能扩展效果(因为Qt 内置的 QLabel是没法看到销毁过程的,那为了看清楚,就自己去创建类MyLabel,继承自 QLabel,再重写析构函数,在析构函数中,加上日志就能直观的观察到对象释放的过程了)其实面向对象"继承",本质上是对现有代码进行的"扩展"当然也可以重写控件中的任何功能,不仅仅是析构函数,后期学习总有一些控件是达不到要求的,那此时就得去功能扩展
  5. 乱码问题和字符集~MySQL(很多地方都涉及到)
  6. 如何在Qt 中打印日志,作为调试信息。使用cout固然可以,但是并不是上策(字符编码处理的不好,也不方便统一进行关闭)Qt 中推荐使用qDebug()完成日志的打印。那之前调试程序都是用调试器(VS/gdb),为啥要打印日志调试呢?(因为有时调试器很多时候是有局限性的,是无法使用的,比如说,假设当前存在的 bug是一个概率性的 bug,出现的概率是1%甚至更小,如果要想去调试,那得调多少次才会出现这一次bug,但如果我们去使用日志,就可以很好的解决这种问题,我直接让这个代码按照逻辑循环走几万次,总会出现几次bug吧,不过无论是哪种方式,本质上都是观察程序执行的中间过程和中间结果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值