天天看點

Qt 之 emit、signals、slot的使用

作者:Qt技術開發老jie

背景

ref : https://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/index.html

信号和槽機制是 QT 的核心機制,要精通 QT 程式設計就必須對信号和槽有所了解。

信号與槽和設計模式中的觀察者模式很類似。當某個事件發生之後,比如,按鈕檢測到自己被點選了一下,它就會發出一個信号(signal)。這種發出是沒有目的的,類似廣播。如果有對象對這個信号感興趣,它就會事先使用連接配接(connect)函數,意思是,用自己的一個函數(成為槽(slot))進行注冊以便于處理這個信号。也就是說,當信号發出時,被連接配接的槽函數會自動被回調。這就類似觀察者模式:當發生了感興趣的事件,某一個操作就會被自動觸發。(這裡提一句,Qt 的信号槽使用了額外的處理來實作,并不是 GoF 經典的觀察者模式的實作方式。)

概念

信号和槽是一種進階接口,應用于對象之間的通信,它是 QT 的核心特性,也是 QT 差別于其它工具包的重要地方。信号和槽是 QT 自行定義的一種通信機制,它獨立于标準的 C/C++ 語言。

是以要正确的處理信号和槽,必須借助一個稱為 moc(Meta Object Compiler)的 QT 工具,該工具是一個 C++ 預處理程式,它為高層次的事件處理自動生成所需要的附加代碼。

Qt 不是使用的“标準的” C++ 語言,而是對其進行了一定程度的“擴充”。是以有人會覺得 Qt 的程式編譯速度慢,這主要是因為在 Qt 将源代碼交給标準 C++ 編譯器,如 gcc 之前,需要事先将這些擴充的文法去除掉。完成這一操作的就是 moc。

換句話說,QT 的拓展文法就是由其實作的。

在我們所熟知的很多 GUI 工具包中,視窗小部件 (widget) 都有一個回調函數用于響應它們能觸發的每個動作,這個回調函數通常是一個指向某個函數的指針。但是,在 QT 中信号和槽取代了這些淩亂的函數指針,使得我們編寫這些通信程式更為簡潔明了。信号和槽能攜帶任意數量和任意類型的參數,他們是類型完全安全的,不會像回調函數那樣産生 core dumps。

所有從 QObject 或其子類 ( 例如 Qwidget) 派生的類都能夠包含信号和槽。

  • 當對象改變其狀态時,信号就由該對象發射 (emit) 出去,這就是對象所要做的全部事情,它不知道另一端是誰在接收這個信号。這就是真正的資訊封裝,它確定對象被當作一個真正的軟體元件來使用。
  • 槽(slot)用于接收信号,但它們是普通的對象成員函數。一個槽并不知道是否有任何信号與自己相連接配接。而且,對象并不了解具體的通信機制。

你可以将很多信号與單個的槽進行連接配接,也可以将單個的信号與很多的槽進行連接配接,甚至于将一個信号與另外一個信号相連接配接也是可能的,這時無論第一個信号什麼時候發射,系統都将立刻發射第二個信号。

總之,信号與槽構造了一個強大的部件程式設計機制。

信号

當某個信号對其客戶或所有者發生的内部狀态發生改變,信号被一個對象發射。隻有定義過這個信号的類及其派生類能夠發射這個信号。

當一個信号被發射時,與其相關聯的槽将被立刻執行,就象一個正常的函數調用一樣。信号-槽機制完全獨立于任何 GUI 事件循環。隻有當所有的槽傳回以後發射函數(emit)才傳回。

如果存在多個槽與某個信号相關聯,那麼,當這個信号被發射時,這些槽将會一個接一個地執行,但是它們執行的順序将會是随機的、不确定的,我們不能人為地指定哪個先執行、哪 個後執行。

信号的聲明是在頭檔案中進行的,QT 的 signals 關鍵字指出進入了信号聲明區,随後即可聲明自己的信号。例如,下面定義了三個信号:

signals:
    void mySignal();
    void mySignal(int x);
    void mySignalParam(int x,int y);
           

在上面的定義中,signals 是 QT 的關鍵字,而非 C/C++ 的。

接下來的一行 void mySignal() 定義了信号 mySignal,這個信号沒有攜帶參數;

