【Qt】信号和槽机制
3、信号和槽机制
信号:各种事件
槽: 响应信号的动作
==当某个事件发生后==,如某个按钮被点击了一下,它就会发出一个被点击的信号(signal)。
某个对象接收到这个信号之后,就会做一些相关的处理动作(称为槽slot)。
但是Qt对象不会无故收到某个信号,要想让一个对象收到另一个对象发出的信号,这时候需要建立连接(connect)
3.1 系统自带的信号和槽
下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?
其实两行代码就可以搞定了,我们看下面的代码
QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式
connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数:
connect(sender, signal, receiver, slot);
参数解释:
- sender:信号发送者
- signal:信号
- receiver:信号接收者
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
这里要注意的是==connect的四个参数都是指针,信号和槽是函数指针,使用connect的时候保留&符号。==
系统自带的信号和槽如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个
这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot。
3.2 自定义信号和槽
Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,比如说点击某个按钮让另一个按钮的文字改变,这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。
3.2.1 自定义信号使用条件
函数声明在类头文件的signals域下
没有返回值,void类型的函数
只有函数声明,没有实现定义
可以有参数,可以重载
通过emit关键字来触发信号,形式:emit object->sig(参数);
3.2.2 自定义槽函数使用条件
qt4 必须声明在类头文件的 private/public/protected slots域下面,qt5之后可以声明再类的任何位置,同时还可以是静态的成员函数,全局函数,lambda表达式
没有返回值,void类型的函数
不仅有声明,还得要有实现
可以有参数,也可以重载
3.2.3 使用自定义信号和槽
【定义场景】:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)
首先定义一个学生类Student和老师类Teacher:
Teacher *pTeacher ;
Student *pStudent;
老师类中声明信号 饿了 hungry
signals:
void hungry();
学生类中声明槽 请客treat
public slots:
void treat();
在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客
void Widget::classIsOver()
{
//触发老师饿了的信号
emit pTeacher->hungry();
}
学生响应了槽函数,并且打印信息
//自定义槽函数 实现
#include<QDebug>
void Student::treat()
{
qDebug() << "Student treat teacher";
}
在窗口中连接信号槽
pTeacher = new Teacher(this);
pStudent = new Student(this);
//建立连接
connect (pTeacher,&Teacher::hungry,pStudent,&Student::treat);
this->classIsOver();
带参数的自定义信号和槽,就声明函数的时候就带上参数就行
老师说他饿了,说要吃海底捞,学生就请吃海底捞
void hungry(QString name); 自定义信号
void treat(QString name ); 自定义槽
void Student::treat(QString name)
{
qDebug()<<"Student treat teacher with "<<name;
}
但是由于有两个重名的自定义信号和自定name义的槽,直接连接会报错,所以需要解决重载问题
//因为函数发生了重载,所以解决
/*
* 1 使用函数指针赋值,让编译器挑选符合类型的函数
* 2 使用static_cast 强制转换,也是让编译器自动挑选符合类型的函数
*/
重载有参函数,使用函数指针赋值:
void(Teacher::*teacher_qstring)(QString) = &Teacher::hungry;
void(Student::*student_qstring)(QString) = &Student::treat;
connect(pTeacher,teacher_qstring,pStudent,student_qstring);
this->classIsOver();
使用强制类型转换解决重载问题:
//也可以使用static_cast静态转换挑选我们要的函数
connect(
pTeacher,
static_cast<void(Teacher:: *)(QString)>(&Teacher:: hungry),
pStudent,
static_cast<void(Student:: *)(QString)>(& Student::treat));
this->classIsOver();
}
使用强制类型处理无参函数
//使用static_cast来转换无参的函数
connect(pTeacher,
static_cast<void(Teacher::*)()>(&Teacher::hungry),
pStudent,
static_cast<void(Student::*)()>(&Student::treat));
this->classIsOver();
调用带参数的信号函数 emit pTeacher->hungry(“海底捞”);
3.3 信号和槽的拓展
- 一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是槽函数调用顺序是不确定的。像上面的例子,可以将一个按钮点击信号连接到关闭窗口的槽函数,同时也连接到学生请吃饭的槽函数,点击按钮的时候可以看到关闭窗口的同时也学生请吃饭的log也打印出来。
- 多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。如:一个窗口多个按钮都可以关闭这个窗口。
- 一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用connect函数,只是信号的接收者和槽函数换成另一个信号的发送者和信号函数。如上面老师饿了的例子,可以新建一个按钮btn。
#include<QPushButton>
pTeacher = new Teacher(this);
pStudent = new Student(this);
connect(pTeacher,
static_cast<void(Teacher::*)()>(&Teacher::hungry),
pStudent,
static_cast<void(Student::*)()>(&Student::treat));
this->classIsOver();
QPushButton *btn = new QPushButton("按钮1",this);
connect(btn,
&QPushButton::clicked,
pTeacher,
static_cast<void(Teacher::*)()>(&Teacher::hungry));
- 信号和槽可以断开连接
可以使用disconnect函数,当初建立连接时connect参数怎么填的,disconnect里边4个参数也就怎么填。这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
connect(pTeacher,teacher_qstring,pStudent,student_qstring);//建立连接
disconnect(pTeacher,teacher_qstring,pStudent,student_qstring);//断开连接
- 信号和槽的关系信号和槽函数参数类型和个数必须同时满足两个条件
1) 信号函数的参数个数必须大于等于槽函数的参数个数
2) 信号函数的参数类型和槽函数的参数类型必须一一对应
hungry(QString) -> treat() ok
hungry(QString) -> treat(int) 编译出错
hungry(QString,int) -> treat(int) 编译出错
hungry(int,String) -> treat(int) ok
3.4 Qt4版本的信号槽写法
connect使用不一样,信号和槽函数声明差不多
connect(信号发送者,SIGNAL(函数原型) ,信号接收者,SLOT(函数原型))
//Qt4 的 信号和槽 ,实现teacher和student带参数的信号和槽连接
connect(pTeacher,SIGNAL(hungry(QString)),pStudent,SLOT(treat(QString)));
这里使用了SIGNAL和SLOT这两个宏,==宏的参数是信号函数和槽函数的函数原型。==
因为直接填入了函数原型,所有这里边编译不会出现因为重载导致的函数指针二义性的问题。但问题是如果函数原型填错了,或者不符合信号槽传参个数类型约定,编译期间也不会报错,只有运行期间才会看到错误log输出。
原因就是这两个宏将后边参数(函数原型)转化成了字符串。目前编译器还没有那么智能去判断字符串里边的内容符不符合运行条件。
SIGNAL和SLOT宏的原理,就是将后边的参数转成字符串 类似 #define toStr(arg) #arg -> “arg”
推荐:以后都用qt5的
3.5 QDebug
qdeubg输出QString默认会转义
//解决方法两个
//1 将QString转成 char *
//qDebug()<<"Student treat teacher with "<<what.toUtf8().data();
//2 使用qDebug().noquote()
qDebug().noquote()<<"Student treat teacher with "<<what;
3.5 Lambda表达式
C++11中的Lambda表达式用于定义匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
分为四个部分:[局部变量捕获列表]、(函数参数)、函数额外属性设置opt、函数返回值->retype、{函数主体}
[capture](parameters) opt ->retType
{
……;
}
//使用lambda的方式,使用函数指针
void(*p)()=
[]()
{
qDebug()<<"Hello Lambda";
};
p();
//简写
[]()
{
qDebug()<<"Hello Lambda";
}();
3.5.1 局部变量引入方式
[ ],标识一个Lambda的开始。由于lambda表达式可以定义在某一个函数体(A)里边,所以lambda表达式有可能会去访问这个(A)函数中的局部变量。中括号里边内容是描述了在lambda表达式里边可以使用的外部局部变量的列表:
[] 表示lambda表达式不能访问外部函数体的任何局部变量
[a] 在函数体内部使用值传递的方式访问a变量
//局部变量捕获
int a = 10;
int b = 20;
error,a没有被捕获,我们需要传个参数过去,但是目的不是自己用的,是给别人用的,所以这里用捕获
[a,b]()
{
qDebug()<<"Hello Lambda";
qDebug()<<a <<b;
}();
捕获局部变量分两个方式:一种值传递,一直引用传递
[=] 函数外的所有局部变量都通过值传递的方式使用, 函数体内使用的是副本
[&] 引用的方式使用lambda表达式外部的所有变量
[&a,&b]()
{
qDebug()<<"Hello Lambda";
qDebug()<<"修改前"<<a <<b;
a = 100;
b = 200;
}();
qDebug()<<"修改后:"<<a <<b;
[=, &a] a使用引用方式, 其余是值传递的方式
[&,a] a使用值传递方式, 其余是引用传递的方式
[this] 在函数内部可以使用类的成员函数和成员变量,=和&形式也都会默认引入
由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。
所以在无特殊情况下建议使用[=](){}
的形式
3.5.2 函数参数
(params)表示lambda函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引用(如:(int &a,int &b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。
3.5.3 选项Opt
Opt 部分是可选项,最常用的是mutable声明,这部分可以省略。外部函数局部变量通过值传递引进来时,其默认是const,所以不能修改这个局部变量的拷贝,加上mutable就可以
int a = 10 ;
[=]()
{
a=20;//编译报错,a引进来是const
}
[=]()mutable
{
a=20;//编译成功
};
3.5.4 槽函数使用Lambda表达式
以QPushButton点击事件为例:
QPushButton *btn = new QPushButton("按钮1",this);
int a = 10;
int b = 20;
connect(btn,&QPushButton::clicked,[=]()
{
qDebug()<<a <<b;
}
);
这里可以看出使用Lambda表达式作为槽的时候不需要填入信号的接收者。当点击按钮的时候,clicked信号被触发,lambda表达式也会直接运行。当然lambda表达式还可以指定函数参数,这样也就能够接收到信号函数传递过来的参数了。
//可以在函数体执行下面这些函数功能
this->close();//关闭窗口
this->resize(500,600);//设置窗口固定大小
由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多,所以在实现槽函数的时候优先考虑使用Lambda表达式。一般我们的使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来,也就是:
[=](){ }
的形式
3.5.5 函数返回值 ->retType
->retType,标识lambda函数返回值的类型。这部分可以省略,但是省略了并不代表函数没有返回值,编译器会自动根据函数体内的return语句判断返回值类型,但是如果有多条return语句,而且返回的类型都不一样,编译会报错,如:
[=]()mutable
{
int b = 20;
float c = 30.0;
if(a>0)
return b;
else
return c;//编译报错,两条return语句返回类型不一致
};
4、复习
- 点赞
- 收藏
- 关注作者
评论(0)