QT学习日记 | 信号与槽

目录

前言

一、初始信号与槽

1、信号与槽的本质

2、信号与槽的使用

3、内置信号、内置槽函数与自定义信号、自定义槽函数

(1)文档查询

(2)自定义信号与内置槽函数的使用

4、信号与槽函数关联关系

5、带参数的信号与槽函数

6、信号与槽函数的重载

二、深剖信号与槽函数细节

[1、宏函数 SIGNAL 与 SLOT](#1、宏函数 SIGNAL 与 SLOT)

[2、为啥通过 ui 指针可以操作界面中拖动创建的控件](#2、为啥通过 ui 指针可以操作界面中拖动创建的控件)


前言

本文主要学习QT最重要的机制之一 ------ 信号与槽;从认识信号与槽到熟练掌握、深刻理解信号与槽相关细节;

一、初始信号与槽

不少朋友在听到信号这一词可能会想到 Linux 中的信号机制,本文中的信号与 Linux 中的信号没有任何关系,但是两者之间又非常的相似;当某一时间到来时,操作系统给当前进程发送一个信号,当进程收到这个信号后会调用指定的处理方法来处理这个信号,关于信号更多细节可以关注下面这篇文章;

Linux | 信号-CSDN博客

1、信号与槽的本质

**信号本质:**在 Qt 中,所谓信号就是某个事件,当某个控件发生了某个事件时,可以根据这个事件做出指定动作;这里的事件就是信号,比如 Qt 中的按钮QPushButton,这个控件被点击,这个点击就是一个事件;

**槽本质:**在 Qt 中,所谓槽的本质就是收到信号时,我们对应需要进行处理这个信号的动作,还是以上述按钮为例,我们希望用户点击按钮时,窗口的标题会发生改变,这里窗口标题发生改变就是槽函数所处理的动作;

2、信号与槽的使用

我们通过 connect 来将某个信号与槽函数来进行绑定,从而达到某个信号发出时,指定的槽函数会被调用;connect 函数如下所示;

QMetaObject::Connection QObject::connect

(

const QObject *sender,

const char *signal,

const QObject *receiver,

const char *method,

Qt::ConnectionType type = Qt::AutoConnection

)
这样拆解不知道看起来是否更加清晰,该函数有五个参数,其中第五个参数一般为缺省即可,因此我们仅仅需要关注前四个参数;

参数一:该参数类型为QObject类的指针,为信号的发出者的地址;

参数二:该参数为 const char* ,该参数为对象发出的信号;

参数三:该参数类型也为QObject类指针,为信号的接收者,也是处理信号的对象;

参数四:该参数为 const char* ,为接收者所要处理的动作;

注意:

1、第一个参数和第三个参数为QObject类,该类为 Qt 提供的一个基类,Qt 为我们提供的所有类都继承自该类,该类可以说是 Qt 所有内置类的 "祖先类";联想一下C++中切片,也就是说派生类可以赋值给基类;

2、参数二和参数四为const char*类型,而实际上,我们传入的信号和槽函数都是一个函数指针,因此在使用时,我们需要使用SIGNAL和SLOTS将对应的函数指针转换成const char*类型;
**需求设计:**我们在ui页面中拖出一个按钮组件,我们期望按下这个按钮指定的窗口也关闭;

1、我们先创建一个基于Widget类的窗口项目;

2、我们点击widget.ui文件,绘制一个按钮,并设置按钮文本

3、信号与槽函数关联

注意到这一步时,我们有两种方法,两种方法各有优劣,实际开发中,哪种方法方便用哪种即可;

3.1、代码添加连接

1)切换到 widget.h 文件,写下槽函数声明

注意这里的函数声明中,有一个我们陌生的关键字,slots,这个是 Qt 自己设置的关键字,同样,我们还可以与 protected、private组合;这个关键字表示声明的是槽函数;在 Qt5 后提出可以省略该关键字;

2)编写槽函数

这里教大家一个小技巧,我们可以直接选中我们函数声明,按住键盘 alt + enter,可以直接一键生成函数定义;生成的函数定义当然放在widget.cc文件,声明与定义分离,这一点以后就不在重复;

这里再次补充一下,qDebug是 Qt 为我们提供的打印类,我们使用这个来替代cout;

3)建立连接

