02 QT信号与槽机制

版权声明:原创不易,如引用,请附原文链接 https://blog.csdn.net/Chiang2018/article/details/88926977

1、信号与槽简介

Qt提供了信号与槽机制用于完成界面操作的响应,是完成任意两个Qt对象之间通信的机制。其中,信号会在某个特定情况或动作下被触发,槽是等同与接受并处理信号的函数。 
每个Qt对象都包含若干个预定义的信号和槽,当某一个特定事件发生时,一个信号被发送,与信号相关联的槽则会响应信号并完成相应的处理。当一个类被继承时,该类的信号与槽也同时被继承,也可以根据自定义信号与槽。
信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。

QT使用connect()函数连接信号和槽。

1、QT5的connect()函数最常用的一般形式:

connect(sender, signal, receiver, slot);
/*
参数:
sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数

例如:QObject::connect(&button, &QPushButton::clicked,&app, &QApplication::quit);
*/

2、QT4的 connect()函数最常用的一般形式如下:

connect(sender, SIGNAL(signal()), receiver, SLOT(slot));
/*
参数:
sender:发出信号的对象的地址
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数

例如:QObject::connect(&button, SIGNAL(clicked()),this, SLOT(quit()));
*/

虽然相对于QT5的形式,QT4的形式书写起来更方便,但有两个很大的缺点:

  • SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。注意到connect()函数的 signal 和 slot 都是接受字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
  • QT4的槽函数必须使用slots关键字来修饰,否则无法关联信号。

注意信号与槽通过connect函数连接后,也可以使用disconnect()函数解除连接,参数与connect()一致。

2、自定义信号槽

使用connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使用系统提供的那部分,还会允许我们自己设计自己的信号和槽。

#include <QObject>
 ////////// newspaper.h //////////

//继承于QObject类
class Newspaper : public QObject
{
    /* 提供信号与槽机制 */
    Q_OBJECT
public:
    Newspaper(const QString & name) :
        m_name(name)
    {
    }
     
    /* 槽函数,用于发送信号 */
    void send()
    {
        /*发送newPaper信号*/
        emit newPaper(m_name);
    }
signals:
    /* 声明newPaper信号而无需定义 */
    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(); 
    return app.exec();
}

1、只有继承了QObject类的类,才具有信号槽的能力。所以,为了使用信号槽,必须继承QObject。凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽,都应该添加这个宏。这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力。

2、在类的声明中,除了public,private外,还有signals 块,且其所列出的,就是该类的信号。信号就是一个个的函数名,返回值是 void(因为无法获得信号的返回值,所以也就无需返回任何值),参数是该类需要让外界知道的数据。信号作为函数名,不需要在 cpp 函数中添加任何实现。

3、emit 是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)。emit 的含义是发出,也就是发出newPaper()信号。感兴趣的接收者会关注这个信号,我们也可以通过传参的形式将实际的报纸名字m_name当做参数传给这个信号。当接收者连接这个信号时,就可以通过槽函数获得实际值。这样就完成了数据从发出者到接收者的一个转移。

4、Qt 5 中,任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。如果槽函数是成员函数,会受到类的prublic、private、protected影响,如果信号是 private 的,这个信号就不能在类的外面连接,也就没有任何意义。

5、一个信号可以连接多个槽函数,这时候槽函数会一个一个调用,但调用顺序是不确定的。

6、槽函数可以被取消链接,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。

7、在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理。

8、信号可以重载,在使用重载的信号时,需要借助函数指针。例如:

/* func为无参数的mysignal信号 */
void (SubWidget::*func)() = &SubWidget::mysignal;

/* func1为有参数的mysignal信号 */
void (SubWidget::*func1)(int,QString) = &SubWidget::mysignal;

connect(&w,func,this,&MyWidget::MySlot2);
connect(&w,func1,this,&MyWidget::MySlot3);

3、Lambda表达式

C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。如果要使用Lambda表达式,必须在QT的.pro文件中加入一句 CONFIG += C++11,以引入C++11模块。首先看一下Lambda表达式的基本构成:

[函数对象参数]  (操作符重载函数参数)  mutable或exception -> 返回值{函数体}

① 函数对象参数;

[],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:

  1. 空。没有使用任何函数对象参数。
  2. =。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
  3. &。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
  4. this。函数体内可以使用Lambda所在类中的成员变量。
  5. a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。

② 操作符重载函数参数;

标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。

③ 可修改标示符;

按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。

④ 错误抛出标示符;

exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)

⑤ 函数返回值;

->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。

⑥ 是函数体;

{},标识函数的实现。

示例如下:

 /* 定义一个局部静态变量按钮b */
QPushButton* b = new QPushButton(this);

/* 定义一个局部变量a */
int a = 10;

/* 使用Lambda表达式关联信号,此时connect只有三个参数 */
connect(b2,&QPushButton::clicked,
            [=](bool ch)mutable
            {
                b1->setText("4444");
                a = 1111;
                qDebug()<<ch;
            }

            );

猜你喜欢

转载自blog.csdn.net/Chiang2018/article/details/88926977