信号和槽
信号和槽,本质都是事件,或者说是某种动作,一个触发一个响应
信号
信号一般写在头文件中,也就是类中,需要关键字signals
限定,就像private
、protected
、public
等
此外信号有几个特点
- 只返回
void
- 只需要声明,不需要实现,即函数体
- 自定义信号可以重载
在teacher.h
举个例子
#ifndef TEACHER_H
#define TEACHER_H
#include <QObject>
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = nullptr);
//自定义信号
signals:
void hungry();
void hungry(QString str);
};
#endif // TEACHER_H
槽
同样的,一般槽函数也是写在类中;在之前的版本中,需要用public slots
进行说明,但后来的Qt则可以将槽函数直接写在public
下,就想当于公有成员函数
槽函数的几个特点
- 只返回
void
- 需要声明以及实现
- 可以
重载
在student.h
中的声明槽函数
#ifndef STUDENT_H
#define STUDENT_H
#include <QObject>
class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QObject *parent = nullptr);
public slots:
void treat();
void treat(QString str);
};
#endif // STUDENT_H
在student.cpp
中实现槽函数
#include "student.h"
#include <QDebug>
Student::Student(QObject *parent) : QObject(parent)
{
}
void Student::treat(){
qDebug() << "请老师吃饭";
}
void Student::treat(QString str){
// 将QString转换为char* 这样输出就不会有双引号
// 先调用.toUtf8() 转换为QByteArray类型 再用.data()转换为char* 类型
qDebug()<<"请老师吃:"<< str.toUtf8().data();
}
connect()
信号和槽通过函数connect()
进行连接,而这个函数还有两个参数,那就是信号和槽对应的对象
这里在widget.h
定义了teacher
和student
的对象
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "teacher.h"
#include "student.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
Teacher * zt;
Student * st;
void classIsOver();
};
#endif // WIDGET_H
下面在widget.cpp
中进行connect
操作
简单分析一下,zt
发出hungry
的信号,st
就响应,进行treat
操作
connect(zt,&Teacher::hungry,st,&Student::treat);
但编译运行后发现,并没有成功输出treat()
中的qDebug() << "请老师吃饭";
其实这里就像把开关接在灯上,连接电源,我们只需要按下按钮,灯就会亮
开关发送被按下的信号,灯响应,进行亮操作
而只有按下事件发生,开关才会被按下吧,灯才会亮
触发信号
void Widget::classIsOver(){
// 自定义信号触发 关键字 emit
//emit zt->hungry();
emit zt->hungry("宫保鸡丁");
}
在widget.cpp
中,完整的代码如下
#include "widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
zt = new Teacher(this);
st = new Student(this);
connect(zt,&Teacher::hungry,st,&Student::treat);
classIsOver();
}
void Widget::classIsOver(){
// 自定义信号触发 关键字 emit
emit zt->hungry();
}
Widget::~Widget()
{
}
disconnect()
和connect()
一样的操作
重载信号和槽
重载的定义就不用说了,这里主要讲重载后如何连接
void hungry(QString str);
void treat(QString str){
qDebug() << "请老师吃" << str;
}
void Widget::classIsOver(){
emit zt->hungry("鸡肉滑蛋饭"); // !!!
}
void (Teacher::*noParaT)(QString) = &Teacher::hungry; // 函数指针 因为信号重载了
void (Student::*noParaS)(QString) = &Student::treat;
connect(zt,noParaT,st,noParaS);
当自定义的信号和槽发生重载后,就必须用函数指针,明确指出函数地址
这里有一点需要注意,信号和槽的参数类型必须一一对应,所以重载就必须同时重载
但信号函数的参数个数可以多于槽函数的参数个数
这里就比如老师要鸡肉滑蛋饭、三份,而学生可以只接收前者
拓展
信号可以连接信号
QPushButton *btn = new QPushButton(this);
btn->setText("classIsOver");
void (Teacher:: *teachersignal2)() = &Teacher::hungry;
void (Student:: *studentslot2)() = &Student::treat;
connect(btn,&QPushButton::clicked,zt,teachersignal2);
connect(zt,teachersignal2,st,studentslot2);
一个信号连接多个槽
classIsOver
后,顺便关闭窗口
connect(btn,&QPushButton::clicked,this,&QWidget::close);
通常来说,一个信号连接上多个槽并没有问题,但有个问题就是,没办法控制槽函数的执行顺序
多个信号可以连接同一个槽
这里定义多个btn
,都表示下课是没问题的
QPushButton *btn2 = new QPushButton(this);
btn2->setText("放学了");
btn2->setGeometry(200,300,80,40);
QPushButton *btn3 = new QPushButton();
btn3->setParent(this);
btn3->setText("誒我就是逃");
btn3->move(300,200);
connect(btn2,&QPushButton::clicked,zt,teachersignal2);
connect(btn3,&QPushButton::clicked,zt,teachersignal2);
上面几个拓展,都是QPushButton
的实例btn
通过clicked
触发信号hungry
,所以也就不需要起初的那个classIsOver()
触发信号的函数了
Qt4的信号和槽
connect(zt,SIGNAL(hungry(QString)),st,SLOT(treat(QString)));
这个之所以被淘汰了,就是因为有个缺点,对参数类型不做检测
编译时可以通过,但运行却出毛病了
原因在于,SIGNAL
、SLOT
括号下的内容,转换为字符串
SIGNAL("hungry(QString)")
SLOT("treat(QString)")
都转换成字符串了,还做啥参数类型对比?
用lambda
书写槽函数
QPushButton *btn5 = new QPushButton(this);
int n=1;
connect(btn5,&QPushButton::clicked,this,
[=]()mutable{btn5->setText("aaaaa");btn5->move(n*100,n*100);++n;} );
lambda
表达式,要修改捕获的参数n
,别忘了加上关键字mutable
mutable
,可修改标识符。按值传递进行捕获参数时,加上mutable
后可以修改值传递进来的拷贝,注意是修改拷贝,而不是修改值本身
在这里,捕获按钮并进行setText
,只能用值传递=
,若是用引用传递&
就会报错,原因是,Qt的按钮会有一种🔒状态,也就是只读状态,而这里还能进行setText
、move
,但其实操作的并不是一开始的那个btn5
,而是拷贝;
而这两个操作并不需要mutable
关键字声明
对mutable
一些补充
int m=10;
QPushButton *btn6 = new QPushButton(this);
btn6->move(100,200);
connect(btn6,&QPushButton::clicked,this,[=]()mutable{ m=m+100;qDebug()<<m;});
QPushButton *btn16 = new QPushButton(this);
btn16->move(200,200);
connect(btn16,&QPushButton::clicked,this,[=](){qDebug()<<m;});
返回
int n=10;
n = [=]()->int{ return 999;} () ;
qDebug() << n;
lambda
需要返回,就用-> return_type
另外,lambda
表达式,就是定义了一个函数,要调用就和普通函数一样,再加一对小括号()
不对等参数类型的connect()
用lambda
表达式,就可以实现不需要信号和槽参数类型必须一一对应
connect(btn,&QPushButton::clicked,this,[=](){
this->treat("鸡肉滑蛋饭");
});
其中信号clicked()
其实是有一个bool
类型的参数的
还有一个骚操作,如果connect()
第三个参数是this
且第四个参数是lambda
表达式,那第三个参数可以省略