C++梁哥笔记day25 | 我的日常分享

C++梁哥笔记day25

一、Qt概述

1.1 什么是Qt

Qt是一个跨平台的C++图形用户界面应用程序框架。它为开发者提供建立艺术级图形界面所需的所用功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程。

$吐血 window系统坏了$

卧槽 卧槽 我直接卧槽 尝试了不知道多少方法
http://bbs.160.com/thread-81600-1-1.html
这个方法里面的sfc /scannow解决了我的问题 还以为系统必须重装了 都快放弃了
2020.2.1 16:00问题解决

二、Qt的发展史

  • 1991年 Qt最早由奇趣科技开发

  • 1996年 进入商业领域,它也是目前流行的Linux桌面环境KDE的基础

  • 2008年 奇趣科技被诺基亚公司收购,Qt成为诺基亚旗下的编程语言

  • 2012年 Qt又被Digia公司收购

  • 2014年4月 跨平台的集成开发环境Qt creator3.1.0发布,同年5月20配发了Qt5.3正式版,至此Qt实现了对iOS、Android、WP等各平台的全面支持。

    当前Qt最新版本为6.0

1.3 支持的平台

  • Windows —– XP、Vista、Win7、Win8、Win2008、Win10
  • Uinux/X11 —– Linux、Sun Solaris、HP-UX、Compaq Tru64 Unix、IBM AIX、SGI IRIX、FreeBSD、BSD/OS 和其他很多X11平台
  • Macintosh —– Mac OS X
  • Embedded —– 有帧缓冲支持的嵌入式Linux平台,Windows CE

1.4 Qt的优点

  • 跨平台,几乎支持所有的平台
  • 接口简单,容易上手,学习Qt框架对学习其他框架有参考意义
  • 一定程度上简化了内存回收机制
  • 开发效率高,能够快速的构建应用程序
  • 有很好的社区氛围,市场份额在缓慢上升
  • 可以进行嵌入式开发

1.5 成功案例

  • Linux桌面环境KED
  • WPS Office办公软件
  • Skype 网络电话
  • Google Earth 谷歌地图
  • VLC 多媒体播放器
  • Virtual Box 虚拟机软件
  • ···········

二、Qt创建一个工程

截屏2021-01-31 下午4.22.52 截屏2021-01-31 下午4.26.41

三、项目介绍

3.1 .pro工程文件

截屏2021-01-31 下午4.37.16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
QT       += core gui 包含的模块

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 大于Qt4版本 才包含widget模块

CONFIG += c++11

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \ 源文件
main.cpp \
widget.cpp

HEADERS += \ 头文件
widget.h

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

.pro 就是工程文件(project),它是qmake自动生成的用于生产Makefile的配置文件。

.pro文件的写法如下:

  • 注释:

    从#开始,到这一行结束

  • 模板变量告诉qmake为这个应用程序生成哪种makefile,下面是可供使用的选择:

    TEMPLATE=app

    • app 建立一个应用程序的makefile,这是默认值,所以如果模板没有被指定,这个将被使用
    • lib 建立一个库的makefile
    • vcapp 建立一个应用程序的Visual Studio项目文件
    • vclib 建立一个库的Visual Studio项目文件
    • subdirs 这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile
  • 指定生成的应用程序名

    TARGET = QtDemo

  • 工程中包含的头文件

    HEADERS += include/painter.h

  • 工程中包含的.ui设计文件

    FORMS += forms/painter.ui

  • 工程中包含的资源文件

    RESOURCES += qrc/painter.qrc

  • greaterThan(QT_MAJOR_VERSION,4):QT += widgets

    这条语句的含义是,如果QT_MAJOR_VERSION版本大于4,需要增加widgets模板。如果项目仅需支持大于4的版本,也可直接添加QT += widgets。不过为了保持代码兼容,最好还是按照Qtcreator生成的语句编写。

  • 配置信息

    CONFIG 用来告诉qmake关于应用程序的配置信息。

    CONFIG += C++11 使用C++11的特性

    在这里使用+=,是因为我们添加我们的配置选项到任何一个已经存在中。这样坐比使用=那样替换已经指定的所有选项更安全。

3.2 main函数介绍

main.cpp

截屏2021-01-31 下午5.40.38

