Qt学习笔记2——深入 Qt5 信号槽新语法

学习自:https://www.devbean.net/2012/08/qt-study-road-2-catelog/
记录学习笔记仅供自己学习使用,如有侵权,请联系作者删除。

1.基本用法

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

// newspaper.h
#include 
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 
#include 

class Reader : public QObject
{
    Q_OBJECT
public:
    Reader() {}

    void receiveNewspaper(const QString & name) const
    {
        qDebug() << "Receives Newspaper: " << name;
    }
};

// main.cpp
#include 

#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()函数地址。编译器能够利用这两个地址,在编译期对这个连接操作进行检查,如果有个任何错误(包括对象没有这个信号,或者信号参数不匹配等),编译时就会发现。

2.有重载的信号

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

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

此时如果还是按照前面的写法,编译器会报出一个错误: 由于这个函数(注意,信号实际也是一个普通的函数)有重载,因此不能用一个取址操作符获取其地址。

因此,我们使用一个函数指针来指明到底是哪一个信号:

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

这样,我们使用了函数指针newspaperNameDate声明一个带有QString和QDate两个参数,返回值是 void 的函数,将该函数作为信号,与Reader::receiveNewspaper()槽连接起来。这样,我们就回避了之前编译器的错误。归根结底,这个错误是因为函数重载,编译器不知道要取哪一个函数的地址,而我们显式指明一个函数就可以了。
如果你觉得这种写法很难看,想像前面一样写成一行,当然也是由解决方法的:

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

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

QObject::connect(&newspaper,
                 static_cast(&Newspaper::newPaper),
                 &reader,
                 &Reader::receiveNewspaper);

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

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

3.C++命名的强制类型转换

形式:

cast-name(expression);

type是转换的目标类型;
expression是要转换的值。
如果type是引用类型,则结果是左值。
cast-name是 static_cast、dynamic_cast、const_cast 和 reinterpret_cast 中的一种。

1.static-cast:
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static-cast。
例如,通过将一个运算对象强制转换成double类型,就能使表达式执行浮点数除法:

double slope = static_cast(j) / i;

static_cast对于编译器无法自动执行的类型转换也非常有用。 但应确保转换后所得的类型就是指针所指的类型,类型一旦不符,将产生未定义的后果。
例如,使用static_cast可以找回存在于void*指针中的值:

void* p =&d;
double *dp = static_cast(p);		//将void*转换回初始的指针类型

2.dynamic_cast: 支持运行时类型识别。

3.const_cast:
只能改变运算对象的底层const,即将常量对象转换成非常量对象,一般称其为“去掉const性质”。

const char *pc;
char *p = const_cast(pc);			//正确:但是通过p写值是未定义的行为

4.reinterpret_cast: 【使用reinterpret_cast是非常危险的行为 】
通常为运算对象的位模式提供较低层次上的重新解释。
例如:

int *ip;
char *pc = reinterpret_cast(ip);	
//我们必须牢记pc所指的真实对象是一个int而非字符,
//如果把pc当成普通的字符指针使用就可能在运行时发生错误!!!

【注意:强制类型转换干扰了正常的类型检查,因此我们强烈建议程序员避免使用强制类型转换。】

4.带有默认参数的槽函数

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

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

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

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

QObject::connect(&newspaper,
                 static_cast(&Newspaper::newPaper),
                 &reader,
                 static_cast(&Reader::receiveNewspaper));

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

目前的办法只有一个:Lambda 表达式。

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

你可能感兴趣的:(QT)