1. 程式人生 > >Qt 物件資料的宣告和使用

Qt 物件資料的宣告和使用

Qt 庫物件資料的宣告和使用


作者: Venus 
C++程式編譯效率
每個C++類的寫法通常情況下:
class A
{
public:
    void something();
private:
    string m_Name; // 姓名
    bool m_Sex;    // 性別
    int m_Age;     // 年齡
};就是在類定義的時候,直接把類成員變數定義成私有成員,更有甚者 定義為Public.(你是否也是如此,呵呵).然而QT卻不是這麼做的。
Qt 2.x類成員變數的定義方法




在定義class的時候(在.h檔案中),只包含有一個類成員變數,只是定義一個成員資料指標,然後由這個指標指向一個數據成員物件,




這個資料成員 物件包含所有這個class的成員資料,然後在class的實現檔案(.cpp檔案)中,定義這個私有資料成員物件。示例程式碼如下:
// File name:  person.h
 
struct PersonalDataPrivate; // 宣告私有資料成員型別
 
class Person
{
public:
 
Person ();   // constructor
virtual ~Person ();  // destructor
void setAge(const int);
int getAge();
 
private:
 
PersonalDataPrivate* d;
};
 
// File name:  person.cpp
 
struct 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;
}然而這種方法,初心者閱讀起來很麻煩,變數檢索困難,相似程式碼很多。 然而這種方式在編譯工程時體現了它強大的優勢。






強大的優勢


1、減少標頭檔案的依賴性
把具體的資料成員都放到cpp檔案中去,這樣,在需要修改資料成員的時候,只需要改cpp檔案而不需要標頭檔案,這樣就可以避免一次因為標頭檔案的修改而導 致所有包含了這個檔案的檔案全部重新編譯一次,尤其是當這個標頭檔案是非常底層的標頭檔案和專案非常龐大的時候,優勢明顯。
同時,也減少了這個標頭檔案對其它標頭檔案的依賴性。可以把只在資料成員中需要用到的在cpp檔案中include一次就可以,在標頭檔案中就可以儘可能的減少include語句
2、增強類的封裝性


這種方法增強了類的封裝性,無法再直接存取類成員變數,而必須寫相應的 get/set 成員函式來做這些事情。
關於這個問題,仁者見仁,智者見智,每個人都有不同的觀點。有些人就是喜歡把類成員變數都定義成public的,在使用的時候方便。只是我個人不喜歡這種方法,當專案變得很大的時候,有非常多的人一起在做這個專案的時候,自己所寫的程式碼處於底層有非常多的人需要使用(#include)的時候,這個方法的 弊端就充分的體現出來了。
Qt 4.4.x類成員變數的定義方法
在 QT 4.4 中,使用了非常多的巨集來做事,這憑空的增加了理解 QT source code 的難度,不知道他們是不是從MFC學來的。就連在定義類成員資料變數這件事情上,也大量的使用了巨集。
在這個版本中,類成員變數不再是給每一個class都定義一個私有的成員,而是把這一項common的工作放到了最基礎的基類 QObject 中,然後定義了一些相關的方法來存取如下:
//------------------------------------------------------
// file name: qobject.h
 
class QObjectData
{
public:
virtual ~QObjectData() = 0;
// 省略
};
 
class QObject
{
Q_DECLARE_PRIVATE(QObject)
 ...
public:
    Q_INVOKABLE explicit QObject(QObject *parent=0);


    virtual ~QObject();
    virtual bool event(QEvent *);
    virtual bool eventFilter(QObject *, QEvent *);
 ....
protected:
 
QObject(QObjectPrivate &dd, QObject *parent = 0);
QObjectData *d_ptr;
}這些程式碼就是在 qobject.h 這個標頭檔案中的。在 QObject class 的定義中,我們看到,資料員的定義為:QObjectData *d_ptr; 定義成 protected 型別的就是要讓所有的派生類都可以存取這個變數,而在外部卻不可以直接存取這個變數。而 QObjectData 的定義卻放在了這個標頭檔案中,其目的就是為了要所有從QObject繼承出來的類的成員變數也都相應的要在QObjectData這個class繼承出 來。而純虛的解構函式又決定了兩件事:
1、這個class不能直接被例項化。換句話說就是,如果你寫了這麼一行程式碼,new QObjectData, 這行程式碼一定會出錯,compile的時候是無法過關的。
2、當 delete 這個指標變數的時候,這個指標變數是指向的任意從QObjectData繼承出來的物件的時候,這個物件都能被正確delete,而不會產生錯誤,諸如,記憶體洩漏之類的。
Q_DECLARE_PRIVATE 該巨集的定義如下:
#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 QObjectPrivate : public QObjectData
{
Q_DECLARE_PUBLIC(QObject)
public:
QObjectPrivate(int version = QObjectPrivateVersion);
virtual ~QObjectPrivate();
}


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 (d_ptr); }
inline const QWidgetPrivate* d_func() const
{ return reinterpret_cast (d_ptr); } \
friend class QWidgetPrivate;


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