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