Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курс ПЯВУ 2 сем / Лекции 2 сем / Л№34.Потоковый ввод / Лекция № 32.Потоковый ввод вывод.odt
Скачиваний:
11
Добавлен:
17.04.2015
Размер:
153.09 Кб
Скачать

If (!inFile)

{ cerr << "Ошибка при открытии файла!\n";

exit(l);

}

. . . .

Для потоков класса fstream второй аргумент функции ореn() должен быть задан явно, так как по умолчанию неясно, в каком направлении предполагается выполнять обмен с потоком. В примере файл CHANGE.DAT открывается для записи и связывается с потоком ioFile, который будет выходным потоком до тех пор, пока с помощью повторного открытия файла явно не изменится направление обмена с файлом или потоком. (Чтобы изменить режимы доступа к файлу, его нужно предварительно закрыть с помощью функции close(), унаследованной всеми тремя файловыми классами из базового класса fstreambase.)

В классе fstreambase, который служит основой для файловых классов, имеются и другие средства для открытия уже существующих файлов.

Если файл явно создан с помощью библиотечной функции "нижнего уровня" creat(), то для него определен дескриптор файла. Этот дескриптор можно использовать в качестве фактического параметра функции fstreambase::attach(). При вызове этой функции используется уточненное имя, содержащее название того потока, который предполагается присоединить к уже созданному файлу с известным дескриптором:

#include <fstream.h> // Классы файловых потоков.

#include <sys\stat.h> // Константы режимов доступа к файлам.

. . . . .

char name[20]; // Вспомогательный массив.

cin >> name; // Ввести имя создаваемого файла.

int descrip = create(name,S_WRITE); // Создать файл.

if (descrip == -1)

{ cout << "\n Ошибка при создании файла";

exit();

}

// Определение выходного файлового потока:

ofstream fileOut;

// Присоединение потока к файлу:

fileOut.attach(descrip);

if (!fileOut)

{ cerr << "\nОшибка присоединения файла!";

exit(l);

}

. . . . .

В классах ifstream, ofstream, fstream определены конструкторы, позволяющие по-иному выполнять создание и открытие файлов. Типы конструкторов для потоков разных классов очень похожи:

имя_класса();

создает поток, не присоединяя его ни к какому файлу;

имя_класса(int fd);

создает поток и присоединяет его к уже открытому файлу, дескриптор которого используется в качестве параметра fd;

имя_класса(int fd, char *buf, int);

создает поток, присоединяя его к уже открытому файлу с дескриптором fd, и использует явно заданный буфер (параметр buf);

имя_класса(char *FileName, int mode, int = ...);

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

Детали и особенности перечисленных конструкторов лучше изучать по документации конкретной библиотеки ввода-вывода.

Работая со средствами библиотечных классов ввода-вывода, чаще всего употребляют конструктор без параметров и конструктор, в котором явно задано имя файла. Примеры обращений к конструкторам без параметров:

ifstream fi; // Создает входной файловый поток fi.

оstream fo; // Создает выходной файловый поток fo.

fstream ff; // Создает файловый поток ввода-вывода ff.

После выполнения каждого из этих конструкторов файловый поток можно присоединить к конкретному файлу, используя уже упомянутую компонентную функцию open():

void open(char *FileName, int режим, int защита);

Примеры:

fi.open("File1.txt",ios::in); // Поток fi соединен с файлом File1.txt.

fi.close(); // Разорвана связь потока fi с файлом File1.txt.

fi.open("File2.txt"); // Поток fi присоединен к файлу File2.txt.

fо.open("NewFile"); // Поток fo присоединяется к файлу NewFile;

// если такой файл отсутствует - он будет создан.

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

Примеры:

ifstream flow1("File.1");

создает входной файловый поток с именем flow1 для чтения данных. Разыскивается файл с названием File.1. Если такой файл не существует, то конструктор завершает работу аварийно. Проверка:

if (!flow1) cerr << "Не открыт файл File.1";

ofstream flow2("File.2");

создается выходной файловый поток с именем flow2 для записи информации. Если файл с названием File.2 не существует, он будет создан, открыт и соединен с потоком flow2. Если файл уже существует, то предыдущий вариант будет удален и пустой файл создается заново. Проверка:

if (!flow2) cerr << "Не открыт файл File.2!";

fstream flow3("File.3");

создается файловый поток flow3, открывается файл File.3 и присоединяется к потоку flow3.

На следующем шага мы закончим рассматривать работу с файлами.

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

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

//OOР19_1.СРР - чтение текстового файла с помощью

// операции >>.

#include <stdlib.h> // Для функции exit().

#include <fstream.h> // Для файловых потоков.

const int lenName = 13; // max длина имени файла.

// Длина вспомогательного массива:

const int lenString = 60;

void main()

{

char source[lenName]; // Массив для имени файла.

cout << "\nВведите имя исходного файла: ";

cin >> source;

ifstream inFile; // Входной файловый поток.

// Открыть файл source и связать его с потоком inFile:

inFile.open(source);

if (!inFile) // Проверить правильность открытия файла.

{

cerr << "\nОшибка при открытии файла " << source;

exit(1); // Завершение программы.

}

// Вспомогательный массив для чтения:

char string[lenString];

char next;

cout << "\n Текст файла:\n\n";

cin.get(); // Убирает код из потока cin.

while(1) // Неограниченный цикл.

{ // Ввод из файла одного слова до пробельного

// символа либо EOF:

inFile >> string;

// Проверка следующего символа:

next = inFile.peek();

// Выход при достижении конца файла:

if (next == EOF) break;

// Печать с добавлением разделительного пробела:

cout << string << " ";

if (next == '\n') // Обработка конца строки.

{

cout << '\n';

// 4 - смещение для первой страницы экрана:

static int i = 4;

// Деление по страницам до 20 строк каждая:

if (!(++i % 20))

{

cout << "\nДля продолжения вывода "

"нажмите ENTER.\n" << endl;

cin.get();

}

}

}

}

Текст этой программы можно взять здесь.

Результат выполнения программы - постраничный вывод на экран текстового файла, имя которого набирает на клавиатуре пользователь по "запросу" программы. Размер страницы - 20 строк. В начале первой страницы - результат диалога с пользователем и поэтому из файла читаются и выводятся только первые 16 строк.

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

while(1) // Неограниченный цикл.

