Глава 11. Основы ввода-вывода в языке C++

Хотя в целом C++ является расширенной версией языка С, это не означает, что для создания эффективной программы достаточно взять каждое выражение на С и записать его синтаксический эквивалент на C++. Очень часто C++ предлагает не только дополнительные операторы, но и новые способы решения традиционных задач. Одним из новшеств языка является уникальная подсистема ввода-вывода, знакомство с которой мы начнем в этой главе, а завершим в главе 15. Разделение данной темы на две главы необходимо из-за разнообразия средств ввода-вывода, используемых в C++, что связано с внедрением в язык концепции объектно-ориентированного прoграммирования. Только после того, как вы уясните принципы создания объектов и манипулирования ими, вы сможете в полной мере освоить новую методику ввода-вывода данных посредством объектов. Пока же вам следует быть готовыми к тому, что некоторые термины и понятия, упоминаемые в настоящей главе, могут оказаться для вас незнакомыми.

Подсистема ввода-вывода в C++

Стандартные функции ввода-вывода, используемые в языке С и объявленные в файле STDIO.H, доступны также и в C++. В то же время C++ располагает своим собственным файлом заголовков IOSTREAM.H, содержащим набор средств ввода-вывода, специфичных для этого языка.
Потоковый ввод-вывод в C++ организуется посредством комплекта стандартных классов, подключаемых с помощью файла IOSTREAM.H. Эти классы содержат перегруженные операторы ввода >> и вывода <<, которые поддерживают работу с данными всевозможных типов. Чтобы лучше понять, почему легче работать с потоками в C++, чём в С, давайте вспомним, как вообще в языке С реализуется ввод и вывод данных.

Прежде всего вспомним о том, что язык С не имеет встроенных средств ввода-вывода. Все функции, такие как printf() или scanf(), предоставляются через внешние библиотеки, хоть и считающиеся стандартными, но не являющиеся частью самого языка. В принципе, это позволяет гибко решать проблемы, возникаю­щие в различных приложениях, в соответствии с их особенностями и назначением.

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

Язык C++ точно так же не располагает встроенными средствами ввода-вывода, но предлагает модульный подход к решению данной проблемы, группируя возможности ввода-вывода в трех основных потоковых классах:

istream     содержит средства ввода
ostream     содержит средства вывода
iostream   поддерживает двунаправленный ввод-вывод, является производным от первых двух классов

Во всех этих классах реализованы операторы << и >>, оптимизированные для работы с конкретными данными.

Библиотека IOSTREAM.H содержит также классы, с помощью которых можно управлять вводом-выводом данных из файлов:

ifstream    Порожден от istreamи подключает к программе файл, предназначенный для ввода данных

ofstream    Порожден от ostream и подключает к программе файл, предназначенный для вывода данных

fstream       Порожден от iostream и подключает к программе файл, предназначенный как для ввода, так и для вывода данных

Стандартные потоки cin, coutи cerr

Стандартным потокам языка С stdin, stdoutи stderr, объявленным в файле STDIO.H, в C++ соответствуют объекты-потоки cin, cout, cerrи clog, подключаемые посредством файла IOSTREAM.H.

cin       Объект класса istream, связанный со стандартным потоком ввода
cout           Объект класса ostream, связанный со стандартным потоком вывода

cerr        Объект класса ostream, не поддерживающий буферизацию и связанный со стандартным потоком ошибок

clog        Объект класса ostream, поддерживающий буферизацию и связанный со стандартным потоком ошибок

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

Операторы ввода (>>) и вывода (<<) данных

Ввод-вывод в C++ значительно усовершенствован и упрощен благодаря универ­сальным операторам >> (ввод) и << (вывод). Их универсальность стала возможной благодаря появившемуся в C++ понятию перегрузки операторов, которая заключается в создании функций, имена которых совпадают с именами стандартных операторов языка. Компилятор различает вызов настоящего и "функционального" опера­торов на основании типов передаваемых им операндов. Операторы >> и << перегружены таким образом, чтобы поддерживать все стандартные типы данных C++, включая классы. Рассмотрим пример вывода данных с помощью функции printf() в языке С:

