QT信号与槽

信号与槽

信号和槽概述

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

Qt 中的所有控件都具有接收信号的能⼒,⼀个控件还可以接收多个不同的信号。对于接收到的每

个信号,控件都会做出相应的响应动作。例如,按钮所在的窗⼝接收到 "按钮被点击" 的信号后,会做

出 "关闭⾃⼰" 的响应动作;再⽐如输⼊框⾃⼰接收到 "输⼊框被点击" 的信号后,会做出 "显⽰闪烁的

光标,等待⽤⼾输⼊数据" 的响应动作。在 Qt 中,对信号做出的响应动作就称之为槽。

信号和槽是 Qt 特有的消息传输机制,它能将相互独⽴的控件关联起来。⽐如,"按钮" 和 "窗⼝"

本⾝是两个独⽴的控件,点击 "按钮" 并不会对 "窗⼝" 造成任何影响。通过信号和槽机制,可以将 "按

钮" 和 "窗⼝" 关联起来,实现 "点击按钮会使窗⼝关闭" 的效果。

信号的本质

信号是由于⽤⼾对窗⼝或控件进⾏了某些操作,导致窗⼝或控件产⽣了某个特定事件,这时 Qt 对

应的窗⼝类会发出某个信号,以此对⽤⼾的操作做出反应。因此,信号的本质就是事件。如:

  • 按钮单击、双击
  • 窗⼝刷新
  • ⿏标移动、⿏标按下、⿏标释放
  • 键盘输⼊

那么在 Qt 中信号是通过什么形式呈现给使⽤者的呢?

  • 我们对哪个窗⼝进⾏操作, 哪个窗⼝就可以捕捉到这些被触发的事件。
  • 对于使⽤者来说触发了⼀个事件我们就可以得到 Qt 框架给我们发出的某个特定信号。
  • 信号的呈现形式就是函数, 也就是说某个事件产⽣了, Qt 框架就会调⽤某个对应的信号函数, 通知使⽤者。

在 Qt 中信号的发出者是某个实例化的类对象。

槽的本质

**槽(Slot)就是对信号响应的函数。**槽就是⼀个函数,与⼀般的 C++函数是⼀样的,可以定义在

类的任何位置( public、protected 或 private ),可以具有任何参数,可以被重载,也可以被直接调

⽤(但是不能有默认参数)。槽函数与⼀般的函数不同的是:槽函数可以与⼀个信号关联,当信号被
发射时,关联的槽函数被⾃动执⾏。

说明

  1. 信号和槽机制底层是通过函数间的相互调⽤实现的。每个信号都可以⽤函数来表⽰,称为信号函数;每个槽也可以⽤函数表⽰,称为槽函数。
  2. 信号函数和槽函数通常位于某个类中,和普通的成员函数相⽐,它们的特别之处在于:信号函数⽤ signals 关键字修饰,槽函数⽤public slots、protected slots或者 private slots 修饰。signals 和 slots 是 Qt 在 C++ 的基础上扩展的关键字,专⻔⽤来指明信号函数和槽函数;信号函数只需要声明,不需要定义(实现),⽽槽函数需要定义(实现)。

信号和槽的使用

连接信号和槽

在 Qt 中,QObject 类提供了⼀个静态成员函数connect() ,该函数专⻔⽤来关联指定的信号函数和槽

函数。
connect() 函数原型:

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

参数说明:

  • sender:信号的发送者;
  • signal:发送的信号(信号函数);
  • receiver:信号的接收者;
  • method:接收信号的槽函数;
  • type: ⽤于指定关联⽅式,默认的关联⽅式为 Qt::AutoConnection,通常不需要⼿动设定。

代码⽰例: 在窗⼝中设置⼀个按钮,当点击 "按钮" 时关闭 "窗⼝" .

查看内置信号和槽

系统⾃带的信号和槽通常是通过 "Qt 帮助⽂档" 来查询。

