БлогNot. C++: сохраняем и читаем статический и динамический массивы структур

C++: сохраняем и читаем статический и динамический массивы структур

Нужен был пример попроще и посовременнее этого, хотя код по ссылке и должен работать в актуальных версиях Studio.

Хотелось показать следующее:

Листинг проверялся в консоли актуальной сборки 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);
}

теги: программирование random c++ список учебное textprocessing

01.02.2021, 18:10; рейтинг: 116