大家好,欢迎来到IT知识分享网。
1.应用程序封装成so动态库时,需要提供头文件,此时为了把私有数据封装起来,就需要把私有数据封装成private的类,把它放在cpp文件而非h文件中,
这样就把私有数据封装起来不给用户看了。
2.在私有数据修改时,不用重新编译所有文件,只需要编译这个动态库就可以了,因为头文件并没有改变。
D指针/私有实现
使用D指针:
class MyClass
{
public:
MyClass();
~MyClass();
private:
MyClassPrivate * const d_ptr;
Q_DECLARE_PRIVATE(MyClass);
};
class MyClassPrivate;
定义了一个指针d_ptr指向私有实现类,然后用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;
只要在类的头文件中使用这两个宏,就可以通过函数直接得到实体类和句柄类的实际类型了,而且这里还声明了友元,使得数据类和句柄
类连访问权限也不用顾忌了
然后这个私有类的实现如下所示:
class MyClassPrivate
{
public:
MyClassPrivate(MyClass *parent);
private:
MyClass * const q_ptr;
Q_DECLARE_PUBLIC(MyClass);
int myVar;
};
这里的q_ptr指针就是指向公开的接口了,然后Q_DECLARE_PUBLIC宏则定义了辅助函数并声明了友元类:
#define Q_DECLARE_PUBLIC(Class) \
inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
friend class Class;
而我们还可以用Q_D和Q_Q两个宏来进一步简化访问:
#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()
以后局部变量可千万不能声明为d和q了。
qt的 Q_PRIVATE_SLOT是用来干什么的:
每个qobject, qwidget这样的类中 ,把内部数据和内部函数都写在private类里面了 ,然后用这个宏把private类里面的函数声明为主类的slot
QApplication构造过程
QApplication::QApplication(int &argc, char **argv)
: QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient) //第一步) //第二步
{
Q_D(QApplication); //第三步
d->construct(); //第四步
}
第一步构造了QApplicationPrivate的实例,其中GuiClient是一个枚举常量,表示应用程序的类型,这里我们不用关心。说道QApplicationPrivate,
如果你看过Qt的源码,你会发现很多类都配对有相应的Private类,这是Qt在封装底层实现时经常用的一种模式,Handle-Body Design Pattern,
也就是说你在xx类中看不到具体的实现,具体的实现都封装在xxPrivate类中,而在xx类的方法只是调用xxPrivate中相应的方法。
第二步用QApplicationPrivate的实例构造了QCoreApplication,这里调用的是QCoreApplication的QCoreApplication::QCoreApplication
(QCoreApplicationPrivate &p)构造函数,代码如下:
QCoreApplication::QCoreApplication(QCoreApplicationPrivate &p)
: QObject(p, 0)
{
init();
// note: it is the subclasses’ job to call
// QCoreApplicationPrivate::eventDispatcher->startingUp();
}[/code]上面代码中的init初始化了一些资源,其中包括事件分发器。
第三步定义了d,这里是通过宏Q_D来实现的,其中宏Q_D的定义如下:]#define Q_D(Class) Class##Private * const d = d_func()所以这里的d即
是调用父类构造函数QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient)时创建的QApplicationPrivate实例。
第四步调用d的construct函数,也就是QApplicationPrivate的实例的construct函数。
QObject解析
QObject是Qt类体系的唯一基类
QObject的大小是8,除了虚函数表指针需要的4个字节以外,另外的4个字节是:
QObjectData *d_ptr;
QObject中的数据被封装在QObjectData类中了。Qt中有一个很重要的设计模式就是句柄实体模式,以QObject为基类的类一般都是句柄类,只有一个指针指
向一个实体类,在实体类中保存全部的数据,而且这个指针还是私有的。Qt的基类其实有两个,一个是QObject,这是句柄类的唯一基类,另一个是QObjectData,
这是实体类的基类。
QObjectData类定义如下:
class QObjectData {
public:
QObject *q_ptr;
};
QObject *q_ptr;指针指向实体类对应的句柄类,这和上面的代码QObjectData *d_ptr;遥相呼应,使得句柄类和实体类可以双向的引用。q指的是Qt接口类,d指的
是Data数据类。
d_func和q_func函数是非常常用的函数,可以理解为一个是得到数据类,一个是得到Qt接口类
实体类派生关系中多了一个QObjectPrivate,这个类封装了线程处理,信号和槽机制等具体的实现,可以说它才是Qt实体类中真正起作用的基类,而QObjectData不过是一层浅浅的数据封装而已
看看在Qt中句柄类和实体类这两条体系是如何构造的?
QPushButton* quit = new QPushButton(“Quit”);
创建一个Qt的按钮,背后大有玄机
QPushButton::QPushButton(const QString &text, QWidget *parent)
: QAbstractButton(*new QPushButtonPrivate, parent)
首先QPushButton的构造函数中调用了QAbstractButton的构造函数,同时马上new出来一个QPushButtonPrivate实体类,然后把指针转换为引用传递给QAbstractButton
QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
: QWidget(dd, parent, 0)
QAbstractButton的构造函数中继续调用基类QWidget的构造函数,同时把QPushButtonPrivate实体类指针继续传给基类
QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WFlags f)
: QObject(dd, ((parent && (parent->windowType() == Qt::Desktop)) ? 0 : parent)), QPaintDevice()
QWidget继续做着同样的事情
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
终于到了基类QObject,这里就直接把QPushButtonPrivate的指针赋值给了d_ptr(还记得这个变量名称吧)
最终在QPushButton构造时同时产生的new QPushButtonPrivate被写到了QObject中的d_ptr中
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
Q_D(QObject);
::qt_addObject(d_ptr->q_ptr = this);
QThread *currentThread = QThread::currentThread();
d->thread = currentThread ? QThreadData::get(currentThread)->id : -1;
Q_ASSERT_X(!parent || parent->d_func()->thread == d->thread, “QObject::QObject()”,
“Cannot create children for a parent that is in a different thread.”);
if (parent && parent->d_func()->thread != d->thread)
parent = 0;
if (d->isWidget) {
if (parent) {
d->parent = parent;
d->parent->d_func()->children.append(this);
}
// no events sent here, this is done at the end of the QWidget constructor
} else {
setParent(parent);
}
}
然后执行QObject的构造函数,这里主要是一些线程的处理,先不理它
QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WFlags f)
: QObject(dd, ((parent && (parent->windowType() == Qt::Desktop)) ? 0 : parent)), QPaintDevice()
{
d_func()->init((parent && parent->windowType() == Qt::Desktop ? parent : 0), f);
}
然后是QWidget的构造函数,这里调用了数据类QWidgetPrivate的init函数,这个函数不是虚函数,因此静态解析成QWidgetPrivate的init函数调用
QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
: QWidget(dd, parent, 0)
{
Q_D(QAbstractButton);
d->init();
}
然后是QAbstractButton的构造函数,这里调用了数据类QAbstractButton的init函数,这个函数不是虚函数,因此静态解析成QAbstractButton的init函数调用
QPushButton::QPushButton(const QString &text, QWidget *parent)
: QAbstractButton(*new QPushButtonPrivate, parent)
{
Q_D(QPushButton);
d->init();
setText(text);
}
然后是QPushButton的构造函数,这里调用了数据类QPushButton的init函数,这个函数不是虚函数,因此静态解析成QPushButton的init函数调用
现在的事情很清楚了,总结一下:
QPushButton在构造的时候同时生成了QPushButtonPrivate指针,QPushButtonPrivate创建时依次调用数据类基类的构造函数
QPushButton的构造函数中显示的调用了基类的构造函数并把QPushButtonPrivate指针传递过去,QPushButton创建时依次调用接口类基类的构造函数
在接口类的构造函数中调用了平行数据类的init函数,因为这个函数不是虚函数,因此就就是此次调用了数据类的init函数
需要指出的是,为什么QPushButtonPrivate实体类指针要转换为引用呢?为什么不是直接传递指针?结论是人家喜欢这样写,就是不传指针传引用,而且要用一个*new之类的怪异语法,
真叫人没有办法,其实这里用指针是一样的,代码看起来也自然一些.
Qt对象有自己的内存管理策略,当父对象被析构时,会将它的所有子对象同时析构。因此,只要一个Qt对象的父对象设置适当,它就会在父对象析构时自动析构,不需要做多余的delete操作。
注意: 由于窗口内的部件会在窗口析构时自动用delete操作符析构,因此这些部件不能定义为成员变量,只能由new操作符动态生成。
各种Qt对象类的构造函数一般都可以接受一个Qt对象指针作为参数,用于设置父对象,这样在Qt对象生成时就确定了它的父对象。另外,有些函数可以改变Qt对象的父对象,如布局类
addWidget函数,它会使加入布局的窗口部件成为布局所在窗口的子对象。窗口内的布局和部件是分开管理的,只有最顶级的布局的父对象是窗口,其他布局的父对象都是包含它的布局,
而窗口部件不管在哪个布局内,其父对象都是窗口本身。
使用Qt进行编程必须对 Qt 中常用的类有一定的了解。这些类可以分成两种:一种不是从 QObject 类派生出来的,用来表示各种基本的数据对象,如字符串、图像、字体等,这里将它们通
称为基本类;另一种都是从 QWidget 类派生出来的,它们表示一个顶级窗口或者窗口部件,这里将它们统称为窗口类
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/14498.html