接下來的一行 void mySignal(int x) 定義 了重名信号 mySignal,但是它攜帶一個整形參數,這有點類似于 C++ 中的虛函數。

從形式上講信号的聲明與普通的 C++ 函數是一樣的,但是信号卻沒有函數體定義,另外,信号的傳回類型都是 void,不要指望能從信号傳回什麼有用資訊。信号由 moc 自動産生,它們不應該在 .cpp 檔案中實作。

槽是普通的 C++ 成員函數,可以被正常調用,它們唯一的特殊性就是很多信号可以與其相關聯。當與其關聯的信号被發射時,這個槽就會被調用。槽可以有參數,但槽的參數不能有預設值。既然槽是普通的成員函數,是以與其它的函數一樣,它們也有存取權限。槽的存取權限決定了誰能夠與其相關聯。同普通的 C++ 成員函數一樣,槽函數也分為三種類型,即 public slots、private slots 和 protected slots。

public slots:在這個區内聲明的槽意味着任何對象都可将信号與之相連接配接。這對于元件程式設計非常有用,你可以建立彼此互不了解的對象,将它們的信号與槽進行連接配接以便資訊能夠正确的傳遞。

protected slots:在這個區内聲明的槽意味着目前類及其子類可以将信号與之相連接配接。這适用于那些槽,它們是類實作的一部分,但是其界面接口卻面向外部。

private slots:在這個區内聲明的槽意味着隻有類自己可以将信号與之相連接配接。這适用于聯系非常緊密的類。

槽也能夠聲明為虛函數,這也是非常有用的。

槽的聲明也是在頭檔案中進行的。例如,下面聲明了三個槽:

public slots:
   void mySlot();
   void mySlot(int x);
   void mySignalParam(int x,int y);
           

信号與槽的關聯

connect

通過調用 QObject 對象的 connect 函數來将某個對象的信号與另外一個對象的槽函數相關聯,這樣當發射者發射信号時,接收者的槽函數将被調用。該函數的定義如下:

bool QObject::connect ( const QObject * sender, const char * signal,
   const QObject * receiver, const char * member ) [static]
           

這個函數的作用就是将發射者 sender 對象中的信号 signal 與接收者 receiver 中的 member 槽函數聯系起來。當指定信号 signal 時必須使用 QT 的宏 SIGNAL(),當指定槽函數時必須使用宏 SLOT()。如果發射者與接收者屬于同一個對象的話,那麼在 connect 調用中接收者參數可以省略。

例如,下面定義了兩個對象:标簽對象 label 和滾動條對象 scroll,并将 valueChanged() 信号與标簽對象的 setNum() 相關聯,另外信号還攜帶了一個整形參數,這樣标簽總是顯示滾動條所處位置的值。

QLabel     *label  = new QLabel;
QScrollBar *scroll = new QScrollBar;
QObject::connect( scroll, SIGNAL(valueChanged(int)),
                  label,  SLOT(setNum(int)) );
           

一個信号甚至能夠與另一個信号相關聯,看下面的例子:

class MyWidget : public QWidget
{
public:
    MyWidget();
...
signals:
    void aSignal();
...
private:
...
    QPushButton *aButton;
};
MyWidget::MyWidget()
{
    aButton = new QPushButton( this );
    connect( aButton, SIGNAL(clicked()), SIGNAL(aSignal()) );
}
           

在上面的構造函數中,MyWidget 建立了一個私有的按鈕 aButton,按鈕的單擊事件産生的信号 clicked() 與另外一個信号 aSignal() 進行了關聯。

這樣一來,當信号 clicked() 被發射時,信号 aSignal() 也接着被發射。當然,你也可以直接将單擊事件與某個私有的槽函數相關聯,然後在槽中發射 aSignal() 信号,這樣的話似乎有點多餘。

disconnect

當信号與槽沒有必要繼續保持關聯時,我們可以使用 disconnect 函數來斷開連接配接。其定義如下:

bool QObject::disconnect ( const QObject * sender, const char * signal,
   const Object * receiver, const char * member ) [static]
           

這個函數斷開發射者中的信号與接收者中的槽函數之間的關聯。

有三種情況必須使用 disconnect() 函數:

1、斷開與某個對象相關聯的任何對象。這似乎有點不可了解,事實上,當我們在某個對象中定義了一個或者多個信号,這些信号與另外若幹個對象中的槽相關聯,如果我們要切斷這些關聯的話,就可以利用這個方法,非常之簡潔。

disconnect( myObject, 0, 0, 0 )
或者
myObject->disconnect()
           

2、斷開與某個特定信号的任何關聯。

disconnect( myObject, SIGNAL(mySignal()), 0, 0 )
或者
 myObject->disconnect( SIGNAL(mySignal()) )
           

3、斷開兩個對象之間的關聯。

disconnect( myObject, 0, myReceiver, 0 )
或者
myObject->disconnect(  myReceiver )
           

在 disconnect 函數中 0 可以用作一個通配符,分别表示任何信号、任何接收對象、接收對象中的任何槽函數。但是發射者 sender 不能為 0,其它三個參數的值可以等于 0。

元對象工具

元對象編譯器 moc(meta object compiler)對 C++ 檔案中的類聲明進行分析并産生用于初始化元對象的 C++ 代碼,元對象包含全部信号和槽的名字以及指向這些函數的指針。

moc 讀 C++ 源檔案,如果發現有 Q_OBJECT 宏聲明的類,它就會生成另外一個 C++ 源檔案,這個新生成的檔案中包含有該類的元對象代碼。例如,假設我們有一個頭檔案 mysignal.h,在這個檔案中包含有信号或槽的聲明,那麼在編譯之前 moc 工具就會根據該檔案自動生成一個名為 mysignal.moc.h 的 C++ 源檔案并将其送出給編譯器;類似地,對應于 mysignal.cpp 檔案 moc 工具将自動生成一個名為 mysignal.moc.cpp 檔案送出給編譯器。

元對象代碼是 signal/slot 機制所必須的。用 moc 産生的 C++ 源檔案必須與類實作一起進行編譯和連接配接,或者用 #include 語句将其包含到類的源檔案中。moc 并不擴充 #include 或者 #define 宏定義 , 它隻是簡單的跳過所遇到的任何預處理指令。

程式樣例

這裡給出了一個簡單的樣例程式,程式中定義了三個信号、三個槽函數,然後将信号與槽進行了關聯,每個槽函數隻是簡單的彈出一個對話框視窗。

信号和槽函數的聲明一般位于頭檔案中,同時在類聲明的開始位置必須加上 Q_OBJECT 語句,這條語句是不可缺少的,它将告訴編譯器在編譯之前必須先應用 moc 工具進行擴充。關鍵字 signals 指出随後開始信号的聲明,這裡 signals 用的是複數形式而非單數,siganls 沒有 public、private、protected 等屬性,這點不同于 slots。另外,signals、slots 關鍵字是 QT 自己定義的,不是 C++ 中的關鍵字。

信号的聲明類似于函數的聲明而非變量的聲明,左邊要有類型,右邊要有括号,如果要向槽中傳遞參數的話,在括号中指定每個形式參數的類型,當然,形式參數的個數可以多于一個。

關鍵字 slots 指出随後開始槽的聲明,這裡 slots 用的也是複數形式。

槽的聲明與普通函數的聲明一樣,可以攜帶零或多個形式參數。既然信号的聲明類似于普通 C++ 函數的聲明,那麼,信号也可采用 C++ 中虛函數的形式進行聲明,即同名但參數不同。例如,第一次定義的 void mySignal() 沒有帶參數,而第二次定義的卻帶有參數,從這裡我們可以看到 QT 的信号機制是非常靈活的。

信号與槽之間的聯系必須事先用 connect 函數進行指定。如果要斷開二者之間的聯系,可以使用函數 disconnect。

