深入浅出信号与槽

一个事实

在实际的项目开发中,大多数时候是直接将组件中预定义的信号连接到槽函数,信号发射时槽函数被调用。

深度的思考

信号是怎么来的?又是如何发射的?

Qt中信号(SIGNAL)的本质

信号只是一个特殊的成员函数声明

  函数的返回值是void类型

  函数只能声明不能定义

信号必须使用signals关键字进行声明

  函数的访问属性自动被设置为protected

  只能通过emit关键字调用函数(发射信号)

信号定义示例

class Test : public QObject    //只有Qt类才能定义信号
{
Q_OBJECT         //必须使用宏Q_OBJECT

signals:        //使用signals声明信号函数,访问级别为protected
    void testSignal(int v);  //信号只能声明不能定义
public:
    void test(int i)
    {
        emit testSignal(i);  //通过emit发射信号
    }
};
#ifndef TESTSIGNAL_H
#define TESTSIGNAL_H

#include <QObject>

class TestSignal : public QObject
{
Q_OBJECT

public:
    void sendSignal(int i)
    {
        emit testSignal(i);
    }
signals:
    void testSignal(int v);
};

#endif // TESTSIGNAL_H
#ifndef RXSIGNAL_H
#define RXSIGNAL_H

#include <QObject>
#include <QDebug>

class RxSignal : public QObject
{
    Q_OBJECT

public:

protected slots:
    void mySlot(int v)
    {
        qDebug() << "void mySlot(int v)";
        qDebug() << "sender:" << sender()->objectName();
        qDebug() << "receiver:" << this->objectName();
        qDebug() << "Value :" << v;
        qDebug() << endl;
    }
};

#endif // RXSIGNAL_H
#include <QCoreApplication>
#include <QDebug>
#include "RxSignal.h"
#include "TestSignal.h"

void emit_signal()
{
    qDebug() << "emit_signal() " << endl;
    RxSignal rx;
    TestSignal tx;

    rx.setObjectName("rx");
    tx.setObjectName("tx");

    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx, SLOT(mySlot(int)));

    for(int i=0; i<3; i++)
    {
        tx.sendSignal(i);
    }

}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    emit_signal();
    return a.exec();
}

 

信号与槽的对应关系
一个信号可以连接到多个槽(一对多)
多个信号可以连接到一个槽(多对一)
一个信号可以连接到另一个信号(转嫁)
连接可以被disconnect函数删除(移除)

实验一:一个信号被映射到两个槽函数上面

#include <QCoreApplication>
#include <QDebug>
#include "RxSignal.h"
#include "TestSignal.h"

void emit_signal()
{
    qDebug() << "emit_signal() " << endl;
    RxSignal rx;
    TestSignal tx;

    rx.setObjectName("rx");
    tx.setObjectName("tx");

    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx, SLOT(mySlot(int)));

    for(int i=0; i<3; i++)
    {
        tx.sendSignal(i);
    }

}
//将一个信号连接到两个槽函数上面
void one_to_multiple()
{
    qDebug() << "one_to_multiple() " << endl;
    RxSignal rx1;
    RxSignal rx2;
    TestSignal tx;

    rx1.setObjectName("rx1");
    rx2.setObjectName("rx2");
    tx.setObjectName("tx");

    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx1, SLOT(mySlot(int)));
    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx2, SLOT(mySlot(int)));

    tx.sendSignal(100);

}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //emit_signal();
    one_to_multiple();
    return a.exec();
}
View Code

实验二:将两个信号映射到一个槽函数上面

#include <QCoreApplication>
#include <QDebug>
#include "RxSignal.h"
#include "TestSignal.h"

void emit_signal()
{
    qDebug() << "emit_signal() " << endl;
    RxSignal rx;
    TestSignal tx;

    rx.setObjectName("rx");
    tx.setObjectName("tx");

    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx, SLOT(mySlot(int)));

    for(int i=0; i<3; i++)
    {
        tx.sendSignal(i);
    }

}
//将一个信号连接到两个槽函数上面
void one_to_multiple()
{
    qDebug() << "one_to_multiple() " << endl;
    RxSignal rx1;
    RxSignal rx2;
    TestSignal tx;

    rx1.setObjectName("rx1");
    rx2.setObjectName("rx2");
    tx.setObjectName("tx");

    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx1, SLOT(mySlot(int)));
    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx2, SLOT(mySlot(int)));

    tx.sendSignal(100);

}

