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创建一个工程
三、项目介绍
3.1 .pro工程文件
1 | QT += core gui 包含的模块 |
.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
解释:
Qt系统提供的标准类名声明头文件没有.h后缀
Qt一个类对应一个头文件,类名就是头文件名
QApplication应用程序类
- 管理图形用户界面应用程序的控制流和主要设置
- 是Qt的整个后台管理的命脉,它包含主事件循环,在其中来自窗口系统和其他资源的所有事件处理和调度。它也能处理应用程序的初始化和结束,并提供对话管理。
- 对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication对象,而不论这个应用程序在同一时间内是不是有0、1、2或更多个窗口
a.exec()
1 |
|
3.3 widget.h 和 widget.cpp
widget.h
1 |
|
widget.cpp
1 |
|
四、qt界面
4.1 一个窗口
案例:
1 |
|
运行结果:
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
3QPushButton 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.30.42](截屏2021-01-31 下午6.30.42.png)
信号槽是Qt框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如按钮检测到自己被点击了一下,它就会发出一个信号(sign)。这种发出时没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽slot)绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
5.1 系统自带的信号和槽
下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?
其实两行代码就可以搞定了
1 | QPushButton *quitBtn = new QPushButton("关闭窗口",this); |
第一行是创建一个关闭按钮,这个之前就学过,第二行就是核心,也就是信号槽的使用方式
connect()函数最常用的一般形式:
1 | connect(sender,signal,receiver,slot); |
- sender:发出信号的对象
- signal:发出的信号
- receiver:接收信号的对象
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
案例:一般写法
1 |
|
运行结果:
案例:使用lambda表达式
1 |
|
运行结果:
![截屏2021-01-31 下午9.25.37](截屏2021-01-31 下午9.25.37.png)
5.2 自定义信号和槽(了解)
使用connect()可以让我们连接系统提供的信号的槽。但是,Qt的信号槽机制并不仅仅是使用系统提供的那部分,还允许我们自己设计自己的信号的槽。
案例:
老师发出饿的信号 学生执行请老师吃饭的动作
首先创建老师类和学生类,注意要继承QObject,因为它是所有类的祖宗,信号的槽的机制都是从它那继承来的,不继承毛都没有。
![截屏2021-01-31 下午11.15.10](截屏2021-01-31 下午11.15.10.png)
widget.cpp
1 |
|
teacher.cpp
1 |
|
student.cpp
1 |
|
main.cpp
1 |
|
teacher.h
1 |
|
student.h
1 |
|
widget.h
1 |
|
运行结果:
定义信号的规则:在signal下方
返回值类型void 只需声明 不用实现,可以有参数,可以重载
定义槽函数的规则:
返回值类型为void 需要声明 需要实现 可以有参数 可以重载
用户可以使用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 |
|
teacher.cpp
1 |
|
student.cpp
1 |
|
main.cpp
1 |
|
teacher.h
1 |
|
student.h
1 |
|
widget.h
1 |
|
运行结果:
自定义信号槽注意事项:
- 发送者和接受者都需要时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的,而反之是不可以的。