Как заполнить массив структур из файла

Обновлено: 05.07.2024

Иногда в файл нужно записывать массив структур. В случае необходимости записи массива структур в бинарный файл, наиболее выгодным способом является способ, где в файл сначала записывается число, обозначающее число элементов в массиве, а только потом сам массив структур. Если этого не делать, то издержки работы с файлом, открытым для чтения в бинарном режиме, двойной проход по файлу, где первый проход только для подсчёта числа элементов, а второй проход для непосредственного чтения значений. Разумеется, проще сразу записать число элементов, чтобы сэкономить время обработки.

Часто новички не различают разницу между массивами и указателями и называют указатель на массив массивом. Когда мы создаём массив с задаваемым числом элементов во время работы программы, тогда мы используем указатель на массив.

int * arr = new int [ 10 ] ; //указатель на начало массива, состоящего из 10 элементов

Поскольку из указателя на массив нельзя узнать размер массива с помощью операции sizeof , то при записи в файл в бинарном режиме нельзя взять и определить за раз, сколько байтов потребуется записать. Это немного неудобно. Тем не менее, в случае работы с массивом с зафиксированным числом элементов, когда заранее точно знаешь это число, возможно записывать в бинарный файл массив структур сразу. В таком случае будут мешать иные проблемы: выравнивания структур, использование типов внутри структур. Очень важно, чтобы в случае записи всего массива структур за раз внутри структуры не было указателей, а типы были простыми (pod-типы). Но не будем углубляться, смотрим пример:

MyContainer O [ SIZE ] ; //Массив структур, умеет хранит 5 элементов типа MyContainer

MyContainer R [ SIZE ] ; //В этот массив будем читать значения из файла

ofstream fo ( PATH , ios:: binary ) ; //Записываем массив O в файл, бинарный режим

ifstream fr ( PATH , ios:: binary ) ; //Читаем файл и переносим значения в массив R

Оборачивание структуры директивой pragma необходимо для того, чтобы типы внутри структуры не уширялись.

Несмотря на то, что выглядит это достаточно удобным, иногда такой способ записи/чтения может тормозить работу программы. Автоматические выравнивания структур появились не сами по себе, а потому что нужно было как-то оптимизировать работу для разных платформ. Малая часть всего комплекса оптимизаций, это как раз автоматическое выравнивание структур. По той причине, что принудительные выравнивания могут тормозить работу программы, а иногда даже приводить к падению программы (как утверждают некоторые люди), некоторые авторы не рекомендуют использовать директиву pragma pack . В таком случае остаётся только поэлементная запись и, разумеется, поэлементное чтение.

Поэлементная обработка массива структур может выглядеть вот так:

MyContainer O [ SIZE ] ; //Массив структур, умеет хранит 5 элементов типа MyContainer

MyContainer R [ SIZE ] ; //В этот массив будем читать значения из файла

fo . write ( ( char * ) &O[i].x, sizeof(O[i].x)); //Записали значение x

fo . write ( ( char * ) &O[i].y, sizeof(O[i].y)); //записали значение y

fr . read ( ( char * ) &R[i].x, sizeof(O[i].x)); //Записали значение x

fr . read ( ( char * ) &R[i].y, sizeof(O[i].y)); //Записали значение y

Несмотря на то, что это работает, в этом коде есть подводные камни в виде платформозависимости. Может получиться так, что при смене версии компилятора или апгрейде оборудования файлы перестанут читаться так, как должны. Это не проблема структур, а проблема непосредственно обычных типов. Для целых типов в некоторых реализациях компиляторов присутствуют специальные переносимые типы, а для чисел с точкой всё сложнее. Я ограничусь обычными типами, ибо легко могу написать бред.

Рассмотрим пример работы с указателем внутри структуры. Для начала просто заполнение и вывод на экран без работы с файлом. Структура, отображаемая в примере, хранит одномерные массивы с разной ёмкостью. Для узнавания начала массивов используется указательная переменная, для узнавания числа вмещаемых элементов дополнительная переменная, обозначающая размер массива.

MyContainer values [ SIZE ] ; //Массив структур называется values, вмещает SIZE структур типа MyContainer

values [ i ] . x [ j ] = count ++ ; //Записываем в массив число count

cout < < values [ i ] . x [ j ] < < '\t' ; //Записываем в массив число count

Даже в таком виде очевидны усложнения кода. Это одна из многих причин недолюбливания указательных переменных. Поскольку мы однозначно знаем, что именно представляет собой указатель: указывает на одномерный массив, а сама структура хранит достаточно данных для работы, такую структуру можно записать в файл, например, по разыменованию указательной переменной или по обращению к индексу массива, если указатель указывает на массив.

MyContainer values [ SIZE ] ; //Массив структур называется values, вмещает SIZE структур типа MyContainer

