QFile.write( myStruct ) - how?

QFile.write( myStruct ) - how?

本文关键字:how myStruct write QFile      更新时间:2023-10-16

我从Qt开始,很长一段时间以来一直被一个问题所困扰。我确信这只是我在C++中看不到的东西。无论如何,请看下面的简单代码,并指出我做错了什么:

typedef struct FILEHEADER {
char udfSignature[8];
char fileName[64];
long fileVersion;
UNIXTIME fileCreation;
UNIXTIME lastRebuild;
FILEPOINTER descriptor;
} fileheader;
QFile f("nanga.dat");
if(f.open(QIODevice::ReadWrite));
f.write(fileheader);

问题5.2.0显示以下错误消息:

C:swudbudbmain.h:113: error: no matching function for call to
'QFile::write(FILEHEADER&)'
file.write(header);
^

关于如何将此结构写入QFile,有什么建议吗?

感谢

考虑到其他人都注意到了明显的错误,让我们注意一下什么时候(只有当时)可以做你想做的事情。

头结构的内存格式依赖于平台和编译器。因此,只有当它是持续时间不超过应用程序运行时的临时数据时,才可以以的方式存储头。如果标头在退出前删除的临时文件中,则表示可以。

另一方面,如果你试图";教导";这种永久存储二进制数据的方式-在应用程序退出后持续,你已经射中了你的学生的脚。还有火箭筒。您根本不能保证编译器的下一个版本会生成与结构字段在内存中的排列相同的代码。或者其他编译器也会这样做

教学笔记

有几个教学方面值得解决:编写可移植和可维护的文件格式的复杂性,以及编程语言C++的惯用用法。一个好的方法将利用两者之间固有的协同作用。

在我在公共论坛上看到的大多数代码中,固定长度的字符串缓冲区是缓冲溢出和不安全代码的网关药物。从教育学角度讲,教任何人都是一种灾难性的习惯。固定大小的缓冲区自动产生额外问题:

  1. 由于填充的存储导致文件膨胀。

  2. 不可能存储任意长的字符串,从而导致数据丢失。

  3. 必须指定并测试";正确的";太长的字符串必须硬塞进短缓冲区时的行为。这也会导致一个错误。

因为你是用C++教学的,所以像其他技术人员用C++编写代码一样编写代码是个好主意。仅仅因为你可以把它写得像C一样,并在这方面使用C,并不意味着这是一个好主意。C++和其他任何语言一样,也有一些习惯用法,这些习惯用法既能产生良好的代码,又能为他人带来良好的理解和可维护性。

为此,应该使用QDataStream。它实现了自己的、可移植的Qt序列化格式。如果你需要从不使用Qt的代码中读取这种格式,请参阅文档——二进制格式是有文档记录的,并且是稳定的。对于简单的数据类型,它就像编写得很好的C代码一样,只是默认情况下,无论平台的端序是什么,文件都是大端序

自制文件格式由";简单地";将C结构写入磁盘总是会受到影响,因为默认情况下您无法控制数据在内存中的排列方式。由于您只是将结构的内存映像复制到文件中,因此您将失去对文件中数据表示方式的控制。编译器的供应商在控制,而不是你。

QDataStreamQIODevice(在QFile中实现)必然会抽象出一些复杂性,因为它们的目标是在用户不必编写大量样板代码来正确解决可移植性方面的情况下可用。以下是将二进制数据写入文件时经常被忽略的方面:

  1. 数值数据的持久性
  2. 数据类型的大小
  3. 在";连续地";存储的数据
  4. 文件格式的未来可扩展性和版本控制
  5. 使用固定大小的缓冲区时,缓冲区溢出和不可避免的数据丢失

正确处理它需要一些预先考虑。不过,这是一个完美的机会,可以使用调试器跟踪通过QDataStream的代码流,以查看字节被推送到文件缓冲区时实际发生了什么。这也是一个检查QDataStreamAPI可移植性方面的机会。大部分代码的存在是有充分理由的,学生们可以理解为什么要这样做。

最终,学生可以重新实现QDataStream的一些最小子集(只处理少数类型可移植),并且可以比较使用Qt和学生实现编写的文件,以评估他们在任务中的成功程度。类似地,QFile可以通过从QIODevice派生并使用C文件API来重新实现。

以下是如何在Qt中真正做到这一点。

// Header File
struct FileHeader { // ALL CAPS are idiomatically reserved for macros
// The signature is an implementation detail and has no place here at all.
QString fileName;
// The file version is of a very dubious use here. It should only
// be necessary in the process of (de)serialization, so ideally it should
// be relegated to that code and hidden from here.
quint32 fileVersion;
QDataTime fileCreationTime;
QDateTime lastRebiuildTime;
// The descriptor is presumably another structure, it can be
// serialized separately. There's no need to store a file offset for it
// here.
};
QDataStream & operator<<(QDataStream& str, const FileHeader & hdr) {
QDataStream & operator>>(QDataStream& str, FileHeader & hdr) {
// Implementation File
static const quint32 kFileHeaderSignature = 0xC5362A99;
// Can be anything, but I set it to a product of two randomly chosen prime
// numbers that is greater or equal to 2^31. If you have multiple file
// types, that's a reasonable way of going about it.
QDataStream & operator<<(QDataStream& str, const FileHeader & hdr) {
str << kFileHeaderSignature
<< hdr.fileName << hdr.fileVersion
<< hdr.fileCreationTime << hdr.lastRebuildTime;
return str;
}
QDataStream & operator>>(QDataStream& str, FileHeader & hdr) {
quint32 signature;
str >> signature;
if (signature != kFileHeaderSignature) {
str.setStatus(QDataStream::ReadCorruptData);
return;
}
str >> hdr.fileName >> hdr.fileVersion
>> hdr.fileCreationTime >> hdr.lastRebuildTime;
return str;
}
// Point of use
bool read() {
QFile file("myfile");
if (! file.open(QIODevice::ReadOnly) return false;
QDataStream stream(&file);
// !!
// !!
// !!
// Stream version is a vitally important part of your file's binary format,
// you must choose it once and keep it set that way. You can also store it
// in the header, if you wish to go to a later version in the future, with the
// understanding that older versions of your software won't read it anymore.
// !!
// !!
// !!
stream.setVersion(QDataStream::Qt_5_1);
FileHeader header;
stream >> header;
...
if (stream.status != QDataStream::Ok) return false;
// Here we can work with the data
...
return true;
}

QFile具有接受任意字节数组的写入方法。你可以试试这样的东西:

fileheader fh = { ...... };
QFile f("nanga.dat");
if(f.open(QIODevice::ReadWrite))
f.write(reinterpret_cast<char*>(&fh), sizeof(fh));

但请记住,一般来说,以这种方式存储任何数据都不是一个好主意。