Добавлен: 03.04.2023
Просмотров: 390
Скачиваний: 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 – Вид окна программы после удаления элемента из списка и печати списка