怎么优化网站内容,免费自助制作永久网站,河南新闻最新消息10条,电商设计作品集作为一名十几年的 C 程序员#xff0c;最近一段时间使用 QT 开发程序#xff0c;发现 QT 中还是有许多值得深入理解的技术。QT 不仅仅是一个应用程序开发框架#xff0c;还有一些对标准 C 的扩充。本文和大家一起探讨 QT 中的元对象系统。 在分析 QT 中的元对象系统之前 程序员最近一段时间使用 QT 开发程序发现 QT 中还是有许多值得深入理解的技术。QT 不仅仅是一个应用程序开发框架还有一些对标准 C 的扩充。本文和大家一起探讨 QT 中的元对象系统。 在分析 QT 中的元对象系统之前我们先回顾一下 C 中的 RTTI 机制。 C的RTTI机制 RTTI 是 Runtime Type Identification 的缩写字面意思是运行时类型识别。C引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型。 为什么需要在运行时根据基类的指针或引用来获得实际所指对象的类型呢这又牵扯到面向对象编程中的多态。 C 中的多态是指通过一个基类指针或引用调用一个虚函数时会根据具体对象的类型来调用该虚函数的不同实现。这样可以实现对象间的通信和转换以及多态的行为和表现。举个例子 #include iostream
#include vector
using namespace std;class Animal {
public:virtual void speak() 0; // pure virtual function
};class Cat : public Animal {
public:void speak() override {cout Meow endl;}
};class Dog : public Animal {
public:void speak() override {cout Woof endl;}
};int main() {vectorAnimal* animals; // create a vector of Animal pointersanimals.push_back(new Cat()); // push a Cat object to the vectoranimals.push_back(new Dog()); // push a Dog object to the vectorfor (auto a : animals) { // use range-based for loop to iterate the vectora-speak(); // polymorphism, calls Cat::speak or Dog::speak}return 0;
} 在上面的代码中假设是一个屋子里的动物调用者不用关心具体是猫还是狗直接调用共同的接口 speak 即可。 可以看出多态的好处很明显可以实现代码的抽象和封装因为我们可以通过一个基类指针或引用来隐藏对象的具体类型和实现细节而只暴露对象的公共接口。这在基于插件的系统架构中使用得非常广泛比如 Visual Studio Code 就是靠插件支撑起来的。多态是一种非常有用的编程技巧它可以让我们的代码更加复用和扩展以及更加抽象和封装提高我们的编程效率和质量。 但有的时候我们可能需要在运行时鉴别出目前的对象是猫或者狗比如狗需要定时出去遛猫不需要。一种解决方法是在基类 Animal 中定义一个 walk方法并给一个默认实现: class Animal {
public:virtual void speak() 0; // pure virtual functionvirtual void walk() {}
}; 在 Dog 类中重写 walk 方法而在 Cat 类中直接使用缺省的空实现。但这种方法有个明显的问题就是会引起类方法的膨胀随着继承越来越多会发现不同类之间有差别的方法越来越多都塞进基类会使得类臃肿不堪。 这个时候就可以请 RTTI 机制出场了。 C 的 RTTI 主要包括两个关键字typeid 和 dynamic_cast。typeid 运算符用于返回表达式的类型。dynamic_cast 运算符用于将基类类型的指针或引用安全地转换为其派生类类型的指针或引用。 typeid 运算符返回一个对 type_info 对象的引用其中type_info 是在头文件中定义的一个类这个类重载了 和 ! 运算符以便可以用于对类型进行比较。type_info 类的实现随厂商而异但包含一个 name() 成员该函数返回一个随实现而异的字符串通常但并非一定是类的名称。例如下面的代码可以判断 pg 指向的是否是 ClassName 类的对象 #include typeinfo
...
if (typeid(Dog) typeid(*a)) {// a points to a Dog object
} dynamic_cast运算符可以用于指针和引用的类型转换它的语法如下 dynamic_casttype(expression) 其中type 是目标类型expression 是要转换的表达式。如果转换成功dynamic_cast 返回一个指向目标类型的指针或引用如果转换失败dynamic_cast 返回一个空指针或引发一个 bad_cast 异常。dynamic_cast 的转换成功的条件是expression 的类型必须是 type 的公有基类或者 type 的公有派生类而且 expression 的类型必须包含虚函数否则编译器会报错。例如下面的代码可以将一个基类指针转换为一个派生类指针 class Base {
public:virtual ~Base() {} // virtual destructor
};class Derived : public Base {
public:void foo() { ... } // derived class method
};Base *pb new Derived(); // base class pointer points to a derived class object
Derived *pd dynamic_castDerived *(pb); // convert to derived class pointer
if (pd) {pd-foo(); // call derived class method
} 如果 pb 指向的不是一个 Derived 类的对象那么 pd 将为 nullptr 无法调用 foo() 方法。 这两个运算符都需要在编译器设置中开启 RTTI 的支持否则可能会出现运行时错误。但是我们在编译程序时通常是没有开启 RTTI 支持的。这是因为 RTTI 会增加程序的开销和复杂度道理很简单RTTI 需要在编译器和运行时系统中维护额外的类型信息。C 作为一个追求效率的语言默认是没有开启 RTTI 的。 C 的 RTTI 机制的优点则是它是一种标准的、跨平台的、内置的类型识别机制只要编译器支持就可以使用。 QT 的元对象系统 QT 的元对象系统是一种在 C 语言之上的扩展相较于 RTTI更加强大如信号和槽机制、运行时类型信息、动态属性系统等。QT 的元对象系统的核心是 QObject 类它是所有可以利用元对象系统的类的基类。 还记得我们在 QT 中定义类通常会继承自 QObject 或其子类并且还会使用一个奇怪的宏Q_OBJECT。 QObject类定义了一些元数据如类名、父类名、信号、槽、属性等这些元数据可以在运行时被访问和操作。而为了启用元对象系统需要在类声明的私有部分内使用 Q_OBJECT 宏这个宏会告诉元对象编译器moc对这个类进行处理。 元对象编译器moc是一个工具它会扫描源代码中包含 Q_OBJECT 宏的类提取其中的元数据并生成相应的元对象代码。这些代码被编译到最终的可执行文件中供 QT 的运行时系统使用。运行时系统可以通过元对象表来访问和操作对象的元数据实现信号和槽的连接、动态属性的添加和访问等功能。 信号和槽机制是 QT 的最大特色它是一种对象间通信的方式。信号和槽都是成员函数信号是当对象状态发生变化时发出的消息槽是对信号做出响应的动作。信号和槽可以在不同的对象、不同的线程之间进行连接实现松耦合的交互。信号和槽的声明和定义都需要使用特定的宏如 signals、slots、emit等这些宏会被 moc 转换为元对象代码。例如下面的代码定义了一个自定义的信号和槽 class MyWidget : public QWidget {Q_OBJECT // enable meta-object system
public:MyWidget(QWidget *parent nullptr);...
signals: // declare signalsvoid mySignal(int value); // a custom signal
public slots: // declare slotsvoid mySlot(int value); // a custom slot
};MyWidget::MyWidget(QWidget *parent) : QWidget(parent) {...connect(this, MyWidget::mySignal, this, MyWidget::mySlot); // connect signal and slot...
}void MyWidget::mySlot(int value) {// do something when the signal is emitted...
} 动态属性系统是一种在运行时给对象添加和访问属性的方式。属性是对象的一些特征如颜色、大小、位置等。动态属性系统允许在不修改类定义的情况下给对象添加新的属性或者修改已有属性的值。动态属性系统使用 QVariant 类来存储属性的值。 QVariant类是一种通用的数据类型它可以存储各种类型的值并在运行时进行类型转换。动态属性系统使用 setProperty() 和 property() 函数来设置和获取属性的值。例如下面的代码给一个按钮对象添加了一个自定义的属性 QPushButton *button new QPushButton(OK);
button-setProperty(myProperty, 123); // add a custom property
int value button-property(myProperty).toInt(); // get the property value 可以看出QT 的元对象系统的优点还是比较明显的它是一种基于 C 的、跨平台的、高级的类型识别机制它可以让程序在运行时获取和操作对象的类型信息实现对象间的无缝交互以及在运行时动态地修改对象的行为和外观。 当然缺点也比较明显它需要在类声明中使用特殊的宏对于有代码洁癖的人来说难以忍受。此外还需要使用一个额外的工具moc来生成元对象代码这可能会增加程序的编译时间和复杂度而且它可能会与一些 C 的特性不兼容如多重继承、模板等。 小结 C 的 RTTI 机制和 QT 的元对象系统这两种机制都可以在运行时获取和操作对象的类型信息实现对象间的通信和转换。 RTTI 是一种标准的、安全的、内置的类型识别机制它可以让程序在运行时识别出对象的类型并进行安全的类型转换。它的缺点是它会增加程序的开销和复杂度而且它可能会破坏程序的封装性和抽象性导致程序的设计不够优雅和灵活。元对象系统是一种高级的、灵活的、扩展的类型识别机制它可以让程序在运行时获取和操作对象的类型信息实现对象间的无缝交互以及在运行时动态地修改对象的行为和外观。它的缺点是它需要在类声明中使用特殊的宏以及使用一个额外的工具moc来生成元对象代码这可能会增加程序的编译时间和复杂度而且它可能会与一些C的特性不兼容如多重继承、模板等。RTTI 和元对象系统都有各自的优缺点它们适用于不同的场景和需求。一般来说如果我们只需要进行简单的类型识别和转换而且不需要使用信号和槽、动态属性等功能那么我们可以使用 RTTI 。如果我们需要进行复杂的类型识别和转换而且需要使用信号和槽、动态属性等功能那么我们可以使用元对象系统。 当然如果要使用 QT 的元对象系统势必需要把 QT 整套框架引入。这里对 C 和 QT 的初学者和爱好者提供一些有用的信息和参考希望对大家有所帮助。