当前位置: 首页 > article >正文

QVariant:Qt中万能类型的使用与理解

目录

1.引言

2.QVariant的用法

2.1.包含头文件

2.2.基本类型的存储与获取

2.3.自定义类型的存储与获取

2.4.枚举类型的存储与获取

2.5.类型检查与转换

2.6.容器类型的存储与获取

3.枚举的问题

4.信号槽中使用自定义结构体

4.1.使用QVariant转换

4.2.直接传递自定义结构体

5.性能开销

5.1. 内存开销

5.2. 时间开销

5.3. 与直接使用具体类型的对比

6.总结


1.引言

        在Qt框架中,QVariant类是一个非常强大的工具,它允许我们存储各种类型的数据,并在需要时将其转换为原始类型。QVariant类似于C语言中的void*,但它更加类型安全,并且能够存储多种数据类型。然而,QVariant在处理枚举类型(enum)、自定义结构体时存在一些限制。本文将探讨如何解决这一问题,使QVariant能够与枚举类型、自定义结构体协同工作。

2.QVariant的用法

  QVariant 是 Qt 中一个非常强大且灵活的类,它可以存储多种不同类型的数据,并且可以在不同类型之间进行转换。

2.1.包含头文件

在使用 QVariant 之前,需要包含相应的头文件:

#include <QVariant>

这一点我是经常忘掉,导致编译错误,但是又不是很容易发现是没有包含头文件QVariant。

2.2.基本类型的存储与获取

QVariant 可以存储许多基本数据类型,如整数、浮点数、字符串等。

存储基本类型

#include <QVariant>
#include <QDebug>

int main() {
    // 存储整数
    QVariant intVariant = 42;
    // 存储浮点数
    QVariant doubleVariant = 3.14;
    // 存储字符串
    QVariant stringVariant = "Hello, World!";

    return 0;
}

获取基本类型

#include <QVariant>
#include <QDebug>

int main() {
    QVariant intVariant = 42;
    QVariant doubleVariant = 3.14;
    QVariant stringVariant = "Hello, World!";

    // 获取整数
    int intValue = intVariant.toInt();
    // 获取浮点数
    double doubleValue = doubleVariant.toDouble();
    // 获取字符串
    QString stringValue = stringVariant.toString();

    qDebug() << "Int value:" << intValue;
    qDebug() << "Double value:" << doubleValue;
    qDebug() << "String value:" << stringValue;

    return 0;
}

2.3.自定义类型的存储与获取

对于自定义类型,需要将其注册到 Qt 的元对象系统中,以便 QVariant 能够处理。

定义自定义类型

#include <QObject>
#include <QVariant>
#include <QDebug>

// 定义自定义类型
class MyClass {
public:
    MyClass(int value) : m_value(value) {}
    int value() const { return m_value; }
private:
    int m_value;
};

// 注册自定义类型
Q_DECLARE_METATYPE(MyClass)

int main() {
    // 存储自定义类型
    MyClass myObject(123);
    QVariant customVariant = QVariant::fromValue(myObject);

    // 获取自定义类型
    if (customVariant.canConvert<MyClass>()) {
        MyClass retrievedObject = customVariant.value<MyClass>();
        qDebug() << "Retrieved value:" << retrievedObject.value();
    }

    return 0;
}

2.4.枚举类型的存储与获取

对于枚举类型,需要使用 Q_ENUM 宏将其注册到元对象系统中。

#include <QObject>
#include <QVariant>
#include <QDebug>

// 定义枚举类型
class MyEnumClass : public QObject {
    Q_OBJECT
public:
    enum MyEnum {
        Option1,
        Option2,
        Option3
    };
    Q_ENUM(MyEnum)
};

int main() {
    // 存储枚举值
    QVariant enumVariant = QVariant::fromValue(MyEnumClass::Option2);

    // 获取枚举值
    if (enumVariant.canConvert<MyEnumClass::MyEnum>()) {
        MyEnumClass::MyEnum retrievedEnum = enumVariant.value<MyEnumClass::MyEnum>();
        qDebug() << "Retrieved enum value:" << retrievedEnum;
    }

    return 0;
}

#include "main.moc"

2.5.类型检查与转换

QVariant 提供了一些方法来检查其存储的数据类型,并进行类型转换。

#include <QVariant>
#include <QDebug>

int main() {
    QVariant variant = 42;

    if (variant.type() == QVariant::Int) {
        qDebug() << "The variant contains an integer.";
    }

    if (variant.canConvert(QVariant::String)) {
        qDebug() << "The variant can be converted to a string.";
    }

    return 0;
}

类型转换

#include <QVariant>
#include <QDebug>

int main() {
    QVariant variant = 42;

    // 转换为字符串
    QString stringValue = variant.toString();
    qDebug() << "Converted to string:" << stringValue;

    return 0;
}

2.6.容器类型的存储与获取

QVariant 也可以存储 Qt 的容器类型,如 QListQMap 等。

