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

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

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

7.3 Обработка двоичных файлов

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

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

  1. Описать файловую переменную с помощью оператора FILE *filename; Здесь filename — имя переменной, где будет храниться указатель на файл.
  2. Открыть файл с помощью функции fopen.
  3. Записать информацию в файл с помощью функции fwrite.
  4. Закрыть файл с помощью функции fclose.

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

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

Рассмотрим основные функции, необходимые для работы с двоичными файлами.

Для открытия файла предназначена функция:

FILE *fopen(const *filename, const char *mode); где, filename — строка, в которой хранится полное имя открываемого файла, mode — строка, которая определяет режим работы с файлом; возможны следующие значения:

  • "rb" — открыть двоичный файл в режиме чтения;
  • "wb" — создать двоичный файл для записи, если файл существует, то его содержимое очищается.
  • "ab" — создать или открыть двоичный файл для добавления информации в конец файла;
  • "rb+" — открыть существующий двоичный файл в режиме чтения и записи;
  • "wb+" — открыть двоичный файл в режиме чтения и записи, существующий файл очищается;
  • "ab+" — двоичный файл открывается или создаётся для исправления существующей информации и добавления новой в конец файла.

Функция fopen возвращает в файловой переменной NULL в случае неудачного открытия файла.

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

Для закрытия файла предназначена функция

int fclose(FILE *filename);

Она возвращает 0 при успешном закрытии файла и NULL в противном случае.

Для удаления файлов существует функция

int remove(const char *filename);

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

Для переименования файлов предназначена функция

int rename(const char *oldfilename, const char *newfilename);

здесь первый параметр — старое имя файла, второй — новое. Возвращает 0 при удачном завершении программы.

Чтение из двоичного файла осуществляется с помощью функции

fread (void *ptr, size, n, FILE *filename)

Эта функция считывает из файла filename в массив ptr n элементов размера size. Функция возвращает количество считанных элементов. После чтения из файла указатель файла смещается на n*size байт.

Запись в двоичный файл осуществляется с помощью функции

fwrite (const void *ptr, size, n, FILE *filename);

Функция записывает в файл filename из массива ptr n элементов размера size. Функция возвращает количество записанных элементов. После записи информации в файл, указатель файла смещается на n*size байт.

Для контроля достижения конца файла есть функция

int feof(FILE * filename);

Она возвращает ненулевое значение, если достигнут конец файла.

Рассмотрим использование двоичных файлов на примере решения двух стандартных задач.

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

#include <iostream>
#include <fstream>
using namespace std;
int main ( )
{
	FILE * f; //Описание файловой переменной.
	int i, n; double a;
	f=fopen ( " abc.dat ", " wb " ); //Создание двоичного файла в режиме записи.
	cout<<" n = "; cin>>n; //Ввод числа n.
	fwrite (&n, sizeof ( int ), 1, f ); //Запись числа в двоичный файл.
	for ( i =0; i<n; i++) //Цикл для ввода n вещественных чисел.
	{
		cout<<" a = "; cin>>a; //Ввод очередного вещественного числа.
		fwrite (&a, sizeof ( double ), 1, f ); //Запись числа в двоичный файл.
	}
	fclose ( f ); //Закрыть файл.
	return 0;
}

Задача 7.5. Вывести на экран содержимое созданного в задаче 7.4 двоичного файла abc.dat.

#include <iostream>
#include <fstream>
using namespace std;
int main ( )
{
	FILE * f; //Описание файловой переменной.
	int i, n; double *a;
	f=fopen ( " abc.dat ", " rb " ); //Открыть существующий двоичный файл в режиме чтения.
	fread (&n, sizeof ( int ), 1, f ); //Читать из файла целое число в переменную n.
	cout<<" n = "<<n<<" \n "; //Вывод n на экран.
	a=new double [ n ]; //Выделение памяти для массива из n чисел.
	fread ( a, sizeof ( double ), n, f ); //Чтение n вещественных чисел из файла в массив a.
	for ( i =0; i<n; cout<<a [ i ]<<" \t ", i++); //Вывод массива на экран.
	cout<<endl;
	fclose ( f ); //Закрыть файл.
	return 0;
}

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

