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

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

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

Добавлен: 25.05.2023

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

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

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

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

#include <fstream>

#include <iostream>

#include <vector>

using namespace std;

int main ()

{

ifstream file_reader("highscores.txt");

if (! file_reader.is_open())

{

cout << “Could not open file!" << '\n';

}

vector<int> scores;

for (int i = 0; i < 10; i++)

{

int score;

if(! (file_reader >> score))

{

breack;

}

scores.push_back(score);

}

}

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

Иногда требуется считывать из файла все данные. В таких случаях необхо­димо отличать ошибки чтения, обусловленные достижением конца файла, от ошибок, вызванных некорректностью файла. Метод eof определяет, достигнут ли конец файла. Можно написать цикл, который считывает максимально возможное количество данных и проверяет результат чтения, пока не происходит ошибка. Затем следует проверить значение, возвра­щаемое методом eof: если оно равно true, находимся в конце файла, в противном случае произошла ошибка. Выяснить причину ошибки можно с помощью метода fail, который возвращает true, если введенные данные некорректны или возникла проблема считывания данных с устройства. После того как конец файла достигнут, нужно вызвать метод clear, чтобы продолжить выполнение операций над файлом [13, с.361][13].

Существует еще одно важное различие между ситуациями, когда данные считываются из файлов и когда их вводит пользователь. Что произойдет, если мы включим в список рекордов не только количество очков, но и имя игрока? Нам потребуется считать из файла как количество очков, так и имя игрока, а для этого необходимо соответствующим образом изменить код. Предыдущие версии программы не смогут считывать файл нового формата. Если много пользователей, изменение формата файла может повлечь серьезные проблемы. Существуют методы обеспечения буду­щей совместимости файла, которые основаны на использовании в файле дополнительных полей или игнорировании новых элементов формата в устаревших версиях программы. Изучение этих методов не относится к теме этого исследования, просто нужно иметь в виду, что определение формата файла является гораздо важнее определения базового интерфейса [14, с.59][14].


Глава 4. Запись в файлы. Позиция в файле

Для записи в файлы применяется тип ofstream (сокращение от output file stream - исходящий файловый поток). Этот тип почти идентичен типу ifstream с единственным исключением: он используется аналогично не объекту cin, а объекту cout.

Рассмотрим простую программу, которая выводит числа от 0 до 9 в файл highscores.txt (этот код в дальнейшем создаст нам нечто более похожее на таблицу рекордов).

#include <fstream>

#include <iostream>

#include <cstdlib>

using namespace std;

int main ()

{

ofstream file_writer("highscores.txt");

if (! file_writer.is_open())

{

cout << "Could not open file!” << *\n';

return 0;

}

// поскольку у нас нет реальных рекордов, мы выведем

// числа от 10 до 1

for (int i = 0; i < 10; i++)

{

file_writer << 10 - i << '\n';

}

}

Здесь не нужно беспокоиться о том, что мы достигли конца файла. Когда при записи в файл вы попадаете в его конец, объект ofstream расширяет файл. Эта операция называется добавлением в конец файла.

Создание нового файла.

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

Конструктор типа ofstream принимает второй аргумент, который определяет метод обработки файла (табл. 1):

Таблица 1 – Методы обработки файла

Аргумент

Метод

ios::app

Добавляет данные в конец файла; после каждой операции записи текущая позиция устанавливается в конец файла

ios::ate

Устанавливает текущую позицию в конец файла

ios::trunc

Удаляет все содержимое файла

ios::out

Разрешает вывод в файл

ios::binary

Разрешает выполнение двоичных операций над потоком

Чтобы воспользоваться несколькими методами одновременно, можно объединять их с помощью вертикальной черты (|):


ofstream a_file(“test.txt”, ios::app | ios::binary);

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

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

При выполнении базовых операций нет необходимости уделять особое внимание позиции в файле: код может просто считывать или записывать очередной фрагмент данных. Тем не менее можно изменять позицию в файле, не выполняя операцию чтения. Это часто необходимо при работе с файлами, которые хранят сложные структуры данных, например, ZIP или PDF, и с файлами, считывание каждого байта из которых занимает много времени или невозможно (например, при реализации базы данных) [11, с.265][15].

Фактически в файле существуют две позиции, одна из которых определяет место для следующей операции чтения, а другая - для следующей операции записи. Узнать текущую позицию для чтения можно с помощью метода tellg (g означает get - получение, чтение данных), а текущую позицию для записи - с помощью метода tellp (р означает put - раз­мещение, запись данных).

Новую позицию в файле также можно задать, перемещаясь от текущей позиции с помощью методов seekp и seekg. Перемещение по файлу назы­вается поиском (это отражено в названиях методов, которые происходят от слова seek - искать). При поиске в файле позиция чтения или записи перемещается на новое место. Оба указанных метода принимают два па­раметра: расстояние и начало поиска. Расстояние измеряется в байтах, а началом поиска является текущая позиция, начало или конец файла. По­сле поиска мы можем считывать или записывать данные в файл, начиная с новой позиции. Изменение одной позиции с помощью операции поиска не влияет на другую позицию.

