Автор Анна Евкова
Преподаватель который помогает студентам и школьникам в учёбе.

Проектирование классов для обработки файлов данных

Содержание:

ВВЕДЕНИЕ

Поскольку программное обеспечение становится все более сложным, особую важность приобретают хорошо структурированные программы, при разработке которых может быть использован восходящий подход: в первую очередь для заданной предметной области выделяются и реализуются наиболее простые функции . Такие программы легко писать и отлаживать, поскольку они, как правило, предоставляют набор модулей, которые могут быть повторно использованы с целью сокращения будущих временных или финансовых затрат. Подход к проектированию таких программ называют архитектурным, для него характерны глобальные ограничения, накладываемые на проектирование прикладного программного обеспечения: выбор стандартов разработки, парадигмы программирования, архитектурных стилей. Большим шагом на пути к совершенствованию языков программирования стало появление объектно-ориентированного подхода. Объектно-ориентированное программирование, представляющее программу как набор объектов и их взаимодействий, способно внести особый вклад в модульность и быстродействие программ. Таким образом, проектирование прикладных программ на объектно-ориентированных языках программирования является актуальной задачей.

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

Цель данной курсовой работы – реализация прикладной программы, осуществляющей обработку файлов данных, на одном из объектно-ориентированных языков программирования высокого уровня. В качестве такого языка в работе был выбран язык программирования C++. Для достижения цели в курсовой работе были поставлены следующие задачи:

  • получить представление об основных принципах объектно-ориентированного программирования и изучить основы языка высокого уровня C++;
  • выявить ключевые особенности проектирования классов, позволяющие вводить информацию, хранить её в файле, а также осуществлять модификацию данных;
  • выполнить реализацию программы обработки файлов данных на выбранном языке, а также ее тестирование и отладку.

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

1. Развитие объектно-ориентированного подхода

Выбор парадигмы программирования, определяющей успешность реализации программного продукта, и соответствующего ей языка программирования имеет решающее значение для специалиста в области информационных технологий и программирования [a.1.]. За последние 50 лет появились сотни языков, поддерживающих различные парадигмы, некоторые из них используют несколько парадигм (такие языки называют мультипарадигменными). Однако, несмотря на большое количество языков программирования, существует несколько действительно важных концепций программирования, и не так много языков, которые были бы актуальны на протяжении более десяти лет. Именно парадигмы программирования определяют общий способ проектирования прикладных программ. Парадигмой программирования называют используемый различными языками подход к программированию, то есть, проще говоря, набор идей и понятий, определяющих стиль написания программ [a.1.].

Инструкции первых языков программирования, появившихся в начале 50-х годов XX века и ориентированных на конкретный компьютер, записывались в исходном коде и выполнялись последовательно. Данные, полученные при выполнении предыдущих инструкций, могли быть считаны из памяти или записаны в нее [a.2.]. Таким образом, программы представляли собой последовательность команд, которые должен был выполнить компьютер. Языки программирования, использующие этот подход (прежде всего, это машинные инструкции и язык ассемблера), образовали императивную парадигму программирования. В отличие от декларативного подхода, при котором задается спецификация решения задачи, в императивном широко используются операторы присваивания. Ранние императивные языки сложны для понимания и решения прикладных задач.

Развитие вычислительных устройств и компьютерной техники привело к появлению процедурных языков. Процедурные языки программирования, первые реализации которых относятся к концу 50-х годов, позволили разбивать задачи на шаги и решать их за шагом, причем возможность определять каждый шаг в процессе решения задачи была предоставлена программисту. Эти языки использовали императивную парадигму, но последовательно выполняемые операторы могли быть собраны в подпрограммы, то есть более крупные целостные единицы кода, с помощью самого языка. Согласно некоторым классификациям, такой подход выделяется в самостоятельную парадигму, получившую название процедурной. К процедурным языкам программирования относятся, например, Паскаль, Си, Алгол, КОБОЛ и Фортран.

Процедурный подход потребовал структурирования будущей программы, разделения ее на отдельные процедуры [a.2.]. При разработке отдельной процедуры о других процедурах требовалось знать только их назначение и способ вызова. Появилась возможность перерабатывать отдельные процедуры, не затрагивая остальной части программы, сокращая при этом затраты труда и машинного времени на разработку и модернизацию программ.

Следующим шагом в углублении структурирования программ стало так называемое структурное программирование, при котором программа в целом и отдельные процедуры рассматривались как последовательности канонических структур: линейных участков, циклов и разветвлений. Возможность читать и проверять программу в виде набора последовательных символов текста привела к повышению производительности труда разработчиков при проектировании и отладке программ. Для того, чтобы повысить структурность программы в целом, подпрограммы (процедуры) получили большую, нежели раньше, независимость – они стали связываться с программами, вызывающими их, исключительно путем передачи аргументов. Поэтому стало считаться нежелательным использование в подпрограммах переменных, принадлежащих другим подпрограммам или главной программе.

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

Все универсальные языки программирования, несмотря на различия в синтаксисе и используемых ключевых словах, реализуют одни и те же канонические структуры: операторы присваивания, циклы и разветвления. Во всех современных языках присутствуют предопределенные (базовые) типы данных (целые и вещественные арифметические типы, символьный и, возможно, строковый тип), имеется возможность использования агрегатов данных, в том числе массивов и структур (записей). Для арифметических данных разрешены обычные арифметические операции, для агрегатов данных обычно предусмотрена только операция присваивания и возможность обращения к элементам агрегата. Вместе с тем при разработке программы для решения конкретной прикладной задачи желательна возможно большая концептуальная близость текста программы к описанию задачи. Например, если решение задачи требует выполнения операций над комплексными числами или квадратными матрицами, желательно, чтобы в программе явно присутствовали операторы сложения, вычитания, умножения и деления данных типа комплексного числа, сложения, вычитания, умножения и обращения данных типа квадратной матрицы. Решение этой проблемы возможно несколькими путями:

  1. Построением языка программирования, содержащего как можно больше типов данных, и выбором для каждого класса задач некоторого подмножества этого языка. Такой язык иногда называют языком-оболочкой. На роль языка-оболочки претендовал язык ПЛ/1, оказавшийся настолько сложным, что так и не удалось построить его формализованное описание. Отсутствие формализованного описания, однако, не помешало широкому применению ПЛ/1 как в Западной Европе, так и в СССР.
  2. Построением расширяемого языка, содержащего небольшое ядро и допускающего расширение, дополняющее язык типами данных и операторами, отражающими концептуальную сущность конкретного класса задач. Такой язык называют языком-ядром. Как язык-ядро были разработаны языки Симула и Алгол-68, не получившие широкого распространения, но оказавшие большое влияние на разработку других языков программирования.