//tsignal.h
...
class TsignalApp:public QMainWindow
{
    Q_OBJECT
    ...
    // 信号聲明區
    signals:
        // 聲明信号 mySignal()
        void mySignal();
        // 聲明信号 mySignal(int)
        void mySignal(int x);
        // 聲明信号 mySignalParam(int,int)
        void mySignalParam(int x,int y);
    // 槽聲明區
    public slots:
        // 聲明槽函數 mySlot()
        void mySlot();
        // 聲明槽函數 mySlot(int)
        void mySlot(int x);
        // 聲明槽函數 mySignalParam (int,int)
        void mySignalParam(int x,int y);
}
...
//tsignal.cpp
...
TsignalApp::TsignalApp()
{
    ...
    // 将信号 mySignal() 與槽 mySlot() 相關聯
    connect(this,SIGNAL(mySignal()),SLOT(mySlot()));
    // 将信号 mySignal(int) 與槽 mySlot(int) 相關聯
    connect(this,SIGNAL(mySignal(int)),SLOT(mySlot(int)));
    // 将信号 mySignalParam(int,int) 與槽 mySlotParam(int,int) 相關聯
    connect(this,SIGNAL(mySignalParam(int,int)),SLOT(mySlotParam(int,int)));
}
// 定義槽函數 mySlot()
void TsignalApp::mySlot()
{
    QMessageBox::about(this,"Tsignal", "This is a signal/slot sample without parameter.");
}
// 定義槽函數 mySlot(int)
void TsignalApp::mySlot(int x)
{
    QMessageBox::about(this,"Tsignal", "This is a signal/slot sample with one parameter.");
}
// 定義槽函數 mySlotParam(int,int)
void TsignalApp::mySlotParam(int x,int y)
{
    char s[256];
    sprintf(s,"x:%d y:%d",x,y);
    QMessageBox::about(this,"Tsignal", s);
}
void TsignalApp::slotFileNew()
{
    // 發射信号 mySignal()
    emit mySignal();
    // 發射信号 mySignal(int)
    emit mySignal(5);
    // 發射信号 mySignalParam(5,100)
    emit mySignalParam(5,100);
}
           

應注意的問題

信号與槽機制是比較靈活的,但有些局限性我們必須了解,這樣在實際的使用過程中做到有的放矢,避免産生一些錯誤。下面就介紹一下這方面的情況。

1 .信号與槽的效率是非常高的,但是同真正的回調函數比較起來,由于增加了靈活性,是以在速度上還是有所損失,當然這種損失相對來說是比較小的,通過在一台 i586-133 的機器上測試是 10 微秒(運作 Linux),可見這種機制所提供的簡潔性、靈活性還是值得的。但如果我們要追求高效率的話,比如在實時系統中就要盡可能的少用這種機制。

2 .信号與槽機制與普通函數的調用一樣,如果使用不當的話,在程式執行時也有可能産生死循環。是以,在定義槽函數時一定要注意避免間接形成無限循環,即在槽中再次發射所接收到的同樣信号。例如 , 在前面給出的例子中如果在 mySlot() 槽函數中加上語句 emit mySignal() 即可形成死循環。

3 .如果一個信号與多個槽相聯系的話,那麼,當這個信号被發射時,與之相關的槽被激活的順序将是随機的。

4)宏定義不能用在 signal 和 slot 的參數中。

既然 moc 工具不擴充 #define,是以,在 signals 和 slots 中攜帶參數的宏就不能正确地工作,如果不帶參數是可以的。例如,下面的例子中将帶有參數的宏 SIGNEDNESS(a) 作為信号的參數是不合文法的:

#ifdef ultrix
#define SIGNEDNESS(a) unsigned a
#else
#define SIGNEDNESS(a) a
#endif
class Whatever : public QObject
{
[...]
signals:
    void someSignal( SIGNEDNESS(a) );
[...]
};
           

5)構造函數不能用在 signals 或者 slots 聲明區域内。

的确,将一個構造函數放在 signals 或者 slots 區内有點不可了解,無論如何,不能将它們放在 private slots、protected slots 或者 public slots 區内。下面的用法是不合文法要求的:

class SomeClass : public QObject
{
    Q_OBJECT
public slots:
    SomeClass( QObject *parent, const char *name )
        : QObject( parent, name ) {}  // 在槽聲明區内聲明構造函數不合文法
[...]
};
           

6)函數指針不能作為信号或槽的參數。

例如,下面的例子中将 void (applyFunction)(QList, void*) 作為參數是不合文法的:

class someClass : public QObject
{
    Q_OBJECT
[...]
public slots:
    void apply(void (*applyFunction)(QList*, void*), char*); // 不合文法
};
           

你可以采用下面的方法繞過這個限制:

typedef void (*ApplyFunctionType)(QList*, void*);
class someClass : public QObject
{
    Q_OBJECT
[...]
public slots:
    void apply( ApplyFunctionType, char *);
};
           