MyContainer R [ SIZE ] ; //Массив структур называется R, вмещает SIZE структур типа MyContainer, будем в него переносить значения из файла

values [ i ] . x [ j ] = count ++ ; //Записываем в массив число count

f1 . write ( ( char * ) &values[i].size_arr, sizeof(values[i].size_arr)); //Сначала просто запись числа

for ( unsigned j = 0 ; j < values [ i ] . size_arr ; j ++ ) < //Потом обход по всему массиву.

f1 . write ( ( char * ) &values[i].x[j], sizeof(values[i].x[j])); //и запись каждого его значения

/*НАМ МАССИВ values В НАСТОЯЩЕЕ ВРЕМЯ БОЛЬШЕ НЕ НУЖЕН, ЧИСТИМ ЕГО*/

R [ i ] . x = new int [ R [ i ] . size_arr ] ; //При чтении обязательно не забыть выделить память.

/*ОБЯЗАТЕЛЬНО ЧИСТИМ НЕНУЖНЫЙ МАССИВ, ЕСЛИ ДЛЯ ЕГО СОЗДАНИЯ БЫЛО new*/

Записать в файл можно и немного проще (в том смысле, что укоротить число строчек):

f1 . write ( ( char * ) &values[i].size_arr, sizeof(values[i].size_arr));

f1 . write ( ( char * ) values [ i ] . x , sizeof ( values [ i ] . x [ 0 ] ) * values [ i ] . size_arr ) ;

Можно использовать более удобное в плане использования решение, описав функции записи и чтения вовнутрь самой структуры. Это будет выглядеть так:

f.write((char*)&size_arr, sizeof(size_arr)); //записываем size_arr

f . write ( ( char * ) x , sizeof ( x [ 0 ] ) * size_arr ) ; //записываем массив, на который указывает указатель

x = new int [ size_arr ] ; //выделяем память согласно считанному числу

f . read ( ( char * ) x , sizeof ( x [ 0 ] ) * size_arr ) ; //читаем непосредственно значения массива

MyContainer values [ SIZE ] ; //Массив структур называется values, вмещает SIZE структур типа MyContainer

MyContainer R [ SIZE ] ; //Массив структур называется R, вмещает SIZE структур типа MyContainer, будем в него переносить значения из файла

values [ i ] . x [ j ] = count ++ ; //Записываем в массив число count

Это можно ещё улучшать. На данный момент я некоторых вещей не знаю, поэтому пока остановлюсь на том, что уже написал. Эти примеры прямой ключ к пониманию записи простых массивов структур в файл и чтение их оттуда. Лучше всего избегать указателей, потому что даже в простой ситуации вы могли увидеть как резко осложняется код.

Иногда встречается код, в котором внутри массива структур хранятся идентичные значения. В моих примерах значениями были массивы, ёмкость которых была различной. Если бы у каждого массива ёмкость была одной и той же, то наиболее употребимый вариант для такого случая — записать сначала в файл число, обозначающее число элементов масива структур, а потом записать саму структуру, и, соответственно, прочитать сначала число, а потом элементы структуры, ориентируясь на прочитанное число. Число файл увеличит совсем на чуть-чуть, а времени сэкономить может много.

const int SIZE_X = 7 ; //Это размер массива, на который будет указывать указатель структуры

unsigned value = 0 ; //Это значение для записи в структуру

MyContainer * arr = new MyContainer [ SIZE_STRUCT ] ; //Создаём массив из SIZE_STRUCT структур

arr [ i ] . x = new int [ SIZE_X ] ; //выделяем память каждому указателю (в каждом индексе массива сырой указатель)

f1 . write ( ( char * ) &SIZE_STRUCT, sizeof(SIZE_STRUCT)); //Сначала записали число, обозначающее вместимость массива структур

for ( unsigned i = 0 ; i < SIZE_STRUCT ; i ++ ) < //Потом записали массив структур

f1 . write ( ( char * ) arr [ i ] . x , sizeof ( arr [ i ] . x [ 0 ] ) * SIZE_X ) ;

for ( unsigned i = 0 ; i < SIZE_STRUCT ; i ++ ) < //Обходим внутренний масив структуры (тот, на который указывает указатель)

delete [ ] arr [ i ] . x ; //зачищаем каждую ячейку от указателей, к которым применялось new

int size_arr = 0 ; //Размер массива, на который будет указывать R, позднее мы узнаем ему значение

f2 . read ( ( char * ) &size_arr, sizeof(size_arr)); //Читаем из файла число в size_arr (число у нас записано в самом начале)

R = new MyContainer [ size_arr ] ; //Выделяем память общему массиву (массиву структур) согласно зачитанному числу