printf("Целое числей.'/%d,, число с плавающей запятой: %f",ivalue, fvalue);

А теперь запишем это же выражение на C++:

cout<< "Целое число: " << ivalue<< ", число с плавающей запятой: " << fvalue;

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

/* на языке С */

scanf("%d%f%c",&ivalue, &fvalue, &c);

// на языке С+.+ 
cin >> ivalue >> fvalue >> c; 

Нет необходимости при вводе данных ставить перед именами переменных оператор взятия адреса &. В C++ оператор >> берет на себя задачу вычисления адреса, определения формата и прочих особенностей записи значения переменной.

Прямым следствием перегрузки является возможность расширения операторов << и>> для обработки данных нестандартных типов. Ниже показано, как перегрузить оператор вывода, чтобы он мог принимать данные нового типа tclient:

ostream&        operator <<(ostream& osout, tclient client)

{

osout<< " " << client.pszname;

osout<< " " << client.pszaddress;

osout<< " " << client .pszphone;

}

Тогда для вывода содержимого структуры client на экран достаточно будет задать такое выражение:

cout << client;

Эффективность этих операторов объясняется компактностью поддерживающего их программного кода. При обращении к функциям типа printf() и scanf() задействуется большой объем кода, основная часть которого не используется при выполнении конкретной операции ввода или вывода данных. Даже если в языке С вы оперируете только целыми числами, все равно будет подгружаться весь код функции с блоками обработки данных самых разных типов. Напротив, в языке C++ реально подгружаться будут только те подпрограммы, которые необходимы для решения конкретной задачи.

В следующей программе оператор >> применяется для чтения данных различных типов:

//
//  insert. срр
//  Эта программа на языке C++ демонстрирует применение оператора >> для
//  ввода данных типа char, int и double, а также строк.
//

#include <iostream.h>
#define INUMCHARS. 45
#define INULL_CHAR 1

void main(void) {

char canswer;

int ivalue;

double dvalue;

char рзгпагае[INUMCHARS + INULL_CHAR];

pout<< "Эта программа позволяет вводить данные различных типов.\n";
cout<< "Хотите попробовать? (Y— да, N — нет) ";

cin >> canswer;

if (canswer == 'У'){ 
cout << "\n"<< "Введите целое число: ";
cin >> ivalue;

cout<< "\n\nВведите число с плавающей запятой: "; cin >> dvalue;

cout<< "\n\nВведите ваше имя: "; cin >> pszname; cout<< "\n\n"; } }

В данном примере оператор << используется в простейшем виде — для вывода текста приглашений. Обратите внимание, что оператор >> выглядит во всех случаях одинаково, если не считать имен переменных.

Теперь приведем пример программы, в которой оператор << применяется для вывода данных различных типов:

//
I/ extract.срр
//  Эта программа на языке C++ демонстрирует применение оператора << для
//  вывода данных типа int, float, а также строк.
//

#include <iostream.h>

void main (void)

{

char description!] = "Магнитофон";

int quantity = 40;

float price = 45.67;  

cout<<       "Наименование товара: " << description<< endl;

cout<<       "Количество на складе: " << quantity<< endl;

cout<<       "Цена в долларах: " << price<< endl;

}  

Программа выведет на экран следующую информацию:

Наименование товара: Магнитофон

Количество на складе: 40    

Цена в долларах: 45.67    

Здесь следует обратить внимание на манипулятор endl. Манипуляторами называются специальные функции, которые в выражениях с потоковыми объектами, такими как coutи cin, записываются подобно обычным переменным, но в действительности выполняют определенные действия над потоком. Манипулятор endlшироко применяется при выводе данных в интерактивных программах, так как помимо записи символа новой строки он очищает буфер потока, вызывая "выталкивание" содержащихся в нем данных. Эту же операцию, но без вывода символа \n, можно выполнить с помощью манипулятора flush.

Рассмотрим некоторые особенности ввода-вывода строк:

//
//   string. срр
//  Эта программа на языке C++ иллюстрирует особенности
//  работы оператора >> со строками.
//

#include <iostream.h>
#define INUMCHARS 45
#define INULL_CHARACTER 1

