Qt - 信号槽

欢迎转载,请注明出处:https://blog.csdn.net/qq_39453936?spm=1010.2135.3001.5343
原文链接: https://blog.csdn.net/qq_39453936/article/details/120513261


一、什么是信号槽?

信号槽,可以理解为观察者模式的一种实现。

  • 信号(Signal):
    就是在特定情况下被发射的事件,例如 PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号。

  • 槽(slot):
    就是对信号响应的函数。槽就是一个函数,可以定义在类的任何部分(public、private 或 protected),也可以被直接调用。


二、连接方式

在这里插入图片描述

 enum ConnectionType {
        AutoConnection,
        DirectConnection,
        QueuedConnection,
        BlockingQueuedConnection,
        UniqueConnection =  0x80
    };
连接方式功能作用
AutoConnection默认值。根据sender和receiver的所处线程在信号发出时作出判断。如果在同一线程使用Qt::DirectConnection连接, 否则使用 Qt :: QueuedConnection 连接。注意:当信号发射时判断的是发出信号这个动作所在的线程,再确定关联类型。
DirectConnection表示一旦信号产生,立即执行槽函数,本质是函数的调用。
QueuedConnection会在receiver所在的线程的event loop中进行处理。
BlockingQueuedConnection通过event loop循环调用slot方法, 但会阻塞直至调用完成返回。注意:sender于receiver不能在同一线程。
UniqueConnection表示只有它不是一个重复连接,连接才会成功。如果之前已经有了一个链接(相同的信号连接到同一对象的同一个槽上),那么连接将会失败并将返回false。这里可以通过按位或与以上四个结合在一起使用。注意:信号槽多连接就会被多次调用。而这个参数就是为此而生,此参数保证同一个信号和槽函数只会绑定一次。

三、connect 的常用方式

信号和槽使用QObject类中的成员函数connect进行关联,该函数有多个重载版本!
形式1:
[static] QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
示例:

QLabel *label = new QLabel;
QScrollBar *scrollBar = new QScrollBar;
QObject::connect(scrollBar, SIGNAL(valueChanged(int)),
                 label,  SLOT(setNum(int)));

形式2
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection) const
示例:

QLabel *label = new QLabel;
QScrollBar *scrollBar = new QScrollBar;
label->connect(scrollBar, SIGNAL(valueChanged(int)),SLOT(setNum(int)));

形式3:
[static] QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
示例:

QLabel *label = new QLabel;
QLineEdit *lineEdit = new QLineEdit;
QObject::connect(lineEdit, &QLineEdit::textChanged,
                 label,  &QLabel::setText);

形式4:
[static] QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
示例:

void someFunction();
QPushButton *button = new QPushButton;
QObject::connect(button, &QPushButton::clicked, someFunction);

Lambda expressions can also be used:

QByteArray page = ...;
QTcpSocket *socket = new QTcpSocket;
socket->connectToHost("qt-project.org", 80);
QObject::connect(socket, &QTcpSocket::connected, [=] () {
        socket->write("GET " + page + "\r\n");
    });

形式5:
[static] QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)
示例:

void someFunction();
QPushButton *button = new QPushButton;
QObject::connect(button, &QPushButton::clicked, this, someFunction, Qt::QueuedConnection);

Lambda expressions can also be used:

QByteArray page = ...;
QTcpSocket *socket = new QTcpSocket;
socket->connectToHost("qt-project.org", 80);
QObject::connect(socket, &QTcpSocket::connected, this, [=] () {
        socket->write("GET " + page + "\r\n");
    }, Qt::AutoConnection);

形式6:
[static] QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type = Qt::AutoConnection)
示例:与形式1相同,只是它使用QMetaMethod指定信号和槽

注意事项:
① 形式1的SINGAL和SLOT宏实际是把该宏的参数转换为字符串,当信号和槽相关联时,使用的是字符串进行匹配,因此,信号和槽的参数类型的名字必须在字符串意义上相同,所以信号和槽无法使用兼容类型的参数,也因此也不能使用typedef或namespace的类型,虽然他们的实际类型相同,但由于字符串名字不同,从而无法使用形式1。
② 形式3的信号和槽函数的参数类型不需完全一致,可以进行隐式转换。形式3还支持typedef和命名空间。
③ 形式3以指针的形式指定信号和槽函数,不需再使用SIGNAL()和SLOT宏。
④ 形式3的槽函数可以不使用slots关键字声明,任意的成员函数都可以是槽函数。形式1的槽函数必须使用slots修饰。
⑤ 形式1的槽函数不受private的限制,也就是说即使槽是private的,仍可通过信号调用该槽函数,而形式3则在使用connect时就会发生错误。
⑥ 当信号或槽函数有重载的形式时,使用形式3可能会产生二义性错误,此时可使用函数指针的形式指定信号或槽函数,或者使用形式1,比如