Дальнейшим развитием второго пути явился объектно-ориентированный подход к программированию.

Основные идеи объектно-ориентированного подхода опираются на следующие положения:

  • Программа представляет собой модель некоторого реального процесса, части реального мира;
  • Модель реального мира или его части может быть описана как совокупность взаимодействующих между собой объектов;
  • Объект описывается набором параметров, значения которых определяют состояние объекта, и набором операций (действий), которые может выполнять объект;
  • Взаимодействие между объектами осуществляется посылкой специальных сообщений от одного объекта к другому;
  • Сообщение, полученное объектом, может потребовать выполнения определенных действий, например, изменения состояния объекта.
  • Объекты, описанные одним и тем же набором параметров и способные выполнять один и тот же набор действий представляют собой класс однотипных объектов.

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

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

Классы объектов часто удобно строить так, чтобы они образовывали иерархическую структуру. Например, класс “Студент”, описывающий абстрактного студента, может служить основой для построения классов “Студент 1 курса”, “Студент 2 курса” и т.д., которые обладают всеми свойствами студента вообще и некоторыми дополнительными свойствами, характеризующими студента конкретного курса. При разработке интерфейса с пользователем программы могут использовать объекты общего класса “Окно” и объекты классов специальных окон, например, окон информационных сообщений, окон ввода данных и т.п. В таких иерархических структурах один класс может рассматриваться как базовый для других, производных от него классов. Объект производного класса обладает всеми свойствами базового класса и некоторыми собственными свойствами, он может реагировать на те же типы сообщений от других объектов, что и объект базового класса и на сообщения, имеющие смысл только для производного класса. Обычно говорят, что объект производного класса наследует все свойства своего базового класса.

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

Таким образом, объектно-ориентированная парадигма программирования является одним из наиболее эффективных подходов. Она позволяет объединить данные и функции-члены, относящиеся к одной сущности, и работать с ними как с одним целым, что является необходимым для решения сложных задач.

2. Классы и объекты в языке C++

Классы представляют собой расширенную концепцию структур данных: подобно структурам данных, они могут содержать элементы данных, но они также могут содержать функции в качестве членов [a.3.].

Объект – это экземпляр класса. В терминах переменных класс будет типом, а объект – переменной.

Классы определяются с использованием класса ключевых слов или структуры ключевых слов со следующим синтаксисом (листинг 1).

Листинг 1. Синтаксис определения класса

class имя_класса {

  спецификатор_доступа_1:

    член1;

  спецификатор_доступа_2:

    член2;

  ...

} имя_объекта;

Здесь имя_класса является допустимым идентификатором для класса, имя_объекта является необязательным списком имен для объектов этого класса. Тело объявления может содержать члены, которые могут быть объявлениями данных или функций, и иметь различные спецификаторы доступа [a.3.].

Классы имеют тот же формат, что и простые структуры данных, за исключением того, что они могут также включать функции и иметь спецификаторы доступа. Спецификатор доступа – это одно из следующих трех ключевых слов: private (личные элементы класса), public (общие элементы класса) или protected (защищенные элементы класса). Эти спецификаторы определяют права доступа – под личными элементами понимаются используемые только функциями своего класса. Доступ к общим функциям возможен из любой точки программы, а доступ к защищенным разрешен только функциям данного класса и его наследникам. По умолчанию все члены класса, объявленные с ключевым словом class, имеют private-доступ ко всем его членам. Таким образом, любой член, который объявлен перед любым другим спецификатором доступа, имеет private-доступ автоматически. Пример класса, имеющего члены с различными спецификаторами доступа, продемонстрирован в листинге 2.

Листинг 2. Пример класса, имеющего члены с различными спецификаторами доступа

class Rectangle {

int width, height;

public:

void set_values (int,int);

int area (void);

} rect;

Здесь объявлен класс (то есть тип) с именем Rectangle и объект (то есть переменная) этого класса, называемая rect. Этот класс содержит четыре члена: два члена данных типа int (ширина и высота прямоугольника) с закрытым доступом (потому что private является уровнем доступа по умолчанию) и две функции-члена с открытым доступом: функции set_values и area – в класс включено только объявление этих функций, но не их определение.

После объявления Rectangle и rect можно получить доступ к любому из открытых членов объекта rect, как если бы они были обычными функциями или обычными переменными, просто вставив точку (.) между именем объекта и именем члена. Это соответствует тому же самому синтаксису, что и доступ к членам простых структур данных [a.4.].

Единственные члены rect, к которым нельзя получить доступ вне класса, – это ширина и высота, поскольку они имеют закрытый доступ и на них можно ссылаться только из других членов этого же класса.

В листинге 3 приведен полный пример класса Rectangle.

Листинг 3. Пример использования объекта класса

#include <iostream>

#include <conio.h>

#include <locale.h>

using namespace std;

class Rectangle {

int width, height;

public:

void set_values (int,int);

int area() {return width*height;}

};

void Rectangle::set_values (int x, int y) {

width = x;

height = y;

}

