Qt系列文章006-Qt元对象介绍

本文详细介绍了Qt的元对象系统,包括QObject类、元对象编译器MOC、信号与槽机制、属性系统以及动态类型转换。通过QPerson类的案例,展示了如何使用Q_OBJECT宏、Q_PROPERTY、Q_CLASSINFO等来实现对象间的通信和属性管理。此外,还讨论了如何利用元对象系统进行类型投射和获取类的元对象信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 前言

  Qt是一个用标准C++编写的跨平台开发类库,它对标准C++进行了扩展,引入了元对象系统、信号与槽、属性等特性,使应用程序的开发变得更高效。本章将开始介绍Qt的这些核心特点,对于理解和编写高效的QtC++程序是大有帮助的。

  本章还介绍 <QtGlobal>头文件 中Qt的一些全局定义,包括数据类型、函数和宏等,介绍Qt的容器类及其相应迭代器的使用方法。这些全局定义和容器类在程序中经常用到,了解其原理便于理解后面遇到的一些实例程序。

公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。

公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶,走上财务自由之路。

官方店:https://shop114595942.taobao.com//

2 正文概述

  这里啰嗦一下,这边的概述讲述的是下面整个核心机制的介绍概述

  Qt本身并不是一种编程语言,它实质上是一个跨平台的C++开发类库,是用标准C++ 编写的类库,它为开发GUI应用程序非GUI应用程序提供了各种类。Qt对标准C++进行了扩展,引入了一些新的概念和功能,例如信号与槽、对象属性等。Qt的元对象编译器(Meta-ObjectCompiler,MOC)是一个预处理器,在源程序被编译前先将这些Qt特性的程序转换为标准C++兼容的形式,然后再由标准C++编译器进行编译。这就是为什么在使用信号与槽机制的类里,必须添加一个 Q_ OBJECT 宏的原因,只有添加了这个宏,moc才能对类里的信号与槽的代码进行预处理。QtCore模块 是Qt类库的核心,所有其他模块都依赖于此模块,如果使用qmake来构建项目,QtCore模块则是被自动加入的。Qt为C++语言增加的特性就是在QtCore模块里实现的,这些扩展特性由Qt的元对象系统实现,包括信号与槽机制、属性系统、动态类型转换等。

3 什么是元对象系统

  Qt的元对象系统(Meta-ObjectSystem)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。

元对象系统由以下三个基础组成。

  • QObject类是所有使用元对象系统的类的基类。

  • 在一个类的private 部分声明Q_OBJECT宏,使得类可以使用元对象的特性,如动态属性、信号与槽。

  • MOC(元对象编译器) 为每个QObject的子类提供必要的代码来实现元对象系统的特性。构建项目时,MOC工具读取C++ 源文件,当它发现类的定义里有Q_OBJECT宏时,它就会为这个类生成另外一个包含有元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被编译和连接。


除了信号与槽机制外,元对象还提供如下一些功能:

  • QObject:metaObject) 函数返回类关联的元对象,元对象类QMetaObject包含了访问元对象的一些接口函数,例如QMetaObjet::className()函数可在运行时返回类的名称字符串。

    Q0bject *obj = new QPushButton;
    obj ->meta0bject ()->className(); //返回"QPushButton"
    
  • QMetaObject::newInstance( ) 函数创建类的一个新的实例。

  • QObject::inherits(const char *className) 函数判断一个对象实例是否是名称为className的类或QObject的子类的实例。例如:

    QTimer *timer = new QTimer;			// QTimer 是Qobject的子类
    timer->inherits ("QTimer") ;		//返回true
    timer->inherits ("QObject") ;		//返回true
    timer->inherits ("QAbstractButton");//返回false, 不是QAbstractButton的子类
    
  • QObject:tr()和QObjec::trUtf8() 函数可翻译字符串,用于多语言界面设计,在后面章节会专门介绍多语言界面设计。

  • Qbjcet:setProperty()QObjet:property() 函数用于通过属性名称动态设置和获取属性值。对于QObject及其子类,还可以使用qobject_ cast() 函数进行动态投射(dynamic cast)。例如,假设QMyWidgetQWidget的子类并且在类定义中声明了Q_OBJECT宏。创建实例使用下面的语句:

    Qobject *obj = new QMyWidget;
    

    变量obj定义为QObject指针,但它实际指向QMyWidget类,所以可以正确投射为QWidget,即:

    Qwidget *widget = qobject_cast<QWidget *> (obj) ;
    

    QObjectQWidget的投射是成功的,因为obj实际是 QMyWidget 类,是QWidget 的子类。也可以将其成功投射为QMyWidget,即:

    QMyWidget* myWidget =qobject_cast<QMyWidget *> (obj) ;
    

    投射为QMyWidget是成功的,因为qobject_cast() 并不区分Qt内建的类型和用户自定义类型。
    但是,若要将obj投射为QLabel 则是失败的,即:

    QLabel *labe1 = qobject_cast<QLabel *>(obj) ;
    

    这样投射是失败的,返回指针label为nullptr,因为QMyWidget不是QLabel的子类。
    使用动态投射,使得程序可以在运行时对不同的对象做不同的处理。


