QObject?是所有Qt對象的基類。
QObject?是Qt模塊的核心。它的最主要特征是關于對象間無縫通信的機制:信號與槽。
使用connect()建立信號到槽的連接,使用disconnect()銷毀連接,使用blockSignals()暫時阻塞信號以避免無限通知循環,使用connectNotify()和disconnectNotify()追蹤連接。
QObject?以對象樹的形式組織起來。當為一個對象創建子對象時,子對象會自動地添加到父對象的children()列表中。父對象擁有子對象的所有權,比如父對象可以在自己的析構函數中刪除它的孩子對象。使用findChild()或findChildren()通過名字和類型查詢孩子對象。
每個對象都有objectName(),也可以通過metaObject()獲得它的類名。可以使用inherits()檢測對象的類是否在某個類的繼承層次結構中。
對象被刪除時,發射destroyed()信號,捕捉這個信號以免懸掛對這個對象的引用。
QObject?通過event()接收事件,通過installEventFilter()和enventFilter()過濾來自其他對象的事件。childEvent()可以捕捉來自子對象的事件。
QTimer可以實現高水平的定時器。
宏Q_OBJECT是任何實現信號、槽或屬性的強制性要求。不管是否需要實現這些機制,都要求使用這個宏。否則會引發一些函數的奇怪行為。
所有的Qt部件都繼承自QObject?。函數isWidgetType()檢測對象是否一個部件。它比以下這些語句要運行得更快:qobject_cast(obj)?或者obj->inherits("QWidget")。
children()返回QObjectList,它是QList的typedef。
沒有復制構造函數和賦值操作符
QObject?既沒有復制構造函數也沒有賦值操作符。實際上它們使用宏Q_DISABLE_COPY()聲明在私有部份。所有派生自QObject?的對象都使用這個宏聲明復制構造函數和賦值操作符為私有。
這樣的主要結果是,在使用QObject子對象作為值的地方要使用QObject類型的指針。因為沒有構造函數,你不能把QObject?的子對象作為值存儲在容器類中,必須存儲它的指針。
自動連接
Qt的元對象系統自動地為QObject?的子類和他們的對象建立信號和槽的連接。只要有名字的對象被定義,槽就會自動擁有簡單的約定命名,連接在運行時間通過函數QMetaObject::connectSlotsByName()執行。
國際化
所有的?QObject?支持Qt的轉換特性。能夠使用戶界面在不同語言間進行轉換。為了將用戶可見的文本得到轉換,必須將它們包裹到函數tr()中。
?
轉自:http://blog.chinaunix.net/uid-374124-id-4121508.html
-------------------------------------------------------------------------------------------------------------------------------------------------
深入了解Qt主要內容來源于Inside Qt系列,本文做了部分刪改,以便于理解。在此向原作者表示感謝!
QObject這個 class 是 QT 對象模型的核心,關于對象模型可以閱讀C++對象模型詳解,絕大部分的 QT 類都是從這個類繼承而來。這個模型的中心特征就是一個叫做信號和槽(signaland slot)的機制來實現對象間的通訊,你可以把一個信號和另一個槽通過 connect(…) 方法連接起來,并可以使用disconnect(…) 方法來斷開這種連接,你還可以通過調用blockSignal(…) 這個方法來臨時的阻塞信號。對于信號和槽可以閱讀Qt 信號和槽函數。
QObject 的對象樹機制:
當你創建一個 QObject 并使用其它對象作為父對象時,這個對象會自動添加到父對象的children() list 中。父對象擁有這個對象,比如,它將在它的析構函數中自動刪除它所有的 child對象。你可以通過 findChild() 或者findChildren()函數來查找一個對象。每個對象都有一個對象名稱(objectName())和類名稱(class name), 他們都可以通過相應的 metaObject 對象來獲得。你還可以通過 inherits() 方法來判斷一個對象的類是不是從另一個類繼承而來。當對象被刪除時,它發出destroyed()信號。你可以捕獲這個信號來避免對QObject的無效引用。QObject可以通過event()接收事件并且過濾其它對象的事件。詳細情況請參考installEventFilter()和eventFilter()。對于每一個實現了信號、槽和屬性的對象來說,Q_OBJECT 宏都是必須要加上的。
QObject 類的實現文件一共有四個:
* qobject.h,QObject class 的基本定義,也是我們一般定義一個類的頭文件。
* qobject.cpp,QObject class 的實現代碼基本上都在這個文件。
* qobjectdefs.h,這個文件中最重要的東西就是定義了 QMetaObject class,這個class是為了實現 signal、slot、properties,的核心部分。
* qobject_p.h,這個文件中的 code 是輔助實現QObject class 的,這里面最重要的東西是定義了一個 QObjectPrivate 類來存儲 QOjbect 對象的成員數據。
理解這個 QObjectPrivate class 又是我們理解 QT kernel source code 的基礎,這個對象包含了每一個 QT 對象中的數據成員,好了,讓我們首先從理解 QObject 的數據存儲代碼開始我么的 QT Kernel Source Code 之旅。
我們知道,在C++中,幾乎每一個類(class)中都需要有一些類的成員變量(class member variable),在通常情況下的做法如下:
class Person { private: string mszName; // 姓名 bool mbSex; // 性別 int mnAge; // 年齡 };
在QT中,卻幾乎都不是這樣做的,那么,QT是怎么做的呢?
幾乎每一個C++的類中都會保存許多的數據,要想讀懂別人寫的C++代碼,就一定需要知道每一個類的的數據是如何存儲的,是什么含義,否則,我們不可能讀懂別人的C++代碼。在這里也就是說,要想讀懂QT的代碼,第一步就必須先搞清楚QT的類成員數據是如何保存的。
為了更容易理解QT是如何定義類成員變量的,我們先說一下QT 2.x 版本中的類成員變量定義方法,因為在 2.x 中的方法非常容易理解。然后在介紹 QT 4.4 中的類成員變量定義方法。
QT 2.x 中的方法
在定義class的時候(在.h文件中),只包含有一個,只是定義一個成員數據指針,然后由這個指針指向一個數據成員對象,這個數據成員對象包含所有這個class的成員數據,然后在class的實現文件(.cpp文件)中,定義這個私有數據成員對象。示例代碼如下:
// File name: person.hstruct PersonalDataPrivate; // 聲明私有數據成員類型class Person { public:Person (); // constructor virtual ~Person (); // destructor void setAge(const int); int getAge();private:PersonalDataPrivate* d; };//--------------------------------------------------------------------- // File name: person.cppstruct PersonalDataPrivate // 定義私有數據成員類型 { string mszName; // 姓名 bool mbSex; // 性別 int mnAge; // 年齡 };// constructor Person::Person () { d = new PersonalDataPrivate; };// destructor Person::~Person () { delete d; };void Person::setAge(const int age) { if (age != d->mnAge) d->mnAge = age; }int Person::getAge() { return d->mnAge; }
在最初學習QT的時候,我也覺得這種方法很麻煩,但是隨著使用的增多,我開始很喜歡這個方法了,而且,現在我寫的代碼,基本上都會用這種方法。具體說來,它有如下優點:
*?減少頭文件的依賴性
把具體的數據成員都放到cpp文件中去,這樣,在需要修改數據成員的時候,只需要改cpp文件而不需要頭文件,這樣就可以避免一次因為頭文件的修改而導致所有包含了這個文件的文件全部重新編譯一次,尤其是當這個頭文件是非常底層的頭文件和項目非常龐大的時候,優勢明顯。
同時,也減少了這個頭文件對其它頭文件的依賴性。可以把只在數據成員中需要用到的在cpp文件中include一次就可以,在頭文件中就可以盡可能的減少include語句
*?增強類的封裝性
這種方法增強了類的封裝性,無法再直接存取類成員變量,而必須寫相應的 get/set 成員函數來做這些事情。
關于這個問題,仁者見仁,智者見智,每個人都有不同的觀點。有些人就是喜歡把類成員變量都定義成public的,在使用的時候方便。只是我個人不喜歡這種方法,當項目變得很大的時候,有非常多的人一起在做這個項目的時候,自己所寫的代碼處于底層有非常多的人需要使用(#include)的時候,這個方法的弊端就充分的體現出來了。
還有,我不喜歡 QT 2.x 中把數據成員的變量名都定義成只有一個字母d,看起來很不直觀,尤其是在search的時候,很不方便。但是,QT kernel 中的確就是這么干的。
QT 4.4.x 中的方法
在 QT 4.4 中,類成員變量定義方法的出發點沒有變化,只是在具體的實現手段上發生了非常大的變化,在 QT 4.4 中,使用了非常多的宏來做事,這憑空的增加了理解 QT source code 的難度,不知道他們是不是從MFC學來的。就連在定義類成員數據變量這件事情上,也大量的使用了宏。
在這個版本中,類成員變量不再是給每一個class都定義一個私有的成員,而是把這一項common的工作放到了最基礎的基類 QObject 中,然后定義了一些相關的方法來存取,好了,讓我們進入具體的代碼吧。
// file name: qobject.hclass QObjectData { public: virtual ~QObjectData() = 0; // 省略 };class QObject { Q_DECLARE_PRIVATE(QObject)public:QObject(QObject *parent=0);protected:QObject(QObjectPrivate &dd, QObject *parent = 0); QObjectData *d_ptr; }
這些代碼就是在 qobject.h 這個頭文件中的。在 QObject class 的定義中,我們看到,數據員的定義為:QObjectData*d_ptr; 定義成 protected 類型的就是要讓所有的派生類都可以存取這個變量,而在外部卻不可以直接存取這個變量。而 QObjectData 的定義卻放在了這個頭文件中,其目的就是為了要所有從QObject繼承出來的類的成員變量也都相應的要在QObjectData這個class繼承出 來。而純虛的析構函數又決定了兩件事:
* 這個class不能直接被實例化。換句話說就是,如果你寫了這么一行代碼,new QObjectData, 這行代碼一定會出錯,compile的時候是無法過關的。
* 當 delete 這個指針變量的時候,這個指針變量是指向的任意從QObjectData繼承出來的對象的時候,這個對象都能被正確delete,而不會產生錯誤,諸如,內存泄漏之類的。
我們再來看看這個宏做了什么,Q_DECLARE_PRIVATE(QObject)
#define Q_DECLARE_PRIVATE(Class) \inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \friend class Class##Private;
這個宏主要是定義了兩個重載的函數,d_func(),作用就是把在QObject這個class中定義的數據成員變量d_ptr安全的轉換成為每一個具 體的class的數據成員類型指針。我們看一下在QObject這個class中,這個宏展開之后的情況,就一幕了然了。
Q_DECLARE_PRIVATE(QObject)展開后,就是下面的代碼:
inline QObjectPrivate* d_func() { return reinterpret_cast<QObjectPrivate *>(d_ptr); } inline const QObjectPrivate* d_func() const { return reinterpret_cast<const QObjectPrivate *>;(d_ptr); } \ friend class QObjectPrivate;
宏展開之后,新的問題又來了,這個QObjectPrivate是從哪里來的?在QObject這個class中,為什么不直接使用QObjectData來數據成員變量的類型?
還記得我們剛才說過嗎,QObjectData這個class的析構函數的純虛函數,這就說明這個class是不能實例化的,所以,QObject這個class的成員變量的實際類型,這是從QObjectData繼承出來的,它就是QObjectPrivate !
這個 class 中保存了許多非常重要而且有趣的東西,其中包括 QT 最核心的 signal 和slot 的數據,屬性數據,等等,我們將會在后面詳細講解,現在我們來看一下它的定義:
下面就是這個class的定義:
class QObjectPrivate : public QObjectData { Q_DECLARE_PUBLIC(QObject)public:QObjectPrivate(int version = QObjectPrivateVersion); virtual ~QObjectPrivate(); // 省略 }
那么,這個 QObjectPrivate 和 QObject 是什么關系呢?他們是如何關聯在一起的呢?
接上節,讓我們來看看這個 QObjectPrivate 和 QObject 是如何關聯在一起的。
// file name: qobject.cpp QObject::QObject(QObject *parent) : d_ptr(new QObjectPrivate) { // ……………………… }QObject::QObject(QObjectPrivate &dd, QObject *parent) : d_ptr(&dd) { // ………………… }
從第一個構造函數可以很清楚的看出來,QObject class 中的 d_ptr 指針將指向一個 QObjectPrivate 的對象,而QObjectPrivate這個class是從QObjectData繼承出來的。
這第二個構造函數干什么用的呢?從 QObject class 的定義中,我們可以看到,這第二個構造函數是被定義為protected 類型的,這說明,這個構造函數只能被繼承的class使用,而不能使用這個構造函數來直接構造一個QObject對象,也就是說,如果寫一條下面的語句, 編譯的時候是會失敗的,
- new?QObject(*new?QObjectPrivate,?NULL); ?
為了看的更清楚,我們以QWidget這個class為例說明。
QWidget是QT中所有UI控件的基類,它直接從QObject繼承而來,?
- class?QWidget?:?public?QObject,?public?QPaintDevice??
- {??
- Q_OBJECT??
- Q_DECLARE_PRIVATE(QWidget)??
- //?.....................??
- } ?
我們看一個這個class的構造函數的代碼:
- QWidget::QWidget(QWidget?*parent,?Qt::WindowFlags?f)??
- :?QObject(*new?QWidgetPrivate,?0),?QPaintDevice()??
- {??
- d_func()->init(parent,?f);??
- } ?
非常清楚,它調用了基類QObject的保護類型的構造函數,并且以 *new QWidgetPrivate 作為第一個參數傳遞進去。也就是說,基類(QObject)中的d_ptr指針將會指向一個QWidgetPrivate類型的對象。
再看QWidgetPrivate這個class的定義:
- class?QWidgetPrivate?:?public?QObjectPrivate??
- {??
- Q_DECLARE_PUBLIC(QWidget)??
- //?.....................??
- }; ?
好了,這就把所有的事情都串聯起來了。
關于QWidget構造函數中的唯一的語句 d_func()->init(parent, f) 我們注意到在class的定義中有這么一句話: Q_DECLARE_PRIVATE(QWidget)
我們前面講過這個宏,當把這個宏展開之后,就是這樣的:
- inline?QWidgetPrivate*?d_func()?{?return?reinterpret_cast<QWidgetPrivate?*>(d_ptr);?}??
- inline?const?QWidgetPrivate*?d_func()?const??
- {?return?reinterpret_cast<const?QWidgetPrivate?*>(d_ptr);?}?\??
- friend?class?QWidgetPrivate;??
很清楚,它就是把QObject中定義的d_ptr指針轉換為QWidgetPrivate類型的指針。
小結:
要理解QT Kernel的code,就必須要知道QT中每一個Object內部的數據是如何保存的,而QT沒有象我們平時寫code一樣,把所有的變量直接定義在類中,所以,不搞清楚這個問題,我們就無法理解一個相應的class。其實,在QT4.4中的類成員數據的保存方法在本質是與QT2.x中的是一樣的,就是在class中定義一個成員數據的指針,指向成員數據集合對象(這里是一個QObjectData或者是其派生類)。初始化這個成員變量的辦法是定義一個 保護類型的構造函數,然后在派生類的構造函數new一個派生類的數據成員,并將這個新對象賦值給QObject的數據指針。在使用的時候,通過預先定義個宏里面的一個inline函數來把數據指針在安全類型轉換,就可以使用了。