百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Qt QVariant的使用(使用qt二次开发周立功can)

zhezhongyun 2025-06-24 18:09 30 浏览

在有些情况下,我们希望把数据存储在一个变量中。例如,我有一个数组,既希望存整数,又希望存浮点数,还希望存string。

想了一个方法,创建一个object类,这是一个很大的类,里面几乎包含了所有类型的数据,如下:

class Object  
 
{  
 
public:  
 
    int intValue;  
 
    float floatValue;  
 
    string stringValue;  
 
   ......
 
}; 

那这个类足以存储int,float,string了,这就类似于QVariant的思路。

Qt提供的QVariant类型,可以把这很多类型都存放进去,到需要使用的时候使用一系列的to函数取出来即可。比如你把int包装成一个QVariant,使用的时候要用QVariant::toInt()重新取出来。需要注意QVariant类型的放入和取出必须是相对应的,也就是你放入一个int就必须按int取出,不能用toString(),Qt不会帮你自动转换。

说白了,存储数据的核心无非就是一个 union,和一个标记类型的type,即假设传递的是整数123,那么它union存储整数123,同时type标志为int;如果传递字符串,union存储字符串的指针,同时type标志QString。

QVariant可以保存很多Qt的数据类型,包括QBrush、QColor、QCursor、QDateTime、QFont、QKeySequence、QPalette、QPen、QPixmap、QPoint、QRect、QRegion、QSize和QString,并且还有C++基本类型,如int、float等。QVariant还能保存很多集合类型,如QMap<QString, QVariant>, QStringList和QList<QVariant>。item view classes,数据库模块和QSettings都大量使用了QVariant类,,以方便我们读写数据。

基本用法:

(1)构造

QVariant v(1);
QVariant v1;
v1.setValue("boy");

(2)使用

if (v.canConvert<int>())
{
bool ok;
int i = v.toInt(&ok);
//int i = v.value<int>();
}

自定义存储QVariant类型

存储自定义类型需要在类声明后加上Q_DECLARE_METATYPE(),如下:

struct MyClass
{
    QString name;
    int age;
}
Q_DECLARE_METATYPE(MyClass)

上面这个类就可以像QMetaType::Type类一样使用,没多少区别,有一点区别就是,上面的类的使用只能如下使用:

MyClass myClass;
QVariant v3 = QVairant::fromValue(myClass);
//
v2.canConvert<MyClass>();
MyClass myClass2 = v2.value<MyClass>();

QVariant被用于构建Qt Meta-Object,因此是QtCore的一部分。当然,我们也可以在GUI模块中使用,例如:

QIcon icon("open.png");  
 
QVariant variant = icon;  
 
// other function  
 
QIcon icon = variant.value<QIcon>(); 

我们使用了value<T>()模版函数,获取存储在QVariant中的数据。这种函数在非GUI数据中同样适用,但是,在非GUI模块中,我们通常使用toInt()这样的一系列to...()函数,如toString()等。

如果你觉得QVariant提供的存储数据类型太少,也可以自定义QVariant的存储类型。被QVariant存储的数据类型需要有一个默认的构造函数和一个拷贝构造函数。为了实现这个功能,首先必须使用Q_DECLARE_METATYPE()宏。通常会将这个宏放在类的声明所在头文件的下面:

Q_DECLARE_METATYPE(BusinessCard)

然后就可以这样使用:

BusinessCard businessCard;  
 
QVariant variant = QVariant::fromValue(businessCard);  
 
// ...  
 
if (variant.canConvert<BusinessCard>())
 
 {  
 
     BusinessCard card = variant.value<BusinessCard>();  
 
    // ...  
 
}

VC 6的编译器有所限制,所以不能使用这些模板函数,如果使用这个编译器,需要使用qVariantFromValue(), qVariantValue<T>()和qVariantCanConvert<T>()这三个宏 。

应用场景1:

Qt一些类可以附带额外数据, 最常用的有QTreeWidgetItem.setData(int column, int role, QVariant &value).

有时经常需要在不同列上或者不同的role上增加一个额外的数据,

当额外数据多一点不会显示杂乱且有时不知存放在哪一列上.这样我们可以定义一个类将所有额外数据放在类中,只需将数据放在一个地方,不仅数据统一还好扩展:

item.setData(0, Qt::UserRole+1, QVariant::fromValue(myClass));

应用场景2

对于QMap<QString, QVariant>结构, 可以很清晰的实现全局配置存储, QString作为键, 而QVariant可存储配置值。

应用场景3