7)信号與槽不能有預設參數。

既然 signal->slot 綁定是發生在運作時刻,那麼,從概念上講使用預設參數是困難的。下面的用法是不合理的:

class SomeClass : public QObject
{
    Q_OBJECT
public slots:
    void someSlot(int x=100); // 将 x 的預設值定義成 100,在槽函數聲明中使用是錯誤的
};
           

8)信号與槽也不能攜帶模闆類參數。

如果将信号、槽聲明為模闆類參數的話,即使 moc 工具不報告錯誤,也不可能得到預期的結果。 例如,下面的例子中當信号發射時,槽函數不會被正确調用:

[...]
public slots:
    void MyWidget::setLocation (pair<int,int> location);
[...]
public signals:
    void MyObject::moved (pair<int,int> location);
           

但是,你可以使用 typedef 語句來繞過這個限制。如下所示:

typedef pair<int,int> IntPair;
[...]
public slots:
    void MyWidget::setLocation (IntPair location);
[...]
public signals:
    void MyObject::moved (IntPair location);
           

這樣使用的話,你就可以得到正确的結果。

9)嵌套的類不能位于信号或槽區域内,也不能有信号或者槽。

例如,下面的例子中,在 class B 中聲明槽 b() 是不合文法的,在信号區内聲明槽 b() 也是不合文法的。

class A
{
    Q_OBJECT
public:
    class B
{
    public slots:   // 在嵌套類中聲明槽不合文法
        void b();
    [....]
    };
signals:
    class B
{
    // 在信号區内聲明嵌套類不合文法
    void b();
    [....]
    }:
};
           

10)友元聲明不能位于信号或者槽聲明區内。

相反,它們應該在普通 C++ 的 private、protected 或者 public 區内進行聲明。下面的例子是不合文法規範的:

class someClass : public QObject
{
    Q_OBJECT
[...]
signals: // 信号定義區
    friend class ClassTemplate; // 此處定義不合文法
};
           

自定義信号槽執行個體

原文來自:https://www.devbean.net/2012/08/qt-study-road-2-custom-signal-slot/

使用connect()可以讓我們連接配接系統提供的信号和槽。但是,Qt 的信号槽機制并不僅僅是使用系統提供的那部分,還會允許我們自己設計自己的信号和槽。這也是 Qt 架構的設計思路之一,用于我們設計解耦的程式。本節将講解如何在自己的程式中自定義信号槽。

信号槽不是 GUI 子產品提供的,而是 Qt 核心特性之一。是以,我們可以在普通的控制台程式使用信号槽。

經典的觀察者模式在講解舉例的時候通常會舉報紙和訂閱者的例子。有一個報紙類Newspaper,有一個訂閱者類Subscriber。Subscriber可以訂閱Newspaper。這樣,當Newspaper有了新的内容的時候,Subscriber可以立即得到通知。在這個例子中,觀察者是Subscriber,被觀察者是Newspaper。在經典的實作代碼中,觀察者會将自身注冊到被觀察者的一個容器中(比如subscriber.registerTo(newspaper))。被觀察者發生了任何變化的時候,會主動周遊這個容器,依次通知各個觀察者(newspaper.notifyAllSubscribers())。

我們來看這個例程,添加2個頭檔案

  • newspaper.h
#ifndef NEWSPAPER_H
#define NEWSPAPER_H
#include <QCoreApplication>
#include <QObject>
#include <QDebug>
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;
};
#endif // NEWSPAPER_H

           
  • reader.h
#ifndef READER_H
#define READER_H
#include <QCoreApplication>
#include <QObject>
#include <QDebug>

class Reader : public QObject
{
    Q_OBJECT
public:
    Reader() {}

    void receiveNewspaper(const QString & name)
    {
        qDebug() << "Receives Newspaper: " << name;
    }
};
#endif // READER_H
           
  • main.cpp
#include <QCoreApplication>
#include <QObject>
#include <QDebug>

#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.h 和 reader.h 都放在 main.cpp 的main()函數之前嗎?答案是,可以,但是需要有額外的操作。

