QT:QObject 簡單介紹

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對象,也就是說,如果寫一條下面的語句, 編譯的時候是會失敗的,

[cpp]?view plaincopy
  1. new?QObject(*new?QObjectPrivate,?NULL); ?

為了看的更清楚,我們以QWidget這個class為例說明。

QWidget是QT中所有UI控件的基類,它直接從QObject繼承而來,?

[cpp]?view plaincopy
  1. class?QWidget?:?public?QObject,?public?QPaintDevice??
  2. {??
  3. Q_OBJECT??
  4. Q_DECLARE_PRIVATE(QWidget)??
  5. //?.....................??
  6. } ?

我們看一個這個class的構造函數的代碼:

[cpp]?view plaincopy
  1. QWidget::QWidget(QWidget?*parent,?Qt::WindowFlags?f)??
  2. :?QObject(*new?QWidgetPrivate,?0),?QPaintDevice()??
  3. {??
  4. d_func()->init(parent,?f);??
  5. } ?

非常清楚,它調用了基類QObject的保護類型的構造函數,并且以 *new QWidgetPrivate 作為第一個參數傳遞進去。也就是說,基類(QObject)中的d_ptr指針將會指向一個QWidgetPrivate類型的對象。

再看QWidgetPrivate這個class的定義:

[cpp]?view plaincopy
  1. class?QWidgetPrivate?:?public?QObjectPrivate??
  2. {??
  3. Q_DECLARE_PUBLIC(QWidget)??
  4. //?.....................??
  5. }; ?

好了,這就把所有的事情都串聯起來了。

關于QWidget構造函數中的唯一的語句 d_func()->init(parent, f) 我們注意到在class的定義中有這么一句話: Q_DECLARE_PRIVATE(QWidget)

我們前面講過這個宏,當把這個宏展開之后,就是這樣的:

[cpp]?view plaincopy
  1. inline?QWidgetPrivate*?d_func()?{?return?reinterpret_cast<QWidgetPrivate?*>(d_ptr);?}??
  2. inline?const?QWidgetPrivate*?d_func()?const??
  3. {?return?reinterpret_cast<const?QWidgetPrivate?*>(d_ptr);?}?\??
  4. friend?class?QWidgetPrivate;??

很清楚,它就是把QObject中定義的d_ptr指針轉換為QWidgetPrivate類型的指針。

小結:

要理解QT Kernel的code,就必須要知道QT中每一個Object內部的數據是如何保存的,而QT沒有象我們平時寫code一樣,把所有的變量直接定義在類中,所以,不搞清楚這個問題,我們就無法理解一個相應的class。其實,在QT4.4中的類成員數據的保存方法在本質是與QT2.x中的是一樣的,就是在class中定義一個成員數據的指針,指向成員數據集合對象(這里是一個QObjectData或者是其派生類)。初始化這個成員變量的辦法是定義一個 保護類型的構造函數,然后在派生類的構造函數new一個派生類的數據成員,并將這個新對象賦值給QObject的數據指針。在使用的時候,通過預先定義個宏里面的一個inline函數來把數據指針在安全類型轉換,就可以使用了。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458356.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458356.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458356.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

利用malloc定義數組

使用malloc方法時&#xff0c;應導入文件 #include<malloc.h> 1.利用malloc定義一維數組 int *num (int *)malloc(sizeof(int)*8); // 定義一個一維數組有8個元素&#xff0c;等價于 int num[8]; 2.利用malloc定義二維數組 int **num &#xff08; int **&#xff09…

C++中基類的析構函數為什么要用virtual虛析構函數

from&#xff1a;https://blog.csdn.net/iicy266/article/details/11906457知識背景要弄明白這個問題&#xff0c;首先要了解下C中的動態綁定。 關于動態綁定的講解&#xff0c;請參閱&#xff1a; C中的動態類型與動態綁定、虛函數、多態實現 正題直接的講&#xff0c;C中基類…

第二章 Python基本元素:數字、字符串和變量

Python有哪些內置的數據類型&#xff1a; True False #布爾型 42 100000000 #整型 3.14159 1.0e8 #浮點型 abcdes #字符串 2.1 變量、名字和對象 python中統一的形式是什么&#xff1f; 對象&#xff0c;所有的對象都是以對象的形式存在…

Mac - 設置NSButton 的背景色

- (void)drawRect:(NSRect)dirtyRect {[super drawRect:dirtyRect];[[NSColor clearColor] setFill];NSRectFill(self.bounds);self.wantsLayer YES;self.layer.cornerRadius 8;self.layer.masksToBounds YES; } 轉載于:https://www.cnblogs.com/741162830qq/p/5157046.html…

C++中static關鍵字作用總結

from&#xff1a;https://www.cnblogs.com/songdanzju/p/7422380.html1.先來介紹它的第一條也是最重要的一條&#xff1a;隱藏。&#xff08;static函數&#xff0c;static變量均可&#xff09; 當同時編譯多個文件時&#xff0c;所有未加static前綴的全局變量和函數都具有全局…

C Primer Plus 第7章 C控制語句:分支和跳轉 7.4 一個統計字數的程序

2019獨角獸企業重金招聘Python工程師標準>>> 首先&#xff0c;這個程序應該逐個讀取字符&#xff0c;并且應該有些方法判斷何時停止&#xff1b;第二&#xff0c;它應該能夠識別并統計下列單位&#xff1a;字符、行和單詞。下面是偽代碼描述&#xff1a; read a cha…