int main () {

setlocale(LC_ALL, "rus");

Rectangle rect;

rect.set_values (3, 4);

cout << "Площадь прямоугольника: " << rect.area();

_getch();

return 0;

}

В этом примере вводится оператор области видимости (::, два двоеточия), который используется также в отношении пространств имен. Здесь он рассматривается в определении функции set_values ​​для определения члена класса вне самого класса. Определение функции-члена было включено непосредственно в определение класса Rectangle, учитывая ее простоту. И наоборот, функция set_values ​​просто объявлена внутри класса, но ее определение находится за пределами класса. В этом внешнем определении оператор :: используется для указания того, что определяемая функция является членом класса Rectangle, а не обычной функцией, не являющейся членом данного класса.

Оператор :: указывает класс, к которому принадлежит определяемый член, предоставляя точно такие же свойства области видимости, как если бы это определение функции было непосредственно включено в определение класса. Например, функция set_values ​​в предыдущем примере имеет доступ к переменным width и height, которые являются закрытыми членами класса Rectangle и, таким образом, доступны только из других членов класса.

Единственная разница между определением функции-члена полностью внутри класса или просто включением ее объявления и последующим ее определением вне класса заключается в том, что в первом случае функция автоматически считается встроенной функцией-членом компилятором, тогда как во втором это обычная (не встроенная) функция-член класса. Это не вызывает различий в поведении, но только при возможных оптимизациях компилятора [a.4.].

Члены класса width и height имеют private-доступ. Объявление их закрытыми означает, что доступ вне класса запрещен. Это имеет смысл, поскольку функция-член для установки значений этих элементов в объекте уже определена; это set_values. Поэтому остальная часть программы не должна иметь прямой доступ к ним. Возможно, в таком простом примере, как этот, трудно понять, насколько полезным может быть ограничение доступа к этим переменным, но в больших проектах может быть очень важно, чтобы значения не могли быть изменены неожиданным образом (неожиданно с точки зрения объекта).

Наиболее важным свойством класса является то, что он является типом, и поэтому можно объявить несколько объектов этого класса. Например, следуя предыдущему примеру класса Rectangle, можно было бы объявить объект rect2 в дополнение к объекту rect (листинг 4).

Листинг 4. Пример использования нескольких объектов класса

#include <iostream>

#include <conio.h>

#include <locale.h>

using namespace std;

class Rectangle {

int width, height;

public:

void set_values (int,int);

int area () {return width*height;}

};

void Rectangle::set_values (int x, int y) {

width = x;

height = y;

}

int main () {

setlocale(LC_ALL, "rus");

Rectangle rect, rect2;

rect.set_values (3, 4);

rect2.set_values (5, 6);

cout << "Площадь прямоугольника 1: " << rect.area() << endl;

cout << "Площадь прямоугольника 2: " << rect2.area() << endl;

_getch();

return 0;

}

В этом конкретном случае классом (типом объектов) является Rectangle, у которого имеется два экземпляра (т.е. объекта): rect и rect2. Каждый из них имеет свои собственные переменные-члены и функции-члены.

Вызов rect.area () не дает тот же результат, что и вызов rect2.area (). Это потому, что каждый объект класса Rectangle имеет свои собственные переменные width и height, так как они – в некотором роде – имеют также свои собственные члены-функции set_value и area, которые работают с собственными переменными-членами объекта.

Классы позволяют программировать с использованием объектно-ориентированных парадигм: данные и функции являются членами объекта, что уменьшает необходимость передавать и переносить обработчики или другие переменные состояния в качестве аргументов функций, поскольку они являются частью объекта, член которого вызывается [a.5.]. При вызовах rect.area и rect2.area не было передано никаких аргументов. Эти функции-члены напрямую использовали элементы данных своих соответствующих объектов rect и rect2.

Конструктор класса – это специальная функция-член класса, которая выполняется всякий раз, когда создаются новые объекты этого класса.

Имя конструктора будет точно таким же, как и у класса, причем конструктор не имеет никакого возвращаемого типа, даже void. Конструкторы могут быть очень полезны для установки начальных значений для определенных переменных-членов [a.5.].

Следующий пример (листинг 5) объясняет концепцию конструктора.

Листинг 5. Пример использования конструктора

#include <iostream>

#include <conio.h>

using namespace std;

class Line {

public:

void setLength(double len);

double getLength(void);

Line(){} // Конструктор класса

private:

double length;

};

void Line::setLength(double len) {

length = len;

}

double Line::getLength( void ) {

return length;

}

int main() {

Line line; // создан объект класса для описания линии

line.setLength(6.0); // задана длина линии

cout << line.getLength() << endl; // длина линии выведена на экран

_getch();

return 0;

}

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

Деструктор – это специальная функция-член класса, которая выполняется всякий раз, когда объект класса выходит из области видимости или когда выражение delete применяется к указателю на объект этого класса.

Деструктор будет иметь точно такое же имя, что и класс с префиксом тильды (~), и он не может ни возвращать значение, ни принимать какие-либо параметры. Деструктор может быть очень полезен для освобождения ресурсов перед выходом из программы.

Классы и объекты этих классов, а также поля и права доступа к ним являются ключевыми понятиями объектно-ориентированного программирования. С появлением объектно-ориентированных технологий классы позволили объединить в одну сущность модули и типы, являясь как структурными единицами, составляющими программу, так и статистическими описаниями определенных типов данных, обрабатываемых в процессе ее выполнения.

3. Работа с файлами в языке C++

Файл данных – это компьютерный файл (именованная область внешней памяти, выделенная для хранения), в котором хранятся данные, используемые компьютерным приложением или системой, включая входные и выходные данные [a.6.]. Файл данных обычно не содержит инструкций или кода для выполнения (то есть компьютерной программы). Большинство компьютерных программ работают с файлами данных.

Файлы данных могут храниться двумя способами:

  1. Текстовые файлы;
  2. Бинарные файлы.

Текстовые файлы (часто называемые ASCII-файлами) хранят информацию в символах кодировки ASCII. Текстовый файл содержит удобочитаемые символы. Пользователь может читать содержимое текстового файла или редактировать его с помощью текстового редактора. В текстовых файлах каждая строка текста заканчивается (разделяется) специальным символов, известным от EOL (от англ. End Of Line – конец строки). Примером текстового файла может служить текстовый документ – файл с расширением .txt.

Бинарные (двоичные) файлы содержат информацию в том же формате, в котором она хранится в памяти, то есть в двоичной форме. В двоичном файле нет разделителей строк и символов перевода. Это позволяет проще и быстрее осуществлять операции чтения и записи, чем при работе с текстовыми файлами [a.6.]. До тех пор, пока файл не нужно переносить в другую операционную систему либо читать при помощи текстового редактора, двоичные файлы являются лучшим способом хранения информации. Примером бинарного файла может служить изображение в формате JPEG.

C++ предоставляет следующие классы для организации операций ввода (вывода) символов из файла (в файл) [a.4.]:

  • ofstream: потоковый класс для записи в файл;
  • ifstream: потоковый класс для чтения файла;
  • fstream: потоковый класс для чтения (записи) из файла (в файл).

Эти классы прямо или косвенно получены из классов istream и ostream. Операции ввода-вывода на экран (в консоль) используют объекты этих классов: cin – это объект класса istream, cout – объект класса ostream. Таким образом, файловые потоки используются точно так же, как cin и cout, с той разницей, что эти потоки связываются с физическими файлами на диске.

Связывание с файлом является первой операцией, выполняемой над объектом одного из этих классов. Эта процедура называется открытием файла. Таким образом, любая операция ввода или вывода, выполненная с объектом класса – файловым потоком – будет применена к физическому файлу.

Чтобы открыть файл, используя объект потока, необходимо использовать его функцию-член open: open (filename, mode), где filename – это строка, представляющая имя открываемого файла, mode – необязательный параметр, который представлен одним из следующих флагов либо их комбинацией:

ios :: in – открыть для операций ввода;

ios :: out – открыть для операций вывода;

ios :: binary – открыть в двоичном режиме;

ios :: ate – установить начальную позицию в конец файла;

Если этот флаг не установлен, начальная позиция – начало файла.

ios :: app – все операции вывода выполняются в конец файла, добавляя содержимое к текущему содержимому файла;

ios :: trunc – если файл открыт для операций вывода и он уже существует, его предыдущее содержимое удаляется и заменяется новым.

Все эти флаги могут быть объединены с помощью побитового оператора ИЛИ (|). Например, код открытия файла example.bin в двоичном режиме для добавления данных представлен в листинге 6.

Листинг 6. Открытие файла в бинарном режиме для добавления данных

ofstream myfile;

myfile.open ("example.bin", ios :: out | ios :: app | ios :: binary);

Каждая функция-член open для классов ofstream, ifstream и fstream имеет режим по умолчанию, который используется, если файл открывается без второго аргумента – ios::out для ofstream, ios::in для ifstream и ios::in | ios::out для fstream.

Для классов ifstream и ofstream режимы ios::in и ios::out соответственно автоматически предполагаются, даже если необязательный параметр, который не включает их, передается в качестве второго аргумента в функцию-член open (флаги комбинируются).

Для fstream значение по умолчанию применяется, только если функция вызывается без указания какого-либо значения для параметра mode. Если функция вызывается с любым значением в этом параметре, режим по умолчанию переопределяется, а не комбинируется.

Поскольку первая задача, выполняемая в файловом потоке, как правило, заключается в открытии файла, эти три класса включают конструктор, который автоматически вызывает функцию-член open и имеет те же параметры, что и эта функция. Поэтому можно было бы просто объявить объект myfile и выполнить ту же операцию открытия, что и в листинге 5, просто написав ofstream myfile ("example.bin", ios :: out | ios :: app | ios :: binary).

Когда все операции ввода-вывода будут завершены, необходимо закрыть файл, чтобы операционная система получила уведомление и ее ресурсы снова стали доступны. Для этого вызывается функция-член close. Эта функция очищает связанные буферы и закрывает файл.

После вызова этой функции объект потока может быть повторно использован для открытия другого файла, а файл будет снова доступен для открытия другими процессами.

В случае, если объект уничтожен, но все еще связан с открытым файлом, деструктор автоматически вызывает функцию-член close.

Потоки текстовых файлов – это потоки, в которых флаг ios::binary не включен в режим их открытия. Эти файлы предназначены для хранения текста, и поэтому все значения, которые вводятся или выводятся из / в них, могут подвергаться некоторым преобразованиям форматирования, которые не обязательно соответствуют их буквальному двоичному значению. Операция записи в текстовый файл выполняется аналогично операции вывода на экран с использованием потока cout, за исключением того, что здесь используется файловый поток (листинг 7).

Листинг 7. Пример записи в текстовый файл

#include <iostream>

#include <conio.h>

#include <locale.h>

#include <fstream>

using namespace std;

int main () {

setlocale(LC_ALL, "rus");

ofstream myfile("example.txt");

if (myfile.is_open())

{

myfile << "Это линия.\n";

myfile << "Это другая линия.\n";

myfile.close();

}

else cout << "Невозможно открыть файл";

_getch();

return 0;

}

В этом примере проверяется, насколько успешным было открытие файла. Функция is_open возвращает значение bool, равное true в том случае, если объект потока действительно связан с открытым файлом, или false в противном случае.

В листинге 8 демонстрируется пример открытия созданного в листинге 7 текстового файла и вывода его содержимого на экран. Файл читается построчно в цикле while при помощи функции getline. Значение, возвращаемое этой функцией, является ссылкой на сам потоковый объект, который как логическое выражение (проверяемое в заголовке цикла while) имеет значение true, если поток готов к дополнительным операциям, и false, если был достигнут конец файла либо произошла какая-то другая ошибка.

