[QT] Meta-object system study notes (2)

This article mainly describes a performance corresponding to the functional division of the meta-object system. If you have any misunderstandings, please correct me!

Walnut

01. Attribute system

1.1. Attribute basis
  1. Properties are similar to data members, but they use the functionality of Qt's meta-object system. The main difference between them is that the access methods are different. For example, attribute values ​​usually use read functions (that is, functions whose function names usually start with get) and set functions (that is, functions whose function names usually start with set) to access their values. In addition to this method, Qt has other ways to access properties.
  2. In Qt, attributes and data members are two different concepts. They may or may not be related. For example, a property named a has the same name as a data member. Although they have the same name, if there is no association between them, the data Member a is completely unrelated to attribute a. Usually, an attribute has data members associated with it, and the naming rule adopted is usually prefixed with m_. For example, if the attribute is named a, then it is associated with The data member name is usually m_a.
  3. Attribute values ​​are accessed as follows:

1. You can use the QObject::property and QObject::setProperty functions for access.
2. If the attribute has an associated access function, you can use the access function for access.
3. The attribute can also be accessed through the QMetaObject class of the meta-object system. Make access.
4. If the attribute is associated with a data member, the attribute
value can be accessed indirectly by accessing the value of the ordinary data member.
ps: Note: Classes in Qt only have attributes and no data members, so attribute values ​​can only be
modified through the first three methods.

  1. To declare attributes in a class, the class must inherit from the QObject class, you should also use the Q_OBJECT macro, and then use the
    QObject::Q_PROPERTYmacro to declare the attributes. The macro syntax is as follows:
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])

Parameter explanation:

  • The options in square brackets are optional, and each option is separated by a space.
  • type: Specify the type of attribute, which can be a type supported by QVariant or a user-defined type. If it is an enumeration type, you need to use the Q_ENUMS macro to register the enumeration. If it is a custom type, you need to use the Q_DECLARE_METATYPE(Type) macro to register it.
  • name: Specifies the name of the attribute.
  • READ getFunction: 1) Used to specify the read function (read the value of the attribute), where READ means reading and cannot be changed, and getFunction is used to specify the function name. 2) If the MEMBER variable is not specified, the READ function must be specified. 3) Generally, the READ function is const, and the READ function must return the type of the attribute or a reference to the type.
  • WRITE setFunction:1) Used to specify the setting function (setting the value of the attribute), where WRITE means writing and cannot be changed, and setFunction is used to specify the function name. 2) This function must have only one parameter, and the return value type must be void. 3) If it is a read-only attribute, there is no need to specify the WRITE function.
  • MEMBER memberName: 1) Used to set the specified member variable memberName to be readable and writable without creating READ and WRITE functions, where MEMBER represents a member and cannot be changed, and memberName represents a member variable in the class. 2) If the READ function is not specified, the MEMBER variable must be specified.
  • RESET resetFunction: Used to reset properties to default values. This function cannot have parameters, and the return value must be void. Among them, RESET means reset and cannot be changed, and resetFunction is used to specify the function name.
  • NOTIFY notifySignal: Indicates that a signal is associated with the attribute. If set, you should specify an existing signal in the class that will be emitted whenever the property value changes. If a MEMBER variable is used, the NOTIFY signal must be zero or one parameter, and the parameter must be of the same type as the attribute that will accept the new value of the attribute.
  • REVISION: Set the version number, the default is 0.
  • DESIGNABLE: Used to set whether the properties are visible in the property editor of the GUI design tool (such as Qt Designer). Most properties are visible. The default value is true, which means they are visible. This variable can also specify a member function that returns a Boolean value instead of true or false.
  • SCRIPTABLE: Set whether the attribute can be accessed by the script engine. The default is true.
  • STORED: Set whether the value of the attribute must be saved when saving the state of the object. This value is true for most properties
  • USER: Set whether the attribute is editable. Each class has only one USER attribute (the default value is false). For example, QAbstractButton::checked is a user-editable attribute.
  • CONSTANT: Indicates that the value of the attribute is a constant. Constant attributes cannot have the WRITE function and NOTIFY signal. For the same object instance, each READ function using a constant attribute must get the same value, but this value can be different for different instances of the class.
  • FINAL: Indicates that the attribute cannot be overridden by derived classes.
/*声明及使用属性示例程序:*/ 

// .h文件内容如下:
#ifndef M_H
#define M_H
#include <QObject>

class A : public QObject
{
    
    
	Q_OBJECT
public:
	// 通常Qt自带的类,属性名如果为File,那么对应的读取函数和设置函数一般都是
	// getFile 、 setFile

	// 声明一个类型为int,名称为a的属性,并使用函数f读取属性值,使用函数g设置属性值
	Q_PROPERTY(int a READ f WRITE g)

	// 声明一个类型为int,名称为 b 的属性,并把该属性与成员变量 m_b 相关联,不设置存取函数。
	Q_PROPERTY(int b MEMBER m_b)

	// 声明一个只读属性 c,本例没有设置该属性值的函数,因此该属性是只读的
	Q_PROPERTY(int c READ getc)

	/*
	*	在存取函数中可把属性与成员变量相关联,放入如下:
	*	ps: 对存取函数的返回类型和参数类型及数量在本例中影响不大,后面会说
	*/

	int f() {
    
     return m_a; }      // 属性a的读取函数
	void g(int i) {
    
     m_a = i; }  // 属性a的设置函数
	int getc() {
    
     m_c = 3; return m_c; } // 属性也可以不与数据成员相关联,直接返回常量3
	int m_a,m_b;               // 属性若命名为a,则与其对应的成员变量习惯上应命名为m_a
private:
	int m_c;                  // 成员变量通常都应声明为私有的,这样可提高程序的封装性
};
#endif 


// .cpp文件如下:
#include "m.h"
#include <iostream>
#include <QDebug>
using namespace std;

int main(int argc, char* argv[])
{
    
    
	A ma;
	// 像普通成员变量一样存取属性值
	ma.m_a = 1; 
	qDebug()<< "m_a = " << ma.m_a;  // 输出1
	
	//因为属性 b 没有存取函数,本例暂时只使用普通成员变量的方式存取该属性值
	ma.m_b = 2;
	qDebug()<< "m_b = " << ma.m_b;
	ma.g(4);  // 使用属性a的设置函数修改属性值
	qDebug()<< "modify later m_a = " << ma.f();  // 输出4,使用读取函数输出属性a,也就是m_a的值
	qDebug()<< "m_c = " << ma.getc();  // 输出 3,属性 c 是只读的,只能通过他的读取函数访问其值,因为没有设置函数,因此无法改变属性 c 的值

	return 0;
}
1.2. QVariant class
  1. You can use QObject::propertyfunctions to read the value of a property, and you QObject::setPropertycan use functions to set the value of a property. However, there are many types of properties. How do you use the property function to return the property value with the correct type? To solve this problem, a QVariant is used to describe this type.
  2. The QVariant class is used to encapsulate information such as the type and value of data members. This class is similar to a C++ union union. A QVariant object can only save a single type of value at a time. This class encapsulates commonly used types in Qt. For types that QVariant does not support (such as user-defined types), you need to use Q_DECLARE_METATYPE(Type)macros to register them.
  3. QVariant has single parameter constructors of common types, so these common types can be converted to QVariant types. At the same time, QVariant also overloads the assignment operator, so values ​​of common types can be assigned directly to QVariant objects. Note: From the C++ syntax, we can know that the single parameter constructor can directly perform type conversion.
  4. Using the QVariant constructor and assignment operator, see the following example:

