C++框架_之Qt的信號和槽的詳解

C++_之Qt的信號和槽的詳解

一、概述

  信號槽是 Qt 框架引覺得豪的機制之一。所謂信號槽,實際就是觀察者模式。當某個事件發生以後,好比,按鈕檢測到本身被點擊了一下,它就會發出一個信號(signal)。這種發出是沒有目的的,相似廣播。若是有對象對這個信號感興趣,它就會使用鏈接(connect)函數,意思是,將想要處理的信號和本身的一個函數(稱爲槽(slot))綁定來處理這個信號。也就是說,當信號發出時,被鏈接的槽函數會自動被回調。這就相似觀察者模式:當發生了感興趣的事件,某一個操做就會被自動觸發。(這裏提一句,Qt 的信號槽使用了額外的處理來實現,並非 GoF 經典的觀察者模式的實現方式。)程序員

  信號和槽是Qt特有的信息傳輸機制,是Qt設計程序的重要基礎,它可讓互不干擾的對象創建一種聯繫。編程

  槽的本質是類的成員函數,其參數能夠是任意類型的。和普通C++成員函數幾乎沒有區別,它能夠是虛函數;也能夠被重載;能夠是公有的、保護的、私有的、也能夠被其餘C++成員函數調用。惟一區別的是:槽能夠與信號鏈接在一塊兒,每當和槽鏈接的信號被髮射的時候,就會調用這個槽。數組

1.1對象樹(子對象動態分配空間不須要釋放)

參考鏈接:https://blog.csdn.net/fzu_dianzi/article/details/6949081app

 

Qt提供了一種機制,可以自動、有效的組織和管理繼承自QObject的Qt對象,這種機制就是對象樹。框架

Qt對象樹在用戶界面編程上是很是有用的。它可以幫助程序員減輕內存泄露的壓力。ide

好比說當應用程序建立了一個具備父窗口部件的對象時,該對象將被加入父窗口部件的孩子列表。當應用程序銷燬父窗口部件時,其下的孩子列表中的對象將被一一刪除。這讓咱們在編程時,可以將主要精力放在系統的業務上,提升編程效率,同時也保證了系統的穩健性。函數

下面筆者將簡單分析對象樹。ui

代碼驗證:this

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QDialog *dlg = new QDialog(0);
    QPushButton *btn = new QPushButton(dlg);
    qDebug() << "dlg = " << dlg;
    qDebug() << "btn = " << btn;
    dlg->exec();
    delete btn;
    qDebug() << "dlg = " << dlg;
    return 0;
}
dlg = QDialog(0x3ea1a0) 
btn = QPushButton(0x3ea228)
/*關閉窗口後,dlg = QDialog(0x3ea1a0)
這說明關閉窗口,不會銷燬該窗口部件,而是將其隱藏起來。
咱們在qDebug() << "dlg = " << dlg;
以後加上
qDebug() << "btn = " << btn;
明顯的,咱們以前已經delete btn,btn指針沒有被賦值爲0,這是編譯器決定的。
執行程序後,必然出現段錯誤。
二、
將程序稍微修改下。*/
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QDialog *dlg = new QDialog(0);
    QPushButton *btn = new QPushButton(dlg);
    qDebug() << "dlg = " << dlg;
    qDebug() << "btn = " << btn;
    dlg->exec();
    delete dlg;
    qDebug() << "btn = " << btn;
    return 0;
}

二、信號和槽

爲了體驗一下信號槽的使用,咱們以一段簡單的代碼說明:spa

Qt5 的書寫方式:(推薦的使用)★★★★★

#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QPushButton button("Quit");
QObject::connect(&button, &QPushButton::clicked,
&app, &QApplication::quit);
    button.show();
    return app.exec();
}

咱們按照前面文章中介紹的在 Qt Creator 中建立工程的方法建立好工程,而後將main()函數修改成上面的代碼。點擊運行,咱們會看到一個按鈕,上面有「Quit」字樣。點擊按鈕,程序退出。

connect()函數最經常使用的通常形式:

connect(sender, signal, receiver, slot);

