上一節咱們詳細分析了connect()
函數。使用connect()
可讓咱們鏈接系統提供的信號和槽。可是,Qt 的信號槽機制並不只僅是使用系統提供的那部分,還會容許咱們本身設計本身的信號和槽。這也是 Qt 框架的設計思路之一,用於咱們設計解耦的程序。本節將講解如何在本身的程序中自定義信號槽。app
信號槽不是 GUI 模塊提供的,而是 Qt 核心特性之一。所以,咱們能夠在普通的控制檯程序使用信號槽。框架
經典的觀察者模式在講解舉例的時候一般會舉報紙和訂閱者的例子。有一個報紙類Newspaper
,有一個訂閱者類Subscriber
。Subscriber
能夠訂閱Newspaper
。這樣,當Newspaper
有了新的內容的時候,Subscriber
能夠當即獲得通知。在這個例子中,觀察者是Subscriber
,被觀察者是Newspaper
。在經典的實現代碼中,觀察者會將自身註冊到被觀察者的一個容器中(好比subscriber.registerTo(newspaper)
)。被觀察者發生了任何變化的時候,會主動遍歷這個容器,依次通知各個觀察者(newspaper.notifyAllSubscribers()
)。函數
下面咱們看看使用 Qt 的信號槽,如何實現上述觀察者模式。注意,這裏咱們僅僅是使用這個案例,咱們的代碼並非去實現一個經典的觀察者模式。也就是說,咱們使用 Qt 的信號槽機制來得到一樣的效果。工具
//!!! Qt5 // newspaper.h #include <QObject> 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(); }
咱們運行上面的程序時,會看到終端輸出 Receives Newspaper: Newspaper A 這樣的字樣。spa
下面咱們來分析下自定義信號槽的代碼。.net
首先看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 文件中的相似聲明。所以,若是咱們的Newspaper
和Reader
類位於 main.cpp 中,是沒法獲得 moc 的處理的。解決方法是,咱們手動調用 moc 工具處理 main.cpp,而且將 main.cpp 中的#include "newspaper.h"
改成#include "moc_newspaper.h"
就能夠了。不過,這是至關繁瑣的步驟,爲了不這樣修改,咱們仍是將其放在頭文件中。許多初學者會遇到莫名其妙的錯誤,一加上Q_OBJECT
就出錯,很大一部分是由於沒有注意到這個宏應該放在頭文件中。設計
Newspaper
類的 public 和 private 代碼塊都比較簡單,只不過它新加了一個 signals。signals 塊所列出的,就是該類的信號。信號就是一個個的函數名,返回值是 void(由於沒法得到信號的返回值,因此也就無需返回任何值),參數是該類須要讓外界知道的數據。信號做爲函數名,不須要在 cpp 函數中添加任何實現(咱們曾經說過,Qt 程序可以使用普通的 make 進行編譯。沒有實現的函數名怎麼會經過編譯?緣由仍是在 moc,moc 會幫咱們實現信號函數所須要的函數體,因此說,moc 並非單純的將 Q_OBJECT 展開,而是作了不少額外的操做)。code
Newspaper
類的send()
函數比較簡單,只有一個語句emit newPaper(m_name);
。emit 是 Qt 對 C++ 的擴展,是一個關鍵字(其實也是一個宏)。emit 的含義是發出,也就是發出newPaper()
信號。感興趣的接收者會關注這個信號,可能還須要知道是哪份報紙發出的信號?因此,咱們將實際的報紙名字m_name
當作參數傳給這個信號。當接收者鏈接這個信號時,就能夠經過槽函數得到實際值。這樣就完成了數據從發出者到接收者的一個轉移。對象
Reader
類更簡單。由於這個類須要接受信號,因此咱們將其繼承了QObject
,而且添加了Q_OBJECT
宏。後面則是默認構造函數和一個普通的成員函數。Qt 5 中,任何成員函數、static 函數、全局函數和 Lambda 表達式均可以做爲槽函數。與信號函數不一樣,槽函數必須本身完成實現代碼。槽函數就是普通的成員函數,所以做爲成員函數,也會受到 public、private 等訪問控制符的影響。(咱們沒有說信號也會受此影響,事實上,若是信號是 private 的,這個信號就不能在類的外面鏈接,也就沒有任何意義。)繼承
main()
函數中,咱們首先建立了Newspaper
和Reader
兩個對象,而後使用QObject::connect()
函數。這個函數咱們上一節已經詳細介紹過,這裏應該可以看出這個鏈接的含義。而後咱們調用Newspaper
的send()
函數。這個函數只有一個語句:發出信號。因爲咱們的鏈接,當這個信號發出時,自動調用 reader 的槽函數,打印出語句。
這樣咱們的示例程序講解完畢。咱們基於 Qt 的信號槽機制,不須要觀察者的容器,不須要註冊對象,就實現了觀察者模式。
下面總結一下自定義信號槽須要注意的事項:
QObject
的子類(固然,槽函數是全局函數、Lambda 表達式等無需接收者的時候除外);QObject::connect()
函數鏈接信號和槽。
本文轉載自www.devbean.net,做者devdean