Note: QVariant does not have a constructor of the char type. If a char value is used, it will be converted to the corresponding int type.

QVariant v(1);  // 调用QVariant(int)构造函数创建一个QVariant类型的对象,并把数值1保存到v中
v = 2.2;       // 调用QVariant的赋值运算符,把值为2保存在v之中,因为上面说了一个QVariant只保存一个单一类型的值
  1. To get the type of value stored by the QVariant object, you can use the following parameters:
Function Descrition
Type type() const Get the type of value currently stored in the QVariant object, similar to the enumeration form QMetaType::Type.
const char* typeName() const Returns the type of value stored by the QVariant object as a string. If it is an invalid type, return 0
const char* typeToName(int t) Return the type represented by the enumeration type QMetaType::Type in the form of a string. If the enumeration value is QMetaType::UnknownType or does not exist, a null pointer is returned.

An example:

QVariant v(1);
qDebug()<< v.typeName();  // 输出int
qDebug()<< v.typeToName(v.type()); // 输出int
  1. To get and set the value stored in the QVariant object, you can use the following functions:

    6.1.: void setValue(const T& v)Store a copy of a value in a QVariant object. If type T is a type that QVariant does not support, use QMetaType to store the value. If QMetaType cannot handle it, a compilation error will occur. Note: If it is a user-defined type, you need to use a macro Q_DECLARE_METATYPE(…)to register it.
    6.2.: T value() constConvert the stored value to type T and return the converted value. The stored value itself will not be changed. If T is a type supported by QVariant, this function has the same functions as toInt, toString and other functions. Note: When using this function, you need to use angle brackets to specify the type of T, such as xx.value();
    6.3, T toT():1) where T is a certain type. If T is int, the function form is int toInt(), For details, see Function Templates in C++ Generic Programming. 2) This function is used to convert the stored value to type T and return the converted value. The stored value itself will not be changed. One of the more commonly used is the toString function, which can convert the stored value into QString form, so that the stored value can be output in the form of a string. 3) Note: There is no toT function corresponding to a custom type, such as class C{}; then there is no toC function. To convert the stored value to a custom type, you need to use the value function, and you also need to register the custom type. .

  2. Note: The enumeration type Type in QVariant has been deprecated.

  3. Using the default constructor of QVariant will create an invalid QVariant object (or an empty QVariant object), which can be judged through the isNull() member function.

An example:

#include<QVariant>
#include <iostream>
using namespace std;

class C
{
    
    
}; //自定义类型

int main(int argc, char *argv[])
{
    
    
	/*QVariant 没有专门的 char 构造函数,此处的字符 a 会被转换为 int 型,因此 v中存储的是数值 97,而不是字符 a */
	QVariant v('a'); 
	cout<<v.value<int>()<<endl; // 输出 97
	cout<<v.value<char>()<<endl; // 输出 a,将 97 转换为 char 型,并输出转换后的值。
	cout<<v.toChar().toLatin1()<<endl; /*输出 a,原因同上,注意 toChar 返回的类型是 QChar 而不是 char */

	cout<<v.toString().toStdString()<<endl; /* 输出 97,把存储在 v 中的值转换为 QString,然后以字符串形式输出 */

	cout<<v.typeName()<<endl; // 输出 int,可见存储在 v 中的值的类型为 int
	cout<<v.typeToName(v.type())<<endl; /*输出 int,其中 type 返回存储值的枚举形式表示的类型,而typeToName 则以字符串形式				显示该枚举值所表示的类型 */

	char c='b';
	v.setValue(c);
	cout<<v.toString().toStdString()<<endl; // 输出 b
	cout<<v.typeName()<<endl; /*输出 char,若是使用 QVariant 构造函数和直接赋值 char 型字符,此处会输出 int,这是 setValue 与他们的区别 */
	
	C mc; // 自定义类型 C 的对象 mc
	//QVariant v1(mc); // 错误,没有相应的构造函数。
	QVariant v2;
	//v2=mc; // 错误,没有与类型 C 匹配的赋值运算符函数。
	//v2.setValue(mc); // 错误,自定义类型 C 未使用宏 Q_DECLARE_METATYPE 声明。
	return 0;  
}
1.3. Use the QObject class to access attribute values ​​and dynamic properties

A macro has been mentioned above: Q_DECLARE_METATYPE(), let’s talk about it in detail here.

  1. Register custom types with QMetaType class

    1.1. The QMetaType class is used to manage named types in the meta-object system. This class is used to help types in QVariant and the connection of signals and slots in the queue. It associates a type name with a type so that it can be dynamically created and destroyed at runtime.
    1.2. The QMetaType::Type enumeration type defines the types supported by QMetaType. Its prototype is: enum Type { void,Bool,Int......UnknowType}
    1.3. Custom types used in QVariant classes and attributes need to be registered before they can be used. Use macros Q_DECLARE_METATYPE()to declare new types and make them available to QVariant and other template-based functions. Calls qRegisterMetaType()provide types to non-template functions.
    1.4. Q_DECLARE_METATYPE(Type)Things to note when using macro declaration types:

    Index Descrition
    After declaring a type using this macro, all template-based functions will know the type
    Classes that use this macro need to have a public default constructor, a public destructor, and a public copy constructor.
    When using this macro, the declared type Type needs to be a fully defined type, so this macro is usually located after the declaration of the class or structure
    For pointer types, Q_DECLARE_OPAQUE_POINTER(T)macros are required for declaration
    For the QVariant class, you only need to use this macro to declare the type and then use it.
    If you need to use this type in signal and slot connections in the queue, or in QObject's property system, you must also call the qRegsiterMetaTypefunction to register the type, because these situations run dynamically
    The following types are automatically registered and do not need to use this macro (see the help document for full details) 1. Pointer types pointing to classes derived from QObject 2. Enumerations registered using Q_ENUM or Q_FLAG 3. Classes with the Q_GADGET macro
    // 一个简单的示例:
    class A {
          
          };
    Q_DECLARE_METATYPE(A)   // 声明位于类定义之后
    
    namespace N {
          
          
    	class B {
          
          };
    }
    Q_DECLARE_METATYPE(N::B)  // 类型位于命名空间的情况
    
    A ma;
    QVariant v;
    v.setValue(ma);
    ma.value<A>();  // 声明后QVariant类便可直接使用
    

    A few words about int qRegisterMetaType<T>()the type of function registration.
    1. When using this function, you need to use angle brackets to specify the type of T, such as qRegisterMetaType()
    2. This function returns the internal ID used by QMetaType
    3. Type T must be declared using the Q_DECLARE_METATYPE(Type) macro
    4. After the type is registered, you can Objects of this type are created and destroyed during runtime
    . 5. The registered class or structure needs to have a public default constructor, a public destructor and a public copy constructor.
    Specifically, use an example to illustrate:

    #include<QVariant>
    #include <iostream>
    using namespace std;
    
    class A {
          
          
    	public: 
    		int i;
    };
    
    class B {
          
          
    	public:
    		int i;
    };
    
    class D {
          
          
    	public:
    		// 该类无默认构造函数
    		D(int) {
          
          }
    };
    
    class E {
          
          };
    
    // 声明类型
    Q_DECLARE_METATYPE(A)
    Q_DECLARE_METATYPE(B)
    //Q_DECLARE_METATYPE(D) // 错误,类 D 没有公有的默认构造函数
    //Q_DECLARE_METATYPE(E) // 错误,因为父类 QObject 的拷贝构造函数、赋值运算符等是私有的
    
    int main(int argc, char* argv[]) {
          
          
    	// 注册类型
    	qRegisterMetaType<B>();
    	//qRegisterMetaType<D>();  // 错误,类型D未使用宏Q_DECLARE_METATYPE(T)声明
    	//qRegisterMetaType<E>();  // 同上
    	A ma;
    	ma.i = 1;
    	B mb;
    	mb.i = 2;
    
    	//QVariant v1(ma); // 错误,没有相应的构造函数
    	QVariant v;
    	v.setValue(ma); // 将对象 ma 存储在 v 之中
    	cout<<v.value<A>().i<<endl; // 输出 1。
    	cout<<v.typeName()<<endl; // 输出 A
    	cout<<v.toString().toStdString()<<endl; // 输出一个空字符,因为 ma 是一个对象,不是一个值。
    
    	// 自定义类型需要使用 userType 才能返回正确的类型 ID
    	cout<<v.typeToName(v.userType())<<endl; // 输出 A
    	cout<<v.typeToName(v.type())<<endl; // 不一定输出 A
    
    	A ma1;
    	ma1 = v.value<A>();  // 把存储在v之中的对象ma赋值给ma1
    	cout << ma1.i << endl;  // 输出1,赋值成功
    	B mb1;
    	//mb1 = v.value<A>();  // 错误,类型不相同
    	mb1 = v.value<B>();  // OK,但是由类型 A 转换到类型 B 失败,此时 value 会返回一个默认构造的值
    	cout << mb1.i << endl;  // 输出0
    	
    	return 0;
    }
    
  2. QVariant QObject::property(const char* name) const

    Function: Get the value of the attribute name name, which is returned in the form of a QVariant object. If the attribute name does not exist, the returned QVariant object is invalid.

  3. setProperty function and dynamic properties

    bool QObject::setProperty(const char* name, const QVariant & v);
    

    3.1. Function: Set the attribute name to the value v.
    3.2. If the attribute is declared using Q_PROPERTY and the value v is compatible with the type of attribute name, then the value v is stored in the attribute name and true is returned.
    3.3. If the value is incompatible with the attribute type, the attribute will not be changed and false will be returned.

