Как записать структуру в бинарный файл c
Как структуру, у которой 5 полей типа char [20] записать в бинарный файл. Точнее записал, но вот не считывается.
Структура объявлена так:
struct kniga
<
char nazvanie[20];
char avtor[20];
char zhanr[20];
char izdatelstvo[20];
char god_izdaniya[20];
>;
struct kniga *ptr;
Вот так записал:
FILE *db;
db=fopen("DataBase.xxx","wb");
fwrite((char*)&(*ptr),sizeof(*ptr),1,db);
fclose(db);
Вот так считываю. То что считал хочу записать в ячейки StringGrid-a:
db=fopen("DataBase.xxx","rb");
fread((char*)&ptr,sizeof(ptr),1,db);
while (fgetc(db)!=EOF)
<
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
>
fclose(db);
Что я не так делаю? Либо записываю, либо читаю, либо и то и то
Pasha
BattleMage
Pasha, написал как ты сказал. Скомпилировалось нормально, запустилось. Ввел некоторые данные - он записал их в бинарный файл. Потом ещё раз запустить решил. Думал, что покажет в ячейках StringGrid-a то что я прошлый раз вводил. Не тут то было. Вылетела ошибка: "EAccessViolation."
Что это значит?
Мне кажется я не так считываю.
db=fopen("DataBase.xxx","rb");
rewind(db);
fread((char*)ptr,sizeof(kniga),1,db);
while (fgetc(db)!=EOF)
ptr=(struct kniga*)malloc(sizeof(struct kniga));
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
free(ptr);
>
fclose(db);
Pasha
Для: BattleMageНе проверял (нет у меня установленных плюсов), но примерно так:
Вот ты в этом коде сначала делаешь fread, а потом в цикле уже делаешь malloc. Делать надо наоборот. Сначала выделить память, а потом в неё уже производить считывание.
И ещё. Выделяешь память странно как-то. Нужно так: ptr = (kniga *) malloc(sizeof(kniga))
BattleMage
1) БОЛЬШОЕ спасибо за советы. Сейчас попробую сделать.
2) Over, а что есть разница между строчками:
ptr = (kniga *) malloc(sizeof(kniga))
и
ptr=(struct kniga*)malloc(sizeof(struct kniga));
BattleMage
Так. Почти все как надо. Только вот один недочет:
добавил очередную запись. закрыл программу. запускаю, а запись которая была добавлена в последний раз два раза встречается в StringGrid - e (на последней и предпоследней строчке). Вот только не понятно: он просто не так как-то в StringGrid выводит это или в текстовом файле такая "каша".
И ещё. Если файл пуст. То StringGrid состоит из строки, где в поле номер записано "1", а в поле название "$u"
сейчас у меня такой код:
void __fastcall TForm1::Button1Click(TObject *Sender)
if ((Edit1->Text=="")||(Edit2->Text=="")||(Edit3->Text=="")||(Edit4->Text=="")||(Edit5->Text==""))
ShowMessage("Нужно заполнить все поля!");
else
ptr=(struct kniga*)malloc(sizeof(struct kniga));
strcpy(ptr->nazvanie,Edit1->Text.c_str());
strcpy(ptr->avtor,Edit2->Text.c_str());
strcpy(ptr->zhanr,Edit3->Text.c_str());
strcpy(ptr->izdatelstvo,Edit4->Text.c_str());
strcpy(ptr->god_izdaniya,Edit5->Text.c_str());
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
db=fopen("DataBase.xxx","a+b");
fwrite(ptr,sizeof(kniga),1,db);
fclose(db);
Edit1->Text="";
Edit2->Text="";
Edit3->Text="";
Edit4->Text="";
Edit5->Text="";
free(ptr);
>
>
Дальше. Когда выделил память, её нужно обнулить. Тогда в ней не будет сожержаться мусор. И потом ещё в цикле проверять значение, возвращаемое функцией fread() (не помню, вроде возвращает количество считанных байт). Если файл оказался пуст, то данные не считаются, и тогда в StringGrid не нужно ничего добавлять.
Over, а что есть разница между строчками:ptr = (kniga *) malloc(sizeof(kniga)) и ptr=(struct kniga*)malloc(sizeof(struct kniga));
приколист Разницы может и нет, но что-то я до сих пор не встречал, чтоб писали как ты: (struct kniga*). Это просто банально лишний код.
European
Для: BattleMageВы уж простите, что я в Тулу со своим самоваром. Но зачем плодить кашу из сишного и плюс-плюсного кода? Неуженли нельзя использовать new и delete вместо malloc и free, неужели нет нормальных функций для работы с файлами?
Pasha
Для: EuropeanНормальные функции . ты еще файлы отображаемые в память предложи использовать - ведь это красивее, чем тупо читать по одной структуре в буффер Пусть человек сначала с основами разберется, а стремление к порядку и красоте кода ему привьют на первой же серьезной работе.
European
<!--QuoteBegin-Pasha+19:07:2007, 11:53 --><span @ 19:07:2007, 11:53 )</span><!--QuoteEBegin-->ты еще файлы отображаемые в память предложи использовать - ведь это красивее, чем тупо читать по одной структуре в буффер
[/quote]
А вот палку перегибать не надо!
<!--QuoteBegin-Pasha+19:07:2007, 11:53 -->
<span @ 19:07:2007, 11:53 )</span><!--QuoteEBegin-->Пусть человек сначала с основами разберется
[/quote]
Ну так все в Turbo С и писать приложения под консоль.
Городить огород типа:
это изучение основ? Учится можно сразу, а не ждать нормальной работы, на которую из-за такого кода в тестовом задании могут и не взять в случае тестового задания, имхо, скорее бы обратили бы внимание не на malloc, а на то, что использование динамического выделенения памяти в данном случае является абсолютно излишним.
Pasha
Для: KmetА еще скорее всего обратили бы внимание на использование текстового файла вместо нормальной базы данных, слишком жесткого ограничения на строки (название книги до 20 символов), код копирования строк без проверки длины (strcpy(ptr->avtor,Edit2->Text.c_str()), имена контролов (Edit1,2,3,4).
С другой стороны, всегда есть вероятность, что это не тестовое задание, а задача по "программированию" с условием "чтение и запись данных в бинарный (двоичный) файл с использованием динамического выделения памяти.". Для European:
Функция new, если посмотришь исходники, вызывает напрямую функцию malloc. delete в общем-то то же самое. Поэтому с программной точки зрения разницы нет. Если уже только гнаться за красивостью кода.
European
Для: OverИногда лучше жевать, чем говорить.
1 - new это не функция, а предопределенный оператор языка C++. Надеюсь разница понятна!
2 - new и delete могут использовать malloc и free, но нет никакой гарантии этого.
3 (и самое главное) - malloc и free ничего не знают о конструкторах и деструкторах классов соответсвенно. malloc выделяет неициализированную память. Интересно, как бы ты передал значение конструктору используя malloc. Далее, как ты выражаешься: "delete в общем-то то же самое"
4 - использование new и delete вперемешку с malloc и free приводит к непредсказуемым последствиям. Т.е. используя оба способа выделения памяти необходимо помнить о том, как была выделена память
5 - избегать использования malloc и free рекомендуют практически все нормальные авторы нормальных книг. Надеюсь фамилии Страуструпа, Майерса и Саттера тебе что-то говорят и ты прочитал хотя бы парочку их трудов. Если нет, то продолжение спора бесполезно
Для: Pasha
судить о целесообразности использования БД в данном мы не можем, так как толком задания не знаем. ограничение длины строки, можно тоже списать на ТЗ. проверка длины строки может производится на уровне компонента, что конечно же коряво, но все же. именна контролов можно списать на не соответстивие code convention, что конечно же плохо, но не так страшно. malloc\free в данном конкретном случае вполне допустимо. И дело не в том, что я не уважаю Страутсрапа и КО, просто если не обращать внимание на VCL, то код вполне соотвествует С-style.
С другой стороны, понимание критериев использования динамической памяти и дисциплина при работе с ней критична для любого С/C++ программиста. и не важно тут, это тестовое задание или просто задача.
BattleMage
БлогNot. Структура со строками string и файловые чтение/запись массива таких структур
Структура со строками string и файловые чтение/запись массива таких структур
В отличие от этого примера, используем в структурном типе данных более удобные в обращении и современные строки string и файловые потоки вместо классических си-строк char * и файлов из <cstdio> . Лекции по всем этим темам можно найти в оглавлении.
В итоге должна получиться программка, которая покажет непосредственное задание значений полям структурной переменной и ввод значений полей с консоли, а также запись файла структур и последующее его контрольное чтение. Проверяться она будет в консоли Visual Studio 2015, проект создан как вот здесь.
В начале файла укажем нужные библиотеки и директивы, в комментариях написано, для чего какая служит:
Опишем структурный тип данных, включающий в себя идентификатор (номер) записи, две строки для хранения имени и даты рождения, а также вещественное поле money для хранения, например, зарплаты:
Мы не указываем какие-либо ограничения на длину строки, потому что сами будем управлять этим.
Так как программа может завершиться тремя способами - нормально, при ошибке записи файла и при ошибке его чтения, напишем функцию error с аргументом n (номер ошибки), которая будет за это отвечать. Ошибка номер ноль, как принято в C++, будет означать нормальное завершение. Вообще такую "общую точку выхода" зачастую полезно делать в процедурно-ориентированном коде:
Функция для ввода данных с консоли input получает аргументами адрес структуры a , куда нужно заносить данные (это может быть, в том числе, и адрес элемента массива структур) и идентификатор записи i , остальные поля записи она запрашивает у пользователя.
В реальности стоило бы снабдить код большим количеством проверок корректности данных и вводом их в некую буферную запись, откуда потом скорректированные данные могут быть скопированы в массив или в файл.
Также обратите внимание, что мы не должны "резать" поля структуры кодом вроде
но с отдельной буферной строкой имели бы на это право, конечно же, прежде, чем копировать её в запись, предназначенную для постоянного хранения.
Со всеми этими оговорками, простейшая функция ввода записи получилась такой:
Функция output , соответственно, выводит в консоль поля записи a , переданной аргументом:
При корректных данных должны получиться столбцы правильной ширины.
Главной программе осталось русифицировать консоль и создать массив записей, для простоты предусмотрим там всего 2 элемента:
Первый элемент с id , равным нулю, зададим программно, а второй введём с консоли:
Откроем файл notes.dat для записи и поместим туда данные, заодно печатая их в консоль:
Обратите внимание, что применение sizeof к составному объекту string некорректно, приходится писать в бинарный файл отдельными полями.
После закрытия файла, откроем его вновь для чтения и покажем прочитанные в цикле записи на экране, после чего можно сделать нормальный выход из программы:
Вы понимаете, что наш файл - двоичный, и открывать его текстовым редактором бессмысленно.
Вот лог работы нашей программы:
скриншот 16-ричного вида файла (см. комменты)
- писать и читать только string.c_str() в бинарные файлы, сведя задачу к предыдущей ссылке (но тогда не нужны и string , а можно делать классически на char * );
- всё же отказаться от бинарных, а ограничиться текстовыми файлами и парсить полученные через getline строки;
- предусмотреть в формате файла одновременное сохранение длины строковых данных объекта string .
В последнем случае имеем примерно такой подход (проверен не на структурах, а на паре строк).
Программа для записи набора string в бинарный файл:
Потом читаем это (возможно, с точностью до кодировки):
В моей консоли Visual Studio 2015 обе программки сработали. Итак, если строка string является полем структуры, разницы в подходах нет.
P.P.S. Ну и, раз пошла такая пьянка, теперь разрешим строкам string в массиве структур иметь произвольную длину и содержать пробелы (по умолчанию чтение string из файлового потока прервётся на первом пробельном символе), а сохранять всё будем в текстовом файле, и читать из него же.
Чтобы всё работало, нам пришлось переписать оператор >> для своей структуры.
Программа создаст и затем прочитает такой файл:
При чтении код сам избавится от лишних пробелов в строках между лексемами, следующими после числа.
Перед изучением данной темы рекомендуется ознакомиться со следующими темами:
Содержание
- 1. Класс BinaryWriter . Назначение
- 2. Взаимодействие класса BinaryWriter с потоками опорных хранилищ
- 3. Конструкторы класса BinaryWriter . Создание экземпляра
- 4. Основные методы класса BinaryWriter
- 4.1. Метод Write() — много перегруженных реализаций
- 4.1.1. Запись одиночных значений. Перегруженный метод Write() . Пример
- 4.1.2. Запись байтовых массивов byte[] в поток. Пример
- 4.1.3. Запись символьных массивов char[] в поток. Пример
Поиск на других ресурсах:
1. Класс BinaryWriter . Назначение
Класс BinaryWriter предназначен для записи данных в двоичном (бинарном) формате. Запись данных может осуществляться в файлы, сеть, изолированное хранилище, память и т.п. Во время записи строк существует возможность указания нужной кодировки. По умолчанию установлена кодировка UTF-8.
Класс реализован в пространстве имен System.IO . Для того, чтобы использовать возможности этого класса нужно добавить строкуКласс BinaryWriter записывает данные, которые представлены:
- примитивными типами: int , float , double и другими;
- строками типа string в указанной кодировке.
2. Взаимодействие класса BinaryWriter с потоками опорных хранилищ
Класс BinaryWriter (а также BinaryReader ) относится к адаптерам потоков. Это означает следующее. Чтобы получить доступ к файлу, сети или памяти, нужно использовать промежуточный класс опорного хранилища ( FileStream , MemoryStream , NetworkStream и т.д.).
На рисунке отображено взаимодействие класса BinaryWriter с файловым хранилищем, которому соответствует класс FileStream .Рисунок. Взаимодействие класса BinaryWriter с файлом через класс FileStream
3. Конструкторы класса BinaryWriter . Создание экземпляра
Класс BinaryWriter имеет несколько конструкторов, наиболее распространенными из которых следующие:
- output – ссылка на абстрактный класс Stream , являющийся вершиной иерархии классов ввода-вывода;
- encoding – система кодировки (Unicode, UTF32, UTF8 или другая). По умолчанию установлена система кодировки UTF8.
Как видно из примера, при создании потоков используется синтаксис с использованием ключевого слова using() . Это освобождает от необходимости закрывать потоки методом Close() , поскольку при таком подходе очистка ресурсов происходит автоматически.
4. Основные методы класса BinaryWriter
Метод имеет 20 перегруженных реализаций, основные из которых приведены ниже.
4.1.1. Запись одиночных значений. Перегруженный метод Write() . Пример
Чтобы записать одиночные значения используются методы Write() , которые имеют следующую общую форму
здесь value – значение одного из стандартных (примитивных) типовв, которое нужно записать в поток.
Пример.
Результат выполнения программы
4.1.2. Запись байтовых массивов byte[] в поток. Пример
Запись байт массивов типа byte[] полезен, когда в поток нужно записывать массивы стандартных типов (например, массивы int[] , double[] , decimal[] и т.д.). Для конвертирования из стандартных типов в тип byte[] удобно использовать возможности класса BitConverter .
- buffer – участок памяти, из которого будут записываться данные в поток побайтно;
- index – позиция (индекс) начала в массиве buffer , с которой будет происходить запись в поток;
- count – количество байт, которые нужно записать в поток.
Результат выполнения программы
4.1.3. Запись символьных массивов char[] в поток. Пример
Для записи строк символов в виде массива char[] используются две следующие реализации метода Write() :
Пример. В примере записывается строка типа string в файловый поток в виде символьного массива char[] .
Результат выполнения программы
4.2. Метод Seek() . Пример
Метод Seek() устанавливает позицию в текущем потоке. Общая форма метода следующая:
Значение origin есть перечислением и имеет тип
Значения из перечисления могут быть следующими:
Пример. В примере продемонстрированы следующие операции:
- запись чисел в файл;
- перезапись ранее записанных чисел;
- дописывание числа в конец файла.
Результат выполнения программы
4.3. Метод Flush() . Пример
Метод Flush() используется для очистки всех буферов текущего модуля записи. После этого можно записывать любые буферизированные данные на устройство, в которое на данный момент разрешено производить запись.
При записи информации в двоичный файл символы и числа записываются в виде последовательности байт.
Для того чтобы записать данные в двоичный файл, необходимо:
- описать файловую переменную типа FAIL * с помощью оператора FILE *filename, здесь filename — имя переменной, где будет храниться указатель на файл.
- открыть файл с помощью функции fopen
- записать информацию в файл с помощью функции fwrite
- закрыть файл с помощью функции fclose
Для того чтобы считать данные из двоичного файла, необходимо:
- описать переменную типа FILE *
- открыть файл с помощью функции fopen
- считать необходимую информацию из файла с помощью функции fread, при этом следить за тем достигнут ли конец файла.
- закрыть файл с помощью функции fclose
Рассмотрим основные функции, необходимые для работы с двоичными файлами.
Для открытия файла предназначена функция fopen.
FILE *fopen(const *filename, const char *mode)
Здесь filename — строка, в которой хранится полное имя открываемого файла, mode — строка, определяющая режим работы с файлом; возможны следующие значения:
- «rb» — открываем двоичный файл в режиме чтения;
- «wb» — создаем двоичный файл для записи; если он существует, то его содержимое очищается;
- «ab» — создаем или открываем двоичный файл для дозаписи в конец файла;
- «rb+» — открываем существующий двоичный файл в режиме чтения и записи;
- «wb+» — открываем двоичный файл в режиме чтения и записи, существующий файл очищается;
- «ab+» — двоичный файл открывается или создается для исправления существующий информации и добавления новой в конец файла.
Функция возвращает в файловой переменной f значение NULL в случае неудачного открытия файла. После открытия файла доступен 0-й его байт, указатель файла равен 0, значение которого по мере чтения или записи смещается на считанное (записанное) количество байт. Текущие значение указателя файла — номер байта, начиная с которого будет происходить операция чтения или записи.
Для закрытия файла предназначена функция fclose:
int fclose(FILE *filename);
Она возвращает 0 при успешном закрытие файла и EOF в противном случае.
Функция remove предназначена для удаления файлов:
int remove(const char *filename);
Эта функция удаляет с диска файл с именем filenema. Удаляемый файл должен быть закрыт. Функция возвращает ненулевое значение, если файл не удалось удалить.
Для переименования файлов предназначена функция rename:
int rename(const char *oldfilename, const char *newfilename);
Первый параметр — старое имя файла, второй — новое. Возвращает 0 при удачном завершении программы.
Чтение из двоичного файла осуществляется с помощью функции fread:
fread(void *ptr, size, n, FILE *filename);
Функция fread считывает из файла filename в массив ptr n элементов размера size. Функция возвращает количество считанных элементов. После чтения из файла его указатель смещается на n*size байт.
Запись в двоичный файл осуществляется с помощью функции fwrite:
fwrite(const void *ptr, size, n, FILE *filename);
Функция fwrite записывает в файл filename из массива ptr n элементов размера size. Функция возвращает количество записанных элементов. После записи информации в файл указатель смещается на n*size байт.
Для контроля достижения конца файла есть функция feof:
int feof(FILE *filename);
Она возвращает ненулевое значение если достигнут конец файла.
Для более точного усвоения материала предлагаю рассмотреть пару стандартных задач.
Задача 1
Создать двоичный файл D:\\game\\noobs.dat и записать в него целое число n и n вещественных чисел.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28Задача 2
Вывести на экран содержимого созданного в прошлой задаче двоичного файла D:\\game\\noobs.dat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28Двоичный файл — последовательная структура данных, после открытия файла доступен первый байт, хранящийся в нем. Можно последовательно записывать или считывать данные из файла. Допустим, необходимо считать пятнадцатое число, а затем первое. С помощью последовательного доступа это можно сделать следующим способом:
Как видно, такое чтение чисел из файла, а затем повторное открытие файла — не самый удобный способ. Гораздо удобнее будет использовать функцию fseek перемещения указателя файла к заданному байту.
int fseek(FILE *filename, long int offset, int origin);
Функция устанавливает указатель текущий позиции файла F в соответствии со значением начала отсчета origin и смещения offset. Параметр offset равен количеству байтов, на которые будет смещен указатель файла относительно начала отсчета, заданного параметром origin. В качестве значения для параметра origin должно быть взято одно из следующих значений отсчета смещения offset, определенных в заголовке stdio.h:
- SEEK_SET — с начала файла;
- SEEK_CUR — с текущей позиции;
- SEEK_END — с конца файла.
Функция возвращает нулевое значение при успешном выполнение операции, ненулевое — при возникновении сбоя при выполнении смещения
Функция fseek фактически реализует прямой доступ к любому значению в файле. Необходимо только знать месторасположение (номер байта) значения в файле. Рассмотрим использование прямого доступа в двоичных файлах на примере решения следующей задачи.
Задача 3
В созданном раннее двоичном файле D:\\game\\noobs.dat, поменять местами наибольшее и наименьшее из вещественных чисел.
Алгоритм решения задачи состоит из следующих этапов:
- чтение вещественных из файла в массив a.
- поиск в массиве а максимального (max) и минимального (min) значения и их номеров (imax, imin).
- перемещения указателя файла к максимальному значению и запись min.
- перемещения указателя файла к минимальному значению и запись max.
Ниже приведен текст программы решения задачи с комментариями.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50Итак, мы рассмотрели основные принципы работы с файлами в C++. В следующих уроках они вам еще встретятся, поэтому усвойте их как можно лучше.
Читайте также:
- 4.1. Метод Write() — много перегруженных реализаций