class A:public QObject{Q_OBJECT signals:void send(int i);};
class B:public QObject{Q_OBJECT public slots: void recv(); void recv(int i);};
A ma;B mb;
QObject::connect(&ma,&A::send,&mb,&B::recv);
解决方式
QObject::connect(&ma,&A::send,&mb,static_cast<void(B::*)(int)>(&B::x));

参数问题:
信号与槽想要链接成功,则槽函数有的参数个数和类型,在对应的信号函数
中必须一一对应,但信号函数的参数个数一定多于或等于槽函数的参数个数

对自定义类型信号槽传值时需先使用qRegisterMetaType注册类型:
1、注册位置:在第一次使用此类连接跨线程的signal/slot之前,一般在当前类的构造函数中进行注册;
2、注册方法:在当前类的顶部包含:#include ,构造函数中加入代码:qRegisterMetaType(“Myclass”);
3、Myclass的引用类型需单独注册:qRegisterMetaType(“Myclass&”);


四、disconnect 的使用

  1. bool QObject::disconnect(const QObject * receiver, const char * method = 0) const
    断开所有发送者的信号与接受者槽的连接
  2. bool QObject::disconnect(const char * signal = 0, const QObject * receiver = 0, const char * method = 0) const
  3. bool QObject::disconnect(const QObject * sender, const char * signal, const QObject * receiver, const char * method) [static]
    断开通常用于以下三种方式:
    (1) 断开某个对像的所有的信号连接::
    disconnect(myObject, 0, 0, 0);
    等价于
    myObject->disconnect();
    (2)断开某个信号与它对应槽的所有连接:
    disconnect(myObject, SIGNAL(mySignal()), 0, 0);
    等价于
    myObject->disconnect(SIGNAL(mySignal()));
    (3)断开某个接收对象的连接:
    disconnect(myObject, 0, myReceiver, 0);
    等价于
    myObject->disconnect(myReceiver);
  4. bool QObject::disconnect(const QObject * sender, const QMetaMethod & signal, const QObject * receiver, const QMetaMethod & method) [static]
  5. bool QObject::disconnect(const QMetaObject::Connection & connection) [static]
  6. bool QObject::disconnect(const QObject * sender, PointerToMemberFunction signal, const QObject * receiver, PointerToMemberFunction method) [static]
    (1)断开所有连接到该对象的信号
    disconnect(myObject, 0, 0, 0);
    (2)断开一切连接到特定信号:
    disconnect(myObject, &MyObject::mySignal(), 0, 0);
    (3)断开一个特定的接收者:
    disconnect(myObject, 0, myReceiver, 0);
    (4)断开一个特定信号到特定槽的连接:
    QObject::disconnect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);

五、QT信号槽机制的优缺点

(1)为什么Qt使用信号与槽机制而不是传统的回调函数机制进行对象间的通信呢?
回调函数的本质是“你想让别人的代码执行你的代码,而别人的代码你又不能动”这种需求下产生的。
回调函数是函数指针的一种用法,如果多个类都关注某个类的状态变化,此时需要维护一个列表,以存放多个回调函数的地址。对于每一个被关注的类,都需要做类似的工作,因此这种做法效率低,不灵活。
(2)解决办法:
Qt使用信号与槽机制来解决这个问题,程序员只需要指定一个类含有哪些信号函数、哪些槽函数,Qt会处理信号函数和槽函数之间的绑定。当信号函数被调用时,Qt会找到并执行与其绑定的槽函数。允许一个信号函数和多个槽函数绑定,Qt会依次找到并执行与一个信号函数绑定的所有槽函数,这种处理方式更灵活。

  • 信号和槽机制的优点
    1)类型安全。需要关联的信号和槽的签名必须是等同。
    即信号的参数类型和参数个数 同接收该信号的槽的参数类型和参数个数相同。

    2)松散耦合。信号和槽机制减弱了Qt对象的耦合度。
    激发信号的Qt对象无须知道是哪个对象的哪个槽需要接收它发出的信号,它只需要做的是在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道哪个对象的哪个槽接收到了信号。
    同样地,对象的槽也不知道是哪些信号关联了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。即使关联的对象在运行时被删除。应用程序也不会崩溃。

  • 信号和槽机制的缺点
    信号和槽机制增强了对象间通信的灵活性,然而这也损失了一些性能。同回调函数相比,信号和槽机制运行速度有些慢。通常,通过传递一个信号来调用槽函数将会比直接调用直接调用非虚函数运行速度慢10倍。原因如下。
    1)需要定位接收信号的对象。
    2)安全地遍历所有的关联。
    3)编组/解组传递的参数。
    4)多线程的时候。信号可能需要排队等待。


六、总结

在这里插入图片描述


小白发文,不喜勿喷
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值