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

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

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

396

Язык Си++

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

int К; float Е;

 

cin » К » Е;

« Е;

cout « "К = " « К « " Е

набрать на клавиатуре

1234.567 89 <Enter>

то получим

К = 1234 0.567

Здесь точка явилась недопустимым символом для к, но корректно воспринята при вводе значения Е. СИМВОЛЫ 89 из входного потока проигнорированы, так как извлечение закончилось при достижении пробела, а больше операция » не применяется.

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

double D; cin » D;

cout « "D = « D;

введя с клавиатуры

112233445566778899 <Enter>

получим округленное значение:

D = 1.122334е+17

Ввод-вывод массивов и символьных массивов-строк - это различающиеся процедуры. Как известно, символьная строка всегда представляется как массив типа char, последним значащим элементом в котором является литера • \о •. Именно до этой литеры оператор вывода переносит в выходной поток символы из памяти:

char Н[] = "Qui pro quo - путаница"; cout « "\n" « H;

На экране дисплея:

Qui pro quo - путаница

Если в той же программе добавить операторы

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

397

char *рН = Н;

cout « "\n" « рН;

то результат не изменится - будет выведена строка, с которой связан указатель рн. Операция вывода «, "настроенная" на операнд типа char *, всегда выводит строку, а не значение указателя, связанного с этой строкой. Чтобы вывести не значение строки, а значение указателя, необходимо явное приведение типа. Например, оператор

cout « "\путсазатель = « (void *)pH;

выведет не значение строки, начало которой есть значение указателя, а собственно адрес. Для указателя, не связанного с символьной строкой (т.е. не имеющего типа char *), и вывод указателя, и вывод результата его приведения к типу (void *) будут одинаковы. Например, выполнение операторов:

int *pi,i = 6; pi = 6i; cout « "\npi = " « pi;

cout « "\n(void *)pi = ' « (void *)pi;

приведет к печати одинаковых значений:

pi = ОхЮбе

(void*) pi = ОхЮбе

Интересно, что в некоторых случаях операторы

c h a r