在帮助⽂档中输⼊:QPushButton的⽗类 QAbstractButton中继续查找关键字signals

这⾥的 clicked() 就是要找的信号。槽函数的寻找⽅式和信号⼀样,只不过它的关键字是 slot。

通过 Qt Creator ⽣成信号槽代码

代码⽰例: 在窗⼝中设置⼀个按钮,当点击 "按钮" 时关闭 "窗⼝" .

  1. 新建项⽬,如下图为新建完成之后所包含的所有⽂件;

  2. 双击 widget.ui ⽂件,进⼊ UI 设计界⾯;

  3. 在 UI 设计窗⼝中拖⼊⼀个 "按钮" ,并且修改 "按钮" 的名称及字体⼤⼩等;

  4. 可视化⽣成槽函数;

当单击 "转到槽..." 之后,出现如下界⾯:对于按钮来说,当点击时发送的信号是:clicked(),所以此处选择:clicked()

对于普通按钮来说, 使⽤ clicked 信号即可. clicked(bool) 没有意义的. 具有特殊状态的按

钮(⽐如复选按钮)才会⽤到 clicked(bool) .

  1. ⾃动⽣成槽函数原型框架;

    (1)在 "widget.h" 头⽂件中⾃动添加槽函数的声明;

    (2)在 "widget.cpp" 中⾃动⽣成槽函数定义.

  2. 在槽函数函数定义中添加要实现的功能. 实现关闭窗⼝的效果.

自定义信号和槽

基本语法

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

信号函数和槽函数有⼀定的书写规范。

  1. ⾃定义信号函数书写规范

    (1)⾃定义信号函数必须写到 "signals" 下;

    (2)返回值为 void,只需要声明,不需要实现;

    (3)可以有参数,也可以发⽣重载;

  2. ⾃定义槽函数书写规范

    (1)早期的 Qt 版本要求槽函数必须写到 "public slots" 下,但是现在⾼级版本的 Qt 允许写到类的"public" 作⽤域中或者全局下;

    (2)返回值为 void,需要声明,也需要实现;

    (3)可以有参数,可以发⽣重载;

  3. 发送信号

    使⽤ "emit" 关键字发送信号 。"emit" 是⼀个空的宏。"emit" 其实是可选的,没有什么含义,只

    是为了提醒开发⼈员。

⽰例1:

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

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

⽰例2:当⽼师说 "上课了",学⽣们就 "回到座位,开始学习"。

  1. 在源⽂件中新建两个类,⼀个是⽼师类,⼀个是学⽣类

在 Qt 中新建类时, 要选择新建类的⽗类.

显然,当前项⽬中还没啥类适合做新类的⽗类, 同时新的类也不是⼀个 "窗⼝" 或者 "控件". 这种情况

⼀般选择 QObject 作为基类.

这样做的好处是这个新类的对象可以搭配 Qt 的对象树机制. 便于对象的正确释放.

对于 "学⽣类" 以上述同样的⽅式进⾏添加,添加完成之后,项⽬⽬录新增⽂件如下:

在 teacher.h 中声明信号函数:

在 student.h 中声明槽函数:

在 widget.h 中实例化 "⽼师类对象" 和 "学⽣类对象";

在 student.cpp 中实现槽函数:

在 widget.cpp 中连接⾃定义信号和槽;

⽰例3:⽼师点击 "按钮" 触发学⽣上课;

运⾏结果如下图⽰:

带参数的信号和槽

Qt 的信号和槽也⽀持带有参数, 同时也可以⽀持重载.

此处我们要求, 信号函数的参数列表要和对应连接的槽函数参数列表⼀致.

此时信号触发, 调⽤到槽函数的时候, 信号函数中的实参就能够被传递到槽函数的形参当中.

⽰例1:重载信号槽

(1)在 "widget.h" 头⽂件中声明重载的信号函数以及重载的槽函数;如下图所⽰:

(2)在 "Widget.cpp" ⽂件实现重载槽函数以及连接信号和槽。

