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

ВУЗ: Не указан

Категория: Курсовая работа

Дисциплина: Не указана

Добавлен: 03.04.2023

Просмотров: 391

Скачиваний: 5

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Поскольку первая задача, выполняемая в файловом потоке, как правило, заключается в открытии файла, эти три класса включают конструктор, который автоматически вызывает функцию-член 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 – Вид окна программы после удаления элемента из списка и печати списка