Четверг , Ноябрь 21 2019
Главная / Студентам / ОАиП / Лекция 9. Общая характеристика языков ассемблера. Работа с файлами. Использование ассемблерных вставок.

Лекция 9. Общая характеристика языков ассемблера. Работа с файлами. Использование ассемблерных вставок.

Существуют два основных типа файлов: текстовые и двоичные. Файлы позволяют пользователю считывать большие объемы данных непосредственно с диска, не вводя их с клавиатуры. 

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

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

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

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

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

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

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

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

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

ofstream F; 

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

F.open(«file», mode); 

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

!!! Обратите внимание на то, что при указании полного имени файла нужно ставить двойной слеш. Например, полное имя файла noobs.txt, находящегося в папке game на диске D:, нужно будет записать так:  

D:\\game\\noobs.txt. 

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

ios::in — открыть файл в режиме чтения данных, этот режим является режимом по умолчанию для потоков ifstream; 

ios::out — открыть файл в режиме записи данных (при этом информация о существующем файле уничтожается), этот режим является режимом по умолчанию для потоков ofstream; 

ios::app — открыть файл в режиме записи данных в конец файла; 

ios::ate — передвинуться в конец уже открытого файла; 

ios::trunc — очистить файл, это же происходит в режиме ios::out; 

ios::nocreate — не выполнять операцию открытия файла, если он не существует; 

ios::noreplace — не открывать существующий файл. 

!!! Параметр mode может отсутствовать, в этом случае файл открывается в режиме по умолчанию для данного потока. 

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

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

//первый способ 
 ofstream F; 
 F.open("D:\\game\\noobs.txt", ios::out); 
//второй способ, режим ios::out является режимом по умолчанию 
//для потока ofstream 
 ofstream F; 
 F.open("D:\\game\\noobs.txt"); 
//третий способ объединяет описание переменной и типа поток 
//и открытие файла в одном операторе 
 ofstream F ("D:\\game\\noobs.txt", ios::out); 

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

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

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

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

F<<a; 

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

G<<b<<c<<d; 

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

F.close(); 

ПРИМЕР: 

Создать текстовый файл D:\\game\\noobs.txt и записать в него n вещественных чисел. 

#include "stdafx.h" 
#include <iostream> 
#include <fstream> 
#include <iomanip> 
using namespace std; 
int main() 
{ 
 setlocale (LC_ALL, "RUS"); 
int i, n; 
double a; 
//описывает поток для записи данных в файл 
 ofstream f; 
//открываем файл в режиме записи, 
//режим ios::out устанавливается по умолчанию 
 f.open("D:\\game\\noobs.txt", ios::out); 
//вводим количество вещественных чисел 
cout<<"n="; cin>>n; 
//цикл для ввода вещественных чисел 
//и записи их в файл 
for (i=0; i<n; i++) 
{ 
cout<<"a="; 
//ввод числа 
cin>>a; 
 f<<a<<"\t"; 
} 
//закрытие потока 
 f.close(); 
system("pause"); 
return 0; 
} 

_______________________________________________________________ 

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

ifstream F; 
F.open(«D:\\game\\noobs.txt», ios::in); 

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

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

F>>a; 

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

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

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

ПРИМЕР: 

В текстовом файле D:\\game\\noobs.txt хранятся вещественные числа, вывести их на экран и вычислить их количество. 

#include "stdafx.h" 
#include <iostream> 
#include <fstream> 
#include <iomanip> 
#include <stdlib.h> 
using namespace std; 
int main() 
{ 
 setlocale (LC_ALL, "RUS"); 
int n=0; 
float a; 
 fstream F; 
//открываем файл в режиме чтения 
 F.open("D:\\game\\noobs.txt"); 
//если открытие файла прошло корректно, то 
if (F) 
{ 
//цикл для чтения значений из файла; выполнение цикла прервется, 
//когда достигнем конца файла, в этом случае F.eof() вернет истину. 
while (!F.eof()) 
{ 
//чтение очередного значения из потока F в переменную a 
 F>>a; 
//вывод значения переменной a на экран 
cout<<a<<"\t"; 
//увеличение количества считанных чисел 
 n++; 
} 
//закрытие потока 
 F.close(); 
//вовод на экран количества считанных чисел 
cout<<"n="<<n<<endl; 
} 
//если открытие файла прошло некорректно, то вывод 
//сообщения об отсутствии такого файла 
else cout<<" Файл не существует"<<endl; 
system("pause"); 
return 0; 
} 

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

При записи информации в двоичный файл символы и числа записываются в виде последовательности байт. 

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

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

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

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

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

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

FILE *fopen(const *filename, const char *mode) 

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

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

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

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

int fclose(FILE *filename); 

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

Функция remove предназначена для удаления файлов. 

int remove(const char *filename); 

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

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

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

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

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

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

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

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

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

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

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

int feof(FILE *filename); 

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

ПРИМЕР: 

Создать двоичный файл D:\\game\\noobs.dat и записать в него целое число n и n вещественных чисел. 

#include "stdafx.h" 
#include <iostream> 
using namespace std; 
int main() 
{ 
 setlocale (LC_ALL, "RUS"); 
int n, i; 
double a; 
FILE *f; //описываем файловую переменную 
//создаем двоичный файл в режиме записи 
 f=fopen("D:\\game\\noobs.dat", "wb"); 
//ввод числа n 
cout<<"n="; cin>>n; 
fwrite(&n, sizeof(int), 1, f); 
//цикл для ввода n вещественных чисел 
for (i=0; i<n; i++) 
{ 
//ввод очередного вещественного числа 
cout<<"a="; 
cin>>a; 
//запись вешественного числа в двоичный файл 
fwrite(&a, sizeof(double), 1, f); 
} 
//закрываем файл 
fclose(f); 
system("pause"); 
return 0; 
} 

ПРИМЕР: 

Вывести на экран содержимого созданного в прошлой задаче двоичного файла D:\\game\\noobs.dat 

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

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

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

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

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

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

SEEK_SET — с начала файла; 
SEEK_CUR — с текущей позиции; 
SEEK_END — с конца файла. 

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

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

ПРИМЕР 

В созданном раннее двоичном файле D:\\game\\noobs.dat, поменять местами наибольшее и наименьшее из вещественных чисел. 

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

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

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

#include "stdafx.h" 
#include <iostream> 
using namespace std; 
int main() 
{ 
 setlocale (LC_ALL, "RUS"); 
int n, i, imax, imin; 
double *a, max, min; 
FILE *f; 
//открытие файла в режиме чтения и записи 
 f=fopen("D:\\game\\noobs.dat", "rb+"); 
//считываем из файла в переменную n количество 
//вещественных чисел в файле 
fread(&n, sizeof(int), 1, f); 
cout<<"n="<<n<<endl; 
//выделяем память для хранения вещественных чисел, 
//которые будут храниться в массиве a 
 a=new double[n]; 
//считываем из файла в массив а вещественные числа 
fread(a, sizeof(double), n, f); 
//поиск максимального и минимального элементов 
//в массиве а и их индексов 
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); 
//освобождение памяти 
delete [ ]a; 
system("pause"); 
return 0; 
}