1恋博、概述
信號槽是 Qt 框架引以為豪的機(jī)制之一。所謂信號槽私恬,實(shí)際就是觀察者模式债沮。當(dāng)某個(gè)事件發(fā)生之后,比如本鸣,按鈕檢測到自己被點(diǎn)擊了一下疫衩,它就會發(fā)出一個(gè)信號(signal)。這種發(fā)出是沒有目的的荣德,類似廣播闷煤。如果有對象對這個(gè)信號感興趣,它就會使用連接(connect)函數(shù)命爬,意思是曹傀,將想要處理的信號和自己的一個(gè)函數(shù)(稱為槽(slot))綁定來處理這個(gè)信號。也就是說饲宛,當(dāng)信號發(fā)出時(shí)皆愉,被連接的槽函數(shù)會自動被回調(diào)。這就類似觀察者模式:當(dāng)發(fā)生了感興趣的事件艇抠,某一個(gè)操作就會被自動觸發(fā)幕庐。(這里提一句,Qt 的信號槽使用了額外的處理來實(shí)現(xiàn)家淤,并不是 GoF 經(jīng)典的觀察者模式的實(shí)現(xiàn)方式异剥。)
信號和槽是Qt特有的信息傳輸機(jī)制,是Qt設(shè)計(jì)程序的重要基礎(chǔ)絮重,它可以讓互不干擾的對象建立一種聯(lián)系冤寿。
槽的本質(zhì)是類的成員函數(shù)歹苦,其參數(shù)可以是任意類型的。和普通C++成員函數(shù)幾乎沒有區(qū)別督怜,它可以是虛函數(shù)殴瘦;也可以被重載;可以是公有的号杠、保護(hù)的蚪腋、私有的、也可以被其他C++成員函數(shù)調(diào)用姨蟋。唯一區(qū)別的是:槽可以與信號連接在一起屉凯,每當(dāng)和槽連接的信號被發(fā)射的時(shí)候,就會調(diào)用這個(gè)槽眼溶。
1.1對象樹(子對象動態(tài)分配空間不需要釋放)
參考連接:https://blog.csdn.net/fzu_dianzi/article/details/6949081
Qt提供了一種機(jī)制悠砚,能夠自動、有效的組織和管理繼承自QObject的Qt對象偷仿,這種機(jī)制就是對象樹哩簿。
Qt對象樹在用戶界面編程上是非常有用的。它能夠幫助程序員減輕內(nèi)存泄露的壓力酝静。
比如說當(dāng)應(yīng)用程序創(chuàng)建了一個(gè)具有父窗口部件的對象時(shí),該對象將被加入父窗口部件的孩子列表羡玛。當(dāng)應(yīng)用程序銷毀父窗口部件時(shí)别智,其下的孩子列表中的對象將被一一刪除。這讓我們在編程時(shí)稼稿,能夠?qū)⒅饕Ψ旁谙到y(tǒng)的業(yè)務(wù)上薄榛,提高編程效率,同時(shí)也保證了系統(tǒng)的穩(wěn)健性让歼。
下面筆者將簡單分析對象樹敞恋。
代碼驗(yàn)證:
int?main(int?argc,?char?*argv[])
{
????QApplication?app(argc,?argv);
????QDialog?*dlg?=?new?QDialog(0);
????QPushButton?*btn?=?new?QPushButton(dlg);
????qDebug()?<<?"dlg?=?"?<<?dlg;
????qDebug()?<<?"btn?=?"?<<?btn;
????dlg->exec();????delete?btn;
????qDebug()?<<?"dlg?=?"?<<?dlg;????return?0;
}
dlg?=?QDialog(0x3ea1a0)?
btn?=?QPushButton(0x3ea228)/*關(guān)閉窗口后,dlg?=?QDialog(0x3ea1a0)
這說明關(guān)閉窗口谋右,不會銷毀該窗口部件硬猫,而是將其隱藏起來。
我們在qDebug()?<<?"dlg?=?"?<<?dlg;
之后加上
qDebug()?<<?"btn?=?"?<<?btn;
明顯的改执,我們之前已經(jīng)delete?btn啸蜜,btn指針沒有被賦值為0,這是編譯器決定的辈挂。
執(zhí)行程序后衬横,必然出現(xiàn)段錯(cuò)誤。
2终蒂、
將程序稍微修改下蜂林。*/int?main(int?argc,?char?*argv[])
{
????QApplication?app(argc,?argv);
????QDialog?*dlg?=?new?QDialog(0);
????QPushButton?*btn?=?new?QPushButton(dlg);
????qDebug()?<<?"dlg?=?"?<<?dlg;
????qDebug()?<<?"btn?=?"?<<?btn;
????dlg->exec();????delete?dlg;
????qDebug()?<<?"btn?=?"?<<?btn;??
??return?0;
}
2遥诉、信號和槽
為了體驗(yàn)一下信號槽的使用,我們以一段簡單的代碼說明:
Qt5 的書寫方式:(推薦的使用)★★★★★
#include <QApplication>
#include <QPushButton>
?int?main(int?argc,?char?*argv[])
{
????QApplication?app(argc,?argv);
????QPushButton?button("Quit");
QObject::connect(&button,?&QPushButton::clicked,&app,?&QApplication::quit);
????button.show();????
return?app.exec();
}
我們按照前面文章中介紹的在 Qt Creator 中創(chuàng)建工程的方法創(chuàng)建好工程噪叙,然后將main()函數(shù)修改為上面的代碼矮锈。點(diǎn)擊運(yùn)行,我們會看到一個(gè)按鈕构眯,上面有“Quit”字樣愕难。點(diǎn)擊按鈕,程序退出惫霸。
connect()函數(shù)最常用的一般形式:
connect(sender,?signal,?receiver,?slot);
參數(shù):
?sender:發(fā)出信號的對象
?signal:發(fā)送對象發(fā)出的信號
?receiver:接收信號的對象
?slot:接收對象在接收到信號之后所需要調(diào)用的函數(shù)
信號槽要求信號和槽的參數(shù)一致猫缭,所謂一致,是參數(shù)類型一致壹店。如果不一致猜丹,允許的情況是,槽函數(shù)的參數(shù)可以比信號的少硅卢,即便如此射窒,槽函數(shù)存在的那些參數(shù)的順序也必須和信號的前面幾個(gè)一致起來。這是因?yàn)榻埽憧梢栽诓酆瘮?shù)中選擇忽略信號傳來的數(shù)據(jù)(也就是槽函數(shù)的參數(shù)比信號的少)脉顿,但是不能說信號根本沒有這個(gè)數(shù)據(jù),你就要在槽函數(shù)中使用(就是槽函數(shù)的參數(shù)比信號的多点寥,這是不允許的)艾疟。
如果信號槽不符合,或者根本找不到這個(gè)信號或者槽函數(shù)敢辩,比如我們改成:
connect(&button,?&QPushButton::clicked,?&QApplication::quit2);
由于 QApplication 沒有 quit2 這樣的函數(shù)蔽莱,因此在編譯時(shí)會有編譯錯(cuò)誤:
'quit2'?is?not?a?member?of?QApplication
這樣,使用成員函數(shù)指針我們就不會擔(dān)心在編寫信號槽的時(shí)候出現(xiàn)函數(shù)錯(cuò)誤戚长。
Qt4 的書寫方式:
int?main(int?argc,?char?*argv[])
{?
????????QApplication?a(argc,?argv);?
????????QPushButton?*button?=?new?QPushButton("Quit");?
????????connect(button,?SIGNAL(clicked()),?&a,?SLOT(quit()));?
????????button->show();?
????????return?a.exec();?
}
這里使用了SIGNAL和SLOT這兩個(gè)宏盗冷,將兩個(gè)函數(shù)名轉(zhuǎn)換成了字符串。注意到connect()函數(shù)的 signal 和 slot 都是接受字符串同廉,一旦出現(xiàn)連接不成功的情況仪糖,Qt4是沒有編譯錯(cuò)誤的(因?yàn)橐磺卸际亲址幾g期是不檢查字符串是否匹配)恤溶,而是在運(yùn)行時(shí)給出錯(cuò)誤乓诽。這無疑會增加程序的不穩(wěn)定性。
Qt5在語法上完全兼容Qt4
小總結(jié):
1>. 格式: connect(信號發(fā)出者對象(指針), &className::clicked, 信號接收者對象(指針), &classB::slot);
2>. 標(biāo)準(zhǔn)信號槽的使用:
connect(sender, &Send::signal, receiver, &Receiver::slot)
3咒程、自定義信號槽
使用connect()可以讓我們連接系統(tǒng)提供的信號和槽鸠天。但是,Qt 的信號槽機(jī)制并不僅僅是使用系統(tǒng)提供的那部分帐姻,還會允許我們自己設(shè)計(jì)自己的信號和槽稠集。
下面我們看看使用 Qt 的信號槽奶段,實(shí)現(xiàn)一個(gè)報(bào)紙和訂閱者的例子:
有一個(gè)報(bào)紙類Newspaper,有一個(gè)訂閱者類Subscriber剥纷。Subscriber可以訂閱Newspaper痹籍。這樣,當(dāng)Newspaper有了新的內(nèi)容的時(shí)候晦鞋,Subscriber可以立即得到通知蹲缠。
#include <QObject>
?//////////?newspaper.h?//////////
class?Newspaper?:?public?QObject
{
????Q_OBJECTpublic:
????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_OBJECTpublic:
????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();
}
●首先看Newspaper這個(gè)類。這個(gè)類繼承了QObject類悠垛。只有繼承了QObject類的類线定,才具有信號槽的能力。所以确买,為了使用信號槽斤讥,必須繼承QObject。凡是QObject類(不管是直接子類還是間接子類)湾趾,都應(yīng)該在第一行代碼寫上Q_OBJECT芭商。不管是不是使用信號槽,都應(yīng)該添加這個(gè)宏搀缠。這個(gè)宏的展開將為我們的類提供信號槽機(jī)制铛楣、國際化機(jī)制以及 Qt 提供的不基于 C++ RTTI 的反射能力。
●?Newspaper類的 public 和 private 代碼塊都比較簡單艺普,只不過它新加了一個(gè) signals蛉艾。signals 塊所列出的,就是該類的信號衷敌。信號就是一個(gè)個(gè)的函數(shù)名,返回值是 void(因?yàn)闊o法獲得信號的返回值拓瞪,所以也就無需返回任何值)缴罗,參數(shù)是該類需要讓外界知道的數(shù)據(jù)。信號作為函數(shù)名祭埂,不需要在 cpp 函數(shù)中添加任何實(shí)現(xiàn)面氓。
●Newspaper類的send()函數(shù)比較簡單,只有一個(gè)語句emit newPaper(m_name);蛆橡。emit 是 Qt 對 C++ 的擴(kuò)展舌界,是一個(gè)關(guān)鍵字(其實(shí)也是一個(gè)宏)。emit 的含義是發(fā)出呻拌,也就是發(fā)出newPaper()信號。感興趣的接收者會關(guān)注這個(gè)信號睦焕,可能還需要知道是哪份報(bào)紙發(fā)出的信號?所以袜炕,我們將實(shí)際的報(bào)紙名字m_name當(dāng)做參數(shù)傳給這個(gè)信號。當(dāng)接收者連接這個(gè)信號時(shí)初家,就可以通過槽函數(shù)獲得實(shí)際值偎窘。這樣就完成了數(shù)據(jù)從發(fā)出者到接收者的一個(gè)轉(zhuǎn)移。
●?Reader類更簡單溜在。因?yàn)檫@個(gè)類需要接受信號陌知,所以我們將其繼承了QObject,并且添加了Q_OBJECT宏炕泳。后面則是默認(rèn)構(gòu)造函數(shù)和一個(gè)普通的成員函數(shù)纵诞。Qt 5 中,任何成員函數(shù)培遵、static 函數(shù)浙芙、全局函數(shù)和 Lambda 表達(dá)式都可以作為槽函數(shù)。與信號函數(shù)不同籽腕,槽函數(shù)必須自己完成實(shí)現(xiàn)代碼嗡呼。槽函數(shù)就是普通的成員函數(shù),因此作為成員函數(shù)皇耗,也會受到 public南窗、private 等訪問控制符的影響。(如果信號是 private 的郎楼,這個(gè)信號就不能在類的外面連接万伤,也就沒有任何意義。)
3.1自定義信號槽需要注意的事項(xiàng)
●發(fā)送者和接收者都需要是QObject的子類(當(dāng)然呜袁,槽函數(shù)是全局函數(shù)敌买、Lambda 表達(dá)式等無需接收者的時(shí)候除外);
●使用?signals 標(biāo)記信號函數(shù)阶界,信號是一個(gè)函數(shù)聲明虹钮,返回 void,不需要實(shí)現(xiàn)函數(shù)代碼膘融;
●槽函數(shù)是普通的成員函數(shù)芙粱,作為成員函數(shù),會受到?public氧映、private春畔、protected 的影響;
●使用?emit 在恰當(dāng)?shù)奈恢冒l(fā)送信號;
●使用QObject::connect()函數(shù)連接信號和槽拐迁。
●任何成員函數(shù)蹭劈、static 函數(shù)、全局函數(shù)和 Lambda 表達(dá)式都可以作為槽函數(shù)
3.2信號槽的更多用法
●一個(gè)信號可以和多個(gè)槽相連
如果是這種情況线召,這些槽會一個(gè)接一個(gè)的被調(diào)用铺韧,但是它們的調(diào)用順序是不確定的。
●多個(gè)信號可以連接到一個(gè)槽
只要任意一個(gè)信號發(fā)出缓淹,這個(gè)槽就會被調(diào)用哈打。
●一個(gè)信號可以連接到另外的一個(gè)信號
當(dāng)?shù)谝粋€(gè)信號發(fā)出時(shí),第二個(gè)信號被發(fā)出讯壶。除此之外料仗,這種信號-信號的形式和信號-槽的形式?jīng)]有什么區(qū)別。
●槽可以被取消鏈接
這種情況并不經(jīng)常出現(xiàn)伏蚊,因?yàn)楫?dāng)一個(gè)對象delete之后立轧,Qt自動取消所有連接到這個(gè)對象上面的槽。
●使用Lambda 表達(dá)式
在使用 Qt 5 的時(shí)候躏吊,能夠支持 Qt 5 的編譯器都是支持 Lambda 表達(dá)式的氛改。
我們的代碼可以寫成下面這樣:
QObject::connect(&newspaper,?static_cast
(const?QString?&)>(&Newspaper::newPaper),
[=](const?QString?&name)?
{?/*?Your?code?here.?*/?}
);
在連接信號和槽的時(shí)候,槽函數(shù)可以使用Lambda表達(dá)式的方式進(jìn)行處理比伏。
4胜卤、Lambda表達(dá)式
C++11中的Lambda表達(dá)式用于定義并創(chuàng)建匿名的函數(shù)對象,以簡化編程工作赁项。首先看一下Lambda表達(dá)式的基本構(gòu)成:
[函數(shù)對象參數(shù)](操作符重載函數(shù)參數(shù))mutable或exception?->返回值{函數(shù)體}
?①函數(shù)對象參數(shù)葛躏;
[],標(biāo)識一個(gè)Lambda的開始悠菜,這部分必須存在舰攒,不能省略。函數(shù)對象參數(shù)是傳遞給編譯器自動生成的函數(shù)對象類的構(gòu)造函數(shù)的悔醋。函數(shù)對象參數(shù)只能使用那些到定義Lambda為止時(shí)Lambda所在作用范圍內(nèi)可見的局部變量(包括Lambda所在類的this)芒率。函數(shù)對象參數(shù)有以下形式:
?▲空。沒有使用任何函數(shù)對象參數(shù)篙顺。
▲=。函數(shù)體內(nèi)可以使用Lambda所在作用范圍內(nèi)所有可見的局部變量(包括Lambda所在類的this)充择,并且是值傳遞方式(相當(dāng)于編譯器自動為我們按值傳遞了所有局部變量)德玫。
▲&。函數(shù)體內(nèi)可以使用Lambda所在作用范圍內(nèi)所有可見的局部變量(包括Lambda所在類的this)椎麦,并且是引用傳遞方式(相當(dāng)于編譯器自動為我們按引用傳遞了所有局部變量)宰僧。
▲?this。函數(shù)體內(nèi)可以使用Lambda所在類中的成員變量观挎。
▲?a琴儿。將a按值進(jìn)行傳遞段化。按值進(jìn)行傳遞時(shí),函數(shù)體內(nèi)不能修改傳遞進(jìn)來的a的拷貝造成,因?yàn)槟J(rèn)情況下函數(shù)是const的显熏。要修改傳遞進(jìn)來的a的拷貝,可以添加mutable修飾符晒屎。
▲?&a喘蟆。將a按引用進(jìn)行傳遞。
? ? ▲?a, &b鼓鲁。將a按值進(jìn)行傳遞蕴轨,b按引用進(jìn)行傳遞。
▲?=骇吭,&a, &b橙弱。除a和b按引用進(jìn)行傳遞外,其他參數(shù)都按值進(jìn)行傳遞燥狰。
▲?&, a, b棘脐。除a和b按值進(jìn)行傳遞外,其他參數(shù)都按引用進(jìn)行傳遞碾局。
int?m?=?0,?n?=?0;
[=]?(int?a)?mutable?{?m?=?++n?+?a;?}(4);
??????[&]?(int?a)?{?m?=?++n?+?a;?}(4);
??????[=,&m]?(int?a)?mutable?{?m?=?++n?+?a;?}(4);
??????[&,m]?(int?a)?mutable?{?m?=?++n?+?a;?}(4);
??????[m,n]?(int?a)?mutable?{?m?=?++n?+?a;?}(4);
??????[&m,&n]?(int?a)?{?m?=?++n?+?a;?}(4);
② 操作符重載函數(shù)參數(shù)荆残;
標(biāo)識重載的()操作符的參數(shù),沒有參數(shù)時(shí)净当,這部分可以省略内斯。參數(shù)可以通過按值(如:(a,b))和按引用(如:(&a,&b))兩種方式進(jìn)行傳遞。
③ 可修改標(biāo)示符像啼;
mutable聲明俘闯,這部分可以省略。按值傳遞函數(shù)對象參數(shù)時(shí)忽冻,加上mutable修飾符后真朗,可以修改按值傳遞進(jìn)來的拷貝(注意是能修改拷貝,而不是值本身)僧诚。
④ 錯(cuò)誤拋出標(biāo)示符遮婶;
exception聲明,這部分也可以省略湖笨。exception聲明用于指定函數(shù)拋出的異常旗扑,如拋出整數(shù)類型的異常,可以使用throw(int)
⑤ 函數(shù)返回值慈省;
->返回值類型臀防,標(biāo)識函數(shù)返回值的類型,當(dāng)返回值為void,或者函數(shù)體中只有一處return的地方(此時(shí)編譯器可以自動推斷出返回值類型)時(shí)袱衷,這部分可以省略捎废。
⑥ 是函數(shù)體;
{}致燥,標(biāo)識函數(shù)的實(shí)現(xiàn)登疗,這部分不能省略,但函數(shù)體可以為空篡悟。
一個(gè)好的學(xué)習(xí)環(huán)境能營造出一種好的學(xué)習(xí)氛圍谜叹,大家互相討論,亦師亦友搬葬,為了同一個(gè)夢想前進(jìn)荷腊,這是一件浪漫并且熱血的事,如果你是C/C++的愛好者急凰,喜歡或者想要學(xué)習(xí)女仰,那么一個(gè)學(xué)習(xí)基地適合你,歡迎每一位想要學(xué)習(xí)抡锈、學(xué)好C/C++的朋友疾忍。(見評論區(qū)),大量資源床三、干貨一罩、大佬離你更近。