深入理解extern用法

from&#xff1a;https://blog.csdn.net/z702143700/article/details/46805241一、 extern做變量聲明 l 聲明extern關鍵字的全局變量和函數可以使得它們能夠跨文件被訪問。 我們一般把所有的全局變量和全局函數的實現都放在一個*.cpp文件里面&#xff0c;然后用一個同名的*.h文…

收集整理的非常有用的PHP函數

為什么80%的碼農都做不了架構師&#xff1f;>>> 1、PHP加密解密 2、PHP生成隨機字符串 3、PHP獲取文件擴展名&#xff08;后綴&#xff09; 4、PHP獲取文件大小并格式化 5、PHP替換標簽字符 6、PHP列出目錄下的文件名 7、PHP獲取當前頁面URL 8、PHP強制下載文件 9、…

進程間的通信方式——pipe(管道)

from&#xff1a;https://blog.csdn.net/skyroben/article/details/715133851.進程間通信每個進程各自有不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到&#xff0c;所以進程之間要交換數據必須通過內核,在內核中開辟一塊緩沖區,進程A把數據從用戶空間拷到內…

bash中(),{},(()),[],[[]]的區別

前言:在bash中遇到各種括號&#xff0c;同時在進行字符數值比較判定時&#xff0c;總是不斷出現問題&#xff0c;于是通過參考《advanced bash-scripting guide》&#xff0c;同時在centos 6.7版本上進行測試&#xff0c;現況總結如下。如有紕漏&#xff0c;望指正。一.()一個命…

多進程和多線程之間的通信方式及通信實現步驟小結

進程間通信方式 # 管道( pipe )&#xff1a;管道是一種半雙工的通信方式&#xff0c;數據只能單向流動&#xff0c;而且只能在具有親緣關系的進程間使用。進程的親緣關系通常是指父子進程關系。 # 有名管道 (namedpipe) &#xff1a; 有名管道也是半雙工的通信方式&#xff0c;…

highcharts 顯示網格

2019獨角獸企業重金招聘Python工程師標準>>> xAxis: { gridLineColor: #197F07, gridLineWidth: 1 }, yAxis: { gridLineColor: #197F07, gridLineWidth: 2 }, 轉載于:https://my.oschina.net/LingBlog/blog/697885

Cheat—— 給Linux初學者和管理員一個終極命令行備忘單

編譯自&#xff1a;http://www.tecmint.com/cheat-command-line-cheat-sheet-for-linux-users/作者&#xff1a; Avishek Kumar原創&#xff1a;LCTT https://linux.cn/article-3760-1.html譯者&#xff1a; su-kaiyao原文稍有改動 當你不確定你所運行的命令&#xff0c;尤其是…

串口操作之API篇 CreateFile

CreateFile http://bbs.fishc.com/thread-72944-1-1.html(出處: 魚C論壇) ------------------------------------------------------------------------CreateFile用于打開串口,如果操作成功,返回一個句柄.1 function CreateFile(lpFileName: PChar; dwDesiredAccess, dwShareM…

云數據庫·ApsaraDB 產品6月刊

【重點關注】RDS發布新規格 RDS于5月下旬發布新產品規格&#xff0c;新規格對齊ECS配置:1.連接數大幅提升 互聯網型的應用特點是發展快速&#xff0c;在云上應用層會基于VM進行橫向擴展&#xff0c;對數據庫的要求除了資…

【同行說技術】教你玩轉iOS的5篇技術干貨

在文章《iOS從小白到大神必讀資料匯總一到四》這個系列中&#xff0c;深入介紹了iOS入門學習及進階的相關技術資料&#xff0c;今天小編繼續發布iOS學習的5篇干貨文章&#xff0c;趕緊來看看吧 &#xff01;喜歡寫博客的工程師博主可以加工程師博主交流群&#xff1a;391519124…

Qt Console Application 與 Qt GUI Application互轉

在桌面開發中&#xff0c;總的來說&#xff0c;包含兩種類型的應用程序&#xff1a;無界面的Console程序和有界面的GUI程序。Qt也不例外&#xff0c;包含Qt Console Application和Qt GUI Application。一、Qt Console Application在VS2015中創建一個Qt Console Application&…

Create Volume 操作(Part I) - 每天5分鐘玩轉 OpenStack(50)

2019獨角獸企業重金招聘Python工程師標準>>> 前面已經學習了 Cinder 的架構和相關組件&#xff0c;從本節我們開始詳細分析 Cinder 的各種操作&#xff0c;首先討論 Cinder 如何創建 volume。 Create 操作流程如下&#xff1a; 客戶&#xff08;可以是 OpenStack 最…

如何有效解決C與C++的相互調用問題

from&#xff1a;https://blog.csdn.net/gobitan/article/details/1532769在實際工作中可能經常要進行C和C的混合編程&#xff0c;C調用C語言的代碼通常都比較容易&#xff0c;但也有一些細節需要注意。C要調用C的代碼就略為麻煩一些&#xff0c;因為C不支持面向對象的特征。一…

Eclipse開發工具之崩潰和備份

1.通過在命令行中輸入“where java”&#xff0c;找到除jdk目錄下的所有java相關程序&#xff0c;直接刪掉&#xff08;一般會在C:WINDOWSsystem32下&#xff09;以后再也不用怕找不到目錄了 2.內存不足&#xff0c;打開Eclipse目錄下的eclipse.ini&#xff0c;把里面的-Xmx512…