Глава 15. Классы ввода-вывода в языке C++

В главе "Основы ввода-вывода в языке C++" были даны общие представления о потоковых объектах cin, cout и cerr и операторах потокового ввода-вывода << и >>. В настоящей главе мы поговорим о стандартных классах C++, управляющих работой этих объектов.

Иерархия классов ввода-вывода

Все потоковые классы порождены от одного класса, являющегося базовым в иерархии, — ios. Исключение составляют лишь классы буферизованных потоков, базовым для которых служит класс streambuf. Всех их можно разделить на четыре категории, как показано в табл. 15.1.

На рис. 15.1 схематически изображена иерархия классов ввода-вывода, порожденных от класса ios.

Классы семейства ios предоставляют программный интерфейс и обеспечивают необходимое форматирование обрабатываемых данных, тогда как непосредственную обработку данных выполняют классы семейства streambuf, управляющие обменом данными между буфером потока и конечным устройством (рис. 15.2).

Таблица 15.1. Категории классов ввода-вывода
Класс Описание
ios         Содержит базовые средства управления потоками, является родительским
для других классов ввода-вывода (файл IOSTREAM.H)
Потоковый ввод
istream  Содержит общие средства потокового ввода, является родительским для
других классов ввода (файл IOSTREAM.H)
ifstream  Предназначен для ввода данных из файлов (файл FSTREAM.H)
istream_withassign    Поддерживает операцию присваивания; существует предопределенный объект cin данного класса, по умолчание читающий данные из стандартного входного потока, но благодаря операции присваивания этот объект может быть переадресован на различные объекты класса istream(файл IOSTREAM.H)
istrstream Предназначен для ввода данных из строковых буферов (файл STRSTREA.H)
Потоковый вывод
ostream  Содержит общие средства потокового вывода, является родительским для других классов вывода (файл IOSTREAM.H)
ofstream Предназначен для вывода данных в файлы (файл FSTREAM.H)
ostream_withassign  Поддерживает операцию присваивания; существуют предопределенные объекты cout, cerr и clog данного класса, по умолчанию выводящие данные в стандартный выходной поток, но благодаря операции присваивания их можно переадресовать на различные объекты класса ostream (файл IOSTREAM.H)
ostrstream  Предназначен для вывода данных в строковые буферы (файл STRSTREA.H)
Потоковый ввод-вывод
iostream  Содержит общие средства потокового ввода-вывода, является родительским для других классов ввода-вывода (файл IOSTREAM.H)
fstream   Предназначен для организации файлового ввода-вывода (файл FSTREAM.H)
strstream Предназначен для ввода-вывода строк (файл STBLSTREA.H)
stdiostream Поддерживает работу с системными средствами стандартного ввода-вывода, существует для совместимости со старыми функциями ввода-вывода (файл STDIOSTR.H)
Буферизованные потоки
streambuf Содержит общие средства управления буферами потоков, является родительским для других буферных классов (файл IOSTREAM.H)
filebuf Предназначен для управления буферами дисковых файлов (файл FSTREAM.H)
strstreambuf  Предназначен для управления строковыми буферами, хранящимися в памяти (файл STRSTREA.H)
stdiobuf  Осуществляет буферизацию дискового ввода-вывода с помощью стандартных системных функций (файл STDIOSTR.H)


Рис. 15.1. Иерархия классов ввода-вывода порожденных от ios


Рис. 15.2. Иерархия классов, порожденных от streambuf

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

Основные функции управления потоковым вводом сосредоточены в классе istream. С каждым из объектов этого класса и его производных связан объект класса streambuf. Эти классы работают в связке: первый осуществляет форматирование, а второй управляет низкоуровневым буферизованным вводом. Функции класса istream, доступные его потомкам, перечислены в табл. 15.2.

