在有些情况下,我们希望把数据存储在一个变量中。例如,我有一个数组,既希望存整数,又希望存浮点数,还希望存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();
}