在前面的章节(信号槽自定义信号槽)中,我们详细介绍了有关 Qt 5 的信号槽新语法。由于这次改动很大,许多以前看起来不是问题的问题接踵而来,因此,我们用单独的一章重新介绍一些 Qt 5 的信号槽新语法。

基本用法

Qt 5 引入了信号槽的新语法:使用函数指针能够获得编译期的类型检查。使用我们在自定义信号槽中设计的Newspaper类,我们来看看其基本语法:

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//!!! Qt5
#include <QObject>
 
////////// newspaper.h
class Newspaper : public QObject
{
    Q_OBJECT
public:
    Newspaper(const QString & name) :
        m_name(name)
    {
    }
 
    void send() const
    {
        emit newPaper(m_name);
    }
 
signals:
    void newPaper(const QString &name) const;
 
private:
    QString m_name;
};
 
////////// reader.h
#include <QObject>
#include <QDebug>
 
class Reader : public QObject
{
    Q_OBJECT
public:
    Reader() {}
 
    void receiveNewspaper(const QString & name) const
    {
        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();
 
    return app.exec();
}

main()函数中,我们使用connect()函数将newspaper对象的newPaper()信号与reader对象的receiveNewspaper()槽函数联系起来。当newspaper发出这个信号时,reader相应的槽函数就会自动被调用。这里我们使用了取址操作符,取到Newspaper::newPaper()信号的地址,同样类似的取到了Reader::receiveNewspaper()函数地址。编译器能够利用这两个地址,在编译期对这个连接操作进行检查,如果有个任何错误(包括对象没有这个信号,或者信号参数不匹配等),编译时就会发现。

有重载的信号

如果信号有重载,比如我们向Newspaper类增加一个新的信号:

 
 
1
void newPaper(const QString &name, const QDate &date);

此时如果还是按照前面的写法,编译器会报出一个错误:由于这个函数(注意,信号实际也是一个普通的函数)有重载,因此不能用一个取址操作符获取其地址。回想一下 Qt 4 中的处理。在 Qt 4 中,我们使用SIGNALSLOT两个宏来连接信号槽。如果有一个带有两个参数的信号,像上面那种,那么,我们就可以使用下面的代码:

 
 
1
2
QObject::connect(&newspaper, SIGNAL(newPaper(QString, QDate)),
                 &reader,    SLOT(receiveNewspaper(QString, QDate)));

注意,我们临时增加了一个receiveNewspaper()函数的重载,以便支持两个参数的信号。在 Qt 4 中不存在我们所说的错误,因为 Qt 4 的信号槽连接是带有参数的。因此,Qt 能够自己判断究竟是哪一个信号对应了哪一个槽。

对此,我们也给出了一个解决方案,使用一个函数指针来指明到底是哪一个信号:

 
 
1
2
3
void (Newspaper:: *newPaperNameDate)(const QString &, const QDate &) = &Newspaper::newPaper;
QObject::connect(&newspaper, newPaperNameDate,
                 &reader,    &Reader::receiveNewspaper);

这样,我们使用了函数指针newspaperNameDate声明一个带有QStringQDate两个参数,返回值是 void 的函数,将该函数作为信号,与Reader::receiveNewspaper()槽连接起来。这样,我们就回避了之前编译器的错误。归根结底,这个错误是因为函数重载,编译器不知道要取哪一个函数的地址,而我们显式指明一个函数就可以了。

如果你觉得这种写法很难看,想像前面一样写成一行,当然也是由解决方法的:

 
 
1
2
3
4
QObject::connect(&newspaper,
                 (void (Newspaper:: *)(const QString &, const QDate &))&Newspaper::newPaper,
                 &reader,
                 &Reader::receiveNewspaper);

这是一种换汤不换药的做法:我们只是声明了一个匿名的函数指针,而之前我们的函数指针是有名字的。不过,我们并不推荐这样写,而是希望以下的写法:

 
 
1
2
3
4
QObject::connect(&newspaper,
                 static_cast<void (Newspaper:: *)(const QString &, const QDate &)>(&Newspaper::newPaper),
                 &reader,
                 &Reader::receiveNewspaper);

对比上面两种写法。第一个使用的是 C 风格的强制类型转换。此时,如果你改变了信号的类型,那么你就会有一个潜在的运行时错误。例如,如果我们把(const QString &, const QDate &)两个参数修改成(const QDate &, const QString &),C 风格的强制类型转换就会失败,并且这个错误只能在运行时发现。而第二种则是 C++ 推荐的风格,当参数类型改变时,编译器会检测到这个错误。

注意,这里我们只是强调了函数参数的问题。如果前面的对象都错了呢?比如,我们写的newspaper对象并不是一个Newspaper,而是Newspaper2?此时,编译器会直接失败,因为connect()函数会去寻找sender->*signal,如果这两个参数不满足,则会直接报错。

带有默认参数的槽函数

Qt 允许信号和槽的参数数目不一致:槽函数的参数数目要比信号的参数少。这是因为,我们信号的参数实际是作为一种返回值。正如普通的函数调用一样,我们可以选择忽略函数返回值,但是不能使用一个并不存在的返回值。如果槽函数的参数数目比信号的多,在槽函数中就使用到这些参数的时候,实际这些参数并不存在(因为信号的参数比槽的少,因此并没有传过来),函数就会报错。这种情况往往有两个原因:一是槽的参数就是比信号的少,此时我们可以像前面那种写法直接连接。另外一个原因是,信号的参数带有默认值。比如

 
 
1
void QPushButton::clicked(bool checked = false)

就是这种情况。

然而,有一种情况,槽函数的参数可以比信号的多,那就是槽函数的参数带有默认值。比如,我们的NewspaperReader有下面的代码:

 
 
1
2
3
4
5
// Newspaper
signals:
    void newPaper(const QString &name);
// Reader
    void receiveNewspaper(const QString &name, const QDate &date = QDate::currentDate());

虽然Reader::receiveNewspaper()的参数数目比Newspaper::newPaper()多,但是由于Reader::receiveNewspaper()后面一个参数带有默认值,所以该参数不是必须提供的。但是,如果你按照前面的写法,比如如下的代码:

 
 
1
2
3
4
QObject::connect(&newspaper,
                 static_cast<void (Newspaper:: *)(const QString &)>(&Newspaper::newPaper),
                 &reader,
                 static_cast<void (Reader:: *)(const QString &, const QDate & =QDate::currentDate())>(&Reader::receiveNewspaper));

你会得到一个断言错误:

 
 
1
The slot requires more arguments than the signal provides.

我们不能在函数指针中使用函数参数的默认值。这是 C++ 语言的限制:参数默认值只能使用在直接地函数调用中。当使用函数指针取其地址的时候,默认参数是不可见的!

当然,此时你可以选择 Qt 4 的连接语法。如果你还是想使用 Qt 5 的新语法,目前的办法只有一个:Lambda 表达式。不要担心你的编译器不支持 Lambda 表达式,因为在你使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。于是,我们的代码就变成了:

 
 
1
2
3
QObject::connect(&newspaper,
                 static_cast<void (Newspaper:: *)(const QString &)>(&Newspaper::newPaper),
                 [=](const QString &name) { /* Your code here. */ });

Qt 学习之路:深入 Qt5 信号槽新语法的更多相关文章

