Учитывая, что все остальные заметили очевидные ошибки, давайте отметим, когда (и только когда) можно делать то, что вы пытаетесь сделать.
Формат структуры заголовка в памяти зависит от платформы и компилятора. Таким образом, вполне нормально хранить заголовок так, как вы это делаете, только если это временные данные, которые сохраняются не дольше, чем время выполнения приложения. Если заголовок находится во временном файле, который вы удаляете перед выходом, все в порядке.
Если, с другой стороны, вы пытаетесь научить этому способу хранения двоичных данных на постоянной основе — чтобы он сохранялся после выхода из приложения, вы попали своим ученикам в ногу. С базукой, не иначе. Вам вовсе не гарантируется, что следующая версия вашего компилятора сгенерирует код с таким же расположением в памяти полей структуры. Или что это сделает какой-то другой компилятор.
Педагогическая записка
Есть несколько педагогических аспектов, на которые стоит обратить внимание: сложность написания переносимого и поддерживаемого формата файла и идиоматическое использование языка программирования C++. Хороший подход будет использовать внутреннюю синергию между ними.
В большинстве кодов, которые я встречал на публичных форумах, строковые буферы фиксированной длины — это лекарство от переполнения буфера и небезопасного кода. С педагогической точки зрения это пагубная привычка учить кого-либо. Буферы фиксированного размера автоматически создают дополнительные проблемы:
Раздувание файла из-за хранения заполнения.
Невозможность хранения произвольно длинных строк и, следовательно, вынужденная потеря данных.
Необходимо указать и протестировать правильное поведение, когда слишком длинные строки должны быть втиснуты в короткие буферы. Это также приводит к ошибкам, возникающим один за другим.
Поскольку вы преподаете на C++, было бы неплохо писать код так, как другие специалисты пишут на C++. Просто потому, что вы можете написать это так, как если бы это был C, и при этом мозговой C, вовсе не означает, что это хорошая идея. C++, как и любой другой язык, имеет идиомы — способы делать вещи, которые приводят как к достойному коду, так и к достойному пониманию и сопровождению другими.
Для этого следует использовать QDataStream
. Он реализует собственный, переносимый внутри Qt формат сериализации. Если вам нужно прочитать этот формат из кода, который не использует Qt, обратитесь к документации — двоичный формат задокументирован и стабилен. Для простых типов данных это делается так же, как это сделал бы прилично написанный код 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;
}
person
Kuba hasn't forgotten Monica
schedule
19.01.2014
operator<<
и использоватьQDataStream
для записи в файл. Почитайте о сериализации данных. - person prajmus   schedule 18.01.2014QByteArray
илиchar *
- person Tob   schedule 19.01.2014FILEPOINTER
является указателем) совершенно бессмысленно. Вы должны быть осторожны, чтобы игнорировать сохраненное значение, когда вы читаете его обратно. - person hyde   schedule 19.01.2014bool serializeFileHeader(QIODevice &out, const fileheader &headerOut)
иbool deserializeFileHeader(QIODevice &in, fileheader &headerIn);
. Первые версии могут просто сбрасывать и считывать содержимое необработанной памяти, уже показанное в ответе (и обратите внимание на мой комментарий об указателе выше), который затем может быть изысканный. - person hyde   schedule 19.01.2014