#include <QVariant>
#include <QList>
#include <QDebug>

int main() {
    // 存储QList
    QList<int> intList = {1, 2, 3};
    QVariant listVariant = QVariant::fromValue(intList);

    // 获取QList
    if (listVariant.canConvert<QList<int>>()) {
        QList<int> retrievedList = listVariant.value<QList<int>>();
        for (int value : retrievedList) {
            qDebug() << "List value:" << value;
        }
    }

    return 0;
}

3.枚举的问题

假设我们有一个简单的枚举类型:

class EnumValue {
public:
    enum Values {
        V1 = 100,
        V2,
        V3
    };
};

我们希望将EnumValue::Values枚举类型存储在QVariant中,并在需要时将其取出。我们可能会尝试以下代码:

QVariant var = EnumValue::V1;
EnumValue::Values ev = var.value<EnumValue::Values>();
qDebug() << "var = " << ev;

不幸的是,这段代码会导致编译错误:

错误: 'qt_metatype_id' is not a member of 'QMetaTypeId<EnumValue::Values>'

问题的根源

这个错误的根本原因是,QVariant在存储和取出数据时,依赖于Qt的元对象系统。为了能够正确地存储和取出自定义类型(包括枚举类型),这些类型必须被Qt的元对象系统所识别。枚举类型默认情况下并没有被Qt的元对象系统处理,因此QVariant无法直接存储和取出枚举类型。

解决方案

方法一:强制类型转换

一种简单的解决方法是使用强制类型转换。由于枚举类型本质上是一个整数,我们可以将QVariant中的值转换为int,然后再将其转换为枚举类型:

QVariant var = EnumValue::V1;
EnumValue::Values ev = static_cast<EnumValue::Values>(var.toInt());
qDebug() << "var = " << ev;

这种方法虽然可行,但它看起来不够优雅,并且容易出错。

方法二:使用Q_DECLARE_METATYPE宏

为了更优雅地解决这个问题,Qt提供了一个宏Q_DECLARE_METATYPE,它可以将自定义类型(包括枚举类型)注册到Qt的元对象系统中。我们只需要在枚举类型定义之后添加这个宏:

class EnumValue {
public:
    enum Values {
        V1 = 100,
        V2,
        V3
    };
};

Q_DECLARE_METATYPE(EnumValue::Values)

然后,我们可以使用QVariant::fromValue()函数将枚举类型存储在QVariant中,并使用QVariant::value()函数将其取出:

QVariant var = QVariant::fromValue(EnumValue::V1);
EnumValue::Values ev = var.value<EnumValue::Values>();
qDebug() << "var = " << ev;

这次,代码将正确输出100,表示我们成功地将枚举类型存储并从QVariant中取出。

4.信号槽中使用自定义结构体

4.1.使用QVariant转换

首先定义一个结构体:

// 定义自定义结构体
struct MyStruct {
    int id;
    QString name;
};

然后用Q_DECLARE_METATYPE(MyStruct)注册自定义结构体

定义一个结构体并发送:

MyStruct stInfo;
stInfo.id = 100;
stInfo.name = "214124";
emit sig_sendInfo(QVariant::fromValue(stInfo));

接收端槽函数处理如下:

connect(信号类指针, &信号类::sig_trainInfo, 槽函数指针, [=](QVariant var){
        if (var.canConvert<TRANS_DOT_INFO>())
        {
            MyStruct stInfo = var.value<MyStruct>();
 
            //...
        }
    });

4.2.直接传递自定义结构体

在使用 Q_DECLARE_METATYPE 宏声明该结构体为元类型,然后在使用信号槽之前调用 qRegisterMetaType 函数进行注册。

// 声明元类型
Q_DECLARE_METATYPE(MyStruct)

// 在合适的地方(如 main 函数中)进行注册
int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    // 注册自定义结构体到元对象系统
    qRegisterMetaType<MyStruct>("MyStruct");

    // 后续代码...

    return a.exec();
}

完整的实例代码如下:

#include <QObject>
#include <QMetaType>
#include <QDebug>
#include <QApplication>

// 定义自定义结构体
struct MyStruct {
    int id;
    QString name;
};

// 声明元类型
Q_DECLARE_METATYPE(MyStruct)

// 发送信号的类
class Sender : public QObject {
    Q_OBJECT
public:
    explicit Sender(QObject *parent = nullptr) : QObject(parent) {}

signals:
    // 定义包含自定义结构体参数的信号
    void mySignal(const MyStruct &data);

public slots:
    void sendData() {
        MyStruct data;
        data.id = 1;
        data.name = "Example";
        // 发射信号
        emit mySignal(data);
    }
};

// 接收信号的类
class Receiver : public QObject {
    Q_OBJECT
public:
    explicit Receiver(QObject *parent = nullptr) : QObject(parent) {}

public slots:
    // 定义槽函数
    void handleSignal(const MyStruct &data) {
        qDebug() << "Received data - ID:" << data.id << ", Name:" << data.name;
    }
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    // 注册自定义结构体到元对象系统
    qRegisterMetaType<MyStruct>("MyStruct");