Функция Описание
ipfx Вызывается перед операцией чтения для проверки наличия ошибок в потоке
isfx Вызывается после каждой операции чтения
get Извлекает из потока требуемое число символов; если указан символ-ограничитель, он не извлекается
getline Извлекает из потока требуемое число символов; если указан символ-ограничитель, он извлекается, но не сохраняется в буфере
read Извлекает из потока требуемое число байтов; применяется при работе с двоичными потоками
ignore Выбрасывает из потока требуемое число символов вплоть до символа-ограничителя
peek Возвращает текущий символ, сохраняя его в потоке
gcount  Определяет число символов, извлеченных из потока во время последней операции чтения
eatwhite Извлекает из потока ведущие пробельные символы; аналогичное действие выполняет манипулятор ws
putback Возвращает в поток символы, извлеченные из него во время последней операции чтения
sync  Синхронизирует внутренний буфер потока с внешним источником символьных данных
seekg Перемещает маркер, обозначающий текущую позицию чтения, на требуемую позицию в потоке
tellg Возвращает позицию маркера чтения
Таблица 15.2. Функции класса istream

Класс ifstream является потомком класса istream, ориентированным на чтение данных из файлов. Его конструктор автоматически создает объект класса filebuf, управляющий низкоуровневой работой с файлом, включая поддержку буфера чтения. Функции класса ifstream перечислены в табл. 15.3.

Таблица 15.3. Функции класса ifstream

ФУНКЦИЯ Описание
open Открывает файл для чтения, связывая с ним объект класса filebuf
close Закрывает файл
setbuf Передает указанный символьный буфер в распоряжение объекта класса filebuf
setmode Задает режим доступа к файлу: двоичный (константа filebuf:: binary) или текстовый (константа filebuf:: text)
attach Связывает указанный открытый файл с объектом класса filebuf
rdbuf Возвращает указатель на объект класса filebuf  
fd Возвращает дескриптор файла
is_open Проверяет, открыт ли файл, связанный с потоком

В следующей программе из файла читаются данные блоками по 80 символов, которые затем выводятся на экран:

//
// ifstream.cpp    
//В этой программе на языке C++ демонстрируется
// использование класса ifstreamдля чтения данных из файла. 
//

#include <fstream.h>

#define iCOLUMNS 80  

void main(void)

{
  charcOneLine[iCOLUMNS];

 fstream ifMyInputStream("IFSTREAM.CPP");

while(ifMylnputStream)    {

ifMylnputStream.getline(cOneLine,    iCOLUMNS);

cout <<   '\n'   << cOneLine;     

}

 ifMylnputStream.close();

}

Конструктор клаccа ifstream создает объект ifMylnputStream, связывая с ним файл IFSTREAM.CPP, который открывается для чтения (по умолчанию в текстовом режиме). Этот объект можно использовать в условных операторах, проверяя его на равенство нулю, что означает достижение конца файла.

Функция getline(), унаследованная от класса istream, читает в массив cOneLineстроку текста длиной iCOLUMNS(80 символов). Ввод будет прекращен при обнаружении символа новой строки \n, конца файла или, если ни одно из этих событий не произошло, 79-го по счету символа (последним записывается символ \0).

После окончания вывода всего файла он закрывается командой close().

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

Основные функции управления потоковым выводом сосредоточены в классе ostream. С каждым из объектов этого класса и его производных связан объект класса streambuf. Эти классы работают в связке: первый осуществляет форматирование, а второй управляет низкоуровневым буферизованным выводом. Функции класса ostream, доступные его потомкам, перечислены в табл. 15.4.

Таблица 15.4. Функции класса ostream
Функция Описание
opfx Вызывается перед каждой операцией записи для проверки наличия ошибок в потоке
оsfx  Вызывается после каждой операции записи для очистки буфера
put    Записывает в поток одиночный байт
write Записывает в поток требуемое число байтов
flush Очищает буфер потока; аналогичное действие выполняет манипулятор flush
seekp Перемещает маркер, обозначающий текущую позицию записи, на требуемую позицию в потоке
tellp Возвращает позицию маркера записи

Класс ofstream является потомком класса ostream, ориентированным на запись данных в файлы. Его конструктор автоматически создает объект класса filebuf, управляющий низкоуровневой работой с файлом, включая поддержку буфера записи. Класс ofstream содержит такой же набор функций, что и класс ifstream(см. табл. 15.3).

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

//
// ofstream. cpp        
// В этой программе на языке C++ демонстрируется
// использование класса ofstreamдля записи данных в
// файл, открытый в текстовом режиме.

#include <fstream.h>