4 元对象特性案例讲解

QPerson类的定义

  实例Meta-objectSystem 演示Qt元对象系统的一-些功能。Meta-objectSystem 是主窗口基于QWidget 的应用程序, 这个设计界面不多,主要是C++代码,涉及元对象特性相关的逻辑。

  在项目创建后,创建一个类QPersonQPerson.h 文件中的类定义如下所示:

class QPerson : public Q0bject
{ 
	Q OBJECT
	Q_CLASSINFO("author", "Wang")
	Q_CLASSINFO("company", "UPC")
	Q_CLASSINFO("version", "1.0.0")
	Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
	Q_PROPERTY(QString name MEMBER m_ name)
	Q_PROPERTY(int score MEMBER m_ score)
private :
	int m_ age=10;
	QString	m_ name ;
	int m score=79;
public:
	explicit QPerson (QString fName, Qobject *parent = nullptr) ;
	int age () ;
	void setAge (int value) ;
	void incAge () ;
signals:
	ageChanged(int value) ;
public slots:
};

  QPersonQObject的子类,在类定义部分使用了宏Q_OBJECT,这样QPerson就获得了元

  对象系统支持,能使用信号与槽、属性等功能。

  QPerson使用 Q_ CLASSINFO 宏定义了3个附加信息,使用Q_PROPERTY宏定义了3个属性,还定义了一个信号。下面是QPerson类的实现代码:

QPerson::QPerson (QString fName, Qobject *parent):QObject(parent)
{ //构造函数
	m_ name= fName ;
}

int QPerson::age ()
{
	//返回age
	return	m_ age;
}

void QPerson::setAge(int value) //设置age
{ 
	m_ age=value;
	emit ageChanged(m_ age); //发射信号
}

void QPerson::incAge ()
{ 
	m_ age++;
	emit ageChanged(m_ age) ;//发射信号
}

  setAge(int)函数用于设置年龄,代码里设置年龄后发射信号ageChanged()。
  incAge()是一个单独的接口函数,与属性无关,但是也发射信号ageChanged()。

5 元对象特性使用详解

  主窗口是基于QWidget的可视化设计的类QmyWidget,其定义如下:

class QmyWidget : public QWidget
{
	Q_ OBJECT .
private:
	QPerson *boy;
	QPerson *girl;
public:
	explicit QmyWidget (QWidget *parent = 0) ;
	~ QmyWidget() ;
private:
	Ui::QmyWidget *ui;
private slots :
//自定义槽函数
void on_ageChanged(int value) ;
void on_ spin_ valueChanged(int arg1) ;
//界面按钮的槽函数
void on_ btnClear_ clicked() ;
void on_ btnBoyInc_ clicked() ;
void on btnGirlInc clicked() ;
void on_ btnClassInfo_ clicked() ;
}

  QmyWidget 类里定义了两个QPerson类型的指针变量,定义了两个自定义槽函数。构造函数
的代码如下:

QmyWidget::QmyWidget (QWidget *parent) : Qwidget (parent), ui (new Ui::Qmywidget)
{ 
	//构造 函数
	ui ->setupUi (this) ;
	boy=new QPerson("王小明"); .
	boy->setProperty("score",95) ;
	boy->setProperty("age", 10) ;
	boy->setProperty("sex" , "Boy");//动态属性
	connect(boy, &QPerson:: ageChanged, this, &QmyWidget::on_ ageChanged);
	
	girl=new QPerson("张小丽");
	girl->setProperty("score",81) ;
	girl->setProperty("age",20) ;
	girl->setProperty("sex", "Girl");//动态属性
	connect(girl, &QPerson:: ageChanged, this, &QmyWidget::on_ ageChanged);
	ui->spinBoy->setProperty("isBoy", true); //动态 属性
	ui->spinGirl->setProperty ("isBoy", false);
	
	connect(ui->spinGirl, SIGNAL (valueChanged(int)),
	this, SLOT(on_spin_valueChanged(int)));
	connect (ui->spinBoy, SIGNAL (valueChanged(int)),
	this, SLOT(on_spin_valueChanged(int)));
	

  创建QPerson类型对象boy后,使用setProperty()函数设置了scoreage属性的值,这两个属性是QPerson类里定义的。还设置了一个属性sex的值。

boy->setProperty("sex", "Boy") ;

  sex属性在QPerson类里没有定义,所以这个属性是个动态属性。

  创建对象boygirl后,它们的ageChanged()信号都与槽函数on_ ageChanged() 关联,设置信号与槽关联时使用了函数指针的形式,因为QPerson只有一个**ageChanged()**信 号,具有缺省函数参数,这样设置关联是可以的。

  为界面上的组件spinBoyspinGirl也设置了–个逻辑型动态属性 isBoy ,分别赋值为truefalse,并且这两个 spinBox 的信号 valueChanged(int) 都与槽函数on_ spin_ valueChanged(int) 相关联 。但是,这里使用 connect() 函数设置关联时必须使用SIGNALSLOT宏的形式,而不能使用函数指针的形式,因为QSpinBox有两个valueChanged()信号,只是参数不同。

  自定义槽函数on_ ageChanged() 用于响应QPersonageChanged() 信号,其实现代码如下:

void QmyWidget: :on_ ageChanged( int value)
{
	//响应QPerson的ageChanged()信号
	Q_ UNUSED (value) ;
	QPerson *aPerson = qobject_ cast<QPerson *>(sender()); //类 型投射
	QString hisName=aPerson->property ("name").toString(); //姓名
	QString hisSex=aPerson->property("sex").toString(); / /动态属性
	int hisAge=aPerson->age() ; //通过接口函数获取年龄
	// int hisAge=aPerson->property("age").toInt() ; / /通过属性获得年龄
	ui ->textEdit->appendPlainText (hisName+","+hisSex+
	QString::asprintf(",年龄=%d",hisAge)) ;

  这里使用了QObject::sender() 函数获取信号发射者。因为信号发射者是QPerson类型对象boy或girl,所以可以用qobject_ cast() 将发射者投射为具体的类型。

QPerson * aPerson = qobject_ cast<QPerson *> (sender () ) ;

  这样得到信号发射者QPerson类型的对象指针aPerson,它指向boy或girl。

  使用aPerson指针,通过property()函数获取name属性的值,也可以获取动态属性sex的值。

  因为在QPerson中,name 属性只用MEMBER关键字定义了一个私有变量表示这个属性,所以只能用property()读取此属性的值,也只能用setProperty()设 置此属性的值。

  读取年龄时,直接用了接口函数,即:

int hisAge=aPerson->age() ;

  当然也可以采用property()函数获取年龄,即:

int hisAge=aPerson->property ("age") . toInt() ;

  因为定义age属性时用READWRITE指定了公共的接口函数,既可以通过property()setProperty() 进行属性读写,也可以直接使用接口函数进行读写。当然,直接使用接口函数速度更快。

  界面上两个分别用于设置boygirl年龄的spinBoxvalueChanged(int)信号与槽函数on_ spin_valueChanged(int) 关联,槽函数代码如下:

void QmyWidget: :on_spin_valueChanged(int arg1)
{
	//响应界面上spinBox的valueChanged(int)信号
	Q_ UNUSED(arg1) ;
	QSpinBox *spinBox = qobject_ cast<QSpinBox *> (sender()) ;
	if (spinBox->property ("isBoy").toBool())
		boy->setAge (spinBox->value());
	else
		girl->setAge (spinBox->value());
}

  这里也使用了信号发射者的类型投射,投射为QSpinBox类型指针spinBox,然后根据spinBox的动态属性isBoy的值,确定调用boygirlsetAge() 函数。

  这种编写代码的方式一般用于为多个同类型组件的同一信号编写-一个槽函数,在槽函数里区分信号来源分别做处理,避免为每个组件分别编写槽函数形成的代码冗余。

  界面上 类的元对象信息 按钮的响应代码如下:

void QmyWidget::on_btnClassInfo_clicked()
{
	//"类的元对象信息"按钮
	const QMetaObject * meta=boy->meta0bject();
	ui->textEdit->clear () ;
	ui->textEdit->appendPlainText("==元对象信息==\n");
	ui->textEdit->appendPlainText(
			QString("类名称: %1\n") .arg (meta->className()));
	ui->textEdit->appendPlainText ("property") ;
	for (int i=meta->propertyOffset() ; i<meta->propertyCount() ;i++)
	{
		QMetaProperty prop=meta->property(i);
		const char* propName=prop.name();
		QString propValue=boy->property(propName).toString();
		ui->textEdit->appendPlainText(QString("属性名称=%1,属性值=%2").arg(propName)
				.arg(propValue));
	}
	ui->textEdit->appendPlainText("");
	ui->textEdi t->appendPlainText("classInfo");
	for (int i=meta->classInfo0ffset(); i <meta->classInfoCount(); ++i)
	{
		QMetaClassInfo classInfo=meta->classInfo(i);
		ui->textEdit->appendPlainText(QString("Name=81; Value=82")
			.arg(classInfo.name()).arg(classInfo.value());
	}
}

  代码里通过boy->metaObject() 获得对象boy的元对象。元对象类QMetaObject封装了访问类的元对象的各种接口函数,例如,QMetaObject:: className() 返回类的名称。


QMetaObject用于属性操作的函数有以下几种:

  • propertyOffset(): 返回类的第一个属性的序号,第一个属性的序号不一定是0。
  • propertyCount(): 返回类的属性个数。
  • QMetaProperty property(int index): 返回序号为index的属性对象,返回值是QMetaProperty
    类型,它封装了对属性的更多特征查询功能,以及属性值的读写功能。

   QMetaClassInfo类封装了类的 classInfo 的访问接口函数,只有 name()和value() 两个接口函数。

  好了! 到这里元对象系统也就差不多了,也不讲太多了,以免大家消化不完,今天元对象系统就到这里,后面我们继续讲属性系统!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值