解決方法:我們手動調用 moc 工具處理 main.cpp,并且将 main.cpp 中的#include "newspaper.h"改為#include "moc_newspaper.h"就可以了。不過,這是相當繁瑣的步驟,為了避免這樣修改,我們還是将其放在頭檔案中。許多初學者會遇到莫名其妙的錯誤,一加上Q_OBJECT就出錯,很大一部分是因為沒有注意到這個宏應該放在頭檔案中。

解析

QObject 關鍵字

首先看Newspaper這個類。這個類繼承了QObject類。隻有繼承了QObject類的類,才具有信号槽的能力。是以,為了使用信号槽,必須繼承QObject。凡是QObject類(不管是直接子類還是間接子類),都應該在第一行代碼寫上Q_OBJECT。不管是不是使用信号槽,都應該添加這個宏。這個宏的展開将為我們的類提供信号槽機制、國際化機制以及 Qt 提供的不基于 C++ RTTI 的反射能力。是以,如果你覺得你的類不需要使用信号槽,就不添加這個宏,就是錯誤的。其它很多操作都會依賴于這個宏。

注意,這個宏将由 moc(我們會在後面章節中介紹 moc。這裡你可以将其了解為一種預處理器,是比 C++ 預處理器更早執行的預處理器。) 做特殊處理,不僅僅是宏展開這麼簡單。moc 會讀取标記了 Q_OBJECT的頭檔案,生成以 moc_為字首的檔案,比如 newspaper.h将生成 moc_newspaper.cpp。你可以到建構目錄檢視這個檔案,看看到底增加了什麼内容。注意,由于 moc 隻處理頭檔案中的标記了Q_OBJECT的類聲明,不會處理 cpp 檔案中的類似聲明。

signals 關鍵字

Newspaper類的 public 和 private 代碼塊都比較簡單,隻不過它新加了一個 signals。signals 塊所列出的,就是該類的信号。信号就是一個個的函數名,傳回值是 void(因為無法獲得信号的傳回值,是以也就無需傳回任何值),參數是該類需要讓外界知道的資料。信号作為函數名,不需要在 cpp 函數中添加任何實作(我們曾經說過,Qt 程式能夠使用普通的 make 進行編譯。沒有實作的函數名怎麼會通過編譯?原因還是在 moc,moc 會幫我們實作信号函數所需要的函數體,是以說,moc 并不是單純的将 Q_OBJECT 展開,而是做了很多額外的操作)。

emit 關鍵字

Newspaper類的send()函數比較簡單,隻有一個語句emit newPaper(m_name);。emit 是 Qt 對 C++ 的擴充,是一個關鍵字(其實也是一個宏)。emit 的含義是發出,也就是發出newPaper()信号。感興趣的接收者會關注這個信号,可能還需要知道是哪份報紙發出的信号?是以,我們将實際的報紙名字m_name當做參數傳給這個信号。當接收者連接配接這個信号時,就可以通過槽函數獲得實際值。這樣就完成了資料從發出者到接收者的一個轉移。

slot 槽函數

Reader類更簡單。因為這個類需要接受信号,是以我們将其繼承了QObject,并且添加了Q_OBJECT宏。後面則是預設構造函數和一個普通的成員函數。Qt 5 中,任何成員函數、static 函數、全局函數和 Lambda 表達式都可以作為槽函數。與信号函數不同,槽函數必須自己完成實作代碼。

槽函數就是普通的成員函數,是以作為成員函數,也會受到 public、private 等通路控制符的影響。(我們沒有說信号也會受此影響,事實上,如果信号是 private 的,這個信号就不能在類的外面連接配接,也就沒有任何意義。)

connect

main()函數中,我們首先建立了Newspaper和Reader兩個對象,然後使用QObject::connect()函數。将信号-槽連接配接起來。然後我們調用Newspaper的send()函數。這個函數隻有一個語句:發出信号。

由于我們的連接配接,當這個信号發出時,自動調用 reader 的槽函數,列印出語句。

自定義信号槽需要注意的事項:

  • 發送者和接收者都需要是QObject的子類(當然,槽函數是全局函數、Lambda 表達式等無需接收者的時候除外);
  • 使用 signals 标記信号函數,信号是一個函數聲明,傳回 void,不需要實作函數代碼;
  • 槽函數是普通的成員函數,作為成員函數,會受到 public、private、protected 的影響;
  • 使用 emit 在恰當的位置發送信号;
  • 使用QObject::connect()函數連接配接信号和槽。