Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Подбельский учебник с++ / Подбельский - главы 10-12

.pdf
Скачиваний:
28
Добавлен:
22.05.2015
Размер:
1.46 Mб
Скачать

416

Язык Си++

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

ostreamS ostream::put(char ее);

оstreams ostream::write(const signed char *array, int n); ostreamS ostream::write(const unsigned char *array, int n);

Функция put () помещает в тот выходной поток, для которого она вызвана, символ, использованный в качестве фактического параметра.

В этом случае эквивалентны операторы:

cout « •z';

и

cout . put('z');

Функция write () имеет два параметра - указатель array на участок памяти, из которого выполняется вывод, и целое значение п, •шределяющее количество выводимых из этого участка символов (байт).

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

Так как функции put() и write () возвращают ссылки на объект того класса, для которого они выполняются, то можно организовать цепочку вызовов:

char s s [ ] = "Merci";

c o u t . p u t ( ' \ n ' ) . w r i t e ( s s , s i z e o f ( s s ) - 1 ) . p u t ( ' ! ' ) . p u t ( ' \ n ' ) ;

На экране (в потоке cout) появится:

Merci!

Если необходимо прочитать из входного потока строку символов, содержащую пробелы, то с помощью операции извлечения » это делать неудобно - каждое чтение строки выполняется до пробела, а ведущие (левые) пробельные символы игнорируются. Если мы хотим, набрав на клавиатуре строку: "Qui vivra verra - будущее покажет (лат.) ", ввести ее в символьный массив, то с помощью операции из-

Глава 11. Ввод-вывод в языке Си++

417

влечения » это сделать несколько хлопотно, все слова будут читаться отдельно (до пробела). Гораздо удобнее воспользоваться функциями бесформатного (двоичного)чтения.

Функции двоичного (бесформатного) чтения данных принадлежат потоку istream. Прежде чем перечислить их, отметим основное свойство двоичного чтения данных. Данные читаются без преобразования их из двоичного представления в текстовое. Например, если во входном потоке размещено представление вещественного числа 1. ЗЕ-З, то это будет воспринято как последовательность из шести байт, и читать эту последовательность с помощью функций двоичного ввода можно только в символьный массив.

Итак, функции чтения. Во-первых, это 6 перегруженных функций get (). Две из них имеют следующие прототипы:

istreamS get(signed char *array, int max_len, char='\n'); istreamfi get(unsigned char *array, int шах len, char='\n');

