Qt信号与槽

"流云开一朵~"


如果你系统地学习过Linux,你一定对"信号"一词不会感到陌生。当我们打开命令行终端,在键盘上敲击"Ctrl +C"、"Ctrl+Z"等组合键,当前命令行执行的程序就会收到系统发出的信号。信号的三要素:信号源、信号类型、信号处理方式,其在Qt中也有信号的概念,它与操作系统层面上的信号有何区别呢?

------前言


信号和槽概述

在 Qt 中,⽤⼾和控件的每次交互过程称为⼀个事件。"用户关闭窗口"是一个实践,"用户"点击按钮是一个事件。每个事件都会发出⼀个信号,例如⽤⼾点击按钮会发出 "按钮被点击" 的号,用户关闭窗口时会发出"窗口被关闭"的信号。

信号发出只是一个过程,或者是只是一种前提。当我们看到红绿灯时,灯光发出红光是一种信号,但我们更要关心的是,这种信号对应要做出的行为是,"停在白线以内,等待绿灯亮起"。显然,这种行为是约定俗成的规定。

Qt中的所有控件都具有接收信号的能力,⼀个控件还可以接收多个不同的信号。对于接收到的每一个信号,都会做出相应的响应动作。而在 Qt 中,对信号做出的响应动作就称之为槽。

🥚 因此,Qt中的信号槽机制,就是给用户发起的事件,添加处理函数。

信号与槽关联函数

当你为一个信号,量身定做了一个函数。可是,你却不让这个信号知道,自己有一个DIY。就像你不告诉小孩子,"在绿灯的条件下,路过斑马线",而顽皮的孩童只知道"过斑马线"。

Connect函数

cpp 复制代码
QMetaObject::Connection QObject::connect(const QObject *sender, 
    const char *signal, 
    const QObject *receiver, 
    const char *method, Qt::ConnectionType type = Qt::AutoConnection)

# Sender: 信号发出者
# signal: 信号类型
# recviver: 信号接收者
# method: 槽函数(信号处理方法) 

注:在Linux中也有一个connect函数,只不过它是在网络编程TCP之中。两者不是同一类


槽的本质

槽(Slot)就是对信号响应的函数。槽就是⼀个函数,与⼀般的 C++ 函数是⼀样的,可以定义在类的任何位置。可以具有任何参数,可以被重载,也可以被直接调⽤。

槽函数与⼀般的函数不同的是:

槽函数可以与⼀个信号关联,当信号发射时,关联的槽函数会⾃动执⾏。

槽函数与信号函数

🍮 信号和槽机制底层是通过函数间的相互调⽤实现的:

每个信号都可以⽤函数来表⽰,称为信号函数;每个槽也可以⽤函数表⽰,称为槽函数。

🍮 信号函数和槽函数通常位于某个类中:

信号函数⽤ signals 关键字修饰,槽函数⽤public slots、protected slots或者private slots修饰。

信号函数只需要声明,不需要定义(实现),⽽槽函数需要定义(实现)。


信号与槽的使用

建立信号与槽的联系,我们可以使用两种方式:

使用代码建立

cpp 复制代码
connect(myButton,&QPushButton::clicked,this,&Widget::handler_button);

slots:
void Widget::handler_button()
{
    if(myButton->text() == "hello qt"){
        myButton->setText("hello world");
    }
    else{
        myButton->setText("hello qt");
    }
}

当我们运行程序后,点击该按钮时,就会发出信号。

接收信号,执行我们connect好的槽函数,完成整个信号处理:

使用ui建立

选择"转到槽":

选择"clicked()"函数,这是QPushButto组件里自带的信号函数。

点击确定后,会在Widget文件下,创建一个新的函数------"槽函数"。

点击按钮后,文本框里的内容发生改变:

我们在编写Qt代码时,使用、控制控件的方式有两种,一种是代码的方式,另一种则是使用ui。ui方式不过是使用xml格式,最终还是会转为C++文件,等待编译。

至于选择哪一种,就看谁方便,就用哪一种。


内置信号与槽

我们通过代码能够直观地使用信号与槽了,可是,我作为一个初学者,咋知道要选择Clicked()作为信号函数呢?这个问题,我们得打开Qt 为我们使用者提供的官方文档:

去Contents中寻找"signals",如果每找到,就去它继承类查找:

进入QAbstractButton后,我们就能够在Content中找到Signals了,其中包含了如下内置的信号函数、槽函数。

在Qt中如果想要让某一个类能够使用信号槽,要在类最开始的地方写下:"Q_OBJECT"

自动生成槽函数:

在通过ui方式建立信号与槽函数的连接时,我们发现"转到槽"生效后,会自动生成:
函数。

⾃动⽣成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中:

🍧 以 " on " 开头,中间使⽤下划线连接起来;

🍧 " XXX " 表⽰的是对象名(控件的 objectName 属性);

🍧 " SSS " 表⽰的是对应的信号。

正如:" on_pushButton_clicked() " ,pushButton 代表的是对象名,clicked 是对应的信号。

按照这种命名⻛格定义的槽函数, 就会被 Qt ⾃动的和对应的信号进⾏连接。但一般情况下,除非是IDE自动生成。我们手动进行connect更好,⼀⽅⾯显式 connect 可以更清晰直观的描述信号和槽的连接关系;另⼀⽅⾯也防⽌信号或者槽的名字拼写错误导致连接失效。

再谈connect函数:

在前面,我们谈及了connect函数是什么,以及它的用法:

我们也知道函数传参是这个过程:

但char* 是一个指针类型,函数指针又是另外一种指针类型,两者压根不是同类型的,怎么可能进行赋值呢?

我们使用Qt 帮助手册中查到,要使用这个connect函数,进行函数传递的时候,需要搭配SINGAL、SLOT宏,这些宏才能够让我们传递的函数指针转变为char*。但,在之前我们压根没有使用SLOT、SIGNAL宏完成这一步必要的转换啊!

从Qt 5开始,上述写法做出了简化,不必再使用SLOT、SINGAL宏了。connect提供了重载版本,在这个重载版本中,第二个和第四个参数成为了泛型参数,允许传入任意指针类型了:

这里泛型参数使用了封装的类型萃取器,能够在编译时得到传入参数的类型,当然这不是本篇要细说的。

此时的connect函数还具备了一定的 "参数检查功能",一旦传入的第一个第二个、第三个第四个形参类型不匹配,就会编译报错。


自定义信号与槽

在 Qt 中,允许⾃定义信号的发送⽅以及接收⽅,即可以⾃定义信号函数和槽函数。但是对于⾃定义的信号函数和槽函数有⼀定的书写规范。

⾃定义信号函数书写规范:

🍿⾃定义信号函数必须写到 "signals" 下.
🍿返回值必须为void,只需要声明,不需要实现.
🍿可以有参数,也可以发⽣重载.
作为信号是一种非常特殊的函数,程序员只需要写出函数声明,告诉Qt这是一个信号即可。这个函数的定义,会在Qt的编译过程中自动生成。

⾃定义槽函数书写规范:

🍜 早期的 Qt 版本要求槽函数必须写到 "public slots" 下但是现在⾼级版本的 Qt 允许写到类"public"作用域或者全局下.

🍜 返回值为 void,需要声明,也需要实现.
🍜 可以有参数,可以发⽣重载.
使⽤ "emit" 关键字发送信号。 "emit" 是⼀个空的宏。"emit" 其实是可选的,没有什么含义,只 是为了提醒开发⼈员。同样,直接替代使用函数名也是可以的,但尽量选择前者。

建立自定义信号与槽:

在 widget.h 中声明⾃定义的信号和槽,如图所⽰:

在 widget.cpp 中实现槽函数,并且关联信号和槽:

带参数的信号与槽:

Qt 的信号和槽也⽀持带有参数, 同时也可以⽀持重载。此处我们要求:

🍓 信号函数的参数列表要和对应连接的槽函数参数列表⼀致,通过这样的机制, 就可以让信号给槽传递数据。

重载信号槽和信号函数:


多对多

信号函数与槽函数的连接方式有哪几种呢?

所以,一个结论:一个槽函数可以被多个信号绑定,一个信号可以绑定多个槽函数。

在实际写代码的过程中,我们也会发现信号函数与槽函数参数是有特点的:

这是因为,一个槽函数可以被多个信号绑定。如果严格要求,信号函数的参数与槽函数参数一致,在这样的规则下,绑定会变得不够灵活。

当与信号参数不一致时,至少能够拿到前N个参数,保证槽函数中的每个参数都是有值的。

所以,一个结论:信号函数的参数个数多于或等于槽函数的参数个数。

如何理解多对多?

把槽函数这个词放到系统编程中,通常被称为:"回调函数"。当事件机制被触发,就会执行回调函数。这种一对一的模式下,一个处理函数只能对应到一个事件上。

Qt中一个信号可以选择多个槽函数,一个槽函数可以与多个信号关联,只要你不手动断开双方的连接,一旦信号发射,就会执行槽函数。此时,这个connect就如同一个关联表一样:记录着信号与槽函数关联的细节信息。

Qt引入信号槽机制,本心是为了让"信号与槽"按照多对多的关系进行关联,从而能够应付复杂的现实问题。但, 在GUI图形化界面的开发过程中,压根不需要这种"复杂的需求","一对一"模式是仅够了的。


信号槽补充

(1) 断开信号与槽

使⽤ disconnect 即可完成断开. disconnect 的⽤法和 connect 基本一致。

(2) Lambda表达式

Qt5 在 Qt4 的基础上提⾼了信号与槽的灵活性,允许使⽤任意函数作为槽函数。 但如果想⽅便的编写槽函数,⽐如在编写函数时连函数名都不想定义,则可以通过 Lambda表达式来达到这个⽬的。

注:Lambda表达式不是本篇需要细讲的

当我们不断发射信号,就会执行connect中绑定的Lambda表达式。不过,Lambda表达式是C++11引入的,所以在负责编译的配置文件中,要加上:(默认是加了的)


信号与槽的优缺点

优点: 松散耦合

🍁信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了⾃ ⼰,Qt的信号槽机制保证了信号与槽函数的调⽤。

🍁⽀持信号槽机制的类或者⽗类必须继承于 QObject 类。

缺点: 效率较低
🍁 与回调函数相⽐,信号和槽慢⼀些,因为它们提供更⾼的灵活性。但在实际应用中差别 不⼤。


总结:

① 什么是信号?什么是槽?

② Qt内置的信号与槽函数

③ 自定义信号与槽函数:不带参的、带参的

④ disconnect、Lambda用法简化槽函数定义


本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

相关推荐
Elihuss1 小时前
ONVIF协议操作摄像头方法
开发语言·php
Swift社区5 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht5 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht5 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20245 小时前
Swift 数组
开发语言
stm 学习ing6 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc7 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe7 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin7 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python