教你学习QT -- 深入理解QObject类

一、概述

        QObject是一个类,是QT所有类的父类。

        QObject作为QT中比较核心的基类,其作用是非常大的,并且其的属性成员函数能实现的功能也是非常多,非常复杂的。在学习QT底层框架时了解这个类是不可少的。以下是把这个类的功能、以及属性成员的函数功能都来介绍一遍。这样在使用其它继承这个类的类时,能够更好的理解。

        (1)QObject是QT对象模型中核心的角色。最重要的特性是有一个强大的机制,这个机制可以无缝的进行对象之间的通信(我们所说的信号与槽)。就是说,QObject这个类引入了信号与槽的机制。

        可以使用Connect()函数将信号连接到槽,同时。可以使用disconnect()函数断开信号与槽的链接

        为了避免永无止境的通知循环,还可以使用blockSignals()函数来临时阻塞信号(在不断开信号与槽的链接下,让信号不发生信号去触发槽)。

        并且可以使用connectNotify()和disconnectNotify()函数来跟踪信号的链接情况。什么时候链接成功,什么时候断开连接。

        (2)对象在对象树中组织自己。当使用QObject创建了一个对象,并且使用这个对象作为父对象创建另一个QObject对象时,后面创建的对象作为子对象,前面一个作为父对象。子对象将自己添加到父对象的children()列表中(父对象可以有很多的子对象,所以是列表)。此时,父对象获得子对象的所有权。也就是说,在父对象删除自己时,父对象在析构函数中自动删除它的子对象(自动完成的,不需要在析构函数中做什么)。

        在对象树中,可以通过对象的名称使用findchild()函数或者findchildren()函数来查找对象

        每一个对象都有一个对象名字,就是上面说的对象名称(这里的对象名称,不是我们在创建一个对象时看到的名称,那个是变量名)。除了有对象名字。对象的类也有相应的类名。可以通过metaobject()函数来找到

        可以判断两个类是否是继承关系。可以使用inherits()来判断对象的类是否继承了QObject类

        (3)当一个对象被删除时,它会发生一个destroyed()信号。可以捕获这个信号来判断这个对象是否还存在。避免对Object对象的悬空引用。

        

        (4)对象还可以接收事件,可以通过event()函数来接收事件。还可以过滤其它对象的事件,可以使用installEveentFilter()和eventFilter()来过滤事件。(对象可以发事件,对象也可以接收事件,对象还可以过滤事件。如B对象还可以过滤A对象发给C对象的事件)。

        

        (5)QObject在QT中,还提供了基本的定时器支持,如果需要使用更高极的定时器直接QTimer这个类即可。

        (6)要注意的是:Q_OBJECT这个宏,是对于任何实现信号、槽或属性的对象都是强制性的。就需要把这个宏加上。

        建议在QObject的所有子类中都是添加这个宏,不管子类们是否使用信号、槽或者属性。因为不这样做,可能会导致某些函数表现出奇怪的行为。

        那么QT是怎么知道使用这个宏的子类就能使用信号、槽或者属性的呢?QT提供了一个元对象编译器,这个元对象编译器就和C语言的编译过程中的预处理一样。先运行元对象编译器在运行C++编译器。

        (7)所有QT的小部件(控件类),也都是继承QObject这个类。判断一个对象是否是一个控件,可以使用iswidgetType()这个函数

注:以上提到的函数,都属于QObject类的成员函数。

1.1、在栈上创建对象

        说明:在栈上创建对象是指在函数内部创建的,属于局部变量,程序在只执行完函数后,对象会被删除。如下

1.2、在堆上创建对象

        说明:在堆上创建对象是指在全局或局部变量创建的指针。是需要手动去删除的。而不会自动删除。如下

二、属性成员函数讲解

因为我们不能去修改QObject的成员函数,所以我们直接定义一个类去继承QObject这个类。为Myobject。

2.1、对象名称函数--objectName

        在QObject类中,定义了使用这个类声明相关对象时,为这个对象设置一个名称的相关函数。

