1:信号与槽概述

信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时 Qt 对应的窗口类会发出某个信号,以此对用户的操作做出反应.因此,信号的本质就是事件.例如
那么在 Qt 中信号是通过什么形式呈现给使⽤者的呢?
在 Qt 中信号的发出者是某个实例化的类对象。
槽就是对信号响应的函数.槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何位置,可以具有任何参数,可以被重载,也可以直接被调用(但是不能有默认参数).槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行.
信号函数的定义是Qt自动在编译程序之前生成的. 编写 Qt 应⽤程序的程序猿⽆需关注.
这种⾃动⽣成代码的机制称为 元编程 (Meta Programming) . 这种操作在很多场景中都能⻅
到.
在Qt中,QObject类提供了一个静态成员函数connect,该函数专门用来关联指定的信号函数与槽函数.
QObject 是 Qt 内置的父类. Qt 中提供的很多类都是直接或者间接继承⾃ QObject.
connect()函数原型:
connect (const QObject *sender,
const char * signal ,
const QObject * receiver ,
const char * method ,
Qt::ConnectionType type = Qt::AutoConnection )#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "QPushButton"
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
void handleClick();
~Widget();
private:
Ui::Widget *ui;
QPushButton * myButton;
};
#endif // WIDGET_H#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
myButton = new QPushButton(this);
myButton->setText("hello world");
//第一个参数为该信号由哪个控件发出来的,第二个参数表示信号的类型,第三个参数表示信号的处理对象,第四个参数表示对象的处理方式
connect(myButton,&QPushButton::clicked,this,&Widget::handleClick);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleClick()
{
if(myButton->text() == QString("hello world"))
{
myButton->setText("Hello Qt");
}
else if(myButton->text() == QString("hello Qt"))
{
myButton->setText("hello World");
}
}#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "QPushButton"
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
void handleClick();
~Widget();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#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()
{
if(ui->pushButton->text() == QString("Hello world"))
{
ui->pushButton->setText("Hello QT");
}
else
{
ui->pushButton->setText("Hello world");
}
}#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

有的uu可能会有以下两个问题
核心只有一个:多看官方文档.

我们通过观察QPushButton,发现里头并没有clicked信号,此时莫急,当没有找到时,我们就继续去父类去查找.因此我们去他的⽗类 QAbstractButton 中继续查找关键字signals.

Qt Creator 可以快速帮助我们⽣成信号槽相关的代码
注意:创建时要生成ui文件.




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

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

⾃动⽣成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中: 1、以 " on " 开头,中间使⽤下划线连接起来; 2、" XXX " 表⽰的是对象名(控件的 objectName 属性)。 3、" SSS " 表⽰的是对应的信号。 如:" on_pushButton_clicked() " ,pushButton 代表的是对象名,clicked 是对应的信号。


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


此处的slots是Qt中自己扩展的关键字(不是C++标准中的语法)


#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();
public slots:
void handleClicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
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,&Widget::handleClicked);
}
void Widget::handleClicked()
{
this->setWindowTitle("按钮已经被按下");
}
Widget::~Widget()
{
delete ui;
}#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}




当点击确定后,Qt Creator直接给我们生成好了一个函数.只需要在其内部编写代码即可


#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();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
this->setWindowTitle("Hello Qt");
}#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}Qt中也允许自定义信号,但是其实比较少见,实际开发中很少会需要自定义信号,因为
使用"emit" 关键字发送信号 。"emit" 是⼀个空的宏。"emit" 其实是可选的,没有什么含义,只是为了提醒开发⼈员。



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



#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();
signals:
//信号函数声明
void MySignal();
//信号函数的重载
void MySignal(const QString &);
public slots:
//槽函数的声明
void MySlot();
//槽函数的重载
void MySlot(const QString &);
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
void (Widget::*Psignal)(const QString &)= &Widget::MySignal;
void (Widget::*Pslot)(const QString &)= &Widget::MySlot;
//链接信号与槽
connect(this,Psignal,this,Pslot);
}
Widget::~Widget()
{
delete ui;
}
void Widget::MySlot()
{
qDebug()<<"调用Myslot()";
}
void Widget::MySlot(const QString &)
{
qDebug()<<"调用Myslot(const QString &)";
}
void Widget::on_pushButton_clicked()
{
emit MySignal("hello world");
}#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}



#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();
signals:
void MySignal(const QString & text);
public slots:
void Myslots(const QString & text);
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#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::Myslots);
}
Widget::~Widget()
{
delete ui;
}
void Widget::Myslots(const QString &text)
{
this->setWindowTitle(text);
}
void Widget::on_pushButton_clicked()
{
emit MySignal("这是标题1");
}
void Widget::on_pushButton_2_clicked()
{
emit MySignal("这是标题2");
}#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

那么我们思考下,应该是要求信号的参数跟槽函数的参数一致,那么这里为什么还会允许信号的参数比槽函数的参数个数多呢
主要是分别有两种形式,分别是
#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();
signals:
void Mysignal();
public:
void HandleMysignal();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
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()
{
qDebug()<<"接收到了信号";
}
void Widget::on_pushButton_clicked()
{
//发送信号
emit Mysignal();
}
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

信号与信号的链接,只需要在上面代码的基础上添加下面的代码即可~



一个信号连接多个槽

