
总第011篇
本文主要梳理总结了在使用Qt做项目开发过程中,用QDataStream来正确操作QString类型数据的问题。这个地方非常容易出错,笔者当时在遇到这个问题时,耗费了大量的时间查找资料,现将解决方法总结成文,方便后来者借鉴之。
QDataStream类提供的读写二进制数据的能力很强,使用也非常方便,非常适合将自定义数据类型进行序列化网络传输,但在使用过程中也有一些问题需要特别注意。
1.QDataStream对QString的操作
先看下面一段代码:
char fileName[100];
memset(fileName,0,100);
QByteArray packData;
packData.clear();
QDataStream in(&packData,QIODevice::ReadWrite);
in.setByteOrder(QDataStream::BigEndian);
in.setVersion(QDataStream::Qt_5_9);
in<<fileName;
qDebug()<<"print : "<<packData.length();
以上代码的输出结果是:print: 5
。为什么结果会是5呢? 继续向下看下面的代码。
char fileName[100];
memset(fileName,1,100);
QByteArray packData;
packData.clear();
QDataStream in(&packData,QIODevice::ReadWrite);
in.setByteOrder(QDataStream::BigEndian);
in.setVersion(QDataStream::Qt_5_19);
in<<fileName;
qDebug()<<"print : "<<packData.length();
以上的代码输出结果是:print: 113
。 上面两段代码展示了QDataStream在写字符数组的情况。我相信,对于这个输出结果,你或多或少都会有一些疑问。
要弄明白这些问题,我们首先要清楚sizeof
、size
和length
的区别:
QString s="abcdefg";
qDebug()<<"sizeof:"<<sizeof(s); // 4
qDebug()<<"length:"<<s.length(); // 7
qDebug()<<"size:"<<s.size(); // 7
sizeof
不是函数,length
和size
都是函数。sizeof
返回的是对象所占内存空间的大小 ,例如32位机器上,int
占4字节,double
占用8字节。对于QString,其源码中只有一个非静态成员变量Data *d;
,其它变量全部是static
类型的,不占空间,所以sizeof(s)
的大小是4。QString源码中关于size
和length
的部分如下所示:
Data *d;
inline int QString::length() const { return d->size; }
inline int size() const { return d->size; }
可以发现,size
与length
两者是一样的,都是返回字符串的长度。
Qt中几个数据类型与C++类型以及sizeof
的对应关系如下表所示:
我们在使用QDataStream类型时,一定要注意每种类型所占用的字节大小。在操作通用内置类型时,代码可以这样写:
QByteArray ba;
QDataStream ds(&ba,QIODevice::WriteOnly);
ds<<quint8(1)<<quint16(2)<<quint32(3); //1+2+4
qDebug()<<"size:"<<ba.size(); // 7
但是在操作QString类型数据时,会出现问题,这也是本文着重要讲解的地方。如下面代码:
QByteArray ba;
QDataStream ds(&ba,QIODevice::WriteOnly);
QString s="e";
ds<<s;
qDebug()<<"size:"<<ba.size(); // 6
为什么结果是6呢?我们再来看源码。从qstring.cpp
中找到以下源码如下:
QDataStream &operator<< (QDataStream &out, const QString &str)
{
if (out.version() == 1)
{
out << str.toLatin1();
}
else {
if (!str.isNull() || out.version() < 3)
{
if ((out.byteOrder() == QDataStream::BigEndian) == (QSysInfo::ByteOrder == QSysInfo::BigEndian))
{
out.writeBytes(reinterpret_cast<const char *>(str.unicode()), sizeof(QChar) * str.length());
}
...
我们可以看到,QDataStream对不同类型的处理是不同的,这里对QString处理是先判断版本,即我们通常使用时的版本设置,然后去执行writeBytes
函数,这个函数的文档解释如下:
QDataStream &QDataStream::writeBytes(const char *s, uint len)
Writes the length specifier len and the buffer s to the stream and returns a reference to the stream.
The len is serialized as a quint32, followed by len bytes from s. Note that the data is not encoded.
也就是说,在写入QString时,还要将文本长度以quint32
格式写入,即4个字节,而sizeof(QChar)
的字节数为2,所以存入QDataStream中的字节数为:4+2*QString::length()
。
那么看一下这个程序的输出结果是多少呢?
QByteArray bytes;
QDataStream send(&bytes, QIODevice::WriteOnly);
send.setVersion(QDataStream::Qt_5_9);
QString str1("hello");
QString str2("你好");
int aa=20;
int bb=30;
send<<str1<<str2<<aa<<bb;
qDebug()<<"test:"<<bytes.length();
结果应该为:test: 30
。14+8+4+4=30。
2.QDataStream的其它操作方法
在不使用<<
操作符时,我们可以写入原生数据,直接调用writeRawData()
方法:
in.writeRawData(fileName,100);
qDebug()<<"print : "<<packData.length();
上面代码输出为print: 100
。它没有在前面补充4字节的数据长度。因此在读取时也必须用readRawData()
方法。
也可以用writeBytes()
方法,如下代码:
in.writeBytes(fileName,100);
qDebug()<<"print : "<<packData.length();
上面代码输出为print: 104
。说明它在前面补了4 字节的长度,在读取的时候必须用writeBytes()
方法。
总之,QDataStream类非常好用,但在使用的过程中一定要注意数据类型序列化后的字节数。
对于Qt中各种类的使用,我们可以查看帮助文档,能力强的可以看框架的源码,对于初学者来说,可以买一本参考书,以书中的实例为引子,逐步加深学习。我当初用的是这两本教材,给大家参考,书中内容安排的很棒。
进阶参考书:
本文到此结束!觉得还行,顺手点个赞吧。。。
=======================================================
欢迎【关注作者、私信作者】。我们一起交流一起进步。
=======================================================