目錄
一、認(rèn)識信號和槽
二、connect函數(shù)
三、自定義槽函數(shù)
四、自定義信號
五、帶參數(shù)的信號和槽
六、信號和槽斷開連接
七、信號和槽存在的意義
八、Lambda表達(dá)式定義槽函數(shù)
一、認(rèn)識信號和槽
概述
在Qt中,用戶和控件的每次交互過程稱為一個(gè)事件。如"用戶點(diǎn)擊按鈕"是一個(gè)事件,"用戶關(guān)閉窗口"也是一個(gè)事件。每個(gè)事件都會發(fā)出一個(gè)信號,如用戶點(diǎn)擊按鈕會發(fā)出"按鈕被點(diǎn)擊"的信號,用戶關(guān)閉窗口會發(fā)出"窗口被關(guān)閉"的信號
Qt中的所有控件都具有接收信號的能力,?個(gè)控件還可以接收多個(gè)不同的信號。對于接收到的每個(gè)信號,控件都會做出相應(yīng)的響應(yīng)動(dòng)作。如:按鈕所在的窗口接收到"按鈕被點(diǎn)擊"的信號后,會做出"關(guān)閉自己"的響應(yīng)動(dòng)作;再如:輸入框自己接收到"輸入框被點(diǎn)擊"的信號后,會做出"顯示閃爍的光標(biāo),等待用戶輸入數(shù)據(jù)"的響應(yīng)動(dòng)作。在Qt中,對信號做出的響應(yīng)動(dòng)作就稱之為槽
信號和槽是Qt特有的消息傳輸機(jī)制,它能將相互獨(dú)立的控件關(guān)聯(lián)起來。如:"按鈕"和"窗口"本身是兩個(gè)獨(dú)立的控件,點(diǎn)擊"按鈕"并不會對"窗口"造成任何影響。通過信號和槽機(jī)制,可以將"按鈕"和"窗口"關(guān)聯(lián)起來,實(shí)現(xiàn)"點(diǎn)擊按鈕會使窗口關(guān)閉"的效果
信號的本質(zhì)
信號是由于用戶對窗口或控件進(jìn)行了某些操作,導(dǎo)致窗口或控件產(chǎn)生了某個(gè)特定事件,這時(shí)Qt對應(yīng)的窗口類會發(fā)出某個(gè)信號,以此對用戶的操作做出反應(yīng)。因此,信號的本質(zhì)就是事件
槽的本質(zhì)
槽(Slot)就是對信號響應(yīng)的函數(shù)。槽就是?個(gè)函數(shù),與一般的C++函數(shù)是一樣的,可以定義在類的任何位置(public、protected或private),可以具有任何參數(shù),可以被重載,也可以被直接調(diào)用(但是不能有默認(rèn)參數(shù))。槽函數(shù)與一般的函數(shù)不同的是:槽函數(shù)可以與信號關(guān)聯(lián),當(dāng)信號被發(fā)射時(shí),關(guān)聯(lián)的槽函數(shù)被自動(dòng)執(zhí)行
說明
- 信號和槽機(jī)制底層是通過函數(shù)間的相互調(diào)用實(shí)現(xiàn)的。每個(gè)信號都可以用函數(shù)來表示,即信號函數(shù);每個(gè)槽也可以用函數(shù)表示,即槽函數(shù)
- 信號函數(shù)和槽函數(shù)通常位于某個(gè)類中,和普通的成員函數(shù)相比,它們的特別之處在于:
信號函數(shù)用signals關(guān)鍵字修飾,槽函數(shù)用public slots、protected slots或者private slots修飾(使用普通成員函數(shù)的方式修飾也可)。signals和slots是Qt在C++的基礎(chǔ)上擴(kuò)展的關(guān)鍵字,專門用來指明信號函數(shù)和槽函數(shù);信號函數(shù)只需要聲明,不需要定義(實(shí)現(xiàn)),而槽函數(shù)需要定義(實(shí)現(xiàn))
Q_OBJECT
若一個(gè)類要使用信號和槽機(jī)制,必須在類中添加Q_OBJECT這個(gè)宏
二、connect函數(shù)
在Qt中,QObject類提供了一個(gè)靜態(tài)成員函數(shù)connect(),該函數(shù)專門用來關(guān)聯(lián)指定的信號函數(shù)和槽
函數(shù)。QObject是Qt內(nèi)置的父類,Qt中提供的很多類都是直接或者間接繼承自QObject
Qt Assistant中connect函數(shù)原型:
//舊版
connect (const QObject *sender,
const char * signal ,
const QObject * receiver ,
const char * method ,
Qt::ConnectionType type = Qt::AutoConnection )
- sender:信號的發(fā)送者
- signal:發(fā)送的信號(信號函數(shù))
- receiver:信號的接收者
- method:接收信號的槽函數(shù)
- type:用于指定關(guān)聯(lián)方式,默認(rèn)的關(guān)聯(lián)方式為Qt::AutoConnection,通常不需要手動(dòng)設(shè)定
但是C++不允許使用兩個(gè)不同的指針類型相互賦值,使用const char*明顯是不行的。因?yàn)镼t Assistant中的函數(shù)聲明,是以前舊版本的Qt的connect函數(shù)的聲明
在以前的版本中,給信號參數(shù)傳參需要要搭配一個(gè)SIGNAL宏,給槽參數(shù)傳參需要搭配一個(gè)SLOT宏。從Qt5開始,對上述寫法進(jìn)行了簡化,給connect重載版本,第二個(gè)參數(shù)和第四個(gè)參數(shù)成了泛型函數(shù),允許傳入任意參數(shù)了
此時(shí)connect函數(shù)就帶有了一定的參數(shù)檢查的功能,若傳入的第一個(gè)參數(shù)和第二個(gè)參數(shù),或者第三個(gè)參數(shù)和第四個(gè)參數(shù)不匹配,代碼出現(xiàn)編譯錯(cuò)誤。不匹配是指:2、4參數(shù)傳入的函數(shù)指針,不是1、3參數(shù)的成員函數(shù)的指針
connect函數(shù)使用案例:點(diǎn)擊按鈕關(guān)閉窗口
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* btn = new QPushButton("按鈕", this);
btn->move(200, 200);
connect(btn, &QPushButton::clicked, this, &QWidget::close);
}
Widget::~Widget()
{
delete ui;
}
三、自定義槽函數(shù)
- 早期的Qt版本要求槽函數(shù)必須寫到"public slots"下,但是現(xiàn)在高級版本的Qt允許寫到類的"public"作用域中或者全局下
- 返回值為void,需要聲明,也需要實(shí)現(xiàn)
- 可以有參數(shù),可以發(fā)生重載
代碼編寫槽函數(shù)
widget.h:?
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPushButton>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void HandleClicked();//槽函數(shù)聲明
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* btn = new QPushButton("按鈕", this);
btn->move(200, 200);
connect(btn, &QPushButton::clicked, this, &Widget::HandleClicked);
}
Widget::~Widget()
{
delete ui;
}
//槽函數(shù)定義
void Widget::HandleClicked()
{
setWindowTitle("按鈕已按下");
}
ui創(chuàng)建槽函數(shù)
自動(dòng)生成的槽函數(shù)的名字是on_pushButton_clicked,其中on是固定的,pushButton是ui中的objectName,clicked寫明了是哪種信號。ui自動(dòng)生成的槽函數(shù)不需要connect函數(shù)就能在觸發(fā)信號時(shí)被回調(diào)。(ui_widget.h中調(diào)用了QMetaObject::connectSlotsByName,它會觸發(fā)自動(dòng)連接信號槽的規(guī)則)
四、自定義信號
自定義信號很少用到。因?yàn)樵贕UI中,用戶的操作行為是可以窮舉的,Qt內(nèi)置的信號已經(jīng)覆蓋到了大部分可能的用戶操作
- 信號是一種特殊的函數(shù),程序員只需寫出函數(shù)聲明,并告訴Qt,這是一個(gè)信號即可。這個(gè)函數(shù)的定義,是Qt在編譯過程中,自動(dòng)生成的(無法干預(yù))
- 信號函數(shù)的返回值必須是void,有沒有參數(shù)都可以,也支持函數(shù)重載
- 信號可以使用emit關(guān)鍵字進(jìn)行發(fā)射(Qt5 emit不寫也行)
五、帶參數(shù)的信號和槽
信號和槽也可以帶參數(shù)。發(fā)射信號時(shí),就可以給信號函數(shù)傳遞實(shí)參,這個(gè)參數(shù)就會被傳遞到對應(yīng)的槽函數(shù)中。信號和槽函數(shù)的參數(shù)類型必須一致,個(gè)數(shù)可以不一致,但是信號的參數(shù)個(gè)數(shù)必須大于槽函數(shù)的參數(shù)個(gè)數(shù)(當(dāng)個(gè)數(shù)不一致時(shí),就會按順序拿到信號的前N個(gè)參數(shù))
一個(gè)信號可以通過connect關(guān)聯(lián)多個(gè)槽函數(shù),一個(gè)槽函數(shù)也能被多個(gè)信號關(guān)聯(lián)
六、信號和槽斷開連接
使用disconnect斷開信號槽的連接(主動(dòng)斷開往往是把信號重新綁定到另一個(gè)槽函數(shù)上)
widget.cpp:
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
btn = new QPushButton("按鈕", this);
btn->move(200, 200);
connect(btn, &QPushButton::clicked, this, &Widget::HandleClicked_1);
QPushButton* changeBtn = new QPushButton("修改按鈕功能", this);
changeBtn->move(200, 400);
connect(changeBtn, &QPushButton::clicked, this, &Widget::ChangeButtonRole);
}
Widget::~Widget()
{
delete ui;
}
void Widget::HandleClicked_1() { setWindowTitle("修改窗口標(biāo)題1"); }
void Widget::HandleClicked_2() { setWindowTitle("修改窗口標(biāo)題2"); }
void Widget::ChangeButtonRole()
{
disconnect(btn, &QPushButton::clicked, this, &Widget::HandleClicked_1);
connect(btn, &QPushButton::clicked, this, &Widget::HandleClicked_2);
qDebug() << "修改成功";
}
widget.h:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void HandleClicked_1();
void HandleClicked_2();
void ChangeButtonRole();
private:
Ui::Widget *ui;
QPushButton* btn;
};
#endif // WIDGET_H
若這里沒有disconnect,會使一個(gè)信號綁定兩個(gè)槽函數(shù),觸發(fā)點(diǎn)擊按鈕,同時(shí)執(zhí)行兩個(gè)槽函數(shù)
七、信號和槽存在的意義
- 解耦合:信號發(fā)送者不需要知道發(fā)出的信號被哪個(gè)對象的槽函數(shù)接收,槽函數(shù)也不需要知道哪些信號關(guān)聯(lián)了自己,Qt的信號槽機(jī)制保證了信號與槽函數(shù)的調(diào)用。支持信號槽機(jī)制的類或者父類必須繼承于QObject類
- 實(shí)現(xiàn)"多對多"的效果:一個(gè)信號可以connect到多個(gè)槽函數(shù)上,一個(gè)槽函數(shù)也可以被多個(gè)信號connect(實(shí)際開發(fā)中,這種情況極少)
缺點(diǎn)
與回調(diào)函數(shù)相比,信號和槽稍微慢?些,因?yàn)樗鼈兲峁┝烁叩撵`活性,盡管在實(shí)際應(yīng)用程序中差別不大。通過信號調(diào)用的槽函數(shù)比直接調(diào)用的速度慢約10倍(這是定位信號的接收對象所需的開銷;遍歷所有關(guān)聯(lián);編組/解組傳遞的參數(shù);多線程時(shí),信號可能需要排隊(duì)),這種調(diào)用速度對性能要求不是非常高的場景是可以忽略的,是可以滿足絕大部分場景
?文章來源:http://www.zghlxwxcb.cn/news/detail-836438.html
八、Lambda表達(dá)式定義槽函數(shù)
- 注意被捕獲變量的生命周期
- 盡量傳值捕獲,傳引用捕獲可能會捕獲到已經(jīng)被釋放的變量,造成程序崩潰
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
setFixedSize(1000, 1000);
QPushButton* button = new QPushButton("點(diǎn)擊移動(dòng)", this);
button->move(400, 800);
connect(button, &QPushButton::clicked, this, [=](){
qDebug() << "Lambda";
button->move(800, 800);
});
}
Widget::~Widget()
{
delete ui;
}
上述代碼傳值捕獲沒問題,傳引用捕獲會崩潰。原因是button是局部變量(它指向的空間位于堆區(qū),但其本身是一個(gè)局部變量的指針),構(gòu)造函數(shù)結(jié)束后button變量即被銷毀,造成程序崩潰文章來源地址http://www.zghlxwxcb.cn/news/detail-836438.html
到了這里,關(guān)于【Qt】信號和槽機(jī)制的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!