我们在创建Qt Widgets Application的时候,会默认生成一些代码,本篇主要是对其涉及到的内容进行解析。
文章目录
- [1. 创建Qt Widgets Application程序-WidgetTest](#1. 创建Qt Widgets Application程序-WidgetTest)
- [2. 首先从main.cpp开始分析:](#2. 首先从main.cpp开始分析:)
- [3. Qt对象模型:对象树与元对象系统](#3. Qt对象模型:对象树与元对象系统)
-
- [3.1 对象树的概念](#3.1 对象树的概念)
- [3.2 对象树的示例程序](#3.2 对象树的示例程序)
- [3.3 元对象系统的概念](#3.3 元对象系统的概念)
- [4. WidgetTest.h](#4. WidgetTest.h)
- [5. WidgetTest.cpp](#5. WidgetTest.cpp)
1. 创建Qt Widgets Application程序-WidgetTest
会创建WidgetTest.pro(配置文件,此处不做解析)、WidgetTest.h、main.cpp、WidgetTest.cpp(我这里习惯使用代码编写界面,没有自动生成界面文件,代码会与使用UI设计器进行界面存在差异,需要的话后期再做解析)
WidgetTest.h
cpp
#ifndef WIDGETTEST_H
#define WIDGETTEST_H
#include <QWidget>
class WidgetTest : public QWidget
{
Q_OBJECT
public:
WidgetTest(QWidget *parent = 0);
~WidgetTest();
};
#endif // WIDGETTEST_H
main.cpp
cpp
#include "WidgetTest.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
WidgetTest w;
w.show();
return a.exec();
}
WidgetTest.cpp
cpp
#include "WidgetTest.h"
WidgetTest::WidgetTest(QWidget *parent)
: QWidget(parent)
{
}
WidgetTest::~WidgetTest()
{
}
2. 首先从main.cpp开始分析:
cpp
#include "WidgetTest.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
WidgetTest w;
w.show();
return a.exec();
}
main函数是程序的入口,这个就不做详细介绍,下面对QApplication a(argc,argv);'和return a.exec();进行解析
(1)QApplication a(argc,argv);
定义一个 Qt 应用程序对象,它的构造函数接收和 main 函数一样的参数,是 Qt 图形界面程序的入口,就像 main 函数是普通 C++ 程序的入口一样。
(2)return a.exec();
会进入 Qt 应用程序的事件循环函数等待用户操作和系统的消息然后进行处理
。
常见的c/c++语言main函数中都是直接return 0 的,程序直接退出。但图形程序通常需要与用户交互,不会自动关闭,而是一直等待用户操作。如果用户点击窗口的关闭按钮, 程序才会结束并返回一个值,默认是 0 。
qt中main函数开始就只是进行一个初始化工作,然后将控制权交给qt,接下来所有事件的处理就只剩下qt的事件循环处理了。
(3)WidgetTest w;
不管这个w实例是dialog或者Widgrt,都会在栈上创建,而不是new出来,这是为啥?
原因:main的窗口是顶层窗口,应用程序会形成以此为根基的对象树,顶层窗口关闭后,如果没有销毁,而是隐藏了,那么在退出的时候没有手动销毁的话,其延伸出来的任何部件都不会释放。所以,在顶层窗口上创建的部件,只 需要制定其parent的参数为this,也就是widget为父窗口就不需要进行delete操作,Qt的对象树使用析构函数会替我们自己完成。
w.show();是显示方法,用来显示窗口
WidgetTest w;
的详细解释涉及到了对象树与元对象系统,下面对其进行详细介绍
3. Qt对象模型:对象树与元对象系统
3.1 对象树的概念
Qt中使用对象树(object tree)来组织和管理所有的QObject类及其子类的对象。当创建一个QObject时,如果使用了其他的对象作为其父对象(parent),那么这个 QObject就会被添加到父对象的children()列表中,这样当父对象被销毁时,这个QObject也会被销毁。实践表明,这个机制非常适合于管理GUI对象。例如,一个 QShortcut(键盘快捷键)对象是相应窗口的一个子对象,所以当用户关闭了这个窗口 时,这个快捷键也可以被销毁。
QWidget作为能够在屏幕上显示的所有部件的基类,扩展了对象间的父子关系。 一个子对象一般也就是一个子部件,因为它们要显示在父部件的区域之中。例如,当关闭一个消息对话框(message box)后要销毁它时,消息对话框中的按钮和标签也会被销毁,这也正是我们所希望的,因为按钮和标签是消息对话框的子部件。当然,也可以自己来销毁一个子对象。关于这一部分内容,可以在帮助索引中査看Object Trees & Ownership关键字。
3.2 对象树的示例程序
新建Qt Gui应用,项目名称为myOwnership,基类选择QWidget,然后类名保持Widget不变。完成后向项目中添加新文件,模板选择C+ +类,类名为MyButton,基类为QPushButton,类型信息选择"继 承自QWidget"。添加完文件后将mybuuon. h文件修改如下:
cpp
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QPushButton>
#include <QDebug>
class MyButton : public QPushButton
{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
~MyButton();
};
#endif // MYBUTTON_H
这里主要是添加了析构函数的声明。然后到mybutton. cpp文件中,修改如下:
cpp
#include "mybutton.h"
MyButton::MyButton(QWidget *parent) :
QPushButton(parent)
{
}
MyButton::~MyButton()
{
qDebug() << "delete button";
}
这里添加了析构函数的定义,这样当 MyButton 的对象被销毁时,就会输出相应的信息。
下面到widget.cpp文件中,修改如下:
cpp
#include "widget.h"
#include "ui_widget.h"
#include "mybutton.h"
#include <QDebug>
#include <QHBoxLayout>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//创建按钮部件,指定widget为父部件
MyButton *button = new MyButton(this);
button->setText(tr("button"));
}
Widget::~Widget()
{
delete ui;
qDebug() << "delete widget";
}
当Widget窗口被销毁时,将输出信息。下面运行程序,然后关闭窗口,在Qt Creator的应用程序输出栏中的输出信息为:
cpp
delete widget
delete button
可以看到,当关闭窗口后,因为该窗口是顶层窗口,所以应用程序要销毁该窗口部件(如果不是顶层窗口,那么关闭时只是隐藏,不会被销毁),而当窗口部件销毁时会自动销毁其子部件。这也就是为什么在Qt中经常只看到new操作而看不到delete操作 的原因。
再来看一下main.cpp文件,其中Widget对象是建立在栈上的:
cpp
Widget w;
w.show();
这样对于对象w,在关闭程序时会自动销毁。而对于Widget中的部件,如果是在堆上创建(使用new操作符),那么只要指定Widget为其父窗口就可以了,也不需要进行delete操作。当对象w销毁时会自动销毁它的所有子部件,这些都是Qt的对象树所完成的。
所以,对于规范的Qt程序,我们要在main()函数中将主窗口部件创建在栈上,例如"Widget w;",而不要在堆上进行创建(使用new操作符)。对于其他窗口部件,可以使用new操作符在堆上进行创建,不过一定要指定其父部件,这样就不用使用delete操作符来销毁该对象了。
3.3 元对象系统的概念
Qt中的元对象系统(Meta-Object System)提供了对象间通信的信号和槽机制、运行时类型信息和动态属性系统。元对象系统是基于以下3个条件的:
- 该类必须继承自QObject类;
- 必须在类的私有声明区声明Q_OBJECT宏(在类定义时,如果没有指定public或者private,则默认为private);
- 元对象编译器Meta-Object Compiler(moc),为QObject的子类实现元对象特性提供必要的代码。
其中,moc工具读取一个C+ +源文件,如果它发现一个或者多个类的声明中包含有Q_OBJECT宏,便会另外创建一个C+ +源文件(就是在项目目录中的debug目录 下看到的以moc开头的C+ +源文件),其中包含了为每一个类生成的元对象代码。 这些产生的源文件或者被包含进类的源文件中,或者和类的实现同时进行编译和链接。
元对象系统主要是为了实现信号和槽机制才被引入的,不过除了信号和槽机制以外,元对象系统还提供了其他一些特性:
- QObjeCt::metaObject()函数可以返回一个类的元对象,它是QMetaObject类的对象;
- QMetaObject::className()可以在运行时以字符串形式返回类名,而不需要C+ +编辑器原生的运行时类型信息(RTTI)的支持
- QObject:: "inherits()函数返回一个对象是否是QObject继承树上一个类的实例的信息;
- QObject: :tr()和QObject: :trUtf8()迸行字符串翻译来实现国际化;
- QObject::setProperty()和QObject::property()通过名字来动态设置或者获取对象属性;
- QMetaObject: :newlnstance()构造该类的一个新实例。
除了这些特性,还可以使用qobject_cast()函数来对QObject类进行动态类型转换,这个函数的功能类似于标准C+ +中的dynamic_cast()函数,但它不再需要RTTI的支持。这个函数尝试将它的参数转换为尖括号中的类型的指针,如果是正确的类型,则返回一个非零的指针,如果类型不兼容则返回0。例如:
cpp
QObject *obj = new MyWidget;
QWidget *widget = qobject_cast<QWidget *>(obj);
信号和槽机制是Qt的核心内容,而信号和槽机制必须依赖于元对象系统,所以它是Qt中很关键的内容。这里只是说明了它的一些应用,关于它的具体实现机制,这里不再讲述。关于元对象系统的具体描述,可以在Qt中查看The Meta Object System关键字。
4. WidgetTest.h
cpp
#ifndef WIDGETTEST_H
#define WIDGETTEST_H
#include <QWidget>
class WidgetTest : public QWidget
{
Q_OBJECT
public:
WidgetTest(QWidget *parent = 0);
~WidgetTest();
};
#endif // WIDGETTEST_H
以下代码用于避免重复定义,不细讲
cpp
#ifndef WIDGETTEST_H
#define WIDGETTEST_H
......
#endif // WIDGETTEST_H
以下代码定义了公共一个继承自QWidget的WidgetTest类
cpp
class WidgetTest : public QWidget
{
Q_OBJECT
public:
WidgetTest(QWidget *parent = 0);
~WidgetTest();
};
-
Q_OBJECT
宏定义是为了实现元对象系统,具体见3.3; -
WidgetTest(QWidget *parent = 0);
是一个有参构造,其形参是其基类指针,基类指针初始定义为0,相当于一个默认参数。 -
~WidgetTest();
析构函数就不细讲,
5. WidgetTest.cpp
cpp
#include "WidgetTest.h"
WidgetTest::WidgetTest(QWidget *parent)
: QWidget(parent)
{
}
WidgetTest::~WidgetTest()
{
}
对于WidgetTest::WidgetTest(QWidget *parent) : QWidget(parent) {}
的含义进行解释:
在讲解原因之前,先请大家看下面的一个例子:
cpp
#include <iostream>
using namespace std;
class Base
{
public:
Base() :m_num(0){
cout << "this is Base()" << endl;
}
Base(int val):m_num(val){
cout << "this is Base(int val)" << endl;
}
private:
int m_num;
};
上方代码定义了一个基类Base,并且有两个构造函数,一个是默认构造函数,一个是有一个整型参数的构造函数。
cpp
class BaseChild: public Base
{
public:
BaseChild(){
cout << "this is BaseChild()" << endl;
}
BaseChild(int val): Base(val){
cout << "this is BaseChild(val)" << endl;
}
private:
int m_num;
};
上方代码定义了一个BaseChild类,并继承Base类,同样的,它也定义了两个构造函数,一个默认,一个有整型参数。
cpp
int main(int argc, char *argv[])
{
BaseChild child1;
BaseChild child2(5);
return 0;
}
main函数实例化了两个子类实例,child1,child2。child1调用默认构造函数。child2调用有整型参数的构造函数。
现在,我们运行程序,会有如下打印:
cpp
this is Base()
this is Basechild()
this is Base(int val)
this is Basechild(val)
看到了吗,我们发现:
- 创建child1时,是先调用了Base的默认构造函数,再调用自己的默认构造函数
- 创建child2时,是先调用了Base(int)这个构造函数,再调用自己的整型参数构造函数。
所以我们回头看BaseChild的构造函数
cpp
BaseChild(int val): Base(val){
cout << "this is BaseChild(val)" << endl;
}
细心的同学,可能早就发现了,初始化列表中的Base(val)正是调用了我们Base基类的有参构造函数,而这样的写法就刚好是我们开头代码中的那段Widget::Widget(QWidget *parent) :QWidget(parent)
。
所以Widget是调用了QWidget下面的构造函数
cpp
QWidget(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
所以得出如下总结:
总结: 如果不指定构造函数,则派生类会调用基类的默认构造函数 · 派生类构造函数的初始化列表只能初始化派生类成员,不能直接初始化继承成员,如果想 要调用基类的有参构造函数,则可以在派生类的初始化列表中显示指定
关于初始化列表部分,可以参考:C++57个入门知识点_31 初始化列表及派生类中的函数隐藏(初始化列表:用于调用父类有参构造并初始化、用于自身成员初始化、对于常量成员初始化;函数隐藏:不同作用域里两个数据名称相同,由内向外隐藏)_初始化列表调用父类构造-CSDN博客
好的,那么我们又提出一个问题,"调用QWidget(parent)这个构造函数,QWidget父类都做了哪些动作呢?"
下面是QWidget源码中的一部分节选:
cpp
QWidget::QWidget( QWidget *parent, const char *name, WFlags f )
: QObject( parent, name ), QPaintDevice( PDT_WIDGET ),
pal( parent ? parent->palette() // use parent's palette
: *qApp->palette() ) // use application palette
{
if ( parent ) {
QChildEvent *e = new QChildEvent( Event_ChildInserted, this );
QApplication::postEvent( parent, e );
}
}
大家从上面可以看出,如果parent参数非空的话,那么该构造函数使用了其父窗口的调色板,并且发送了QChildEvent事件,这会让新的窗口成为parent所指窗口的子窗口,那么当父窗口被删除时,子窗口也会自动的被删除。
这其实是用到了Qt对象树的概念。
本部分转自:正确理解Widget::Widget(QWidget *parent) :QWidget(parent)这句话,也可参考简单总结的:正确理解Widget::Widget(QWidget *parent) :QWidget(parent)这句话-CSDN博客