/*выделяем память указателям, в каждой ячейке массива структур лежит сырой указатель*/

f2 . read ( ( char * ) R [ i ] . x , sizeof ( R [ i ] . x [ 0 ] ) * SIZE_X ) ; //Зачитали значения и сохранили их в выделенную память

Разумеется, для того, чтобы это проделывать и хоть что-то понимать, нужны неплохие знания и структур и массивов. Поскольку в примерах задействуется указательная переменная, то, само собой, требуется некоторое понимание указателей. Случаи могут встретиться более сложные. Не всегда можно сохранить за один раз блок памяти. Чтобы понимать когда можно, когда нет, как раз и нужно понимание указателей и знание, когда выделяется непрерывный блок памяти, а когда выделенное хранится в памяти в разбросанном виде. Но новичкам, как правило, хватает варианта с записью числа в начало файла и последующей записью массива структур. При этом варианты у новичков немного проще, чем показал я. Вся эта тема только вводная в тему записи в бинарные файлы массивов структур. Как я упоминал недавно, развивать пока что не могу, потому что у меня есть определённая нехватка нужных знаний. Тем не менее, надеюсь, что эти примеры сильно помогут вам, потому что даже в 2018г. в интернете сложно найти вот именно вот это вот, когда оно многим, как мне кажется, на самом деле нужно. Могу лишь констатировать, что вам важно разобраться в ходе написания показанных кодов, чтобы задействовать это самостоятельно.

Чтение из файла

Добрый день! В этой статье я расскажу о том, как написать программу, которая будет считывать строки из файла. Покажу как записать их в массив или вывести. При написании программы будут использоваться функции из стандартной библиотеки языка C++.

Стандартная библиотека языка C++ <fstream> включает множество функций для работы с файлами. Описание функций можно найти в сети или в учебниках по C++. Здесь же я опишу одну, которая позволит произвести чтение строки из файла.

Содержание файла strings.txt

Три строки, содержащиеся в файле, я запишу массив и выведу на экран.

Пингвин читает содержимое файла

Переходим к написанию программы на C++.

Нашей программе понадобятся два заголовочных файла <iostream> и <fstream>. Первый нужен будет для использования вывода на консоль, второй для работы с файлами.

Объявим две целочисленные константы len и strings, они будут хранить максимальную длину наших строк и количество строк. Объявим символьную константу ch, которая будет хранить разделяющий символ. Первые две константы будут использоваться для объявления массива. Мой файл содержит 3 строки

При помощи значений двух первых констант объявим двумерный массив символов.

Создадим объект класса ostream, в конструктор поместим адрес файла, флаги открытия.

Флаг ios::in позволяет открыть файл для считывания, ios::binary открывает его в двоичном режиме.

Далее стоит проверить открылся ли файл, если не открылся, то завершаем работу программы.

В данный момент программа имеет такой вид

Теперь остается описать алгоритм считывания строк из файла и занесения их в массив с последующим выводом. Для этого понадобится цикл от нуля до strings с инкрементом переменной r. Во время каждого прохода цикла используем перегруженную функцию getline() объекта fs с тремя аргументами.

fs.getline(Массив_символов, Макс_длина_строки, Разделитель_строк)

Функция считывает символы из файла, пока не считает количество равное Макс_длина_строки, либо не встретит на своём пути символ равный Разделитель_строк, а после записывает считанные символы в Массив_символов. В качестве разделителя в моём текстовом файле используется перенос строки.

После сразу же выводим содержимое строки, хранящееся в массиве, при помощи поточного вывода в консоль cout.

Весь листинг конечной программы

За счет константных переменных её можно легко модернизировать. Изменив константу strings, можно указать количество выводимых строк. Чтение из файла будет производится до тех пор, пока массив не заполнится нужным количеством строк. Изменив константу ch, можно изменить разделитель строк(Например, можно разделять их пробелом и занести в массив отдельные слова из файла и т.д.).

Если Вас интересует запись в файл, то почитайте статью о чтении из input.txt и записи данных в файл output.txt.

Пример работы string и пример считывания всех строк из файла в массив с последующим выводом

по-прежнему очень новый для C, но начинающий навязывать это.

Моя программа должна создавать/записывать файл и хранить информацию из массива структур. Эта часть в порядке. В чем у меня проблемы с чтением из этого файла обратно в пустой массив структур.

здесь мои структуры:

здесь мой метод загрузки, который читает из моего файла обратно в мой массив структур:

также здесь мой метод записи:

однако, когда я распечатываю свою collection массивов после загрузки. ее пустой. хотя я могу видеть свой файл в папке проекта и открывать его и проверять, что данные там.

Я правильно понял, что мне не нужен буфер, так как мне не нужно делать какие-либо обработки на нем, прежде чем использовать его?

