我从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);
Qt 5.2.0告诉我以下错误消息:
C:\sw\udb\udb\main.h:113: error: no matching function for call to
'QFile::write(FILEHEADER&)'
file.write(header);
^
关于如何将此结构编写为QFile
?
由于
答案 0 :(得分:5)
鉴于其他人都已经发现了明显的错误,让我们注意何时(以及仅在时),你可以做你想做的事情。
标头结构的内存中格式取决于平台和编译器。因此,以你的方式存储标题是完全正常的只有当它的临时数据持续不超过应用程序的运行时。如果标题位于退出前删除的临时文件中,则表示没问题。
另一方面,如果你尝试“教授”这种永久存储二进制数据的方式 - 在应用程序退出之后持续,你就会用脚射击你的学生。用火箭筒,不能少。您根本不能保证下一版本的编译器将生成具有相同内存排列的结构字段的代码。或者其他一些编译器会这样做。
有几个值得解决的教学方面:编写可移植和可维护文件格式的复杂性,以及编程语言C ++的习惯用法。一种好的方法将利用两者之间固有的协同作用。
在我在公共论坛上看到的大多数代码中,固定长度的字符串缓冲区是缓冲溢出和不安全代码的网关药物。在教学方面,教给任何人都是一种灾难性的习惯。固定大小的缓冲区自动创建额外的问题:
由于存储填充而导致文件膨胀。
无法存储任意长的字符串,从而导致数据被强制丢失。
当太长的字符串必须被插入短缓冲区时,必须指定并测试“正确”行为。这也会引发一个错误。
由于您使用C ++进行教学,因此编写像其他技术人员用C ++编写的代码一样是个好主意。只是因为你可以把它写成像C一样,并且脑海中的C,那并不意味着它根本就是个好主意。与任何其他语言一样,C ++也有成语 - 做事的方式既可以产生合适的代码,也可以为他人提供良好的理解和可维护性。
为此,应该使用QDataStream
。它实现了自己的Qt内部可移植序列化格式。如果您需要从不使用Qt的代码中读取此格式,refer to the documentation - 二进制格式已记录并且稳定。对于简单的数据类型,除了默认情况下file is always big-endian no matter what the platform's endianness is之外,它的编写方式与C代码完全相同。
通过“简单地”将C结构写入磁盘完成的自制文件格式总是受到影响,因为默认情况下您无法控制数据在内存中的排列方式。由于您只是将结构的内存映像复制到文件中,因此您无法控制数据在文件中的表示方式。编译器的供应商处于控制之中,而不是你。
QDataStream
和QIODevice
(在QFile
中实现)必然会抽象出一些复杂性,因为它们旨在无需用户编写大量样板代码即可正常使用便携性方面。以下经常被忽略将二进制数据写入文件的方面:
正确地解决它需要一些深谋远虑。但是,这是一个绝佳的机会,可以使用调试器通过QDataStream
跟踪代码流,看看当字节被推送到文件缓冲区时会发生什么。这也是一个检查QDataStream
API的可移植性方面的机会。大多数代码存在的原因很充分,学生可以理解为什么会这样做。
最终,学生可以重新实现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;
}
答案 1 :(得分:1)
QFile
具有write方法,该方法接受任意字节数组。你可以尝试这样的事情:
fileheader fh = { ...... };
QFile f("nanga.dat");
if(f.open(QIODevice::ReadWrite))
f.write(reinterpret_cast<char*>(&fh), sizeof(fh));
但请记住,一般来说,以这种方式存储任何数据并不是一个好主意。