QT slots和signal底層原理
Qt 中的所有控件都具有接收信號(hào)的能力,一個(gè)控件還可以接收多個(gè)不同的信號(hào)。對(duì)于接收到的每個(gè)信號(hào),控件都會(huì)做出相應(yīng)的響應(yīng)動(dòng)作。例如,按鈕所在的窗口接收到“按鈕被點(diǎn)擊”的信號(hào)后,會(huì)做出“關(guān)閉自己”的響應(yīng)動(dòng)作;再比如輸入框自己接收到“輸入框被點(diǎn)擊”的信號(hào)后,會(huì)做出“顯示閃爍的光標(biāo),等待用戶輸入數(shù)據(jù)”的響應(yīng)動(dòng)作。在 Qt 中,對(duì)信號(hào)做出的響應(yīng)動(dòng)作就稱為槽。
信號(hào)和槽機(jī)制底層是通過函數(shù)間的相互調(diào)用實(shí)現(xiàn)的。每個(gè)信號(hào)都可以用函數(shù)來表示,稱為信號(hào)函數(shù);每個(gè)槽也可以用函數(shù)表示,稱為槽函數(shù)。例如,“按鈕被按下”這個(gè)信號(hào)可以用 clicked() 函數(shù)表示,“窗口關(guān)閉”這個(gè)槽可以用 close() 函數(shù)表示,信號(hào)和槽機(jī)制實(shí)現(xiàn)“點(diǎn)擊按鈕會(huì)關(guān)閉窗口”的功能,其實(shí)就是 clicked() 函數(shù)調(diào)用 close() 函數(shù)的效果。
信號(hào)函數(shù)和槽函數(shù)通常位于某個(gè)類中,和普通的成員函數(shù)相比,它們的特別之處在于:
信號(hào)函數(shù)用 signals 關(guān)鍵字修飾,槽函數(shù)用 public slots、protected slots 或者 private slots 修飾。signals 和 slots 是 Qt 在 C++ 的基礎(chǔ)上擴(kuò)展的關(guān)鍵字,專門用來指明信號(hào)函數(shù)和槽函數(shù)。
信號(hào)和槽執(zhí)行流程:
moc查找頭文件中的signals,slots,標(biāo)記出信號(hào)和槽
將信號(hào)槽信息存儲(chǔ)到類靜態(tài)變量staticMetaObject中,并且按聲明順序進(jìn)行存放,建立索引。
當(dāng)發(fā)現(xiàn)有connect連接時(shí),將信號(hào)槽的索引信息放到一個(gè)map中,彼此配對(duì)。
當(dāng)調(diào)用emit時(shí),調(diào)用信號(hào)函數(shù),并且傳遞發(fā)送信號(hào)的對(duì)象指針,元對(duì)象指針,信號(hào)索引,參數(shù)列表到active函數(shù)
通過active函數(shù)找到在map中找到所有與信號(hào)對(duì)應(yīng)的槽索引
根據(jù)槽索引找到槽函數(shù),執(zhí)行槽函數(shù)
信號(hào)和槽是一種高級(jí)接口,應(yīng)用于對(duì)象之間的通信,它是QT的核心特性,也是QT區(qū)別于其它工具包的重要地方。信號(hào)和槽是QT自行定義的一種通信機(jī)制,它獨(dú)立于標(biāo)準(zhǔn)的C/C++語言,因此要正確的處理信號(hào)和槽,必須借助一個(gè)稱為moc(Meta ObjectCompiler)的QT工具,該工具是一個(gè)C++預(yù)處理程序,它為高層次的事件處理自動(dòng)生成所需要的附加代碼。
在我們所熟知的很多GUI工具包中,窗口小部件(widget)都有一個(gè)回調(diào)函數(shù)用于響應(yīng)它們能觸發(fā)的每個(gè)動(dòng)作,這個(gè)回調(diào)函數(shù)通常是一個(gè)指向某個(gè)函數(shù)的指針。但是,在QT中信號(hào)和槽取代了這些凌亂的函數(shù)指針,使得我們編寫這些通信程序更為簡(jiǎn)潔明了。 信號(hào)和槽能攜帶任意數(shù)量和任意類型的參數(shù),他們是類型完全安全的,不會(huì)像回調(diào)函數(shù)那樣產(chǎn)生core dumps。
所有從QObject或其子類(例如Qwidget)派生的類都能夠包含信號(hào)和槽。當(dāng)對(duì)象改變其狀態(tài)時(shí),信號(hào)就由該對(duì)象發(fā)射(emit)出去,這就是對(duì)象所要做的全部事情,它不知道另一端是誰在接收這個(gè)信號(hào)。這就是真正的信息封裝,它確保對(duì)象被當(dāng)作一個(gè)真正的軟件組件來使用。槽用于接收信號(hào),但它們是普通的對(duì)象成員函數(shù)。一個(gè)槽并不知道是否有任何信號(hào)與自己相連接。而且,對(duì)象并不了解具體的通信機(jī)制。
你可以將很多信號(hào)與單個(gè)的槽進(jìn)行連接,也可以將單個(gè)的信號(hào)與很多的槽進(jìn)行連接,甚至于將一個(gè)信號(hào)與另外一個(gè)信號(hào)相連接也是可能的,這時(shí)無論第一個(gè)信號(hào)什么時(shí)候發(fā)射系統(tǒng)都將立刻發(fā)射第二個(gè)信號(hào)??傊?,信號(hào)與槽構(gòu)造了一個(gè)強(qiáng)大的部件編程機(jī)制。
槽是普通的C++成員函數(shù),可以被正常調(diào)用,它們唯一的特殊性就是很多信號(hào)可以與其相關(guān)聯(lián)。當(dāng)與其關(guān)聯(lián)的信號(hào)被發(fā)射時(shí),這個(gè)槽就會(huì)被調(diào)用。槽可以有參數(shù),但槽的參數(shù)不能有缺省值。
既然槽是普通的成員函數(shù),因此與其它的函數(shù)一樣,它們也有存取權(quán)限。槽的存取權(quán)限決定了誰能夠與其相關(guān)聯(lián)。同普通的C++成員函數(shù)一樣,槽函數(shù)也分為三種類型,即public slots、private slots和protected slots。
public slots:在這個(gè)區(qū)內(nèi)聲明的槽意味著任何對(duì)象都可將信號(hào)與之相連接。這對(duì)于組件編程非常有用,你可以創(chuàng)建彼此互不了解的對(duì)象,將它們的信號(hào)與槽進(jìn)行連接以便信息能夠正確的傳遞。
protected slots:在這個(gè)區(qū)內(nèi)聲明的槽意味著當(dāng)前類及其子類可以將信號(hào)與之相連接。這適用于那些槽,它們是類實(shí)現(xiàn)的一部分,但是其界面接口卻面向外部。
private slots:在這個(gè)區(qū)內(nèi)聲明的槽意味著只有類自己可以將信號(hào)與之相連接。這適用于聯(lián)系非常緊密的類。
槽也能夠聲明為虛函數(shù),這也是非常有用的
元對(duì)象編譯器moc(meta object compiler)對(duì)C++文件中的類聲明進(jìn)行分析并產(chǎn)生用于初始化元對(duì)象的C++代碼,元對(duì)象包含全部信號(hào)和槽的名字以及指向這些函數(shù)的指針。
moc讀C++源文件,如果發(fā)現(xiàn)有Q_OBJECT宏聲明的類,它就會(huì)生成另外一個(gè)C++源文件,這個(gè)新生成的文件中包含有該類的元對(duì)象代碼。例如,假設(shè)我們有一個(gè)頭文件mysignal.h,在這個(gè)文件中包含有信號(hào)或槽的聲明,那么在編譯之前 moc 工具就會(huì)根據(jù)該文件自動(dòng)生成一個(gè)名為mysignal.moc.h的C++源文件并將其提交給編譯器;類似地,對(duì)應(yīng)于mysignal.cpp文件moc工具將自動(dòng)生成一個(gè)名為mysignal.moc.cpp文件提交給編譯器。
元對(duì)象代碼是signal/slot機(jī)制所必須的。用moc產(chǎn)生的C++源文件必須與類實(shí)現(xiàn)一起進(jìn)行編譯和連接,或者用#include語句將其包含到類的源文件中。moc并不擴(kuò)展#include或者#define宏定義,它只是簡(jiǎn)單的跳過所遇到的任何預(yù)處理指令。