信号和槽存在意义
所谓的信号槽,终究要解决的问题,就是响应用户的操作
信号槽,其实在GUI开发的各种框架中,是一个比较有特色的存在
其他的GUI开发框架,搞的方式都要更简洁一些~~ 网页开发 (js + dom api)
网页开发中响应用户操作,主要就是挂回调函数
button.onclick = handle;
function handle(){
...
}
处理函数就像控件的一个属性/成员一样
不需要搞一个单独的connect完成上述的信号槽连接~
一对一.
一个事件,只能对应一个处理函数
一个处理函数也只能对应到一个事件上.
Qt信号槽,connect这个机制,设想很美好的~^
- 解耦合.把触发用户操作的控件和处理对应用户的操作逻辑解耦合.
- "多对多"效果,一个信号,可以connect到多个槽函数上. 一个槽函数,也可以被多个信号connect.
Qt中谈到的信号和槽"多对多”就和数据库中的多对多非常类似的
一个信号,可以connect到多个槽函数上
一个槽函数,也可以被多个信号connect
综上,Qt引I入信号槽机制,最本质的目的(初心)
就是为了能够让信号和槽之间按照“多对多”的方式来进行关联~~ 其他的GUI框架往往也不具备这样的特性~
其实在GUI开发的过程中,“多对多”这件事,其实是个“伪需求
实际开发很少会用到
绝大部分情况,一对一就够用了.
信号与槽的连接⽅式
⼀对⼀
主要有两种形式,分别是:⼀个信号连接⼀个槽和⼀个信号连接⼀个信号
-
⼀个信号连接⼀个槽
-
⼀个信号连接另⼀个信号
⼀对多
⼀个信号连接多个槽
多对⼀
多个信号连接⼀个槽函数
信号与槽的断开
使⽤disconnect即可完成断开.
disconnect的⽤法和connect基本⼀致
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handleClick();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleClick()
{
this->setWindowTitle("修改窗口标题");
}
添加一个修改槽函数的Button
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handleClick();
void handleClick2();
private slots:
void on_pushButton_2_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleClick()
{
this->setWindowTitle("修改窗口标题");
}
void Widget::handleClick2()
{
this->setWindowTitle("修改窗口标题2");
}
void Widget::on_pushButton_2_clicked()
{
//1.先断开pushButton原来的信号槽
disconnect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
//2.重新绑定信号槽
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick2);
}
void Widget::handleClick()
{
this->setWindowTitle("修改窗口标题");
qDebug() << "handleClick";
}
void Widget::handleClick2()
{
this->setWindowTitle("修改窗口标题2");
qDebug() << "handleClick2";
}
如果切换的时候不disconnect,到时候一个信号就会触发两个槽函数,点一下第一个按钮qdebug会同时输出handleClick和handleClick2
使用lambda表达式定义槽函数
Qt5在Qt4的基础上提⾼了信号与槽的灵活性,允许使⽤任意函数作为槽函数。
但如果想⽅便的编写槽函数,⽐如在编写函数时连函数名都不想定义,则可以通过Lambda表达式来达到这个⽬的。
Lambda表达式是C++11增加的特性。C++11中的Lambda表达式⽤于定义并创建匿名的函数对
象,以简化编程⼯作。
Lambda表达式的语法格式如下
[ 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以上的版本⽆需⼿动添加,在新建项⽬时会⾃动添加
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->setText("按钮");
button->move(200, 200);
connect(button, &QPushButton::clicked, this, [](){
qDebug() << "lambda 被执行了!";
});
}
Widget::~Widget()
{
delete ui;
}
lambda表达式,是一个回调函数
这个函数,无法直接获取到上层作用域中的变量的~~
lambda为了解决上述问题,引l入了“变量捕获”语法通过变量捕获,获取到外层作用域中的变量
connect(button, &QPushButton::clicked, this, [button](){
qDebug() << "lambda 被执行了!";
button->move(300, 300);
});
connect(button, &QPushButton::clicked, this, [button, this](){
qDebug() << "lambda 被执行了!";
button->move(300, 300);
this->move(100, 100);
});
写作[=]
这个写法的含义就是把上层作用域中所有的变量名都给捕获进来
connect(button, &QPushButton::clicked, this, [=](){
qDebug() << "lambda 被执行了!";
button->move(300, 300);
this->move(100, 100);
});
后续如果我们对应的槽函数比较简单,而且是一次性使用的.就经常会写作这种lambda的形式
另外也要确认捕获到lambda内部的变量是有意义的
回调函数执行时机是不确定的(用户啥时候点击按钮不知道的)无论何时用户点击了按钮,捕获到的变量都能正确使用
由于此处的button是new出来的变量,生命周期跟随整个窗口(挂到对象树上,窗口关闭才会释放)
这个东西就可以在后面随时使用了
类似的this指向的对象widget
这个变量是在main函数结束销毁 main结束,说明进程结束了,
只要进程不结束,widget就可用.this也就可用了,
lambda除了可以按照值的方式来捕获变量[=]
还可以按照引l用的方式来捕获[&]
(Qt中很少这么写)捕获到的变量一般就是各种控件的指针. 指针变量按照值传递或者引用来传递,都无所谓
如果按引用,还得更关注这个引用的变量本身的生命周期
lambda语法是C++11中引l入的
对于Qt5及其更高版本,默认就是按照C++11来编译的
如果使用Qt4或者更老的版本,就需要手动在.pro文件中加上C++11的编译选项
2、函数参数 ( )
(params)表⽰Lambda函数对象接收的参数,类似于函数定义中的⼩括号表⽰函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引⽤(如:(int &a,int &b))两种⽅式进⾏传递。函数参数部分可以省略,省略后相当于⽆参的函数
3、选项Opt
Opt部分是可选项,最常⽤的是 mutable声明 ,这部分可以省略。
Lambda表达式外部的局部变量通过值传递进来时,其默认是const,所以不能修改这个局部变量的拷⻉,加上mutable就可以修改。
4、Lambda表达式的返回值类型 ->
可以指定Lambda表达式返回值类型;如果不指定返回值类型,则编译器会根据代码实现为函数推导⼀个返回类型;如果没有返回值,则可忽略此部分。
5、Lambda表达式的函数体 { }
Lambda表达式的函数体部分与普通函数体⼀致。⽤ { } 标识函数的实现,不能省略,但函数体可以为空。
信号与槽的优缺点
优点:松散耦合
信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了⾃⼰,Qt的信号槽机制保证了信号与槽函数的调⽤。⽀持信号槽机制的类或者⽗类必须继承于QObject类。
缺点:效率较低
与回调函数相⽐,信号和槽稍微慢⼀些,因为它们提供了更⾼的灵活性,尽管在实际应⽤程序中差别不⼤。通过信号调⽤的槽函数⽐直接调⽤的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是⾮常⾼的场景是可以忽略的,是可以满⾜绝⼤部分场景