久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

QT信號機(jī)制(

 quasiceo 2012-12-26

QT信號機(jī)制(全文)

2007年08月14日08:19 來源:ChinaUnix博客 作者:Dave DeWalt 編輯:周榮茂 評論:0
本文Tag: Linux程序開發(fā)

    Qt用預(yù)編譯器和宏來保證強(qiáng)大的跨平臺能力,信號機(jī)制則是其中最精妙之處,。本文分析了幾種常見的信號處理機(jī)制,,然后詳細(xì)介紹了Qt的Signal/Slot機(jī)制。

     關(guān)鍵詞:信號機(jī)制Signal/Slot Win32 MFC Linux Qt

     首先要說明,,這里所說的信號不是Unix中進(jìn)程間通信的信號,。這里的信號更多地與圖形界面的輸入輸出聯(lián)系在一起(當(dāng)然也可以是不可見的操作)。自從計(jì)算機(jī)程序從字符界面轉(zhuǎn)為圖形界面,,用戶的輸入一下子變得繁雜和豐富起來,,不同的輸入位置、不同的輸入設(shè)備,、不同的焦點(diǎn)位置,、不同的輸入值組合起來構(gòu)成了許許多多的信號。一下子,,這個(gè)世界變得五彩繽紛,。當(dāng)前的三大主流操作系統(tǒng)——Windows、Unix和MAC都提供了令人賞心悅目的圖形界面,。雖然它們出自不同公司,,自身還有很多分支,但是在圖形操作與管理上還是大致類同的:都有桌面,、有圖標(biāo),,有大大小小規(guī)則或不規(guī)則的窗口,,窗口上有標(biāo)題,、邊框、菜單以及按鈕等各種控件,,用戶可以用鍵盤在當(dāng)前焦點(diǎn)輸入內(nèi)容,,可以用鼠標(biāo)點(diǎn)擊任意的窗口和控件。就能動性來說,,是由用戶主導(dǎo)程序下一步作何操作,,而不象字符時(shí)代那樣由程序來主導(dǎo)用戶,。這也就是所謂的“事件驅(qū)動”。在一個(gè)事件驅(qū)動的系統(tǒng)中,,不論是Windows,,還是Unix,都脫離不了以下的處理框架:

     當(dāng)某個(gè)應(yīng)用程序收到操作系統(tǒng)發(fā)送的事件時(shí),,它就要判斷這個(gè)事件該由誰處理,。處理過程本身又可能引起新的事件發(fā)生,這就要告訴操作系統(tǒng)我發(fā)出了什么信號,。如此這般循環(huán)往復(fù),,青山之水常流。那么,,一個(gè)具體的信號究竟是如何觸發(fā)與它對應(yīng)的函數(shù)呢,?絕大部分的系統(tǒng)都是采用了回調(diào)的機(jī)制,所謂“回調(diào)”其實(shí)就是指向某一個(gè)函數(shù)的指針,。在C語言中函數(shù)名其實(shí)也是一個(gè)指針,,因此回調(diào)其實(shí)是一個(gè)指向指針的指針。在不同的開發(fā)框架或開發(fā)包中,,對于回調(diào)的實(shí)現(xiàn)有著一些細(xì)微的差別,。初接觸Qt時(shí),我一直在想它是如何處理各種平臺的信號調(diào)用,。雖然C語言本身是平臺無關(guān)的,,但具體到某一個(gè)操作系統(tǒng)、某一個(gè)開發(fā)包,,信號機(jī)制會有些不同,。而信號是面向?qū)ο蟮拈_發(fā)環(huán)境中一個(gè)很重要的環(huán)節(jié),如果要設(shè)計(jì)一個(gè)類庫或程序框架,,就必須很好地考慮不同平臺間的差異,。接觸了Qt之后,感覺Qt選擇了一條頗具特色的處理途徑——Signals/Slot,,中文名暫定為“信號/反應(yīng)槽”,。在Qt的內(nèi)部設(shè)計(jì)中,通過信號/反應(yīng)槽(signals/slot)的使用對回調(diào)進(jìn)行了很好的封裝,。為了更好地了解該機(jī)制我們先看一下其他幾種常用的信號相關(guān)程序,。

    1.Win32

     Win32的程序總是從WinMain開始執(zhí)行。在WinMain的代碼中,,主要功能一般有三個(gè):一是注冊窗口類,,二是在屏幕上顯示窗口,三是實(shí)現(xiàn)消息環(huán)。消息環(huán)的作用就是從應(yīng)用程序隊(duì)列中取出操作系統(tǒng)放入的消息,,從而實(shí)現(xiàn)用戶和程序之間的交互(也包括象定時(shí)器之類的非用戶輸入的消息),。應(yīng)用程序不定期地在消息環(huán)中等待消息的到來。如下所示:// 消息環(huán)

    while(GetMessage(&msg, NULL, 0, 0))

    {

    TranslateMessage(&msg);

    DispatchMessage(&msg);

    }

    這一段程序包括了形成一個(gè)標(biāo)準(zhǔn)消息環(huán)的三個(gè)基本API:GetM essage(),、TranslateMessage()和ispatchMessage()

    ,,采用加速鍵和非模式對話框時(shí)將相應(yīng)改變消息環(huán)的結(jié)構(gòu)。在Windows中,,GetMessage()是多任務(wù)的核心,。在應(yīng)用程序的消息隊(duì)列中出現(xiàn)一條消息之前,該函數(shù)并不返回任何東西,。GetMessage()的等待阻塞了當(dāng)前進(jìn)程,,因而為正在運(yùn)行的其他應(yīng)用程序提供了檢查私有消息環(huán)的機(jī)會。出現(xiàn)一條消息后,,GetMessage()將取出該消息,,并將信息存儲在一個(gè)MSG數(shù)據(jù)結(jié)構(gòu)中。對于每一條迫使退出消息環(huán),、進(jìn)程終止的消息(WM_QUIT除外),,GetMessage()返回TRUE。通常在消息環(huán)后面跟一個(gè)返回語句,,迫使WinMain()返回系統(tǒng),。緊跟著GetMessage()的TranslateMessage()對msg進(jìn)行處理并修改該數(shù)據(jù)塊的內(nèi)容。DispatchMessage()負(fù)責(zé)查找應(yīng)調(diào)用哪一個(gè)窗口過程,,這種選擇是根據(jù)msg中hwnd所標(biāo)識的窗口進(jìn)行決策,。窗口過程對消息進(jìn)行處理, 完畢后即返回到消息環(huán),, 再次執(zhí)行GetMessage(),。如下圖所示:

     為了對所關(guān)心的消息做出處理,窗口在創(chuàng)建時(shí)一定要提供一個(gè)消息回調(diào)函數(shù),,不管該創(chuàng)建過程是顯式調(diào)用還是其他API函數(shù)隱式生成,。用戶在該回調(diào)函數(shù)中要對每一個(gè)關(guān)心的消息做出判斷與處理,從C語言的觀點(diǎn)來看,,一個(gè)窗口過程(回調(diào)函數(shù))就是這樣一個(gè)函數(shù):接受四個(gè)參數(shù),,返回一個(gè)LRESULT值,一個(gè)switch語句在過程內(nèi)占用了大量的代碼以完成各個(gè)行為動作,。

    2.MFC

     雖然直接用Win32 API開發(fā)的程序運(yùn)行效率高,、條理分明,但開發(fā)起來卻較為復(fù)雜,,維護(hù)工時(shí)也耗用較多,,因此現(xiàn)在Windows環(huán)境中大部分用C++開發(fā)的應(yīng)用程序使用了微軟提供的MFC類庫。它是面向?qū)ο笤O(shè)計(jì)的,,雖然乍一看其編程風(fēng)格與Win32迥然不同,,但那是高度封裝的結(jié)果,其內(nèi)部的實(shí)現(xiàn)與Win32沒有區(qū)別,。MFC的一個(gè)主導(dǎo)設(shè)計(jì)思想就是程序框架下(CFrameWnd)的視圖/文檔模型,,同時(shí)定義了許多宏來簡化編程,其消息的傳遞也與宏息息相關(guān)(有關(guān)MFC的解剖可看侯捷先生的《深入淺出MFC》第二版),。通過使用這些宏,,應(yīng)用程序自身將維護(hù)這一張可能為數(shù)不菲的消息映射表。對于程序員來說,,只需要點(diǎn)擊鼠標(biāo)就可完成以上的工作,,開發(fā)效率有了很大的提高。3.Linux

    Linux(包括其他的Unix)和Windows的一個(gè)很大不同點(diǎn)在于其圖形界面的管理是與內(nèi)核分開的,,負(fù)責(zé)圖形操作(還包括鍵盤,、鼠標(biāo)等事件捕獲)的模塊是X Window。請注意,,此處的“Window”與微軟的Windows毫無親戚關(guān)系,。X Window包括三大部分:服務(wù)端(XServer)、客戶端(X Client)和協(xié)議(X protocol),,示意圖如下:我們平時(shí)在Linux下開發(fā)的有圖形界面的程序一般就是X Window中的客戶端程序,,相對應(yīng)的庫就是X Lib。X Lib是X Window中最低層的接口庫,,相當(dāng)于微軟Windows中的 API,。這個(gè)庫封裝了對X protocol的存取,提供了超過610個(gè)函數(shù),。由于X protocol可以在網(wǎng)絡(luò)上傳播,,因此X Window中服務(wù)器端和客戶端可以不在一臺機(jī)器上,這一點(diǎn)和微軟Windows有著很大的區(qū)別,。對比X Lib與Win32 API的處理方式,,可以發(fā)現(xiàn)雖然兩者框架不一、風(fēng)格不一,,但在流程處理上都有異曲同工之妙,。

    4.Qt

     Qt中的類庫有接近一半是從基類QObject上繼承下來,信號與反應(yīng)槽(signals/slot)機(jī)制就是用來在QObject類或其子類間通訊的方法,。作為一種通用的處理機(jī)制,,信號與反應(yīng)槽非常靈活,可以攜帶任意數(shù)量的參數(shù),,參數(shù)的類型也由用戶自定,。同時(shí)其本身也是類型安全的,,任何一個(gè)從QObject或其子類繼承的用戶類都可以使用信號與反應(yīng)槽。信號的作用如同Windows系統(tǒng)中的消息,。在Qt中,,對于發(fā)出信號的對象來說,它并不知道是誰接收了這個(gè)信號,。這樣的設(shè)計(jì)可能在某些地方會有些不便,,但卻杜絕了緊耦合,于總體設(shè)計(jì)有利,。反應(yīng)槽是用來接收信號的,, 但它實(shí)際上也是普通的函數(shù),程序員可以象調(diào)用普通函數(shù)一樣來調(diào)用反應(yīng)槽,。與信號類似的是,,反應(yīng)槽的擁有者也不知道是誰向它發(fā)出了信號。在程序設(shè)計(jì)過程中,,多個(gè)信號可以連接至一個(gè)反應(yīng)槽,,類似的,一個(gè)信號也可以連接至多個(gè)反應(yīng)槽,,甚至一個(gè)信號可以連接至另一個(gè)信號,。在Windows中,如果我們需要多個(gè)菜單都激發(fā)一個(gè)函數(shù),,一般是先寫一個(gè)共用函數(shù),,然后在每個(gè)菜單的事件中調(diào)用此函數(shù)。在Qt中如果要實(shí)現(xiàn)同樣的功能,,就可以把實(shí)現(xiàn)部分寫在一個(gè)菜單中,,然后把其他菜單與這個(gè)菜單級聯(lián)起來。雖然信號/反應(yīng)槽機(jī)制有很多優(yōu)點(diǎn),,使用也很方便,,但它

    也不是沒有缺點(diǎn)。最大的缺點(diǎn)在于要稍微犧牲一點(diǎn)性能,。根據(jù)Trolltech公司的自測,,在CPU為Intel PentiumII 500 Mhz的PC機(jī)上,對于一個(gè)信號對應(yīng)一個(gè)反應(yīng)槽的連接來說,,一秒鐘可以調(diào)用兩百萬次,;對于一個(gè)信號對應(yīng)兩個(gè)反應(yīng)槽的連接來說,一秒鐘可以調(diào)用一百二十萬次,。這個(gè)速度是不經(jīng)過連接而直接進(jìn)行回調(diào)的速度的十分之一,。請注意這里的十分之一速度比是調(diào)用速度的比較,而不是一個(gè)完整函數(shù)執(zhí)行時(shí)間的比較,。事實(shí)上一般情況下一個(gè)函數(shù)的總執(zhí)行時(shí)間大部分是在執(zhí)行部分,,只有小部分是在調(diào)用部分,,因些這個(gè)速度是可以接受的。這就象面向?qū)ο蟮木幊毯驮缧┠甑慕Y(jié)構(gòu)化編程相比一樣:程序的執(zhí)行效率并沒有提高,,反而是有所下降的,,但現(xiàn)在大家都在用面向?qū)?/p>

    象的方法編寫程序。用一部分執(zhí)行效率換回開發(fā)效率與維護(hù)效率是值得的,,況且現(xiàn)在已是P4為主流的時(shí)代,。我們先來看一個(gè)簡單的樣例:

    class Demo : public QObject

    {

    Q_OBJECT

    public:

    Demo();

    int value() const { return val; },;

    public slots:

    void setValue( int );

    signals:

    void valueChanged( int );

    private:

    int val;

    };

     由樣例可看到,,類的定義中有兩個(gè)關(guān)鍵字slots和signals,還有一個(gè)宏Q_OBJECT,。在Qt的程序中如果使用了信號與反應(yīng)槽就必須在類的定義中聲明這個(gè)宏,,不過如果你聲明了該宏但在程序中并沒有信號與反應(yīng)槽,對程序也不會有任何影響,,所以建議大家在用Qt寫程序時(shí)不妨都把這個(gè)宏加上,。使用slots定義的就是信號的功能實(shí)現(xiàn),即反應(yīng)槽,,例如:

    void Demo::setValue( int v )

    {

    if ( v != val ) {

    val = v;

    emit valueChanged(v);

    }

    }

     這段程序表明當(dāng)setValue執(zhí)行時(shí)它將釋放出valueChanged這個(gè)信號,。以下程序示范了不同對象間信號與反應(yīng)槽的連接。

    Demo a, b;

    connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));

    b.setValue( 11 );

    a.setValue( 79 );

    b.value(); // b的值將是79而不是原先設(shè)的11

     在以上程序中,,一旦信號與反應(yīng)槽連接,,當(dāng)執(zhí)行a.setValue(79)時(shí)就會釋放出一個(gè)valueChanged(int)的信號,對象b將會收到這個(gè)信號并觸發(fā)setValue(int)這個(gè)函數(shù),。當(dāng)b在執(zhí)行setValue(int)這個(gè)函數(shù)時(shí),,它也將釋放valueChanged(int)這個(gè)信號,當(dāng)然b 的信號無人接收,,因此就什么也沒干,。示意圖如下:請注意,在樣例中我們僅當(dāng)輸入變量v不等于val時(shí)才釋放信號,,因此就算對象

    a與b進(jìn)行了交叉連接也不會導(dǎo)致死循環(huán)的發(fā)生,。由于在樣例中使用了Qt特有的關(guān)鍵字和宏,而Qt本身并不包括C++的編譯器,,因此如果用流行的編譯程序(如Windows下的Visual C++或Linux下的gcc)是不能直接編譯這段代碼的,,必須用Qt的中間編譯工具moc.exe把該段代碼轉(zhuǎn)換為無專用關(guān)鍵字和宏的C++代碼才能為這些編譯程序所解析、編譯與鏈接,。

     以上代碼中信號與反應(yīng)槽的定義是在類中實(shí)現(xiàn)的,。那么,非類成員的函數(shù),,比如說一個(gè)全局函數(shù)可不可以也這樣做呢,?答案是不行,,只有是自身定義了信號的類或其子類才可以發(fā)出該種信號。一個(gè)對象的不同信號可以連接至不同的對象,。當(dāng)一個(gè)信號被釋放時(shí),,與之連接的反應(yīng)槽將被立刻執(zhí)行,就象是在程序中直接調(diào)用該函數(shù)一樣,。信號的釋放過程是阻塞的,,這意味著只有當(dāng)反應(yīng)槽執(zhí)行完畢后該信號釋放過程才返回。如果一個(gè)信號與多個(gè)反應(yīng)槽連接,,則這些反應(yīng)槽將被順序執(zhí)行,,排序過程則是任意的。因此如果程序中對這些反應(yīng)槽的先后執(zhí)行次序有嚴(yán)格要求的話,,應(yīng)特別注意,。使用信號時(shí)還應(yīng)注意:信號的定義過程是在類的定義過程即頭文件中實(shí)現(xiàn)的。為了中間編譯工具moc的正常運(yùn)行,,不要在源文件(.cpp)中定義信號,,同時(shí)信號本身不應(yīng)返回任何數(shù)據(jù)類型,即是空值(void),。如果你要設(shè)計(jì)一個(gè)通用的類或控件,,則在信號或反應(yīng)槽的參數(shù)中應(yīng)盡可能使用常規(guī)數(shù)據(jù)以增加通用性。如上例代碼中valueChanged的參數(shù)為int型,,如果它使用了特殊類型如QRangeControl::Range,,那么這種信號只能與RangeControl中的反應(yīng)槽連接。如前所述,,反應(yīng)槽也是常規(guī)函數(shù),,與未定義slots的用戶函數(shù)在執(zhí)行上沒有任何區(qū)別。但在程序中不可把信號與常規(guī)函數(shù)連接在一起,,否則信號的釋放不會引起對應(yīng)函數(shù)的執(zhí)行,。要命的是中間編譯程序moc并不會對此種情況報(bào)錯(cuò),C++編譯程序更不會報(bào)錯(cuò),。初學(xué)者比較容易忽略這一點(diǎn),,往往是程序編好了沒有錯(cuò)誤,邏輯上也正確,,但運(yùn)行時(shí)就是不按自己的意愿出現(xiàn)結(jié)果,,這時(shí)候應(yīng)檢查一下是不是這方面的疏忽。Qt的設(shè)計(jì)者之所以要這樣做估計(jì)是為了信號與反應(yīng)槽之間匹配的嚴(yán)格性,。既然反應(yīng)槽與常規(guī)函數(shù)在執(zhí)行時(shí)沒有什么區(qū)別,,因此它也可以定義成公共反應(yīng)槽(public slots)、保護(hù)反應(yīng)槽(protected slots)和私有反應(yīng)槽(private slots),。如果需要,,我們也可以把反應(yīng)槽定義成虛函數(shù)以便子類進(jìn)行不同的實(shí)現(xiàn),,這一點(diǎn)是非常有用的。只討論一下信號與反應(yīng)槽的使用好象還不過癮,,既然Qt的X11 Free版提供了源代碼,,我們就進(jìn)去看一下在QObject中connect的實(shí)現(xiàn)。由于Qt是一個(gè)跨平臺的開發(fā)庫,,為了與不同平臺上的編譯器配合,,它定義了一個(gè)中間類QMetaObject,該類的作用是存放有關(guān)信號/反應(yīng)槽以及對象自身的信息,。這個(gè)類是Qt內(nèi)部使用的,,用戶不應(yīng)去使用它。

     以下是QMetaObject的定義(為了瀏覽方便,,刪除了一部分次要代碼):

    class Q_EXPORT QMetaObject

    {

    public:

    QMetaObject( const char * const class_name, QMetaObject *superclass,

    const QMetaData * const slot_data, int n_slots,

    const QMetaData * const signal_data, int n_signals);

    virtual ~QMetaObject();

    int numSlots( bool super = FALSE ) const; /* 反應(yīng)槽的數(shù)量 */

    int numSignals( bool super = FALSE ) const; /* 信號的數(shù)量 */

    int findSlot( const char *, bool super = FALSE ) const;

    /* 根據(jù)反應(yīng)槽的名稱找到其在列表中的索引 */

    int findSignal( const char *, bool super = FALSE ) const;

    /* 根據(jù)信號的名稱找到其在列表中的索引 */

    const QMetaData *slot( int index, bool super = FALSE ) const;

    /* 根據(jù)索引取得反應(yīng)槽的數(shù)據(jù) */

    const QMetaData *signal( int index, bool super = FALSE ) const;

    /* 根據(jù)索引取得信號的數(shù)據(jù) */

    QStrList slotNames( bool super = FALSE ) const;

    /* 取得反應(yīng)槽列表 */

    QStrList signalNames( bool super = FALSE ) const;

    /* 取得信號列表 */

    int slotOffset() const;

    int signalOffset() const;

    static QMetaObject *metaObject( const char *class_name );

    private:

    QMemberDict *init( const QMetaData *, int );

    const QMetaData *slotData; /* 反應(yīng)槽數(shù)據(jù)指針 */

    QMemberDict *slotDict; /* 反應(yīng)槽數(shù)據(jù)字典指針 */

    const QMetaData *signalData; /* 信號數(shù)據(jù)指針*/

    QMemberDict *signalDict; /* 信號數(shù)據(jù)字典指針*/

    int signaloffset;

    int slotoffset;

    };

    再看一下QObject中connect的實(shí)現(xiàn),。剝?nèi)ゴ种?,函?shù)中便露出一個(gè)更細(xì)化的函數(shù):connectInternal,,它又做了哪些工作呢?讓我們看一下:

    void QObject::connectInternal( const QObject *sender, int signal_index,

    const QObject *receiver,

    int membcode, int member_index )

    {

    QObject *s = (QObject*)sender;

    QObject *r = (QObject*)receiver;

    if ( !s->connections ) {

    /* 如果某個(gè)對象有信號或反應(yīng)槽但沒有建立相互連接是不會建立連接列表的,,這樣可減少一些無謂的資源消耗 */

    s->connections = new QSignalVec( 7 );

    s->connections->setAutoDelete( TRUE );

    /* 無連接時(shí),,連接列表將被自動刪除 */

    }

    QConnectionList *clist = s->connections->at( signal_index );

    if ( !clist ) {

    /* 建立與信號源對象中某一個(gè)信號所對應(yīng)的接收對象的列表 */

    clist = new QConnectionList;

    clist->setAutoDelete( TRUE );

    s->connections->insert( signal_index, clist );

    }

    QMetaObject *rmeta = r->metaObject();

    switch ( membcode ) {

    /* 取得信號或反應(yīng)槽的數(shù)據(jù)指針 */

    case QSLOT_CODE:

    rm = rmeta->slot( member_index, TRUE );

    break;

    case QSIGNAL_CODE:

    rm = rmeta->signal( member_index, TRUE );

    break;

    }

    QConnection *c = new QConnection( r, member_index,

    rm ? rm->name : "qt_invoke", membcode );

    /* 創(chuàng)建一個(gè)新的信號/反應(yīng)槽連接 */

    clist->append( c ); /* 信號源端加入這一對連接 */

    if ( !r->senderObjects ) {

    /* 類似于信號源端,反應(yīng)槽端的連接列表也是動態(tài)創(chuàng)建的 */

    r->senderObjects = new QObjectList;

    }

    r->senderObjects->append( s ); /* 反應(yīng)槽端加入這一對連接 */

    }

    到此,,信號與反應(yīng)槽的連接已建立完畢,,那么信號產(chǎn)生時(shí)又是如何觸發(fā)反應(yīng)槽的呢?從QObject的定義中可以看出其有多個(gè)activate_signal的成員函數(shù),,這些函數(shù)都是protected的,,也即只有其自身或子類才可以使用??匆幌滤膶?shí)現(xiàn):

    void QObject::activate_signal( QConnectionList *clist, QUObject *o )

    {

    if ( !clist ) /* 有效性檢查 */

    return;

    QObject *object;

    QConnection *c;

    if ( clist->count() == 1 ) {

    /* 對某一個(gè)對象的一個(gè)具體信號來說,,一般只有一種反應(yīng)槽與之相連,這樣事先判斷一下可以加快處理速度 */

    c = clist->first();

    object = c->object();

    sigSender = this;

    if ( c->memberType() == QSIGNAL_CODE )

    object->qt_emit( c->member(), o ); /* 信號級連 */

    else

    object->qt_invoke( c->member(), o );/* 調(diào)用反應(yīng)槽函數(shù) */

    } else {

    QConnectionListIt it(*clist);

    while ( (c=it.current()) ) { /* 有多個(gè)連接時(shí),,逐一掃描 */

    ++it;

    object = c->object();

    sigSender = this;

    if ( c->memberType() == QSIGNAL_CODE )

    object->qt_emit( c->member(), o ); /* 信號級連 */

    else

    object->qt_invoke( c->member(), o ); /* 調(diào)用反應(yīng)槽函數(shù) */

    }

    }

    }

    至此我們已經(jīng)可以基本了解Qt中信號/反應(yīng)槽的流程,。我們再看一下Qt為此而新增的語法:三個(gè)關(guān)鍵字:slots、signals和emit,,三個(gè)宏:SLOT(),、SIGNAL()和Q_OBJECT。在頭文件qobjectdefs.h中,,我們可以看到這些新增語法的定義如下:

    #define slots // slots: in class

    #define signals protected // signals: in class

    #define emit // emit signal

    #define SLOT(a) "1"#a

    #define SIGNAL(a) "2"#a

    由此可知其實(shí)三個(gè)關(guān)鍵字沒有做什么事情,,而SLOT()和SIGNAL()宏也只是在字符串前面簡單地加上單個(gè)字符,以便

    程序僅從名稱就可以分辨誰是信號,、誰是反應(yīng)槽,。中間編譯程序moc.exe則可以根據(jù)這些關(guān)鍵字和宏對相應(yīng)的函數(shù)進(jìn)行“翻譯”,,以便在C++編譯器中編譯。剩下一個(gè)宏Q_OBJECT比較復(fù)雜,,它的定義如下:

    #define Q_OBJECT \

    publi \

    virtual QMetaObject *metaObject() const { \

    return staticMetaObject(); \

    }

    \

    virtual const char *className() const; \

    virtual void* qt_cast( const char* ); \

    virtual bool qt_invoke( int, QUObject* ); \

    virtual bool qt_emit( int, QUObject* ); \

    QT_PROP_FUNCTIONS

    \

    static QMetaObject* staticMetaObject(); \

    QObject* qObject() { return (QObject*)this; } \

    QT_TR_FUNCTIONS

    \

    private: \

    static QMetaObject *metaObj;

    從定義中可以看出該宏的作用有兩個(gè):一是對與自己相關(guān)的QMetaObject中間類操作進(jìn)行聲明,,另一個(gè)是對信號的釋放操作和反應(yīng)槽的激活操作進(jìn)行聲明。當(dāng)moc.exe對頭文件進(jìn)行預(yù)編譯之后,,將會產(chǎn)生一個(gè)可供C++編譯器編譯的源文件,。以上述的Demo類為例,假設(shè)它的代碼文件分別為d e m o . h和d e m o . c p p ,,預(yù)編譯后將產(chǎn)生

    moc_demo.cpp,,其主要內(nèi)容如下:

    QMetaObject *Demo::metaObj = 0;

    void Demo::initMetaObject()

    {

    if ( metaObj )

    return;

    if ( strcmp(QObject::className(), "QObject") != 0 )

    badSuperclassWarning("Demo","QObject");

    (void) staticMetaObject();

    }

    QMetaObject* Demo::staticMetaObject()

    {

    if ( metaObj )

    return metaObj;

    (void) QObject::staticMetaObject();

    typedef void(Demo::*m1_t0)(int);

    m1_t0 v1_0 = Q_AMPERSAND Demo::setValue; /* 定位反應(yīng)槽的入口 */

    QMetaData *slot_tbl = QMetaObject::new_metadata(1);

    /* 新建一個(gè)反應(yīng)槽數(shù)據(jù) */

    QMetaData::Access *slot_tbl_access = QMetaObject::new_metaaccess(1);

    slot_tbl[0].name = "setValue(int)"; /* 反應(yīng)槽名稱 */

    slot_tbl[0].ptr = *((QMember*)&v1_0);

    /* 通過反應(yīng)槽名稱可以找到反應(yīng)槽的入口指針 */

    slot_tbl_access[0] = QMetaData::Public; /* 權(quán)限類型 */

    typedef void(Demo::*m2_t0)(int);

    m2_t0 v2_0 = Q_AMPERSAND Demo::valueChanged; /* 定位信號的入口 */

    QMetaData *signal_tbl = QMetaObject::new_metadata(1); /* 新建信號數(shù)據(jù) */

    signal_tbl[0].name = "valueChanged(int)"; /* 信號名稱 */

    signal_tbl[0].ptr = *((QMember*)&v2_0);

    /* 通過信號名稱可以找到信號的入口指針 */

    metaObj = QMetaObject::new_metaobject(

    /* 創(chuàng)建一個(gè)與demo類相關(guān)的QMetaObject對象 */

    "Demo", "QObject",

    slot_tbl, 1,

    signal_tbl, 1,

    0, 0 );

    metaObj->set_slot_access( slot_tbl_access ); /* 設(shè)置權(quán)限 */

    return metaObj;

    }

    // 有信號時(shí)即激活對應(yīng)的反應(yīng)槽或另一個(gè)信號

    void Demo::valueChanged( int t0 )

    {

    activate_signal( "valueChanged(int)", t0 );

    }

    該文件中既沒有Qt特有的關(guān)鍵字,也沒有特殊的宏定義,,完全符合普通的C++語法,,因此可以順利編譯和鏈接。

    

    

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多