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

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_OBJECT

signals://定义一个信号//应该是个成员函数指针,只是被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_OBJECT
public:
    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
访客追踪插件


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值