Qt的信号和槽机制
信号和槽机制是qt的优点之一 ,信号(signal)是某个对象发生某个事件之后向外广播出的一个信息,而槽( slot )是对此信号感兴趣的对象在这个信号产生时做出的动作,也就是调用函数完成某种操作。如果某个对象对此信号感兴趣,就需要使用connect函数将该信号与对应的槽函数绑定。比如,某个按钮被点击,向外广播出了按钮被点击的信号,而按钮点击的动作对应绑定槽函数的执行。
Qt5 的书写方式
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton buttonQuit( "点击按钮" );
QObject::connect( &buttonQuit , &QPushButton::clicked,
&app , &QApplication::quit);
buttonQuit.show();
return app.exec();
}
- 创建好一个Qt工程 , 并在主函数文件中更改代码 ;
- 运行程序,可在界面看到一个按钮,并且按钮的显示文本为*“点击按钮"*,单击按钮,窗口关闭。其中我们单击按钮的时候按钮对象向外广播出了一个click信号,这个信号被我们之前所调用的QObject类种的connect函数与QApplication对象中的quit槽函数绑定。故执行了quit函数,程序退出。
- 槽函数的格式
connect(sender, signal, receiver, slot); - 参数的含义:
sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数(槽函数) - 信号与槽函数的传参问题
槽函数的形参必须与信号函数的前半部分一致,即槽函数对应的参数可以在信号函数对应的参数位置找到。允许信号参数的个数多于槽函数信号的个数,但必须满足这个规则。 - Qt4与Qt5中connect函数使用方式对比
Qt4
connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
Qt5
connect(&button, &QPushButton::clicked, &QApplication::quit2);
对比
首先,Qt5兼容Qt4的写法。
其次,Qt4SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串,故在编译的时候并不会检查信号与槽是否对应,即我们无法在编译的时候知道信号与槽是否绑定成功,如果出错,只会在程序运行的时候出现。Qt5的写法在编译的时候就进行语法检查,防止出错,可以迅速的定位程序中的错误。
结论:推荐Qt5的写法。 - 自定义信号和槽
Qt中除了系统定义的信号函数和槽函数之外,我们也可以自己设计信号和槽。
我们以狗狗铲屎官类和哈士奇类为例子来说明自定义信号和槽机制。当狗狗铲屎官给哈士奇扔一个鸡腿过去之后,哈士奇接到鸡腿,即可美餐一顿。代码如下:
//--doghashiqi.h--
#include <QObject>
#include <QDebug>
class DogHashiqi : public QObject
{
Q_OBJECT
public:
DogHashiqi(const QString & name) :
m_name(name)
{
}
void eat( QString food )
{
qDebug() << m_name << "enjoy" << food << "nice" ;
}
private:
QString m_name;
};
//------manchanshiguan.h---------
#include <QObject>
#include <QDebug>
class ManChanshiguan : public QObject
{
Q_OBJECT
public:
ManChanshiguan( QString name ):m_name(name)
{
}
void feedDog( QString food )
{
qDebug() << m_name << "feed dog" ;
emit timeToEat( food ) ;
}
signals:
void timeToEat( QString food ) ;
};
//------main.cpp------
#include <QCoreApplication>
#include "manchanshiguan.h"
#include "doghashiqi.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
ManChanshiguan manChanshiguan( "Leng Wa" );
DogHashiqi dogHashiqi( "Wang Zai" );
QObject::connect( &manChanshiguan , &ManChanshiguan::timeToEat ,
&dogHashiqi , &DogHashiqi::eat );
manChanshiguan.feedDog( "Chicken" );
return app.exec();
}
- ManChanshiguan 和 DogHashiqi 类中都继承于QObject类,这是信号和槽机制实现的基础;
- QObject类(基类或者其派生类),都应在类定义的第一行写上Q_OBJECT,这个宏为我们的类提供信号和槽机制、国际化机制、QT提供的不基于C++ RTTI的反射能力;
- DogHashiqi类中有一个eat函数和构造函数,其中构造函数用于给哈士奇初始化名字,eat即为槽函数,哈士奇可以用这个函数来接受铲屎官扔出的鸡腿信号并享受鸡腿;
- ManChanshiguan类种有feedDog函数和信号函数声明timeToEat,feedDog是铲屎官喂狗的动作,而timeToEat即告诉哈士奇有鸡腿了。通过timeToEat函数中的参数告诉哈士奇我扔给你的是什么食物(信号与槽函数的参数传递),emit 是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)。用于告诉哈士奇该吃饭了(发出timeToEat信号);
- 主函数中我们定义一个铲屎官 “Leng Wa” ,哈士奇 "Wang Cai " , “LengWa” 调用喂狗函数扔出一个鸡腿,哈士奇"Wang Cai"即可吃掉鸡腿;
- 要点回顾
发送者和接收者必须是QObject的子类(槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
使用 signals 标记信号函数,信号是一个函数声明,返回 void;
槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的限定;
使用 emit 发送信号;
使用QObject::connect()函数连接信号和槽;
任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。 - 扩展
一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。
一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
槽可以被取消链接
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
使用Lambda 表达式
在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
我们的代码可以写成下面这样:
connect( senderAddr , signal ,
[=]()
{
//完成某种动作
} ) ;
-
Lambda表达式
-
函数对象参数;
[ ],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
空。没有使用任何函数对象参数;
=, 函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量);
&, 函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量);
this , 函数体内可以使用Lambda所在类中的成员变量;
a, 将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符;
&a , 将a按引用进行传递;
a, &b, 将a按值进行传递,b按引用进行传递;
=,&a, &b , 除a和b按引用进行传递外,其他参数都按值进行传递;
&, a, b , 除a和b按值进行传递外,其他参数都按引用进行传递。
int m = 0, n = 0;
[=] (int a) mutable { m = ++n + a; }(4);
[&] (int a) { m = ++n + a; }(4);[=,&m] (int a) mutable { m = ++n + a; }(4); [&,m] (int a) mutable { m = ++n + a; }(4); [m,n] (int a) mutable { m = ++n + a; }(4); [&m,&n] (int a) { m = ++n + a; }(4);
-
操作符重载函数参数;
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。 -
可修改标示符;
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身); -
错误抛出标示符;
exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int) -
函数返回值;
返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略; -
函数体;
{},标识函数的实现,这部分不能省略,但函数体可以为空。