信号和槽函数
一、自定义信号和槽
1.1、自定义槽函数
自定义slot和普通的成员函数没有什么区别。
自定义槽函数方式一:
Qt5自定义槽函数的方法
void handleClikecd();
以前的Qt版本,自定义槽函数的方法()
public/private/protected slots:
void handleClikecd();
此处的slots是Qt自己扩展的关键字(不是c++标准中的语法)
自定义槽函数方式二:(快速的方式)
通过ui设计界面里,拖动一个控件到窗口,然后右键弹出对话框,点击“转到槽”,然后选择信号,就会生成自定义槽函数,并且与该槽函数建立连接。(无需用connect进行连接)
运行程序:
实现槽函数自动连接
- 如果我们通过图形化界面的方式创建控件,此时就会推荐快速的方式来连接信号槽。
- 如果我们是通过代码的方式来创建控件,此时就会推荐手动connect来连接信号槽。
2.1、自定义信号
自定义信号,在实际开发中比较少见。
信号就是用户的某个操作,在GUI中,用户能够进行哪些操作,是可以穷举的,Qt的内置信号,基本上覆盖了所有可能的用户操作。
信号是一种非常特殊的函数。
- 程序员只要告诉Qt,这是一个信号就可以了;这个函数的定义,是在Qt的编译过程中,自动生成的。(自动生成的过程,程序员无法干预)
- 作为信号函数,这个函数的返回值,必须为void。(有没有参数都可以,还支持重载)
- signals是Qt自己扩展出来的关键字。
qmake的时候,调用一些代码的分析/生成工具,扫描到类中包含signals这个关键字的时候,就自动的把函数声明认为是信号,并对信号函数自动生成函数的定义。
自定义信号:
- 在.h文件里写入自定义信号,和槽函数。
//信号
signals:
void mySignal();
//槽函数
public :
void handleMySignal();
- 在.cpp文件里的定义。
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(this,&Widget::mySignal,this,&Widget::handleMySignal);
//发送出自定义信号
emit mySignal();
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleMySignal()
{
this->setWindowTitle("信号被触发");
}
- 发送自定义信号,可以在任意合适的代码中。
列如:我们在ui界面创建一个按钮,然后点击按钮,转到槽,选择信号,然后再自定义槽函数中写入发送的自定义信号。(此时,点击按钮,就会发送自定义信号)
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(this,&Widget::mySignal,this,&Widget::handleMySignal);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleMySignal()
{
this->setWindowTitle("信号被触发");
}
void Widget::on_pushButton_clicked()
{
//发送出自定义信号
//发送自定义信号,可以在任意合适的代码中,不一定非在构造函数中。
emit mySignal();
}
补充:
emit 自定义信号;(emit是Qt扩展的关键字,表示发射了自定义信号,connect连接自定义信号和槽,必须发射自定义信号)
二、带参数的信号槽
信号和槽可以带参数,当信号带参数时,槽的参数必须和信号的参数一致。(发射信号的时候,可以给信号函数传递实参,与之对应的这个参数就会被传递给对应的槽函数中)
- 参数一致,主要要求的是类型一致,可以参数个数不一致,前提是信号的参数个数比槽的参数个数要多。
- 传参可以起到复用代码的作用。
例子1:
写一个带参数的信号槽
1.给信号和槽函数设置相同的参数
//信号
signals:
void mySignal(const QString& Text);
//槽函数
public:
void handleMySignal(const QString& Text);
- 发射信号后,传给信号,然后传给槽函数
例子2:
如何通过一套信号槽,起到设置不同标题的效果
在上面代码的基础上,在ui文件里创建一个按钮,然后转到槽,选择合适的信号。
补充: - 通过这一套信号槽,搭配不同的参数,就可以起到设置不同标题的效果。(不止是标题,可以是其他的效果)
- 信号函数的参数个数,超过槽函数的参数个数,是可以正常使用得。( 信号函数的参数个数,少于槽函数的参数个数,代码无法编译通过)
三、信号和槽的其余说明
信号和槽的连接方式:
一对一:一个信号对一个槽函数;一个信号对另一个信号
一对多:一个信号对多个槽函数
多对一:多个信号对一个槽函数
3.1、信号和槽的存在的意义
- 为什么信号函数的参数个数,可以超过槽函数的参数个数?
1、如果我们严格要求参数个数一致,就意味着信号绑定到槽之间的要求变高了。
2、我们让信号和槽之间的绑定更加灵活,就是要信号函数的参数个数,可以超过槽函数的参数个数。
3、个数不一致时,槽函数按照参数顺序,拿到信号的前N个参数,要保证槽函数的参数都是有值的。
让某个类可以定义信号和槽,必须在该类最开始的地方添加Q_OBJECT宏,不加宏,如果定义信号和槽,会编译出差。
2. GUI开发框架响应用户操作
- Qt信号槽,connect这个机制。
1、解耦合:把触发用户操作的控件和处理对应用户的操作逻辑解耦合。
2、实现多对多这个效果:一个信号,可以connect到多个槽函数上,一个槽函数,可以被多个信号connect,其他GUI开发框架不具备得。- 一般网页开发。
一对一:一个事件,只能对应一个处理函数;一个处理函数,只能对应到一个事件上。
3.2、信号和槽的断开连接
1.使用disconnect来断开信号槽连接
disconnect和connect使用的方式非常类似。
disconnect(this,&Widget::mySignal,this,&Widget::handleMySignal);
disconnect用的比较少,大部分情况下,把信号和槽连接上之后,就不要管了,主动断开往往是把信号重新绑定1到另一个槽函数上。
3.3、 用lambda表达式定义槽函数
lambda表达式是一个语法糖,本质是匿名函数,应用在回调函数场景中,适用与代码较短的槽函数。
格式:
[capture](params) opt -> ret {body;};
- capture: 捕获列表
- params: 参数列表
- opt: 函数选项(可省略)
- ret: 返回值类型(可省略)
- body: 函数体
lambda表达式,是一个回调函数,这个函数,无法直接获取到上层作用域中的变量,为了解决这个问题,引入了“变量捕捉”语法,通过变量捕捉,获取到外层作用域中的变量;lambda表达式是一次性使用。
capture : 捕获一定范围内的变量
[] - 不捕捉任何变量
[&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
[=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获):拷贝的副本在匿名函数体内部是只读的
[=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
[bar] - 按值捕获 bar 变量, 同时不捕获其他变量
[&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
[this] - 捕获当前类中的this指针
//通常常用[=]进行捕捉
mutable 或 exception 声明
mutable: 这部分可以省略。按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是
值本身)。
exception: exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。
lambda表达式的基本写法
connect(ui->pushButton,&QPushButton::clicked,[=](参数){
//具体代码
this->close();
});
补充:
lambda 语法是c++ 11 中引入的。
对于Qt5 以及更高的版本,默认就是按照 c++11 来编译的。
如果使用Qt4或者更加古老的版本,就需要手动在 .pro 文件中加上 c++ 11 的编译选项
CONFIG += C++11
四、信号和槽的总结
- 信号槽是啥,和 Linux 中的信号进行了对比,
1)信号源
2)信号的类型
3)信号的处理方式 - 信号槽使用.
connect - 如何查阅文档.
一个控件,内置了哪些信号, 信号都是何时触发,
一个控件,内置了哪些槽,槽都是什么作用.
很有可能需要的信号槽,还得到这个类的父类/爷爷类/祖宗类去进行查询 - 自定义槽函数.
本质上就是自定义一个普通的成员函数
还可以让 Qt Creator(界面的方式去创建) 自动生成(虽然没有显式 connect,但是可以通过函数名字特定规则来完成自动连接) - 自定义信号,
信号本质就是成员函数.(函数的定义是 Qt 自己生成的,咱们只需要写函数声明)
signals: 自定义关键字
emit 来完成信号的发射(emit 也可以省略) - 信号和槽还可以带有参数.
发射信号的时候,把参数传递给对应的槽
信号的参数和槽的参数要一致
1)类型匹配
2)个数,信号的参数要多于槽的参数. - 信号槽存在的意义
解耦合
多对多效果.(非常类似于 mysql 中的多对多的)
演示了信号槽多对多连接的情况 - disconnect 使用方式.
- lambda 表达式, 简化槽函数的定义
- 高内聚(写代码的时候,某个功能点的代码都被集中放在一起);低耦合(功能与功能之间干扰较少)。
- 信号与槽的优缺点
优点: 松散耦合
信号发送者不需要知道发出的信号被哪个对象的槽的数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于 Q0bject类。
缺点: 效率较低
与回调函数相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性,尽管在实际应用程序中差别不大。通过信号调用的槽函数比直接调用的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调用速度对性能要求不是非常高的场景是可以忽略的,是可以满足绝大部分场景。
一个客户端程序中,最慢的环节往往是"人"。
假设本身基于回调的方式是 10us,使用信号槽的方式是 100us.对于使用程序的人来说,是感知不到的。