#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 EmitSignal();
signals:
void Mysignal();
public slots:
void Myslots1();
void Myslots2();
void Myslots3();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton * Btn = new QPushButton("按钮",this);
//链接信号与槽
connect(this,&Widget::Mysignal,this,&Widget::Myslots1);
connect(this,&Widget::Mysignal,this,&Widget::Myslots2);
connect(this,&Widget::Mysignal,this,&Widget::Myslots3);
//进行链接
connect(Btn,&QPushButton::clicked,this,&Widget::Mysignal);
}
Widget::~Widget()
{
delete ui;
}
void Widget::Myslots1()
{
qDebug()<<"第一个信号";
}
void Widget::Myslots2()
{
qDebug()<<"第二个信号";
}
void Widget::Myslots3()
{
qDebug()<<"第三个信号";
}
void Widget::EmitSignal()
{
emit Mysignal();
}
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

#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();
signals:
void Mysignal1();
void Mysignal2();
public:
void Myslot();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(this,&Widget::Mysignal1,this,&Widget::Myslot);
connect(this,&Widget::Mysignal2,this,&Widget::Myslot);
emit Mysignal1();
emit Mysignal2();
}
Widget::~Widget()
{
delete ui;
}
void Widget::Myslot()
{
qDebug()<<"Myslot";
}
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
信号与槽函数之间是可以通过disconnect函数来断开信号与槽的连接. PS:disconnect函数用的还是比较少的,大部分情况下,把信号与槽函数连接上了以后,就不必再管了,主动断开往往是把信号重新绑定到另一个槽函数上.
#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();
public:
void Myslots();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton * Btn = new QPushButton("按钮",this);
Btn->move(200,200);
connect(Btn,&QPushButton::clicked,this,&Widget::Myslots);
disconnect(Btn,&QPushButton::clicked,this,&Widget::Myslots);
}
Widget::~Widget()
{
delete ui;
}
void Widget::Myslots()
{
this->setWindowTitle("Hello world");
}#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
断开信号前,我们点击按钮,此时窗口的标题会显示hello world.

当断开信号后,此时我们无论如何点击按钮,窗口的标题都不会发生改变.
如果我们想方便地编写槽函数,比如在编写函数时连函数名都不想定义,则可以通过Lambda表达式来达到这个目的. Lambda表达式的本质是一个"匿名函数",主要应用在"回调函数"的场景中.
[capture] (params) opt->ret
{
Function body;
};capture | 捕获列表 |
|---|---|
params | 参数表 |
opt | 函数选项 |
ret | 返回值类型 |
Function body | 函数体 |
符号 | 说明 |
|---|---|
[] | 局部变量捕获列表.Lambda表达式不能访问外部函数体的任何局部变量 |
[a] | 在函数体内部使用值传递的方式访问变量a |
[&a] | 在函数体内部使用引用传递的方式访问变量a |
[=] | 函数外的所有局部变量都通过值传递的方式使用,函数体内使用的是副本 |
[&] | 以引用的方式使用Lambda表达式外部的所有变量. |
[=,&foo] | foo使用引用方式,其余则是值传递的方式. |
[&,foo] | foo使用值传递方式,其余使用引用传递 |
[this] | 在函数内部可以使用类的成员函数与成员变量,=和&形式也都会默认引入. |

#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();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton * Btn = new QPushButton("按钮",this);
[=]()
{
Btn->setText("测试Lambuda表达式");
}();
}
Widget::~Widget()
{
delete ui;
}
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#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();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton * Btn1 = new QPushButton("按钮1",this);
QPushButton * Btn2 = new QPushButton("按钮2",this);
Btn2->move(200,200);
[=]()
{
Btn1->setText("测试按钮1");
Btn2->setText("测试按钮2");
}();
}
Widget::~Widget()
{
delete ui;
}
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#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();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton * Btn1 = new QPushButton("按钮1",this);
QPushButton * Btn2 = new QPushButton("按钮2",this);
Btn2->move(200,200);
[Btn1,Btn2]()
{
Btn1->setText("测试按钮1");
Btn2->setText("测试按钮2");
}();
}
Widget::~Widget()
{
delete ui;
}
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
(params) 表示Lambda函数对象接收的参数,类似于函数定义中的⼩括号表⽰函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引⽤(如:(int &a,int &b))两种⽅式进⾏传递。函数参数部分可以省略,省略后相当于⽆参的函数。

Opt 部分是可选项,最常⽤的是 mutable声明 ,这部分可以省略。 Lambda表达式外部的局部变量通过值传递进来时,其默认是 const,所以不能修改这个局部变量的拷贝,加上mutable 就可以修改.
#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();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H#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 * Btn1 = new QPushButton("Btn1",this);
QPushButton * Btn2 = new QPushButton("Btn2",this);
int m = 10;
Btn1->move(300,100);
Btn2->move(500,100);
connect(Btn1,&QPushButton::clicked,this,[m]()mutable{m = 20;qDebug()<< m;});
connect(Btn2,&QPushButton::clicked,this,[=](){qDebug()<< m;});
}
Widget::~Widget()
{
delete ui;
}
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
可以指定 Lambda表达式 返回值类型;如果不指定返回值类型,则编译器会根据代码实现为函数推导⼀个返回类型;如果没有返回值,则可忽略此部分.


Lambda表达式的函数体部分与普通函数体⼀致。⽤ { } 标识函数的实现,不能省略,但函数体可以为空.

那么到这里,Qt中的信号与槽的核心内容博主就讲完啦,接下来我们来小结一下
1:首先学习了信号槽是啥.
2:然后再是信号槽的使用------>connect函数. 3:如何查阅文档
4:自定义槽函数
5:自定义信号
6:带参数的信号与槽
7:信号与槽的存在意义
8:disconnect函数的使用方式. 9:Lambda表达式,简化槽函数的定义.
这里博主给uu们详细讲解下什么是耦合与内聚
平常我们写代码时,要追求"高内聚,低耦合",这里博主通过一个故事来讲解