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用法简化槽函数定义


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

祝你好运,向阳而生~

相关推荐
奋斗的小花生1 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功1 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程1 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
Mr.Q3 小时前
Qt多边形填充/不填充绘制
qt
萧鼎4 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步