目录
[1. 信号和槽的基本概念](#1. 信号和槽的基本概念)
[2. connect() 函数](#2. connect() 函数)
[2.1 概念的介绍](#2.1 概念的介绍)
[2.2 connect() 的具体使用方式:](#2.2 connect() 的具体使用方式:)
[2.3 两个问题](#2.3 两个问题)
[3. 自定义槽函数](#3. 自定义槽函数)
[3.1 以代码方式来实现自定义槽函数](#3.1 以代码方式来实现自定义槽函数)
[3.2 以图形化界面来实现自定义槽函数](#3.2 以图形化界面来实现自定义槽函数)
[4. 自定义信号](#4. 自定义信号)
[5. 带参数的信号槽](#5. 带参数的信号槽)
[5.1 参数个数的影响](#5.1 参数个数的影响)
[5.2 参数类型的影响](#5.2 参数类型的影响)
[5.3 Widget.h中的 Q_OBJECT](#5.3 Widget.h中的 Q_OBJECT)
[6. 存在的意义](#6. 存在的意义)
[7. 信号和槽的断开连接](#7. 信号和槽的断开连接)
[8. 定义槽函数的时候,也是可以使用 lambda 表达式的](#8. 定义槽函数的时候,也是可以使用 lambda 表达式的)
1. 信号和槽的基本概念
Qt 中的信号,涉及到三个要素:
1. 信号源:有哪个控件发出的信号
2. 信号类型:用户进行不同的操作,就可能触发不同的信号
例如:
- 点击按钮,触发点击信号
- 在输入框中移动光标,触发移动光标的信号
- 勾选一个复选框
- 选择一个下拉框,都会触发不同的信号
我们写的 GUI程序,就是要让用户进行操作,就是要和用户进行交互,这个过程中就需要关注,用户当前的操作具体是什么样的操作。
3. 信号的处理方式:槽(slot)---> 就是函数
- Qt 中可以使用 connect 这样的函数,把一个信号和一个槽关联起来
- 后续只要信号触发了,Qt 就会自动的执行槽函数
所谓的 "槽函数" 本质上就是一种回调函数(callback):
- C语言中的回调函数:实现转移表
- C++中的回调函数:STL中,函数对象/仿函数,lambda 表达式
- Linux中的回调函数:信号处理、线程的入口函数
Qt 中,一定是先关联 信号 和 槽,然后再触发这个信号,顺序不能掉到,否则信号就不知道如何处理了!!!
2. connect() 函数
注意:这个函数和 TCP Socket 中的建立连接的函数,没有任何关系,知识名字一样而已
2.1 概念的介绍
connect() 是由 Qobject 提供的静态的成员函数。
Qt 中提供的这些类,本身是存在一定的继承关系的:

所以:Qt 中的类都是可以间接的使用 connect() 函数。
2.2 connect() 的具体使用方式:
connect (const QObject *sender,
const char * signal ,
const QObject * receiver ,
const char * method ,
Qt::ConnectionType type = Qt::AutoConnection )
- sender:信号源,当前这个信号是有哪个控件发出来的
- signal:信号的类型
- receiver:信号的接收者
- method:信号的处理方法,要处理信号的对象提供的成员函数
- Qt::ConnectionType type = Qt::AutoConnection:暂时不用考虑
eg:界面上一个按钮,用户点击按钮,关闭窗口


- click:是一个 slot 函数,作用就是在调用的时候相当于点击了一下按钮,前面有个锯齿的图标就是 槽函数
- clicked:点击完了,要发出一个点击信号,前面有个wifi信号的图标就是 信号函数
- connect 要求这两个参数:button,&QPushButton::clicked是要匹配的,button 的类型如果是 QPushButton* ,此时,第二个参数的信号必须是 QPushButton 内置的信号 / 父类的信号,不能是其它的类的信号:QLineEdit 的信号
- 第四个参数也可以写成:&Widget::close,close 其实是QWidget 内置的槽函数,但是 Widget 是 QWidget 的子类,所以也继承了父类的槽函数
- close 前面也是有锯齿的图标的,所以 close 也是槽函数,具体的实现不需要我们去关心,具体的作用就是关闭当前的窗口 / 控件

运行:

一点击,就会关闭该窗口。
2.3 两个问题
- 第一个问题:怎么知道 QpushButton 中有 clicked 信号?怎么知道 Widget 中有 close 信号?
- 回答:多看文档 ->

在文档中,在 QpushButton 当前类并没有找到对应的 clicked 信号,有可能是在 QpushButton 的父类中,父类的父类,父类的父类的父类......

QAbstractButton Class 中存在 clicked :

点击进去,就是详细的说明:
signal void QAbstractButton::clicked(bool checked = false)
This signal is emitted when the button is activated (i.e., pressed down then released while the mouse cursor is inside the button), when the shortcut key is typed, or when click() or animateClick() is called. Notably, this signal is not emitted if you call setDown(), setChecked() or toggle().
If the button is checkable, checked is true if the button is checked, or false if the button is unchecked.
See also pressed(), released(), and toggled().
译文:
信号 void QAbstractButton::clicked(bool checked = false)
当按钮被激活(即鼠标光标位于按钮内时按下并释放)、按下快捷键,或调用 click() 或 animateClick() 时,会发出此信号。需要注意的是,如果调用了 setDown()、setChecked() 或 toggle(),则不会发出此信号。
如果按钮是可选中的,则当按钮被选中时 checked 为 true,未选中时为 false。
另请参见 pressed()、released() 和 toggled()。
Widget对应的父类好像没有在文档中找到,但是在编码的地方可以看见,Widget 继承 QWidget:

而Qwidget中包含close函数:

- 第二个问题:connect() 函数中的第二个参数和第四个参数要求的类型是 char* ,但是我们在上面通过点击按钮来实现关闭窗口的界面中,调用的connect函数中的第二个和第四个的参数类型是跟标准的不一样的

char* 和 函数指针 不是同一个东西,其实所谓的指针就是一个"统称",char*, short* , int*, 结构体* 都是不同的类型!!!


分别对应的就是 void(*)() 和 bool(*)(),再怎么说都是和 connect 函数中规定的第二个参数:const char * signal ,和第四个参数:const char * method , 不一样呀!!!
查看配套的文档:

这个函数声明是以前旧版本的 Qt 的connect 函数的声明,以前的版本和现在的其实是有区别的,此时给信号参数传参,就要搭配一个SIGNAL宏,给槽函数传参就要搭配一个 SLOT 宏,此时传入的函数指针就会被转成 char*。

从 Qt 5开始,对上面的写法进行了简化,不再需要写上面的两个宏了,给connect 提供了重载版本,重载版本中,第二个参数和第四个参数成了 泛型参数,允许我们传入任意类型的函数指针了。
在编写编码的地方,跳转到它的声明:

此时 connect 函数就带有了一定的 参数检查 功能,如果你传入的第一个参数和第二个参数不匹配,或者第三个和第四个参数不匹配(不匹配:2,4参数的函数指针,不是1,3参数的成员函数)此时代码编译出错!!!
3. 自定义槽函数
自定义槽函数非常关键,开发中大部分情况都需要自定义槽函数的。槽函数就是用户触发某个操作之后,要进行的业务逻辑。
3.1 以代码方式来实现自定义槽函数
所谓的 slot 就是普通的成员函数,所谓的自定义一个 槽函数,操作过程和自定义一个普通的成员函数,没啥区别!!
通过点击按钮,实现窗口的标题变化:


运行:


3.2 以图形化界面来实现自定义槽函数
拖拽一个 PushButton 过去,右键,选择转到槽:

修改pushbutton中的内容:


选择clicked():

这个窗口就列出来了 QPushButton 给我们提供的所有的信号,同时还包含了 QPushButton 父类的信号!!!
自动跳到补全的代码界面中:

填写内容:

运行:

点击一下按钮

在 Qt 中,除了通过 connect 来连接信号槽之外,还可以通过函数名字的方式来自动连接!!!
在 widget.h 文件中自动生成的:

pushButton:这个部分式按钮的 objectName:

clicked():是信号的名字

此处的 slots 是Qt 自己扩展的关键字(不是C++标准中的语法),Qt 里广泛使用了 元编程 技术(基于代码,生成代码),qmake 构建 Qt 项目的时候,就会调用专门的扫描器,扫描代码中特定的关键字(slots这种),基于关键字自动生成一大堆相关的代码。
void on_pushButton_clicked(); 将 pushButton 这个按钮,点击这样的信号,来和当前这个函数建立连接。
当函数名符合上述规则之后,Qt 就能自动的把信号和槽给建立上联系!!!
将名字改掉,pushButton可没有click这样的信号,他只有一个槽叫做click:


再次运行一下程序,再次点击按钮,就不行了:

此时,也报错了:

其实,在 Qt 中调用这个函数:connectSlotsByName 的时候,就会触发上述自动连接信号槽的规则!!!
connectSlotsByName 此函数就是在我们自动生成的那个 ui_widget.h 的那个头文件:

如果通过图形化创建控件,还是推荐使用这种快速的方式来连接信号槽。如果我们是通过代码的方式来创建控件,还是得手动 connect(你的代码中没有调用connectSlotsByName ,不过你调用了,也可使用上述那种自动的方式)
4. 自定义信号
自定义信号比较少见,实际开发中很少会需要自定义信号。信号对应到用户的某个操作,在 GUI,用户能够进行哪些操作,是可以穷举的,Qt 内置的信号,基本上已经覆盖到上述所有可能的用户操作。
因此,使用 Qt 内置的信号,就足以应付大部分的开发场景了。
Widget 虽然还没有定义任何信号,由于继承自 QWidget 和 QObject,这两类里面已经提供了一些信号,可以直接使用。
所谓的 Qt 的信号,本质上就是一个 函数,Qt 5 以及更高版本中,槽函数和普通的成员函数之间没啥差别了。
但是,信号,则是一类非常特殊的函数:
- 程序员只要写出函数声明,并且告诉Qt,这是一个 "信号"即可。这个函数的定义,是 Qt 在编译过程中,自动生成的。(自动生成的过程,程序员无法干预)信号在 Qt 中是特殊的机制。Qt 生成的信号函数的实现,要配合 Qt 框架做很多既定的操作
- 作为信号函数,这个函数的返回值,必须是 void,有没有参数都可以,也可以支持重载

这个也是 Qt 自己扩展出来的关键字,qmake 的时候,调用一些代码的分析 / 生成工具,扫描到类中 包含 signals 这个关键字的时候,此时,就会自动的把下面的函数声明认为是信号,并且给这些信号函数自动的生成函数定义





运行:窗口的标题并没有改变

因为这里只进行了将信号和槽建立连接,不代表信号发出来了!!!信号并没有真正的触发出来。

如何才能触发出自定义信号呢?
Qt 中内置的信号,都不需要我们手动通过代码来触发,用户在 GUI,进行某些操作,就会自动触发对应的信号(发射信号的代码已经内置到 Qt 框架中了)
关键字 emit 发射,emit mySignal(); 就会触发对应的信号。


发送信号的操作,也可以在任意合适的代码中,不一定非得在构造函数里。


运行:

点击按钮:


其实在 Qt5 中 emit 现在啥也不做,真正的操作都包含在 mySignal 内部生成的生成的函数定义了,即使不写 emit,信号也能发出去,即使如此,实际开发中,建议还是把 emit 都加上,加上可读性更高,更明显的标识出,这里是发射自定义的信号了
5. 带参数的信号槽
上述的信号和槽都是没有参数的。
信号和槽,也可以带参数。**当信号带有参数的时候,槽的参数必须和信号的参数一致。**此时发送信号的时候,就可以给信号函数传递实参,与之对应的这个参数就会被传递到对应的槽函数中,就可以起到让信号给槽传参效果了

函数声明的时候可以只写参数的类型即可,可以不用写形参的名字
参数一致,一致主要是要求的是类型,个数如果不一致也是可以的,但是不一致的时候,要求信号的参数必须要比槽的参数的个数要更多

运行,点击:

传参可以起到复用代码的效果,有多个逻辑,逻辑整体一致,但是涉及到的数据不同,就可以通过函数-参数来复用代码,并且在不同的场景中传入不同的参数即可


运行:

分别点击:


通过一套信号槽,搭配不同的参数,就可以起到设置不同标题的效果

5.1 参数个数的影响
Qt 中很多内置的信号,也是带有参数的的(这些参数不是我们自己传递的)clicked 信号就带有一个参数:

参数为:bool,这个参数表示当前按钮是否处于 "选中" 状态,这个选中状态 对于 QPushButton 没啥意义,对于 QCheckBox 复选框,就很有用了。
信号函数的参数个数,超过了槽函数的参数个数,此时,都是可以正常使用的




运行:


但是信号函数的参数个数,少于槽函数的参数个数,此时代码是无法编译通过的


运行:

为什么允许信号的参数比槽的参数多???
一个槽函数,有可能会绑定多个信号,如果我们严格要去参数个数一致,就意味着信号绑定到槽的要求变高了,换而言之,现在的规则,则允许信号和槽之间的绑定更加灵活了,更多的信号可以绑定到这个槽函数上了。
个数不一致,槽函数就会按照参数的顺序,拿到信号的前 N 个参数,至少需要确保,槽函数的每个参数都是有值的。
5.2 参数类型的影响



带有参数的信号,要求信号的参数和槽的参数要一致。
类型,个数要满足要求(信号的参数个数要多于槽的参数个数)
5.3 Widget.h中的 Q_OBJECT

**Qt 中如果要让某个类能够使用信号槽(可以在类中定义信号和槽函数),则必须要在类最开始的地方,写下 Q_OBJECT 宏,属于 Qt 中硬性规定。**Q_OBJECT 这个宏展开会有很多额外的宏的代码,再进一步展开:

进行信号槽相关操作的话,就必须加上 Q_OBJECT 这个宏,否则就会出错!!!


6. 存在的意义
1. 解耦合,把触发 用户操作的控件 和 处理对应用户的操作逻辑 解耦合
2.实现 "多对多"的效果:
- 一个信号,可以 connect 到多个槽函数上
- 一个槽函数,也可以被多个信号 connect





但是,其实在 GUI 开发的过程中,"多对多" 这件事,其实是个为需求,实际开发中很少会用到,绝大部分情况,一对一就够用了
7. 信号和槽的断开连接
使用 disconnect 来断开信号槽的连接,disconnect 使用的方式和 connect 是非常类似的,disconnect 用的情况是比较少的,大部分的情况下,把信号和槽连上了就不用管了。主动断开往往是把信号重新绑定到另一个槽函数上。




运行:


如果没有 disconnect,就会构成,一个信号绑定了两个槽函数,触发信号的时候,两个槽函数,都会执行,一对多


8. 定义槽函数的时候,也是可以使用 lambda 表达式的
lambda 本质就是一个 "匿名函数",主要应用在 "回调函数"场景中,用于一次性使用

运行:

想实现点击按钮之后,按钮移动:
lambda 表达式,是一个回调函数,这个回调函数,无法直接获取到上层作用域中的变量的,lambda 为了解决上述问题,引入了 "变量捕获" 语法,通过变量捕获,获取到外层作用域中的变量

运行:

点击按钮:

但是,如果当前 lambda 里面想使用更多的外层变量该咋办???
写作 =,这个写法的含义就是把上层作用域中所有的变量名都给捕获进来!!!

后续,如果我们对应的槽函数比较简单,而且是一次性使用的,就经常会写作 lambda 的形式
另外也要确认捕获到的 lambda 内部的变量是有意义的:回调函数执行的实际是不确定的,用户不知道什么时候点击按钮,无论何时用户点击了按钮,捕获到的变量都能正确使用

button 是 new 出来的变量,生命周期是跟随整个窗口(挂到对象数上,窗口关闭才会释放)这个东西就可以在后面随时使用了
类似的,this 指向的对象 widget:

widget 是在 main 函数结束销毁,main函数结束,说明进程结束,只要进程不结束,widget 就可用,this 也就可用了。
lambda 除了可以按照 值的方式来捕获变量 =,还可以按照引用的方式来捕获 \& (Qt 中很少这么写) 捕获到的变量一般就是各种控件的指针,指针变量按照值传递或者引用来传递,都无所谓,但是按照引用的话,还得关注这个引用的变量本身的生命周期。