void main (void) {

char ps zname[ INUMCHARS + INULL_CHARACTER] ;

cout<< "Введите ваше имя и фамилию: ";

cin >> pszname;

cout << "\n\nСпасибо, " << pszname;

В результате выполнения программы будет выведена следующая информация:

Введите ваши имя и фамилию: Александр Иванов
Спасибо, Александр

Почему не была выведена вся строка? Это связано с особенностью оператора >>: он прекращает чтение строки, как только встречает первый пробел, знак табуляции или символ новой строки (кроме ведущих). Поэтому в переменной pszname сохранилось только имя, но не фамилия. Чтобы решить эту проблему, следует воспользоваться функцией cin.get (), как показано ниже:

//
//  cinget.cpp
//  Эта программа на языке C++ демонстрирует применение оператора >>
//  совместно с функцией get()для ввода строк с пробелами.
//

#include <iostream.h>
#define.INUMCHARS 45                         .
j#define   INULL_CHARACTER  1

void main(void)

char pszname[INUMCHARS + INULL CHARACTER];

cout<< "Введите ваше имя и фамилию: "; 

cin.get(pszname, INUMCHARS);

cout << "\n\nСпасибо, " << pszname;

Теперь программа отобразит данные следующим образом:

Введите ваше имя и фамилию: Александр Иванов
Спасибо, Александр Иванов

Функция cin .get (), помимо имени переменной, принимающей данные, ожидает два дополнительных аргумента: максимальное число вводимых символов и символ, служащий признаком конца ввода. В качестве последнего по умолчанию используется символ \n. Функция cin.get() считывает все символы в строке, включая пробелы и знаки табуляции, пока не будет прочитано указанное число символов или не встретится символ-ограничитель. Если в качестве ограничителя необходимо, к примеру, использовать знак *, следует записать такое выражение:

cin.get(pszname,   INUMCHARS,   '*');

Флаги и функции форматирования

Работа всех потоковых объектов из библиотеки IOSTREAM.H контролируется флагами форматирования, определяющими такие параметры, как, например, основание системы счисления при выводе целых чисел и точность представления чисел с плавающей запятой.

Флаги можно устанавливать с помощью функции setf(), а сбрасывать — с помощью функции unsetf (). Есть два варианта функции setf() с одним аргументом типа long и с двумя. Первым аргументом является набор флагов, объединенных с помощью операции побитового ИЛИ (|). Возможные флаги перечислены в таблице 11.1.

Таблица 11.1. Флаги форматирования
Флаг  Назначение
skipws     при вводе пробельные литеры пропускаются
left  выводимые данные выравниваются по левому краю с дополнением символами-заполнителями по ширине поля
right выводимые данные выравниваются по правому краю с дополнением символами-заполнителями по ширине поля (установлен по умолчанию)
internal  при выравнивании символы-заполнители вставляются между символом знака или префиксом основания системы счисления и числом
dec     целые числа выводятся по основанию 10 (установлен по умолчанию); устанавливается также манипулятором dec
oct   целые числа выводятся по основанию 8; устанавливается также манипулятором oct
hex 

целые числа выводятся по основанию 16; устанавливается также манипулятором hex

showbase  при выводе целых чисел отображается префикс, указывающий на основание системы счисления
showpoint при выводе чисел с плавающей запятой всегда отображается десятичная точка, а хвостовые нули не отбрасываются
uppercase   шестнадцатеричные цифры от А до F, а также символ экспоненты Е отображаются в верхнем регистре
showpos

 при выводе положительных чисел отображается знак плюс

scientific числа с плавающей запятой отображаются в научном формате (с экспонентой)
fixed числа с плавающей запятой отображаются в фиксированном формате (без экспоненты)
unitbuf  при каждой операции вывода буфер потока должен очищаться
stdio при каждой операции вывода буферы потоков stdout и stderr должны очищаться

  Вторым аргументом является специальная битовая маска, определяющая, какую группу флагов можно модифицировать. Имеются три стандартные маски:

adjustfield =     internal | left | right

basefield  =    dec | oct | hex

floatfield =    fixed | scientific

Оба варианта функции setf() возвращают значение типа long, содержащее предыдущие установки всех флагов.

Все перечисленные флаги, а также константы битовых масок и упоминавшиеся манипуляторы dec, hexи octявляются членами класса ios - базового в иерархии всех классов ввода-вывода. В этот класс входят, помимо прочего, функции fill() , precision ( ) и width ( ) , тоже связанные с форматированием выводимых данных!

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

Когда установлен флаг scientificили fixed, функция precision() задает точность представления чисел с плавающей запятой, в противном случае определяет общее количество значащих цифр. Аналогичные действия выполняет манипулятор setprecision() . По умолчанию точность равна 6. Вызванная без аргументов, функция возвращает текущее значение точности.

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

Манипуляторы setfill () , setprecision() и setw(), также являющиеся членами класса ios, относятся к категории параметризованных, и для работы с ними необходимо дополнительно подключить к программе файл IOMANIP.H.

Следующая программа является написанным на C++ аналогом программы printf.c, рассмотренной в предыдущей главе.

//
//   ioflags.cpp
//   Эта программа на языке C++ демонстрирует применение
//   флагов и функций форматирования.
//

#include <strstrea.h>

#define MAX_LENGTH 20 void row (void);

main{) {

char  с = 'A',psz[]  = "Строка для экспериментов",
strbuffer[MAX_LENGTH];

int   ivalue = 1234;

double dPi   = 3.14159265;

// вывод символа с
row(); // [ 1]
cout<< с;

// вывод ASCII-кода символа с
row() ; // [2]
cout << (int)c;

// вывод символа с ASCII-кодом 90 

row (); // [ 3]    

cout << (char)90; 

// вывод значения ivalue в восьмеричной системе

row(); //[4]   

cout << oct << ivalue;

// вывод значения ivalue в шестнадцатеричной системе

//сбуквами в нижнем регистре

row (); // [5]

cout << hex << ivalue;

// вывод значения ivalue в шестнадцатеричной системе

// с буквами в верхнем регистре

row (); // [ 6]

cout.setf(ios::uppercase);

cout << hex << ivalue;

cout.unsetf(ios::uppercase); // отмена верхнего регистра символов

cout<< dec;              // возврат к десятичной системе

// вывод одного символа, минимальная ширина поля равна 5,
// выравнивание вправо с дополнением пробелами
row();// [ 7]
cout.width(5); cout<< с;

// вывод одного символа, минимальная ширина поля равна 5,

// выравнивание влево с дополнением пробелами

row(); // [ 8]

cout.width(5);

cout.setf(ios::left);

cout << c;

cout.unsetf(ios::left);

// вывод строки, отображаются 24 символа
row();// [9] cout<< psz;

// вывод минимум 5-ти символов строки
row();// [10] cout.width(5); cout<< psz;

// вывод минимум 38-ми символов строки,

// выравнивание вправо с дополнением пробелами

row();// [11]

cout.width(38); cout << psz;

// вывод минимум 38-ми символов строки,

// выравнивание влево с дополнением пробелами

row ();// [12] 

cout.width(38);

cout.setf(ios::left);

cout << psz;

cout.unsetf(ios::left);

// вывод значения ivalue, по умолчанию отображаются 4 цифры
row();// [13] cout<< ivalue;

// вывод значения ivalue со знаком

row ();// [14]

cout.setf(ios::showpos);

cout << ivalue;

cout.unsetf(ios::showpos) ;

// вывод значения ivalue минимумиз 3-х цифр, отображаются 4 цифры
row ();// [15]
cout.width(3); cout << ivalue;

// вывод значения ivalue минимум из 10-ти цифр,
} // выравнивание вправо с дополнением пробелами
row();// [16]
cout.width(10); cout<< ivalue;

// вывод значения ivalue минимум из 10-ти цифр,

// выравнивание влево с дополнением пробелами

row();// [17]

cout.width(10);

cout.setf(ios::left) ;

cout << ivalue;

cout.unsetf(ios::left);

// вывод значения ivalue минимум из 10-ти цифр,

// выравнивание вправо с дополнением нулями

row (); // [18]

cout.width (10);

cout.fill ('0');

cout << ivalue;

cout.fill (' ');

// вывод значения dPiс форматированием по умолчанию
row(); // [19]
cout<< dPi;

// вывод значения dPi, минимальная ширина поля равна 20,
// выравнивание вправо с дополнением пробелами
row{); // [20]
cout.width (20); cout<< dPi;

// вывод значения dPi, минимальная ширина поля равна 20,

// выравнивание вправо с дополнением нулями

row();// [21]

cout.width(20);

cout.fill('0');

cout << dPi;

cout. fill (' ');

// вывод значения dPi, минимальная ширина поля равна 20,

// выравнивание влево с дополнением пробелами

row();// [22]

cout.width(20);

cout.setf(ios::left) ;

cout<< dPi;

// вывод значения dPi, минимальная ширина поля равна 20,

// выравнивание влево с дополнением нулями

row();// [23]

cout.width(20);

cout. fill ('0');

cout << dPi;

cout.unsetf(ios::left);

cout.fill(' ');

// вывод 19-ти символов строки, минимальная ширина поля равна 19

row();// [24]

ostrstream(strbuffer, 20).write(psz,19)<< ends;

cout.width(19);

cout << strbuffer;

// вывод первых двух символов строки

row(); // [25]  

ostrstream(strbuffer, 3).write(psz,2) << ends;

cout<< strbuffer;

// вывод первых двух символов строки, минимальная ширина поля равна 19,
// выравнивание вправо с дополнением пробелами

row();// [26] cout.width(19); cout << strbuffer;

// вывод первых двух символов строки, минимальная ширина поля равна 19,
// выравнивание влево с дополнением пробелами row();// [27] cout.width(19);

cout .setf (ios :: left) ; cout << strbuffer; cout .unsetf (ios :: left) ;

// вывод значенияdPi из5-ти значащих цифр
row ();// [28]
cout .precision (9); cout << dPi;

// вывод значения dPiиз 2-х значащих цифр, минимальная ширина поля

// равна 20, выравнивание вправо с дополнением пробелами

row ();// [29]

cout. width (20) ;

cout .precision (2);

cout << dPi;

// выводзначенияdPi из 4-хзначащихцифр
row () ; // [30]
cout .precision (4); cout << dPi;

// вывод значения dPi из 4-х значащих цифр, минимальная ширина поля
// равна 20, выравнивание вправо с дополнением пробелами
row(); // [31]
cout. width(20); cout<< dPi;

// вывод значения dPi, минимальная ширина поля равна 20,

// 4 цифры после десятичной точки, выравнивание вправо

IIс дополнением пробелами, научный формат (с экспонентой)

row ();// [32]

cout . setf (ios :: scientific) ;

cout. width ( 20 );

cout << dPi;

cout. unset f (ios: : scientific );

return (0);

}