также, поскольку я уже статически выделял пространство для памяти. Правильно ли я считаю, что могу просто читать прямо в массив?

вот мой код печати:

Я вижу здесь несколько проблем. Сначала, как вы читаете файл:

Это будет считываться в коллекцию 1 байт из fileName в Collection
Вы должны проверить возвращаемый статус fread() , когда он будет успешным, он сообщит вам общее количество прочитанных байтов. (параметр 2 * параметр 3 или 1 * 1 в вашем случае).

Когда я изменяю свой код чтения следующим образом:

Он успешно читает из файла. однако теперь у вас другая проблема. Скажем, ваш файл содержит следующее:

Поэтому (я думаю), что формат для вашего файла, имя (ASCII), рейтинг (int) и URL (ASCII). Теперь предположим, что ваша функция main() выглядит так:

То, что вы вернетесь на stdout , будет примерно таким:

Причина в том, что вы объявили свой массив статическими (и очень большими) элементами. fread() попытается прочитать размер sizeof(struct Video) который составляет 1024 + 4 + 1024 байта, поэтому, если каждая из ваших строк не является точным размером (1024 символа для имени и URL-адреса), тогда вы получите что похоже на испорченные или пустые данные.

Я бы предложил прочитать, пока вы не нажмете пробел и не сохраните каждое значение в правильном элементе, вместо того, чтобы пытаться прочитать полный файл в массиве.

РЕДАКТИРОВАТЬ:
Если вы хотите заполнить свой массив следующим образом:

Вы должны гарантировать длину данных. В вашем примере вам нужно сказать, что "имя, однако, много символов, + пробелов = 1024" и одинаково для URL. Кажется, это ужасное пространство. Лучше всего заполнить массив одним элементом за раз:

Вы можете использовать fscanf() для чтения до пробела. Честно говоря, если вы собираетесь заполнять один элемент за раз, я просто использую указатели символов для имени и URL-адреса и динамически назначаю память, чтобы у вас не было огромных потерь в массивах.

Структуры часто образуют массивы. Чтобы объявить массив структур, вначале необходимо определить структуру (то есть определить агрегатный тип данных), а затем объявить переменную массива этого же типа. Например, чтобы объявить 100-элементный массив структур типа addr , который был определен ранее, напишите следующее:

Это выражение создаст 100 наборов переменных, каждый из которых организован так, как определено в структуре addr .

Чтобы получить доступ к определенной структуре, указывайте имя массива с индексом. Например, чтобы вывести ZIP-код из третьей структуры, напишите следующее:

Как и в других массивах переменных, в массивах структур индексирование начинается с 0.

Пример со списком рассылки

Чтобы показать, как используются структуры и массивы структур, в этом разделе создается простая программа работы со списком рассылки, и в ее массиве структур будут храниться адреса и связанная с ними информация. Эта информация записывается в следующие поля: name (имя), street (улица), city (город), state (штат) и zip (почтовый код, индекс).

Вся эта информация, как показано ниже, находится в массиве структур типа addr :

Обратите внимание, что поле zip имеет целый тип unsigned long . Правда, чаще можно встретить хранение почтовых кодов, в которых используются строки символов, потому что этот способ подходит для почтовых кодов, в которых вместе с цифрами используются и буквы (как, например, в Канаде и других странах). Однако в нашем примере почтовый индекс хранится в виде целого числа; это делается для того, чтобы показать использование числового элемента в структуре.

Вот main() — первая функция, которая нужна программе:

Функция начинает выполнение с инициализации массива структур, а затем реагирует на выбранный пользователем пункт меню.

Функция init_list() готовит массив структур к использованию, обнуляя первый байт поля name каждой структуры массива. (В программе предполагается, что если поле name пустое, то элемент массива не используется.) А вот сама функция init_list() :

Функция menu_select() выводит меню на экран и возвращает то, что выбрал пользователь.

Обратите внимание, что если все элементы массива структур заняты, то find_free() возвращает -1. Это удобное число, потому что в массиве нет -1-го элемента.

Функция delete() предлагает пользователю указать индекс той записи с адресом, которую требуется удалить. Затем функция обнуляет первый байт поля name .

И последняя функция, которая требуется программе, — это list() , которая выводит на экран весь список рассылки. Из-за большого разнообразия компьютерных сред язык С не определяет стандартную функцию, которая бы отправляла вывод на принтер. Однако все нужные для этого средства имеются во всех компиляторах С. Возможно, вам самим захочется сделать так, чтобы программа работы со списками могла еще и распечатывать список рассылки.

Ниже программа обработки списка рассылки приведена полностью. Если у вас остались какие-либо сомнения относительно ее компонентов, введите программу в компьютер и проверьте ее работу, делая в программе изменения и получая соответствующие результаты.

Читайте также: