QT--信号槽与Lambda表达式
信号和槽机制
信号槽是 Qt 框架引以为豪的机制之⼀。所谓信号槽,实际就是观察者模式。当某个事件发⽣之后,⽐如,按钮检测到⾃⼰被点击了⼀下,它就会发出⼀个信号(signal)。这种发出是没有⽬的的,类似⼴播。如果有对象对这个信号感兴趣,它就会使⽤连接(connect)函数,意思是,将想要处理的信号和⾃⼰的⼀个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会⾃动被回调。这就类似观察者模式:当发⽣了感兴趣的事件,某⼀个操作就会被⾃动触发。(这⾥提⼀句,Qt 的信号槽使⽤了额外的处理来实现,并不是 GoF 经典的观察者模式的实现⽅式。)
信号和槽
为了体验⼀下信号槽的使⽤,我们以⼀段简单的代码说明:
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton button("Quit");
QObject::connect(&button, &QPushButton::clicked,&app, &QApplication::quit);
button.show();
();
}
我们按照前⾯⽂章中介绍的在 Qt Creator 中创建⼯程的⽅法创建好⼯程,然后将main()函数修改为上⾯的代码。点击运⾏,我们会看到⼀个按钮,上⾯有“Quit”字样。点击按钮,程序退出。
connect()函数最常⽤的⼀般形式:
connect(sender, signal,receiver, slot);
参数:
sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调⽤的函数
信号槽要求信号和槽的参数⼀致,所谓⼀致,是参数类型⼀致。如果不⼀致,允许的情况是,槽函数的参数可以⽐信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前⾯⼏个⼀致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数⽐信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使⽤(就是槽函数的参数⽐信号的多,这是不允许的)。
如果信号槽不符合,或者根本不到这个信号或者槽函数,⽐如我们改成:
connect(&button, &QPushButton::clicked, &QApplication::quit2);
lambda编程由于 QApplication 没有 quit2 这样的函数,因此在编译时会有编译错误:
'quit2' is not a member of QApplication
这样,使⽤成员函数指针我们就不会担⼼在编写信号槽的时候出现函数错误。
1. Qt4 的书写⽅式:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton *button = new QPushButton("Quit");
connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
button->show();
();
}
这⾥使⽤了SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。注意到connect()函数的 signal
和 slot 都是接受字符串,⼀旦出现连接不成功的情况,Qt4是没有编译错误的(因为⼀切都是字符串,编译期是不检查字符串是否匹配),⽽是在运⾏时给出错误。这⽆疑会增加程序的不稳定性。
Qt5在语法上完全兼容Qt4
⾃定义信号槽
使⽤connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使⽤系统提供的那部分,还会允许我们⾃⼰设计⾃⼰的信号和槽。
下⾯我们看看使⽤ Qt 的信号槽,实现⼀个报纸和订阅者的例⼦:
有⼀个报纸类Newspaper,有⼀个订阅者类Subscriber。Subscriber可以订阅Newspaper。这样,当Newspaper有了新的内容的时
候,Subscriber可以⽴即得到通知。
#include <QObject>
// newspaper.h //
class Newspaper : public QObject
{
Q_OBJECT
public:
Newspaper(const QString & name) :
m_name(name)
{
}
void send()
{
emit newPaper(m_name);
}
signals:
void newPaper(const QString &name);
private:
QString m_name;
};
// reader.h //
#include <QObject>
#include <QDebug>
class Reader : public QObject
{
Q_OBJECT
public:
Reader() {}
void receiveNewspaper(const QString & name)
{
qDebug() << "Receives Newspaper: " << name;
}
};
// main.cpp //
#include <QCoreApplication>
#include "newspaper.h"
#include "reader.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Newspaper newspaper("Newspaper A");
Reader reader;
QObject::connect(&newspaper,&Newspaper::newPaper, &reader,&Reader::receiveNewspaper);
newspaper.send();
();
}
1. ⾸先看Newspaper这个类。这个类继承了QObject类。只有继承了QObject类的类,才具有信号槽的能⼒。所以,为了使⽤信号槽,
必须继承QObject。凡是QObject类(不管是直接⼦类还是间接⼦类),都应该在第⼀⾏代码写上Q_OBJECT。不管是不是使⽤信号槽,都应该添加这个宏。这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能⼒。2. Newspaper类的 public 和 private 代码块都⽐较简单,只不过它新加了⼀个 signals。signals 块所列出的,就是该类的信号。信号
就是⼀个个的函数名,返回值是 void(因为⽆法获得信号的返回值,所以也就⽆需返回任何值),参数是该类需要让外界知道的数据。信号作为函数名,不需要在 cpp 函数中添加任何实现。
3. Newspaper类的send()函数⽐较简单,只有⼀个语句emit newPaper(m_name);。emit 是 Qt 对 C++ 的扩展,是⼀个关键字(其
实也是⼀个宏)。emit 的含义是发出,也就是发出newPaper()信号。感兴趣的接收者会关注这个信号,可能还需要知道是哪份报纸发出的信号?所以,我们将实际的报纸名字m_name当做参数传给这个信号。当接收者连接这个信号时,就可以通过槽函数获得实际值。这样就完成了数据从发出者到接收者的⼀个转移。
4. Reader类更简单。因为这个类需要接受信号,所以我们将其继承了QObject,并且添加了Q_OBJECT宏。后⾯则是默认构造函数和
⼀个普通的成员函数。Qt 5 中,任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。与信号函数不同,槽函数必须⾃⼰完成实现代码。槽函数就是普通的成员函数,因此作为成员函数,也会受到 public、private 等访问控制符的影响。
(如果信号是 private 的,这个信号就不能在类的外⾯连接,也就没有任何意义。)
⾃定义信号槽需要注意的事项
1. 发送者和接收者都需要是QObject的⼦类(当然,槽函数是全局函数、Lambda 表达式等⽆需接收者的时候除外);
2. 使⽤ signals 标记信号函数,信号是⼀个函数声明,返回 void,不需要实现函数代码;
3. 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
4. 使⽤ emit 在恰当的位置发送信号;
5. 使⽤QObject::connect()函数连接信号和槽。
6. 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
信号槽的更多⽤法
1. ⼀个信号可以和多个槽相连
如果是这种情况,这些槽会⼀个接⼀个的被调⽤,但是它们的调⽤顺序是不确定的。
1. 多个信号可以连接到⼀个槽
只要任意⼀个信号发出,这个槽就会被调⽤。
1. ⼀个信号可以连接到另外的⼀个信号
当第⼀个信号发出时,第⼆个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
1. 槽可以被取消链接
这种情况并不经常出现,因为当⼀个对象delete之后,Qt⾃动取消所有连接到这个对象上⾯的槽。
1. 使⽤Lambda 表达式
在使⽤ Qt 5 的时候,能够⽀持 Qt 5 的编译器都是⽀持 Lambda 表达式的。
我们的代码可以写成下⾯这样:
QObject::connect(&newspaper, static_cast<void (Newspaper:: *)
(const QString &)>(&Newspaper::newPaper),
[=](const QString &name)
{
/* Your code here. */
}
);
在连接信号和槽的时候,槽函数可以使⽤Lambda表达式的⽅式进⾏处理
Lambda表达式
C++11中的Lambda表达式⽤于定义并创建匿名的函数对象,以简化编程⼯作。⾸先看⼀下Lambda表达式的基本构成:
[函数对象参数](操作符重载函数参数)mutable或exception ->返回值{函数体}
① 函数对象参数;
[],标识⼀个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的地⽅(此时编译器可以⾃动推断出返回值类型)时,这部分可以省略。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论