Опубликован: 07.03.2015 | Уровень: для всех | Доступ: свободно | ВУЗ: Компания ALT Linux
Лекция 7:

Организация ввода-вывода в C++

< Лекция 6 || Лекция 7: 12345 || Лекция 8 >

7.2 Работа с текстовыми файлами в C++

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

Для работы с файлами используются специальные типы данных, называемые потоками4Вообще говоря, потоки являются классами, которым будет посвящена специальная глава..Поток ifstream служит для работы с файлами в режиме чтения. Поток ofstream служит для работы с файлами в режиме записи. Для работы с файлами в режиме как чтения, так и записи служит поток iofstream.

В программах на C++ при работе с текстовыми файлами необходимо подключать библиотеки iostream и fstream.

Для того, чтобы записать данные в текстовый файл, необходимо:

  1. Описать переменную типа ofstream.
  2. Открыть файл с помощью функции open.
  3. Вывести информацию в файл.
  4. Закрыть файл.

Для того, чтобы считать данные из текстового файла, необходимо:

  1. Описать переменную типа ifstream.
  2. Открыть файл с помощью функции open.
  3. Считать информацию из файла, при считывании каждой порции данных необходимо проверять достигнут ли конец файла.
  4. Закрыть файл.

7.2.1 Запись информации в текстовый файл

Для того, чтобы начать работать с текстовым файлом, необходимо описать переменную типа ofstream. Например, с помощью оператора

ofstream F;5Далее термин "поток" будет использоваться для указания переменной, описанной как ofstream, ifstream, iofstream, а термин "файл" для указания реального файла на диске.

будет создана переменная F для записи информации в файл. На следующем этапе файл необходимо открыть для записи. В общем случае оператор открытия потока будет иметь вид:

F.open("file", mode);

Здесь F — переменная, описанная как ofstream, file — имя файла на диске, mode — режим работы с открываемым файлом.

Файл может быть открыт в одном из следующих режимов:

  • ios::in — открыть файл в режиме чтения данных, этот режим является режимом по умолчанию для потоков ifstream;
  • ios::out — открыть файл в режиме записи данных (при этом информация в существующем файле уничтожается), этот режим является режимом по умолчанию для потоков ofstream;
  • ios::app — открыть файл в режиме записи данных в конец файла;
  • ios::ate — передвинуться в конец уже открытого файла;
  • ios::trunc — очистить файл, это же происходит в режиме ios::out;
  • ios::nocreate — не выполнять операцию открытия файла, если он не существует6При открытии файла в режиме ios::in происходит как раз обратное, если файл не существует, он создаётся;
  • ios::noreplace — не открывать существующий файл.

Параметр mode может отсутствовать, в этом случае файл открывается в режиме по умолчанию для данного потока7ios::in — для потоков ifstream, ios::out — для потоков ofstream..

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

Открыть файл (в качестве примера возьмём файл abc.txt) в режиме записи можно одним из следующих способов:

//Первый способ.
ofstream f;
f.open ( " abc.txt ", ios::out );
//Второй способ,
//режим ios::out является режимом по умолчанию для потока ofstream
ofstream f;
f.open ( " abc.txt " )
//Третий способ объединяет описание переменной типа поток
//и открытие файла в одном операторе
ofstream f( " abc.txt ", ios::out );

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

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

Например, для записи в поток F переменной a оператор вывода будет иметь вид:

F<<a;

Для последовательного вывода в поток G переменных b, c и d оператор вывода станет таким:

G<<b<<c<<d;

Закрытие потока осуществляется с помощью оператора:

F.close();

В качестве примера рассмотрим следующую задачу.

Задача 7.1. Создать текстовый файл abc.txt и записать туда n вещественных чисел.

Текст программы с комментариями:

#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
int main ( )
{
	int i, n; double a;
	ofstream f; //Описывает поток для записи данных в файл.
	f.open ( " abc.txt " ); //Открываем файл в режиме записи,
		//режим ios::out устанавливается по умолчанию.
	cout<<" n = "; cin>>n; //Ввод количества вещественных чисел.
	for ( i =0; i<n; i++)
	{
		cout<<" a = "; cin>>a; //Ввод очередного числа.
		if ( i<n-1) //Если число не последнее,
			f<<a<<" \t "; //записать в файл это число и символ табуляции, иначе
		else f<<a; //записать только число.
	}
	f.close ( ); //Закрытие потока.
return 0;
}

Обратите внимание, что в текстовый файл записываются не только вещественные числа, но и символы табуляции. Таким образом, в конце файла после последнего числа находится символ табуляции. По этой причине может возникнуть проблема при чтении информации (п. 7.2.2), так как символ табуляции будет интерпретирован как вещественное число. Чтобы этого избежать в программе была применена следующая конструкция:

if (i<n-1) f<<a<<"\t"; else f<<a;

Здесь реализована проверка ввода последнего числа. После него символ табуляции отсутствует.

В результате работы программы будет создан текстовый файл abc.txt, который можно просмотреть средствами обычного текстового редактора (рис. 7.1,рис. 7.2).

7.2.2 Чтение информации из текстового файла

Процесс работы программы к задаче 7.1. Ввод исходных данных.

Рис. 7.1. Процесс работы программы к задаче 7.1. Ввод исходных данных.
Текстовый файл abc.txt, созданный программой к задаче 7.1.

увеличить изображение
Рис. 7.2. Текстовый файл abc.txt, созданный программой к задаче 7.1.

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

ifstream F;
F.open("abc.txt", ios::in);

8Указание режима ios::in можно, конечно, опустить, ведь для потока ifstream значение ios::in является значением по умолчанию, тогда оператор open можно будет записать так F.open("abc.txt");

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

Например, для чтения из потока F в переменную a оператор ввода будет иметь вид:

F>>a;

Для последовательного ввода из потока G в переменные b, с и d оператор ввода станет таким:

G>>b>>c>>d;

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

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

f.eof();

Здесь F — имя потока, функция возвращает логическое значение: true — если достигнут конец файла, если не достигнут функция возвращает значение false.

Следовательно, цикл для чтения содержимого всего файла можно записать так.

while ( ! f.eof ( ) ) //Организован цикл, условием окончания цикла
//является достижение конца файла, в этом случае f.eof() вернёт true.
{
	F>>a; //Чтение очередного значения из потока F в переменную a.
	...
	<обработка значения переменной a>
}

Рассмотрим следующую задачу.

Задача 7.2. В текстовом файле abc.txt хранятся вещественные числа (рис. 7.2), вывести их на экран и вычислить их количество.

Текст программы с комментариями приведён ниже.

#include <iostream>
#include <fstream>
using namespace std;
int main ( )
{
	ifstream f; //Поток для чтения.
	float a; int n=0;
	f.open ( " abc.txt " ); //Открываем файл в режиме чтения.
	if ( f ) //Если открытие файла прошло корректно, то
	{
		while ( !f.eof ( ) ) //Организован цикл, выполнение цикла
		//прервётся, когда будет достигнут конца файла.
		{
			f>>a; //Чтение очередного значения из потока f в переменную a.
			cout<<a<<" \t "; //Вывод значения переменной a
			n++; //Увеличение количества считанных чисел.
		}
		f.close ( ); //Закрытие потока.
		cout<<" n = "<<n<<endl; //Вывод на экран количества чисел.
	}
	else cout<<"Файл не найден"<<endl; //Если открытие файла прошло некорректно, то
	//вывод сообщения, об отсутствии такого файла.
	return 0;
}

Результат работы программы к задаче 7.2:

3.14159 2.789 -21.14 543.89 -90.1 n=5

Программа работает корректно, если текстовый файл abc.txt был создан с помощью программы к задаче 7.1. Предположим, что файл создавался в текстовом редакторе, и пользователь ввёл после последней цифры символ пробела, табуляции или перехода на новую строку. Тогда результат будет таким:

3.14159 2.789 -21.14 543.89 -90.1 -90.1 n= 6

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

while ( !f.eof ( ) )
{
	f>>a;
	if ( !f.eof ( ) ) //Проверяем, достигнут ли конец файла, если нет — печатаем значение a
	{
	cout<<a<<" \t ";
	n++;
	}
}

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

#include <iostream>
#include <fstream>
using namespace std;
int main ( )
{
	ifstream f;
	float a; int i, n=5;
	f.open ( " abc.txt " );
	if ( f )
	{
		for ( i =1; i<=n; f>>a, cout<<a<<" \t ", i++);
			f.close ( );
	}
	else cout<<"Файл не найден"<<endl;
	return 0;
}

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

Задача 7.3. В файле abc.txt (рис. 7.2) хранится массив вещественных чисел, дописать в файл этот же массив, упорядочив его по возрастанию.

Алгоритм решения задачи очень простой. Считываем в массив данные из текстового файла, упорядочиваем массив, дописываем его в этот же файл.

Для чтения данных из файла описываем поток ifstream, открываем его в режиме чтения и последовательно, пока не достигнем конца файла, считываем все элементы в массив. Сортировку проведём методом пузырька. Обратите внимание, что поток нужно открыть так, чтобы была возможность дописать в конец файла упорядоченный массив.

Текст программы с комментариями приведён ниже.

#include <iostream>
#include <fstream>
using namespace std;
int main ( )
{
	ifstream f; //Поток для чтения.
	ofstream g; //Поток для записи.
	float *a, b;
	a=new float [ 100 ];
	int i, j, n=0;
	f.open ( " abc.txt ", ios::in ); //Открываем файл в режиме чтения.
	if ( f )
	{
		while ( !f.eof ( ) )
		{
			f>>a [ n ];
			n++;
		}
		//Сортировка массива.
		for ( i =0; i<n-1; i++)
			for ( j =0; j<n-i -1; j++)
			if ( a [ j ]>a [ j +1 ])
			{
				b=a [ j ];
				a [ j ]=a [ j + 1 ];
				a [ j +1]=b;
			}
		f.close ( ); //Закрываем поток для чтения.
		g.open ( " abc.txt ", ios::app ); //Открываем поток для того, чтобы дописать данные.
		g<<" \n "; //Запись в файл символа конца строки
		for ( i =0; i<n; i++) //Запись в файл
		if ( i<n-1) g<<a [ i ]<< " \t "; //элемента массива и символа табуляции.
		else g<<a [ i ]; //Запись последнего элемента массива
		g.close ( ); //Закрытие файла.
	}
	else cout<<"Файл не найден"<<endl;
	delete [ ] a;
	return 0;
}

Содержимое файла abc.txt после запуска программы к задаче 7.3

3.14159 2.798 -21.14 543.89 -90.1
-90.1 -21.14 2.798 3.14159 543.89
< Лекция 6 || Лекция 7: 12345 || Лекция 8 >
Сергей Радыгин
Сергей Радыгин

Символы кириллицы выводит некорректно. Как сделать чтобы выводился читабельный текст на русском языке?

Тип приложения - не Qt,

Qt Creator 4.5.0 основан на Qt 5.10.0. Win7.

 

Юрий Герко
Юрий Герко

Кому удалось собрать пример из раздела 13.2 Компоновка (Layouts)? Если создавать проект по изложенному алгоритму, автоматически не создается  файл mainwindow.cpp. Если создавать этот файл вручную и добавлять в проект, сборка не получается - компилятор сообщает об отсутствии класса MainWindow. Как правильно выполнить пример?