文章目录
一、项目创建以及默认生成的main文件和Pro文件
1.项目创建
1.1 项目名称和路径
在QT中,创建的项目名称和路径不能有空格和中文。可以加下划线。
1.2 创建项目时的基类
在创建项目的时候,有三个基类可以选择,分别是QWidget,QMainWindows,QDialog。这三种的关系是什么?
QWidget 是 QMainWindows,QDialog 的父类,也就是QMainWindows,QDialog 继承了QWidget。
(1)QWidget :现在我们可以简单地将 QWidget 看为一个空窗口,就是里面什么都没有(没有各种菜单栏工具栏)。
补充:
(2)QMainWindows:继承了QWidget,子类是可以有自己的特性,那么现在可以简单地认为 QMainWindows 就是有各种工具栏,菜单栏的窗口。
MainWindow的结构分为五个部分:菜单栏(Menu Bar)、工具栏(Toolbars)、停靠窗口(Dock Widgets)、状态栏(Status Bar)和中央窗口(Central Widget)
(3)Qdialog:是对话框,可以简单地理解为我们平时所用的一些网站或者软件弹出来的提示,比如说输入的时候密码用户错误等等。
1.3 创建QWidget 类
现在就先创建一个最简单的 QWidget 类:
1.4 main 文件简单介绍
(1)从上面图片可以看出,程序把所有的命令行变量和命令行变量数据(argc 和 argv)交给应用程序对象(a)管理了。
(2)本来代码运行完了就会一闪而过,但是有了下面这句代码就不会。
1.5 Pro 文件
.pro 文件也称为项目工程文件,一般不会在这个文件写东西。是要通过 .pro 文件生成其他的文件的。
(1).pro 文件的内容
QT += core gui //Qt包含的模块,这里有开发所要用的很多类
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于4版本 加入 widgets 模块
TARGET = 01_QtFirst //生成.exe程序的名称
TEMPLATE = app //模板 应用程序模板
SOURCES += main.cpp\ //源文件
mywidget.cpp
HEADERS += mywidget.h //头文件
1.6 .h 文件
(1)Q_OBJECT 这一个宏,加了这个宏,就支持信号和槽。
2.项目的文件
当我创建了一个项目之后,一般会有四个文件:项目的工程文件.pro 文件;项目的头文件.h;程序的入口 main 函数文件;还有项目要实现的东西,比如说要做一个窗口的.cpp 文件,这个文件通常是要做的窗口的类的构造函数,我们会在这个构造函数里面增加各种部件。然后在 main 函数中调用,创建窗口类的一个对象,这样就能对窗口进行实例化,然后展示我们做的窗口。
(1)mywidget.cpp
通常我们所要增加的部件就是在这个构造函数里面加。
(2)main.cpp
二、命名规范和常用快捷键
1.命名规范
2.常用快捷键
三、QPushButton 按钮
在使用前包含头文件:
1.创建按钮第一种方法
这一种方法调用的是创建按钮的默认构造函数。如果想要进行其他的操作,那么需要去调用其他的函数。
比如说显示按钮,设置按钮的文字。尤其要注意,创建了一个按钮,如果不做依赖,那么按钮就是一个独立的对象。这是什么意思呢?就是按钮会另开一个新的窗口,而不是放在当前的窗口上。
2.创建按钮第二种方法
3.按钮移动 move
防止按钮覆盖:
btn2->move(50,50);
4.重置窗口大小 resize
只运行一行代码的效果:
所以要重置窗口的大小:使用resize方法
5.设置窗口标题 setWindowTitle
6.设置窗口固定大小 setFixedSize
// 设置窗口固定大小
setFixedSize(600,400);
四、Qt中的对象模型(对象树)
在前面的步骤中,我使用 new 创建了两个按钮,但是并没有写释放的函数。
其实这两个对象在程序执行完了之后都会被释放。
在Qt中一定程度上简化了内存回收机制,当创建的对象 指定的父亲是由QObject或者Object派生的类时候 ,这个对象被加载到对象树上。当窗口关闭掉时候,树上的对象也都会被释放掉。
这个就有点株连九族的味道了,如果一个对象指定了父亲是 QObject或者Object派生的类(比如QWidget等等),父亲被干掉了,就会连累孩子列表中的对象。
(1)在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
当父对象析构的时候,这个列表中的所有对象也会被析构。也就是爸爸被干掉了,儿子们也会被干掉(注意,这里的父对象并不是继承意义上的父类!)
(2)QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。
(3)我们也可以自己删除子对象,它们会自动从其父对象列表中删除。
五、Qt坐标系
以左上角为原点(0,0),X向右增加,Y向下增加。
六、信号槽
1.信号槽简介
信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。
当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。
这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。
也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
connect 函数的使用方法:
2.系统自带的信号和槽
上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?
QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
3.我个人的理解
3.1
Qt中的信号和槽机制,其实是为了将两个没有联系的对象联系起来,从而满足一些需求。
比如说,客户有这样的需求:界面想要有一个按钮,点击按钮,界面窗口就能关闭。但是问题来了,按钮和界面窗口是两个独立的对象,彼此发生的动作是不知道的呀。意思就是说,界面怎么知道按钮被点击了;按钮点击了之后,界面窗口为什么会知道去关闭,而不是放大收缩?等等。
所以必须要将这两个对象联系起来,并且按钮被点击了之后,能够通知界面窗口;收到了通知之后界面窗口要去执行规定的操作。从这里也可以推出,两个对象联系起来有四要素:
(1)两个对象的标识符;
(2)特定的事件发生了才通知另一个对象,而不是随便一个事件发生了就通知。所以要有指定的事件。其实这样解释不对,这是观察者模式,观察者观察到被观察者发生了感兴趣的事情,就去执行对应的动作,这样才是对的。
(3)执行指定操作。
3.2
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
这里发出的信号和处理信号的函数为什么是:
&QPushButton::clicked
和
&MyWidget::close
而不是用实例后的对象去调用呢?也就是为什么不是这样写呢?这是系统自带的信号,按钮时属于系统中某个类的对象,如果系统有满足需求的信号,使用系统自带的就行,这样很方便,也便于别人阅读代码。毕竟都是公有的嘛。
像下面这种写法,可能是要自定义信号和处理函数。
connect(quitBtn,quitBtn->clicked,this,this->close);
4.自定义信号和槽
下面模拟一个案例,下课后,老师发出饿了的信号,学生接收信号后请老师吃饭。
这个案例还需要老师和学生这两个类,所以我们要加在项目里面,项目里面快速添加类的方法:就是在项目名称文件,也就是最顶级的文件(与项目名称同名)那里,点击鼠标右键,然后添加新文件。就可以快速添加老师和学生类,并且会自动生成相应的头文件和源文件。
4.1 自定义信号
信号其实就是一个函数,自定义信号就是自定义函数,不过有些要注意的地方:
(1)在这个案例里面,信号是由老师发出来的,信号是一个函数。函数在使用前,也是要声明;函数的声明一般是在头文件中。所以我们将饿了这个信号函数的声明写在 Teacher.h 头文件里面:
4.2 自定义槽
槽就是处理信号的函数:
(1)将处理信号的函数的声明写在 Student.h 头文件里面:
(2)将处理信号函数的实现,写在 Student.cpp 文件中:
4.3 包含自定义信号和槽的头文件
在项目中窗口头文件中,要使用学生类和老师类记得包含他们的头文件,并且我们通常会将老师对象和学生对象作为项目的属性,包含在窗口类中。
4.4 需要触发信号,发出信号
在教师类中,定义了信号函数,也将教师和学生用 connect 联系了起来,那么学生能捕捉到老师发出的信号了吗?还不能,因为信号还没有被触发,所以说在定义的信号和槽中,要记得写触发信号的函数,也就是将信号发出的函数。
4.5 自定义信号和槽进行重载
(1)connect 函数的参数数据类型
在说自定义信号和槽重载之前,再来看看 connect 函数的参数列表都是什么:
可以知道 connect 函数的参数四要素是:信号的发送者,信号的函数,信号的接受者,处理信号的函数。但这四要素是什么数据类型呢?Qt里面嵌套的是 C/C++ 的代码,C/C++ 里面参数传递的方法无非就是三种:值传递,指针传递,引用传递。值传递是不会用的一般情况下,所以这里无非就是使用了指针或者引用,但是引用的本质也是指针,所以 connect 函数的四个参数都是指针。
1)第一,三个参数:指向信号发送者和信号接收者的指针
2)第二,四个参数:指向信号函数的函数和信号处理函数的函数指针
this->zt = new Teacher(this);
this->st = new Student(this);
connect(zt,&Teacher::hungry,st,&Student::treat);
看上面这段代码中的 &Teacher::hungry
和&Student::treat
这两个是什么意思?这是connect 函数的第二、四个参数,是函数的指针。那么为什么要这样写呢?
hungry ,treat 是函数名,函数名本身就是指针。但是类名并不是指针,hungry 和 treat 分别是 Teacher 和 Student 类中的成员函数。所以要先找到 Teacher 和 Student 类,就是对这两个类进行取地址(&),再加上函数的作用域符(::)。
(2)自定义信号和槽进行重载会出现的问题
1)重载信号函数
我们使用的案例仍然是下课了,老师发出饿了的信号,但是这一次老师说要吃“戳毛煎鸡蛋”,也就是发出了新的信号。那么我们需要重新生命信号函数,这样是可以的,但这用的是C++,可以对原有的信号函数进行重载。
重载信号函数时,只需要在函数的参数列表加上“戳毛煎鸡蛋”对应的数据类型,是QString。信号函数是在 Teacher 类中的。
2)重载信号处理函数
有了新的信号,那么信号接收者要有新信号的处理函数,这里我们可以新定义一个处理函数,也可以进行重载,现在选择重载。
3)重载函数出现的问题
将新信号和新信号处理函数联系起来,问题就来了。从上面的图片可以知道,现在 Teacher 和 Student 类中分别有两个信号函数和两个信号处理函数。在调用 connect 函数的时候,怎么准确地将信号函数和信号处理函数传给 connect 函数,因为现有一个类有两个。
比如说,我现在想要传新的信号和新的处理函数,像下面的写法,是无法准确定位到的,会出现二义性,也就是编译器不知道你传的是新的还是旧的函数的指针。
connect(zt,&Teacher::hungry,st,&Student::treat);
connect(zt,&Teacher::hungry,st,&Student::treat);
4)解决问题
我们知道第二、四个参数是函数指针。现在要解决的问题就是准确定位到新或者旧函数。那么我可以想办法将新或者旧函数的地址取出来,然后定义一个函数指针来接收,接着就将函数指针传给 connect 函数,问题就解决了。
所以解决的步骤就有两个:1、取出目标函数的地址,放到函数指针中;2、将函数指针传给 connect 函数。
现在我要使用的是新的信号和新信号处理函数:
void hungry(QString foodName);
void treat(QString foodName);
所以要先声明两个两个函数指针,函数指针的声明方法:函数的返回值 + (*函数指针名) + (函数的参数列表)。
void(Teacher:: *teacherSingnal)(QString) = &Teacher::hungry;
void(Student:: *studentSlot)(QString) = &Student::treat;
上面的代码又给了我惊喜,为什么 &Teacher::hungry
不用再上 QString 的参数呢?像&Teacher::hungry(QString)
这样写呢?
首先这样写编译器不知道是什么意思,没有这种语法。还是那句话,hungry 本身就是指针了,那么这样写怎么知道是哪个函数?void(Teacher:: *teacherSingnal)(QString)
这句代码就已经做了限定,告诉编译器要去返回值为 void 的 参数列表为 QString 的函数的,相当于就做了优化,等号右边就不用加什么了。
Teacher:: *teacherSingnal
和 Student:: *studentSlot
这两个指针要记得加上作用域,Teacher 和 Student,不然不知道是哪个类中的函数指针。
5.信号和槽拓展
在前面的例子里,当下课了这个函数被调用了,老师类中的饿了信号才会被触发,也就是被发出去。这是利用调用函数来触发信号,那么我们现在是来做界面的,就希望能够用其他的部件来触发信号。比如说有一个下课的按钮,当下课的按钮被点击了,就触发了信号,就能够将信号发出去,信号接收者能够接收到,并且能够去处理信号。这个怎么实现呢?
按钮被点击,然后信号被触发,接着信号接收者能够接收到信号,并能够处理信号。 这个需求有四个对象,按钮,信号函数,信号接收者,信号处理函数。
(1)首先要将老师和学生对象联系起来,这是不变的
this->zt = new Teacher(this);
this->st = new Student(this);
QPushButton * btn = new QPushButton("点击下课",this);
resize(600,400);
void(Teacher:: *teacherSingnal_1) = &Teacher::hungry;
void(Student:: *studentSlot_1) = &Student::treat;
connect(zt,teacherSingnal_1,st,studentSlot_1);
(2)接着就是按钮去触发信号,需要做一个动作,将信号触发,将信号发出去。这里将点击按钮和信号触发联系在一起,也就是点击了按钮,信号就会发出去,也是使用 connect 函数。所以按钮要和教师对象联系起来。
connect(btn,&QPushButton::clicked,zt,teacherSingnal_1);
其实就是按钮发送点击下课的信号给教师,教师收到了信号之后;就将饿了的信号发出去,学生接收到了饿了的信号,处理信号,请老师吃饭。
这里需要注意的点就是,老师饿了的信号,要收到了按钮发过来的信号才能发出去,之前的是调用一个函数来将饿了的信号发出去。
(3)总结
1)一个信号可以和多个槽相连:
如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
2)多个信号可以连接到一个槽:
只要任意一个信号发出,这个槽就会被调用。
3)一个信号可以连接到另外的一个信号:
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
4)槽可以被取消链接:
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
6.Qt4的信号和槽的写法
connect(zt,SIGNAL(hungry(QString)),st,SLOT(treat(QString)));
这里使用了SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。注意到connect()函数的 signal 和 slot 都是接受字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
Qt5在语法上完全兼容Qt4,而反之是不可以的。
七、Lambda 表达式
C++11中的Lambda表达式用于 定义并创建匿名的函数对象,以简化编程工作。
首先看一下Lambda表达式的基本构成:
[capture](parameters) mutable ->return-type
{
statement
}
[函数对象参数](操作符重载函数参数)mutable ->返回值{函数体}