使用Qt的postEvent()

VxCommonLib的IMsgObserver中有封装了一个PostMsg, 可是它有个不友好的地方:每次需要PostMsg一个新的数据时必须做两件事

(1)定义一个转换函数

(2)定义一个转换的QEvent类.

一个简单的例子如下:

// 一个QEvent类
class CAudioDataEvent:public QEvent
{
public:
    CAudioDataEvent(QEvent::Type type);
    AUDIOUVMETER m_audioData;
};
 
// 转换函数
static QEvent *_createAudioDataEvent(void *theParam,int type)
{
    CAudioDataEvent *pAudioEvent = new CAudioDataEvent(QEvent::Type(type));
    memcpy(&pAudioEvent->m_audioData, theParam, sizeof(AUDIOUVMETER));
   return pAudioEvent;
}
 
//事件发出
void CVxPlayViewController_X::__audiovu(AUDIOUVMETER* theParam)
{
    IMsgObserver *pMsg = GetIMsgObserver();
    pMsg->PostMsg(m_pParent, _createAudioDataEvent, TURBO_EDIT_PLAYVIEW_AUDIODATA,theParam ,m_pParent);
}
 
//事件接收
CAudioDataEvent *vxDataEvent = static_cast<CAudioDataEvent *>(event);
AUDIOUVMETER audioData = vxDataEvent->m_audioData;

出现以上问题主要原因是找不到一个可以储存任意类型的结构导致每次都要重定义事件, 没错你想到了,根据上面的知识找到了解决方案.
我们先定义好数据,改进如下:

//事件类
class  CDsVariantDataEvent : public QEvent
{
public:
    CDsVariantDataEvent(Type type, QVariant variant);
 
    QEvent(type)
    {
        m_variant = variant;
    }
 
    inline QVariant data() const { return m_variant; }
 
protected:
    QVariant m_variant;
};
 
// 发送类
 void CDsMsgEngine::PostMsg(int eventType, QVariant variant/* = QVariant()*/)
 {
    QObject *anObject = NULL;
    MSGMAPITER it = m_msgMapTable.find(eventType);
    if(it != m_msgMapTable.end())
    {
        ObserverList *pObserverList = (*it).second;
        for(int i = 0; i < pObserverList->size(); ++i)
       {
             //csj 2010-7-3
            if((*pObserverList)[i].anObject==NULL ||  (*pObserverList)[i].observer==anObject)
            {
                   CDsVariantDataEvent *pEvent = new CDsVariantDataEvent(static_cast<QEvent::Type>(eventType),variant);
                   QApplication::postEvent((*pObserverList)[i].observer,pEvent);
             }
       }
    }
}

这样以后每次使用异步事件就可以:

//发送
GetIMsgObserver()->PostMsg(TURBO_EDIT_PLAYVIEW_AUDIODATA,QVariant::fromValue(MyClass)));
//接收
CDsVariantDataEvent *pEvent = static_cast<CDsVariantDataEvent *>(event);
MyClass myClass = pEvent->data().value<MyClass>();

应用场景4

自定义类型要能在队列信号槽中使用,使用Q_DECLARE_METATYPE()后还要使用qRegisterMetaType()注册成QMetaType类型, 使用队列信号槽其实是很强大的,但我们常会使用其它的方式解决问题。

3、序列化使用

要能序列化自定义类,还需要
qRegisterMetaTypeStreamOperators()注册,还需要实现两个函数:

QDataStream &operator<<(QDataStream &out, const MyClass &myObj);
QDataStream &operator>>(QDataStream &in, MyClass &myObj);

这样就可以将数据序列化, 到底这样有什么用呢。。。还未搞懂

正如上面实现的全局配置QMap<QString, QVariant>, 你是否想保存成配置文件等下次程序启动的时候再回复呢, 是的可以而且和你想的一样简单.

(1)保存配置

QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);   // we will serialize the data into the file
out << map; 

(2)读取配置

QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file);    // read the data serialized from the file
in >> map;  

唯一需要注意的是所有的
qRegisterMetaTypeStreamOperators()都要在读取配置文件之前, 否则会因为无法识别自定义类取序列化失败.

需要说明的是当你保存的配置是QFont,QRect,QKeySequence, QList<>等你才是知道他的强大之处, 可惜的是我们现在的程序往往用不着.

应用场景2

只要是继承QIODevice都可以序列化写和读, 如果我告诉你QAbstractSocket, QLocalSocket, QNetworkReply, QProcess都是继承QIODevice,