FILE * f;
int i, n; double a;
f=fopen ( " file.dat ", " rb " );
for ( i =0; i <15; fread (&a, sizeof ( double ), 1, f ), i++);
fclose ( f );
f=fopen ( " file.dat ", " rb " );
fread (&a, sizeof ( double ), 1, f );
fclose ( f );

Как видно, такое чтение чисел из файла, а затем повторное открытие файла — не самый удачный способ. Гораздо удобнее использовать функцию перемещения указателя файла к заданному байту:

int fseek(FILE *F, long int offset, int origin);

Функция устанавливает указатель текущей позиции файла F, в соответствии со значениями начала отсчёта origin и смешения offset. Параметр offset равен количеству байтов, на которые будет смещён указатель файла относительно начала отсчёта, заданного параметром origin. Если значение offset положительно, то указатель файла смещается вперёд, если отрицательно — назад. Параметр origin должен принимать одно из следующих значений, определённых в заголовке stdio.h:

  • SEEK_SET— отсчёт смещения offset вести с начала файла;
  • SEEK_CUR — отсчёт смещения offset вести с текущей позиции файла;
  • SEEK_END — отсчёт смещения offset вести с конца файла.

Функция возвращает нулевое значение при успешном выполнении операции и ненулевое, если возник сбой при выполнении смещения.

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

Задача 7.6. В созданном в задаче 7.4 двоичном файле abc.dat поменять местами наибольшее и наименьшее из вещественных чисел.

Алгоритм решения задачи состоит из следующих этапов:

  • Чтение вещественных чисел из файла в массив a.
  • Поиск в массиве a максимального (max) и минимального (min) значения и их номеров (imax, imin).
  • Перемещение указателя файла к максимальному значению и запись min.
  • Перемещение указателя файла к минимальному значению и запись max.

Ниже приведён текст программы решения задачи с комментариями.

#include <iostream>
#include <fstream>
using namespace std;
int main ( )
{
	FILE * f; //Описание файловой переменной.
	int i, n, imax, imin;
	double *a, max, min;
	f=fopen ( " abc.dat ", " rb + " ); //Открыть файл в режиме чтения и записи.
	fread (&n, sizeof ( int ), 1, f ); //Считать из файла в переменную n количество элементов в
	файле.
	a=new double [ n ]; //Выделить память для хранения вещественных чисел,
	//эти числа будут хранится в массиве a.
	fread ( a, sizeof ( double ), n, f ); //Считать из файла в массив a вещественные числа.
	//Поиск максимального, минимального элемента в массиве a, и их индексов.
	for ( imax=imin =0, max=min=a [ 0 ], i =1; i<n; i++)
	{
		if ( a [ i ]>max)
		{
			max=a [ i ];
			imax= i;
		}
		if ( a [ i ]<min )
		{
			min=a [ i ];
			imin= i;
	}
	}
	//Перемещение указателя к максимальному элементу.
	fseek ( f, sizeof ( int )+imax* sizeof ( double ),SEEK_SET);
	//Запись min вместо максимального элемента файла.
	fwrite (&min, sizeof ( double ), 1, f );
	//Перемещение указателя к минимальному элементу.
	fseek ( f, sizeof ( int )+imin * sizeof ( double ),SEEK_SET);
	//Запись max вместо минимального элемента файла.
	fwrite (&max, sizeof ( double ), 1, f );
	//Закрытие файла.
	fclose ( f );
	//Освобождение памяти, выделенной под массив a.
	delete [ ] a;
	return 0;
}
< Лекция 6 || Лекция 7: 12345 || Лекция 8 >
Сергей Радыгин
Сергей Радыгин

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

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

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

 

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

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