ПА>Ламерский вопрос
Почему ламерский? Нормальный вопрос, который обычно встречается большинству программеров.
ПА>Есть у меня некая структура (struct).
ПА>Требуется создать файл, куда писать данные в виде этих структур. А также читать. А также перемещаться по файлу.
ПА>Как это сделать? (Плиз с ма-аленьким примером или в какую сторону копать).
Всё зависит от многих вещей, как всегда
Для начала давай объявим твою структуру:
struct mystruct {
int i;
char buf[20];
double d;
};
Теперь, допусти, нам нужно положить её в файл средствами C/C++.
Семейсво функций FILE рассматривать не будет в связи с её архаичносью, и начнём сразу с варианта, который не входит в стандарт, но присутствует во многих компиляторах.
#include <sys/stat.h>
#include <memory.h>
#include <fcntl.h>
#include <stdio.h>
#include <io.h>
struct mystruct {
int i;
char buf[5];
double d;
};
int main(int argc, char* argv[])
{
// открываем файл
int fh = _open("file.dat",_O_RDWR | _O_BINARY);
if (fh == -1)
{
// или при необходимости создаём новый
fh = _creat("file.dat",_S_IREAD | _S_IWRITE);
if (fh == -1)
// не шмагла :xz:
return 1;
}
// готовим структуру для записи
mystruct ms;
memset(&ms,0,sizeof ms);
ms.i = 1;
ms.d = 2;
// позиционируемся в конец файла
_lseek(fh,0,SEEK_END);
// добавляем новую структуру
_write(fh,&ms,sizeof ms);
// позиционируемся в начало
_lseek(fh,0,SEEK_END);
// читаем первую записанную структуру
_read(fh,&ms,sizeof ms);
return 0;
}
Эта программа открывает файл (либо создаёт его при необходимости) и добавляет в него новую структуру, затем читает первый экземпляр.
Всё казалось бы нормально, но если ты посмотришь размер созддаваемого файла, то он всегда будет кратен 24 байтам (вариант Visual C++), хотя размер структуры равен 4+5+8=17 байт. Это происходит потому, что компиляторы по умолчанию выравнивают размер структур в целях оптимизации. Следовательно, наша первая задача отменить это поведение по умолчанию. Стандартных средств сделать это нет, но как правило компиляторы содержат специальную опцию коммандной строки и/или прагму, позволяющую это делать.
Ещё одной неверной деталью в нашем примере является использование типа переменной int. Для разных версий операционных систем размер инта может быть разным и лучше явно указать размер используемого типа — short или long.
Изменим описание структуры:
#pragma pack(push,1)
struct mystruct {
long i;
char buf[5];
double d;
};
#pragma pack(pop)
Теперь запись в файл даст вполне ожидаемый результат.
Здесь можно отметить ещё одну деталь. В качестве строки я использовал массив char[5]. Использование классов типа CString std::string не приведёт ни к чему хорошему. Фактически ты сохранишь не саму строку, а содержимое класса, который её реализует. Допустим, класс CMyString реализован следующим образом:
class CMyString {
public:
int len;
char *str;
// ....
};
Объявление такой структуры как
struct mystruct {
long i;
CMyString str;
double d;
};
будет фактически соответствовать следующему варианту:
struct mystruct {
long i;
int str_len;
char *str_str;
double d;
};
Т.е. в месте, где ты ожидаешь строку будет указатель на буфер в памяти, который (в смысле не буфер, а указатель на него) ты благополучно и сохранишь в файле.
Теперь рассмотрим вариант с потоками. Вообще-то, лучше конечно использовать новую версию <fstream>, но у меня она до сих не вызывает никакого доверия. По-этому, воспользуемся старым вариантом:
#include <memory.h>
#include <fstream.h>
#pragma pack(push,1)
struct mystruct {
long i;
char buf[5];
double d;
};
#pragma pack(pop)
int main(int argc, char* argv[])
{
// создаём или открываем файл
fstream f("file.dat",ios::binary|ios::in|ios::out);
// готовим структуру для записи
mystruct ms;
memset(&ms,0,sizeof ms);
ms.i = 1;
ms.d = 2;
// позиционируемся в конец файла
f.seekp(0,ios::end);
// добавляем новую структуру
f.write((unsigned char*)&ms,sizeof ms);
// позиционируемся в начало
f.seekp(0,ios::beg);
// читаем первую записанную структуру
f.read((unsigned char*)&ms,sizeof ms);
return 0;
}
Ниже вариант использования Windows API вместо фуекций CRTL:
#include <windows.h>
#pragma pack(push,1)
struct mystruct {
long i;
char buf[5];
double d;
};
#pragma pack(pop)
int main(int argc, char* argv[])
{
// создаём или открываем файл
HANDLE fh = ::CreateFile(
TEXT("file.dat"),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
// готовим структуру для записи
mystruct ms;
memset(&ms,0,sizeof ms);
ms.i = 1;
ms.d = 2;
// позиционируемся в конец файла
::SetFilePointer(fh,0,0,FILE_END);
// добавляем новую структуру
DWORD dw=0;
::WriteFile(fh,&ms,sizeof ms,&dw,NULL);
// позиционируемся в начало
::SetFilePointer(fh,0,0,FILE_BEGIN);
// читаем первую записанную структуру
::ReadFile(fh,&ms,sizeof ms,&dw,NULL);
return 0;
}
И т.д.
Еще можно привести вариант исользования класса CFile из MFC, но, в принципе, он будет не очень сильно отличаться.
Может я немного увлёкся, но главное чтобы было понятно
ЗЫ. Далше тебя ждут другие вопросы:
Как узнать количество записанных структу в файле?
Правильный ответ — не вычислять это по размеру файла, а добавить в начало заголовок (специальную структуру), содержащую необходимую служебную информацию: фактический размер файла, версию формата, число записей, смещение к первому блоку и т.п.
Как добавлять записи переменной длины?
Можно к каждой записи добавить свой заголовок, описывающий её структуру.
Как удалять ненужные записи из файла?
Можно просто помечать их как удалённые, а в последствии организовать упаковку файла. Можно организовать список удалённых страниц и использовать их в дальнейшем вместо добавления новых в конец.
Как обеспечить совместный доступ к файлу из нескольких программ.
Блокировки, отдельный сервер доступа и ещё куча всяких вариантов.
Как сделать динамическую структуру записей в файле...
У-у-у...
После всего этого возникает вполне законный вопрос — а может лучше сразу взять стандартную базу данных?
Шутю я, шутю.