Листинг 8. Пример чтения из текстового файла

#include <iostream>

#include <conio.h>

#include <locale.h>

#include <fstream>

#include <string>

using namespace std;

int main () {

setlocale(LC_ALL, "rus");

string line;

ifstream myfile ("example.txt");

if (myfile.is_open())

{

while (getline (myfile, line))

{

cout << line << '\n';

}

myfile.close();

}

else cout << "Невозможно открыть файл";

_getch();

return 0;

}

Следующие функции-члены необходимы для проверки определенных состояний потока (все они возвращают логическое значение bool):

bad()

Возвращает true, если операция чтения или записи завершается неудачно. Например, в случае, когда предпринята попытка записать данные в файл, который не открыт для записи, или если на устройстве, на котором осуществляется попытка записи, не осталось свободного места.

fail()

Возвращает true в тех же случаях, что и bad(), но также и в случае ошибки форматирования, например, когда извлекается алфавитный символ, а ожидается целое число.

eof()

Возвращает true, если файл, открытый для чтения, достиг конца.

good()

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

Функция-член clear() может использоваться для сброса флагов состояния.

Таким образом, работа с файлами позволяет упростить манипуляцию данными в языке C++. Доступ к файлам и работа с ними организованы довольно просто: сначала осуществляются открытие или создание файлов с определенными флагами, затем с ними производятся некоторые операции, после чего файлы закрываются.

4. Проектирование приложения

Целью курсовой работы является реализация прикладной программы, осуществляющей обработку файлов данных, на языке программирования высокого уровня C++. В рамках данной работы в качестве предметной области выбрана информация о сотрудниках некоторого предприятия. Обрабатываемые файлы, таким образом, представляют собой текстовые документы – каждая строка такого документа содержит набор атрибутов (данные), описывающих сотрудника (фамилия и имя, знак зодиака, дата рождения).

Для достижения поставленной цели в работе была выполнена программа на языке С++ в виде консольного приложения. В главной функции main() реализовано меню пользователя, в котором каждому действию соответствует определенная цифра. Реализованы следующие функции для работы с данными: добавление, редактирование, удаление записи, поиск сотрудников по фамилии, сохранение данных в текстовый файл и ввод данных из файла, вывод на экран.

Для хранения данных о сотруднике был спроектирован класс, поля которого позволяют управлять всеми атрибутами сотрудника. Кроме того, реализованы функции-члены для ввода и вывода данных. Исходный код класса приведен в листинге 9.

Листинг 9. Класс ZNAK, описывающий сотрудника некоторого предприятия

class ZNAK

{

public:

string name; // фамилия, имя

string zodiak; // знак зодиака

int birthday[3]; // дата рождения

ZNAK(){}

void inputData()

{

cin.ignore(cin.rdbuf()->in_avail());

string name, surname;

cout << "Введите фамилию: ";

getline(cin, surname);

while (cin.fail() || isEmpty(surname))

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Введите фамилию: ";

getline(cin, surname);

}

cout << "Введите имя: ";

getline(cin, name);

while (cin.fail() || isEmpty(name))

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Введите имя: ";

getline(cin, name);

}

this->name = surname + " " + name;

this->name = std::string(RUS(this->name.c_str()));

cout << "Введите знак зодиака (строчными буквами): ";

getline(cin, this->zodiak);

this->zodiak = std::string(RUS(this->

zodiak.c_str()));

while (cin.fail() || isEmpty(this->zodiak) || zodiaks.find(this->

zodiak) == zodiaks.end())

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Введите знак зодиака (строчными буквами): ";

getline(cin, this->zodiak);

this->zodiak = std::string(RUS(this->

zodiak.c_str()));

}

cout << "День рождения: ";

cin >> this->birthday[0];

while (cin.fail() || this->birthday[0] < 1 || this->birthday[0] > 31)

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. День рождения: ";

cin >> this->birthday[0];

}

cout << "Месяц рождения: ";

cin >> this->birthday[1];

while (cin.fail() || this->birthday[1] < 1 || this->birthday[1] > 12)

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Месяц рождения: ";

cin >> this->birthday[1];

}

cout << "Год рождения: ";

cin >> this->birthday[2];

while (cin.fail() || this->birthday[2] < 1900 || this->birthday[2] > 2019)

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Год рождения: ";

cin >> this->birthday[2];

}

}

void printData(ostream &os)

{

os << this->name << " " << this->zodiak << " " << this->birthday[0] << "." << this->birthday[1] << "." << this->birthday[2] << endl;

}

};

Здесь функция-член класса inputData осуществляет ввод информации о сотруднике с клавиатуры, а функция-член printData позволяет вывести данные на экран (при использовании оператора cout в качестве параметра функции) или в файл (если используется объект класса ofstream). Кроме того, обрабатываются накладываемые на используемые типы данных ограничения:

  • имя и фамилия сотрудника не могут быть пустыми или состоять только из пробельных символов;
  • название знака зодиака должно быть существующим (то есть являться элементом множества допустимых значений, которое представлено контейнером стандартной библиотеки языка set);
  • день рождения должен быть целым числом от 1 до 31, месяц – от 1 до 12, год – от 1900 до 2019.

При недопустимом формате введенных данных предусмотрена выдача сообщений об ошибках и повторный ввод. Все необходимые проверки корректности ввода осуществляются в заголовках цикла while.

Файл может быть сохранен на диске в текстовом формате в кодировке cp1251 (ANSI). Для корректного отображения кириллических символов считанных данных на экране или в сохраняемом текстовом файле (при открытии его в текстовом редакторе) используется функция RUS (листинг 10), осуществляющая перекодировку в ANSI из кодировки cp866.

Листинг 10. Функция для конвертирования символов

char *RUS(const char *str)

{

static char buf[512];

OemToCharA(str, buf);

return buf;

}

Для работы с большим объемом данных (информации о нескольких сотрудниках) используется однонаправленный линейный список, каждый узел которого состоит из поля данных (представленного объектом класса ZNAK из листинга 9) и указателя на следующий элемент списка (рисунок 1). Исходный код класса, на базе которого реализован список, представлен в листинге 11. Внутри класса также находятся функции для поиска сотрудника и вывода данных на экран или в файл.

Рисунок 1 – Схема линейного односвязного списка

Листинг 11. Класс List для работы с информацией о сотрудниках

class List

{

public:

ZNAK data;

List* next;

List(){}

void PrintList(ostream &os)

{

List* p = this;

while (p != NULL)

{

p->data.printData(os);

p = p->next;

}

}

List* FindElem(ZNAK elem)

{

List* p = this;

while (p != NULL)

{

if ((p->data.name.compare(elem.name) == 0) && (p->data.zodiak.compare(elem.zodiak) == 0)

&& (p->data.birthday[0] == elem.birthday[0]) && (p->data.birthday[1] == elem.birthday[1])

&& (p->data.birthday[2] == elem.birthday[2]))

break;

p = p->next;

}

return p;

}

void FindElem(string sur)

{

List* p = this;

bool f = false;

while (p != NULL)

{

string surname = p->data.name.substr(0, p->

data.name.find(" "));

if (surname.compare(sur) == 0)

{

f = true;

p->data.printData(cout);

}

p = p->next;

}

if (!f)

cout << "Данные не найдены" << endl;

}

};

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

5. Тестирование разработанной программы

Результатом тестирования приложения является набор тестов, на которых была проверена работа спроектированной программы, в виде входных текстов, предъявленных программе, и текстов ее ответных реакций. Для демонстрации программы необходимо подготовить тестовый набор данных. Объем этих данных должен быть достаточным для демонстрации всех функций разработанной программы с учетом проверок и обработки исключительных ситуаций [a.7.]. В общем виде необходим набор тестов (ручное тестирование), на которых была проверена работа спроектированной программы. В данной работе тестирование приводится в виде набора снимков экрана с комментариями.

Для тестирования программы необходимо запустить файл ProjectZNAK.exe. На экране появится окно консольного приложения, в котором отображаются пункты меню (рисунок 2). При нажатии на клавиатуре на определенную цифру выполняется соответствующая функция.

Рисунок 2 – Вид окна программы при запуске

При попытке вывести содержимое списка программа сообщит об ошибке, поскольку список пуст, после чего предложит выбрать команду меню еще раз (рисунок 3).

Рисунок 3 – Вид окна программы при попытке печати пустого списка

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

Для того, чтобы иметь возможность манипулировать данными входного файла, необходимо считать информацию из файла в список. Рисунок 5 иллюстрирует процесс чтения файла «people.txt» с некоторыми данными о сотрудниках и добавления этих данных в список.

Рисунок 4 – Вид окна программы после добавления элемента в список

Рисунок 5 – Вид окна программы после чтения данных из входного файла

Добавление новых данных при чтении из входного файла выполняется в конец списка. При этом строки, не являющиеся набором атрибутов, корректно описывающих сотрудника, просто игнорируются. Для того, чтобы в этом убедиться, достаточно посмотреть на содержимое входного файла «peoples.txt» (рисунок 6) и на вид списка после выполнения команды вывода на экран, который приведен на рисунке 7.

Рисунок 6 – Содержимое входного файла "peoples.txt"

Рисунок 7 – Вид окна программы после вывода списка сотрудников на экран

На рисунке 8 продемонстрирован вид окна программы после добавления одной записи в начало списка и печати списка перед удалением одной из записей (на экране шесть записей) и после него (пять записей на экране).

Вид окна программы после поиска по фамилии проиллюстрирован на рисунке 9.

Рисунок 8 – Вид окна программы после удаления элемента из списка и печати списка

Рисунок 9 – Вид окна программы после поиска сотрудника по фамилии

Вид окна программы в процессе редактирования последней добавленной записи и после вывода отредактированного списка на экран представлен на рисунке 10. Как следует из рисунка, запись «Петров Иннокентий весы 8.10.1999» была заменена на «Абрамов Станислав стрелец 5.12.1998».

Рисунок 10 – Вид окна программы после редактирования информации о сотруднике и вывода списка на экран

На рисунке 11 приведен вид окна программы и содержимого файла «db.txt» после ввода команды сохранения списка в этот файл.

Рисунок 11 – Вид окна программы и содержимого выходного файла после сохранения списка

Таким образом, тестирование, выполненное для программы целиком, продемонстрировало корректное взаимодействие всех функций разработанного приложения.

ЗАКЛЮЧЕНИЕ

В рамках курсовой работы на языке программирования высокого уровня C++ было разработано приложение для предметной области «Информация о сотрудниках», выполняющее ряд функций: программа позволяет вводить информацию, осуществлять поиск, модификацию и удаление данных. Предусмотрена возможность сохранения данных в файл и их чтение из файла. Программа имеет понятный консольный интерфейс, выводит все необходимые пояснения и подсказки, является законченной и удобной для использования. Таким образом, цель курсовой работы достигнута в полном объеме.

В ходе работы были выполнены все поставленные задачи:

  • получено представление об основных принципах объектно-ориентированного программирования;
  • выявлены ключевые особенности проектирования классов, позволяющие вводить информацию, хранить её в файле, а также осуществлять модификацию данных;
  • выполнена реализация и отладка программы обработки файлов данных, а также проведено ее тестирование. По результатам тестов получен вывод о том, что все необходимые функции работают корректно.

СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ

        1. Ключарев А.А., Матьяш В.А., Щекин С.В. Структуры и алгоритмы обработки данных: Учебное пособие / СПбГУАП. СПб., 2004.
        2. Колдаев В.Д. Основы алгоритмизации и программирования: Учебное пособие / Колдаев В.Д; Под ред. проф.Л.Г. Гагариной - М.: ИД ФОРУМ, НИЦ ИНФРА-М, 2016. - 416 с.
        3. Павловская Т. А. C/C++. Программирование на языке высокого уровня: учебник. СПб. : ПИТЕР, 2007. - 461 с.
        4. Страуструп, Б. Язык программирования C++ [Текст] = The C++ Programming Language : специальное издание / Б. Страуструп ; пер.: С. Анисимов, М. Кононов ; ред.: Ф. Андреев, А. Ушаков. - [Б. м.] : Бином-Пресс, 2008. - 1098 с.
        5. Кнут, Д. Искусство программирования [Текст] = The art of computer programming: [в 3 т.]. Т. 1. Основные алгоритмы / Д. Кнут ; ред. Ю. В. Козаченко. - 3-е изд. - М. : Вильямс, 2014. - 720 с.
        6. Демидович, Е.М. Основы алгоритмизации и программирования. Язык СИ [Текст] : учебное пособие / Е. М. Демидович. - 2-е изд., испр. и доп. - СПб. : БХВ - Петербург, 2008. - 440 с.
        7. Вирт, Н Алгоритмы и структуры данных. Новая версия для Оберона + CD [Текст] / Н. Вирт ; пер. Д. Б. Подшивалов. - 2-е изд., испр. - М. : ДМК Пресс, 2012. - 272 с.

Приложение 1. Исходный код программы

//Работа с линейным однонаправленным списком

#define _CRTDBG_MAP_ALLOC

#include <stdlib.h>

#include <crtdbg.h>

#ifdef _DEBUG

#ifndef DBG_NEW

#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )

#define newDBG_NEW

#endif

#endif

#include <iostream>

#include <string>

#include <set>

#include <iomanip>

#include <sstream>

#include <fstream>

#include <windows.h>

using namespace std;

// вспомог. функция для проверки строки на пустоту

bool isEmpty(const string& str) { return str.find_first_not_of(' ') == str.npos || str.empty(); }

// вспомог. функция для перекодировки из cp866 в ANSI (при сохранении в файл и выводе на экран)

char *RUS(const char *str)

{

static char buf[512];

OemToCharA(str, buf);

return buf;

}

// множество, содержащее названия знаков зодиака

set<string> zodiaks = { "овен", "телец", "близнецы", "рак", "лев", "дева", "весы",

"скорпион", "стрелец", "козерог", "водолей", "рыбы" };

class ZNAK

{

public:

string name; // фамилия, имя

string zodiak; // знак зодиака

int birthday[3]; // дата рождения

ZNAK() {}

// Ввод данных - фамилия, имя, знак зодиака и дата рождения

void inputData()

{

// проверки добавляются в соответствии с заданной предметной областью

cin.ignore(cin.rdbuf()->in_avail());

string name, surname;

cout << "Введите фамилию: ";

getline(cin, surname);

while (cin.fail() || isEmpty(surname))

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Введите фамилию: ";

getline(cin, surname);

}

cout << "Введите имя: ";

getline(cin, name);

while (cin.fail() || isEmpty(name))

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Введите имя: ";

getline(cin, name);

}

this->name = surname + " " + name;

this->name = std::string(RUS(this->name.c_str()));

cout << "Введите знак зодиака (строчными буквами): ";

getline(cin, this->zodiak);

this->zodiak = std::string(RUS(this->zodiak.c_str()));

while (cin.fail() || isEmpty(this->zodiak) || zodiaks.find(this->zodiak) == zodiaks.end())

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Введите знак зодиака (строчными буквами): ";

getline(cin, this->zodiak);

this->zodiak = std::string(RUS(this->zodiak.c_str()));

}

cout << "День рождения: ";

cin >> this->birthday[0];

while (cin.fail() || this->birthday[0] < 1 || this->birthday[0] > 31)

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. День рождения: ";

cin >> this->birthday[0];

}

cout << "Месяц рождения: ";

cin >> this->birthday[1];

while (cin.fail() || this->birthday[1] < 1 || this->birthday[1] > 12)

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Месяц рождения: ";

cin >> this->birthday[1];

}

cout << "Год рождения: ";

cin >> this->birthday[2];

while (cin.fail() || this->birthday[2] < 1900 || this->birthday[2] > 2019)

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите значение повторно. Год рождения: ";

cin >> this->birthday[2];

}

}

// вспомог. функция для вывода одной записи в поток os

void printData(ostream &os)

{

os << this->name << " " << this->zodiak << " " << this->birthday[0]

<< "." << this->birthday[1] << "." << this->birthday[2] << endl;

}

};

class List

{

public:

ZNAK data; // информационное поле (данные) узла списка

List* next; // указатель на следующий узел списка

List(){}

// Вывод элементов списка на экран или в файл

void PrintList(ostream &os)

{

List* p = this;

while (p != NULL)

{

p->data.printData(os);

p = p->next;

}

}

// Поиск элемента в списке по имени, знаку зодиака, дате рождения

List* FindElem(ZNAK elem)

{

List* p = this;

while (p != NULL)

{

if ((p->data.name.compare(elem.name) == 0) && (p->data.zodiak.compare(elem.zodiak) == 0)

&& (p->data.birthday[0] == elem.birthday[0]) && (p->data.birthday[1] == elem.birthday[1])

&& (p->data.birthday[2] == elem.birthday[2]))

break;

p = p->next;

}

return p;

}

// Поиск элемента в списке по фамилии

void FindElem(string sur)

{

List* p = this;

bool f = false;

while (p != NULL)

{

string surname = p->data.name.substr(0, p->data.name.find(" "));

if (surname.compare(sur) == 0)

{

f = true;

p->data.printData(cout);

}

p = p->next;

}

if (!f)

cout << "Данные не найдены" << endl;

}

};

// Редактирование списка

void UpdateElem(List** ptr, ZNAK r2)

{

List* p = *ptr;

p->data.birthday[0] = r2.birthday[0];

p->data.birthday[1] = r2.birthday[1];

p->data.birthday[2] = r2.birthday[2];

p->data.name = r2.name;

p->data.zodiak = r2.zodiak;

}

// Добавление элемента в конец списка

void AddElem(List** begin, List** cur, ZNAK elem)

{

List* p = new List;

p->data = elem; //проверка, является ли список пустым

if (*begin == NULL)

{

p->next = NULL;

*begin = p;

}

else

{

p->next = (*cur)->next;// или p->next = NULL;

(*cur)->next = p;

}

*cur = p;

}

// Добавление элемента в начало списка

void AddFirstElem(List** begin, ZNAK elem)

{

List* p = new List;

p->data = elem; //проверка, является ли список пустым

if (*begin == NULL)

{

p->next = NULL;

}

else

{

p->next = *begin;

}

*begin = p;

}

// Удаление элемента из списка

void DelElem(List** begin, List* ptrCur)

{

List* p;

if (ptrCur == *begin)

{

// удаляем первый элемент

*begin = (*begin)->next;

}

else

{

// устанавливаем вспомогательный указатель на элемент, предшествующий удаляемому

p = *begin;

while (p->next != ptrCur)

p = p->next; // удаление элемента

p->next = ptrCur->next;

}

delete ptrCur;

}

// Очистка памяти

void Free(List** begin)

{

if (*begin == 0)

return;

List* p = *begin;

List* t;

while (p)

{

t = p;

p = p->next;

delete t;

}

*begin = NULL;

}

int main()

{

List* head = NULL;

List* cur = NULL;

setlocale(LC_CTYPE, "Russian");

int n = -1;

ZNAK r;

// Меню пользователя

while (n != 0)

{

cout << endl << "1 - Добавить в конец" << endl <<

"2 - Вывод списка" << endl <<

"3 - Добавить в начало" << endl <<

"4 - Поиск" << endl <<

"5 - Поиск по фамилии" << endl <<

"6 - Удаление" << endl <<

"7 - Редактировать" << endl <<

"8 - Сохранить в файл" << endl <<

"9 - Считать из файла" << endl <<

"0 - Выход\nВыберите действие: ";

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cin >> n;

while (cin.fail() || (n < 0) || (n > 9))

{

cin.clear();

cin.sync();

cout << "Ошибка ввода, выберите действие: ";

cin >> n;

}

switch (n)

{

case 1:

{

r.inputData();

AddElem(&head, &cur, r);

break;

}

case 2:

{

if (head)

head->PrintList(cout);

else

cout << "Нет данных!" << endl;

break;

}

case 3:

{

r.inputData();

AddFirstElem(&head, r);

break;

}

case 4:

{

if (!head)

{

cout << "Нет данных!" << endl;

break;

}

List* ptr;

r.inputData();

ptr = head->FindElem(r);

if (ptr == NULL)

cout << "Запись не найдена!" << endl;

else

ptr->data.printData(cout);

break;

}

case 5:

{

if (!head)

{

cout << "Нет данных!" << endl;

break;

}

string surname;

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите фамилию: ";

getline(cin, surname);

surname = std::string(RUS(surname.c_str()));

head->FindElem(surname);

break;

}

case 6:

{

if (!head)

{

cout << "Нет данных!" << endl;

break;

}

List* ptr;

r.inputData();

ptr = head->FindElem(r);

if (ptr == NULL)

cout << "Запись не найдена!" << endl;

else

{

DelElem(&head, ptr);

cout << "Запись удалена!" << endl;

};

break;

}

case 7:

{

if (!head)

{

cout << "Нет данных!" << endl;

break;

}

List* ptr;

r.inputData();

ptr = head->FindElem(r);

if (ptr == NULL)

cout << "Запись не найдена!" << endl;

else

{

ZNAK r2;

r2.inputData();

UpdateElem(&ptr, r2);

cout << "Запись отредактирована!" << endl;

};

break;

}

case 8:

{

string fname;

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите имя файла для сохранения списка: ";

getline(cin, fname);

ofstream file;

file.open(fname);

while (!file.is_open())

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Невозможно открыть файл для сохранения списка! Введите имя файла для сохранения списка: ";

getline(cin, fname);

}

if (head)

{

head->PrintList(file);

cout << "Список сохранен в файл!" << endl;

}

else

file << "Нет данных!" << endl;

file.close();

break;

}

case 9:

{

string fname;

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Введите имя файла для открытия списка: ";

getline(cin, fname);

ifstream file;

file.open(fname);

while (!file.is_open())

{

cin.clear();

cin.ignore(cin.rdbuf()->in_avail());

cout << "Невозможно открыть файл для открытия списка! Введите имя файла для открытия списка: ";

getline(cin, fname);

}

char dot;

string s;

ZNAK r;

while (getline(file, s))

{

istringstream ss(s);

string surname, name, zodiak;

int b1, b2, b3;

if (ss >> surname >> name >> zodiak >> b1 >> dot >> b2 >> dot >> b3)

{

name = string(RUS(surname.c_str())) + " " + string(RUS(name.c_str()));

zodiak = string(RUS(zodiak.c_str()));

if (!isEmpty(surname) && !isEmpty(name) && !isEmpty(zodiak) && zodiaks.find(zodiak) != zodiaks.end()

&& (b1 >= 1) && (b1 <= 31) && (b2 >= 1) && (b2 <= 12) && (b3 >= 1900) && (b3 <= 2019))

{

r.name = name;

r.zodiak = zodiak;

r.birthday[0] = b1;

r.birthday[1] = b2;

r.birthday[2] = b3;

AddElem(&head, &cur, r);

}

}

}

cout << "Список получен из файла!" << endl;

file.close();

break;

}

}

}

Free(&head);

_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);

_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);

_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);

_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);

_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);

_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);

_CrtDumpMemoryLeaks();

system("pause");

return 0;

}