**注意:**这里我们第二个参数和第四个参数并没有使用宏函数SIGNAL()与SLOTS(),这一点我们后面说明;

**参数一:**这里我们用ui界面绘制的这个按钮,因此我们在ui这个指针下寻找按钮对象;

**参数二:**这里clicked表示点击信号,这里点击表示鼠标按下并抬起;同样还有如下信号;

关于这里的状态切换以及点击带参数这两个可能大家不大理解,这里暂不做介绍,后面讲解复选框自然会介绍;
**参数三:**处理信号的对象,也就是接收者;

**参数四:**信号的处理动作;(这里动作也就是槽函数,我们可以使用 Qt 为我们提供的,也可以使用自己写的,这里用自己写的,填写我们刚写下的槽函数地址即可)

此时,我们想要的功能便实现了,可以点击运行项目,点击按钮后会关闭窗口,并且还会打印内容;接下来介绍第二种方法,我们重新创建一个新的项目;

3.2、自动添加链接

1)还是在ui界面绘制一个按钮,步骤就不再赘述,与上述方法中步骤一相同

2)右击按钮,选择转到槽

此时会弹出如下窗口,实际上,这里就是选择按钮发出的信号,等同于我们调用connect时前两个参数的填写;这里前五个信号,我们在上述都讲过的,这里不再赘述;我们直接选择clicked;

此时会在widget.cpp文件自动生成函数定义,且这个函数定义名字是有讲究的,我们不能修改这个槽函数的名字,否则会关联失败,具体原因,后面会详细介绍;

3)编写槽函数

到这一步,我们连接就完毕了,所有工作完成;我们可以直接运行程序查看效果;

3、内置信号、内置槽函数与自定义信号、自定义槽函数

上述案例中,我们使用了内置信号clicked,与自定义槽函数handler,实际上, Qt 也为我们提供了一些内置信号与槽函数,我们可以通过Qt Creator的帮助文档中查询;下面我们以 QpushButton 为例;

(1)文档查询

点击 Qt Creator 左侧帮助一栏;

在索引中输入指定目标,这里输入 QPushButton;

该类相关接口菜单如下(右边那个,用红色框圈住了菜单中常用的);

这里我们发现,我们并没有看到信号,实际上由于继承这一特性,信号很可能在父类,我们可以通过如下方式找到其父类;

我们点击找到其父类,我们在父类的属性菜单中,很容易找到了信号与槽函数字段;

如下所示,这信号不就是我们刚刚讲过的信号吗?我们便可以通过这样的方式进行文档查询;

(2)自定义信号与内置槽函数的使用

之前那个关闭窗口,我们是使用内置信号 clicked 与自定义槽函数 handler 来实现的;下面我们再使用 自定义信号与内置槽函数 close 再来实现一次;我们首先再次创建一个新项目;
还是一样,ui界面绘制按钮,如下所示;

widget.h文件声明信号;

**注意:**信号仅需声明,无需定义,且无返回值;
我们在使用connect关联函数;

这就完了吗?并没有,这里我们仅仅只是进行了信号关联,我们并没有发送这个信号;我们可以使用 emit关键字来发送信号;我们再返回ui页面,右击按钮,转到槽,选择clicked;然后我们再这个槽函数发送信号;

编译运行程序,此时我们便可以点击按钮,发出自定义信号,接着窗口就会便关闭;

4、信号与槽函数关联关系

**一对一:**一个信号对应一个槽函数;

**一对多:**一个信号对应多个槽函数;

**多对多:**多个信号对应多个槽函数;

之前,我们都是采用一对一的方式,接下来,我们再来试试一对多;我们再创建一个空项目,继承自widget类;
绘制出一个按钮,如下所示;

在widget声明一个信号与两个槽函数;

实现这两个槽函数,槽函数啥都不干,完成一个打印自己函数名的工作即可;

为这两个槽函数建立连接,都连接到 mySignal 这一个信号上;

我们想点击按钮,执行这两个槽函数,因此我们为按钮的点击信号设置一个槽函数;来到设计师界面(ui界面),右击按钮,转到槽,选择clicked信号;并编写点击按钮对应槽函数;

此时我们编译运行程序,我们每次点击按钮都会发送要给 mySignal 信号,而这个信号与 handler1 与 handler2 槽函数都进行了关联,这两个槽函数都会被执行,因此一次点击就会有两个打印结果;