在定义函数指针时要指明函数指针的作⽤域。

(3)执⾏结果如下图所⽰:

⽰例2:信号槽参数列表匹配规则

  1. 在 "widget.h" 头⽂件中声明信号和槽函数;

  2. 在 "widget.cpp" ⽂件中实现槽函数以及连接信号和槽;

    其实信号的参数个数可以多于槽函数的参数个数,但是槽的参数个数不能多于信号参数个数.

    但是实际开发中最好还是保持参数个数也能匹配⼀致

⽰例3:

  1. 在 "widget.h" 头⽂件中声明信号和槽函数;

  2. 在 "widget.cpp" ⽂件中实现槽函数以及连接信号和槽;

信号与槽的连接⽅式

⼀对⼀

主要有两种形式,分别是:⼀个信号连接⼀个槽 和**⼀个信号连接⼀个信号**。

⼀个信号连接⼀个槽

⽰例:

  1. 在 "widget.h" 中声明信号和槽以及信号发射函数;

  2. 在 "widget.cpp" 中实现槽函数,信号发射函数以及连接信号和槽;

⼀个信号连接另⼀个信号

⽰例:

在上述⽰例的基础上,在 "widget.cpp" ⽂件中添加如下代码:

⼀对多

⼀个信号连接多个槽

⽰例:

  1. 在 "widget.h" 头⽂件中声明⼀个信号和三个槽;

  2. 在 "widget.cpp" ⽂件中实现槽函数以及连接信号和槽;

多对⼀

多个信号连接⼀个槽函数

⽰例:

  1. 在 "widget.h" 头⽂件中声明两个信号以及⼀个槽;

  2. 在 "widget.cpp" ⽂件中实现槽函数以及连接信号和槽;

信号和槽的其他说明

信号与槽的断开

使⽤ disconnect 即可完成断开.

disconnect 的⽤法和 connect 基本⼀致.

⽰例:

使⽤ Lambda 表达式定义槽函数

Lambda表达式 是 C++11 增加的特性。C++11 中的 Lambda表达式⽤于定义并创建匿名的函数对

象,以简化编程⼯作。

Lambda表达式 的语法格式如下:

c 复制代码
[ capture ] ( params ) opt -> ret { 
 Function body; 
};

说明:

capture 捕获列表
params 参数表
opt 函数选项
ret 返回值类型
Function body 函数体

1、局部变量引⼊⽅式 [ ]

\] : 标识⼀个 Lambda表达式 的开始。不可省略。 | 符号 | 说明 | |:------------:|--------------------------------------:| | \[ \] | 局部变量捕获列表。Lambda表达式不能访问外部函数体的任何局部变量 | | \[a\] | 在函数体内部使⽤值传递的⽅式访问a变量 | | \[\&b\] | 在函数体内部使⽤引⽤传递的⽅式访问b变量 | | \[=\] | 函数外的所有局部变量都通过值传递的⽅式使⽤, 函数体内使⽤的是副本 | | \[\&\] | 以引⽤的⽅式使⽤Lambda表达式外部的所有变量 | | \[=, \&foo\] | foo使⽤引⽤⽅式, 其余是值传递的⽅式 | | \[\&, foo\] | foo使⽤值传递⽅式,其余引⽤传递 | | \[this\] | 在函数内部可以使⽤类的成员函数和成员变量,= 和 \& 形式也都会默认引⼊ | 说明: * 由于使⽤引⽤⽅式捕获对象会有局部变量释放了⽽Lambda函数还没有被调⽤的情况。如果执⾏Lambda函数,那么引⽤传递⽅式捕获进来的局部变量的值不可预知。所以绝⼤多数场合使⽤的形式为: \[=\] () { } * 早期版本的 Qt,若要使⽤Lambda表达式,要在 ".pro" ⽂件中添加: CONFIG += C++11因为 Lambda表达式 是 C++11 标准提出的。Qt5 以上的版本⽆需⼿动添加,在新建项⽬时会⾃动添加。 ![在这里插入图片描述](https://file.jishuzhan.net/article/1778683030450212865/03c5aa06ced2e2c28c0a6584e87ff999.webp) ⽰例1:Lambda表达式的使⽤ ![在这里插入图片描述](https://file.jishuzhan.net/article/1778683030450212865/397093013de902b61ce04e7687ae42aa.webp) ⽰例2:以 \[=\] ⽅式传递,外部的所有变量在Lambda表达式中都可以使⽤ ![在这里插入图片描述](https://file.jishuzhan.net/article/1778683030450212865/428f666579775bef90f9448231c57f14.webp) ⽰例3:以 \[a\] ⽅式传递,在Lambda表达式中只能使⽤传递进来的 a ![在这里插入图片描述](https://file.jishuzhan.net/article/1778683030450212865/98adee29f96447ab3bf044f424ae3b80.webp) #### 2、函数参数 ( ) (params) 表⽰ Lambda函数对象接收的参数,类似于函数定义中的⼩括号表⽰函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引⽤(如:(int \&a,int \&b))两种⽅式进⾏传递。函数参数部分可以省略,省略后相当于⽆参的函数。 #### 3、选项Opt Opt 部分是可选项,最常⽤的是 mutable声明 ,这部分可以省略。 Lambda表达式外部的局部变量通过值传递进来时,其默认是 const,所以不能修改这个局部变量的拷 ⻉,加上mutable 就可以修改。 #### 4、Lambda表达式的返回值类型 -\> 可以指定 Lambda表达式 返回值类型;如果不指定返回值类型,则编译器会根据代码实现为函数推导⼀个返回类型;如果没有返回值,则可忽略此部分。 #### 5、Lambda表达式的函数体 { } Lambda表达式的函数体部分与普通函数体⼀致。⽤ { } 标识函数的实现,不能省略,但函数体可以为空。 #### 6、槽函数使⽤Lambda表达式来实现 ⽰例1:点击按钮关闭窗⼝; ![在这里插入图片描述](https://file.jishuzhan.net/article/1778683030450212865/5f3e81d886ceee740df1874da9ba2beb.webp) 当 "connect" 函数第三个参数为 "this" 时,第四个参数使⽤ Lambda表达式时,可以省略掉"this" ### 信号与槽的优缺点 **优点: 松散耦合** 信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了⾃⼰,Qt的信号槽机制保证了信号与槽函数的调⽤。⽀持信号槽机制的类或者⽗类必须继承于 QObject类。 **缺点: 效率较低** 与回调函数相⽐,信号和槽稍微慢⼀些,因为它们提供了更⾼的灵活性,尽管在实际应⽤程序中差别不⼤。通过信号调⽤的槽函数⽐直接调⽤的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是⾮常⾼的场景是可以忽略的,是可以满⾜绝⼤部分场景。

相关推荐
努力也学不会java1 分钟前
【Java并发】深入理解synchronized
java·开发语言·人工智能·juc
TDengine (老段)1 分钟前
TDengine 数学函数 CEIL 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
-雷阵雨-5 分钟前
MySQL——数据类型
数据库·mysql
星竹晨L17 分钟前
【C++】深入理解list底层:list的模拟实现
开发语言·c++
LB211222 分钟前
Redis 黑马skyout
java·数据库·redis
豐儀麟阁贵29 分钟前
Java知识点储备
java·开发语言
TDengine (老段)30 分钟前
TDengine 浮点数新编码 BSS 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
TDengine (老段)34 分钟前
TDengine 数学函数 ASIN() 用户手册
大数据·数据库·sql·物联网·时序数据库·tdengine·涛思数据
豐儀麟阁贵38 分钟前
2.3变量与常量
java·开发语言
什么半岛铁盒2 小时前
C++11 多线程与并发编程
c语言·开发语言·c++