void row(void) {

static int In = 0;

cout << " \n [";

cout.width(2) ;

cout<< ++ln<< "] "; }

Результат работы программы будет выглядеть следующим образом:

[ 1]   А 

[ 2]    65

[ 3]    Z

[ 4]   2322

[ 5]    4d2

[ 6]    4D2

[ 7]А

[ 8]А

[ 9]  Строка для экспериментов

[10]  Строка для экспериментов

[11]'       Строка для экспериментов

[12]Строка для экспериментов

[13]1234

[14]+1234

[15]1234

[16]1234

[17]  1234

[18]   0000001234

[19]  3.14159

[20]   3.14159

[21]   00000000000003.14159

[22]   3.14159

[23]    3.141590000000000000

[24]  Строка для эксперим

[25]  Ст

[26] Ст

[27] Ст

[28] 3.14159265

[29] 3.1

[30] 3.142

[31]3.142

[32] 3.1416e+000

В этой программе следует обратить внимание на использование класса ostrstream в пунктах 24 и 25, а также неявно в пунктах 26 и 27. Этот класс управляет выводом строк. Необходимость в нем возникла в связи с тем, что флаги форматирования оказывают влияние на работу оператора <<, но не функции write() класса ostream(класс ostrstream является его потомком и наследует данную функцию), которая записывает в поток указанное число символов строки. Выводимые ею данные всегда прижимаются к левому краю. Поэтому мы поступаем следующим образом:

ostrstream(strbuffer, 20).write(psz,19)<< ends;
cout.width(19); cout << strbuffer;

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

Итак, конструктор создал требуемый объект, связанный с буфером strbuffer. Этот объект является обычным потоком в том смысле, что им можно управлять как и любым другим потоком вывода, только данные будут направляться не на экран, а в буфер. Вторым действием вызывается функция write() объекта, записывающая в поток первые 19 символов строки psz. Оператор точка (.) в данном случае обозначает операцию доступа к члену класса. Здесь тоже есть своя особенность: функция write() возвращает адрес потока, из которого она была вызвана. А это, в свою очередь, означает, что мы можем тут же применить к потоку оператор вывода <<. Итого, три действия в одном выражении! Манипулятор endsкласса ostreamвставляет в поток символ \0,служащий признаком завершения строки. Именно поэтому в буфере было зарезервировано 20 ячеек — на единицу больше, чем количество выводимых символов строки psz. В результате выполненных действий в буфере оказалась сформированной готовая строка, которая, в конце концов, и направляется в поток cout.

Теперь рассмотрим, что происходит в пункте 25:

ostrstream (strbuf fer, 3) .write (psz, 2) << ends;
cout << strbuffer;

Последовательность действий та же, но резервируется не весь буфер, а только первые три ячейки. Трюк в том, что объект cout воспринимает массив strbuffer как обычную строку, но поскольку в третьей ячейке стоит символ \0,то получается, что строка состоит из двух символов! В пунктах 26 и 27 используется содержимое буфера, полученное в пункте 25.

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