Dynamic properties

  • If the attribute name is not declared using Q_PROPERTY, the attribute and value are added to the object as a new attribute and false is returned. This is a dynamic attribute.
  • Dynamic properties can still be queried using property, and an invalid QVariant object can be set to delete dynamic properties.
  • Dynamic properties are based on instances of a certain class, which means that dynamic properties are added to objects of a certain class, not to QMetaObject. This means that you cannot use the member functions of QMetaObject to obtain dynamic property information. .
  • Changing the value of a dynamic property sends a QDynamicPropertyChangeEvent to the object.

An example of dynamic properties and using property and setProperty to access property values:

// m.h:
#ifndef M_H //要使用元对象系统,需在头文件中定义类。
#define M_H
#include<QObject>

class B{
    
    
	public:
		int i;
};

class C{
    
    
	public:
		int i;
};

class D{
    
    
	public:
		int i;
};
// 注册自定义的类型
Q_DECLARE_METATYPE(B)
Q_DECLARE_METATYPE(C)

class Z : public QObject 
{
    
     
	Q_OBJECT
public:
	 Z(){
    
    };
	 
	 Q_PROPERTY(B b READ fb WRITE gb)
	 Q_PROPERTY(C c READ fc WRITE gc)
	 Q_PROPERTY(D d READ fd WRITE gd)
	 
	 B fb(){
    
     return m_mb; }
	 void gb(B x){
    
     m_mb=x; }
	 C fc(){
    
     return m_mc; } 
	 void gc(C x){
    
     m_mc=x; }
	 D fd(){
    
     return m_md; } 
	 void gd(D x){
    
     m_md=x; }
	 B m_mb; 
	 C m_mc; 
	 D m_md;  
};
#endif // M_H

// .cpp:
#include "m.h"
#include<QVariant>
#include <iostream>
using namespace std;

int main(int argc, char* argv[]) {
    
    
	// 注册类型
	qRegisterMetaType<B>();
	qRegisterMetaType<C>();
	B mb; C mc; D md;
	Z mz;
	
	mb.i = 2;
	mc.i = 3;
	md.i = 4;
	mz.gb(mb);
	mz.gc(mc);
	mz.gd(md);
	
	// 使用 porperty 和 setProperty 存取属性值
	//mz.property("d"); // 错误,不能使用 property 函数访问属性 d,因为属性 d 的类型 D 未注册
	mz.property("MMM");  // OK,MMM属性不存在,返回一个空的QVariant对象
	cout << mz.fd().i << endl;  // 输出4,虽然不能使用 property 函数访问属性 d,但仍可使用存取函数访问该属性的值
	
	QVariant v;  B mb1;
	mb1.i = 6;
	v.setValue(mb1);
	//mz.setProperty("b", mb1);   // 错误,第二个参数的类型不匹配
	mz.setProperty("b",v);  // 正确设置属性b的值的方法,把属性b的值设置为v中存储的值mb1
	mz.setProperty("c",v);  // 正确,但是属性c的类型与v中存储的值的类型不兼容,因此属性c不会被更改
	mz.setProperty("c",7); // 原因同上
	cout<<mz.property("b").typeName()<<endl; // 输出 B,输出属性 b 的类型
	cout<<mz.property("c").typeName()<<endl; // 输出 C,输出属性 c 的类型
	cout<<mz.property("b").value<B>().i<<endl; // 输出 6。输出的是 mb1.i 的值。
	cout<<mz.property("c").value<C>().i<<endl; // 输出 3,属性 c 的值并未被更改

	// 动态属性
	mc.i = 7;
	v.setValue(mc);
	//mz.setProperty("w", mc);  // 错误,第二个参数的类型不匹配
	mz.setProperty("x", v);  // 动态属性,新增加属性x,并设置其值为v中存储的值
	cout<<mz.property("x").typeName()<<endl;  // 输出 C,即动态属性 x 的类型
	cout<<mz.property("x").value<C>().i<<endl; // 输出 7
	Z mz1;
	//cout<<mz1.property("x").typeName()<<endl; // 错误,动态属性 x 是基于对象 mz 的
	cout<<mz1.property("b").typeName()<<endl; // 输出 B,属性 b 不是动态属性

	return 0;
}
1.4. Use reflection mechanism to obtain attribute information
  1. QMetaProperty class

    ①. Function: Used to describe the properties of an object. You can use the member functions of this class to obtain information about the object properties.
    ②. Determine the behavior of attributes (bool type return value):

    Function Descrition
    isReadable() Readable returns true
    isWritable() Writable returns true
    isValid() Returns true if the attribute is valid
    isConstant() Whether CONSTANT is true when declaring the attribute
    isEnumType() Returns true if the attribute's type is an enumeration
    isFinal() Whether FINAL is true when declaring the property
    isFlagType() Returns true if the attribute's type is a flag enum
    isResettable() Returns true if the property can be reset to its default value, that is, the RESET function is specified when declaring the property.
    bool isUser(const QObject* object=Q_NULLPTR) const Whether USER is true when declaring the attribute
    bool isStored(const QObject* object=Q_NULLPTR) const Whether STORED is true when declaring the attribute
    bool isScriptable(const QObject* object=Q_NULLPTR) Whether SCRIPTABLE is true when declaring the attribute
    bool isDesignable(const QObject* object=Q_NULLPTR) Whether DESIGNABLE is true when declaring the attribute

    ③. Other member functions:

    Function Descrition
    const char* name() const Get the name of the attribute
    const char* typeName() const Returns the name of this property type
    QVariant::Type type() const Returns the type of the attribute, whose value is one of the enumeration values ​​of QVariant::Type
    int userType() const Returns the user type of this attribute. The return value is one of the values ​​registered using QMetaType (an enumeration value in the QMetaType class). If the type is not registered, QMetaType::UnknownType is returned.
    int propertyIndex() const Returns the index of this property
    QMetaEnum enumerator() const If the attribute type is an enumeration type, the enumeration is returned, otherwise the returned value is undefined.
    QVariant read(const QObject* object) const Read the value of the attribute from the given object object. If the value can be read, return the value, otherwise return an invalid QVariant object.
    bool write(QObject* object, const QVariant & v) const Write the value v as the attribute value into the given object object. If the writing is successful, it returns true, otherwise it returns false. If the type of the value is not the same as the type of the property, a conversion is attempted. If the property is resettable, an empty QVariant object (that is, an invalid QVariant object) is equivalent to calling the reset() function, or setting it in other ways. a default-constructed object
    bool reset(QObject* object) const Use the RESET function to reset the properties of the given object object. If the reset is successful, it returns true, otherwise it returns false.
    bool hasNotifySignal() const If the attribute has a notification signal, return true, otherwise return false
    QMetaMethod notifySignal() const If the attribute specifies a notification signal, the QMetaMethod instance of the signal is returned, otherwise an invalid QMetaMethod is returned.
    int notifySignalIndex() const Returns the index of the attribute notification signal, otherwise returns -1
  2. Member functions related to properties in the QMetaObject class