{

inFile.getline(string,lenString);

next = inFile.peek();

if (next == EOF) break;

cout << string;

. . .

Следующая программа читает текстовый файл и разделяет его на две части - строки, не содержащие последовательности из двух символов '//', и строки, начинающиеся такой парой символов. Иначе говоря, эта программа позволяет удалить из исходного текста программы на языке С++ комментарии, начинающиеся парой символов '//' и заканчивающиеся признаком конца строки '\n'. В программе определены два выходных потока outtext и outcom, связанные соответственно с создаваемыми заново файлами text.cpp и comment. Имя входного файла с текстом анализируемой программы на языке С++ определяет (вводит с клавиатуры) пользователь. С этим файлом "связывается" функцией open() входной поток inFile. Для проверки безошибочного открытия файлов проверяются значения выражений (!имя_потока). При истинности результата вызывается вспомогательная функция errorF(). Вспомогательная переменная int len, позволяет проследить за необходимостью перехода к новой строке в потоке outtext, если во входном потоке inFile обнаружена пара символов '//'. Символы входного потока последовательно читаются в переменную simb и выводятся в нужный выходной поток. Если не встречен символ '/', то все просто - вывод идет в поток outtext. Так как обнаружение во входном отдельного символа '/' не есть признак начала комментария, то в этом случае анализируется следующий символ, читаемый из входного потока в переменную next. Если next имеет значение '/', то это начало комментария, и последующий вывод нужно вести в поток outcom, предварительно "закрыв" строку в потоке outtext символом '\n'. Комментарии в тексте программы поясняют остальные детали алгоритма.

//OOР19_2.СРР - выделение комментариев из текста на С++;

//посимвольные чтение и запись иэ текстового файла.

#include <stdlib.h>

#include <fstream.h>

void errorF(char *ss) // Вспомогательная функция.

{

cerr << "\nОшибка при открытии файла" << ' ' << ss << '\n';

exit(1);

}

const int lenName = 23; // Длина массива для имени файла.

void main()

{

char progName[lenName]; // Массив для имени файла.

cout << "\nВведите полное имя анализируемой программы: ";

cin >> progName;

ifstream inFile; // Входной поток.

// Связываем входной поток с файлом программы:

inFile.open(progName);

if (!inFile) errorF(progName);

char simb, last, next; // Вспомогательные переменные.

ofstream outtext, outcom; // Два выходных потока.

// Переменная для вычисления длин строк программы:

int len = 0;

outtext.open("text.cpp",ios::ate);

if (!outtext) errorF("text.cpp");

outcom.open("comment",ios::app);

if (!outcom) errorF("comment");

while (inFile.get(simb)) // Читает символы до EOF.

{

len++; // Длина очередной строки программы.

if (simb == '\n')

len = 0; // Начнется новая строка программы.

if (simb !='/') // Это не начало комментария.

// Вывод символа строки программы:

outtext.put(simb);

else

// Когда simb == '/' - возможно начало комментария.

{ // Проверка на EOF:

if (!inFile.get(next)) break;

if (next == '/')

{ // Теперь уже точно комментарий.

if (len != 1)

// "Закрываем" строку программы:

outtext.put('\n');

outcom.put(simb);

outcom.put(next);

// Цикл до конца комментария,

// т.е. до конца строки:

do

{ // Чтение символа из файла:

inFile.get(simb);

// Запись символа в поток:

outcom.put(simb);

} while (simb!='\n');

}

else

// Вывод символов, не входящих в комментарий:

{

outtext.put(simb);

outtext.put(next);

}

}

}

}

Текст этой программы можно взять здесь.

Результат выполнения программы - два файла text.cpp и comment в текущем каталоге, из которого "запущена" на выполнение программа. В первом файле - текст программы без комментариев, во втором - текст всех комментариев. В качестве примера можно выполнить программу для текста из файла OOP19_2.СРР, т.е. разобрать исходный текст этой же программы.

Для разнообразия при открытии файлов text.cpp и comment в функциях open() использованы разные флаги, определяющие режим работы с соответствующим потоком. Результат одинаков - флаги ios::ate и ios::арр в этом случае неразличимы. Запись в файлы идет с их дополнением. После каждого нового выполнения программы новая "порция" текстовой информации дописывается в каждый файл. Если необходимо, чтобы сохранялся в файлах только последний результат, второй параметр функции open() проще всего задавать по умолчанию.

Как и для других потоков, для потоков, связанных с файлами, допустима перегрузка операций обмена. Для иллюстрации приведем следующую программу OOР19_3.СРР, в которой перегружена операция включения в поток <<. Действие операции распространено на аргументы типа ofstream& и element, где element - пользовательский тип, а именно структура. В программе с помощью конструктора класса ofstream определяется поток file1 и связывается с файлом ABC.

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

//OOР19_3.СРР - запись структур в файл перегруженной операцией <<.

#include <fstream.h>

struct element { // Определение некоторой структуры.

int nk, nl;

float zn; };

// Операция-функция, расширяющая действие операции <<.

ofstream& operator << (ofstream& out, element el)

{

out << ' ' << el.nk << ' ' << el.nl <<

' ' << el.zn << '\n';

return out;

}

int main()

{

const int numbeEl = 5; // Количество структур в массиве.

element arel[numbeEl] = { 1, 2, 3.45, 2, 3, 4.56,

22, 11, 45.6, 3, 24, 4.33, 3, 6, -5.3 };

// Определяем поток и связываем его с новым файлом ABC:

ofstream file1("ABC");

if (!file1)

{ cerr << "Неудача при открытии файла ABC."; return 1; }

// Запись в файл ABC массива структур:

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

file1 << arel[i];

}

Текст этой программы можно взять здесь.

Результат выполнения программы - создание файла с именем АВС в текущем каталоге и запись в этот файл элементов массива из пяти структур element. Содержимое файла АВС:

1 2 3.45

2 3 4.56

22 11 45.6

3 24 4.33

3 6 -5.3

Файл АВС создается заново при каждом выполнении программы. Чтобы файл создавался один раз и была возможность его дополнения, нужно добавить в конструктор второй параметр таким образом:

ofstream file1("ABC",ios::app);

В этом случае при двух последовательных выполнениях программы результат в файле АВС будет таким:

1 2 3.45

2 3 4.56

22 11 45.6

3 24 4.33

3 6 -5.3

1 2 3.45

2 3 4.56

22 11 45.6

3 24 4.33

3 6 -5.3