Файловый ввод-вывод

Во всех программах на языке C++, рассмотренных до сих пор, для ввода и вывода данных применялись стандартные потоки cin и cout. Если для этих же целей удобнее работать с файлами, следует воспользоваться классами ifstream и ofstream, которые порождены от классов istream и ostream и унаследовали от них функции, соответственно, чтения и записи. Необходимо также подключить файл FSTREAM.H (он, в свою очередь, включает файл IOSTREAM.H). В следующей программе демонстрируется работа с файлами посредством классов ifstream и ofstream:

//
//   fstream. cpp
//   Эта программа на языке C++ демонстрирует, как создавать
//  потоки ifstream и ofstream для обмена данными с файлом.
//

#include <fstream.h>

int main(void) {

char c;

ifstream ifsin("text.in",   ios::in); if(lifsin)

cerr<<  "\nНевозможно открыть файл text.in для чтения.";

ofstream ofsout("text.out",   ios::out);

if(!ofsout)

cerr<<  "\nНевозможно открыть файл text.outдля записи.";

while (ofsout &&  ifsin.get(c)   ) 
ofsout.put (c);

if sin. close (); 
ofsout. close () ;

return(0); }

Программа создает объект ifsin класса ifstream и связывает с ним файл TEXT. IN, находящийся в текущем каталоге. Всегда следует проверять доступность указанного файла. В данном случае проверка осуществляется достаточно оригиналь­ным образом. Оператор ! в потоковых классах перегружен таким образом, что, будучи примененным к соответствующему объекту, при наличии каких-либо ошибок в потоке возвращает ненулевое значение. Аналогичным образом создается и объект ofsoutкласса ostream, связываемый с файлом TEXT.OUT.

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

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

ifstream ifsin;
.
.
.
ifsin.open("weekl.in");
.
.
.
if sin.close () ;
ifsin.open("week2.in")
.
.
.
if sin. close () ;

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

ofstream ofsout("file.out", ios::app | ios::nocreate);

В данном выражении делается попытка создать объект ofsout и связать его с файлом FILE.OUT. Поскольку указан флаг ios::nocreate, подключение не будет создано, если файл FILE.OUT не существует. Флаг ios::app означает, что все выводимые данные будут добавляться в конец файла. В следующей таблице перечислены флаги, которые можно использовать во втором аргументе конструкторов файловых потоков (допускается объединение флагов с помощью операции побито­вого ИЛИ):

Флаг                     Назначение