#include <string.h>

#define iSTRING_MAX 40

void main (void) {

int i = 0;

char pszString [iSTRING_MAX] •= "Записываемая строка\n";

// файл по умолчанию открывается в текстовом режиме
ofstream ofMyOutputStream("OFSTREAM.OUT") ;

// строка выводится символ за символом;
// обратите внимание, что символ '\n' .
// преобразуется в два символа
while (pszString[i] != '\0'){

ofMyOutputStream.put (pszStringfi] ) ;
cout<< !'\nПозиция маркера записи: " << ofMyOutputStream.tellp() ;

i++; }

// запись всей строки целиком

ofMyOutputStream.write(pszString, strlen(pszString));
cout<< "\nНовая позиция маркера записи: " << ofMyOutputStream.tellp();

ofMyOutputStream.close() ; -}

Вот результаты работы программы:

Позиция маркера записи:      1
Позиция маркера записи:      2
Позиция маркера записи:      3
.
.
.
Позиция маркера записи: 18
Позиция маркера записи: 19
Позиция маркера записи: 21
Новая позиция маркера записи: 42

Цикл while последовательно, символ за символом, с помощью функции put() записывает содержимое строки pszstring в выходной поток. После вывода каждого символа Вызывается функция tellp(),возвращающая текущую позицию маркера записи. Результатам работы этой функции следует уделить немного внимания.

Строка pszString содержит 20 символов плюс концевой нулевой символ (\0), итого — 21. На хотя, если судить по выводимой информации, в поток записывается 21 символ, последним из них будет не \0. Его вообще не окажется в файле. Дело в том, что при выводе данных в файл, открытый в текстовом режиме, автоматически выполняется преобразование \n = CR/LF, т.е. символ новой строки преобразуется в пару символов возврата каретки и перевода строки. (При чтении происходит обратное преобразование.) Именно поэтому наблюдается "скачок" счетчика: после 19 идет 21. Функция put(), записывающая в файл символ \n, на самом деле помещает в файл два других символа.

Функция write() выполняет такое же преобразование, поэтому конечное значение счетчика равно 42, а не 41.

Двоичные файлы

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

//
//       binary.срр
//       Эта программа является модификацией предыдущего примера
//       и демонстрирует работу с файлом,открытым в двоичном режиме.
//

#include <fstream. h>
#include <string.h>

#define iSTRING_MAX 40

void main (void) <<

int i = 0;

char pszString [iSTRING_MAX] = "Записываемая строка\n";

// файл открывается в двоичном режиме

ofstream ofMyOutputStream("OFSTREAM.OUT", ios :: binary)

// строка выводится символ за символом;
// обратите внимание, что символ ' \n '
// никак не преобразуется
while (pszString [i]!= '\0') {

ofMyOutputStream.put (pszString [i]);

cout <<' "\nПозиция маркера записи: << ofMyOutputStream.tellpO ;

i++> 1

// запись всей строки целиком         .

ofMyOutputStream.write(pszString, strlen(pszString)),

cout<< "\nНовая позиция маркера записи: "

<< ofMyOutputStream.tellp() ;
ofMyOutputStream.close() ;
}

Вот результаты работы программы:

Позиция маркера записи:  1

Позиция маркера записи:  2

Позиция маркера записи:  3

Позиция маркера записи: 18

Позиция маркера записи: 19

Позиция маркера записи: 20

Новая позиция маркера записи: 40

При выводе строки pszString не происходит замены концевого символа \n парой символов возврата каретки и перевода строки, поэтому в поток записывается 20 символов — ровно столько, сколько содержится в строке.

Буферы потоков

В основе всех буферизованных потоков ввода-вывода в C++ лежит класс streambuf. В этом классе описаны основные операции, выполняемые над буферами ввода-вывода (табл. 15.5). Любой класс, порожденный от ios, наследует указатель на объект класса streambuf. Именно последний выполняет реальную работу по вводу и выводу данных.

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

