如果面試官問你Qt最明顯差別于其它開發架構的特征是什麼,那麼你的回答一定是信号和槽,為此,小豆君将拿出三節的時間來講述信号和槽。
本節隻講解非線程間的信号槽,等後面學習的深入,再講解線程間信号槽通信。
第一節,信号槽的基本概念。
第二節,信号槽的擴充知識。
第三節,分析Qt的moc與編譯器生成的moc源檔案,幫助大家了解信号槽的來龍去脈。
2.3.1 引子
在面向對象的程式設計方法中,都會建立很多的執行個體,每個執行個體都是單獨的,要想每個執行個體能夠協同合作,那麼就會需要一種對象間傳遞消息的機制,在很多架構中都采用回調函數來進行對象間資訊傳遞。
回調是一個指向函數的指針,如果想要一個處理函數通知一些事件,你需要将這個指針傳遞給處理函數。處理函數在适當時間調用回調函數。MFC就是使用的回調函數,但回調可能是不直覺的,不易于了解的,并且也不能保證是類型安全的。
Qt為了消除回調函數等的弊端,進而開發了一種新的消息傳遞機制,即信号和槽。
例如,當我們要求滑鼠點選某個按鈕時,對應的視窗就需要關閉,那麼這個按鈕就會發出一個點選信号,而視窗接收到這個信号後執行關閉視窗。那麼,這個信号就是按鈕被點選,而槽就是視窗執行關閉函數。
可以将信号和槽了解成“指令-執行”,即信号就是指令,槽就是執行指令。
2.3.2 信号
當一個對象的内部狀态發生改變時,如果其它對象對它的狀态需要有所反應,這時就應該讓這個類發出狀态改變的信号。
聲明信号使用signals關鍵字
發送信号使用emit關鍵字
注意:
1.所有的信号聲明都是公共的,是以Qt規定不能在signals前面加public,private, protected。
2.所有的信号都沒有傳回值,是以傳回值都用void。
3.所有的信号都不需要定義。
4.必須直接或間接繼承自QOBject類,并且開頭私有聲明包含Q_OBJECT。
5.當一個信号發出時,會立即執行其槽函數,等待槽函數執行完畢後,才會執行後面的代碼,如果一個信号連結了多個槽,那麼會等所有的槽函數執行完畢後才執行後面的代碼,槽函數的執行順序是按照它們連結時的順序執行的。
6.在連結信号和槽時,可以設定連結方式為:在發出信号後,不需要等待槽函數執行完,而是直接執行後面的代碼。
7.發出信号使用emit關鍵字。
8.信号參數的個數不得少于槽參數的個數。
2.2.3 槽
槽其實就是普通的C++函數,它唯一特點就是能和信号連結。當和它連結的信号被發出時,這個槽就會被調用。
聲明槽可以使用:public/protected/private slots:
以上是Qt4的做法,在Qt5中你也不需要使用這些聲明,每個函數都可以被當作是槽函數,而且還可以使用Lambda表達式來作為槽。不過為了程式的可讀性,我還是推薦槽函數要聲明一下。
2.2.4連接配接信号和槽
使用connect函數連接配接信号和槽
2.2.4.1 原型1
static QMetaObject::Connection connect( const QObject *sender, //信号發送對象指針 const char *signal, //信号函數字元串,使用SIGNAL() const QObject *receiver, //槽函數對象指針 const char *member, //槽函數字元串,使用SLOT() Qt::ConnectionType = Qt::AutoConnection//連接配接類型,一般預設即可);//例如connect(pushButton, SIGNAL(clicked()), dialog, SLOT(close()));
Qt4和Qt5都可以使用這種連接配接方式
2.2.4.2 原型2
static QMetaObject::Connection connect( const QObject *sender, //信号發送對象指針 const QMetaMethod &signal,//信号函數位址 const QObject *receiver, //槽函數對象指針 const QMetaMethod &method,//槽函數位址 Qt::ConnectionType type = Qt::AutoConnection//連接配接類型,一般預設即可);//例如connect(pushButton, QPushButton::clicked, dialog, QDialog::close);
Qt5新增這種連接配接方式,這使得在編譯期間就可以進行拼寫檢查,參數檢查,類型檢查,并且支援相容參數的相容性轉換。
2.2.5 信号槽示例
建立視窗項目SignalsAndSlotsWidget,類名同為SignalsAndSlotsWidget,基類選擇QWidget。
在ui中拖入1個QLineEdit,1個QLable,1個QSlider,如下圖
頭檔案
class SignalsAndSlotsWidget : public QWidget{ Q_OBJECTpublic: explicit SignalsAndSlotsWidget(QWidget *parent = 0); ~SignalsAndSlotsWidget();signals: //自定義信号,發出此信号,使得QLabel顯示文字 void sigShowVal(const QString&);public slots: //自定義槽,當LineEdit發出文字改變的信号時,執行這個槽 void sltLineEditChanged(const QString& text);private: Ui::SignalsAndSlotsWidget *ui;};
源檔案
SignalsAndSlotsWidget::SignalsAndSlotsWidget(QWidget *parent) : QWidget(parent), ui(new Ui::SignalsAndSlotsWidget){ ui->setupUi(this); int max = 100; int min = 0; ui->horizontalSlider->setMaximum(max);//設定最大最小值 ui->horizontalSlider->setMinimum(min); //設定QLineEdit隻能輸入數字,且為0-100 QIntValidator* validator = new QIntValidator(min, max, this); ui->lineEdit->setValidator(validator); connect(ui->lineEdit, &QLineEdit::textChanged, this, &SignalsAndSlotsWidget::sltLineEditChanged); connect(this, SIGNAL(sigShowVal(QString)), ui->label, SLOT(setText(QString)));}SignalsAndSlotsWidget::~SignalsAndSlotsWidget(){ delete ui;}void SignalsAndSlotsWidget::sltLineEditChanged(const QString &text){ int val = text.toInt(); ui->horizontalSlider->setValue(val);//設定slider目前值 emit sigShowVal(text);//通知label顯示文字}
運作程式,在輸入框中輸入0-100,看看有什麼變化。
好了,今天就到這裡,我們下期再見。