  1. Qt 学习之路 2(16):深入 Qt5 信号槽新语法

    Qt 学习之路 2(16):深入 Qt5 信号槽新语法  豆子  2012年9月19日  Qt 学习之路 2  53条评论 在前面的章节(信号槽和自定义信号槽)中,我们详细介绍了有关 Qt 5 的信号 ...

  2. QT学习之路--深入了解信号槽

    槽函数可以和一个信号相连接,当这个信号发生时,它可以被自动调用.connect()语句的原型类似于:connect(sender, SIGNAL(signal), receiver, SLOT(slo ...

  3. 【QT学习笔记】二、信号槽和自定义信号槽

    1. 信号槽 int main(int argc, char *argv[]) { QApplication app(argc, argv); QPushButton button("Qui ...

  4. 《Qt 学习之路 2》目录

    <Qt 学习之路 2>目录 <Qt 学习之路 2>目录  豆子  2012年8月23日  Qt 学习之路 2  177条评论 <Qt 学习之路 2>目录 序 Qt ...

  5. Qt 学习之路 2(11):布局管理器

    Home / Qt 学习之路 2 / Qt 学习之路 2(11):布局管理器 Qt 学习之路 2(11):布局管理器  豆子  2012年9月4日  Qt 学习之路 2  70条评论 所谓 GUI 界 ...

  6. Qt 学习之路 2(5):自定义信号槽

    Home / Qt 学习之路 2 / Qt 学习之路 2(5):自定义信号槽 Qt 学习之路 2(5):自定义信号槽  豆子  2012年8月24日  Qt 学习之路 2  131条评论 上一节我们详 ...

  7. Qt 学习之路 2(4):信号槽

    Home / Qt 学习之路 2 / Qt 学习之路 2(4):信号槽 Qt 学习之路 2(4):信号槽  豆子  2012年8月23日  Qt 学习之路 2  110条评论 信号槽是 Qt 框架引以 ...

  8. QT学习之路--创建一个对话框

    Q_OBJECT:这是一个宏,凡是定义信号槽的类都必须声明这个宏. 函数tr()全名是QObject::tr(),被他处理过的字符串可以使用工具提取出来翻译成其他语言,也就是做国际化使用. 对于QT学 ...

  9. Qt 学习之路 2(71):线程简介

    Qt 学习之路 2(71):线程简介 豆子 2013年11月18日 Qt 学习之路 2 30条评论 前面我们讨论了有关进程以及进程间通讯的相关问题,现在我们开始讨论线程.事实上,现代的程序中,使用线程 ...

随机推荐

  1. shell 常用

    /etc/password      用户的 home路径设置 chwon  groupname:username    path_or_file  -R    # 修改文件左右者 chomd

  2. codevs 1557 热浪

    传送门 题目描述 Description 德克萨斯纯朴的民眾们这个夏天正在遭受巨大的热浪!!!他们的德克萨斯长角牛吃起来不错,可是他们并不是很擅长生產富含奶油的乳製品.Farmer John此时以先天 ...

  3. 抓取天涯文章的蜘蛛代码,刚经过更新(因为天涯页面HTML代码变化)

    #_*_coding:utf-8-*- import urllib2 import traceback import codecs from BeautifulSoup import Beautifu ...

  4. java怎么连接sql server,需要注意的几点

    一.JAVA连接SQL的语句    JAVA连接SQL2000语句为:  Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");  Drive ...

  5. Streams and .NET

    http://www.codeguru.com/csharp/csharp/cs_data/streaming/article.php/c4223/Streams-and-NET.htm In thi ...

  6. 网页错误404 or 500

    HTTP 错误 400 400 请求出错 由于语法格式有误,服务器无法理解此请求.不作修改,客户程序就无法重复此请求. HTTP 错误 401 401.1 未授权:登录失败 此错误表明传输给服务器的证 ...

  7. 防止 DDoS 攻击的五个「大招」!

    提到 DDoS 攻击,很多人不会陌生.上周,美国当地时间 12 月 29 日,专用虚拟服务器提供商 Linode 遭到 DDoS 攻击,直接影响其 Web 服务器的访问,其中 API 调用和管理功能受 ...

  8. java.util.regex.Pattern的应用

    java.util.regex.Pattern 正则表达式的一种已编译的实现. 正则表达式通常以字符串的形式出现,它首先必须被编译为Pattern类的一个实例.结果模型可以用来生成一个Matcher, ...

  9. Altium Designer 定义板子外框

    Altium Designer 提供多种定义板子外形的方法. 第一种方法,在Files 面板(在界面下面System菜单条中查找)中选择PCB Templates命令.在这个界面下您可以选择符合您设计 ...

  10. bzoj1014

    动态询问LCP,所以我们不好用后缀数组考虑使用维护序列问题的splay+hash求LCP这里mark一下,hash求LCP常用mo=9875321自然溢出的话交上去莫名其妙WA了这里树上某节点hash ...