那么你是否会想到, 应用场景1中的功能都可以应用到网络数据交换和进程间通信. 实现过程其它和场景1一样,只不过是将QFile换成你想要

的类而已, 可惜的是我们也少用到.

4.QVariant源码实现

内部使用一个union管理所有类型, 对于复杂的类主要使用o, prt, shared管理. shared中存储的会使用隐式共享, 其它会进行深拷贝.

(1)内部存储结构

union Data
{
    char c;
    int i;
    uint u;
    bool b;
    double d;
    float f;
    qreal real;
    qlonglong ll;
    qulonglong ull;
    QObject *o;
    void *ptr;
    PrivateShared *shared;
} data;

当是几个常用基本类型, 刚直接将数据存储在char, int, bool等等上面, 继续QObject类将会放到o上,其它Qt内置类型,则存储在shared上, 而用户自定义的数据存储在ptr上.在构造QVariant
时有个大的switch负责存储责任.

static void construct(QVariant::Private *x, const void *copy)
{
    x->is_shared = false;
    switch (x->type)
 
{
    case QVariant::String:
            v_construct<QString>(x, copy);
            break;
    case QVariant::Char:
            v_construct<QChar>(x, copy);
            break;
    case QVariant::StringList:
           v_construct<QStringList>(x, copy);
           break;
    case QVariant::Map:
           v_construct<QVariantMap>(x, copy);
           break;
    case QVariant::Hash:
            v_construct<QVariantHash>(x, copy);
            break;
    case QVariant::List:
            v_construct<QVariantList>(x, copy);
            break;
    case QVariant::Date:
            v_construct<QDate>(x, copy);
            break;
    case QVariant::Time:
            v_construct<QTime>(x, copy);
            break;
    case QVariant::DateTime:
            v_construct<QDateTime>(x, copy);
            break;
    case QVariant::ByteArray:
            v_construct<QByteArray>(x, copy);
            break;
    case QVariant::BitArray:
           v_construct<QBitArray>(x, copy);
           break;
#ifndef QT_NO_GEOM_VARIANT
    case QVariant::Size:
           v_construct<QSize>(x, copy);
           break;
    case QVariant::SizeF:
           v_construct<QSizeF>(x, copy);
           break;
    case QVariant::Rect:
           v_construct<QRect>(x, copy);
           break;
    case QVariant::LineF:
           v_construct<QLineF>(x, copy);
           break;
    case QVariant::Line:
           v_construct<QLine>(x, copy);
           break;
    case QVariant::RectF:
           v_construct<QRectF>(x, copy);
           break;
    case QVariant::Point:
           v_construct<QPoint>(x, copy);
           break;
.............................. // 省略
    default:
        void *ptr = QMetaType::construct(x->type, copy);
        if (!ptr)
 
       {
              x->type = QVariant::Invalid;
        }
 
       else
 
      {
            x->is_shared = true;
            x->data.shared = new QVariant::PrivateShared(ptr);
       }
      break;
    }
    x->is_null = !copy;
}
 
//调用v.value()
inline T value() const
{ return qvariant_cast<T>(*this); }
//
template<typename T> inline T qvariant_cast(const QVariant &v)
{
    const int vid = qMetaTypeId<T>(static_cast<T *>(0));
    if (vid == v.userType())
        return *reinterpret_cast<const T *>(v.constData());  // 自定义数据使用reinterpret_cast<const T *>返回
    if (vid < int(QMetaType::User)) {
        T t;
        if (qvariant_cast_helper(v, QVariant::Type(vid), &t)) // Qt内置,使用一个模版返回相对应数据, switch太大就不放出来了.
            return t;
    }
    return T();
}

相关推荐

Python入门学习记录之一:变量_python怎么用变量

写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...

python变量命名规则——来自小白的总结

python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...

Python入门学习教程:第 2 章 变量与数据类型

2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...

绘制学术论文中的“三线表”具体指导

在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...

Python基础语法知识--变量和数据类型

学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...

一文搞懂 Python 中的所有标点符号

反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...

Python变量类型和运算符_python中变量的含义

别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...

从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序

在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...

Python中下划线 ‘_’ 的用法,你知道几种

Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...

解锁Shell编程:变量_shell $变量

引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...

一文学会Python的变量命名规则!_python的变量命名有哪些要求

目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...

更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for

src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...

C++第五课:变量的命名规则_c++中变量的命名规则

变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....

Rust编程-核心篇-不安全编程_rust安全性

Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...

探秘 Python 内存管理:背后的神奇机制

在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...