Как записать структуру в файл c
Обновлено: 21.11.2024
До сих пор мы использовали текстовый режим для чтения и записи данных в файл и из него. В этой главе мы узнаем, как мы можем читать и записывать данные в файл и из него, используя двоичный режим. Напомним, что в двоичном режиме данные хранятся в файле так же, как и в памяти, поэтому в двоичном режиме не происходит никакого преобразования данных. Поскольку преобразование не происходит, двоичный режим работает значительно быстрее, чем текстовый.
Функции fread() и fwrite() обычно используются для чтения и записи двоичных данных в файл и из него соответственно. Хотя мы также можем использовать их и в текстовом режиме.
Начнем с функции fwrite().
Синтаксис: size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);
Функция fwrite() записывает в файл данные, указанные указателем void ptr.
ptr : указывает на блок памяти, содержащий элементы данных для записи.
size : указывает количество байтов каждого записываемого элемента.
n : количество записываемых элементов.
fp : это указатель на файл, в который будут записаны элементы данных.
В случае успеха возвращается количество элементов, успешно записанных в файл. При ошибке возвращается число меньше n . Обратите внимание, что два аргумента ( size и n ) и возвращаемое значение fwrite() имеют тип size_t, который в большинстве систем имеет тип unsigned int.
Чтобы лучше понять функцию fwrite(), рассмотрим следующие примеры:
Пример 1. Запись переменной
Это записывает значение переменной f в файл.
Пример 2. Запись массива
Это записывает весь массив в файл.
Пример 3. Запись некоторых элементов массива
Это записывает в файл только первые два элемента массива.
Пример 4. Написание структуры
Это записывает содержимое переменной student_1 в файл.
Пример 5: Запись массива структуры
Это записывает весь массив студентов в файл.
Допустим, мы не хотим записывать все элементы массива в файл, вместо этого мы хотим записать в файл только 0-й и 1-й элементы массива.
Теперь вы поняли, как работает функция fwrite(). Давайте создадим программу, используя функцию fwrite().
В следующей программе показано, как использовать функцию fwrite().
Ожидаемый результат:
Как это работает:
В строках 4–10 объявляется структура employee, состоящая из четырех членов, а именно: name — массив символов, обозначение — также массив символов, age — тип int, а зарплата — float. Наряду с определением структуры также объявляется переменная emp типа struct employee.
В строке 14 объявлены три переменные n, i и chars типа int.
В строке 15 объявляется указатель структуры fp типа struct FILE.
В строке 17 функция fopen() вызывается с двумя аргументами, а именно "employee.txt" и "wb". В случае успеха он возвращает указатель на файл employee.txt и открывает файл employee.txt в режиме только для записи. В случае ошибки возвращается NULL.
В строках 19–23 оператор if используется для проверки значения fp. Если он равен NULL, оператор printf() выводит сообщение об ошибке и программа завершает работу. В противном случае программа продолжает работу с оператором, следующим за оператором if.
В строках 27–28 программа запрашивает у пользователя, сколько записей он хочет ввести, и сохраняет число в переменной n.
В строках 30–50 операторы цикла for просят пользователя ввести четыре элемента информации, а именно имя, должность, возраст и зарплату. Обратите внимание, что в строке 34 вызывается функция fflush() для сброса (удаления) символа новой строки из стандартного ввода, который был введен при вводе количества записей в строке 28. Если не было вызова fflush(stdin), то gets( ) в строке 37 считывала бы символ новой строки из стандартного ввода и не ждала ввода пользователя. В строке 48 вызывается функция fwrite() для записи структурной переменной emp в файл в двоичном режиме. Мы уже знаем, что в случае успеха fwrite() возвращает количество элементов, записанных в файл. Здесь мы записываем данные одной структурной переменной, поэтому fwrite() вернет 1. В случае ошибки она вернет число меньше 1. Затем возвращаемое значение fwrite() присваивается переменной chars. В строке 49 оператор printf() выводит количество элементов, успешно записанных в файл.
В строке 52 функция fclose() используется для закрытия файла.
сообщить об этом объявлении
сообщить об этом объявлении
В этом руководстве вы узнаете о типах структур в программировании на C с помощью примеров.
В программировании на C структура (или структура) представляет собой набор переменных (могут быть разных типов) под одним именем.
Определить структуры
Прежде чем создавать структурные переменные, необходимо определить их тип данных. Для определения структуры используется ключевое слово struct.
Синтаксис структуры
Здесь определена структура производного типа Person. Теперь вы можете создавать переменные этого типа.
Создать переменные структуры
При объявлении типа структуры хранилище или память не выделяются. Чтобы выделить память данного типа структуры и работать с ней, нам нужно создать переменные.
Вот как мы создаем структурные переменные:
Еще один способ создания структурной переменной:
- person1 и person2 являются структурными переменными Person
- p[] — это массив структур Person размером 20.
Доступ к членам структуры
Существует два типа операторов, используемых для доступа к элементам структуры.
-
<ли>. - Оператор-участник
- -> — оператор указателя структуры (будет обсуждаться в следующем руководстве)
Предположим, вы хотите получить доступ к зарплате человека2 . Вот как вы можете это сделать.
Пример 1: структуры C++
Вывод
В этой программе мы создали структуру с именем Person . Мы также создали переменную Person с именем person1 .
В main() мы присвоили значения переменным, определенным в Person для объекта person1.
Обратите внимание, что мы использовали функцию strcpy() для присвоения значения person1.name .
Это связано с тем, что name представляет собой массив символов (C-строку), и мы не можем использовать с ним оператор присваивания = после объявления строки.
Наконец мы напечатали данные человека1 .
Тип ключевого слова
Мы используем ключевое слово typedef для создания псевдонима для типов данных. Он обычно используется со структурами для упрощения синтаксиса объявления переменных.
Например, давайте посмотрим на следующий код:
Мы можем использовать typedef для написания эквивалентного кода с упрощенным синтаксисом:
Пример 2: определение типа C++
Вывод
Здесь мы использовали typedef со структурой Person для создания псевдонима person .
Теперь мы можем просто объявить переменную Person, используя псевдоним человека:
Вложенные структуры
В программировании на C можно создавать структуры внутри структуры. Например,
Предположим, вы хотите установить для imag переменной num2 значение 11. Вот как вы можете это сделать:
Пример 3. Вложенные структуры C++
Вывод
Почему структуры в C?
Предположим, вы хотите хранить информацию о человеке: его/ее имя, номер гражданства и зарплату. Вы можете создать разные переменные name , citNo и зарплата для хранения этой информации.
Что делать, если вам нужно хранить информацию более чем об одном человеке? Теперь вам нужно создать разные переменные для каждой информации о человеке: name1 , citNo1 , зарплата1 , имя2 , citNo2 , зарплата2 и т. д.
Лучше было бы собрать всю связанную информацию в единой структуре имени Person и использовать ее для каждого человека.
Возьмите свой любимый напиток, редактор и компилятор, включите несколько мелодий и начните структурировать программу на C, состоящую из нескольких файлов.
Часто говорят, что искусство компьютерного программирования частично заключается в управлении сложностью и частичном присвоении имен вещам. Я утверждаю, что это в значительной степени верно с добавлением «иногда это требует рисования прямоугольников».
В этой статье я назову некоторые вещи и устраню некоторые сложности при написании небольшой программы на C, которая в общих чертах основана на структуре программы, которую я обсуждал в разделе "Как написать хорошую основную функцию C", но отличается. Этот что-нибудь сделает. Возьмите свой любимый напиток, редактор и компилятор, включите несколько мелодий, и давайте вместе напишем немного интересную программу на C.
Философия хорошей программы Unix
Первое, что нужно знать об этой программе C, это то, что это инструмент командной строки Unix. Это означает, что он работает (или может быть перенесен) на операционные системы, которые обеспечивают среду выполнения Unix C. Когда Unix была изобретена в Bell Labs, она с самого начала была пропитана философией дизайна. Моими собственными словами: программы делают одно, делают это хорошо и воздействуют на файлы. Хотя имеет смысл делать что-то одно и делать это хорошо, часть о «действиях с файлами» кажется немного неуместной.
Оказывается, абстракция "файла" в Unix очень эффективна. Файл Unix представляет собой поток байтов, который заканчивается маркером конца файла (EOF). Вот и все. Любая другая структура файла определяется приложением, а не операционной системой.Операционная система предоставляет системные вызовы, которые позволяют программе выполнять набор стандартных операций с файлами: открывать, читать, записывать, искать и закрывать (есть и другие, но эти важные). Стандартизация доступа к файлам позволяет различным программам использовать общую абстракцию и работать вместе, даже если разные люди реализуют их на разных языках программирования.
Наличие общего файлового интерфейса позволяет создавать компонуемые программы. Вывод одной программы может быть вводом другой программы. Семейство операционных систем Unix по умолчанию предоставляет три файла при выполнении программы: стандартный ввод (stdin), стандартный вывод (stdout) и стандартный файл ошибок (stderr). Два из этих файлов открыты только для записи: stdout и stderr, а stdin открыт только для чтения. Мы видим это в действии всякий раз, когда используем перенаправление файлов в командной оболочке, такой как Bash:
Эту конструкцию можно кратко описать так: вывод ls записывается в стандартный вывод, который перенаправляется на стандартный ввод grep, чей стандартный вывод перенаправляется на sed, чей стандартный вывод перенаправляется для записи в файл с именем ack в текущем каталог.
Мы хотим, чтобы наша программа хорошо работала в этой экосистеме столь же гибких и замечательных программ, поэтому давайте напишем программу, которая читает и записывает файлы.
MeowMeow: концепция потокового кодировщика/декодера
Когда я был росистым ребенком, изучавшим информатику в s, существовало множество схем кодирования. Некоторые из них предназначались для сжатия файлов, некоторые для упаковки файлов вместе, а у других не было никакой цели, кроме как быть мучительно глупыми. Примером последней является схема кодирования MooMoo.
Чтобы дать нашей программе цель, я обновлю эту концепцию для 2000-х годов и реализую концепцию, называемую кодированием MeowMeow (поскольку Интернет любит кошек). Основная идея здесь состоит в том, чтобы взять файлы и закодировать каждую часть (половину байта) текстом «мяу». Строчная буква обозначает ноль, а прописная — единицу. Да, это увеличит размер файла, поскольку мы обмениваем 4 бита на 32 бита. Да, это бессмысленно. Но представьте удивление на чьем-то лице, когда это происходит:
Это будет потрясающе.
Реализация, наконец
Полный исходный код можно найти на GitHub, но во время написания я расскажу о своем мыслительном процессе. Цель состоит в том, чтобы проиллюстрировать, как структурировать программу C, состоящую из нескольких файлов.
Уже установив, что я хочу написать программу, кодирующую и декодирующую файлы в формате MeowMeow, я запустил оболочку и ввел следующие команды:
Короче говоря, я создал каталог, полный пустых файлов, и передал их в git.
Несмотря на то, что файлы пусты, вы можете сделать вывод о назначении каждого из их названий. На случай, если вы не сможете, я снабдил каждое касание кратким описанием.
Обычно программа запускается как один простой файл main.c, содержащий только две или три функции, решающие проблему. А потом программист опрометчиво показывает эту программу подруге или ее начальнику, и вдруг количество функций в файле резко увеличивается для поддержки всех всплывающих новых «функций» и «требований». Первое правило «Клуба программ» — не говорить о «Клубе программ». Второе правило — минимизировать количество функций в одном файле.
Честно говоря, компилятору C совершенно все равно, если каждая функция в вашей программе находится в одном файле. Но мы не пишем программы для компьютеров или компиляторов; мы пишем их для других людей (которые иногда являются нами). Я знаю, что это, вероятно, сюрприз, но это правда. Программа воплощает в себе набор алгоритмов, которые решают задачу с помощью компьютера, и важно, чтобы люди понимали это, когда параметры задачи меняются непредвиденным образом. Людям придется изменить программу, и они будут проклинать ваше имя, если у вас есть все 2049 функций в одном файле.
Поэтому мы, хорошие и настоящие программисты, разбиваем функции, группируя похожие функции в отдельные файлы. Здесь у меня есть файлы main.c, mmencode.c и mmdecode.c. Для таких небольших программ это может показаться излишним. Но небольшие программы редко остаются маленькими, поэтому планировать расширение — это «хорошая идея».
А как насчет файлов .h? Я объясню их в общих чертах позже, но вкратце они называются файлами header и могут содержать определения типов языка C и директивы препроцессора C. Заголовочные файлы не должны не содержать никаких функций. Заголовки можно рассматривать как определение интерфейса прикладного программирования (API), предлагаемого файлом с расширением .c, который используется другими файлами .c.
Но что, черт возьми, такое Makefile?
Я знаю, что все вы, крутые ребята, используете интегрированную среду разработки "Ultra CodeShredder 3000" для написания следующего блокбастера, и создание вашего проекта состоит из смешивания Ctrl-Meta-Shift-Alt-Super-B.Но в мое время (и сегодня) много полезной работы выполняли программы на C, созданные с помощью Makefiles. Makefile — это текстовый файл, содержащий рецепты работы с файлами, и программисты используют его для автоматизации сборки двоичных файлов своих программ из исходного кода (и других вещей тоже!).
Возьмем, к примеру, эту маленькую жемчужину:
Текст после октоторпа/фунта/решётки является комментарием, например, в строке 00.
Строка 01 — это присвоение переменной, где переменная TARGET принимает строковое значение my_sweet_program. По соглашению, хорошо, я предпочитаю, все переменные Makefile пишутся с заглавной буквы и используют символы подчеркивания для разделения слов.
Строка 02 состоит из имени файла, который создает рецепт, и файлов, от которых он зависит. В данном случае цель — my_sweet_program, а зависимость — main.c.
Последняя строка, 03, имеет отступ с табуляцией, а не с четырьмя пробелами. Это команда, которая будет выполняться для создания цели. В этом случае мы вызываем cc внешний интерфейс компилятора C для компиляции и компоновки my_sweet_program.
Использовать Makefile очень просто:
Файл Makefile, который создаст наш кодировщик/декодер MeowMeow, значительно сложнее, чем в этом примере, но основная структура та же. Я расскажу об этом в стиле Барни в другой статье.
Форма следует за функцией
Моя идея состоит в том, чтобы написать программу, которая читает файл, преобразует его и записывает преобразованные данные в другой файл. Следующее сфабрикованное взаимодействие с командной строкой — это то, как я представляю себе использование программы:
Нам нужно написать код для обработки командной строки и управления входными и выходными потоками. Нам нужна функция для кодирования потока и записи его в другой поток. И, наконец, нам нужна функция для декодирования потока и записи его в другой поток. Подождите секунду, я говорил только о написании одной программы, но в приведенном выше примере я вызываю две команды: мяу и немяу? Я знаю, вы, вероятно, думаете, что это чертовски сложно.
Незначительное отступление: argv[0] и команда ln
Если вы помните, сигнатура функции C main такова:
где argc — это количество аргументов командной строки, а argv — это список указателей на символы (строк). Значение argv[0] — это путь к файлу, содержащему исполняемую программу. Многие служебные программы Unix с дополнительными функциями (например, сжатие и распаковка) выглядят как две программы, но на самом деле это одна программа с двумя именами в файловой системе. Трюк с двумя именами достигается путем создания «ссылки» файловой системы с помощью команды ln.
Пример из /usr/bin на моем ноутбуке:
Здесь git и git-receive-pack — это один и тот же файл с разными именами. Мы можем сказать, что это один и тот же файл, потому что у них одинаковый номер инода (первый столбец). Индексный дескриптор — это функция файловой системы Unix, которая выходит за рамки этой статьи.
Хорошие и/или ленивые программисты могут использовать эту особенность файловой системы Unix, чтобы писать меньше кода, но вдвое больше программ, которые они создают. Во-первых, мы пишем программу, которая меняет свое поведение в зависимости от значения argv[0], затем обязательно создаем ссылки с именами, вызывающими такое поведение.
В нашем Makefile ссылка unmeow создается по следующему рецепту:
Я склонен параметризовать все в своих файлах Makefile, редко используя "голую" строку. Я группирую все определения в верхней части Makefile, что облегчает их поиск и изменение. Это имеет большое значение, когда вы пытаетесь перенести программное обеспечение на новую платформу и вам нужно изменить все свои правила, чтобы использовать xcc вместо cc.
-
Вы еще не сталкивались с объектно-ориентированным программированием (по крайней мере, некоторые из вас).
-
Стандартная библиотека C предоставляет ряд функций для доступа к тексту или форматированным данным
-
Вы должны сначала открыть файл перед чтением данных из файла
|
-
Текстовые данные — это данные, представленные с использованием кода ASCII
-
Функция форматированного чтения fread() используется для чтения форматированных данных
-
Когда вы закончите запись файла, вы должны закрыть файл, чтобы избежать возможной потери данных
-
Функция форматированной печати fprintf() используется для записи форматированных данных
-
Доступ "последовательный файл" означает, что для чтения N-го байта в файле необходимо сначала прочитать N-1-й байт
(По индукции это означает, что вы должны сначала прочитать все, что предшествует чему-либо)
-
смещение               новая позиция в файле. (Это точное местоположение новой позиции зависит от того, откуда )
Новая позиция равна смещению в байтах от начала файла |
Новая позиция равна смещению в байтах от текущей позиции чтения в файле |
Новая позиция равна смещению в байтах от конца файла | TR>
-
Когда вы читаете файл данных, вы должны знать, какие данные в нем хранятся.
Вам не нужно читать все структуры из файла, если вы точно знаете, что вам нужно. Чтобы получить эту запись, вы используете функцию fseek(). Эта функция манипулирует индикатором позиции файла, обеспечивая произвольный доступ к данным файла.
Из Урока прошлой недели я настроил код, который записывает структуры пяти человек в файл family.data. Запустите код из этого урока, чтобы создать файл, который будет использоваться в этом уроке.
Зная, что файл family.data содержит структуры пяти человек, я могу использовать функцию fseek(), чтобы установить индикатор позиции файла и прочитать определенную запись (структуру), хранящуюся в файле. Вот как определяется функция fseek():
int fseek(FILE *stream, long offset, int откуда);
*stream — это дескриптор открытого файла. Его можно открыть для чтения, записи или того и другого.
смещение — это место, где установлен индикатор позиции файла.
откуда ссылается, откуда измеряется аргумент смещения; это не всегда начало файла. Константы SEEK_SET , SEEK_CUR и SEEK_END представляют начало файла, текущее положение индикатора позиции в файле и конец файла соответственно.
Нажмите здесь, чтобы просмотреть пример кода, извлекающий определенную запись из файла family.data. Ключом к коду является оператор fseek() в строке 30:
fseek(fp, (record-1)*sizeof(struct person), SEEK_SET);
Переменная fp — это указатель на ФАЙЛ. запись — это структура, которую нужно прочитать (введите значения от 1 до 5). Оператор sizeof используется в структуре person для получения ее размера в байтах, таким образом вычисляется смещение. Константа SEEK_SET указывает функции искать файл с начала.
После установки индикатора позиции в файле функция fread() извлекает структуру из файла. Код отображает результаты. Вот пример запуска:
Выбрать какую запись (от 1 до 5)? 4
Рекорд 4:
Имя: Мисси
Возраст: 12
Вес: 80,2
Скажем, у Мисси был день рождения, и сейчас ей 13 лет. Чтобы обновить ее запись в файле, сначала прочтите ее запись. В памяти запись модифицируется, обновляя элемент age структуры человека. Затем эта запись записывается обратно в ту же позицию в файле. Указатель позиции в файле должен быть установлен для первоначального чтения определенной записи и снова установлен для ее обратной записи в то же место в файле.
Нажмите здесь, чтобы просмотреть код, обновляющий запись в файле. Константа RECORD установлена на извлекаемую запись 3, которая является элементом массива структур Мисси. Вот оператор fseek(), который повторяется в коде три раза:
fseek(fp, RECORD*sizeof(struct person), SEEK_SET);
Первый раз нужно расположить запись для чтения. Второй раз — поместить обновленную запись для записи. И последний раз — прочитать запись, чтобы подтвердить, что изменение было внесено.
Вот результат:
Рекорд 4:
Имя: Мисси
Возраст: 12,
Вес: 80,2
Рекорд обновлен:
Рекорд 4:
Имя: Мисси
/>Возраст: 13
Вес: 80,2
Произвольный доступ к файлам позволяет вам извлекать и помещать части файла, что намного эффективнее, чем обработка всего файла, манипулирование им в памяти, а затем запись обратно. Чтобы выполнить произвольный доступ к файлу, вам не нужно открывать файл каким-либо особым образом; просто используйте функцию fseek(), чтобы установить индикатор позиции файла, и вы выполняете произвольный доступ к файлу.
Одна из проблем, которая возникает со структурами и файлами, — это указатели. Когда структура является указателем или содержит указатели, вы должны убедиться, что вы записываете данные в файл, а не адрес указателя. Я рассмотрю этот вопрос в уроке на следующей неделе.
3 мысли о «Чтение и запись структур в файл»
Я никогда этого не делал, в большинстве операций по сохранению данных, которые я когда-либо делал, использовались реляционные базы данных, а в остальных — XML или JSON. (О, и MongoDB, о которой я стараюсь забыть!)
Очевидно, что описанный вами подход более компактен, но я бы беспокоился о сохранении материала в файл, специфичный для C.Знаете ли вы, можно ли легко читать созданные здесь файлы на других языках?
Отличный вопрос! Я бы предположил, что пока формат задокументирован, файл можно читать на любом языке. Опять же, я не настолько хорошо разбираюсь в чтении необработанных данных на других языках. Тем не менее, я считаю, что XML, JSON и другие форматы были разработаны для обеспечения большей гибкости в хранении данных, чем жесткие структуры C.
Раньше я читал все типы файловых данных, используя эту технику. У меня была книга (может быть, и сейчас есть), в которой перечислены все популярные тогда форматы файлов. Если бы вы могли создать структуру на языке C, которая соответствовала бы способу хранения данных в файле, вы могли бы ее прочитать. Особенно для низкоуровневых сетевых пакетов, которые все довольно согласованы, использование структуры было тем, как ими манипулировали.
Преимущество XML и JSON заключается в том, что это не просто форматы данных, а форматы метаданных «данные ПЛЮС», поэтому вы можете более или менее реконструировать структуру данных, не зная о ней. Недостатком является, очевидно, много лишнего багажа. В частности, файлы XML могут содержать больше метаданных, чем данных.
Любой файл в конечном счете представляет собой строку битов и может быть прочитан на любом языке, но программисты на языках более высокого уровня были бы недовольны, если бы им пришлось создавать отдельный код для преобразования этой строки битов в структуру данных для каждой отдельной структуры C. он был создан из.
Не должно быть слишком сложно написать модуль или класс, скажем, на Python, который брал бы файловый поток, массив байтов или что-то еще и спецификацию базовой структуры данных и преобразовывал одно в другое.
Читайте также: