【Qt】9.信号和槽_信号和槽存在的意义
文章目录
- 1. 信号和槽存在的意义
- 2. 信号和槽的其他说明
- 2.1 信号与槽的断开
- 3. 使用 Lambda 表达式定义槽函数
- 4. 信号与槽的优缺点
1. 信号和槽存在的意义
所谓的信号槽,终究要解决的问题,就是响应用户的操作。
信号槽,其实在
GUI
开发的各种框架中,是一个比较有特色的存在。
其他的
GUI
开发框架,搞的方式都要更简洁一些。不需要搞一个单独的connect
完成上述的信号槽连接。网页开发中响应用户操作,主要就是挂回调函数。
处理函数,就像控件的一个属性/成员一样
大部分的
GUI
开发框架都是这么搞的:
- —对—
- 一个事件,只能对应一个处理函数
- 一个处理函数也只能对应到一个事件上
Qt
信号槽,connect
这个机制,设想很美好的
解耦合,把触发用户操作的控件和处理对应用户的操作逻辑解耦合
"多对多"效果
一个信号,可以
connect
(绑定)到多个槽函数上一个槽函数,也可以被多个信号
connect
Qt
中谈到的信号和槽“多对多”就和数据库中的多对多非常类似的。一个信号,可以
connect
到多个槽函数上。一个槽函数,也可以被多个信号
connect
。综上,
Qt
引入信号槽机制,最本质的目的(初心)就是为了能够让信号和槽之间按照“多对多”的方式来进行关联。其他的
GUI
框架往往也不具备这样的特性。实际上,随着程序开发这个事情,大家经验越来越多。
其实在
GUI
开发的过程中,“多对多”这件事,其实是个“伪需求”,实际开发很少会用到。绝大部分情况,一对一就够用了。新出现的一些图形化开发框架,很少有再继续支持这种多对多的了
2. 信号和槽的其他说明
2.1 信号与槽的断开
使用disconnect来断开信号槽的连接。
disconnect使用的方式和connect是非常类似的。
disconnect用的比较少的。
大部分的情况下,把信号和槽连上了之后,就不必管了。
主动断开往往是把信号重新绑定到另一个槽函数上。
运行:
点击修改窗口标题按钮
点击切换槽函数按钮,再点击修改窗口标题按钮
不过切换后就切不回去了,因为没有设置。
为了方便观察,我添加了QDebug:
handleclick//点击“修改窗口标题”按钮
handleclick//点击“修改窗口标题”按钮
//点击下面按钮
handleclick2//点击“修改窗口标题”按钮
handleclick2//点击“修改窗口标题”按钮
//点击下面按钮
handleclick2//点击“修改窗口标题”按钮
handleclick2//点击“修改窗口标题”按钮
如果没有disconnect
,就会构成一个信号绑定两个函数。
handleclick//点击“修改窗口标题”按钮
handleclick//点击“修改窗口标题”按钮
//点击下面按钮
handleclick//点击“修改窗口标题”按钮
handleclick2
handleclick//点击“修改窗口标题”按钮
handleclick2
//点击下面按钮
handleclick//点击“修改窗口标题”按钮
handleclick2
handleclick2
handleclick//点击“修改窗口标题”按钮
handleclick2
handleclick2
这个很有趣了,怎么点一次下面按钮,多一次click2
呢?
- 第一次点击“下面按钮” (
pushButton_2
):
on_pushButton_2_clicked()
被调用。
connect
函数被执行,为pushButton
的clicked
信号 新增 了一个连接到handleclick2
。
- 现在,
pushButton
的clicked
信号同时连接了两个槽:handleclick
和handleclick2
。- 第二次点击“修改窗口标题”按钮:
clicked
信号被发射。Qt会按连接顺序调用所有与之连接的槽函数。
- 先调用
handleclick
-> 输出handleclick
- 再调用
handleclick2
-> 输出handleclick2
- 结果:你看到了
handleclick
和handleclick2
两行输出。- 第二次点击“下面按钮” (
pushButton_2
):
on_pushButton_2_clicked()
再次被调用。
connect
函数又一次被执行,为pushButton
的clicked
信号 又新增 了一个连接到handleclick2
。
- 现在,
pushButton
的clicked
信号连接了三个槽:handleclick
、handleclick2
、handleclick2
。- 第三次点击“修改窗口标题”按钮:
clicked
信号被发射。
- 调用
handleclick
-> 输出handleclick
- 调用第一个
handleclick2
-> 输出handleclick2
- 调用第二个
handleclick2
-> 输出handleclick2
- 结果:你看到了
handleclick
和两行handleclick2
输出。这个过程会不断重复,每点击一次
pushButton_2
,就会多一个handleclick2
的连接,导致输出越来越多。
3. 使用 Lambda 表达式定义槽函数
Qt5
在Qt4
的基础上提高了信号与槽的灵活性,允许使用任意函数作为槽函数。但如果想方便的编写槽函数,比如在编写函数时连函数名都不想定义,则可以通过
Lambda
表达式 来达到这个目的。
Lambda
表达式 是C++11
增加的特性。C++11
中的Lambda
表达式 用于定义并创建匿名的函数对象,以简化编程工作。
Lambda
表达式 的语法格式如下:
[ capture ] ( params ) opt -> ret { Function body;
};
capture
:捕获列表
params
:参数表
opt
:函数选项
ret
:返回值类型
Function body
:函数体
点击按钮后,日志出现:lambda
被执行了!
如果我们往lambda
里面写move
就会报错
因为,lambda
表达式,是一个回调函数。C++
中这个函数,无法直接获取到上层作用域中的变量的。
lambda
为了解决上述问题,引入了“变量捕获”语法。通过变量捕获,获取到外层作用域中的变量。
运行后点击按钮,按钮就到300,300
了。
也可以再改一下 :
运行:
点击后:
如果当前
lambda
里面想使用更多的外层变量咋办?写作
[=]
这个写法的含义就是把上层作用域中所有的变量名都给捕获进来!
效果和上面一样
后续如果我们对应的槽函数比较简单,而且是一次性使用的,就经常会写作这种
lambda
的形式另外也要确认捕获到
lambda
内部的变量是有意义的因为回调函数执行时机是不确定的(用户啥时候点击按钮不知道的)
无论何时用户点击了按钮,捕获到的变量都能正确使用
QPushButton* button = new QPushButton(this);
由于此处button
是new
出来的变量,生命周期跟随整个窗口(挂到对象树上,窗口关闭才会释放)
这个东西就可以在后面随时使用了。
类似的,this
指向的对象widget
lambda
除了可以按照值的方式来捕获变量[=]
还可以按照引|用的方式来捕获[&]
(Qt
中很少这么写)捕获到的变量一般就是各种控件的指针,指针变量按照值传递或者引用来传递,都无所谓。如果按引用,还得更关注这个引用的变量本身的生命周期。
lambda
语法是C++11
中引入的。对于
Qt5
及其更高版本,默认就是按照C++11
来编译的。如果使用
Qt4
或者更老的版本,就需要手动在.pro
文件中加上C++11
的编译选项。
CONFIG += c++11
4. 信号与槽的优缺点
优点: 松散耦合
信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,
Qt
的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于QObject
类。
缺点: 效率较低
与回调函数相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性,尽管在实际应用程序中差别不大。通过信号调用的槽函数比直接调用的速度慢约
10
倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调用速度对性能要求不是非常高的场景是可以忽略的,是可以满足绝大部分场景。
一个客户端程序中, 最慢的环节往往是 “人”。
假设本身基于回调的方式是
10us
, 使用信号槽的方式是100us
。对于使用程序的人来说, 是感知不到的。