as follows:

  • int indexOfProperty(const char* name) const :返回属性 name 的索引,否则返回-1
  • int propertyCount() const:返回属性的数量(包括从父类继承的属性)
  • int propertyOffset() const:返回父类中的属性数量,也就是说此返回值是此类第一个属性的索引位置
  • QMetaProperty property(int index) const:返回索引号为 index 的属性的元数据,若不存在这样的属性,则返回空的QMetaProperty
  • QMetaProperty userProperty() const:返回 USER 标志设置为 true 的属性的元数据

一个示例:

// .h:
#ifndef M_H //要使用元对象系统,需在头文件中定义类。
#define M_H
#include<QObject>
#include <iostream>
using namespace std;

class Z : public QObject
{
    
     
	Q_OBJECT
public:
	Q_PROPERTY(int b READ gb WRITE sb)
	int gb(){
    
    return m_mb;} 
	void sb(int x){
    
    m_mb=x;}
	int m_mb; 
};
#endif // M_H

// .cpp:
#include "m.h"
#include<QMetaProperty>

int main(int argc, char *argv[])
{
    
    
	Z mz;
	const QMetaObject* p = mz.metaObject();
	QMetaProperty pe = p->property(p->indexOfProperty("b"));  // 获取属性b的元数据
	cout << pe.name() << endl;  // 输出属性的名称b
	cout << pe.typeName() << endl;  // 输出属性b的类型int
	pe.write(&mz, 47);  // 把值47写入属性b
	cout << pe.read(&mz).value<int>() << endl;  // 输出属性b的值47
	return 0; 
}

02、信号与槽

  1. 信号和槽是用于对象之间的通信的,这是Qt的核心。 为此Qt引入了一些关键字,他们是slots、signals、emit,这些都不是C++关键字,是Qt特有的,这些关键字会被Qt的moc转换为标准的C++语句。
  2. Qt的部件类中有一些已经定义好了的信号和槽,通常的做法是子类化部件类,然后添加自己的信号和槽。
  3. 因为信号和槽与函数相似,所以通常把信号称为信号函数,槽称为槽函数。
2.1、 信号与槽原理
1、C++虽然是面向对象的语言,但程序的具体实现代码仍然是由函数来实现的,因此所谓的对象之间的通信,从程序设计语言语法角度来看
就是函数调用的问题,只不过是某个对象的成员函数调用另一个对象的成员函数而已。 信号和槽其实也就是设计模式中的观察者模式的一种
实现。
2、函数调用的几种形式,这里看图:

Insert image description here
见上图,假设函数 f 需要 g 的处理结果,有以下几种处理方式:
①:最简单直接的方式就是直接调用函数g,但这种方式有一个缺点,那就是调用者必须知道g的名称以及函数g的参数类型,但是如果f只需要g的处理结果就可以了,而g的处理结果不一定需要函数g来完成,它也可以是x、y或其他名称的函数来完成,那么这种直接调用函数的方式就没办法达到我们想要的需求,因为系统不知道用户会调用哪个函数来完成这个处理结果,也就是系统不知道调用的函数名究竟是g、x、y还是其他名称的函数。
②:回调函数,即在函数f中使用一个指向函数的指针去调用需要的函数,这样就可以调用任意名称的函数(只要函数类型与指针相同即可),此时只要是完成了函数g功能的函数都可以作为函数f的结果被调用,这样就不会被函数名称所限制。比如:

void (*p)(int i, int j);   // 假设这是系统内部代码
f(...) {
    
    	// 假设f也是系统内部源代码函数
	p(2,3);
	....
}

// 假设g和h都是由我们程序员实现的代码
void g(int,int) {
    
     .... };
void h(int,int) {
    
     .... };

p = h;   // 只需要对指向函数的指针赋予不同的函数地址,便能实现由系统回调不同的函数

③:Qt使用的信号和槽机制:

注意:信号和槽不是C++标准代码,因此这些代码需要使用Qt的moc进行重新编译。

基本思想如下:

  1. 创建一个信号,其中创建信号需要一些规则。
  2. 当需要调用外部函数时,发送一个信号。
  3. 此时与该信号相关联的槽便会被调用,槽其实就是一个函数,当然要使函数称为槽也是有一定规则的,槽与信号的关联需要我们程序员自己去完成。
  4. 在Qt中,信号和槽都需要位于一个类之中,不能独立于类外实现。

如下图所示:
Insert image description here

2.2、 创建信号与槽

