Qt_log2000_信号与槽中的connect函数之深入part2

来源:互联网 发布:淘宝学校哪家好 编辑:程序博客网 时间:2024/06/04 00:24

Qt学习记录6 (5’)

Qt; C++ 11; Qt父子窗体; Qt父子窗体间信息传递; Qt信号与槽; 函数指针;

学习Qt将近2个月了,现在对学习所得进行记录。本文是log2000计划的一部分

实验环境:
Qt5.8.0 支持C++ 11
ubuntu 14.04 64bit


现在看一下Qt中的signal,这是C++里面没有的东西
假设有一个A类和一个B类,A类发射信号,定义如下(选择在a.h中没在a.cpp中定义)

#ifndef A_H#define A_H#include <QObject>class A : public QObject{    Q_OBJECTsignals://定义一个信号//应该是个成员函数指针,只是被Qt简化为这种无形参无返回值的形式    void signal();public:    void useSignal(){//使用信号        emit signal();//emit时Qt会帮你调用signal所指向的槽函数,可以只想别的类的函数,如B类    }};#endif // A_H

B类接收信号,定义如下(选择在b.h中没在b.cpp中定义)

#ifndef B_H#define B_H#include <QObject>#include <iostream>using namespace std;class B : public QObject{    Q_OBJECTpublic:    void fun() { //定义一个“槽”        cout<<"接受对象a的信号,运行对象b的函数。"<<endl;    }};#endif // B_H
  • signals应该是个成员函数指针,只是被Qt简化为这种无形参无返回值的形式
  • emit signal();时Qt会帮你调用signal所指向的槽函数,可以只想别的类的函数,如B类

将A类的信号与B类的函数连接起来,(Copyright © http://blog.csdn.net/s_gy_zetrov. All Rights Reserved)这个过程,这个框架,就是图形界面的一大核心

main函数

#include "a.h"#include "b.h"int main(int argc, char *argv[]){    A a;    B b;    //连接信号和槽    QObject::connect(&a,&A::signal,                     &b,&B::fun);    //使用信号    a.useSignal();    return 0;}
  • connect中的&B::fun一定是个类成员函数的指针,signal既然要跟B类的fun函数连接起来,要“对等”,signal本身一定也是一个成员函数的指针,但由于signal和A本身就是分开写的,(Copyright © http://blog.csdn.net/s_gy_zetrov. All Rights Reserved)所以必须用这种&A::signal成员变量指针的形式写这个signal

下面试一试我们自己写出connect的函数,自己把A类的一个信号和B类的一个函数连接起来。

“解开”

#include <iostream>using namespace std;//第四步才看class A;class B;typedef void (A::*Apointer)();typedef void (B::*Bpointer)();//第一步开始看class A {  public:    void (A::*click)();//给A类定义了成员变量click,类型为成员函数指针类型,click理论上等于onClicked()    void onClicked(){            cout<<"按A上面的按钮,调用了自己的onClick函数!"<<endl;        }    //第四步才看    B* slotObj;    void TreatClickEvent(){        (slotObj->*(Bpointer)click)();    }};//第三步才看class B {  public:    int x=5;    void onClicked(){        cout<<"按A上面的按钮,调用了B的onClick函数! 成员变量x的值为"<<x<<endl;    }};//第一步开始看:复习成员变量指针void runMemberVariblePointer(A * obj, int A::* pMember) {    cout<<obj->*pMember<<endl;}//第一步开始看:复习成员函数指针void runfuncName(A * obj, void (A::*func)()){    (obj->*func)();}//第一步看:组合成员变量指针和成员函数指针void runfuncPointer(A * obj, void (A::*( A::*pfunc ))()){ //Apointer A::* pfunc    (obj->*(obj->*pfunc))();}//typedef void (A::*Apointer)();//第二步才看//typedef void (A::*(A::*Signal))();typedef Apointer A::* Signal;void connect(A* a, Signal signal, Apointer slot){ //void (A::*slot)()    a->*signal = slot;}//第三步才看void connect(A* a, Signal signal, Bpointer slot){    a->*signal = (Apointer) slot;}//第四步才看void connect(A* a, Signal signal, B* b, Bpointer slot){    a->*signal = (Apointer) slot;    a->slotObj = b;}int main(int argc, char *argv[]){    //第一步、理解信号的本质:成员函数指针类型的特殊成员变量    //第二步、连接A本身的信号与槽    A a;    runfuncName(&a,&A::onClicked);    connect(&a,&A::click,&A::onClicked);//a.click = &A::onClicked;    (a.*a.click)();    runfuncPointer(&a,&A::click);    //第三步:连接A的信号到B的槽    B b; B * fb = &b;    connect(&a, &A::click, &B::onClicked);//a.click = (void (A::*)())&B::onClicked;    (a.*a.click)();    (b.*(Bpointer)a.click)();//(fb->*(Bpointer)a.click)();    //第四步:完善连接A的信号到B的槽    connect(&a, &A::click,            fb, &B::onClicked);    a.TreatClickEvent();    return 0;}

运行结果

Starting /home/pc/build-testmyConnect-Desktop_Qt_5_8_0_GCC_64bit-Debug/testmyConnect...按A上面的按钮,调用了自己的onClick函数!按A上面的按钮,调用了自己的onClick函数!按A上面的按钮,调用了自己的onClick函数!按A上面的按钮,调用了B的onClick函数! 成员变量x的值为4197508按A上面的按钮,调用了B的onClick函数! 成员变量x的值为5按A上面的按钮,调用了B的onClick函数! 成员变量x的值为5/home/pc/build-testmyConnect-Desktop_Qt_5_8_0_GCC_64bit-Debug/testmyConnect exited with code 0

看到:

  • runMemberVariblePointer函数:int A::* pMember,是成员变量指针,实际上就是普通指针加上一个A::
  • runfuncName函数:void (A::*func)(),是成员函数指针,实际上就是普通函数指针加上一个A::
  • runfuncPointer函数:void (A::*( A::*pfunc ))(),从外层看,是成员函数指针void(A::* ~ )。从内层看,A::*pfunc,这个是成员变量指针的定义。如果使用typedef,将第四步提前看一下,typedef void (A::*Apointer)();这个就是使用typedef定义的普通A类成员函数定义法,Apointer就是成员函数指针类型的类型名。于是刚才的void (A::*( A::*pfunc ))()是可以换为Apointer A::* pfunc;的,是等价的。Apointer A::* pfunc;这个形式就与runMemberVariblePointer函数中的int A::* pMember形式完全一样了。
  • 现在runfuncPointer函数参数的写法看懂了,接下来看怎么调用。(obj->*(obj->*pfunc))();中obj这个对象出现了2次,首先要通过这个obj把第一个A::*“解开”,也就是obj->*pfunc把内层A::*pfunc部分“解开”,接着还需“解开”外面的一层,因为外层本身就是A类的成员函数指针类型,所以使用(obj->*(~))();解开它。
  • 接下来看main函数如何运行,首先调用runfuncName,通过a对象调用A类的onClicked,对应上面函数定义的(Copyright © http://blog.csdn.net/s_gy_zetrov. All Rights Reserved)成员函数指针类型,通过成员函数指针的方式调用onClicked。输出了第一行【按A上面的按钮,调用了自己的onClick函数!】接下来使用了connect,把a传进去,把A::click传进去,把A::onClicked传进去,把A类的一个signal连接到一个A类的slot,实现了a.click = &A::onClicked;
  • main函数中(a.*a.click)();调用click,a.click就是成员变量,因为这个成员变量是个成员变量指针,所以要加个*,然后再前面加上“对象.”
  • 到第二步处看connect函数的定义,在前面加了typedef简化语法,signal就相当于之前的pfunc类型,槽slot的类型就是成员函数指针类型。connect实际上就是把信号的成员变量指针那一层解开,剩下成员函数指针,直接与slot相连a->*signal = slot;(obj->*(obj->*pfunc))();比较,slot相当于外面这一层,a->*signal相当于解开里层,于是剩下外层的signa可以直接与slot做“=”
  • signal实际上就是一个成员函数指针类型,只不过信号写到connect函数里面当参数的时候connect必须要把它跟对象分开为两个参数,所以才又套了一层成员变量指针。
  • typedef Apointer A::* Signal;使用Apointer简化signal,写typedef语句,使用Apointer来定义signal语句,对应typedef void (A::*(A::*Signal))();处就可改为typedef Apointer A::signal;

接下来扩展,A类的click要指向B类的onClicked。看第三步。

  • void connect(A* a, Signal signal, Bpointer slot)中,a->*signal = (Apointer) slot;强制类型转换,把slot转为A类型,再赋给A类的signal,输出了【按A上面的按钮,调用了B的onClick函数! 成员变量x的值为4197508】,x的值并不是5,说明虽然调用的B类的onClicked函数,但传递的this指针不对,是a的this指针。
  • 于是还需要传入b的和this指针。于是强制转为b的指针(b.*(Bpointer)a.click)();相当于执行(fb->*(Bpointer)a.click)();现在输出的是【按A上面的按钮,调用了B的onClick函数! 成员变量x的值为5】

但这么连接A的信号与B的函数还是太麻烦,于是看到第四步

  • void connect(A* a, Signal signal, B* b, Bpointer slot),a和b对象地址都传进去了。传进去之后首先还是a->*signal = (Apointer) slot;需要强转因为是slot不是Apointer类型,然后a->slotObj = b;看到A类定义里面的第四步,有一个B* slotObj;
  • A类中的TreatClickEvent()定义模拟的是emit signal
  • 最后main函数中将他们连接起来connect(&a, &A::click,fb, &B::onClicked);a.TreatClickEvent();这样与Qt中的写法就一致了。最后输出最后一句【按A上面的按钮,调用了B的onClick函数! 成员变量x的值为5】

小挑战:

实现任意两个类的连接,如何?

提示:

引入继承结构和虚函数,onclick如果是虚函数会方便一点。继承结构是保证B* slotObj;可以只想任何其他类型,把B变成父类或者在AB上新建一个共同的父类。(Copyright © http://blog.csdn.net/s_gy_zetrov. All Rights Reserved)还有就是在connect里面使用模板。这样真正实现Qt中的connect,但Qt中的connect前提也是类都必须继承QObject。QObject做得就是signal和emit的工作。


visitor tracker
访客追踪插件