參數:

 sender:發出信號的對象

 signal:發送對象發出的信號

 receiver:接收信號的對象

 slot:接收對象在接收到信號以後所須要調用的函數

信號槽要求信號和槽的參數一致,所謂一致,是參數類型一致。若是不一致,容許的狀況是,槽函數的參數能夠比信號的少,即使如此,槽函數存在的那些參數的順序也必須和信號的前面幾個一致起來。這是由於,你能夠在槽函數中選擇忽略信號傳來的數據(也就是槽函數的參數比信號的少),可是不能說信號根本沒有這個數據,你就要在槽函數中使用(就是槽函數的參數比信號的多,這是不容許的)。

若是信號槽不符合,或者根本找不到這個信號或者槽函數,好比咱們改爲:

connect(&button, &QPushButton::clicked, &QApplication::quit2);

因爲 QApplication 沒有 quit2 這樣的函數,所以在編譯時會有編譯錯誤:

'quit2' is not a member of QApplication

這樣,使用成員函數指針咱們就不會擔憂在編寫信號槽的時候出現函數錯誤。

Qt4 的書寫方式:

int main(int argc, char *argv[]) 
{ 
        QApplication a(argc, argv); 
        QPushButton *button = new QPushButton("Quit"); 
        connect(button, SIGNAL(clicked()), &a, SLOT(quit())); 
        button->show(); 
        return a.exec(); 
}

這裏使用了SIGNAL和SLOT這兩個宏,將兩個函數名轉換成了字符串。注意到connect()函數的 signal 和 slot 都是接受字符串,一旦出現鏈接不成功的狀況,Qt4是沒有編譯錯誤的(由於一切都是字符串,編譯期是不檢查字符串是否匹配),而是在運行時給出錯誤。這無疑會增長程序的不穩定性。

Qt5在語法上徹底兼容Qt4

小總結: 

  1>. 格式: connect(信號發出者對象(指針), &className::clicked, 信號接收者對象(指針), &classB::slot);
  2>. 標準信號槽的使用:
    connect(sender, &Send::signal, receiver, &Receiver::slot)

三、自定義信號槽

使用connect()可讓咱們鏈接系統提供的信號和槽。可是,Qt 的信號槽機制並不只僅是使用系統提供的那部分,還會容許咱們本身設計本身的信號和槽。

下面咱們看看使用 Qt 的信號槽,實現一個報紙和訂閱者的例子:

有一個報紙類Newspaper,有一個訂閱者類Subscriber。Subscriber能夠訂閱Newspaper。這樣,當Newspaper有了新的內容的時候,Subscriber能夠當即獲得通知。

 

#include <QObject>
 ////////// newspaper.h //////////
class Newspaper : public QObject
{
    Q_OBJECT
public:
    Newspaper(const QString & name) :
        m_name(name)
    {
    }
 
    void send()
    {
        emit newPaper(m_name);
    }
 
signals:
    void newPaper(const QString &name);
 
private:
    QString m_name;
};
 
////////// reader.h //////////
#include <QObject>
#include <QDebug>
 
class Reader : public QObject
{
    Q_OBJECT
public:
    Reader() {}
 
    void receiveNewspaper(const QString & name)
    {
        qDebug() << "Receives Newspaper: " << name;
    }
};
 
////////// main.cpp //////////
#include <QCoreApplication>
 
#include "newspaper.h"
#include "reader.h"
 
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
 
    Newspaper newspaper("Newspaper A");
    Reader reader;
    QObject::connect(&newspaper, &Newspaper::newPaper,
                     &reader,    &Reader::receiveNewspaper);
    newspaper.send();
 
    return app.exec();
}

●首先看Newspaper這個類。這個類繼承了QObject類。只有繼承了QObject類的類,才具備信號槽的能力因此,爲了使用信號槽,必須繼承QObject。凡是QObject類(不論是直接子類仍是間接子類),都應該在第一行代碼寫上Q_OBJECT不論是不是使用信號槽,都應該添加這個宏。這個宏的展開將爲咱們的類提供信號槽機制、國際化機制以及 Qt 提供的不基於 C++ RTTI 的反射能力。

 