    // 创建发送者和接收者对象
    Sender sender;
    Receiver receiver;

    // 连接信号和槽
    QObject::connect(&sender, &Sender::mySignal, &receiver, &Receiver::handleSignal);

    // 触发信号
    sender.sendData();

    return a.exec();
}

#include "main.moc"

5.性能开销

QVariant 是 Qt 中一个用于存储多种不同数据类型的通用容器类,它为数据的存储和处理提供了很大的灵活性,但也带来了一定的性能开销,下面从几个方面详细分析:

5.1. 内存开销

  • 额外的元数据存储QVariant 除了要存储实际的数据外,还需要存储关于该数据类型的元信息。这包括数据类型的标识(如 QMetaType::Type),用于在运行时识别存储的数据类型。这种额外的元数据存储会增加内存的使用量,尤其是在需要存储大量 QVariant 对象时,内存开销会更加明显。
  • 深拷贝问题:对于一些复杂的数据类型(如自定义类或容器),QVariant 在存储时可能会进行深拷贝。深拷贝意味着会复制整个对象及其所有成员,这会占用额外的内存空间。例如,当存储一个包含大量元素的 QList 时,QVariant 会复制该列表的所有元素,导致内存使用量显著增加。

5.2. 时间开销

  • 类型检查和转换:在使用 QVariant 时,经常需要进行类型检查和转换操作。例如,使用 canConvert 方法检查是否可以将 QVariant 转换为特定类型,以及使用 toXXX 系列方法(如 toInttoString)进行类型转换。这些操作需要在运行时进行类型判断和转换逻辑,会带来一定的时间开销。特别是在频繁进行类型检查和转换的场景下,性能影响会更加突出。
    QVariant var = "123";
    if (var.canConvert<int>()) {
        int num = var.toInt();
    }
  • 构造和析构:创建和销毁 QVariant 对象也会有一定的时间开销。构造 QVariant 对象时,需要初始化其内部的元数据和存储数据;析构时,需要释放相关的资源。对于简单的数据类型,这种开销可能相对较小,但对于复杂的数据类型,开销会相应增加。

5.3. 与直接使用具体类型的对比

  • 性能差异:相比于直接使用具体的数据类型,QVariant 的性能要低很多。直接使用具体类型时,编译器可以进行更多的优化,并且不需要进行额外的类型检查和转换。例如,直接使用 int 类型进行计算的速度会比使用 QVariant 存储 int 类型并进行操作快得多。
    // 直接使用 int 类型
    int a = 10;
    int b = 20;
    int result = a + b;

    // 使用 QVariant 存储 int 类型
    QVariant varA = 10;
    QVariant varB = 20;
    if (varA.canConvert<int>() && varB.canConvert<int>()) {
        int resultVariant = varA.toInt() + varB.toInt();
    }
  • 适用场景:虽然 QVariant 存在性能开销,但在某些场景下它是非常有用的。例如,当需要处理多种不同类型的数据,或者在不同模块之间传递数据且数据类型不确定时,QVariant 可以提供很大的便利。但在对性能要求极高且数据类型明确的场景下,应尽量避免使用 QVariant

6.总结

   QVariant 是 Qt 框架中一个极为实用的类,它为不同类型数据的存储与处理提供了统一的解决方案,不妨去试一试吧!


http://www.kler.cn/a/589535.html

相关文章:

  • python中多重继承和泛型 作为模板让子类实现具体业务逻辑
  • Linux错误(2)程序触发SIGBUS信号分析
  • 基于Springboot+Typst的PDF生成方案,适用于报告打印/标签打印/二维码打印等
  • 开源文档管理系统 Paperless-ngx
  • 【后端开发面试题】每日 3 题(十三)
  • 利用golang embed特性嵌入前端资源问题解决
  • 【经验分享】SpringBoot集成WebSocket开发-03 使用WebSocketSession为每个对话存储独立信息
  • Vue3中正确解析RefImpl对象
  • Hyperlane:轻量、高效、安全的 Rust Web 框架新选择
  • Java 大视界 -- Java 大数据机器学习模型的对抗攻击与防御技术研究(137)
  • 为什么手机上用 mA 和 mAh 来表示功耗和能耗?
  • java学习总结:JSP、Servlet
  • vue3项目如何使用keepAlive?如何实现回退到这个页面时不刷新,跳转至这个页面时会刷新?
  • Redis主从集群和哨兵集群
  • CML(Current Mode Logic)电平详解
  • MyBatis XMLMapperBuilder 是如何解析 SQL 映射文件的? 它读取了哪些信息?
  • docker安装rabbitmq
  • pyyaml_include 2.x 版本使用说明
  • Spring Cloud Gateway 生产级实践:高可用 API 网关架构与流量治理解析
  • Linux应用软件编程(多任务:进程间通信)