具体的函数有objectName()查看对象名。setobjectName()设置对象名,objectNameChanged()对象名发生改变时会发出的一个信号。

        注意,如果定义的对象不使用setobjectName()去设置一个名称,则对象名默认是一个空字符。具体使用方法如下。

        

2.2、QObject 的构造函数

        因为我们新定义的类是完全继承QObject的。所以我们的Myobject这个类的构造函数的形参是和父类的构造函数的形参是一样。在定义子类的对象时,通过子类的形参传入到父类上,并且是先执行父类的构造函数在执行子类的构造函数的。这个是C++的基础,在这里提一下。

        构造函数形参如下。

子类构造函数定义

        在调用子类构造函数时,会把子类的形参一并传给父类的形参。所以定义子类对象时,QObiect对象的对象树依然成立。

说明:根据构造函数的形参可以看出,形参是QObject型的指针,并且默认指向为空。根据指针名parent是父的意思。在这里就是指父对象的意思。并且在我们创建的对象时,是没有进行传参进去的。此时默认也是为空。如下。

2.2.1、对象树

        对象树是QObject类管理对象的一种方式。这个对象树的构成是在创建对象时,根据传入构造函数的形参就以及确定的了。

        所谓对象树,就是树状型的,有头有分支。在对象上表现为有父对象,有子对象,有子对象的子对象等等。

        父对象是子对象的拥有者,比如说一个窗口,窗口上面有几个按钮。那么这个窗口就是父对象。子对象就是两个按钮。当我们关闭窗口时,窗口上面的按钮也会被关闭。所以在上面介绍过。当删除父对象时,子对象也会被删除。

        所以可以把父对象看成是一个容器,用来装对象的。当然父对象也还有自己的特性。除了容器特性还有其它特性。

        如果定义的一个对象,没有传参进去。那这个对象就在最顶层。如果这个对象是一个控件型的对象(widget),那么这个控件在最上层,它就是窗口了(window)。其它子对象就是窗口上面控件(如按钮这些)。

2.2.1.1、父对象

        父对象,是对象树中的最上层。在对象定义中体现为,这个对象在其它对象创建时,把它当作形参传入到构造函数。或者是在这个对象定义时,没有传入形参(说明这个对象没有父对象。它就是最顶层的)。如下

        

2.2.1.2、子对象

        子对象,是指在创建对象时,给构造函数传入有参,并且这个参是已经定义的对象。此时前面以及定义的对象作为父对象。后面定义的做为子对象。如下

在删除父对象时,也会删除子对象。从析构函数可以看出。因为我们都知道,在删除对象时,会执行析构函数的。如下

         如果不是父子关系,删除O1时,并不会删除O2.如下。

        

2.3、QObject的析构函数

        析构函数是在删除对象时执行的程序。当删除对象后,会把和这个对象相关的东西都取消掉,如对象下的子对象会一并被删除。与该对象连接的信号与槽这些要会被断开连接。