сс[5] = { ' а 1 ,

' Ь 1 , ' с 1 , ' d ' , ' е 1 }

c o u t

« " \ n c = " «

е е ;

приведут к выводу

ее = abede

но надеяться на это нельзя.

При вводе строки на клавиатуре набираются любые символы до тех пор, пока не будет нажата клавиша Enter. Например, для операторов

char line[255], stroke[80]; cin » line » stroka;

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

398 Язык Си++

1 \0', тем самым завершая строку. Если при выполнении этого оператора ввода набрать на клавиатуре:

ERGO (следовательно) (лат.)

получим line="ERGO", и s t r o k a = " (следовательно)". Символы " (лат.)" не будут восприняты.

Так как операции «, » "настроены" на заранее определенные типы данных, то иногда возникают несоответствия, которых могло бы не быть при использовании библиотечных функций ввода scanf () и вывода printf () из стандартной библиотеки языка Си. Например, рассмотрим попытку вывести на экран (в поток cout) значение символа, получаемого от клавиатуры с помощью библиотечной функции getch(), описанной в файле conio.h:

cout « "От клавиатуры поступил символ: " « getch(); Если на клавиатуре нажать клавишу ' в', то на экране получим:

От клавиатуры поступил символ: 66

Дело в том, что функция getch() ввода символа от клавиатуры без отображения на экране возвращает значение типа int, т.е. имеет прототип:

int getch(void);

Поэтому печатается не изображение символа 'В1 , а его целочисленный код. Чтобы получить изображение символа, необходимо явное приведение типа:

cout « (char)getch();

11.3.Форматированиеданныхприобменах с потоками

Форматирование пересылаемых данных. Непосредственное применение операций вывода << (включение в поток) и ввода >> (извлечение из потока) к стандартным потокам cout, cin, cerr, clog для данных базовых типов приводит к использованию "умалчиваемых" форматов внешнего представления пересылаемых значений. Например, при выводе чисел каждое из них занимает ровно столько по-

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

399

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

int il = 1, i2 = 2, i3 = 3, i4 = 4, i5 = 5; cout « "\n" « il « i2 « i3 « i4 « i5;

приведет к такому результату: 12345

Для улучшения читаемости проще всего явно ввести разделительные пробелы. Выполнив оператор

cout « "\n"« i

получим более наглядный результат: 1 2 3 4 5

Следующий шаг - добавление пояснительного текста и(или) символов табуляции. Эти приемы мы уже неоднократно применяли в программах, но никак не изменяли 'формат самих выводимых значений. Ширина (количество позиций) внешнего представления каждого числа выбирается автоматически, исходя из необходимого количества позиций. Единообразие не всегда устраивает пользователя программы. Например, периодическую дробь 1.0 / 3.0 можно представить весьма различными способами:

0.30.3333 З.Зе-1 О.ЗЗЗЗЗЗЗеО

Однако стандартное представление при выводе с помощью оператора

cout « "\nl . 0 / 3.0 = " « 1.0 / 3.0; будет всегда одинаковым:

1.0 / 3.0 = 0.333333

Такое поведение выходного потока при использовании операции включения со значением типа double предусматривается по умолчанию. Форматы представления выводимой информации и правила восприятия данных, вводимых из потока, могут быть изменены программистом с помощью флагов форматирования. Эти флаги унаследованы всеми потоками библиотеки из базового класса ios. Флаги реализованы в виде отдельных фиксированных битов чисел типа long, поэтому несколько флагов с помощью логических битовых выражений можно объединять, тем самым по-разному комбинируя свойства

400 Язык Си++

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

slcipws = 0x0001

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

left = 0x0002

вывод значения с левым выравниванием (прижать к левому краю поля);

right = 0x0004

вывод значения с правым выравниванием (это значение устанавливается по умолчанию);

internal = 0x0008

принятый в качестве заполнителя символ (по умолчанию пробел) помещается между числовым значением и знаком числа

либо признаком основания системы счисления (см. ниже ком-

понент ioa: :x_fill);

dec = 0x0010

десятичная система счисления; oct = 0x0020

восьмеричная система счисления; hex = 0x0040

шестнадцатеричная система счисления; showbase = 0x0080

напечатать при выводе признак системы счисления (0х для шестнадцатеричных чисел, 0 - для восьмеричных чисел);

showpoint = 0x0100

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

uppercase = 0x0200

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

showpos = 0x0400

печатать знак числа (символ ' + •) при выводе положительных чисел;

scientific = 0x0800

для вещественных чисел (типов float, double) использовать представление в формате с плавающей точкой (научное пред-

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

401

 

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

fixed = 0x1000

для вещественных чисел (типов float, double) использовать представление в формате с фиксированной точкой, причем количество цифр дробной части определяется заданной по умолчанию точностью (см. ниже переменную x_precision);

unitbuf = 0x2000

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

stdio = 0x4000

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

Все флаги форматирования в виде отдельных фиксированных битов входят в компонент класса ios:

long x_flags; // Переменная представления флагов // форматирования

Именно эта переменная, относящаяся к конкретному потоку, анализируется при обменах и влияет на преобразование информации. В библиотеке классов ввода-вывода существуют принадлежащие классу ios функции flags () и setf о для проверки значений перечисленных флагов, для установки флагов и для их сбрасывания в исходные (умалчиваемые) состояния. Флаги могут обрабатываться как по отдельности, так и группами, для чего используют дизъюнктивные выражения, в которых флаги связаны побитовой операцией ' | • (ИЛИ).

Кроме флагов для управления форматом используются следующие компонентные переменные класса ios:

int x_vridth

задает минимальную ширину поля вывода; int x_precision

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

int x_fill

определяет символ заполнения поля вывода до минимальной ширины, определенной x_width. По умолчанию x_fi l l имеет значение пробела.

26-2432

402

 

Язык Си++

 

 

Для

изменения

компонентных переменных x_fiags, x_width,

x_f ill,

x_preeision

программист может использовать общедоступ-

ные функции класса ios:

static long bitalloc ();

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

char fill () ;

возвращает текущий символ заполнения незанятых (пустых) позиций поля вывода;

char fill(char);

заменяет символ заполнения значением параметра, возвращает предыдущий символ заполнения;

long flags();

возвращает текущий набор битов флагов форматирования; long flags(long);

устанавливает биты флагов форматирования в соответствии со значением параметра. Возвращает предыдущее значение флагов;

int precision();

возвращает текущее значение точности представления при выводе вещественных чисел (типа float и double);

int precision(int n);

устанавливает по значению параметра п точность представления вещественных чисел, возвращает предыдущее значение точности;

long setf(long);

устанавливает флаги в соответствии с тем, как они помечены в фактическом параметре. Возвращает предыдущую установку флагов;

long setf(long _setbits, long _field);

устанавливает флаги в соответствии со значениями параметров. Биты, помеченные в параметре _field, сбрасываются (очищаются), а затем устанавливаются те биты, которые отмечены в параметре _setbits;

long unsetf(long);

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

L int width();

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

403

возвращает установленное значение ширины поля; int width(int);

устанавливает значение ширины поля в соответствии со значением параметра;

static int xallocO;

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

Следующие компоненты (константы) класса ios определены как статические, т.е. существуют в единственном экземпляре для класса в целом и требуют при обращении указания имени класса (ioe: :). В определении класса ios они описаны таким образом:

static const long adjustfield; // left | right I internal static const long basefield; // dec I oct I hex

static const long floatfield; // scientific I fixed

Каждая из этих констант объединяет несколько установленных битов флагов форматирования. Эти константы удобно использовать в том случае, когда перед установкой флага требуется сбросить все флаги, которые не могут быть одновременно с ним установлены. Для сбрасывания флагов константа используется в качестве второго параметра функции setf ().

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

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

//Р11-02.СРР - форматирование выводимой информации

#include

<strstrea.h>

 

void main()

 

{ char name[] = "Строка длиной 52 символа "

 

"в поле шириной 58 позиций.";

cout «

"\n\n"

 

cout.width(58);

// Ширина поля вывода для потока cout

//Символ заполнения пустых позиций поля: cout.fill('$');

//Первый вывод строки в поток uout:

26*

404

Язык Си++

cout « name « andl;

cout.width(58);

// Убрать нельзя

// Заполнитель между знаком и значением:

cout.setf(ios::internal);

double dd = -33.4455;

cout « dd « endl;

// Вывод вещественного значения

cout.width(58);

// Убрать нельзя

//Смена выравнивания: cout.setf(ios::left,ios::adjustfield);

//Символ заполнения пустых позиций поля: cout.fill('#');

//Второй вывод строки в поток cout: cout « name « endl;

long nn = 90000; // Шестнадцатеричное значение 0xl5f90

//Смена основания счисления: cout.setf(ios::hex,ios::basefield);

//Выводить признак основания счисления: cout.setf(ios::showbase);

//Переход на верхний регистр:

sout.setf(ios::uppercase); cout.width(58); // Убрать нельзя

cout « nn « endl; // Вывод целого значения типа long

cout.width(58);

// Убрать нельзя

//Смена выравнивания: cout.setf(ios::internal,ios::adjustfield);

//Символ заполнения пустых позиций поля: cout.fill('$');

cout.unsetf(0x0200); // Переход на нижний регистр

cout « nn « endl; // Вывод целого значения типа long

)

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

$$$$$$Строка длиной 52 символа в поле шириной 58 позиций. -$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$33.4455 Строка длиной 52 символа в поле шириной 58 позиций.###### 0X15F90################################################## 0x$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$15f90

Прокомментируем программу и результаты. По умолчанию ширина поля вывода устанавливается равной длине принятого представления выводимого значения. Поэтому действие функции width ()

 

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

 

 

 

 

405

 

однократное, и ее нужно при каждом выводе значения явно использо-

 

вать, если умалчиваемое значение ширины поля вывода не устраивает

 

программиста.

Функция f i l l О

устанавливает символ

заполнения

 

пустых позиций поля. При первом выводе строки name[]

по умолча-

!

нию установлено выравнивание по правому краю

поля, и символы

|

' $ ' помещены слева от содержимого строки. Перед выводом значения

вещественной

переменной dd

функцией

satf()

установлен флаг

!

internal . Под его влиянием символ заполнения разместился между

 

знаком ' -' и числовым значением 33.4455. Ширина поля явно указа-

 

на в 58 позиций.

 

 

 

 

 

 

 

Перед вторым выводом строки патеП

"под влиянием"

второго

 

параметра

(adjustfield) функции setf ()

сброшены флаги

righ t и

|

i n t e r n a l

и явно установлен флаг left

выравнивания

по

левому

 

краю. Изменен символ заполнения пустых позиций

•#'. Перед выво-

 

дом длинного целого числа nn установлено основание системы счис-

 

ления (basefield - сбрасывает флаги оснований счисления; hex -

s

явно устанавливает шестнадцатеричное

основание). Установлены

;флаги showbase и uppercase и ширина поля вывода.

? Число 90000 выведено в шестнадцатеричном виде, признаком ох

;обозначено основание системы счисления, для изображения шестнад-

цатеричных цифр и признака основания используются прописные

{буквы. Так как при переходе к выравниванию по левому краю флаг

i n t e r n a l оказался сброшенным, то символ заполнения ' # ' размещен

 

не после признака основания счисления ох, а заполняет правую

{

пустую часть поля. Заключительный вывод значения nn, равного

;

90000, выполнен с флагами i n t e r n a l и left. Для перехода на нижний

j

регистр использована функция unsetf О с явным значением флага

I

uppercase.

|

Манипуляторы. Несмотря на гибкость и большие возможности

 

управления форматами с помощью компонентных функций класса

 

ios, их применение достаточно громоздко. Более простой способ из'

 

менения параметров и флагоь форматирования обеспечивают мани-

 

пуляторы, к возможностям которых мы перейдем.

 

Манипуляторами называют специальные функции, позволяющие

 

программисту изменять состояния и флаги потока. Особенность ма-

 

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

 

имена (без параметров) и вызовы (с параметрами) можно использо-

 

вать в качестве правого операнда для операции обмена (« или » ) . В

 

качестве левого операнда в этом выражении, как обычно, использует-

406

Язык Си++

ся поток (ссылка на поток), и именно на этот поток оказывает влияние манипулятор.

Прежде чем переходить к перечислению манипуляторов и их свойств, напомним, что мы уже пользовались некоторыми из них (см. гл. 5, программу Р5-03.СРР). Например, манипулятор hex позволяет устанавливать шестнадцатеричное основание счисления выводимых в поток cout числовых значений. Выполнив последовательность операторов:

cout

«

"\пДесятичное число:

" « 15 « hex;

cout

«

"ХпШестнадцатеричное

представление: " « 15;

получим на экране:

Десятичное число: 15 Шестнадцатеричное представление: OxF

Как наглядно показывает результат, применение манипулятора hex изменило одно из свойств потока cout. Десятичная целая константа 15 воспринимается и выводится на экран в шестнадцатеричном виде.

В качестве параметра каждый манипулятор автоматически (без явного участия программиста) получает ссылку на тот поток, с которым он используется в выражении. После выполнения манипулятора он возвращает ссылку на тот же поток. Поэтому манипуляторы можно использовать в цепочке включений в поток или извлечений из потока. При выполнении манипулятора никаких обменов данными с потоком не происходит, но манипулятор изменяет состояние потока. Например, выполнив оператор с манипуляторами hex, oct, dec:

cout « 15 « hex « 15 « oct « 15 « dec « 15;

в качестве результата получим:

150xF01715

Манипуляторы библиотеки классов ввода-вывода языка Си++ делятся на две группы: манипуляторы с параметрами и манипуляторы без пара-

метров.

 

 

 

Манипуляторы без параметров:

 

 

dec

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

флаг

десятичной системы

 

счисления,

 

 

hex

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

флаг

шестнадцатеричной

 

системы счисления;

 

 

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

407

oct

-

при вводе и выводе устанавливает флаг восьмеричной си-

 

 

стемы счисления;

 

ws

-

действует только при вводе и предусматривает извлечение

 

 

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

 

 

табуляции ' \ t '

и '\v, символ перевода строки Лп1 , сим-

 

 

вол возврата

каретки '\г', символ перевода

страни-

 

 

цы' \ f ) ;

 

 

endl

- действует только при выводе, обеспечивает включение в

 

 

выходной поток символа новой строки и сбрасывает буфер

 

 

(выгружает содержимое) этого потока;

 

ends

- действует только при выводе и обеспечивает включение в

 

 

поток нулевого признака конца строки;

 

flush

- действует только при выводе и очищает выходной поток,

 

 

т.е. сбрасывает его буфер (выгружает содержимое буфера).

Обратите внимание, что не все перечисленные манипуляторы действуют как на входные, так и на выходные потоки, ws действует только при вводе; endl, ends, flush-только при выводе.

Манипуляторы dec, hex, oct, задающие основание системы счисления, изменяют состояние потока, и это изменение остается в силе до следующего явного изменения.

Манипулятор endl рекомендуется использовать при каждом выводе, который должен быть незамедлительно воспринят пользователем. Например, его использование просто необходимо в таком операторе:

cout « "Ждите! Идет набор статистики." « endl;

При отсутствии endl здесь нельзя гарантировать, что сообщение пользователю не останется в буфере потока cout до окончания набора статистики.

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

cout « "Введите название файла:" « flush;

cin » fileName; // Здесь filename - символьный массив

Манипуляторы с параметрами определены в файле iomanip.h: setbase(int n)

устанавливает основание (п) системы счисления. Значениями параметра п могут быть: 0, 8, 10 или 16. При использовании

408

Язык Си++

параметра 0 основание счисления при выводе выбирается десятичным. При вводе параметр 0 означает, что целые десятичные цифры из входного потока должны обрабатываться по правилам стандарта ANSI языка Си;

resetiosflags(long L)

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

setiosflags(long L)

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

setfill(int n)

значение параметра п в дальнейшем используется в качестве кода символа-заполнителя, который помещается в незанятых позициях поля при вводе значения. (См. компонент x _ fili класса ios.);

setprecision(int n)

определяет с помощью значения параметра п точность представления вещественных чисел, т.е. максимальное количество цифр дробной части числа при вводе и выводе. (См. компонент x_precision класса ios.);

setw(int n)

значение параметра п задает минимальную ширину поля вывода. (См. компонент x_width класса ios.)

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

Итак, для управления форматом вывода (включения в поток) класс ios имеет следующие компоненты:

компонент(атрибут) x_flags;

функции доступа к атрибуту x_flags: flags () и self ();

• атрибуты управления форматом: x_width, x_precision,

п р и н а д л е ж а щ и е

к л а с с у

ф у н к ц и и :

w i d t h ( ) ,

p r e c i s i o n ( ) ,

 

f i l K ) ;

 

 

 

 

манипуляторы (вставляемые в цепочки обмена).

 

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

409

 

11.4. Ввод-вывод для типов, определенных пользователем

При передачах данных базовых типов потоки cin, cout "знают", как выполнять преобразования значений разных типов. Напомним, что при перегрузке функций конкретная реализация выбирается в соответствии с сигнатурой, т.е. зависит от фактических параметров. Подобным образом в выражениях cin » операнд и cout « операнд каждому типу правого операнда соответствует свое правило преобразования данных. Эти правила заранее оформлены в виде процедур (функций) специального вида, называемых операциями-функциями. Определения этих функций находятся в библиотеке ввода-вывода, а прототипы размещены в заголовочном файле iostream.h.

Чтобы использовать операции обмена » и « с данными производных типов, определяемых пользователем, необходимо расширить действие указанных операций, введя новые операции-функции. Каждая из операций обмена « и » бинарная, причем левым операндом служит объект, связанный с потоком, а правый операнд должен иметь желаемый тип. Этот бинарный характер операций обмена отражает спецификация параметров соответствующих операций-функций. Первый параметр - ссылка на объект потокового класса (тип istreami либо ostreami), второй параметр - ссылка или объект желаемого типа Тип возвращаемого значения должен быть ссылкой на тот поток, для которого предназначена операция. Таким образом, формат операции-функции для перегрузки операций таков:

ostreamt

operator « (ostreams out,

новый_тип имя)

{ . . .

 

//

Любые операторы для параметра нового_типа

out «

. . .

//

Вывод значений нового_типа

r e t u r n

out;

//

Возврат ссылки на

объект класса ostream

Здесь новый_тип - тип. определенный пользователем. Таким образом, если определить структурный тип (класс):

struct point

// Точка трехмерного евклидова пространства

{ float x;

// Декартовы координаты точки

float у;

 

float z;

 

410

Язык Си++

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

ostream& // Тип возвращаемого значения

 

operator

«

(ostreamA t,

point

d)

{ return t « "\nx

=

" « d.x « "

у

= " « d.y «

 

z

= " « d.z;

 

 

Отметим, что в теле функции с,"названием" operator «, операция « используется в первом бинарном выражении с объектом t типа ostreams в качестве левого операнда. Правыми операндами для каждой из операций « служат значения стандартных (базовых) типов - символьные сроки и вещественные числа типа float. Результат операции t « "\пх - "

ссылка на объект типа ostream. Эта ссылка используется в качестве левого операнда при выводе « d. х и т.д. Так как d - имя формального параметра структурного типа point, то для обращения к компонентам структуры (х, у, z) используются уточненные имена d.x, d.y, d.z. В результате выполнения всей цепочки операций вывода в теле операциифункции формируется ссылка на объект типа ostream, и именно это значение ссылки возвращает в точку вызова оператор return. Тем самым операция включения в поток «, применяемая в вызывающей программе к операнду типа point, вернет ссылку на выходной поток. Таким образом, сохраняется возможность применения операции « несколько раз в одном выражении, как это разрешено для операндов базовых типов (т.е. допустимы цепочки вывода). Например, если, определив структуру point и введя для нее приведенную выше операцию-функцию operator « (), выполнить программу

void main()

{ poin t F = { 10 . 0 , 2 0 . 0 , 30 .0 };

cout « "\пКоординаты

точки: " « F;

}

 

 

то на экране дисплея получим:

Координаты

точки:

= 30.0

х = 10.0

у = 20 . 0

Как видите, в данном примере при выводе в поток cout равноправно используются и вывод значения базового типа char [ ], и вывод значений объекта F типа point, определенного пользователем.

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

411

 

Напомним, что класс ostream, поток (объект) cout и "стандартные" режимы выполнения операции вывода (для базовых типов) определены в заголовочном файле iostream.h, который необходимо поместить в начале текста программы до текста операциифункции operator « ().

В качестве еще одного примера перегрузки (расширения действия) операции вывода «, рассмотрим следующую программу, в которой для представления почтового адреса используется структурный тип с названием address:

//Р11-03.СРР - перегрузка операции вывода " « " #include <iostream.h>

struct address

// Почтовый адрес

{ char *country;

// Страна

char *city;

// Город

char *street;

// Улица

int number_of_house;

// Номер дома

//Определение операции-функции, "распространяющей"

//действие операции включения в поток « на операнд

//типа address:

оstreams // Тип возвращаемого значения

 

 

operator

«

(ostreamfi out, address ad)

{ out «

"\nCountry:

" « ad.country;

out

«

"\nCity:

"

« ad.city;

out

«

"\nStreet:

" « ad.street;

out « "\nHouse: " « ad.number_of_house; return out;

}

void main()

{ address ad = { "Russia", "Moscow", "Arbat", 11 }; cout « "\пЗначекие структуры (почтовый адрес):"; cout « ad « "\n";

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

Значение структуры (почтовый адрес): Country: Russia

City: Moscow

Street: Arbat House: 11

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

)
ostreamfi

412

ЯзыкСи++

В операторе return возвращается ссылка на него (имеющая тип ostreamS).

Как показано на примерах, перегрузка операции вывода « позволяет не только организовать с ее помощью вывод значений пользовательских типов, но и обеспечивает программиста широкими возможностями оформления результатов. К сожалению, расширить действие операции можно только на пользовательские типы и поэтому невозможно непосредственно изменить формат вывода какоголибо из базовых типов. Например, не уластся ввести процедуру operator « (), с помощью которой при использовании операнда типа char * в стандартный поток будет выводиться и длина символьной строки, и ее содержимое.

Чтобы решить указанную задачу, необходимо определить структуру, компонентами которой будут связанный со строкой указатель char * и целая переменная со значением, равным длине строки. Вот для такой структуры перегрузка операции вывода « вполне допустима. В следующей программе это реализовано:

//Р11-04.СРР - вывод информации о структуре-строке iinclude <iostream.h>

#include <string.h> // Для работы со строковыми функциями // Определение класса (пользовательского типа):

struct string { int length; char *line;

}; // Прототип операции-функции для перегрузки операции «:

ostreamS operator « (ostraami out, string str); void main()

{ string st; // Об-ьект st класса string strcpy(st.line,"Содержимое строки."); st.length = etrlen(st.line);

cout « at;

// Тип возвращаемого значения operator « (ostreamfi out, string str)

{ out « "\n Длина строки: " « str.length; out « "\n Значение строки: " « str.line; return out;

}

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

Длина строки: 18 Значение строки: Содержимое строки.

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

413

 

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

Для перегрузки (расширения действия) операции ввода » необходимо определить операцию-функцию вида:

istreamS operator » (istreamS xn, новый_типЬ имя)

{

// Любые операторы для параметра нового типа

in »

//

Ввод значений

р

 

return in;

//

Ввод значений нового типа

 

 

Возврат ссылки на объект класса istream

Здесь новый_тип - тип, определенный пользователем, т.е. некоторый класс или его частный случай - структурный тип. Основное отличие от перегрузки операции вывода - необходимость в качестве второго параметра использовать ссылку. Для уже введенного выше структурного типа "точка трехмерного евклидова пространства" можно с помощью перегрузки операции ввода » очень изящно определить последовательность ввода с консоли значений координат. Налример, удобной может оказаться операция-функция, использованная в следующей программе:

//Р11-05.СРР - перегрузка операции ввода » iinclude <iostream.h>

struct point // Точка трехмерного евклидова пространства { float х;

float у; float z;

istreams

// Тип возвращаемого значения

 

operator » (istreamS in, points d)

{ cout «

"\n Введите три координаты точки: " « "\nx

in » d.x;

cout « "y = "; in » d.y; cout « "z = "; in » d.z; return in;

)

void main() { point D; cin » D;

При выполнении на экране может быть, например, такой текст:

414

ЯзыкСи++

Введите три координаты точки:

х= 100 <Enter>

у= 200 <Enter>

z= 30£ <Enter>

Впредложенной операции-функции operator » () выполняется не только "чтение" (ввод) данных, набираемых пользователем на клавиатуре (в примере 100, 200, 300), но и выводятся на экран соответствующие подсказки, что упрощает использование программы. В данной программе операция-функция operator » () работает не только со входным потоком, ссылка на который передается как параметр, но и со стандартным выходным потоком cout, для которого обычным образом используется операция вывода «. Поэтому до определения операции-функции operator » () в текст программы необходимо включить заголовочный файл iostream.h.

Следующая программа еще раз иллюстрирует сказанное. В программе определен структурный тип employee, предназначенный для структур, содержащих сведения о некоторых "служащих":

• фамилия - char name[50];

• оклад - long salary;

• возраст - i n t age;

Можно было бы ввести и другие компоненты, например, название отдела, должность, стаж работы и т.д., но для целей нашего примера это лишнее. В теле операции-функции operator » о, распространяющей действие операции ввода » на структуру типа employee, на экран (поток cout) выводятся подсказки и из потока типа istream считываются набираемые на клавиатуре данные.

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

//Р11-06.СРР - перегрузка операций ввода » и вывода « iinclude <iostream.h>

// Определение класса "служащий":

struct employee

// Определение класса "служащий"

дг пата[50];

// Фамилия

..:д salery;

// Оклад

•>t age;

// Возраст

5;

//Определение операции-функции, "распространяющей"

//действие операции ввода » на операнд типа employee:

istreamS // Тип возвращаемого значения operator » (istreamS input, employee& em)

( cout « "\п\пВведите сведения о служащем:" « "\пФамилия: ";

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

415

 

 

 

input

»

em.name;

 

cout «

"Оклад: ";

 

input

»

em.salery;

 

cout «

"Возраст: ";

 

input

»

em.age;

 

return

input;

 

)

 

 

 

//Прототип операции-функции для перегрузки операции «: ostreamfi operator « (ostreami, employee);

void main()

{ employee E;

// Определен об^кт класса employee

cin

»

E;

 

cout

«

E;

 

}

 

 

 

//Определение операции-функции для перегрузки

//операции «:

ostreamfi operator « (ostreamt out, employee e)

{ out « "\пВведены следующие сведения о служащем:"; out « "\пИмя: " « e.name;

out « ", оклад: " « е.salery « " руб."; out « ", возраст: " « в.age « " лет.\п"; return out;

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

Введите сведения о служащем: Фамилия: Иванов <Enter> Оклад: 140000 <E*iter> Возраст: 2)9 <Enter>

Введены следующие сведения о служащем:

Имя: Иванов, оклад: 140000 руб , возраст: 39 лет.

В программе для структуры типа employee перегружены как операция ввода », так и операция вывода «.

11.5.Функциидляобменаспотоками

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