zoukankan      html  css  js  c++  java
  • 关于Qt信号与槽机制的传递方向性研究(结论其实是错误的,但是可以看看分析过程)

    最近由于项目的需求,一直在研究Qt。信号与槽机制是Qt的一大特色,该机制允许两者间传递参数,依次来实现对象间的通信。这个参数会分别存在于信号的参数列表和槽函数的参数列表中。需要注意的是,若将槽函数绑定至信号,槽函数的参数列表元素数目只能少于等于信号的参数列表元素数目。而且顺序和类型不能改变。至于缺少的参数应从信号参数尾部开始缺少。

    突然今天想起来一个问题,如果一个对象发出信号,将内部的一个成员变量(非简单类型)作为参数向外发送,槽函数就可以接收到这个对象,那么槽函数是否可以完全操作这个对象呢?如果可以完全操作,那么这个信号与槽机制就是一个双向的通讯过程,即A可以触发B,B又能对A进行操作;如果操作无效,则说明该机制是一个单向的通讯过程,即A触发B,B进行处理,但不能反向写数据。

    闲言少叙,让实例说话

    先建立一个console application

    建立头文件CustomClasses.h

    代码如下:

    [cpp] view plain copy
     
    1. #ifndef CUSTOMCLASSES_H  
    2. #define CUSTOMCLASSES_H  
    3.   
    4. #include <QObject>  
    5.   
    6. class TPerson  
    7. {  
    8. private:  
    9.     QString _Name;  
    10. public:  
    11.     TPerson();  
    12.     void SetName(QString Name);  
    13.     QString GetName();  
    14. };  
    15.   
    16. class TSender:public QObject  
    17. {  
    18.     Q_OBJECT  
    19. private:  
    20.     TPerson _p;  
    21. public:  
    22.     TSender();  
    23.     void click();  
    24.     void disp();  
    25. signals:  
    26.     void NewName(TPerson P);  
    27. };  
    28.   
    29. class TReceiver:public QObject  
    30. {  
    31.     Q_OBJECT  
    32. public:  
    33.     TReceiver();  
    34. public slots:  
    35.     void GetNewName(TPerson P);  
    36. };  
    37. #endif // CUSTOMCLASSES_H  

    这里定义了三个类TPerson、TSender和TReceiver。

    TPerson是一个很简单的类,包含了一个私有成员_Name,分别具有Get和Set方法。

    TSender类中包含一个私有的TPerson实例_p,click()方法模拟按钮单击,当执行click()方法后会发出NewName(TPerson P)信号。

    TReceiver类中包含一个GetNewName(TPerson P)槽,用于接收来自TSender的NewName(TPerson P)信号。

    建立对应的CustomClasses.cpp

    代码如下:

    [cpp] view plain copy
     
    1. #include "CustomClasses.h"  
    2. #include <QtCore>  
    3. #include <QObject>  
    4. TPerson::TPerson()  
    5. {  
    6.     //  
    7. }  
    8. void TPerson::SetName(QString Name)  
    9. {  
    10.     this->_Name=Name;  
    11. }  
    12.   
    13. QString TPerson::GetName()  
    14. {  
    15.     return this->_Name;  
    16. }  
    17.   
    18. TSender::TSender()  
    19. {  
    20.     this->_p.SetName("Jack");  
    21. }  
    22.   
    23. void TSender::click()  
    24. {  
    25.     qDebug()<<"now begin to send person";  
    26.     emit this->NewName(this->_p);  
    27.     qDebug()<<"Person has sended";  
    28. }  
    29.   
    30. void TSender::disp()  
    31. {  
    32.     qDebug()<<"current person name is:"<<this->_p.GetName();  
    33. }  
    34.   
    35. TReceiver::TReceiver()  
    36. {  
    37.     //  
    38. }  
    39.   
    40. void TReceiver::GetNewName(TPerson P)  
    41. {  
    42.     qDebug()<<"receive a person name is:"<<P.GetName();  
    43.     qDebug()<<"begin to change name";  
    44.     P.SetName("Rose");  
    45.     qDebug()<<"finish to change name";  
    46. }  

    该文件是上述内容的实现,但值得注意的是,在void TReceiver::GetNewName(TPerson P)的实现中,对P参数进行了操作,调用了SetName方法。

    主文件main.cpp

    代码如下:

    [cpp] view plain copy
     
    1. #include <QtCore/QCoreApplication>  
    2. #include <QtCore>  
    3. #include <QObject>  
    4. #include "CustomClasses.h"  
    5.   
    6. int main(int argc, char *argv[])  
    7. {  
    8.     QCoreApplication a(argc, argv);  
    9.     TSender send;  
    10.     TReceiver recv;  
    11.     QObject::connect(&send, SIGNAL(NewName(TPerson)), &recv, SLOT(GetNewName(TPerson)));  
    12.     send.disp();  
    13.     send.click();  
    14.     send.disp();  
    15.     return a.exec();  
    16. }  

    该代码实例化TSender类和TReceiver类,并绑定NewName信号与GetNewName槽。代码很简单,其他的就不再多做介绍了。

    下面看运行结果:

    程序运行结果

    我们来分析一下这个程序。在声明了send实例和recv实例后,绑定了相关事件。首先查看了一下当前send对象中的TPerson类实例 _p的Name属性。第一行显示的是初始的Jack;然后调用了send的click()方法,该方法放出了NewName信号,此时该信号被recv的GetNewName槽函数接收,该函数显示了当前接收到对象的Name值。然后调用了接收对象的SetName方法,将Name设置为“Rose”。槽函数执行完毕后,代码跳转到触发NewName信号的位置,即click()方法的“emit this->NewName(this->_p); ”后面;最后再显示一下send实例内部的_p实例Name属性。此时观察到虽然在槽函数中重新设置了Name属性,但并没有改变信号发起方实例的属性值(依旧是Jack)。因此我们可以说,这种传递对象的信号与槽机制是单向通信的。传递方向是信号发出方到信号接收方。当然这个例子中只是传递的一个对象,本人试验了一下,如果将上述代码进行小幅修改,将传递参数变为对象指针,则最终会影响到信号发送发的数据。如下图所示:

    传递指针后的运行结果

    也就是说,如果传递的是指针,则该机制是双向通讯的,关键还是看如何去使用它们。我个人不推荐使用指针传递。因为一个信号可以绑定多个槽函数,若其中有一个对数据进行了修改,则会影响后续的槽函数执行,此时的参数状态未知,也不易于维护。当然了,什么事也不是绝对的,特殊情况下除外。

    另外补充两点:

    第一:上文中提到的“槽函数”这个词并不贴切。因为该方法是不能包含返回值的。必须是void,因此称之为“槽过程”比较贴切,但是已经广泛认同了这个叫法了,所以就没有深究。

    第二:开始时为了图省事,将TPerson、TSender和TReceiver三个类的定义和实现都放到了main.cpp中。结果无论如何也编译不过去。提示“undefined reference vTable for class ...”, ...是这三个类的类名。后来查到资料得知在Qt程序编译时,遇到signal和slots定义的地方会调用moc工具对其进行转换,转换为标准c++代码(signal和slots不是标准C++的关键字),而moc工具是只识别.h文件的,因此还是老老实实用标准的C++类定义方法,别偷懒了吧:)

    http://blog.csdn.net/chaijunkun/article/details/6249573

  • 相关阅读:
    ubuntu 制做samba
    《Programming WPF》翻译 第4章 前言
    《Programming WPF》翻译 第4章 3.绑定到数据列表
    《Programming WPF》翻译 第4章 4.数据源
    《Programming WPF》翻译 第5章 6.触发器
    《Programming WPF》翻译 第4章 2.数据绑定
    《Programming WPF》翻译 第4章 1.不使用数据绑定
    《Programming WPF》翻译 第5章 7.控件模板
    《Programming WPF》翻译 第5章 8.我们进行到哪里了?
    《Programming WPF》翻译 第5章 5.数据模板和样式
  • 原文地址:https://www.cnblogs.com/findumars/p/5589535.html
Copyright © 2011-2022 走看看