解释:

  • Qt系统提供的标准类名声明头文件没有.h后缀

  • Qt一个类对应一个头文件,类名就是头文件名

  • QApplication应用程序类

    • 管理图形用户界面应用程序的控制流和主要设置
    • 是Qt的整个后台管理的命脉,它包含主事件循环,在其中来自窗口系统和其他资源的所有事件处理和调度。它也能处理应用程序的初始化和结束,并提供对话管理。
    • 对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication对象,而不论这个应用程序在同一时间内是不是有0、1、2或更多个窗口
  • a.exec()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
//应用程序类 初始化我们的应用程序
QApplication a(argc, argv);
//创建一个窗口控件
Widget w;
//显示一个窗口 hide隐藏窗口
//窗口默认是隐藏的
w.show();
//a.exec() 主事件循环(带阻塞 等待用户操作界面)
return a.exec();
}

3.3 widget.h 和 widget.cpp

widget.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class Widget : public QWidget
{
Q_OBJECT//让widget支持信号和槽机制

public:
Widget(QWidget *parent = nullptr);
~Widget();
};
#endif // WIDGET_H

widget.cpp

1
2
3
4
5
6
7
8
9
10
11
#include "widget.h"

Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//界面设计是在 窗口控件的构造函数中设计
}

Widget::~Widget()
{
}

四、qt界面

4.1 一个窗口

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//界面设计是在 窗口控件的构造函数中设计
//this 代表的是当前的主窗口

//设置主窗口标题
this->setWindowTitle("第一个窗口");

//固定窗口(不可拖动放大缩小)
this->setFixedSize(600,450);
//设置窗口大小resize(可拖动放大缩小)
this->resize(650,320);

//创建一个按钮
QPushButton b;
//在Qt创建按钮尽量使用指针 一般不用普通变量
QPushButton *button = new QPushButton();
button->setParent(this);
button->setText("我是按钮");

QPushButton *button1 = new QPushButton("我是按钮1",this);
//默认控件会显示在主窗口的左上方
button1->move(150,50);
}

Widget::~Widget()
{
}

运行结果:

截屏2021-01-31 下午5.40.38

4.2 对象模型(对象树)

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

  • QObject是以对象树的形式组织起来的。

    • 当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是parent,也就是父对象指针。

      这相当于,在创建Object对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。

    • 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)

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

  • Qwidget是能够在屏幕上显示一切组件的父类。

    • QWidget 继承自QObject,因此也继承了这种对象树关系。一个孩子自动的成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。

    • 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。

      比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。

Qt引入对象树的概念,在一定程度上解决了内存问题。

  • 当一个QObject对象在堆山创建的时候,Qt会同时为其创建一个对象树。

    不过,对象树种对象的顺序是没有意义的。这意味着,销毁这些对象的顺序也是未定义的。

  • 任何对象树种的QObject对象delete的时候,如果这个对象中有parent,则自动将其从parent中的children()列表中删除;如果有孩子,则自动delete每一个孩子。Qt保证没有QObject会被delete两次,这是由析构顺序决定的。

    如果QObject在栈上创建,Qt保持同样的行为。正常情况下,这也不会发生什么问题。来看下面的代码片段:

    1
    2
    3
    4
    {
    QWidget window;
    QPushButton quit("Quit",&window);
    }

    作为父组件的window和作为子组件的quit都是QObject的子类(事实上,它们都是QWidget的子类,而QWidget是QObject的子类)。这段代码不会出错,quit的析构函数不会被调用两次,因为标准C++要求,局部对象的析构顺序应按照其创建顺序的相反过程(其实就是先入栈的后弹栈)。因此,这段代码在超出作用范围时,会先调用quit的析构函数,释放后,再将其从父对象window的子对象列表中删除,然后是window调用析构函数,这时候其子对象列表中的quit已经没有了,所以quit不会被释放两次。

    但是,如果是使用下面的代码,就会出错

    1
    2
    3
    QPushButton quit("quit");
    QWidget window;
    quit.setParent(&window);

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

    由此我么看到,Qt的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能会时不时跳出来烦扰一下,所以,我们最好从开始就养成良好的习惯,在Qt中,尽量在构造的时候就指定parent对象,并且大胆的在堆上创建。