同样多对多的方式我就不一一演示了;有兴趣的可以自己下去做实验;

拓展:我们不仅可以信号与槽函数进行关联,我们还以信号与信号关联,一个信号被触发,他会触发另一个信号;

5、带参数的信号与槽函数

我们的信号与槽函数可以带参,通过参数,将信号的参数传递给槽函数;不过我们必须遵守如下规则;

1、参数类型匹配

2、信号的参数个数要大于等于槽函数参数
我们声明一个信号,两个槽函数,如下所示;

接下来,我们在.cpp文件中对这两个槽函数进行定义;

我们在构造函数中连接并发送这个信号;

接着运行程序,如下所示;

6、信号与槽函数的重载

信号与槽函数可以进行重载,只不过在重载后,进行connect时,需要显示指定connect哪一个重载函数;

依然,我们创建一个信号,用这个信号连接两个重载槽函数;首先创建新项目,声明如下信号与槽函数;

我们在对声明槽函数进行定义,如下所示;

最后对槽函数进行连接,主要是这里连接的代码,由于函数重载,我们如果直接连接,不知道连接到哪一个槽函数,因此我们需要先显示指定出类型,然后再连接;如下代码所示;

尤其是红色框内的代码逻辑,函数指针那块代码可能会有些绕;

二、深剖信号与槽函数细节

1、宏函数 SIGNAL 与 SLOT

前面我们说过connect第二个和第四个参数是 const char* 类型,需要用宏 SIGNAL 与 SLOT 将函数指针转换成 const char* 类型;可我们上述所有代码都没有使用这两个宏函数;这是因为在 Qt5 中,提供了这个函数的另一个版本;

template <typename PointerToMemberFunction>

QMetaObject::Connection QObject::connect

(

const QObject *sender,

PointerToMemberFunction signal,

const QObject *receiver,

PointerToMemberFunction method,

Qt::ConnectionType type = Qt::AutoConnection

)
这个PointerToMemberFunction是一个模板,这里涉及C++中萃取技术;因此在 Qt5 中,我们通常不用使用这两个宏函数;

2、为啥通过 ui 指针可以操作界面中拖动创建的控件

首先我们来看 ui 指针到底是什么类型,这个指针声明在 widget.h 文件中;

它的类型是Ui命名空间里的Widget类指针;注意,我们这个 ui 指针所在类的类名也是Widget,而这个Widget并不在Ui这个命名空间内;那么Ui命名空间的Widget在哪里声明的呢?

实际上,Qt 采用元编程的技术,所谓元编程,就是用代码生成代码,我们的 Qt 代码首先经过 qmake 编译器,编译出C++代码,再拿这个C++代码生成最后可执行程序;

如何证明?

我们编译程序后,会在同级目录下生成一个Build文件夹,这个文件夹就会存放qmake编译生成的代码;

以HelloWorld项目为例;我们点金对应生成的Build文件夹;

这个ui_widget.h 文件便是我们 设计师页面,生成的头文件,我们点进这个文件;

Ui命名空间内的Widget便是继承于Ui_Widget类;

我们通过拖动控件的方式来构建一个Label标签;如下所示;

我们重新编译,接着来看Build文件夹下的 Ui_Widget.h 文件;

这时,我们的 Ui_Widget.h 文件多了一个QLable控件;同样的代码,我们给这个QLabel 改个名字;我们再观察这个文件;我们把这个QLabel控件的名字改为 label_1234;

我们观察 Ui_Widget.h 文件,发现QLabel标签的名字果然发生了改变;

综上所述,我们通过ui指针本质就是 Ui_Widget 类型的指针,而这个类是 widget.ui 文件经过qmake编译后,生成了一个Ui_Widget.h 的文件,而这个文件内有 Ui_Widget 这个类;

而我们 Widget.h 文件中声明的 ui 指针正是Ui命名空间中的 Widget 类,而该类中又有我们通过 Widget.ui 文件中拖动生成的各类控件;故我们可以通过 ui 指针来操作界面中拖动创建的控件;

相关推荐
西岸行者6 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意6 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码6 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J6 天前
从“Hello World“ 开始 C++
c语言·c++·学习