2.4、connec()连接函数

        connect()连接函数是QT的一大特色。这个其实是QT对C++语言的扩展,使用meta object compiler,uesr interface compiler这两个小工具。相当于QT自己做了这两个小编译器。与C++的编译器不一样。是配合C++编译器对程序进行编译的。

        信号与槽:signal and slot。连接函数就是把信号与槽进行连接的。我们在上面介绍时提过。QT有一个很强大的机制。就是无缝的进行对象之间的通信。这个通信就是信号与槽的信息交互。在这个通信中的信号与槽,是支持属于同一个对象的。也支持对象之间的(必须是同一类对象,因为这个连接函数是属于QObject这个类的,只要进行信号与槽的连接相应的对象是继承这个类就行。)同一个对象:是指信号和槽都是一个对象里面的。对象之间:指信号和槽可以分别是两个对象的。比如信号属于对象A,槽属于对象B。这样就是有B对象发送信号,B对象进行接收,槽函数操作。

        发送为信号,接收为槽。

        什么是信号与槽:信号和槽都分别是一个独立的函数,这个函数都是属于对象里面的成员函数。然后在QT的各种类中,都有相应的一个信号的声明与定义以及槽函数的声明。在使用原有的信号与槽时,我们只需要把信号与槽连接在一起并且重新定义槽函数(在槽函数里面编写我们接收到信号后需要做的事情即可)。

        当然使用人家类中定义好的信号,信号连接到的槽函数也可以是我们后期自己声明定义的槽函数。而且信号也是可以我们根据需要来声明定义自己的信号,然后把自己定义的信号在连接到其它槽都是可以的。

        一般来说,信号只有声明和形参。没有定义的实体。而槽函数,需要有定义的实体。

        然后信号和槽的函数的形参数据是基本一样的,就是在信号函数里面传入什么,槽函数的形参数据也是和信号的数据一样。

        槽函数在一些连接函数原型里面,可以直接是普通的函数,不需要添加关键字声明。有一些连接函数原型就不能是普通函数,相应加相关的关键字声明才行。

        信号与槽的调用执行:既然信号与槽都是函数,那肯定都是需要去调用的。信号作为一个对象里面的函数,我们在程序执行的过程中,在需要的情况下去调用了这个信号函数,此时就相当于发送信号了。然后接收对象在受到信号后,就会去调用槽函数了。在QT界面中,拿按钮来说。当按下按钮时,就会去调用按钮类下的一个信号函数。如果是我们自己写的程序中,需要去发送信号的时候,就在直接去调用信号函数就可以了,自己调用的信号函数可以是人家写好的(如按钮按下的信号函数,并不一定要在UI界面上按下按钮才能调用,也可以自己在程序里面任意调用。),也可以是自己定义的。然后接收的对象在接收到信号后就去调用相应槽函数。

2.4.2、connect()函数的定义

        由QObject类的声明可知,connect()是一个重载函数,如下

        说明:可以看到connect()有很多的重载的函数。它们的形参都有差异。系统会根据我们带入的形参去执行相关的程序函数。

        那么多的重载函数,它们的形参都不一样,那么我们使用的时候怎么去带入形参去选择函数呢。可以看出,虽然它们的形参都不同,但是它们的形参架构其实是一样的。可以看到基本上形参都是五个,其中前面两个为要发送的信号地址和这个信号是那个对象的对象地址。第三和第四个则是要连接的槽的地址和这个槽属于那个对象的对象地址。最后面一个是数据类型吧。

        形参的框架都一样,区别在于不同重载函数传入的型号或者方式的差异而已。

        以下是单独介绍各个函数原型的形参原理和使用方法!

2.4.2.1、函数原型1

函数原型定义如下。

    static QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);

        说明:这个函数是一个static静态类型的函数,是不需要定义对象就可以通过类名在类外进行引用的。

        这个函数会创建一个东西的连接,是什么的连接呢?是指发送的对象的一个指定类型的信号。到接收对象的一个函数。

        参数说明:这个函数的返回值是一个Connection型,其结果是连接的结果(0或者1)。

  形参1: const QObject *sender 是发送信号的对象地址。

形参2:const char *signal 是要发送的信号地址,这里是传入一个字符串地址。

形参3:const QObject *receiver 是接收到信号的对象地址。

形参4:const char *member 是接收信号的对象在接收到信号后,该对象会响应的一个函数地址(槽)。这里传入一个字符串地址。

形参5:Qt::ConnectionType = Qt::AutoConnection 是信号与槽连接的一个类型。这是一个枚举,固定了几种类型。默认的类型是AutoConnection

ConnectionType枚举包含的类型如下

