C++: сохраняем и читаем статический и динамический массивы структур
Нужен был пример попроще и посовременнее этого, хотя код по ссылке и должен работать в актуальных версиях Studio.
Хотелось показать следующее:
- создание массива структур с разнотипными данными - вещественное число, строка класса string, булева переменная;
- запись массива структур в текстовый файл и чтение обратно из файла поточными средствами C++.
Листинг проверялся в консоли актуальной сборки Visual Studio 2019.
#include <cstdlib> #include <iostream> #include <fstream> #include <string> #include <clocale> #include <Windows.h> using namespace std; struct test { double val; string str; bool b; }; void error (int n, int st = 0) { //Центр обработки ошибок string msg[] = { "Нормальное завершение (0)", "Ошибка разбора данных (1)", "Не удалось открыть data.txt для записи (2)", "Не удалось открыть data.txt для чтения (3)" }; cout << endl << msg[n]; if (st) cout << endl << "Номер строки = " << st; cout << endl << "Нажмите Enter для завершения"; cin.clear(); cin.ignore(INT_MAX,'\n'); //Ждём нажатия Enter "с гарантией" exit (n); //Выйти с кодом завершения n } int main () { //setlocale (LC_ALL, ".1251"); //Если раскомментировать, в файле нужна будет "," между целой и дробной частью чисел SetConsoleCP(1251); SetConsoleOutputCP(1251); //Не кроссплатформенно, из <Windows.h> const int n = 3; test data[n] = { {1.5, "Name", false}, {2.5, "Снова здорово", true}, {3.14, "ПИ", true} }; string delimiter = "\t"; //Разделитель полей записи //Пишем массив записей построчно в текстовый файл с разделителем delimiter ofstream fw("data.txt"); if (!fw) error(2); for (int i = 0; i < n; i++) { fw << data[i].val << delimiter << data[i].str << delimiter << data[i].b; if (i < n - 1) fw << endl; } fw.close(); //Читаем массив записей построчно из файла ifstream fr("data.txt"); if (!fr) error (3); string str; int i = 0; while (1) { if (fr.eof()) break; getline (fr, str); size_t pos = 0; try { if ((pos = str.find(delimiter)) != string::npos) { //Найден 1-й разделитель data[i].val = stod(str.substr(0, pos)); str.erase(0,pos + delimiter.length()); //Удалить обработанное из строки } else error (1, i + 1); if ((pos = str.find(delimiter)) != string::npos) { //Найден 2-й разделитель data[i].str = str.substr(0, pos); str.erase(0, pos + delimiter.length()); //Удалить обработанное из строки data[i].b = (str == "1" ? true : false); } else error(1, i + 1); } catch (...) { error (1, i + 1); } if (i < n - 1) i++; //Если ещё есть место в массиве! else break; } //Выводим прочитанное на экран for (int i = 0; i < n; i++) { cout << data[i].val << delimiter << data[i].str << delimiter << data[i].b; if (i < n - 1) cout << endl; } error (0); } /* Основной недостаток этого кода очевиден - фиксированное количество записей. Чтобы научиться работать с наборами записей любой размерности, и нужны динамические структуры! */
Основной недостаток этого кода очевиден - мы работаем с фиксированным количеством записей. Чтобы научиться работать с наборами записей любой размерности и нужны динамические структуры.
Ниже с подробными комментариями показана переделанная программа. На этот раз генерируется и записывается в текстовый файл случайное количество случайных структур, а затем они читаются в динамический односвязный список. После вывода списка память, выделенная под его элементы, освобождается.
Обратите в коде внимание на необходимость инициализации генераторов случайных чисел, чтобы получать каждый раз различные значения, а также на функцию roundTo
, написанную для округления вещественного числа до заданного количества знаков в дробной части.
#include <cstdlib> #include <iostream> #include <fstream> #include <string> #include <random> #include <chrono> #include <cmath> #include <Windows.h> using namespace std; struct test { double val; string str; bool b; test* next; //Добавили в структуру указатель на следующий элемент }; void error(int n, size_t st = 0) { //Центр обработки ошибок string msg[] = { "Нормальное завершение (0)", "Ошибка разбора данных (1)", "Не удалось открыть data.txt для записи (2)", "Не удалось открыть data.txt для чтения (3)", "Не удалось выделить память (4)" }; cout << endl << msg[n]; if (st) cout << endl << "Номер строки = " << st; cout << endl << "Нажмите Enter для завершения"; cin.clear(); cin.ignore(INT_MAX, '\n'); //Ждём нажатия Enter "с гарантией" exit(n); //Выйти с кодом завершения n } string random_string(int min, int max) { //Случайная строка от min до max символов длиной, с инициализацией каждый раз unsigned seed = chrono::system_clock::now().time_since_epoch().count(); default_random_engine int_generator(seed); uniform_int_distribution <int> int_distribution(min, max); size_t len = int_distribution(int_generator); string str = ""; default_random_engine char_generator(seed); uniform_int_distribution <int> char_distribution('А', 'Я'); for (size_t i = 0; i < len; i++) str += char_distribution(char_generator); return str; } void copy1rec(test* dest, test* src) { //Копирование одной записи dest->val = src->val; dest->str = src->str; //В отличие от char *, string так скопируется dest->b = src->b; } double roundTo(double val, size_t signs) { //Округление val до signs знаков в дробной части double p = pow(10, signs); double value = (int)(val * p + .5); return (float)value / p; } int main() { SetConsoleCP(1251); SetConsoleOutputCP(1251); //Не кроссплатформенно, из <Windows.h> test* head = NULL, * current = NULL, * prev = NULL; //Голова списка, текущий, предыдущий элементы unsigned seed = chrono::system_clock::now().time_since_epoch().count(); default_random_engine generator(seed); uniform_real_distribution <double> distribution(1, 5); //Для выбора вещественных чисел default_random_engine int_generator(seed); uniform_int_distribution <int> int_distribution(5, 20); //Для выбора целых чисел size_t n = int_distribution(int_generator); //Случайная длина списка string delimiter = "\t"; //Разделитель полей записи //Пишем массив случайных записей построчно в текстовый файл с разделителем delimiter ofstream fw("data.txt"); if (!fw) error(2); for (size_t i = 0; i < n; i++) { fw << roundTo(distribution(generator), 3) << delimiter << random_string(5, 10) << delimiter << (i % 2 ? true : false); if (i < n - 1) fw << endl; } fw.close(); //Читаем массив записей построчно из файла ifstream fr("data.txt"); if (!fr) error(3); string str; size_t cnt = 0; //Счётчик прочитанных записей while (1) { if (fr.eof()) break; getline(fr, str); size_t pos = 0; test data; //Временная запись try { if ((pos = str.find(delimiter)) != string::npos) { //Найден 1-й разделитель data.val = stod(str.substr(0, pos)); str.erase(0, pos + delimiter.length()); //Удалить обработанное из строки } else error(1, cnt + 1); if ((pos = str.find(delimiter)) != string::npos) { //Найден 2-й разделитель data.str = str.substr(0, pos); str.erase(0, pos + delimiter.length()); //Удалить обработанное из строки data.b = (str == "1" ? true : false); } else error(1, cnt + 1); } catch (...) { error(1, cnt + 1); } if (!head) { //Это первый элемент head = new test(); //Выделить память под первый элемент if (!head) error(4); copy1rec(head, &data); //Скопировать туда временную запись head->next = NULL; //Указатель на следующую запись пуст prev = head; //Указатель на предыдущую запись пока на вершине списка } else { //Уже были элементы current = new test(); //Выделить память под новый элемент if (!current) error(4); copy1rec(current, &data); //Скопировать туда временную запись prev->next = current; //Предыдущий элемент показывает на текущий current->next = NULL; //После текущего пока ничего нет prev = current; //Адрес в памяти текущего элемента будет предыдущим на следующем шаге } cnt++; //Просто подсчёт записей для удобства } //Выводим прочитанное на экран test* temp = head; for (size_t i = 0; i < n; i++) { cout << temp->val << delimiter << temp->str << delimiter << temp->b; if (i < n - 1) cout << endl; temp = temp->next; } /* Ещё раз прошли по списку, удаляя элементы ("принцип стека" будет нарушен, удаляем не в порядке, обратном добавлению, но с односвязным списком этого и не сделать за один проход по нему) */ temp = head; test* next = NULL; for (size_t i = 0; i < n; i++) { next = temp->next; delete temp; temp = next; } error(0); }
01.02.2021, 18:10 [1025 просмотров]