概述
信号和槽机制是 QT 的核心机制,要精通 QT 编程就必须对信号和槽有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是 QT 的核心特性,也是 QT 区别于其它工具包的重要地方。信号和槽是 QT 自行定义的一种通信机制,它独立于标准的 C/C++ 语言,因此要正确的处理信号和槽,必须借助一个称为 moc(Meta Object Compiler)的 QT 工具,该工具是一个 C++ 预处理程序,它为高层次的事件处理自动生成所需要的附加代码。
在我们所熟知的很多 GUI 工具包中,窗口小部件 (widget) 都有一个回调函数用于响应它们能触发的每个动作,这个回调函数通常是一个指向某个函数的指针。但是,在 QT 中信号和槽取代了这些凌乱的函数指针,使得我们编写这些通信程序更为简洁明了。 信号和槽能携带任意数量和任意类型的参数,他们是类型完全安全的,不会像回调函数那样产生 core dumps。
所有从 QObject 或其子类 ( 例如 Qwidget) 派生的类都能够包含信号和槽。当对象改变其状态时,信号就由该对象发射 (emit) 出去,这就是对象所要做的全部事情,它不知道另一端是谁在接收这个信号。这就是真正的信息封装,它确保对象被当作一个真正的软件组件来使用。槽用于接收信号,但它们是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且,对象并不了解具体的通信机制。
你可以将很多信号与单个的槽进行连接,也可以将单个的信号与很多的槽进行连接,甚至于将一个信号与另外一个信号相连接也是可能的,这时无论第一个信号什么时候发射系统都将立刻发射第二个信号。总之,信号与槽构造了一个强大的部件编程机制。
指针的使用
1.首先利用fatherdialog的默认构造函数(fatherdialog.cpp代码中未给出)即不带参数的构造函数,构造一个名为w的对象.
2.在点击Child Dialog按钮时,将w作为参数传给带参数的构造函数,构造出一个新的对象,并调用新对象的show函数.
#include "fatherdialog.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
FatherDialog w;//1.调用默认构造函数.
w.show();
return a.exec();
}
#include "fatherdialog.h"
#include "ui_fatherdialog.h"
#include <QMessageBox>
FatherDialog::FatherDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::FatherDialog)
{
ui->setupUi(this);
QObject::connect(ui->childB,
&QPushButton::clicked,
this,
&FatherDialog::showChildDialog);
}
FatherDialog::~FatherDialog()
{
delete ui;
}
void FatherDialog::on_pushButton_clicked()
{
QMessageBox::information(this,"提示","<font size='26'>请告诉我为什么</font>",QMessageBox::Ok);
}
void FatherDialog::showChildDialog()
{
QDialog * d= new QDialog(this);//2.在已存在的对象中,将自身作为形参传给新的对象,调用有参数的构造函数.
d->show();
}
自定义类型指针
1.首先使用自定义数据类型typedef 将变量pHello的类型定义为为一个函数指针,指向的函数返回值类型为void,无参数2. 定义了一个void函数指针pf,并将其指向Hello
3.通过指针pf调用函数.
4.再创建一个pHello类型的变量p(其实就是一个指向函数返回值类型为void,不带参数的函数指针),将其指向Hello,并通过指针调用.
#include <iostream>
using namespace std;
void hello(){
cout<<"abc"<<endl;
}
int abc(int x){
return x+1;
}
typedef void (*pHello)();//变量pHello的类型为一个函数指针,指向的返回值类型为void,无参数
int main(int argc, char *argv[])
{
//int (*fAbc)(int axc);
//fAbc=&abc;
//cout<<(*fAbc)(1)<<endl;
void (*pf)();//定义了一个void函数指针pf
pf = &hello;//将函数指针指向hello
//pf = hello;
(*pf)();//调用函数
//pf();
pHello p = &hello;//创造一个void函数指针p指向hello
(*p)();
return 0;
}
/*
typedef int (*pF)(int);
pF y;
y=&abc;
cout<<(*y)(1)<<endl;
*/
父类子类的函数指针
1.先通过指向父类的sayHello函数的函数指针,用父类对象调用父类的函数.
2.子类对象使用指向父类的sayHello函数的函数指针时,因为指针指向的不是父类的虚函数,所以仍然调用的是父类中的同名函数
3.创建函数指针cp指向父类中的虚函数.第一次通过父类对象调用的是父类中已经实例化的虚函数.
4.通过子类的对象来调用cp,因为子类中已经对父类的虚函数进行重写,则调用子类中的同名函数.
总结:
1.子类对象可以使用父类的函数指针,当指向的函数是虚函数并且在子类中已经有该函数的重写时,调用子类的函数,否则调用父类的函数.
#include <iostream>
using namespace std;
class Person {
public:
void sayHello(){
cout<<"你好"<<" ";
printf("%d\n",&Person::sayHello);//输出函数地址
}
virtual void sayName(){
cout<<"我没有名字"<<" ";
printf("%d\n",&Person::sayName);
}
};
class Child : public Person {
public:
void sayHello(){
cout<<"你好"<<" ";
printf("%d\n",&Child::sayHello);
}
virtual void sayName(){
cout<<"我是小明"<<" ";
printf("%d\n",&Child::sayName);
}
};
typedef void (*FunctionPointer)();
typedef void (Person::*PersonMemberFunctionPointer)();//PersonMemberFunctionPointer为指向Person类中函数类型为void不带参数的函数的函数指针
typedef void (Child::*ChildMemberFunctionPointer)();//ChildMemberFunctionPointer为指向Child类中函数类型为void不带参数的函数的函数指针
void runfuncName(Person * obj, void (Person::*func)() ){//PersonMemberFunctionPointer func
(obj ->* func)();
}
int main(int argc, char *argv[])
{
Person someone;
Child xiaoming;
PersonMemberFunctionPointer pp;
pp = &Person::sayHello;
(someone .* pp)();//调用someone的sayHello函数
//等价于 (&someone ->* pp)();
//也等价于 someone.sayHello();
(xiaoming.*pp)();//因为pp指向父类的sayHello函数,虽然能够被子类调用但指向的还是父类的函数.
//pp=&Child::sayHello;(不能运行,需要强制转换)
ChildMemberFunctionPointer cp = &Child::sayHello;
(xiaoming.*cp)();//调用子类的函数
runfuncName(&xiaoming,pp);//调用父类的函数
PersonMemberFunctionPointer pp2 = &Person::sayName;
(someone.*pp2)();//调用虚函数本身
(xiaoming.*pp2)();//必须是公开继承,才有权限
//由于子类中已经实现了虚函数,同时用子类的实例化对象调用所以是子类的已实现的函数
//pp2 = &Child::sayName;(不能运行,需要强制转换)
return 0;
}
理解信号
第一步:1.click为函数指针
2. pMember为指向A类的int类型的成员变量的指针
3. func为指向A类中的返回值为void类型的函数的函数指针
4. pfunc为指向函数指针的指针
第二步:
1.调用runfunName函数,使用成员函数指针调用函数.输出第一条”按A上面的按钮,调用了自己的onClick函数!”
2.调用conncet函数,将指向函数指针的指针clikc指向A::onClicked;然后通过a.*a.click来调用对象a中的onClicked()函数.输出第二条”按A上面的按钮,调用了自己的onClick函数!”
3.调用runfunPointer函数,将指向函数指针的指针pfunc指向传进去的函数指针click,通过pfunc调用onClicked()函数.输出第三条”按A上面的按钮,调用了自己的onClick函数!”
第三步:
1. 调用connect函数,将指向B类的返回值为void类型的函数的函数指针slot强转为Apointer类型后在赋给signal(signal为指向A类的返回值为void类型的函数的函数指针的指针)
2.通过A类的实例化对象a来调用B类的onClicked()函数,因为没有确定的B类的实例化对象,所以B类的成员变量x的值为随机值4358424.输出为”按A上面的按钮,调用B的onClicked函数!成员变量x的值为4358424”
3.通过B类的实例化对象b来调用B类的onClicked()函数, 因为有确定的B类的实例化对象b,所以B类的成员变量x的值为所赋的处置5;输出结果为” 按A上面的按钮,调用B的onClicked函数!成员变量x的值为5”.
第四步:
1.调用connect函数,将指向B类的返回值为void类型的函数的函数指针slot强转为Apointer类型后在赋给signal(signal为指向A类的返回值为void类型的函数的函数指针的指针);同时将A类的成员变量—B类对象的指针slotObj指向B的对象b,在调用对象a的函数TreatClickEvent(),使用指针slotObj调用函数,输出结果为” 按A上面的按钮,调用B的onClicked函数!成员变量x的值为5 ”
总结:
槽:带有实现的成员函数
信号:实现为空的成员函数;
通过conncet函数,来将信号和槽连接起来, 所有从 QObject 或其子类 ( 例如 Qwidget ) 派生的类都能够包含信号和槽。因为信号与槽的连接是通过 QObject 的 connect() 成员函数来实现的。
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
其中 sender 与 receiver 是指向对象的指针,SIGNAL() 与 SLOT() 是转换信号与槽的宏。
如代码中的void connect(A* a, Signal signal, B* b, Bpointer slot)其中 a为发射对象的指针,signal为信号,b为接收对象的指针.
除此以外, 一个信号可以连接多个槽,代码中的click信号连接了两个槽.
#include <iostream>
using namespace std;
//第四步才看
class A;
class B;
typedef void (A::*Apointer)();//Apointer为指向A类的返回值为void类型的函数的函数指针
typedef void (B::*Bpointer)();//Bpointer为指向B类的返回值为void类型的函数的函数指针
//第一步开始看
class A {
public:
void (A::*click)();//click为函数指针
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) {//pMember为指向A类的int类型的成员变量的指针
cout<<obj->*pMember<<endl;
}
//第一步开始看:复习成员函数指针
void runfuncName(A * obj, void (A::*func)()){//func为指向A类中的返回值为void类型的函数的函数指针.
(obj->*func)();
}
//第一步看:组合成员变量指针和成员函数指针
void runfuncPointer(A * obj, void (A::*( A::*pfunc ))()){ //Apointer A::* pfunc
(obj->*(obj->*pfunc))();//pfunc为指向函数指针的指针.
}
//typedef void (A::*Apointer)();
//第二步才看
//typedef void (A::*(A::*Signal))();
typedef Apointer A::* Signal;//signal为指向A类的返回值为void类型的函数的函数指针的指针
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)();//此时调用的还是A::onClicked();
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的对象,所以x的值为随机的.
(b.*(Bpointer)a.click)();//(fb->*(Bpointer)a.click)();此时通过B的确定对象来调用,x的值为5
//第四步:完善连接A的信号到B的槽
connect(&a, &A::click,
fb, &B::onClicked);//将B的是实例化对象赋给A中的B类对象
a.TreatClickEvent();
return 0;
}
信号发射和槽的接收
在A类中定义信号,B类中定义槽,调用conncet函数将信号和槽连接后,将信号发散,与之相对应的槽接收信号后,执行操作,输出”接受对象a的信号,运行对象b的函数。
#ifndef A_H
#define A_H
#include <QObject>
class A : public QObject
{
Q_OBJECT
signals://定义一个信号
void signal();
public:
void useSignal(){//使用信号
emit signal();
}
};
#endif // A_H
#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
#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;
}