Позиция в файле задается с помощью одного из трех флагов (табл. 2):

Таблица 2 – Флаги, задающие позицию в файле

Аргумент

Метод

ios_base::beg

Поиск от начала файла

ios_base::cur

Поиск от текущей позиции

ios_base::end

Поиск от конца файла

Например, чтобы переместить текущую позицию в начало файла перед тем, как начать запись в него, можно написать следующее:

file_writer.seekp(0, ios_base::beg);


Значение, возвращаемое методами tellp и tellg, представляет собой особый тип переменной - streampos, который определен в стандартной библиотеке. Значение можно преобразовать в целое число и обратно, одна­ко использование streampos явно задает тип данных. Целое число можно использовать где угодно, а у типа streampos есть конкретное назначение. Переменные streampos хранят позиции внутри файлов и используются для установки указателя на эти позиции. Задавая переменным подходящий тип, мы четко указываем их назначение.

streampos pos = file_reader.tellg();

Иногда перемещать текущую позицию в файле не требуется: достаточно просто считать файл от начала до конца. Тем не менее многие форматы файлов оптимизированы для добавления в них новых данных. Новые данные добавляются в конец файла гораздо быстрее, чем в середину. Когда данные добавляются в середину файла, необходимо сдвигать все его со­держимое, находящееся после места вставки, так же как при добавлении нового элемента в середину массива.

Дополним написанную ранее программу, которая работает с таблицей рекордов, возможностью добавления нового рекорда в файл. Поскольку для этого необходимо как считывать данные из файла, так и записывать их, мы воспользуемся классом fstream, который поддерживает обе опе­рации (можно считать, что он объединяет возможности классов ofstream и ifstream). Сначала считаем новый рекорд, который вводит пользо­ватель, а затем будем считывать каждую строку файла, пока не найдем рекорд меньше введенного. В этом месте вставим новый рекорд. Сохраним эту позицию и вернемся к ней, считав все оставшиеся строки файла в вектор. Запишем в файл новый рекорд, а затем все остальные рекорды, которые уже были в файле.

int main ()

{

fstream file (

"highscores.txt",

ios::in | ios::out

);

if (! file.is_open())

{

cout << “Could not open file!" << ‘\n’;

return 0;

}

int new_high_score;

cout << “Enter a new high score: ";

сin >> new_high_score;

streampos pre_score_pos = file.tellg();

int cur_score;

while (file >> cur_score)

{

if (cur_score < new_high_score)

{

break;

}

pre_score_pos = file.tellg();

}

if (file.fail() && ! file.eof())

{

cout << "Bad score/read--exiting";

return 0;

}

file.clear();

file.seekg(pre_score_pos);

vector<int> scores;

while (file >> cur_score)

{

scores.push_back(cur_score);

}

if (! file.eof())

{

cout << "Bad score/read--exiting";


return 0;

}

file.clear();

file.seekp(pre_score_pos);

if (pre_score_pos != std::streampos(0))

{

file << endl;

}

file << new_high_score << endl;

for (vector<int>::iterator itr - scores.begin();

itr != scores.end();

++itr)

{

file << *itr << endl;

}

}

II. Практическая часть

Глава 5. Применение механизмов Visual C++ для работы с текстовыми файлами на практике

Для выполнения практического задания реализуем описанный в предыдущих главах пример работы с файлом результатов соревнований в среде Visual Studio 2017.

Запустим Visual Studio 2017 и создадим проект консольного приложения в Visual C++ (рис. 1-2).

Рисунок 1 – Среда разработки Visual Studio 2017

Для создания проекта консольного приложения выберем пункт меню «Файл/Создать/Проект» и в диалоговом окне выберем «Консольное приложение Windows» [4, с.26][16].

В графе «Имя» зададим название приложения «HightScores».

В графе «Расположение» зададим путь к проекту «d:\Projects».

В графе «Имя решения» зададим наименование решения «HightScores».

Рисунок 2 – Выбор консольного приложения

Для создания консольного приложения «HightScores» кликнем на кнопку «Ok» диалогового окна. Созданный проект отобразится в редакторе кода (рис. 3).

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

Рисунок 3 – Созданный проект

Далее создаем файл «scores.txt» и записываем в него данные от 15 до 1 (рис. 4).

Листинг 1 – Код заполнения файла данными

// создаем файл и записываем в него данные от 1 до 15

ofstream file_writer("scores.txt");

if (!file_writer.is_open())

{

cout << "Could not open file!" << '\n';

return 0;

}

for (int i = 0; i < 15; i++)

{

file_writer << 15 - i << '\n';

}

Рисунок 4 – Создание и заполнение файла

Далее, читаем данные из файла «scores.txt» (рис. 5).

Листинг 2 – Код чтения данных из файла и сохранение их в векторе

// чтение данных из файла

ifstream file_reader("scores.txt");

if (!file_reader.is_open())

{

cout << "Could not open file!" << '\n';