● Newspaper類的 public 和 private 代碼塊都比較簡單,只不過它新加了一個 signals。signals 塊所列出的,就是該類的信號。信號就是一個個的函數名,返回值是 void(由於沒法得到信號的返回值,因此也就無需返回任何值),參數是該類須要讓外界知道的數據。信號做爲函數名,不須要在 cpp 函數中添加任何實現。

 

●Newspaper類的send()函數比較簡單,只有一個語句emit newPaper(m_name);。emit 是 Qt 對 C++ 的擴展,是一個關鍵字(其實也是一個宏)。emit 的含義是發出,也就是發出newPaper()信號。感興趣的接收者會關注這個信號,可能還須要知道是哪份報紙發出的信號?因此,咱們將實際的報紙名字m_name當作參數傳給這個信號。當接收者鏈接這個信號時,就能夠經過槽函數得到實際值。這樣就完成了數據從發出者到接收者的一個轉移。

 

● Reader類更簡單。由於這個類須要接受信號,因此咱們將其繼承了QObject,而且添加了Q_OBJECT宏。後面則是默認構造函數和一個普通的成員函數。Qt 5 中,任何成員函數、static 函數、全局函數和 Lambda 表達式均可以做爲槽函數。與信號函數不一樣,槽函數必須本身完成實現代碼。槽函數就是普通的成員函數,所以做爲成員函數,也會受到 public、private 等訪問控制符的影響。(若是信號是 private 的,這個信號就不能在類的外面鏈接,也就沒有任何意義。)

3.1自定義信號槽須要注意的事項

●發送者和接收者都須要是QObject的子類(固然,槽函數是全局函數、Lambda 表達式等無需接收者的時候除外);

●使用 signals 標記信號函數,信號是一個函數聲明,返回 void,不須要實現函數代碼;

●槽函數是普通的成員函數,做爲成員函數,會受到 public、private、protected 的影響;

●使用 emit 在恰當的位置發送信號;

●使用QObject::connect()函數鏈接信號和槽。

●任何成員函數、static 函數、全局函數和 Lambda 表達式均可以做爲槽函數

3.2信號槽的更多用法

 一個信號能夠和多個槽相連

  若是是這種狀況,這些槽會一個接一個的被調用,可是它們的調用順序是不肯定的。

多個信號能夠鏈接到一個槽

  只要任意一個信號發出,這個槽就會被調用

●一個信號能夠鏈接到另外的一個信號

  當第一個信號發出時,第二個信號被髮出。除此以外,這種信號-信號的形式和信號-槽的形式沒有什麼區別。

●槽能夠被取消連接

  這種狀況並不常常出現,由於當一個對象delete以後,Qt自動取消全部鏈接到這個對象上面的槽。

●使用Lambda 表達式

  在使用 Qt 5 的時候,可以支持 Qt 5 的編譯器都是支持 Lambda 表達式的。

  咱們的代碼能夠寫成下面這樣:

QObject::connect(&newspaper, static_cast<void (Newspaper:: *)
(const QString &)>(&Newspaper::newPaper),
[=](const QString &name) 
{ /* Your code here. */ }
);

在鏈接信號和槽的時候,槽函數可使用Lambda表達式的方式進行處理。

四、Lambda表達式

