天天看點

Qt中的信号和槽機制與回調函數一、簡介二、回調函數

一、簡介

信号和槽是 Qt 特有的消息傳輸機制,它能将互相獨立的控件關聯起來。

舉個簡單的例子,按鈕和視窗本是兩個獨立的控件,點選按鈕并不會對視窗造成任何影響。通過信号和槽機制,我們可以将按鈕和視窗關聯起來,實作“點選按鈕會使視窗關閉”的效果。

在 Qt 中,使用者和控件的每次互動過程稱為一個事件,比如“使用者點選按鈕”是一個事件,“使用者關閉視窗”也是一個事件。每個事件都會發出一個信号,例如使用者點選按鈕會發出“按鈕被點選” 的信号,使用者關閉視窗會發出“視窗被關閉”的信号。 Qt 中的所有控件都具有接收信号的能力,一個控件還可以接收多個不同的信号。對于接收到的每個信号,控件都會做出相應的響應動作。例如,按鈕所在的視窗接收到“按鈕被點選”的信 号後,會做出“關閉自己”的響應動作;再比如輸入框自己接收到“輸入框被點選”的信号後,會做出“顯示閃爍的光标,等待使用者輸入資料”的響應動作。在 Qt 中,對信号做出的響應動作就稱為槽 。

信号和槽機制底層是通過函數間的互相調用實作的。每個信号都可以用函數來表示,稱為“信号函數”;

每個槽也可以用函數表示,稱為“槽函數”。

例如,“按鈕被按下”這個信号可以用 clicked() 函數表示,“視窗關閉”這個槽可以用 close() 函數表示,信号和槽機制實作“點選按鈕 會關閉視窗”的功能,其實就是 clicked() 函數調用 close() 函數的效果。 信号函數和槽函數通常位于某個類中,和普通的成員函數相比,它們的特别之處在于:

  1. 信号函數用 signals 關鍵字修飾,槽函數用 public slots、protected slots 或者 private slots 修飾。signals 和 slots 是 Qt 在 C++ 的基礎上擴充的關鍵字,專門用來指明信号函數和槽函數。
  2. 信号函數隻需要聲明,不需要定義(實作),而槽函數需要定義(實作)。

實際開發中,可以使用 Qt 提供的信号函數和槽函數,也可以根據需要自定義信号函數和槽函數。  

自定義信号和槽函數

和信号函數不同,槽函數必須手動定義(實作)。槽函數可以在程式中直接調用,但主要用來響應某個信号。自定義一個槽函數時,需要注意以下幾點: 

  • 槽函數的傳回值必須和信号函數相同,由于信号函數的傳回值一定是 void,是以槽函數的 傳回值也必須為 void;
  • 對于帶參的信号函數,槽函數可以選擇接收所有參數,則參數的類型、順序、個數都必須與信号函數相同; 也可以選擇接收前幾個參數,這些參數的類型、順序都必須與信号函數相同; 還可以選擇不接受任何參數。
  • 槽函數的參數個數隻能比信号函數少,不能比信号函數多;
  • 槽函數的參數不能有預設值。

slots 關鍵字可以和 public、protected、private 搭配使用,它們的差別是:

  • public slots:該關鍵字修飾的槽函數,既可以在目前類及其子類的成員函數中調用,也可以在類外部的其它函數(比如 main() 函數)中調用;
  • protected slots:該關鍵字修飾的槽函數,僅允許在目前類及其子類的成員函數内調用,不 能在類外部的其它函數内調用;
  • private slots:該關鍵字修飾的槽函數,隻允許在目前類的成員函數内調用,不能在子類中調用,也不能在類外部的其它函數内調用。

通常情況下,槽函數使用 public slots 修飾。  

 在 Qt 中信号和槽函數都是獨立的個體,本身沒有任何聯系,但是由于某種特性需求我們可以将二者連接配接到一起,在 Qt 中我們需要使用 QOjbect類中的 connect 函數進行二者的關聯。