Класс streambuf является абстрактным, т.е. создать его объект напрямую нельзя (его конструктор является защищенным и не может быть вызван из программы). В то же время, имеются три производных от него класса, предназначенные для работы с потоками конкретного типа: filebuf (буферы дисковых файлов), strstreambuf (строковые буферы, хранящиеся в памяти) и stdiobuf (буферизация дискового ввода-вывода с помощью стандартных системных функций). Кроме того, можно создавать собственные классы, порожденные от streambuf, переопределяя его виртуальные функции (табл. 15.6) и настраивая, таким образом, его работу в соответствии с потребностями конкретного приложения. Только из производных классов можно вызывать и многочисленные защищенные функции этого класса, управляющие областью резервирования (табл. 15.7).

Таблица 15.5. Открытые функции класса streambuf
Открытая функция Описание
in_avail Возвращает число символов в области ввода
sgetc Возвращает символ, на который ссылается указатель области ввода; при этом указатель не перемещается
snextc Перемещает указатель области ввода на одну позицию вперед, после чего возвращает текущий символ
sbumpc Возвращает текущий символ и затем перемещает указатель области ввода на одну позицию вперед
stossc Перемещает указатель области ввода на одну позицию вперед, но не возвращает символ
sputbaqkc Перемещает указатель области ввода на одну позицию назад, возвращая символ в буфер
sgetn Читает требуемое количество символов из буфера
out_waiting Возвращает число символов в области вывода
sputc Записывает символ в буфер и перемещает указатель области вывода на одну позицию вперед
sputn Записывает требуемое количество символов в буфер и перемещает указатель области вывода на соответствующее число позиций
dbp В текстовом виде записывает в стандартный выходной поток различного рода информацию о состоянии буфера

Таблица 15.6. Виртуальные функции класса streambuf
Виртуальная функция Описание
sync Очищает области ввода и вывода
setbuf Добавляет к буферу указанную зарезервированную область памяти
seekoff Перемещает   указатель   области   ввода   или   вывода   на   указанное количество байтов относительно текущей позиции
seekpos Перемещает указатель области ввода или вывода на указанную позицию относительно начала потока
overflow Очищает область вывода
underflow Если область ввода пуста, заполняет ее данными из источника
pbackfail Вызывается функцией sputbackc() в случае неудачной попытки вернуться назад на один символ

Таблица 15.7. Защищенные фугкции класса streambuf
Защищенная функция Описание
base Возвращает указатель на начало области резервирования
ebuf Возвращает указатель на конец области резервирования
blen Возвращает размер области резервирования
phase Возвращает указатель на начало области вывода
pptr Возвращает значение указателя области вывода
epptr Возвращает указатель на конец области ввода
eback  Возвращает указатель на начало области ввода
gptr Возвращает значение указателя области ввода
egptr Возвращает указатель на конец области вывода
setp Задает значения указателей, связанных с областью вывода
setg  Задает значения указателей, связанных с областью ввода
pbump Перемещает указатель области вывода на указанное число байтов относительно текущей позиции
gbump Перемещает указатель области ввода на указанное число байтов относительно текущей позиции
setb Задает значения указателей, связанных с областью резервирования
unbuffered  Задает или возвращает значение переменной, определяющей состояние буфера
allocate Вызывает виртуальную функцию deallocate для создания области резервирования
deallocate Выделяет память для области резервирования (виртуальная функция)

Классы файловых потоков, такие как ifstream, ofstream и fstream, содержат встроенный объект класса filebuf, который вызывает низкоуровневые системные функции для управления буферизованным файловым вводом-выводом. Для объектов класса filebuf области ввода и вывода, а также указатели текущей позиции чтения и записи всегда равны друг другу. Функции этого класса перечислены в табл. 15.8.

Таблица 15.8.Функции класса filebuf
Функция Описание
open Открывает файл, связывая с ним объект класса filebuf
close Закрывает файл, "выталкивая" все содержимое области вывода
setmode Задает режим доступа к файлу: двоичный (константа filebuf:: binary) или текстовый (константа filebuf: :text)
attach Связывает указанный открытый файл с объектом класса filebuf
fd Возвращает дескриптор файла
is_open Проверяет, открыт ли файл, связанный с потоком