信号和槽的创建,等下会有一个例子。

  1. 只有QObject及其派生类才能使用信号和槽机制,且在类之中还需要使用Q_OBJECT宏。
  2. 信号需符合以下规则
    ①:信号使用signals关键字声明,在其后面有一个冒号 “:”,在其前面不要加public、private、protected访问控制符,信号默认是public的。
    ②:信号只需像函数那样声明即可(无需实现),其中可以带有参数,参数的作用就是用于和槽的通信,这也跟普通函数的参数传递规则一样。信号虽然像函数,但是对它的调用方式不一样,信号需要使用emit关键字发送。
    ③:信号只需声明,不需要对其定义,信号是由moc自动去生成的。
    ④:信号的返回值只能是void类型的。
  3. 声明槽需符合以下规则
    ①:声明槽需要使用slots关键字,在其后面有一个冒号 “:”,且槽需要使用public、private、protected访问控制符之一。
    ②:槽就是一个普通的函数,可以像使用普通函数一样进行使用,槽与普通函数的主要区别就是,槽可以与信号关联。
  4. 发射信号需符合以下规则
    ①:发射信号需要使用emit关键字,注意,在emit后面不需要冒号。
    ②:emit发射的信号使用的语法与调用普通函数相同,比如有一个信号为void f(int),则发送的语法为: emit f(3);
    ③:当信号被发射时,与其相关联的槽函数会被调用(注意:信号和槽需要使用QObject::connect函数进行绑定之后,发射信号后才会调用相关联的槽函数)。
    ④:注意:因为信号位于类中,因此发射信号的位置需要位于该类的成员函数中或该类能见到信号的标识符的位置。
  5. 信号和槽的关系(重要)
    ①:槽的参数的类型需要与信号参数的类型相对应。
    ②:槽的参数不能多于信号的参数,因为若槽的参数更多,则多余的参数不能接收到信号传递过来的值,若在槽中使用这些多余的无值参数,就会产生错误。
    ③:若信号的参数多余槽的参数,则多余的参数将被忽略。
    ④:一个信号可以与多个槽关联,多个信号也可以与同一个槽关联,信号也可以关联到另一个信号上。
    ⑤:若一个信号关联到多个槽时,则发射信号时,槽函数按照关联的顺序依次执行。
    ⑥:若信号连接到另一个信号,则当第一个信号发射时,会立即发射第二个信号。
  6. 因Qt在其类库中预定义了很多信号和槽,因此在Qt中可以仅使用Qt类库中预定义的信号和槽,也可以只使Qt类库中预定义的信号而使用自己的槽,也可以使用Qt类库中预定义的槽来相应自己定义的信号,当然,自己定义信号,自己定义槽必须会。

一个示例:

// Demo.h:
#ifndef DEMO_H
#define DEMO_H

#include <QObject>
#include <iostream>
using namespace std;

class A : public QObject
{
    
    
	Q_OBJECT
signals:
	void s();  // 定义一个无参的信号
	void s(int, int);   // 可以重载
	//void s2() {}  // error: 只能声明不能定义
	void s3();
public:
	void g() {
    
    
		emit s3();  // 发射信号,在关联之前,发射信号不会有响应
	}
};

class B : public QObject
{
    
    
	Q_OBJECT
public slots:
	void x() {
    
      // OK,槽就是一个普通函数,只需要使用slots关键字,且能和信号相关联
		cout << "x: " << endl;
	}
public:
	void g() {
    
    
		//emit s3();  // error,在类B中无法看见s3信号函数
	}
};

/*
* 提示:一般类与类之间通信,我们采用的是如下形式:
*  比如:Cain.h中声明了信号 c  类QDemo.h中声明了槽函数load(),然后我们在QDemo.cpp中关联他们
*  首先,QDemo.h中包含Cain.h,在QDemo类的声明前前置标识  class Cain; 为的就是在后面不会报错
*  Cain* cain = new Cain();
*  QObject::connect(cain,&Cain::c,this, &QDemo::load);  // 第五个参数默认就行
*/

#endif

// Demo.cpp:
#include "Demo.h"

int main(int argc, char* argv[]) {
    
    
	A ma;  B mb;
	QObject::connect(&ma,&A::s3,&mb,&B::x);  // 关联信号和槽
	ma.g();  // 调用对象mb的成员函数x输出x,可见ma与mb之间通信建立OK
	
	return 0;
}
2.3、 信号与槽的连接
  1. 信号和槽使用QObject类中的成员变量函数connect进行关联,该函数有多个重载版本,如下所示:
    形式①:

    static QMetaObject::Connection connect(const QObject* sender,
    									   const char* signal,
    									   const QObject* receiver,
    									   const char* method,
    									   Qt::ConnectionType type = Qt::AutoConnection);
    
    // 示例
    class A : public QObject {
          
          
     	Q_OBJECT
     singals: void s(int i);
    };
    
    class B : public QObject {
          
          
     	Q_OBJECT
     public slots: void x(int i) {
          
          };
    };
    
    A ma;  B mb;
    QObject::connect(&ma, SIGNAL(s(int)), &mb, SLOT(x(int)));
    

    信号的指定必须使用宏 SIGNAL()和槽必须使用宏 SLOT(),这两个宏能把括号中的内容转换为与形参相对应的 const char*形式。在指定函数时,只能指定函数参数的类型,不能有参数名,也不能指定函数的返回类型。比如 SLOT( x(int i)),是错误的,因为指定了参数名 i,正确形式为 SLOT(x(int) )。
    各参数意义如下:
    sender:表示需要发射信号的对象。
    signal:表示需要发射的信号,该参数必须使用SIGNAL()宏。
    receiver:表示接收信号的对象。
    method:表示与信号相关联的槽函数,这个参数也可以是信号,从而实现信号与信号的关联。该参数若是槽,需使用 SLOT()宏,若是信号需使用 SIGNAL 宏。
    type:用于指明信号和槽的关联方式,它决定了信号是立即传送到一个槽还是在稍后时间排队等待传送。关联方式使用枚举 Qt::ConnectionType 进行描述,下表为其取值及意义:

    枚举 解释
    Qt::AutoConnection 0 ( 自动关联,默认值)若接收者驻留在发射信号的线程中(即信号和槽在同一线程中),则使用 Qt :: DirectConnection,否则,使用 Qt :: QueuedConnection。当信号发射时确定使用哪种关联类型
    Qt::DirectConnection 1 直接关联。当信号发射后,立即调用槽。在槽执行完之后,才会执行发射信号之后的代码(即 emit 关键字之后的代码)。该槽在信号线程中执行
    Qt::QueuedConnection 2 队列关联。当控制权返回到接收者线程的事件循环后,槽才会被调用,也就是说 emit 关键字后面的代码将立即执行,槽将在稍后执行,该槽在接收者的线程中执行
    Qt::BlockingQueuedConnection 3 阻塞队列关联。和 Qt :: QueuedConnection 一样,只是信号线程会一直阻塞,直到槽返回。如果接收者驻留在信号线程中,则不能使用此连接,否则应用程序将会死锁
    Qt::UniqueConnection 0x80 唯一关联。这是一个标志,可使用按位或与上述任何连接类型组合。当设置 Qt :: UniqueConnection 时,则只有在不重复的情况下才会进行连接,如果已经存在重复连接(即,相同的信号指向同一对象上的完全相同的槽),则连接将失败,此时将返回无效的 QMetaObject::Connection

    返回值的类型为QMetaObject::Connection,如果成功将信号连接到槽,则返回连接的句柄,否则,连接句柄无效,可通过将句柄转换为bool来检查该句柄是否有效。该返回值可用于QObject::disconnect()函数的参数,以断开该信号和槽的关联。至于该类型不必深入研究,了解即可,后面会说。

    形式②:

    QMetaObject::Connection connect(const QObject* sender,
    								const char* signal,
    								const char* method,
    								Qt::ConnectionType type = Qt::AutoConnection) const;
    
    // 示例:
    class A : public QObject {
          
          
    Q_OBJECT
    singals: void s(int i);
    };
    
    class B : public QObject {
          
          
    	Q_OBJECT
    public slots: void x(int i) {
          
          };
    };
    
    A ma;  B mb;
    mb.connect(&ma, SIGNAL(s(int)),SLOT(x(int)));  
    // 调用方式是mb.connect  注意这里的不同(很少用,至少我用这么久Qt基本不使用这种方式,也许适用于特定的场合,
    // 比如A 中的对象B 在A中建立连接)
    

    参数意义同形式1,这里不重复强调,需要注意的是:

    1、此函数是非静态的,它是QObject的成员函数
    2、此函数是形式①的简化版本,相当于是connect(sender,signal,this,method,type)

    形式③:

    static QMetaObject::Connection connect(const QObject* sender,
    									   PointerToMemberFunction signal,
    									   const QObject* receiver,
    									   PointerToMemberFunction method,
    									   Qt::ConnectionType type = Qt::AutoConnection);
    
    // 示例:
    class A : public QObject {
          
          
    Q_OBJECT
    singals: void s(int i);
    };
    
    class B : public QObject {
          
          
    	Q_OBJECT
    public slots: void x(int i) {
          
          };
    };
    
    A ma;  B mb;
    QObject::connect(&ma, &A::s, &mb, &B::x);  // 这是Qt5中新加入的函数,也是我现在用的最多的一种形式。
    

    参数意义同前面两种形式,好处是这个函数在编写就会进行类型检查。

    形式④:

    static QMetaObject::Connection connect(const QObject* sender,
    									   PointerToMemberFuntion signal,
    									   Functor functor);
    
    // 示例:
    class A : public QObject {
          
          
    Q_OBJECT
    singals: void s(int i);
    };
    
    class B : public QObject {
          
          
    	Q_OBJECT
    public slots: 
    static void x(int i) {
          
          };
    };
    
    A ma;
    /*
    * void s(int i)是类 A 中定义的信号
    * void x(int i)是类 B 中定义的静态槽
    */
    QObject::connect(&ma, &A::s, &B::x);
    

    该函数的第三个参数支持仿函数、全局函数、静态函数、Lambda 表达式,但是唯独不能是类的非静态成员函数。
    该形式的函数其实是一个模板函数,其完整原型类似如下:

    template<typename PointerToMemberFunction, typename Functor>
    				  static QMetaObject::Connection connect(...)
    

    形式⑤:

    static QMetaObject::Connection QObject::connect(const QObject* sender,
    												const QMetaMethod& signal,
    												const QObject* receiver,
    												const QMetaMethod& method,
    												Qt::connectionType type = Qt::AutoConnection);
    // 此函数的工作方式与形式1相同,只是它使用QMetaMethod指定的信号和槽。
    
  2. 形式③与形式①的区别:
    ①:形式 1 的 SINGAL 和 SLOT 宏实际是把该宏的参数转换为字符串,当信号和槽相关联时,使用的是字符串进行匹配,因此,信号和槽的参数类型的名字必须在字符串意义上相同,所以信号和槽无法使用兼容类型的参数,也因此也不能使用 typedef 或namespace 的类型,虽然他们的实际类型相同,但由于字符串名字不同,从而无法使用形式 1。
    ②:形式 3 的信号和槽函数的参数类型不需完全一致,可以进行隐式转换。形式 3 还支持typedef 和命名空间。
    ③:形式 3 以指针的形式指定信号和槽函数,不需再使用 SIGNAL()和 SLOT 宏。
    ④:形式 3 的槽函数可以不使用 slots 关键字声明,任意的成员函数都可以是槽函数。形式 1 的槽函数必须使用 slots 修饰。
    ⑤:形式 1 的槽函数不受 private 的限制,也就是说即使槽是 private 的,仍可通过信号调用该槽函数,而形式 3 则在使用 connect 时就会发生错误。
    ⑥:当信号或槽函数有重载的形式时,使用形式 3 可能会产生二义性错误,此时可使用函数指针的形式指定信号或槽函数,或者使用形式 1,比如:

    class A : public QObject {
          
          
    	 Q_OBJECT
     singals: void s(int i);
    };
    
    class B : public QObject {
          
          
    	Q_OBJECT
     public slots: 
     		void x() {
          
          };
     		void x(int i) {
          
          };
    }
    
    A ma;  B mb;
    QObject::connect(&ma, &A::s, &mb, &B::x);  // error,二义性错误
    //解决方式:
    QObject::connect(&ma, &A::s, &mb, static_cast<void (B::&)(int)>(&B::x)); // OK,
    
