在前面的章節(信號槽和自定義信號槽)中,咱們詳細介紹了有關 Qt 5 的信號槽新語法。因爲此次改動很大,許多之前看起來不是問題的問題接踵而來,所以,咱們用單獨的一章從新介紹一些 Qt 5 的信號槽新語法。app
Qt 5 引入了信號槽的新語法:使用函數指針可以得到編譯期的類型檢查。使用咱們在自定義信號槽中設計的Newspaper
類,咱們來看看其基本語法:ide
//!!! Qt5 #include <QObject> ////////// newspaper.h class Newspaper : public QObject { Q_OBJECT public: Newspaper(const QString & name) : m_name(name) { } void send() const { emit newPaper(m_name); } signals: void newPaper(const QString &name) const; private: QString m_name; }; ////////// reader.h #include <QObject> #include <QDebug> class Reader : public QObject { Q_OBJECT public: Reader() {} void receiveNewspaper(const QString & name) const { 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(); }
在main()
函數中,咱們使用connect()
函數將newspaper
對象的newPaper()
信號與reader
對象的receiveNewspaper()
槽函數聯繫起來。當newspaper
發出這個信號時,reader
相應的槽函數就會自動被調用。這裏咱們使用了取址操做符,取到Newspaper::newPaper()
信號的地址,一樣相似的取到了Reader::receiveNewspaper()
函數地址。編譯器可以利用這兩個地址,在編譯期對這個鏈接操做進行檢查,若是有個任何錯誤(包括對象沒有這個信號,或者信號參數不匹配等),編譯時就會發現。函數
若是信號有重載,好比咱們向Newspaper
類增長一個新的信號:ui
void newPaper(const QString &name, const QDate &date);
此時若是仍是按照前面的寫法,編譯器會報出一個錯誤:因爲這個函數(注意,信號實際也是一個普通的函數)有重載,所以不能用一個取址操做符獲取其地址。回想一下 Qt 4 中的處理。在 Qt 4 中,咱們使用SIGNAL
和SLOT
兩個宏來鏈接信號槽。若是有一個帶有兩個參數的信號,像上面那種,那麼,咱們就可使用下面的代碼:spa
QObject::connect(&newspaper, SIGNAL(newPaper(QString, QDate)), &reader, SLOT(receiveNewspaper(QString, QDate)));
注意,咱們臨時增長了一個receiveNewspaper()
函數的重載,以便支持兩個參數的信號。在 Qt 4 中不存在咱們所說的錯誤,由於 Qt 4 的信號槽鏈接是帶有參數的。所以,Qt 可以本身判斷到底是哪個信號對應了哪個槽。.net
對此,咱們也給出了一個解決方案,使用一個函數指針來指明究竟是哪個信號:設計
void (Newspaper:: *newPaperNameDate)(const QString &, const QDate &) = &Newspaper::newPaper; QObject::connect(&newspaper, newPaperNameDate, &reader, &Reader::receiveNewspaper);
這樣,咱們使用了函數指針newspaperNameDate
聲明一個帶有QString
和QDate
兩個參數,返回值是 void 的函數,將該函數做爲信號,與Reader::receiveNewspaper()
槽鏈接起來。這樣,咱們就回避了以前編譯器的錯誤。歸根結底,這個錯誤是由於函數重載,編譯器不知道要取哪個函數的地址,而咱們顯式指明一個函數就能夠了。指針
若是你以爲這種寫法很難看,想像前面同樣寫成一行,固然也是由解決方法的:code
QObject::connect(&newspaper, (void (Newspaper:: *)(const QString &, const QDate &))&Newspaper::newPaper, &reader, &Reader::receiveNewspaper);
這是一種換湯不換藥的作法:咱們只是聲明瞭一個匿名的函數指針,而以前咱們的函數指針是有名字的。不過,咱們並不推薦這樣寫,而是但願如下的寫法:對象
QObject::connect(&newspaper, static_cast<void (Newspaper:: *)(const QString &, const QDate &)>(&Newspaper::newPaper), &reader, &Reader::receiveNewspaper);
對比上面兩種寫法。第一個使用的是 C 風格的強制類型轉換。此時,若是你改變了信號的類型,那麼你就會有一個潛在的運行時錯誤。例如,若是咱們把(const QString &, const QDate &)
兩個參數修改爲(const QDate &, const QString &)
,C 風格的強制類型轉換就會失敗,而且這個錯誤只能在運行時發現。而第二種則是 C++ 推薦的風格,當參數類型改變時,編譯器會檢測到這個錯誤。
注意,這裏咱們只是強調了函數參數的問題。若是前面的對象都錯了呢?好比,咱們寫的newspaper
對象並非一個Newspaper
,而是Newspaper2
?此時,編譯器會直接失敗,由於connect()
函數會去尋找sender->*signal
,若是這兩個參數不知足,則會直接報錯。
Qt 容許信號和槽的參數數目不一致:槽函數的參數數目能夠比信號的參數少。這是由於,咱們信號的參數實際是做爲一種返回值。正如普通的函數調用同樣,咱們能夠選擇忽略函數返回值,可是不能使用一個並不存在的返回值。若是槽函數的參數數目比信號的多,在槽函數中就使用到這些參數的時候,實際這些參數並不存在(由於信號的參數比槽的少,所以並無傳過來),函數就會報錯。這種狀況每每有兩個緣由:一是槽的參數就是比信號的少,此時咱們能夠像前面那種寫法直接鏈接。另一個緣由是,信號的參數帶有默認值。好比
void QPushButton::clicked(bool checked = false)
就是這種狀況。
然而,有一種狀況,槽函數的參數能夠比信號的多,那就是槽函數的參數帶有默認值。好比,咱們的Newspaper
和Reader
有下面的代碼:
// Newspaper signals: void newPaper(const QString &name); // Reader void receiveNewspaper(const QString &name, const QDate &date = QDate::currentDate());
雖然Reader::receiveNewspaper()
的參數數目比Newspaper::newPaper()
多,可是因爲Reader::receiveNewspaper()
後面一個參數帶有默認值,因此該參數不是必須提供的。可是,若是你按照前面的寫法,好比以下的代碼:
QObject::connect(&newspaper, static_cast<void (Newspaper:: *)(const QString &)>(&Newspaper::newPaper), &reader, static_cast<void (Reader:: *)(const QString &, const QDate & =QDate::currentDate())>(&Reader::receiveNewspaper));
你會獲得一個斷言錯誤:
The slot requires more arguments than the signal provides.
咱們不能在函數指針中使用函數參數的默認值。這是 C++ 語言的限制:參數默認值只能使用在直接地函數調用中。當使用函數指針取其地址的時候,默認參數是不可見的!
固然,此時你能夠選擇 Qt 4 的鏈接語法。若是你仍是想使用 Qt 5 的新語法,目前的辦法只有一個:Lambda 表達式。不要擔憂你的編譯器不支持 Lambda 表達式,由於在你使用 Qt 5 的時候,可以支持 Qt 5 的編譯器都是支持 Lambda 表達式的。因而,咱們的代碼就變成了:
QObject::connect(&newspaper, static_cast<void (Newspaper:: *)(const QString &)>(&Newspaper::newPaper), [=](const QString &name) { /* Your code here. */ });