目录
前言:
继上文我们了解了QT的环境,历史的基本知识,以及了解了如何创建一个项目,项目的内容都包括什么,本文我们学习的是如何在GUI界面上打印Hello world,重要的不是hello world本身,而是在hello world背后牵扯到的N个知识点。
那么废话不多说,直接进入主题吧!
No.1->label
图形化的方式:
第一种打印hello world的方式我们打算使用控件label来实现。
我们还是快速的创建了一个项目。
使用label我们可以使用两种方式,第一种是ui界面直接使用图形化的方式创建,第二种是纯代码的方式进行创建。
首先,我们使用图形化的方式进行创建。直接点击form文件,在里面Display widgets找到label。
直接将它拖拽到ui界面即可:
那么运行:
也是成功运行了。
可是,如果只是介绍这点东西,那这QT简直就没啥可以学的。我们不妨看看其他文件出现了什么变化:
在以xml格式形成的form文件中,出现了以上的变化,什么string什么Hello world,什么class Qlabel什么name = label的。
这是因为qmake,当我们往ui界面拖拽了些许控件的时候,qmake在编译的时候就会通过xml文件生成一些C++代码,生成C++的代码我们想看就应该在该ui文件生成的头文件里面看:
QT_BEGIN_NAMESPACE
class Ui_Widget
{
public:
QLabel *label;
void setupUi(QWidget *Widget)
{
if (Widget->objectName().isEmpty())
Widget->setObjectName(QString::fromUtf8("Widget"));
Widget->resize(800, 600);
label = new QLabel(Widget);
label->setObjectName(QString::fromUtf8("label"));
label->setGeometry(QRect(310, 220, 191, 81));
retranslateUi(Widget);
QMetaObject::connectSlotsByName(Widget);
} // setupUi
void retranslateUi(QWidget *Widget)
{
Widget->setWindowTitle(QCoreApplication::translate("Widget", "Widget", nullptr));
label->setText(QCoreApplication::translate("Widget", "Hello world", nullptr));
} // retranslateUi
};
namespace Ui {
class Widget: public Ui_Widget {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_WIDGET_H
除了最上面的原本ui就有的代码,下面的方法中的label->setText就是qmake生成的代码。
这是我们使用图形化方式打印Hello world发生的变化。
纯代码的方式:
对于纯代码的方式,我们就需要用到C++的知识了。label这个控件,本质上是一个类,那么我们要使用这个类,大多数时候得包它的头文件的,如果有的时候我们没有包括某个头文件,但是能正常使用某个类的话,一般都是因为该类的头文件被间接包含了,对于label,或者说对于QT中的类的头文件都是对应的同名的头文件:
可是当我们引用到了该头文件的时候,会发现为什么会有两个头文件?
好像我们在C++和C语言混用的时候也出现了?
这是因为上古时期,QT使用的都是.h的文件,但是呢,在98年C++98成立之后,规定包含头文件都是以cstdio这种形式包含,所以我们之后包含头文件的话都是应该使用QLabel这种形式了。
那么new了之后,label的构造函数我们应该如何构建?其实,在上文,我们介绍过了对象树的概念,至于为什么需要对象树,我们下文介绍。在该label的初始化中,我们将参数给定为this,就是说label的父对象是widget,先构建好。
初始化之后,我们设置文本的方法在前文的ui文件生成的头文件也已经告诉我们了,使用的是setText方法:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLabel* label = new QLabel(this);
label->setText("Hello world");
}
此时我们运行试试:
可以发现,在ui界面的左上角同样出现了一个Hello world,默认是出现在QT坐标系的左上角,我们也没有设置对应的坐标。这是纯代码的方式。
在纯代码的方式,我们涉及到了新的知识点是:对象树,坐标系。
好了,以为到这里就结束了吗?并没有,如果我们细看setText方法里面的内容提示:
发现它的参数其实是QString,而不是我们熟知的string,这是为什么呢?
其实在当年,表示一个字符串的话,C语言肯定是不好用的,因为字符串都是以\0结尾的,不巧的是,当时的C++也并不是很好用,所以QT就自己造了一套轮子,比如QString,QVector等,但是C++后面的容器什么的也是越做也好了,这就尴尬了, QT不能又给自己的轮子删除了吧?所以在后面的时候,就让两种容器共存了,并且QT也提供了将C语言风格的字符串隐式转换为QString的构造函数。所以我们可以写成label->setText(QString("Hello world")),但是不用,直接写就行。这其实是一个历史问题。
而实际上呢,对于QString来说,在字符编码方面处理的比std::string要好一点,但是现在咱们对于编码方面的处理了解还不是很好,所以咱们也就听听~~
内存泄漏的问题
细心的人会发现我们创建一个label的时候,实例化该对象的时候都是使用的new,为什么不是在栈上创建呢?
当某个控件,或者说某个对象,在失去作用的时候,就代表该UI界面出现bug了,失效了,那么什么是失去作用呢?比如该对象出了自己的栈帧空间,就像这样:
可以发现我们创建的labelone并没有在上面打印出来我们想要的Hello qt,这实际上就是因为该对象随着构造函数的销毁而销毁了。
所以我们都是十分推荐使用new,我们甚至不用担心内存泄漏的问题,因为QT有自己的机制可以让new出来的对象自动析构了。
是否记得对象树?
当我们new了一个对象的时候,该对象会在对象树上指定一个父类对象,当父类对象析构的时候,会带着它析构,其实就像Linux中的目录树状结构:
我们光这样说了,不妨借助打印看看:
文件中选择添加新项目,选择C++:
class name即我们的类名,同样需要选择父类,但是呢,在这里base class的选择里面并没有我们希望的父类,所以我们不妨自己手写一个QLabel:
就生成了对应的.cpp文件和对应的.h文件:
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QLabel>
class Mylabel : public QLabel
{
public:
Mylabel(QWidget* parent);
};
#endif // MYLABEL_H
在对应的.h文件里面,我们只需要将构造函数的参数变成QWidget*即可。
这里有个小技巧:alt + enter可以快速切换声明和定义,F4可以快速切换cpp文件和h文件
在对应的.cpp文件里面:
#include "mylabel.h"
Mylabel::Mylabel(QWidget* parent)
: QLabel(parent)
{
}
调用了它对应的父类的构造,才能让我们这个自定义的类挂接到对象上,就像ABC,B先到了树上,带着C才能到。
那么我们需要打印一个信息,即自己被析构了,我们打算在析构函数里面写:
Mylabel::~Mylabel()
{
std::cout << "Mylabel 被销毁!!" << std::endl;
}
好了,开始实验:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
Mylabel* mylabel = new Mylabel(this);
}
Widget::~Widget()
{
delete ui;
}
在构造函数我们new了一个对象,运行程序并关闭程序:
在下面的应用程序输出确实打印了点什么东西,这也证明了QT确实存在自己的内存释放机制。
接下来,我们应该解决的是乱码问题。
乱码问题解释
解释这个乱码问题之前,不妨回答一个问题:一个汉字是由多少个字节构成的?
这个问题百分之90的人都会回答错,只要回答了一个具体的数字,基本就是错的,因为这个问题的答案取决于当前用的是什么字符集。
也就是我们常说的编码,对于不同的编码,汉字对应的字节有就不一样,比如GBK,我们在VS上试验打印出来是两个字节,这是因为Windows默认的是GBK编码,但是如果我们使用的是Linux环境,默认的是utf8编码,此时打印出来就是3个字节了。
所以,取决于对应的编码。
好了,现在的问题是,QT界面使用的是什么编码?
当我们点击任意的文件,选择使用记事本打开,并且点击另存为,此时右下角出现的就是编码方式,utf8代表的就是utf8编码方式,ASCII代表的就是GBK方式。
问题来了,我们是修改对应的字符串的编码方式还是QT的编码方式呢?似乎都不是很妥当。
所以在QT里面的QString很好的处理了编码方式,不仅如此,QT中也提供了专门用来打印日志的工具,它是一个宏,也很好的处理了编码,这个宏也是可以一键关闭的,使用如下:
#include "mylabel.h"
#include <QDebug>
Mylabel::Mylabel(QWidget* parent)
: QLabel(parent)
{
}
Mylabel::~Mylabel()
{
qDebug() << "Mylabel 被销毁!!";
}
就非常完美了。
以上是乱码问题的解释。
一个小结:
小结:
1.认识 QLabel 类,能够在界面上显示字符串.通过 setText 来设置的. 参数 QString(Qt 中把 C++ 里的很多容器类, 进行了重新封装. 历史原因
2.内存泄露/文件资源泄露
3. 对象树, Qt 中通过对象树,来统一的释放界面的控件对象.
Qt 还是推荐使用 new 的方式在堆上创建对象,通过对象树,统一释放对象创建对象的时候,在构造函数中,指定父对象(此时才会挂到对象树上)
如果你的对象没有挂到对象树上,就必须要记得手动释放!!
4. 通过继承自 Qt 内置的类, 就可以达到对现有控件进行功能扩展效果.
Qt 内置的 QLabel, 没法看到销毁过程的,为了看清楚,就创建类 MyLabel, 继承自 QLabel
重写析构函数.
在析构函数中,加上日志,直观的观察到对象释放的过程了,
5.乱码问题 和 字符集~ MySQL(很多地方都涉及到)6.如何在 Qt 中打印日志,作为调试信息
(具体这个宏叫啥名字,太长了,俺也记不住)使用 cout 固然可以, 但是并不是上策(字符编码处理的不好,也不方便统一进行关闭)
Qt 中推荐使用 qDebug() 完成日志的打印.
7.为啥要打印日志调试呢??
调试器很多时候是有局限性的,是无法使用的.
假设当前 bug 是一个概率性的 bug, 出现的概率是 1% 甚至更小要想调试,无法使用调试器了.
无论是哪种方式,本质上都是观察程序执行的中间过程和中间结果~
使用日志, 就可以很好的解决这种问题~~
感谢阅读!