C++11中的Lambda表達式用於定義並建立匿名的函數對象,以簡化編程工做。首先看一下Lambda表達式的基本構成:

                                              [函數對象參數](操做符重載函數參數)mutable或exception ->返回值{函數體}

 ①函數對象參數;

  [],標識一個Lambda的開始,這部分必須存在,不能省略。函數對象參數是傳遞給編譯器自動生成的函數對象類的構造函數的。函數對象參數只能使用那些到定義Lambda爲止時Lambda所在做用範圍內可見的局部變量(包括Lambda所在類的this)。函數對象參數有如下形式:

     ▲空。沒有使用任何函數對象參數。

     ▲=。函數體內可使用Lambda所在做用範圍內全部可見的局部變量(包括Lambda所在類的this),而且是值傳遞方式(至關於編譯器自動爲咱們按值傳遞了全部局部變量)。

     ▲&。函數體內可使用Lambda所在做用範圍內全部可見的局部變量(包括Lambda所在類的this),而且是引用傳遞方式(至關於編譯器自動爲咱們按引用傳遞了全部局部變量)。

     ▲ this。函數體內可使用Lambda所在類中的成員變量。

     ▲ a。將a按值進行傳遞。按值進行傳遞時,函數體內不能修改傳遞進來的a的拷貝,由於默認狀況下函數是const的。要修改傳遞進來的a的拷貝,能夠添加mutable修飾符。

     ▲ &a。將a按引用進行傳遞。

       ▲ a, &b。將a按值進行傳遞,b按引用進行傳遞。

     ▲ =,&a, &b。除a和b按引用進行傳遞外,其餘參數都按值進行傳遞。

     ▲ &, a, b。除a和b按值進行傳遞外,其餘參數都按引用進行傳遞。

int m = 0, n = 0;
[=] (int a) mutable { m = ++n + a; }(4);
      [&] (int a) { m = ++n + a; }(4);

      [=,&m] (int a) mutable { m = ++n + a; }(4);
      [&,m] (int a) mutable { m = ++n + a; }(4);

      [m,n] (int a) mutable { m = ++n + a; }(4);
      [&m,&n] (int a) { m = ++n + a; }(4);

② 操做符重載函數參數;

標識重載的()操做符的參數,沒有參數時,這部分能夠省略。參數能夠經過按值(如:(a,b))和按引用(如:(&a,&b))兩種方式進行傳遞。

③ 可修改標示符;

mutable聲明,這部分能夠省略。按值傳遞函數對象參數時,加上mutable修飾符後,能夠修改按值傳遞進來的拷貝(注意是能修改拷貝,而不是值自己)。

④ 錯誤拋出標示符;

exception聲明,這部分也能夠省略。exception聲明用於指定函數拋出的異常,如拋出整數類型的異常,可使用throw(int)

⑤ 函數返回值;

->返回值類型,標識函數返回值的類型,當返回值爲void,或者函數體中只有一處return的地方(此時編譯器能夠自動推斷出返回值類型)時,這部分能夠省略。

⑥ 是函數體;

{},標識函數的實現,這部分不能省略,但函數體能夠爲空。

總結:

 

 

案例代碼:

mainwidget.h

#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include <QPushButton>
#include "subwidget.h" //子窗口頭文件

class MainWidget : public QWidget
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = 0);
    ~MainWidget();

public slots:
    void mySlot();
    void changeWin();
    void dealSub();
    void dealSlot(int, QString);

private:
    QPushButton b1;
    QPushButton *b2;
    QPushButton b3;

    SubWidget subWin;
};

#endif // MAINWIDGET_H

subwidget.h

#ifndef SUBWIDGET_H
#define SUBWIDGET_H

#include <QWidget>
#include <QPushButton>

class SubWidget : public QWidget
{
    Q_OBJECT
public:
    explicit SubWidget(QWidget *parent = 0);

    void sendSlot();

signals:
     /* 信號必須有signals關鍵字來聲明
      * 信號沒有返回值,但能夠有參數
      * 信號就是函數的聲明,只需聲明,無需定義
      * 使用:emit mySignal();
      * 信號能夠重載
     */

    void mySignal();
    void mySignal(int, QString);

public slots:

private:
    QPushButton b;
};

#endif // SUBWIDGET_H

main.cpp

#include "mainwidget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWidget w;//執行MainWidget的構造函數
    w.show();

    return a.exec();
}

mainwidget.cpp

#include "mainwidget.h"
#include <QPushButton>
#include <QDebug> //打印

MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
    b1.setParent(this);
    b1.setText("close");
    b1.move(100, 100);

    b2 = new QPushButton(this);
    b2->setText("abc");

    connect(&b1, &QPushButton::pressed, this, &MainWidget::close);
    /* &b1: 信號發出者,指針類型
     * &QPushButton::pressed:處理的信號,  &發送者的類名::信號名字
     * this: 信號接收者
     * &MainWidget::close: 槽函數,信號處理函數  &接收的類名::槽函數名字
     * 發送-處理-接收-處理
    */

    /* 自定義槽,普通函數的用法
     * Qt5:任意的成員函數,普通全局函數,靜態函數
     * 槽函數須要和信號一致(參數,返回值)
     * 因爲信號都是沒有返回值,因此,槽函數必定沒有返回值
     */
    connect(b2, &QPushButton::released, this, &MainWidget::mySlot);

    connect(b2, &QPushButton::released, &b1, &QPushButton::hide);

    /* 信號:短信
     * 槽函數:接收短信的手機
     */

    setWindowTitle("老大");
    //this->setWindowTitle("老大");//等價同上

    b3.setParent(this);
    b3.setText("切換到子窗口");
    b3.move(50, 50);

    //顯示子窗口
    //subWin.show();

    connect(&b3, &QPushButton::released, this, &MainWidget::changeWin);


    //處理子窗口的信號
//    void (SubWidget::*funSignal)() = &SubWidget::mySignal;
//    connect(&subWin, funSignal, this, &MainWidget::dealSub);

//     void (SubWidget::*testSignal)(int, QString) = &SubWidget::mySignal;
//    connect(&subWin, testSignal, this, &MainWidget::dealSlot);

    //Qt4信號鏈接
    //Qt4槽函數必須有slots關鍵字來修飾
    connect(&subWin, SIGNAL(mySignal()), this, SLOT(dealSub()) );

    connect(&subWin, SIGNAL(mySignal(int,QString)),
            this, SLOT(dealSlot(int,QString)) );
    //缺點: SIGNAL SLOT 將函數名字 -> 字符串  不進行錯誤檢查

    //Lambda表達式, 匿名函數對象
    //C++11增長的新特性, 項目文件: CONFIG += C++11
    //Qt配合信號一塊兒使用,很是方便

    QPushButton *b4 = new QPushButton(this);
    b4->setText("Lambda表達式");
    b4->move(150, 150);
    int a = 10, b = 100;
    connect(b4, &QPushButton::clicked,
            // = :把外部全部局部變量、類中全部成員以值傳遞方式
            // this: 類中全部成員以值傳遞方式
            // & : 把外部全部局部變量, 引用符號
            [=](bool isCheck)
            {
                qDebug() << isCheck;
            }


            );


    resize(400, 300);
}

void MainWidget::dealSlot(int a, QString str)
{
    // str.toUtf8() -> 字節數組QByteArray
    // ……data()  -> QByteArray -> char *
    qDebug() << a << str.toUtf8().data();
}

void MainWidget::mySlot()
{
    b2->setText("123");
}

void MainWidget::changeWin()
{
    //子窗口顯示
    subWin.show();
    //本窗口隱藏
    this->hide();
}


void MainWidget::dealSub()
{
    //子窗口隱藏
    subWin.hide();
    //本窗口顯示
    show();
}

MainWidget::~MainWidget()
{

}

subwidget.cpp

#include "subwidget.h"

SubWidget::SubWidget(QWidget *parent) : QWidget(parent)
{
    this->setWindowTitle("小弟");
    b.setParent(this);
    b.setText("切換到主窗口");

    connect(&b, &QPushButton::clicked, this, &SubWidget::sendSlot);

    resize(400, 300);
}

void SubWidget::sendSlot()
{
    emit mySignal();
    emit mySignal(250, "我是子窗口");
}

SingnalAndSlot.pro

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = 03_SignalAndSlot
TEMPLATE = app


SOURCES += main.cpp\
        mainwidget.cpp \
    subwidget.cpp

HEADERS  += mainwidget.h \
    subwidget.h

CONFIG += C++11

 以上資料來源於互聯網和本身的看法。若有雷同,不勝榮幸。加入咱們的QQ羣,裏面有資料,能夠相互交流

相關文章
相關標籤/搜索