ios: : in                    Файл открывается для чтения, его содержимое не очищается

ios: : out                  Файл открывается для записи

ios: : ate                 После создания объекта маркер текущей позиции устанавливается в конец файла

ios: : арр                   Все выводимые данные добавляются в конец файла

ios: : trunc              Если файл существует, его содержимое очищается (автоматически устанавливается при открытии файла для записи)

ios: : nocreate      Объект не будет создан, если файл не существует

ios: : noreplace   Объект не будет создан, если файл существует

ios: : binary          Файл открывается в двоичном режиме (по умолчанию — в текстовом)

Для обмена данными с файлом можно также использовать объект класса fstream. Например, в следующем выражении файл UPDATE.DATоткрывается для чтения и записи данных:

fstream io("update.dat",   ios::in   |   ios::app);

К объектам класса iostream(а класс fstream является его потомком) можно применять функции seekg() и seekp(),позволяющие управлять положением маркера текущей позиции файла. Функция seekg() задает положение маркера, связанного с чтением данных из файла, а функция seekg() — с записью. Обе функции требуют указания одного или двух аргументов. Если указан один аргумент, он считается абсолютным адресом маркера. Если два, то первый задает относительное смещение, а второй — направление перемещения. Существует также функция tellg(), возвращающая текущее положение маркера чтения, и функция tellp(), возвращающая текущее положение маркера записи. Рассмотрим небольшой фрагмент программы:

streampos current_position = io.tellp();

io << objl << obj2 << obj3;

io.seekp(current_position);

io.seekp(sizeof(objl), ios::cur);

io<< newobj2;

Сначала создается указатель current_positionтипа streampos, который инициализируется текущим адресом маркера записи. Во второй строке в поток io записываются три объекта. В третьей строке с помощью функции seekp() указатель перемещается в сохраненную позицию. Далее с помощью оператора sizeof() вычисляется объем памяти в байтах, занимаемый в файле объектом obj1, и функция seekp() "перескакивает" через него. В результате поверх объекта obj2 записывается объект newobj2.

Вторым аргументом функций seekg() и seekp() может быть один из следующих флагов: ios::beg(смещение на указанную величину от начала файла), ios: : cur(смещение на указанную величину от текущей позиции) и ios:: end (смещение на указанную величину от конца файла). Например, данное выражение переместит маркер чтения на 5 байтов от текущей позиции:

io.seekg(5,   ios::cur);

Следующее выражение переместит маркер на 7 байтов от конца файла:

io.seekg(-7,   ios::end);

Определение состояния потока

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

Функция                       Действие

eof()                 Возвращает ненулевое значение при обнаружении конца файла

fail()                  Возвращает ненулевое значение в случае возникновения какой-либо ошибки в потоке, возможно не фатальной; если функция bad() при этом возвращает 0, то, скорее всего, можно продолжать работу с потоком, предварительно сбросив флаг ios:: failbit

bad()
                 Возвращает ненулевое значение в случае возникновения серьезной ошибки ввода-вывода; в этом случае продолжать работу с потоком не рекомендуется

good()               Возвращает ненулевое значение, если биты состояния не установлены

rdstate()             Возвращает текущее состояние потока в виде одной из констант: ios: :goodbit(нет ошибки), ios:: eofbit(достигнут конец файла), ios::failbit(возможно, некритическая ошибка форматирования или преобразования), ios: :badbit(критическая ошибка)

clear()                Задает состояние потока; принимает аргумент типа int, который по умолчанию равен 0, что соответствует сбросу всех битов состояния, в противном случае содержит одну или несколько перечисленных в предыдущем пункте констант, объединенных с помощью операции побитового ИЛИ (|)

Приведем пример:

ifstreampfsinfile("sample.dat", ios::in);

if (pfsinfile.eof ())

pfsinfile.clear() ;   //  состояние потока pfsinfile сбрасывается

if (pfsinfile. fail () )

cerr<< ">>> ошибка при создании файла sample.dat<<<";

if (pfsinfile. good ()) cin >> my_object;

if(!pfsinfile) // другой способ обнаружения ошибки

cout<< ">>> ошибка при создании файла sample.dat<<<";