//将两个信号连接到一个槽函数上面
void multiple_to_one()
{
    qDebug() << "multiple_to_one() " << endl;
    RxSignal rx;
    TestSignal tx1;
    TestSignal tx2;

    rx.setObjectName("rx");
    tx1.setObjectName("tx1");
    tx2.setObjectName("tx2");

    QObject::connect(&tx1, SIGNAL(testSignal(int)), &rx, SLOT(mySlot(int)));
    QObject::connect(&tx2, SIGNAL(testSignal(int)), &rx, SLOT(mySlot(int)));

    tx1.sendSignal(100);
    tx2.sendSignal(200);

}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    multiple_to_one();
    return a.exec();
}
View Code

 实验三:信号到信号的映射

#include <QCoreApplication>
#include <QDebug>
#include "RxSignal.h"
#include "TestSignal.h"

void emit_signal()
{
    qDebug() << "emit_signal() " << endl;
    RxSignal rx;
    TestSignal tx;

    rx.setObjectName("rx");
    tx.setObjectName("tx");

    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx, SLOT(mySlot(int)));

    for(int i=0; i<3; i++)
    {
        tx.sendSignal(i);
    }

}
//将一个信号连接到两个槽函数上面
void one_to_multiple()
{
    qDebug() << "one_to_multiple() " << endl;
    RxSignal rx1;
    RxSignal rx2;
    TestSignal tx;

    rx1.setObjectName("rx1");
    rx2.setObjectName("rx2");
    tx.setObjectName("tx");

    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx1, SLOT(mySlot(int)));
    QObject::connect(&tx, SIGNAL(testSignal(int)), &rx2, SLOT(mySlot(int)));

    tx.sendSignal(100);

}

//将两个信号连接到一个槽函数上面
void multiple_to_one()
{
    qDebug() << "multiple_to_one() " << endl;
    RxSignal rx;
    TestSignal tx1;
    TestSignal tx2;

    rx.setObjectName("rx");
    tx1.setObjectName("tx1");
    tx2.setObjectName("tx2");

    QObject::connect(&tx1, SIGNAL(testSignal(int)), &rx, SLOT(mySlot(int)));
    QObject::connect(&tx2, SIGNAL(testSignal(int)), &rx, SLOT(mySlot(int)));

    tx1.sendSignal(100);
    tx2.sendSignal(200);

}

//信号到信号的映射
void signal_to_signal()
{
    qDebug() << "signal_to_signal() " << endl;
    RxSignal rx;
    TestSignal tx1;
    TestSignal tx2;

    rx.setObjectName("rx");
    tx1.setObjectName("tx1");
    tx2.setObjectName("tx2");

    QObject::connect(&tx1, SIGNAL(testSignal(int)), &tx2, SIGNAL(testSignal(int)));
    QObject::connect(&tx2, SIGNAL(testSignal(int)), &rx, SLOT(mySlot(int)));

    tx1.sendSignal(100);
    tx2.sendSignal(200);

}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    signal_to_signal();
    return a.exec();
}
View Code

从打印结果看,当tx1发送信号的时候,就会转嫁到tx2上面。信号参数会被原封不动的搬过来,将从tx1中得到的参数包裹到它自己的参数里面,,最后tx2就将它作为自己的信号发送出去了。

不可忽视的军规
1.Qt类只能在头文件中声明
2.信号与槽的原型应该完全相同
3.信号参数多于槽参数时,多余的参数被忽略(这只是个例外,将这条规则忘记吧,只记住第2条军规就行了)
4.槽函数的返回值必须是void类型
5.槽函数可以像普通成员函数一样被调用
6.信号与槽的访问属性对于connect/disconnect无效(在类的外部可以直接调用类的成员函数,破坏了面向的封装性。但是它所带来的好处,远大于它的破坏。)

信号与槽的意义
最大限度的弱化了类之间的耦合关系
在设计阶段,可以减少不必要的接口类(抽象类)
在开发阶段,对象间的交互通过信号与槽动态绑定

小结:

信号只是一个特殊的成员函数声明
信号必须使用signals关键字进行声明
信号与槽可以存在多种对应关系
信号与槽机制使得类间关系松散,提高类的可复用性

 

原文地址:https://www.cnblogs.com/-glb/p/13457841.html