QT --- 初识QT
一、通过代码构建helloworld界面
一般通过代码来构造界面的时候,通常会把构造界面的代码放到Widget/MainWindow的构造函数中。
Qt中每个类都有对应同名的头文件
上古时期,Qt用的是这种风格的文件。1998年之后,C++标准成立了,C++98标准。规定,包含头文件,统一使用#include<cstdio>代替原有的#include<stdio.h>
Lable叫做标签,界面上一个用来显示内容字符串的控件,创建对象的时候,可以直接堆上创建,也可以在栈上创建。跟推荐在堆上创建对象。
this,给当前这个label对象,指定一个"父对象" this 就是Widget w。给这个对象里面设置一个文本,设置控件中,要显示的文本是啥。
Qt诞生于1991年,C++还没有形成标准,C++更没有标准库这样的改变了。当时,如何表示一个字符串,可以使用C风格字符串,也可以使用C++的string。Qt为了让自己的开发能变得的顺畅,就自己发明了一套轮子,搞了一系列的基础类,来支持Qt的开发,
很多年之后,上述这些容器等内容,已经打磨的很好了,形成了C++标准。
很显然,这些已经引入的Qt自己包装好的这些容器类,也不可能删了。就只能和现有的标准库中的容器类共存了。后续的代码中,还会经常见到QString这样的一些东西,而很少见到std::string Qstring和std::string之间也能很方便的相互转换。
在QString中也提供了C风格字符串作为参数的构造函数,不显示构造QString,上述代码中,C风格字符串也会隐式构造程QString对象。QString对应的头文件,已经被很多Qt内置的其他类都包含了。所以我们不需要显式包含QString头文件。通过代码创建QLable默认式在左上角。如果想放到其他的位置,也是可以的。
代码:
二、内存泄露问题的讨论。
这个代码,new了对象之后,咋没有delete呢?不delete不就出现内存泄露了吗?
关注内存泄漏,是要融入到DNA中的事情,内存泄露是一个非常可怕的事情。
三、对象树
上面的代码在QT中不会产生内存泄漏,lable对象会适合的时候被析构释放 ~~ 虽然没有手动写delete,确实能释放。之所以能够把对象释放掉,主要是因为把这个对象挂到了对象树上。前端开发也涉及到了类似的对象树(DOM),本质上也是一个树形结构(N叉树),通过树形结构把界面上的各种元素组织起来。Qt中也有一个对象书树也是N叉树,把界面上的各种元素组织起来了。
通过这个树形结构,就把界面上要显示的这些控件对象都组织起来了。使用对象树,把这些内容组织起来,最主要的目的,就是为了能够在合适的时机,把这些对象统一进行释放。
如果树上的这些对象,统一销毁时最好不过的,如果某个对象提前销毁,此时就会导致对应的控件就在界面上不存在了。此处通过new的方式创建对象,也就是为了把这个对象生命周期,交给Qt的对象树来统一管理。
当把对象改成在栈上创建,此时就可以看到,运行起来的程序无法显示出hello world此时lable对象随着构造函数的结束就销毁了。
我们来验证一下对象树的存在性。可以用自己编写的类来实现验证。创建自定义类,最主要的目的,是自定义一个析构函数,在析构函数中,完成打印,方便我们看到最终的自动销毁对象的结果!!
#include <QLabel>
class MyLable : public QLabel
{
public:
MyLable(QWidget* parent);
~MyLable();
};
#endif // MYLABLE_H
#include "mylable.h"
#include <iostream>
MyLable::MyLable(QWidget* parent)
:QLabel(parent)
{}
MyLable::~MyLable()
{
std::cout << "对象被销毁" << std::endl;
}
日志有:说明析构函数是执行了,虽然没有手动delete,但是由于把MyLable挂到了对象树上。此时窗口被销毁的时候,就会自动销毁对象树中的所有对象。MyLable的析构时执行到了的。
预期打印的时被销毁三个中文,但是实际的显示效果,出现了乱码。原因就是编码方式不匹配。
关于乱码问题的解释:
在计算机中,一个汉字,占多少个字节?
针对这个问题,只要你回答出具体的数字,就一定时错的!。前提条件:当前中文编码使用的是哪种方式(字符集)。计算机种存储的都是二进制码。英文字母是通过ASCII码表,规定了每个字符,都有一个对应的数字来表示。只是表示英文,一个字节足够了,毕竟英文字母数目非常有限。
中文一共多少个汉字呢?日常的常用字,大概是4000多个。算上各种生僻字,总数差不多6W多个。我们仍然使用一个大表格,给每个汉字,分配一个整数即可。那么具体这个表格是什么样子。具体每个数字都使用哪个数字表示这个事情就不一定了,字符集表示汉字的字符集,其实是有很多种的不同的字符集,表示同一个汉字,使用的数字并不相同!
目前表示汉字字符集,主要两种方式
1、GBK中国大陆 使用2个字节表示一个汉字。
2、UTF-8变长编码,表示一个符号,使用的字节数有变化。2 - 4字节,但是在utf-8中,一个汉字,一般是3个字节。Linux中默认就是utf-8。
如果你字符串本身是utf-8编码的,但是终端是按照GBK的方式解析的显示的,此时就会出现乱码,(拿着utf-8这里的数值,去查询gbk的码表)此时就会出现乱码了。
Qt Creator内置的终端是utf-8的方式来显示字符串吗?
这个终端好像不能设置字符编码,既然出现乱码了,这里不是utf-8了。当前表示中文,主流的方式,还得是utf-8,它支持各种语言文字。Qt中有一个东西,QString,是可以帮助我们自动的处理编码方式的,不只是QString,Qt也提供了专门用来打印日志的工具,也能自动处理编码方式。Qt中提供了QDebug工具,借助这个工具,就可以完成打印日志的过程,很好的处理字符编码了。
QDebug是Qt中的类,又不会直接使用这个类。
这个宏,封装了QDebug对象,直接使用qDebug()这个东西就可以当作cout来使用。后续在Qt中,如果想通过打印日志的方式,输出一些调试信息,都有限使用qDebug,使用qDebug还有一个好处,打印的调试日志,是可以统一进行关闭的。如果你的程序发布给用户,不希望用户看到这些日志的!qDebug可以通过编译开关,来实现一键式关闭。
小结:
1、认识了QLabel类,能够在界面上显示字符串。
通过setText来设置的,参数QString。这是一个历史原因
2、内存泄漏/文件资源泄漏。
3、对象树,Qt中通过对象树,来统一的释放界面的控件对象。Qt还是推荐使用new的方式在堆上创建对象,通过对象树,统一释放资源。创建对象的时候,在构造函数中,指定父对象此时才会挂到对象树上。如果你的对象没有挂到对象树上,就必须记得手动释放。
4、通过继承自Qt内置的类,就可以达到对现有控件进行功能扩展的效果。Qt内置的QLabel,没法看到销毁过程,为了看清楚我们就创建类MyLable,继承QLabel重写析构函数。在析构函数,加上日志,直观的观察到对象释放的过程了。也可以重写控件中的任何功能,不仅仅是构造,达到功能扩展的目的,面向对象继承本质上就对现有的代码进行扩展。我们要学习,也要思考需要悟道。
5、乱码问题和字符集的问题。在MySQL中很多地方都涉及到。
6、如何在Qt中打印日志,作为调试信息。使用Qt中推荐的qDebug()完成日志的打印。
四、使用输入框实现helloworld
完成一个helloworld可以通过很多种控件来实现。
使用编辑框来完成hello world
单行编辑框 QLineEdit
多行编辑框 QTextEdit
代码:
使用纯代码的方式实现:
#include "widget.h"
#include "ui_widget.h"
#include <QLineEdit>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLineEdit* qline = new QLineEdit(this);
qline->setText("hello world");
}
Widget::~Widget()
{
delete ui;
}
五、使用按钮实现helloworld
使用pushbutton就是一个普通的按钮。按钮可以设置一个点击效果。Qt中的信号槽机制。本质就是给按钮的点击操作关联上一个处理函数,当用户点击的时候,就会执行这个处理函数。、
需要使用connect(),Linux网络编程也学过这个函数,这个函数用来建立连接,然后才能读写数据。Qt中的connect是QObject这个类提供的静态函数,这个函数的作用就是连接信号和槽。和TCP的建立连接操作没有任何的关系。第一个参数谁发的信号,ui->pushbutton,访问到form file(ui文件)中创建的控件!此时就会给这个控件分配一个objectName属性。这个属性的值,要求是在界面上唯一的。不能和别人重复。会自动生成一个,也可以手动修改换成别的。
第二个参数传入,关联到父类中,然后
点击按钮的时候就会自动触发这个信号。
this 谁来处理这个信号,第四个参数具体怎么处理。
代码:
#include "ui_widget.h"
#include <QObject>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->pushButton,&QPushButton::clicked,this,&Widget::Handler);
}
Widget::~Widget()
{
delete ui;
}
void Widget::Handler()
{
if(ui->pushButton->text() == "hello world")
ui->pushButton->setText("hello qt");
else
ui->pushButton->setText("hello world");
}
使用纯代码的方式实现。
#include "widget.h"
#include "ui_widget.h"
#include<QObject>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//使用纯代码的方式实现helloworld,信号槽机制
//button我们定义为了Widget类中的私有成员。
button = new QPushButton(this);
button->setText("helloworld");
connect(button,&QPushButton::clicked,this,&Widget::Handler);
}
Widget::~Widget()
{
delete ui;
}
void Widget::Handler()
{
if(button->text() == "helloworld")
{
button->setText("hello qt");
}
else
{
button->setText("helloworld");
}
}
左边为纯代码创建,右边为ui创建
对于纯代码版本,按钮对象是咱们自己new的。为了保证其他函数中能够访问这个变量,就需要把按钮对象设定为Widget类的成员变量。右边的按钮对象不需要咱们自己new,new对象的操作已经被Qt自动生成了。而且这个按钮对象,已经作为ui对象里的一个成员变量了。也无需作为Widget的成员。实际开发中,这两种都很主要,难分主次!如果当前程序界面,界面内容是比较固定的,此时就会以图形化的方式来构造界面。但是如果你的程序界面,经常要动态变化,此时就会以代码的方式来构造界面。这两种方式,哪种方便就用哪种。而且两种方式也可以配合使用,后面这两种方式都会涉及到。
六、Qt中的命名规范
给变量/函数/文件/类 起名字,是非常有讲究的。
1、起的名字要有描述性,不要使用abc,xyz这种比较无规律的名字来描述。
2、如果名字比较长,有多个单词构成,就需要使用适当的方式来进行区分不同的单词。Qt中偏好使用大写字母来进行单词分割的。这种命名法叫做驼峰命名法。一种是小驼峰给变量命名,一种是大驼峰给类命名。我们入乡随俗。你公司中使用的项目,使用的驼峰/蛇形/其他。
七、认识Qt窗口坐标体系
坐标体系:以左上角为原点(0,0),右边为x,向下为y。平面直角坐标系(笛卡尔坐标系)
给Qt的某个控件,设置位置,就需要指定坐标,对于这个控件来说坐标系原点就是相对于父窗口的
代码:
#include "widget.h"
#include "ui_widget.h"
#include<QObject>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//使用纯代码的方式实现helloworld,信号槽机制
//button我们定义为了Widget类中的私有成员。
button = new QPushButton(this);
button->setText("helloworld");
connect(button,&QPushButton::clicked,this,&Widget::Handler);
button->move(200,300);
}
可以使用move这个函数来移动控件的位置。坐标背后的单位:像素。