Каждая из этих функций выполняет извлечение (чтение) последовательности байтов из стандартного входного потока и перенос их в символьный массив, задаваемый первым параметром. Второй параметр определяет максимально допустимое количество прочитанных байтов. Третий параметр определяет ограничивающий символ (байт), при появлении которого во входном потоке следует завершить чтение. По умолчанию третий параметр имеет значение '\п' - переход на следующую строку, однако при обращении к функции его можно задавать и по-другому. Значение этого третьего параметра из входного потока не удаляется, он в формируемую строку (символьный массив) не переносится, а вместо него автоматически добавляется "концевой" символ строки '\0'. Если из входного потока извлечены ровно max_len - 1 символов, однако ограничивающий символ (например, по умолчанию р \п') не встретился, то концевой символ помещается после введенных символов. Массив, в который выполняется чтение, должен иметь длину не менее max_len символов. Если из входного потока не извлечено ни одного символа, то .устанавливается код ошибки. Если до появления ограничивающего символа и до извлечения max_ien - 1 символов встретился конец файла EOF, TO чтение прекращается как при появлении ограничивающего символа.

Функция с прототипом

1 s t r e a m s g a t ( a t r e a m b u f t b u f , c h a r = ' \ n ' ) ;

извлекает из входного потока символы и помещает их в буфер, опре-

деленный первым параметром. Чтение продолжается до появления

27-2432

418

Язык Си++

 

ограничивающего символа, которым по умолчанию является

1 \ п \ но он

может быть установлен явно любым образом.

 

Три следующие

варианта функции get О позволяют

прочесть из

входного потока один символ. Функции

istreamfi get(unsigned charS cc); л.streams get (signed chart cc) ;

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

int get();

получает код извлеченного из потока символа в качестве возвращаемого значения. Если поток пуст, то возвращается код конца файла EOF.

Функции "ввода строк":

1streams getline(signed char *array, int len, char='\n'); istreamS getline(unsigned char *array, int len, char='\n');

подобны функциям get О с теми же сигнатурами, но переносят из входного потока и символ-ограничитель.

Функция

int peek () ;

позволяет "взглянуть" на очередной символ входного потока. Точнее, она возвращает код следующего символа потока (или EOF, если поток пуст), но оставляет этот символ во входном потоке. При необходимости этот символ можно в дальнейшем извлечь из потока с помощью других средств библиотеки. Например, следующий цикл работает до конца строки (до сигнала от клавиши Enter):

char cim;

while(cin.peeM) '= '\n')

{ cin.get(cim); cout.put(cim); }

Принадлежащая классу istream функция

istreamt putback(char cc) ;

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

Аналогичным образом функция

int gcount();

Глава 11 Ввод-вывод в языке Си++

подсчитывает количество символов, которые были извлечены из входного

потока при последнем обращении к нему.

"водного

Функция

 

istreamS ignore(int n = 1,

EOF) ;

 

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

Функции

istreams read(signed char *array, int numb);

istreamb read(unsigned char *array, int numb);

выполняют чтение заданного количества numb символов в массив array.

Полезны следующие функции того же класса istream: 1streams seekg(long pos);

устанавливает позицию чтения из потока в положение, определяемое значением параметра.

istreamS seekg(long pos, seek_dir dir);

выполняет перемещение позиции чтения вдоль потока в направлении, определенном параметром dir, принимающим значения из перечисления enum seekdir { beg, cur, end }. Относительная величина перемещения (в байтах) определяется значением параметра long pos. Если направление определено как beg, то смещение от начала потока; cur - от текущей позиции; end - от конца потока;

long tellg()

определяет текущую позицию чтения из потока.

Подобные перечисленным функции класса ostream: long tellp()

определяет текущую позицию записи в поток; ostreams seekp(long pos, seek_dir dir);

аналогична функции seekgO , но принадлежит классу ostream и

выполняет относительное перемещение позиции записи в поток; ostreams seekpdong pos) ;

устанавливает абсолютную позицию записи в поток.

420

Язык Си++

11.6. Строковые потоки (обмены в основной памяти)

Классы istrstream, ostrstream, strstream, определяемые в заголовочном файле strstream.h (он в компиляторах под MS-DOS имеет более короткое название strstrea.h, так как длина имени файла в MS-DOS не может превышать 8 символов), предназначены для создания потоков, связанных с участками (областями) основной памяти. Достаточно часто такие участки памяти определяются в программе как символьные массивы. Именно поэтому в обозначениях указанных классов используется абревиатура (приставка) "str" - сокращение английского слова string (строка), а объекты этих классов называютстроковымипотоками.

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

имя_класса имя_потока(параметры_конструктора);

Имя_класса в данном случае - это одно из имен istrstream, ostrstream, strstream. Имя_потока - это идентификатор (произвольно выбираемое программистом имя объекта данного класса). Типы параметров и их число различны для конструкторов разных классов.

Входные строковые потоки. Эти потоки создаются с помощью такого конструктора класса istrstream:

istrstream имя_потока(char *str);

Обязательным параметром конструктора объектов класса istrstream является указатель str на уже существующий участок основной памяти.

Например, следующие операторы

char buf[40] ; istrstream inBuf(buf);

определят входной строковый поток с именем inBuf и свяжут его с участком памяти, предварительно выделенным для символьного массива buf []. Теперь этот строковый поток inBuf может использоваться как левый операнд операции извлечения ».

В следующей программе определена строка, содержащая символьную информацию "123.5 Salve"", затем определен и связан с этой строкой входной строковый поток instr. Из потока instr и тем са-

Глава 11. Ввод-вывод в языке Си++

421

мым из строки, адресуемой указателем stroke, разделенные пробелами значения переносятся в переменную real типа double и символьный массив array [10]. Текст программы:

//Р11-07.СРР - строковые потоки, операция извлечения » // Автоматически включается файл юзtream.h:

#include <strstrea.h> void main()

{ // Выделена область памяти (строка):

char *stroJca = "123.5 Salve!";

// Создан входной строковый поток instr: istrstream instr(stroka);

char array[10]; double real;

// Извлекаем информацию из строкового потока:

instr

» real

»

array;

 

// Вывод на экран:

 

cout «

"\narray

= " « array « "

real = " «

 

real

« endl;

 

)

