conncet函數的使用方法

概述

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章