2.4、 断开信号与槽

老实说,我接触Qt以来,还从来没有使用过断开信号与槽,只是看源码的时候看见有这个函数,然后问了一些Qt开发丰富的老师和朋友,这里直接给你们说结论吧!

断开的意义:更加灵活地控制你好的触发和槽函数的执行,提高程序的效率和管理资源的安全性。

信号和槽使用QObject类中的成员函数disconnect函数断开其关联,该函数有多个重载版本,如下所示:

  1. 形式①:
    static bool QObject::disconnect(const QObject* sender,
    								const char* signal,
    								const QObject* receiver,
    								const char* method);
    
    /*
    * 断开sender对象中的信号singal与receiver对象中槽函数method的关联
    * 注意:此函数指定signal 和 method时需要使用SIGNAL 和 SOLT宏。
    * 如果连接成功断开,则返回true,否则返回false
    * 当所涉及的任何一个对象被销毁时,信号槽连接被移除
    * 0 可以用作通配符,分别表示“任意信号”、“任何接收对象” 或 “接收对象中的任何插槽”
    * sender 永远不会为0
    * 如果 signal 为 0,则将 receiver 对象中的槽函数 method 与所有信号断开。否则,则只与指定的信号断开
    * 此方法可断开槽与所有信号的关联,比如:
    * 类 A 中有信号 void s()和 void s1(); 类 B 中有槽 void x();
    */
    
    A ma; B mb;
    // 然后把 ma 中的信号 s 和 s1 与 mb 中的槽 x 相关联
    QObject::connect(&ma, &A::s, &mb, B::x);
    QObject::connect(&ma, &A::s1, &mb, B::x);
    // 若 signal 为 0,则 mb 中的 x 会断开与 s 和 s1 的关联
    QObject::disconnect(&ma, 0, &mb, SLOT(x()));
    
    /*
    * 如果 receiver 为 0,此时 method 也必须为 0
    * 如果 method 为 0,则断开与连接到 receiver 的任何连接。否则,只有命名 method 的
    * 槽将被断开连接,而所有其他槽都将被单独保留。如果没有 receiver,则 method 必须为 0,
    * 因此不能断开所有对象上指定的槽。比如:
    * 类 A 中有信号 void s(); 类 B 中有槽 void x(); 类 C 中有槽 void y();
    * A ma; B mb, mb1; C mc;
    * 然后把信号 s 与对象 mb 中的槽 x、对象 mb1 中的槽 x、对象 mc 中的槽 y 相关联,
    * 若 receiver 被指定为 mb,method 为 0,则 mb 中的 x、mb1 中的 x 会与信号 s 断开,但 mc 中的 y不会与信号 s 断开
    * 若 receiver 被指定为 mb,method 为 x,则 mb 中的 x 会与信号 s 断开,mb1 中的 x、mc 中的 y 不会与 s 断开
    */
    
    // 除此之外,还有以下几种常用的用法:
    disconnect(&ma, 0, 0, 0);  // 断开与对象 ma 中的所有信号相关联的所有槽
    disconnect(&ma , SIGNAL(s()), 0, 0); // 断开与对象 ma 中的信号 s 相关联的所有槽
    disconnect(&ma, 0, &mb, 0; // 断开 ma 中的所有信号与 mb 中的所有槽的关联
    
  2. 形式②:
    static bool QObject::disconnect ( const QMetaObject::Connection &connection)
    该函数断开使用 connect 函数返回的信号和槽的关联,若操作失败则反回 false。
  3. 形式③:
    static bool QObject::disconnect(const QObject *sender,
    								PointerToMemberFunction signal,
    							 	const QObject*receiver, 
    							 	PointerToMemberFunction method);
    
    此方法与形式 1 相同,只是指定函数的方式是使用函数指针。
    注意:该函数不能断开信号连接到一般函数或 Lambda 表达式之间的关联,此时需要
    使用形式 2 来断开这种关联。
  4. 形式④:
    static bool QObject::disconnect(const QObject *sender,
     								const QMetaMethod &signal,
    								const QObject*receiver, 
    								const QMetaMethod &method);
    
    该函数与形式 1 相同,只是指定函数的方式是使用 QMetaMethod 类。
  5. 形式⑤:
    bool QObject::disconnect(const char *signal = Q_NULLPTR,
    						 const QObject *receiver = Q_NULLPTR,
    						 const char *method = Q_NULLPTR) const;
    
    注意:该函数是非静态的,该函数是形式 1 的重载形式。
  6. 形式⑥:
    bool QObject::disconnect(const QObject *receiver,
    						 const char *method = Q_NULLPTR) const;
    
    注意:该函数是非静态的,该函数是形式 1 的重载形式。

注意:若关联信号和槽时使用的是函数指针形式,则在断开信号和槽时,最好使用相对应
的函数指针形式的 disconnect 版本,以避免产生无法断开关联的情形。

2.5、 关键字原型
Key Descrition
signals 最终被#define 置换为一个访问控制符,其简化后的语法为: #define signals public
slots 最终被#define 置换为一个空宏,即简化后的语法为:#define slots
emit 同样被#define 置换为一个空宏,即简化后为:#define emit

以上关键字选中按F2就可以跳转到qobjectdefs.h中查看原型。
通过原型可以看出来,使用关键字,比如emit,其实就是一个简单的函数调用。

03、对象树与生命期

  1. 为什么要使用对象树:GUI 程序通常是存在父子关系的,比如一个对话框之中含有按钮、列表等部件,按钮、列表、对话框等部件其实就是一个类的对象(注意是类的对象,而非类),很明显这些对象之间是存在父子关系的,因此一个 GUI 程序通常会由一个父对象维护着一系列的子对象列表,这样更方便对部件的管理,比如当按下 tab 键时,父对象会依据子对象列表令各子对象依次获得焦点。当关闭对话框时,父对象依据子对象列表,找到每个子对象,然后删除它们。在 Qt 中,对对象的管理,使用的是树形结构,也就是对象树。
  2. 子对象和父对象:本小节的父/子对象是相对于由对象组成的树形结构而言了,父节点对象被称为父对象,子节点对象被称为子对象。注意:子对象并不是指类中的对象成员。

对象树和生命期,emmm,说实话,了解即可,知道怎么回事儿,也许以后面试会被问到,翻阅了一些资料,整理了一下,简单记录一下。

3.1、组合模式与对象树
  1. 组合模式指的是把类的对象组织成树形结构,这种树形结构也称为对象树,Qt 使用对象树来管理 QObject 及其子类的对象。注意:这里是指的类的对象而不是类。把类组织成树形结构只需使用简单的继承机制便可实现。
  2. 使用组合模式的主要作用是可以通过根节点对象间接调用子节点中的虚函数,从而可以间接的对子节点对象进行操作。
  3. 组合模式的基本思想是使用父类类型的指针指向子类对象,并把这个指针存储在一个数组中(使用容器更方便),然后每创建一个类对象就向这个容器中添加一个指向该对象的指针。

继承的思想,C++开发者应该不会陌生,下面的例子就是这个思想:

#include <iostream>
#include <vector>  // 使用STL容器vector
using namespace std;

class A {
    
     //顶级父类
public:
	vector<A*> v;	//存储父类类型指针,也可以使用数组,但是数组没法自动扩容
public: // 养成成员变量和成员函数明确分开来写,即使是同权限
	void add(A* ma) {
    
    
		v.push_back(ma);		
	}
};

class B : public A {
    
     // 需要继承自A
public:
	void add(A* ma) {
    
    
		v.push_back(ma);
	}
};

class C : public A {
    
    
// 该类继承自A,但是没有add函数,所以该类的对象不能有子对象
};

int main(int argc, char* argv[])
{
    
    
	A ma, ma1, ma2, ma3;
	B mb, mb1, mb2, mb3;
	C mc, mc1, mc2, mc3;
	// 创建对象树
	ma.add(&mb);
	ma.add(&mb1);
	mb.add(&mb3);
	mb.add(&mc);
	mb.add(&mc1);
	mb1.add(&mc2);
	mb1.add(&ma1);
	mb1.add(&ma2);
}

对象树结构图大致就是如下:
Insert image description here

一个组合模式的小示例:

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class A {
    
    
public:
	string name; // 用于存储创建的对象的名称
	vector<A*> v; // 创建一个存储父类类型指针的容器
public:
	A() {
    
    }  // 无参构造
	A(string n) {
    
     name = n; } // 有参构造
	void add(A* ma) {
    
    
		v.push_back(ma);
	}
	virtual string g() {
    
     return name; }  // 虚函数,用于获取对象名
	void f() {
    
      // 用于显示当前对象的容器v中存储的内容
		if(!v.empty()) {
    
     // 若v不为空
			cout << "name: " <<; // 输出当前对象名称
			for(vector<int>::size_type i = 0; i != v.size(); ++i)
			{
    
    
				cout << v[i]->g() << ",";  // 输出容器v中存储的对象的名称,注意g是虚函数
				cout << endl;
			}
		}
	}
	
	virtual void pt() {
    
     // 该函数会被递归调用,用以输出整个对象树中对象的名称。
		f();
		for(vector<int>::size_type i = 0; i != v.size(); i++)
		{
    
    
			v[i]->pt(); // 注意pt是虚函数,假如v[i]类型为其子类型B时,则会调用B::pt()
		}
	}
};

class B : public A {
    
     // 继承自类A,代码与A类似
public:  
	string name;
public:
	B(string n){
    
    name=n;}
	void add(A* ma) {
    
     
		v.push_back(ma); 
	}
	string g(){
    
     return name;}
	void f(){
    
    
		if(!v.empty()){
    
    
			cout<<name<<"="; 
			for (vector<int>::size_type i = 0; i!=v.size(); i++) {
    
    
					cout<<v[i]->g()<<",";
				}
			cout<<endl;
			} 
		} // f结束
void pt(){
    
    
	  f();
	  for (vector<int>::size_type i = 0; i!=v.size(); i++) {
    
    
	   v[i]->pt(); 
 		} 
 	}
}; // 类B结束

class C : public A {
    
    
public: //需要继承自类A,该类无add函数,也就是说该类的对象不能有子对象。
	string name;
public:
	C(string n){
    
     name=n;}
	void f(){
    
    
		if(!v.empty()){
    
     
			cout<<name<<"="; 
			for (vector<int>::size_type i = 0; i!=v.size(); i++) {
    
    	
				cout<<v[i]->g()<<",";
				}
			cout<<endl;  
			}  
		} //f结束
	string g(){
    
    return name;}
	void pt(){
    
    
	  f();
	  for (vector<int>::size_type i = 0; i!=v.size(); i++) {
    
    
	  	 v[i]->pt();
	  	}
}; //类C结束


int main() {
    
    
	//创建对象时传递该对象的名称以便存储。
	A ma("ma"),ma1("ma1"),ma2("ma2"),ma3("ma3"),ma4("ma4");
	B mb("mb"),mb1("mb1"),mb2("mb2"),mb3("mb3"),mb4("mb4"); 
	C mc("mc"),mc1("mc1"),mc2("mc2"),mc3("mc3"),mc4("mc4");
	ma.add(&mb); //ma.v[0]=&mb;
	mb.add(&mb1); //mb.v[0]=&mb1;
	mb.add(&mb2); //mb.v[1]=&mb2;
	ma.add(&mb1); //ma.v[1]=&mb1;
	mb1.add(&mb3); //mb1.v[0]=&mb3;
	mb1.add(&mb4); //mb1.v[1]=&mb4;
	mb2.add(&mc1); //mb2.v[0]=&mc1;
	mb2.add(&mc2); //mb2.v[1]=&mc2;
	mb2.add(&ma1); //mb2.v[2]=&ma1;
	cout<<"各对象拥有的子对象"<<endl;
	ma.f(); mb.f(); mb1.f(); mb2.f();
	cout<<endl<<"整个对象中结构"<<endl;
	ma.pt();
}

运行结果大致如下:
Insert image description here

3.2、 QObject、对象树、生命期

鉴于自己也没咋理解,也写不出来什么示例,这里只普及一下理论。

  1. 方便理解,把由 QObject 及其子类创建的对象称为 QObject、QObject 对象或 Qt 对象。在 Qt 中,QObject 对象通常就是指的 Qt 部件。

  2. QObject类是所有 Qt 对象的基类,是 Qt 对象模型的核心,所有 Qt 部件都继承自 QObject。

  3. QObject 及其派生类的单形参构造函数应声明为 explicit,以避免发生隐式类型转换。

  4. QObject 类既没有复制构造函数也没有赋值操作符函数(实际上它们被声明为私有的),因此无法通过值传递的方式向函数传递一个 QObject 对象。

  5. Qt 库中的 QObject 对象是以树状结构组织自已的,当创建一个 QObject 对象时,可以为其设置父对象,新创建的对象会被加入到父对象的子对象列表中(可通过QObject::children()函数查询),因为 Qt 的部件类,都是以 QObject 为基类,因此,Qt 的所有部件类都具有对象树的特性。

  6. 对象树的组织规则:
    ①:每一个 QObject 对象只能有一个父 QObject 对象,但可以有任意数量的子 QObject 对象。比如:

    A ma; B mb; C mc;
    ma.setParent(&mb);  // 将对象ma添加到mb的子对象列表中
    ma.setParent(&mc);  // 该语句会把 ma 从 mb 的子对象列表中移出,并将其添加到mc 的子对象列表中
    

    ②:QObject 对象会把指向各个子对象地址的指针放在 QObjectList 之中。QObjectList 是QList<QObject*>的别名,QList 是 Qt 的一个列表容器。

  7. 对象删除规则(注意:以下规则并非 C++语法规则,而是 Qt 的对象树规则):
    ①:基本规则:父对象会自动删除子对象。父对象拥有对象的所有权,在父对象被删除时会在析构函数中自动删除其子对象。
    ②:手动删除子对象:当手动删除子对象时,会把该子对象从父对象的列表中移除,以避免父对象被删除时该子对象被再次删除。总之 QObject 对象不会被删除两次。
    ③:当一个对象被删除时,会发送一个 destroyed()信号,可以捕捉该信号以避免对 QObject对象的悬垂引用。

  8. 对象创建规则:
    ①:子对象通常应创建在堆中(使用 new 创建),此时就不再需要使用 delete 将其删除了,当父对象被删除时,会自动删除该子对象。
    ②:对于 Qt 程序,父对象通常创建在栈上,不应创建在堆上(使用 new 创建)
    ③:子对象不应创建在栈中,因为若父对象比子对象更早的结束生命期(即父对象创建于子对象之后),则子对象会被删除两次,第一次发生在父对象生命期结束时,由 Qt 对象树的规则,使用父对象删除子对象,第二次发生在子对象生命期结束时,由 C++规则删除子对象。这种错误可使用先创建父对象后创建子对象的方法解决,依据 C++规则,子对象会先被删除,由 Qt 对象树规则知,此时子对象会从父对象的列表中移除,当父对象结束生命期时,就不会再次删除子对象了。

  9. 其他规则:应确保每一个 QObject 对象在 QApplication 之后创建,在 QApplication 销毁之前销毁,因此 QObject 对象不能是 static 存储类型的,因为 static 对象将在 main()返回之后才被销毁,其销毁时间太迟了。

  10. 对象的名称:可以为每个对象设置一个对象名称,其主要作用是方便对对象树进行查询和管理。对象名称和对象是不同的,比如 A ma; 其中 ma 是对象,若为 ma 设置一个名称为“SSS”,则对象 ma 的对象名称为“SSS”。

  11. 设置父对象的方法:
    ①:创建对象时,在构造函数中指定父对象,QObject 类及其子类都有一个形如 QObject*parent=0 的形参的构造函数,因此我们可以在创建对象时在构造函数中直接指定父对象。
    ②:使用 void QObject::setParent(QObject *parent)函数为该对象设置父对象。

  12. Set the object name: The object name is specified by the objectName attribute of QObject (the default value is an empty string). The reading functions of this attribute are as follows (Note: The class name of the object can be queried. QMetaObject::className())

QString objectName() const   // 读取该对象的名称
void setObjectName(const QString& name); // 设置该对象的名称为name
  1. To query information about the object tree, you can use the following member functions in the QObject class:
Function Descrition
QObject* parent() const Returns a pointer to the parent object
const QObjectList& children() const Returns a list of pointers to all child objects in the parent object, with the newly added child object at the end of the list (certain operations can change this order, such as raising or lowering QWidget child objects). The QObjectList types are as follows:typedef QList<QObject*> QObjectList;
QList<T> findChildren ( const QString& name=QString(),Qt::FindChildOptions options=Qt::FindChildrenRecursively) const Returns all sub-objects that can be converted to type T and whose name is name. If there is no such object, an empty list is returned. If name is the default value, the names of all objects will be matched. The function is executed recursively. The main purpose of this function and findChild introduced below is to obtain the pointer to the child object (or child component) through the parent object (or parent component). name parameter: This parameter is QObject::setObjectNamethe name set by the function. options parameter: This parameter is used to specify the method of querying sub-objects. This parameter needs to specify an FindChildOptionenumeration value of type, FindChildOptions(note that there is an extra s at the end) which is QFlags<FindChildOption>the result of renaming using typedef. This parameter can take the following two FindChildOptiontypes of enumeration values: ①: Qt::FindDirectChlidrenOnly: Indicates to find the direct sub-object of the object. ②: Qt::FindChildrenRecursively: Indicates recursively querying all sub-objects of the object.
T findChild(const QString& name=QString(),Qt::FindChildOptions options=Qt::FindChildrenRecursively) const This function has the same effect as findChildren, but this function can only return a single child object.
void dumpObjectTree() This function can output the entire object tree structure of an object in debug mode. This function does nothing in release mode.

This is roughly the content. This article was written for nearly a week. I checked a lot of information and sorted it out. After all, I don’t do my homework and write articles so seriously every time. Of course I have to record it.
In the meta-object system section, there is one last event to be sorted out!

Guess you like

Origin blog.csdn.net/m0_43458204/article/details/131786470