概述
信號和槽機制是 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;
}