Результат выполнения программы (на экране): array = Salve! real = 123.5

В следующем примере с помощью входного строкового потока выполняется чтение информации, передаваемой в качестве параметра командной строки функции main:

//Р11-08.СРР - входной строковый поток; чтение аргумента

//

 

функции main()

 

 

// Автоматически включается файл iostream.h: -

•include

<strstrea.h>

 

 

void main(int argc, char *argv[])

// Определены аргументы

(

char name[40];

// Выделяется область памяти

 

// Создает строковый поток input:

 

istrstream input(argv[l]);

 

 

 

// Извлекаем информацию из строкового потока:

 

input

»

name;

 

 

 

// Вывод в стандартный поток

(на

экран):

}

cout

«

"\пПри вызове аргумент

=

" « name « endl;

Если программа будет запущена на выполнение директивой:

С:\>Р11-О8.ЕЗОЕ FileName <Enter>

422 Язык Си++

то на экране появится сообщение: При вызове аргумент = FileName

Извлечение информации из строкового потока с помощью операции » выполняется, начиная от первого непробельного символа до ближайшего пробела (точнее до ближайшего обобщенного пробельного символа). Если необходимо читать и пробельные символы, то можно воспользоваться функциями бесформатного обмена get() и g e t l i n e ( ) . Эти функции, вызываемые для символьных потоков, позволяют организовать, например, копирование строк. Для копирования строк в библиотеке языка Си существует специальная функция strcpyO, определенная в заголовочном файле string . h . Копирование несложно организовать и с помощью циклического переноса символов из одного массива в другой. Однако интересный вариант копирования получается при использовании строковых потоков. Например, в следующей программе выполняется "копирование" содержимого строки, адресуемой указателем line, в заранее созданный

символьный массив

array[] . В соответствии с форматом функции

g e t l i n e O ее первый

параметр - массив, в который ведется чтение,

второй параметр - предельное количество читаемых символов, третий параметр - ограничивающий символ, после извлечения которого обмен прекращается. Функция g e t l i n e O вызывается для чтения из потока inpotok, связанного со строкой line. Текст программы:

//Р11-09.СРР - копирование строки функцией getlineO. #include <strstrea.h>

void main()