建议:从堆区申请空间(使用new),而不是从栈区(定义一个普通变量)

4.3 Qt窗口坐标体系

以左上角为原点(0,0),x向右增加,y向下增加

截屏2021-01-31 下午6.26.23

五、信号和槽机制(重要)

![截屏2021-01-31 下午6.30.42](截屏2021-01-31 下午6.30.42.png)

信号槽是Qt框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如按钮检测到自己被点击了一下,它就会发出一个信号(sign)。这种发出时没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽slot)绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。

5.1 系统自带的信号和槽

下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?

其实两行代码就可以搞定了

1
2
QPushButton *quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&Widegt::cloce);

第一行是创建一个关闭按钮,这个之前就学过,第二行就是核心,也就是信号槽的使用方式

connect()函数最常用的一般形式:

1
connect(sender,signal,receiver,slot);
  • sender:发出信号的对象
  • signal:发出的信号
  • receiver:接收信号的对象
  • slot:接收对象在接收到信号之后所需要调用的函数(槽函数)

案例:一般写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//设置窗口标题
this->setWindowTitle("信号和槽");
//设置大小
this->resize(500,320);
//创建一个按钮
QPushButton *btn1 = new QPushButton("关闭按钮");
btn1->setParent(this);
//需求:单击btn 关闭主窗口
//分析:信号的发起者是btn1按钮 信号接受者是主窗口 执行关闭操作(槽函数)
//建立信号和槽的关系 使用connect
connect(btn1,&QPushButton::clicked,this,&Widget::close);
}

Widget::~Widget()
{
}


运行结果:

截屏2021-01-31 下午9.22.34

案例:使用lambda表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//设置窗口标题
this->setWindowTitle("信号和槽");
//设置大小
this->resize(500,320);

//使用lambda表达式
QPushButton *btn2 = new QPushButton("点我啊",this);
btn2->move(100,100);
connect(btn2,&QPushButton::clicked,[=](){
//获取按钮文本
QString text = btn2->text();
qDebug()<<text;
//设置按钮文本
btn2->setText("文本改变了");
});
}

Widget::~Widget()
{
}

运行结果:

![截屏2021-01-31 下午9.25.37](截屏2021-01-31 下午9.25.37.png)

5.2 自定义信号和槽(了解)

使用connect()可以让我们连接系统提供的信号的槽。但是,Qt的信号槽机制并不仅仅是使用系统提供的那部分,还允许我们自己设计自己的信号的槽。

案例:

老师发出饿的信号 学生执行请老师吃饭的动作

首先创建老师类和学生类,注意要继承QObject,因为它是所有类的祖宗,信号的槽的机制都是从它那继承来的,不继承毛都没有。

截屏2021-01-31 下午9.29.46

![截屏2021-01-31 下午11.15.10](截屏2021-01-31 下午11.15.10.png)

widget.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "widget.h"
#include <QPushButton>
#include "teacher.h"
#include "student.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QPushButton *btn = new QPushButton("按钮",this);
this->resize(450,300);
this->setWindowTitle("这是一个窗口");
Teacher *t = new Teacher();
Student *s = new Student();

connect(t,&Teacher::hunger,s,&Student::treat);
/*connect(t,&Teacher::hunger,[=](){
s->treat();
});*/

connect(btn,&QPushButton::clicked,[=](){
emit t->hunger();//老师发出信号
});
}

Widget::~Widget()
{
}

teacher.cpp

1
2
3
4
5
6
#include "teacher.h"

Teacher::Teacher(QObject *parent) : QObject(parent)
{

}

student.cpp

1
2
3
4
5
6
7
8
9
10
11
#include "student.h"
#include <QDebug>
Student::Student(QObject *parent) : QObject(parent)
{

}

void Student::treat()
{
qDebug()<<"请老师吃饭";
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

teacher.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef TEACHER_H
#define TEACHER_H

#include <QObject>

class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = nullptr);

signals://自定义信号函数
void hunger();

public slots://自定义槽函数
};

#endif // TEACHER_H

student.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef STUDENT_H
#define STUDENT_H

#include <QObject>

class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QObject *parent = nullptr);

signals:

public slots:
void treat();

};

#endif // STUDENT_H

widget.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = nullptr);
~Widget();
};
#endif // WIDGET_H