注意,并非所有的控件之間都能通過信号和槽關聯起來,信号和槽機制隻适用于滿足以下條件 的控件:  

控件類必須直接或者間接繼承自 QObject 類,Qt 提供的控件類都滿足這一條件。 控件類中必須包含 private 屬性的 Q_OBJECT 宏。

将某個信号函數和某個槽函數關聯起來,需要借助 QObject 類提供的 connect() 函數。

QT4中的連接配接方式:

QObject::connect(const QObject *sender, SIGNAL(signal()), 
                 const QObject *receiver, SLOT(slot())); 
           

而在QT5中,連接配接方式如下:

connect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::method);
           

拿點選but按鈕關閉視窗舉例:  

​
connect( &But, SIGNAL(clicked()), &widget, SLOT(close()) );        //QT4

​
connect( &But, &QPushButton::clicked, &widget, &QWidget::close );  //QT5
           

和舊版本相比,新版的 connect() 函數改進了指定信号函數和槽函數的方式,不再使用 SIGNAL() 和 SLOT() 宏。

注意:QT5中代替Qt4的信号槽連接配接,在編譯時即對連接配接的函數指針進行檢測,提高了安全性。而QT4的連接配接方式是基于宏的,隻能在運作時進行檢測。

最好使用QT5的連接配接方式,因為這樣能檢查出錯誤

一個 connect() 函數隻能關聯一個信号函數和一個槽函數,程式中可以包含多個 connect() 函數,能實作以下幾種效果:

  • 關聯多個信号函數和多個槽函數;
  • 一個信号函數可以關聯多個槽函數,當信号發出時,與之關聯的槽函數會一個接一個地執行,但它們執行的順序是随機的,無法人為指定哪個先執行、哪個後執行;
  • 多個信号函數可以關聯同一個槽函數,無論哪個信号發出,槽函數都會執行。

此外,connect() 函數的 method 參數還可以指定一個信号函數,也就是說,信号之間也可以 互相關聯,這樣當信号發出時,會随之發出另一個信号。

二、回調函數

QT的信号與槽的本質就是個回調函數

1.什麼是回調函數?

  

簡而言之,回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(位址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,我們就說這是回調函數。

#include <iostream>
using namespace std;

typedef int (*pFun)(int,int);    //定義一個函數指針

int sub(int a, int b) {
    return a-b;
}
//回調函數
int callBack(int a, int b, pFun F) {
    return F(a,b);
}

int test(int a, int b) {
    return callBack(a,b,sub);
}

int main() {
    cout << "函數調用:" << test(2,3) << endl;
    return 0;
}
           

回調函數的本質是“你想讓别人的代碼執行你的代碼,而别人的代碼你又不能動”這種需求下産生的。

2.為什麼要使用回調函數?

  

因為可以把調用者與被調用者分開。調用者不關心誰是被調用者,所有它需知道的,隻是存在一個具有某種特定原型、某些限制條件(如傳回值為int)的被調用函數。

信号和槽與回調函數的差別:

①回調函數使用函數指針來實作的,如果多個類都關注一個類的動态變化,這樣就會需要寫出一個比較長的清單來管理這些類之間的關系。稍微在編碼方面不那麼靈活,稍顯備援。

②QT使用信号與槽來解決這個連接配接問題,這種方式比較清晰簡單一些,一個類隻需要清楚自己有幾個槽函數有幾個信号,然後将信号與槽進行連接配接,QT會自己處理函數的調用關系。這樣在軟體設計角度更加的清晰,靈活,不容易出錯。

③Qt信号與槽機制降低了Qt對象的耦合度。發信号的對象不需要知道有幾個槽函數,也不需要關系是否收到信号,或者誰收到了,誰沒收到。同樣的槽函數也不需要知道誰是信号的發出者。信号隻需要在合适的時機發出即可,降低了對象之間的耦合度。

繼續閱讀