{ char *line = "000 111 \t222\n333\t444 555"; istrstream inpotok(line);

char array[80]; inpotole.getline(array,sizeof(array),'\0'); cout « "\narray = " « array « endl;

Результат выполнения программы:

array

000 111

222

333444 555

Врезультатах выполнения обратите внимание на влияние символов ' \ t \ ' \ n ' , присутствующих как в исходной строке line, так и перенесенных в символьный массив array. Они, естественно, не выводятся на экран, а обеспечивают табуляцию и смену строки.

Глава 11. Ввод-вывод в языке Си++

423

Безымянные входные строковые потоки. Конструктор класса istrstream позволяет создавать и связывать с заданной областью памяти безымянные входные строковые потоки. Например, чтение информации, переданной при запуске функции main() в командной строке, обеспечит следующая программа:

//Р11-10.СРР - безымянный входной строковый поток; чтение

//

данных

с помощью операции извлечения »

(•include

<strstrea.h>

 

void main(int Narg,

char *arg[])

( char path[80] ;

 

 

// Чтение из безымянного потока:

istrstream(arg[0])

»

path;

cout «

"\пПолное

имя

программы:\n" « path « endl;

)

 

 

 

Результат выполнения программы, например, такой:

Полное имя программы: D:\WWP\TESTPROG\P11-10.EXE

По соглашениям языка и операционной системы arg[0] всегда содержит полное наименование программы с указанием диска и полного пути к каталогу, где находится ЕХЕ-файл. Вызов конструктора istrstream (arg [0]) создает безымянный объект - входной строковый поток, связанный с символьным массивом, адрес которого передается как значение указателя arg[0]. К этому потоку применима операция извлечения », с помощью которой полное наименование программы переносится

ввиде строки из arg [ 0 ] в символьный массив path [ ].

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

//Р11-11.СРР - чтение из безымянных строковых потоков

^include <strstrea.h> void main()

{ char *line = "000 111 \t222\n333\t444 555"; char array[80]; // Вспомогательный массив

//Чтение из безымянного потока до пробела: istrstream(line) » array;

cout « "\narray = " « array « endl; //Повторное чтение из безымянного потока: istrstream(line) » array;

cout « "\narray = " « array « endl;

//Вызов функции getlineO для безымянного потока:

424

Язык Си++

istrstream(line) .getline(array,sizeof(array),'\0') cout « "array = " « array « endl;

Результат выполнения программы:

array = 000

 

 

array = 000

 

 

array = 000

111

222

333

444

555

 

Из первого безымянного потока данные в массив array извлекаются (до первого пробела) операцией ». Второй безымянный поток связан с той же строкой. Из него в массив array снова с начала строки читается та же самая информация. Третий безымянный поток читается с помощью функции getline О, которая полностью копирует строку line в символьный массив array[]. Извлечение данных функцией getline() продолжается до появления нулевого признака конца строки ' \ 0 ' . Этот символ также переносится в массив array и служит признаком конца созданной строки.

Выходные строковые потоки. Эти потоки обычно создаются с помощью такого конструктора класса ostrstream:

ostrstream имя_потока(char *str, int len, int mode);

Необязательное имя_потока - это идентификатор, произвольно выбираемый программистом. Указатель str должен адресовать уже существующий участок памяти. Параметр int len определяет размеры этого участка (буфера). Последний параметр - индикатор режима обмена mode. Режим обмена строкового потока при выводе определяет размещение информации в связанной с потоком строке. Для задания конкретного режима используется флаг или дизъюнкция нескольких флагов:

ios::out

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

ios::ate

позиция записи устанавливается в месте размещения нулевого признака конца строки ' \0' (запись в продолжение);

ios::арр

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

Глава 11 Ввод-вывод в языке Си++

Вместе с флагом ios: :out могут быть указаны (в дизъюнктивной форме) флаги i o s : : ate или ios : : арр. В обоих случаях при формировании потока позиция записи устанавливается на нулевой признак '\0' конца строки (буфера потока), т.е. предполагается запись в конец потока.

По умолчанию выбирается ios : : out, т.е. строковый поток создается для вывода (записи) информации с начала строки (буфера).

Начнем рассмотрение основных возможностей выходных строковых потоков с их безымянного варианта. В следующей программе значение строки обязательного аргумента функции main с помощью операции вставки « переносится в безымянный строковый поток, связанный с символьным массивом path[]:

//Р11-12.СРР - запись в безымянный выходной строковый

//

поток («)

 

#include

<strstrea.h>

 

void main(int Narg, char *arg[])

 

{ char path[80];

 

ostrstream (path, sizeof (path) ) «

arg[0] « '\C ;

cout «

"\пПолное имя программы:

" «

)

path « endl;

 

 

 

Результат выполнения программы:

Полное имя программы: D:\WWP\TESTPROG\P11-12.EXE

Так как операция включения « не переносит в выходной поток признак конца строки • \0', то его пришлось явным образом поместить в выходной поток (тем самым в буфер path [ ]).

Функция write() применительно к выходному потоку позволяет записывать в него данные без форматирования, т.е. строка записывается вместе с пробельными символами и символом конца строки '\0' . Чтобы продемонстрировать особенности ее применения к безымянным выходным строковым потокам, рассмотрим следующую программу:

//Р11-13.СРР - запись в безымянный выходной строковый

//поток; копирование строки с использованием

//функции write()

^include <strstrea.h> void main()

{ char lat[] = "Quod erat demonstrandum!";

char rus[] = " - Что и требовалось доказать!";

426

Язык Си++

char result[60]; ostrstream(result,sizeof(result)).write(lat,sizeof(lat)) ostrstream(result,

sizeof(result),ios::ate).write(rus,sizeof(rus)) cout « "\n" « result « endl;

Результат выполнения программы;

Quod erat demonstrandum! - Что и требовалось доказать!

В программе два безымянных потока, каждый из которого "настроен" на символьный массив r e s u l t [ ] . При создании первого безымянного

потока в

качестве параметров конструктора указываются - массив

r e s u l t []

и его размеры, т.е. длина массива в байтах. Функция write ()

"присоединяется" с помощью операции "точка" непосредственно к конструктору и тем самым вызывается для созданного им безымянного потока. В качестве фактических параметров функции write () используются указатель lat на строку и количество записываемых символов. Так как длина строки меньше длины буфера result, то буфер не заполняется целиком. При создании второго безымянного потока, кроме буфера r e s u l t и его длины, в конструкторе (в качестве третьего параметра) использован флаг ios: :ate, под действием которого поток создается как "дополняемый". Тем самым последующая запись в поток выполняется не с начала потока, а начиная с позиции окончания предыдущей записи ' \ 0 \ Именно туда функция write() помещает строку, адресованную указателем rus. Тем самым в массиве result [] осуществляется конкатенация строк, что видно из результатов. Из массива result [] вывод в поток cout выполняется до появления символа конца строки ' \0' в массиве result .

Следующая программа иллюстрирует особенности последовательного вывода в строковый поток данных разных типов с помощью операции включения:

//Р11-14.СРР - вывод Е строковый поток операцией « #include<strstrea.h>

void main()

{ char buffer[180];

ostrstream outstring(buffer,sizeof(buffer), ios::out|ios::ate);

outstring « "\пБеэ явного включения разделителей" « " текст в потоке\п\"сливается\":\п ";

outstring « 123456789 « -456 « +1.23456789; outstring « -0.1234567898+1 « +123.456789е-3 « ends;

Глава 11. Ввод-вывод в языке Си++

427

 

cout « "\n" « buffer « endl;

 

Результат выполнения программы:

Без явного включения разделителей текст в потоке ||сливается" :

123456789-456 1.23456789-1.2345678 0.123457

Как показывают результаты, последовательное обращение к строковому потоку приводит к записи "в продолжение", т.е. указатель позиции записи при создании потока устанавливается на его начало, а затем перемещается на длину каждой новой записи. Никаких промежутков или разделителей между выводимыми данными не добавляется. Более того, операция включения в поток « даже не переносит в него нулевой ограничитель конца строки ' \ 0 ' . Поэтому его нужно добавлять явно, если в дальнейшем требуется использовать буфер потока в качестве строки. Числовая информация, включаемая в поток операцией «, форматируется. При этом знак + для чисел заменяется пробелом, вещественные числа, заданные в экспоненциальной форме (в научной нотации), переводятся в форму с фиксированной точкой. Выполняется округление дробной части вещественного числа. Перечисленные и проиллюстрированные результатами особенности форматирования могут быть изменены с помощью средств управления форматом как и для стандартных потоков.

Двунаправленные строковые потоки. Основной конструктор строковых потоков, создаваемых как для чтения, так и для записи, имеет следующий формат:

strstream имя_потока(char *buf, int lenBuf, int mode);

где

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

lenBuf - длина в байтах участка памяти;

mode - индикатор режима обмена с создаваемым потоком. В качестве индикатора режима обмена используется дизъюнкция флагов, принадлежащих классу ios. Флаги ios: :in и ios: :out определяют направление обмена. Флаги i o s : : a t e и ios:app влияют на размещение указателя позиции чтения/записи в буфере и т.д.

428

Язык Си++

В следующей программе с символьным массивом buffer [] связывается двунаправленный поток string. Затем последовательно выполняются операции записи в поток и чтения из потока.

//Р11-15.СРР - ввод и вывод для двунаправленного // строкового потока

tinclude <strstrea.h> void main()

{ char buffer[180];

char stroka[150], ss[150];

//Строковый поток string связан с массивом buffer: strstream string(buffer,sizeof(buffer),ios::in|ios::out); string « "В строковый поток записывается "

"это предложение." « ends;

//Чтение иэ строкового потока string в массив stroke: string.getline(stroke,sizeof(stroka),'\0');

//Вывод в стандартный поток содержимого массива

//strolca:

cout « "Xnstroka = " « stroke;

//Возвращение позиции чтения/записи к началу потока

//string:

string.seekg(0L,ios::beg);

// Чтение из строкового потока до пробельного символа: string » ss;

cout « "\nss в " « ss; string » ss;

cout « "\nss = " « ss; string.getline(ss,sizeof(ss),'\0'); cout « "\nss = " « ss;

Результат выполнения программы (на экране):

strока = В строковый поток записывается это предложение. ss = В

ss = строковый

зз = поток записывается это предложение.

Обратите внимание, что функция getline О переносит из потока string даже ведущие пробельные символы. Комментарии в тексте программы и результаты ее выполнения достаточно подробно иллюстрируют основные особенности работы с двунаправленными потоками. Отметим только необходимость при операциях « явно занести в поток признак конца строки (манипулятор ends), а при повторном чтении из потока - необходимость перейти к его началу.

Глава 11. Ввод-вывод в языке Си++

429

 

Переход к началу потока для чтения из него выполняет функция seekgO, первый параметр которой (типа long) указывает нулевую величину смещения, а второй - положение, от которого это смещение отсчитывается. В классе ios определены три возможных начала отсчета:

ios: :Ьед

-

от начала потока (его буфера);

.

ios:: end

-

от конца потока;

ios:: cur - от текущей позиции чтения/записи.

Обратите внимание, что для двунаправленного потока класса strstream определены два указателя позиций - позиции записи и позиции чтения. Именно поэтому в программе после окончания записи в поток string функция getline () выполняет чтение от его начала, и не требуется перевода указателя чтения (что делает функция seekp ()).

Перегрузка операций ввода-вывода и использование строковых по-

токов для межмодульного обмена. Строковые потоки можно использовать по-разному. С их помощью в участок памяти, связанный со строковым потоком, можно заносить разнотипную информацию, затем извлекать ее по нужным правилам. Строковый поток можно в этом случае сделать внешним и с его помощью осуществлять межмодульный обмен. В следующей программе с помощью дуального (двунаправленного) строкового потока выполняется обмен между функциями. Строковый поток с именем obmen и связанный с ним символьный массив Link[] определены как глобальные и тем самым доступны во всех функциях файла с программой. В основной программе в строковый поток obmen заносятся данные из массива структур. Предварительно в поток записывается значение количества элементов массива. В функции result() выполняется чтение из строкового потока. При первом обращении к потоку "извлекается" значение количества элементов, используемое затем для организации цикла чтения структур. Структурный тип с именем element и две операциифункции для перегрузки операций обмена » и « со строковыми потоками определены до функции main ().

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

//Р11-16.СРР - перегрузка операций обмена (« , ») и

//

двунаправленный строковый поток ввода-вывода

#include <strstrea.h>

 

const int lenLink = 200; // Глобальная константа

char Link[lenLink];

// Глобальный символьный массив

// Строковый поток obmen связан с массивом Link:

strstream

obmen(Link,sizeof(Link),ios: :in Iios::out);

430

Язык Си++

struct element { int nk, nl; float zn;

};

strstreamS operator »(strstreamS in, elements el) { in » el.nk; in » el.nl; in » el.zn;

return in;

>;

strstreamS operator «(strstreams out, elements el)

{

out « '

'

« el.nk «

' '

« el.nl « '

' « el.zn;

 

return out;

 

 

 

 

 

};

 

 

 

 

 

 

// Функция чтения из потока и вывода на экран:

void result(void)

 

 

 

 

{

element zuar;

 

 

 

 

 

int numb;

 

 

 

 

 

 

obmen »

numb;

 

 

 

 

 

cout « "\nnumb = " « numb;

 

 

 

 

for(int j = 0; j < numb; j++)

 

 

 

{ obmen

» zuar;

 

 

 

 

 

cout « "\nelement[" « j « "] = ";

 

cout

«

zuar.nk «

'\t'

«

zuar.nl

«

 

 

 

 

•\t'

«

zuar.zn;

 

void main()

{ char buffer[180]; 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 }

// Запись в строковый поток:

obmen «

numbeEl;

 

for (int i = 0; i < numbeEl;

obmen

«

arel[i];

 

obmen «

'\0';

 

result();

 

 

 

Результат выполнения программы:

numb = 5

 

 

 

 

element[0] = 1

2

3.45

element[1] =

2

3

4.56

element[2] =

22

11

45 .599998

element[3] =

3

24

4.33

element[4] =

3

6

-5 .3

Глава 11. Ввод-вывод в языке Си++

11.7. Работа с файлами

Основное отличие внешней памяти ЭВМ от основной (иначе оперативной) памяти - возможность сохранения информации при отключении ЭВМ. Информация во внешней памяти (на диске, на магнитных лентах и т.п.) сохраняется в виде файлов - именованных объектов, доступ к которым поддерживает (обеспечивает) операционная система ЭВМ. Поддержка операционной системы состоит в том, что в ней имеются средства:

создания файлов;

уничтожения файлов;

поиска файлов на внешнем носителе информации (на диске);

чтения и записи данных из файлов и в файлы;

открытия файлов;

закрытия файлов;

позиционирования файлов.

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

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

1 - создание файла;

2 - создание потока;

3 - открытие файла;

4 - "присоединение" файла к потоку;

5 - обмены с файлом с помощью потока;

6 - "отсоединение" потока от файла;

7 - закрытие файла;

8 - уничтожение файла.

Все перечисленные действия могут быть выполнены с помощью средств библиотеки классов ввода-вывода языка Си++. Однако су-

432

Язык Си++

ществует несколько альтернативных вариантов их выполнения. Кратко остановимся на наиболее простых и удобных механизмах реализации указанных действий.

Создание файла может быть на "нижнем уровне" выполнено с помощью библиотечной функции (из библиотеки ANSI С):

int creat (const char *path, int amode);

Прототип этой функции находится в заголовочном файле io.h. Функция creat() по заданному имени файла path создает новый файл либо "очищает" и подготавливает для работы уже существующий. Параметр amode нужен только для вновь создаваемого файла. Файл создается для работы в таком режиме обмена, который соответствует значению определенной в файлах f c n t l . h и s t d l i b . h гло-

бальной переменной _fmode (OJTEXT ИЛИ O_BINARY). Значение О_ТЕХТ

определяет текстовый режим обмена с файлом, при котором в процессе чтения из файла каждая пара символов CR (OxOD - конец строки), LF (ОхОА - переход к началу строки) преобразуется в один символ новой строки ' \ п ' . При записи в файл в текстовом режиме обмена каждый символ новой строки ' \п' преобразуется в пару CR, LF. Чтение из файла в текстовом режиме че может продолжаться, если обнаружен символ, обозначающий конец файла. В этом случае считается, что достигнут конец файла, т.е. выполнено условие EOF. *

В двоичном режиме обмена O_BINARY преобразований символов не происходит, и их значения не анализируются.

По умолчанию переменная _fmode устанавливается равной О_ТЕХТ, т.е. файл создается для работы в текстовом режиме. Программист может явно изменить режим обмена, используя последовательность:

#include <fcntl.h> _fmode=0_BINARY;

Параметр amode определяет режим доступа к создаваемому файлу. Предопределены в заголовочном файле sys\stat . h следующие кон- станты-значения этого параметра:

S_IWRITE

-

разрешена запись в файл;

S_IREAD

-

разрешено только чтение из файла;

S_IREAD I S_IWRITE

- разрешены и чтение, и запись.

Если файл с полным именем «path не существует, то он создается заново, для него устанавливаются по значению параметра amode ре-

Глава 11. Ввод-вывод в языке Си++

433

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

Если файл с полным именем *path уже существует и для него был определен режим доступа "только чтение" S_IREAD, TO ВЫЗОВ функции creat() завершается неудачно, файл с указанным именем останется неизмененным.

Если файл с полным именем *path уже существует и для него установлен режим записи S_IWRITE, то функция creat () обнуляет длину файла, оставляя неизмененными атрибуты его режимов.

При успешном завершении функция creat () возвращает неотрицательное целое число - индивидуальный логический номер (дескриптор) файла. В противном случае возвращается значение - 1 . Пример создания файла с проверкой результата:

#include <io.h>

// Для функций create()

#include <process.h>

 

// Для функций exit()

iinclude <iostream.h>

 

#include <sys\stat.h>

// Значения параметров amode

char *fileName = "EXAMPLE.CPP"; int fileNumb;

fileNumb = creat(fileName,S_IWRITE); if (fileNumb == -1)

{ cerr « "Ошибка при создании файла"; exit(l);

Здесь файл с именем EXAMPLE. СРР создается в те:<ущем каталоге. Явно указан режим файла, предусматривающий только запись в файл. По умолчанию файл создается как текстовый О_ТЕХТ.

Потоки для работы с файлами создаются как объекты следующих классов:

of stream - для вывода (записи) данных в файл;

if stream - для ввода (чтения) данных из файла;

f stream - для чтения и для записи данных (двунаправленный обмен).

Чтобы использовать эти классы, в текст программы необходимо включить дополнительный заголовочный файл fstream.h. После этого в программе можно определять конкретные файловые потоки, соответствующих типов (объекты классов ofstream, ifstream, fstream), например, таким образом:

434

Язык Си++

ofstream outFile; // Определяется выходной файловый поток ifstream inFile; // Определяется входной файловый поток fstream ioFile; // Определяется файловый поток для ввода

// и вывода

Создание файлового потока (объекта соответствующего класса) связывает имя потока с выделяемым для него буфером и инициализирует переменные состояния потока. Так как перечисленные классы файловых потоков наследуют свойства класса ios, то и переменные состояния каждого файлового потока наследуются из этого базового класса. Так как файловые классы являются производными от классов ostream (класс ofstrean), istream (класс ifstream), stream (класс fstream), то они поддерживают описанный в предыдущих параграфах форматированный и бесформатный обмен с файлами. Однако прежде чем выполнить обмен, необходимо открыть соответствующий файл и связать его с файловым потоком.

Открытие файла в самом общем смысле означает процедуру, информирующую систему о тех действиях, которые предполагается выполнять с файлом. Существуют функции стандартной библиотеки языка Си для открытия файлов fopen (), open (). Но работая с файловыми потоками библиотеки ввода-вывода языка Си++, удобнее пользоваться компонентными функциями соответствующих классов.

Создав файловый поток, можно "присоединить" его к конкретному файлу с помощью компонентной функции ореп(). Функция open () унаследована каждым из файловых классов ofstream, ifsream, fstream от класса fstreambase (для простоты он не показан на рис. 11.3). С ее помощью можно не только открыть файл, но и связать его с уже определенным потоком. Формат функции:

void open(const char *fileName,

int mode = умалчиваемое_значение,

int protection = умалчиваемое_эначение);

Первый параметр - fiieName - имя уже существующего или создаваемого заново файла. Это строка, определяющая полное или сокращенное имя файла в формате, регламентированном операционной системой. Второй параметр - mode {режим) - дизъюнкция флагов, определяющих режим работы с открываемым файлом (например, только запись или только чтение). Флаги определены следующим образом:

enum ios: :open mode (

in

= 0x01, // Открыть только для чтения

out

= 0x02, // Открыть только Д ЛЯ записи

Глава11.Ввод-выводвязыкеСи++

435

ate

=

0x04,

арр

=

0x08,

trunc

= 0x10,

nocreate

=

0x20,

noreplace = 0x40,

binary

= 0x80

При открытии искать конец файла Дописывать данные в конец файла Вместо существующего создать новый файл Не открывать новый файл (Для

несуществующего файла функция open выдаст ошибку)

Не открывать существующий файл (Для существующего выходного файла, не имеющего режимов ate или арр, выдать ошибку) Открыть для двоичного (не текстового) обмена

Назначения флагов поясняют комментарии, однако надеяться, что именно такое действие на поток будет оказывать тот или иной флаг в конкретной реализации библиотеки ввода-вывода, нельзя. Как пишет автор языка Си++ [26], "смысл значений open_mode скорее всего зависит от реализации". Например, различие между флагами ios: -.ate и ios: :app проявляется весьма редко, и часто они действуют одинаково. Однако ниже в пояснениях к программе Р11-19.СРР приведен пример использования флага i o s : : арр в конструкторе класса ofstream, где использование ios: : ate приведет к ошибке открытия файла. Умалчиваемое значение параметра mode зависит от типа потока, для которого вызывается функция open().

Третий параметр - protection (защита) - определяет защиту и достаточно редко используется. Точнее, он устанавливается по умолчанию и умалчиваемое значение обычно устраивает программиста.

Как обычно вызов функции ореп() осуществляется с помощью уточненного имени

имя^б-ьекта класса. вызов_принадлежащей_классу_функции

Итак, открытие и присоединение файла к конкретному файловому потоку обеспечивается таким вызовом функции open ():

имя_потока.open(имя_файла, режим, защита);

Здесь имя_потока - имя одного из объектов, принадлежащих классам of stream, ifstream, f stream. Примеры вызовов для определенных выше потоков:

outFile.open("С:\\USER\\RESULT.DAT");

inFile.open("DATA.TXT");

ioFile.open("CHANGE.DAT",ios::out);

28*