说明:ConnectionType类型一共有5个值。每个类型值代表的意思如下。

        AutoConnection(自动的):自动的类型,也是默认选择的类型。判断发送的信号对象与接收函数的对象是不是都在同一个线程里面。如果在同一个线程里面就使用DirectConnection(直接连接)这个类型。如果不在同一个线程里面就使用QueuedConnection(排队连接)这个类型。

        DirectConnection(直接连接):槽函数和信号函数是在同一个线程里面的。当发送信号时,接收到后会立即调用槽函数了。需要执行完成当前槽函数后才会执行线程后面的程序。

        QueuedConnection(排队连接):槽函数和信号函数的对象不在同一个线程。发送信号后,这个槽函数不会立即调用执行,而是继续执行这个线程后面的程序,需要当程序轮询执行到接收信号的对象槽函数所在的线程时才会执行。没轮询到就不执行。

        BlockingQueuedConnection(阻塞排队连接):与上面的相似,都是跨线程,发送信号的对象和接收信号的对象不在同一个线程。但是又有区别。这个的信号的发送对象所在的线程需要等待。等待槽函数线程执行完相应的槽函数后在唤醒发送信号所在线程继续执行该线程的程序。(比如:发送对象1在线程1,接收对象2在2号线程。此时在1号线程发出信号,把信号放入消息队列,然后等待2号线程应答唤醒,才会继续执行这个线程后面的程序。2号线程从信息队列中取出信号,然后执行槽函数,执行完槽函数后唤醒1号线程)。(是2和3的组合吧)

        

UniqueConnection(独立连接):这是一个判断标志,判断是不是独立的连接。当一个信号以及连接了一个槽。如果使用这个类型,在去使用相同的信号去连接相同的槽,就会判断本次连接前有没有以及连接的。如果有这个就连接不上。(不给重复连接的意思)。不使用这个类,重复连接就没事。这个类型是需要配合上面三个使用的,不单独使用。

注意:这个函数的形参传入的信号地址与槽函数的地址是以字符串的地址的形式传入的,是比较老的方式。但是我们在程序里面,真实的信号与槽都是函数。那么怎么把函数转成字符串地址呢。在QT中给出了两个宏,SIGNAL()与SLOT()。我们把信号函数与槽函数分别放到这两个宏里面,就会返回如函数形参一样的字符串地址类型了。

      函数使用方式如下。

说明:上面定义了两个对象,对象类型是QLabel和QScroBar两个不同的类型。我们知道,连接函数connect是属于QObject类的成员函数。那么这个函数连接的信号与曹的对象必须是QObject型的对象才可以,或者是继承了QObject这个类的子类才行。这里这两个对象既然可以进行连接,那么必然这两个对象的类肯定是继承了QObject这个类的,属于子类。

        QScroBar是一个滚动条类型对象,它作为要发送信号的对象。

        QLabel是一个标签类型对象,它作为信号接收的对象。

        QScroBar要发送的信号是滚动条数据发生变换是会触发的一个信号。这个信号函数的形参是当前滚动条的数值。

        QLabel作为接收方,使用的槽函数是一个添加了关键字声明为槽函数的函数,这个函数是设置标签的值。函数的形参是要设置的值。

        上面的作用就是:当滚动条滑动后数值发生了改变,会在标签上面显示当前滚动条的值。

        思路:滚动条发生改变,发出一个信号,标签对象接收到这个信号后去执行设置标签数值的函数。

        注意的是:在这个函数原型中,连接的槽函数不能是普通函数,只能是添加了关键字声明为槽函数的函数,如果需要使用普通函数作为槽函数,那只能通过我们自己重新声明和定义一个槽函数,然后在新定义的槽函数里面引用普通的函数。信号与槽都是传入字符串地址的。需要经过两个宏把信号与槽函数转成字符串。然后宏里面传入的信号与槽函数的形参只需要填写数据类型就可以,不需要填写变量。如下形式是错误的

2.4.2.2、函数原型2

        函数原型如下:

    inline QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;

        说明:这个函数是没有使用static这个关键字来声明的,说明这个函数只能通过对象的方式来进行调用。

        这个函数原型的形参与函数原型1的形参相比,少了一个接收信号的对象的地址。然后其它四个形参以及类型都是一样的。这个函数的形参只有发送信号的对象地址,信号地址,槽函数地址和类型。并没有接收信号的对象地址,其中信号与槽函数的形参还是字符串的格式。

        因为这个函数的需要通过对象去访问。然后使用对象去调用这个函数时,这个对象就是接收信号的对象了。

2.4.2.3、函数原型3

        函数原型如下:

static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, 
                                        const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection);

        说明:这个函数是一个static静态类型的函数,是不需要定义对象就可以通过类名在类外进行引用的。

        这个函数原型的形参与函数原型1的形参相比,信号发送的对象地址和信号接收对象的地址是一样的。区别在于这个原型函数的发送信号与接收槽函数的形参是一个成员函数的指针(函数地址)。而函数原型1的则是字符串。其它都一样。

        使用方法如下。

        说明:在传入信号指针时,需要使用信号发送对象的类来做作用域到信号函数,并且取地址即可。函数接收也是一样。因为这里传入的是成员函数的指针,是函数的地址,所以这里不需要考虑信号与槽函数的形参(函数原型1是需要的,这也是区别之一),在调用时加入即可。

2.4.2.4、函数原型4

        函数原型如下:

static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor);

        说明:这个函数是一个static静态类型的函数,是不需要定义对象就可以通过类名在类外进行引用的。

        这个函数原型的形参与函数原型3对比,信号发送对象与发送信号的格式是一样,区别在于这个函数原型的接收信号的对象并没有了。只有一个函数地址作为形参。

这个函数地址(不是成员函数),支持命名函数格式,也支持匿名函数格式,因为lamda表达式也是一个函数地址。

2.4.2.4.1、命名函数

        这个函数地址不属于任何类里的成员函数,只是一个普通的函数(在C++称命名函数)。使用方式如下。

        说明,在关联的槽函数只是一个普通的命名函数时,当信号发生后,就会去执行关联到的命名函数了。命名函数的形参建议与信号函数的形参一致,这样才能使用信号传过来的形参数据。如果不需要信号的形参数据就不用一样。如下。

        说明:直接定义一个没有形参的命名函数也是可以。

2.4.2.4.2、匿名函数(lamda表达式)

        匿名函数,是指一个没有名称的函数(没有函数名)。那么怎么去描述一个没有名称的函数呢?这里使用的就是lamda表达式去描述   [] () {} ;小括号就是函数的形参,大括号就是函数的函数体,然后函数名和函数返回值使用中括号表示。这就是lamda表达式的语法结构。或者在中括号里面带=号也可以,如下。[=] () {}。中括号里面可以带很多东西的,如对象,对象地址都可以。具体的去看更详细的lamda介绍,默认什么都不写。

使用方式如下。这是匿名函数带形参的。

匿名函数不带形参的如下.

.

中括号带等号的如下。

2.4.2.5、函数原型5

        函数原型如下:

static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, 
                                const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection);

        

        这个函数原型与函数原型4是差不多的。形参中,发送信号的对象与要发送的信号传入格式是一样的(对象地址和成员函数地址)。接收的槽函数也是一样的,是函数地址(一样是普通的函数,不属于任何类的,独立的,在C++里面是命名函数,或者匿名函数的格式)。区别在于多了第三个形参const QObject *context。这个形参传入的是一个对象的地址。但这个对象不属于接收信号的对象。因为在函数原型4和5这两个中,并没有接收的对象。const QObject *context这个对象是指槽函数是在这个线程下执行的。

        我们根据前面的介绍,可以知道对象在发送信号后程序有两种执行机制,一种是槽函数的执行与发送信号的对象在同一个线程,另一种是槽函数与发送信号的对象不在同一线程。

        然后这个函数原型的第三个形参,就指定槽函数在const QObject *context这个对象下的线程执行。函数原型4的,槽函数的执行是默认与发送信号的对象在同一个线程的。

应用如下。

        说明:此时匿名函数是需要程序轮询到O2这个对象所在的线程才会执行。有可能o1和o2是同一线程,有可能不是。

2.5、其它函数讲解

注:就把QObject类当初一个普通的类讲解,讲解它里面的各种成员函数的作用、使用方法。

2.5.1、connectNotify /连接通知

        函数原型如下:

[virtual protected] void QObject::connectNotify(const QMetaMethod &signal)

        首先这是一个虚函数,是由继承QObject的子类来重写的。

        这是一个通知函数,是什么的通知函数呢?是信号连接成功的通知。什么时候会执行这个函数呢。就是要发送信号的对象,连接到其它对象上面了(这里是槽函数的对象)。那这个发送信号的对象就会去执行这个虚函数。

        使用方式如下。

(1)首先重写connectNotify函数。

(2)然后在连接信号与槽时,会由信号发送的对象去调用这个虚函数

        说明:大致意思是,发送信号的对象在连接到其它对象的槽函数后,发送信号的对象就会收到一个通知,这个通知就是触发虚函数了。

2.5.2、disconnect 断开连接函数

        说明,信号与曹可以通过connect函数来连接,也可以使用disconnect函数来断开连接。disconnect的函数原型也是有很多的,很多函数原型的形参是与connect函数原型的形参是一样的。基本上使用什么原型的连接函数,就使用对应的断开连接原型就可以。当然,断开连接函数除了有与连接函数原型一样的原型以外,还有其它的原型,方便更多的操作。具体的看帮助即可。这里就不一一举例了。

2.5.3、disconnectNotify 断开连接通知

函数原型:

[virtual protected] void QObject::disconnectNotify(const QMetaMethod &signal)

        说明:这是一个虚函数,由QObject的子类重写。它与连接通知函数一样的性质。在断开连接后,信号这边的对象会收到一个通知,会去执行这个函数。

2.5.4、blockSignals阻塞信号

函数原型:

bool blockSignals(bool b) noexcept;

        说明:这个函数是阻塞信号的发送。并且不会断开信号与槽的连接。

        这个函数的形参是一个boot类型。true为阻塞。false为非阻塞。

2.6、对象树的基本信息

        说明:根据前面对对象树的介绍,可知对象是由对象树显示的。这里介绍的就是如何显示对象树的信息。

2.6.1、dumpObiectInfo() 对象信息

函数原型如下:

void dumpObjectInfo() const;

函数使用方法如下

说明:dumpObjectInfo这个函数是显示对象信息的。由上面可知,对象的信息包括对象名称、对象的输出信号与对应接收信号的对象名称与槽函数、对象的输入信号(其它对象连接信号到本对象的信号)等。

        因为有时候我们在声明对象时,是不去设置对象名称的,那如果不设置对象名称,那对象信息里面的表达如下。

2.6.2、dumpObjectTree对象树信息

函数原型如下:

void dumpObjectTree() const;

说明:这个函数是显示一个对象树的信息,就是一个对象树下面的子对象的框架。如下

2.7、对象的属性

        说明:C/C++语言是静态语言,编译型语言。具体表示如下
定义一个结构体:

struct A{

int a;

int b;

}

此时可以访问结构体里面的a和b两个变量,但是如果想访问其它变量如c,则需要重新在结构体里面添加c的变量。不然是不可以访问的。这就是编译型语言。

        动态语言,则是可以在程序运行过程中直接去添加。不需要先添加在编译才可以。虽然QT是用C++语言的,但是QT却是吸收有动态语言的优点。QT有一个动态属性的特点。

2.7.1、setProperty设置属性

说明:这是一个设置属性的函数,这个函数的形参是一个枚举型的QVariant,这个枚举里面有很多的数据类型。说明这个函数可以把属性设置的多样性。

函数原型:

bool setProperty(const char *name, const QVariant &value);

使用方式如下:

删除属性,如下直接传入空的QVariant就可以。

        2.7.2、dynamicPropertyNames属性查询

        说明:这个函数是返回一个对象里面属性链表,链表里面就是查询对象的所有属性。

函数原型:

QList<QByteArray> dynamicPropertyNames() const;

如下

        说明:仅仅是获取获取到属性的名称,并没有其它的信息。

2.7.3、property获取属性

说明:这是一个获取属性的值的函数,根据属性名称来获取属性值。返回值是QVariant这个枚举。

函数原型:

QVariant property(const char *name) const;

它的返回值是一个枚举,比较抽象。可以直接转换。如下

内容太多。先发布,更多后续的在更新!

  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值