运行结果:

截屏2021-01-31 下午11.13.00
  1. 定义信号的规则:在signal下方

    返回值类型void 只需声明 不用实现,可以有参数,可以重载

  2. 定义槽函数的规则:

    返回值类型为void 需要声明 需要实现 可以有参数 可以重载

  3. 用户可以使用emit函数发出信号

5.3 信号的槽的重载

![截屏2021-01-31 下午9.42.10](截屏2021-01-31 下午9.42.10.png)

要使用函数指针指明类型,因为重载后同一个函数名的函数不止一个,编译器不知道去匹配哪一个。

案例:

![截屏2021-01-31 下午11.15.10](截屏2021-01-31 下午11.15.10.png)

widget.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "widget.h"
#include <QPushButton>
#include "teacher.h"
#include "student.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QPushButton *btn = new QPushButton("按钮",this);
this->resize(450,300);
this->setWindowTitle("这是一个窗口");
Teacher *t = new Teacher();
Student *s = new Student();

void (Teacher::*p1)(QString) = &Teacher::hunger;//指明了类型
void (Student::*p2)(QString) = &Student::treat;
connect(t,p1,s,p2);

connect(btn,&QPushButton::clicked,[=](){
emit t->hunger("红烧肉");//老师发出信号
});
}

Widget::~Widget()
{
}

teacher.cpp

1
2
3
4
5
6
#include "teacher.h"

Teacher::Teacher(QObject *parent) : QObject(parent)
{

}

student.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "student.h"
#include <QDebug>
Student::Student(QObject *parent) : QObject(parent)
{

}

void Student::treat()
{
qDebug()<<"请老师吃饭";
}

void Student::treat(QString str)
{
qDebug()<<"请老师吃"<<str;
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

teacher.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef TEACHER_H
#define TEACHER_H

#include <QObject>

class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = nullptr);

signals://自定义信号函数
void hunger();
void hunger(QString str);

public slots://自定义槽函数
};

#endif // TEACHER_H

student.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef STUDENT_H
#define STUDENT_H

#include <QObject>

class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QObject *parent = nullptr);

signals:

public slots:
void treat();
void treat(QString str);

};

#endif // STUDENT_H

widget.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = nullptr);
~Widget();
};
#endif // WIDGET_H

运行结果:

截屏2021-01-31 下午11.30.55

自定义信号槽注意事项:

  • 发送者和接受者都需要时QObject的子类(当然,如果槽函数是全局函数、lambda表达式等无需接受者的时候除外)
  • 信号的槽函数的返回值是void
  • 信号只需要声明,不需要事先
  • 槽函数需要声明,也需要实现
  • 槽函数时普通的成员函数,作为成员函数,会受到public、private、protected的影响
  • 使用emit在恰当的位置发送信号
  • 任何成员函数、static函数、全局函数和lambda表达式可以作为槽函数
  • 信号槽要求信号的槽的参数一致,所谓一致,是参数类型一致,参数数量一致。
  • 如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号少)

5.3 信号槽的拓展

  • 一个信号可以和多个槽相连

    如果是这种情况,这些槽会一个接一个地被调用,但是它们的调用顺序是不确定的。

  • 多个信号可以连接到一个槽

    只要任意一个信号发出,这个槽就会被调用

  • 一个信号可以连接另外一个信号

    当一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。

  • 槽可以被取消连接

    这种情况并不经常出现,因为当一个对象delete之后,Qt会自动取消所有连接到这个对象上面的槽。

  • 信号槽可以断开

    利用disconnect关键字是可以断开信号槽的

  • 使用lambda表达式

    在使用Qt5及以上的时候,能够支持Qt5及以上的编译器都是支持lambda表达式的。

    在连接信号的槽的时候,槽函数可以使用lambda表达式的方式进行处理。

5.4 QT4版本的信号槽写法

1
connect(zt,SIGNAL(hungry(QString)),st,SLOT(treat(QString)));

这里使用了SIGNAL和SLOT这两个宏,将两个函数转换成了字符串。

注意到,connect()函数的SIGNAL和SLOT都是接收字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而运行时给出错误。这无疑会增加程序的不稳定性。

Qt5在语法上是完全兼容Qt4的,而反之是不可以的。