В следующей программе создаются два файловых объекта: fbMylnputBuf и fbMyOutputBuf. Оба они открываются в текстовом режиме с помощью функции open(): первый — для чтения, второй — для записи. Если при открытии файлов не возникнет никаких ошибок, то каждый из них связывается с соответствующим объектом класса istream и ostream. Далее в цикле while символы читаются из файла fbMylnputBuf с помощью функции get() класса istream и записываются в файл fbMyOutputBuf с помощью функции put() класса ostream. Подсчет числа строк осуществляется с помощью выражения

iLineCount  +=   (ch ==   '\n');   

Когда в поток помещается символ \n, выражение в скобках возвращает 1 и счетчик iLineCount увеличивается на единицу, в противном случае он остается неизменным.

Оба файла закрываются с помощью функции close().

//
//   filebuf.cpp
//   Эта программа на языке C++ демонстрирует, как работать
//   с объектами класса filebuf.
//

#include <f stream. h>

t#include <process.h>
// содержит прототип функции exit()

void main (void) 

{

char ch;

int iLineCount = 0;

filebuf fbMylnputBuf, fbMyOutputBuf;

fbMylnputBuf .open ("FILEBUF.CPP", ios::in); if (fbMylnputBuf .is_open()== 0) (

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

exit (1);

}

istream is(SfbMylnputBuf) ;

fbMyOutputBuf.open("output.dat",ios::out); if(fbMyOutputBuf.is_open() == 0) {

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

exit(2); }

ostream os(SfbMyOutputBuf);

while (is) {

is.get(ch);

os.put(ch);  

iLineCount += (ch== '\n');

}

bMylnputBuf. close ();
fbMyOutputBuf.close();

cout << "Файл содержит " << iLineCount << " строк";

}

Cтроковые буферы

Класс strstreambuf управляет символьным буфером, расположенным в динамической памяти.

Несколько примеров форматного вывода данных

В первом примере на экран выводится список факториалов чисел от 1 до 25 с выравниванием влево:

//
//  fact.cpp
//  Эта программа на языке C++ выводит список факториалов чисел от 1 до 25.
//

#include <iostream.h>
main ()

{
double number = 1.0,factorial = 1.0;

cout .precision (0);  //0 цифрпослезапятой

cout .setf (ios:: left) ;    //  выравнивание влево

cout .setf (ios:: fixed) ;   //     фиксированный формат (безэкспоненты)

for(int i << 0; i < 25;i++) {

factorial *= number++;

cout << factorial << endl; }

return(0);
}      
 

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

1

2

6

24

120

720

5040

40320

362880

3628800

39916800

479001600

6227020800

87178291200

1307674368000

20922789888000

355687428096000

6402373705728000

121645100408832000

2432902008176640000

51090942171709440000 

1124000727777607680000

25852016738884976640000

620448401733239439360000  

15511210043330985984000000

Во втором примере на экран выводится таблица квадратов и квадратных корней целых чисел от 1 до 15.

//
//       sqrt.cpp
//  Эта программа на языке C++ строит таблицу квадратов и квадратных
//  корней чисел от 1 до 15.
//

#include <iostream.h>
# include <math.h>

main () {

double number =1.0,square, sqroot;

cout << "Число\tKвaдрат\t\tKвадратный корень\n";

cout <<  " ______________________________________ \n";

cout .setf (ios:: fixed) ; // фиксированный формат (без экспоненты)

for(int i = 1; i < 16;i++) {

square = number * number; // вычисление квадрата числа
sqroot = sqrt(number);   // нахождение корня числа

cout.fill('0');    // заполнение недостающих позиций нулями
cout.width(2);    // ширина столбца — минимум 2 символа
cout.precision(0); // 0 цифрпосле запятой
cout << number << "\t";

cout.fill(' ');   // использовать пробел в качестве заполнителя cout << square << "\t\t";

cout.precision(6);  // 6 цифр после запятой
cout<< sqroot<< endl;

number++; }

return(0); }

Программа выведет следующую таблицу чисел:

число  корень  квадратный корень

01

1

1.000000

02

4

1.414214

03

9

1.732051

04

16

2.000000

05

25

2..236068

06

36

2.449490

07

49

2.645751

08

64

2.828427

09

81

3.000000

10

100

3.162278

11

121

3.316625

12

144

3.464102

13

169

3.605551

14

196

3.741657

15

225

3.872983