Классы и потоки ввода-вывода

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

Основополагающим базовым классом является класс ios. Производными от него являются классы istream – поддерживает базовые операции ввода,
os­tream – поддерживает базовые операции вывода. Двунаправленный поток под­держивается классом iostream, производным от istream и ostream, что можно представить схемой:

ios

____________|_________

| |

istream ostream

|_____________________|

|

iostream

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

#include<iostream.h>

Начав с определения потока как абстракции для моделирования прохо­ждения потока данных от источника к приемнику, класс iostream обеспечивает иерархию классов для управления буферированным и непосредственным вво­дом-выводом для устройств и файлов. Пользователь может создавать собствен­ные классы ввода-вывода на базе стандартных классов.

Стандартный ввод-вывод

С++ поддерживает четыре предопределенных потоковых объекта:

1. cin – стандартный ввод, как правило с клавиатуры, аналог stdin в С (объект класса istream).

2. cout – стандартный вывод, как правило на экран, аналог stdout в C (объект класса ostream).

3. cerr– стандартный небуферизованный вывод ошибок, как правило на экран, аналог stderr в С (объект класса ostream).

4. clog– буферизованный вывод ошибок, нет аналога в С (объект класса ostream).

Можно переназначать стандартные потоки на другие устройства и файлы.

Для каждого потока "перегружены" (переопределены) два оператора:

>>– оператор извлечения (чтения) из потока (для ввода с клавиатуры: "считать из");

<<– оператор записи (вставки) в поток (для вывода на экран: "вывести на").

Замещающие функции способны воспринимать аргументы любых основ­ных типов данных (char, char*(string), int, long, float, double) и могут быть расширены для того, чтобы воспринимать аргументы типа class.

Ввод для встроенных типов

Файл заголовков iostream.h содержит объявление объекта вида:

istream cin;

Класс iostream определяет оператор ввода >> для набора стандартных ти­пов:

class istream

{ // …

public: // общедоступные функции

istream & operator >> (char&); // символ

istream & operator >> (char*); // символьная строка

istream & operator >> (short&); // короткий целый

istream & operator >> (int&); // целый

istream & operator >> (long&); // длинный целый

istream & operator >> (float&); // действительный

istream & operator >> (double&); // двойной действительный

}

Операция >> (извлечения, чтения) заменяет функцию ввода scanf() и луч­ше защищает от ошибок. Левый операнд – это объект типа класса istream. Пра­вый операнд может быть любого типа, для которого определен ввод потока. По умолчанию оператор >> опускает пробельные символы, а затем считывает сим­волы, соответствующие типу вводимого данного. Функции преобразования и форматирования данных зависят от их типов.

Для ввода-вывода можно строить цепочки операций слева направо. Например, так можно ввести несколько данных в С++:

int i; float f;

cin >> I >> f;

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

Для целых типов short, int, long действие операции >> по умолчанию заклю­чается в пропуске пробельных символов и преобразовании целого значения пу­тем чтения символов ввода до символа, который не является допустимым для данного типа.

Для типов с плавающей точкой float иdouble действие операции >> со­стоит в пропуске пробельных символов и преобразовании значения с плаваю­щей точкой до символа, который не может быть частью числа с плавающей точ­кой.

Вывод для встроенных типов

Файл заголовков iostream.h содержит объявление объекта вида:

ostream cout;

Класс ostream определяет оператор вывода << для встроенных типов:

class ostream

{ // …

public: // общедоступные функции

ostream & operator << (char); // символ

ostream & operator << (char*); // символьная строка

ostream & operator << (int i); // целый

ostream & operator << (long);// длинный целый

ostream & operator << (double); // двойной действительный

ostream & put(char);// символ

}

Операция <<, называемая вставкой, заменяет функцию вывода printf(). Левый операнд – это объект cout типа класса ostream. Правый операнд может иметь любой тип, для которо­го определен вывод потоком для встроенных типов.

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

Например:

int i, k=234; double d ; char ch='A';

cout<<”Значение k=” << k <<’\n’; // в конце символ перевода строки

cin>> i >> d; // ввод данных i, d

cout<<”i=”<<i<<” d=”<<d<<’\n’; // вывод значений i, d

cout<<”ch=”<<ch; // вывод символа A

Пример 1.

Потоки ввода-вывода.

#include<iostream.h> // подключение библиотек ввода-вывода

void main()

{ int i;

char str[ ]=”Пример вывода в С++\n”;

cout<<”Вывод строки”<<str;

cout<<”Введите целое и длинное целое число: “;

long l; // переменная описывается перед первым использованием

cin>>i>>l; //ввод данных

cout<<”i=”<<i<<” l=”<<l<<’\n;

cout<<”Введите строку: ”;

cin>>str;

cout<<”Введена строка: ”<<str;

}

Замечание. Можно также использовать любые библиотечные функции ввода-вывода языка С (printf(), scanf() и др.). Кроме того, в другом контексте преопределенные операторы << и >> задают операции сдвига.

Пример 2.

Встроенные типы вставок (вывода данных). Целые типы преобразуются по правилам (по умолчанию) для printf (если эти правила не изменены путем установки флагов ios).

#include<iostream.h> // подключение библиотек ввода-вывода

#include<stdio.h> // для printf

#include<conio.h> // для консольных функций

void main()

{ int i=5; int *pi=&i; long l=12345678; double d=345.6789; char ch='A';

clrscr(); // чистка экрана результатов

cout<<”Вывод с помощью функции printf():\n”);

printf(“i=%d адрес i pi=%#x l=%ld d=%.4f ch=%c\n”, i,pi,l,d,ch);

cout<<”Вывод с помощью потока cout:\n”;

cout << ”i=”<< i << ” адрес i=” << &I << ” l =” << l << ” d=” <<d;

cout<<” ch=”<<ch<<'\n';

getch(); // задержка экрана результатов

}

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

Вывод с помощью функции printf():

i=5 адрес i pi=0xfff4 l=12345678 d=345.6789 ch=A

Вывод с помощью потока cout:

i=5 адрес i=0x8f61fff4 l=12345678 d=345.6789 ch=A

Управление вводом-выводом

Символьные извлечения.

Для типа char действие операции >> состоит в пропуске пробельных символов и чтения следующего (непробельного) символа. Если требуется про­честь следующий символ (любой), то можно использовать одну из функций-эле­ментов get() класса istream:

сhar ch;

cin.get(ch); // ch устанавливается на следующий символ потока,

// даже если это пробельный символ.

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

get(char* buf, int max, int term=’\n’).

Эта функция считывает символы из входного потока в символьный мас­сив buf до тех пор, пока не будет считано max -1символов, либо пока не встре­тится символ, заданный term, в зависимости от того, что будет раньше. Завер­шающий пустой символ (нуль-байт – ‘\0’) добавляется автоматически. По умолчанию око­нечным символом (терминатором), который не требуется задавать, является ‘\n’. Сам терминатор в массив buf не считывается и из входного потока istream не удаляется. Массив buf должен иметь размер не менее max символов.

Пример 3.

Ввод строки цифр и преобразование их в целое число типа int с помощью функции atoi(s) и вывод числа в 10-ом, 16-ом, 8-ом форматах с использованием идентификаторов dec, hex, oct, вставленных в поток вывода для управления форматом выходного потока, endlаналог'\n'. (см. Манипуляторы).

#include<iostream.h> // подключение библиотек ввода-вывода

#include<conio.h> // для консольных функций

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

const int size=35; // размер буфера строки

void main()

{ clrscr();

int value; // переменная для числа

char s[size]; // буфер строки

cout<<"Value= ";

cin.get(s, size,'\n'); // ввод строки числа

value=atoi(s); // преобразование строки в число

cout<<"Decimal="<<dec<<value<<" Hexadecimal=0x"<<hex<<value

<<" Octal=0"<<oct<<value<<endl;

getch();

}

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

Value=123

Decimal=123 Hexadecimal=0x7b Octal=0173

Замечание. Для ввода строки вместо функции cin.get(s, size,'\n');можно использовать cin>>s. Этот оператор сработает, но если пользователь введет бо­лее 34 символов, оператор ввода продолжит запись за пределами буфера s, при этом, возможно, данные и код, располагающиеся за буфером, будут уничтоже­ны. Такая операция может привести к краху системы!

Пример 4.

Использование функции get для безопасного чтения строк.

#include<iostream.h>

void main()

{ char s[35]; // буфер строки

char c; // переменная символа

cout<<"Введите строку не более 34 символов:\n";

cin.get(s, 35); // пропущен 3-й аргумент по умолчанию '\n'

cout<<"Вы ввели строку: "<<s<<endl;

if(cin.get(c) && c!='\n') // если следующий символ не '\n',то вывод

cout<<"Достигнута максимальная длина строки\n";

}

При вводе строки длиной более 34 символов она будет усечена и ввод бу­дет безопасным. Одна проблема все же остается. Символ '\n' или другой сим­вол, завершающий ввод, остается в потоке и должен быть прочитан еще одним оператором cin.get(), как показано последним оператором if. Если cin.get()не читает символ '\n',ввод будет усечен. Этот факт необходимо учитывать при на­писании программ.

Проблему непрочитанного символа '\n' можно решить с помощью метода cin.getline()с тремя параметрами (совпадет с get()), при этом символ раздели­тель '\n' также помещается в принимающую строку символов (s).

Для ввода неформатированных и непреобразованных двоичных данных используется функция cin.read((char*)&x, sizeof(x)).

Форматирование ввода/вывода.

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

Ширина вывода.

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

int ios::width(int w); // устанавливает поле шириной w символов и воз­вращает предыдущую ширину,

int ios::width(); // возвращает предыдущую ширину, не внося из­менений.

По умолчанию width=0, то есть вывод выполняется без заполнителей. При w≠0, если длина числа меньше w используются заполнители, иначе выводится фактическое число без усечения.

Например:

int i=123;

int oldw=cout.width(6);

cout<< i; // вывод будет 123 (перед числом 3 пробела), затем width=0

cout.width(oldw); // восстановление предыдущей ширины width=6

После каждой форматной вставки ширина обнуляется, например:

int i, j;

cout.width(4);

cout<< i <<" "<< j <<;

Здесь i будет выведено четырьмя символами, однако пробел и j будут иметь минимально необходимое число символов.

Заполнители и дополнение вправо и влево.

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

По умолчанию символом-заполнителем является пробел. Изменить данное умолчание позволяет функция fill:

int i=123;

cout.fill ("*");

cout.width(6);

cout<< i; // на экран будет выведено ***123

По умолчанию устанавливается выравнивание по правому краю (дополне­ние символами-заполнителями). Это можно изменить функциями setf и unsetf:

int i=56;

cout.width(6);

cout.fill ("#");

cout.setf (ios::left, ios::adjustfield);

cout<< i; // на экране: 56####

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

Можно также использовать манипуляторы setfill, setiosflags, resetiosflags.

Манипуляторы.

Это специальные операции (похожие на функции) для более простого из­менения ширины и других параметров форматирования. Для их использования программа должна иметь строку:

#include<iomanip.h>

Манипуляторы принимают в качестве аргумента ссылку на поток и воз­вращают ссылку на тот же поток. Поэтому манипуляторы могут объединяться в цепочку вставок (или извлечений из потока) для того, чтобы изменять состоя­ние потока в виде побочного эффекта, без фактического выполнения каких-либо вставок или извлечений. В таблице ниже в столбце синтаксис задано направле­ние потока: входной – ins >>, выходной – outs <<.

Таблица

Манипулятор Синтаксис Действие

Установка флага форматирования с преобразованиями:

dec outs << dec 10-ым ins >> dec

hex outs << hex 16-ым ins >> hex

oct outs << oct 8-ым ins >> oct

ws ins >> ws Извлечение пробелов

endl outs << endl Вставка символа новой строки

и очистка потока

ends outs << ends Вставка конечного нулевого

символа в строку

flush outs << flush Очистка ostream

setbase(int) outs << setbase(n) Установка системы счисления

(0, 8, 10, 16). 0 это по умолча-

нию 10-ая с.с. при выводе и

правила С для литералов це-

лых чисел при вводе

resetiosflags(long) ins>>resetiosflags(1) Очистка битов в ins или outs

outs<<resetiosflags(1) аргументом 1

setiosflags(long) ins >> setiosflags(1) Установка битов в ins или outs

outs << setiosflags(1) аргументом 1

setfill(int) ins >> setfill(n) Установка символа

outs >> setfill(n) заполнителя в n

setprecision(int) ins >> setprecision(n) Установка точности представле-

outs>>setprecision(n) ния чисел с плавающей точкой,

равной n разрядам

setw(int) ins >> setw(n) Установка ширины поля

outs << setw(n) в значение n

Пример 5.

Применение функций и манипуляторов форматирования вывода в про­грамме.

#include<iostream.h>

#include<conio.h>

#include<iomanip.h>

void main()

{ clrscr();

int i=36, j=45;

cout<<"Вывод с установленной шириной поля 5 числа i:\n";

int oldw=cout.width(5); // установка ширины поля вывода

cout<< i <<'\n';

cout<<"После каждой вставки формата ширина поля=0:\n";

cout<< " oldw=" << oldw << '\n';

cout<< i << '\n';

cout<< "Установка поля 5 влияет только на 1-ю переменную:\n";

cout.width(5);

cout<< i << " " << j << '\n';

cout<< "Манипулятор setw(5) упрощает вывод переменных:\n";

cout<< setw(5) << i << setw(5) << j << '\n';

cout<< "Манипуляторы dec, hex, oct изменяют сист. счисл. \n";

cout<< "(оставляя эти изменения в силе) — вывод i:\n";

cout<< dec << i << " " << hex << i << " " << oct << i <<endl;

cout<< "Манипулятор endl аналог '\n' очищает поток"<<endl;

cout<< "i= " << i <<endl;

cout<< "Заполнитель пробел можно заменить на *"<<endl;

cout.fill ( '*' ); // задание символа заполнения функцией fill

cout<< setw(6) << i << endl; // установка ширины поля 6 числа i

cout<< "Выравнивание можно изменить функцией setf:"<<endl;

cout.width(6);

cout.setf(ios::left, ios::adjustfield);

cout<< "i= " << i <<endl;

getch();

}

Вывод чисел с плавающей точкой.

Большие и малые числа выводятся в экспоненциальном формате (е, Е), а не фиксированном. Например, число 1234567.8 печатается 1.2345678Е+07.

Числа с нулевой дробной частью печатаются как целые. Например, число 95.0 печатается 95.

Для вывода десятичных чисел в фиксированном формате можно использо­вать следующие операторы:

cout.setf(ios::fixed, ios::floated);

cout.setf(ios::showpoint);

Первый вызов функции setf обеспечивает печать чисел в фиксированном, а не экспоненциальном представлении. Второй вызов задает печать десятичной точки, даже если дробная часть числа равна 0.

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

cout<< setprecision(2) << x;

например, вместо числа 16.38567 печатается 16.39.

Пример 6.

Если отсутствует переназначение, то ввод с клавиатуры отображается на экран. Следующая программа копирует cin в cout.

#include<iostream.h>

void main()

{ char ch;

while (cin >> ch)

cout << ch;

}

Обратите внимание, что cin >> ch может рассматриваться как булевское выражение. Этот эффект стал возможен благодаря определениям в классе ios. А именно, такие выражения как (cout) или (cin >> ch) рассматриваются как ука­затель на переменную, значение которой определяется кодом ошибки пото­ка. Нулевой указатель (трактуемый как "ложь") индицирует ошибку в потоке, а не­нулевой указатель ("истина") означает ее отсутствие. Можно использовать опе­рацию отрицания (!), в этом случае (!cout) будет "истина" при возникнове­нии ошибки в потоке cout и "ложь", если они отсутствуют:

if (!cout) errmsg("Output error!");

cin — это входной поток, подключенный к стандартному выводу. Он мо­жет правильно обрабатывать все стандартные типы данных. Как известно, в С вывод подсказки (приглашения) без символа новой строки ('\n') в стандартный выходной поток stdout требует обращения к fflush(stdout), чтобы эта подсказка могла появиться. В С++ обращение к cin автоматически очищает cout.

Рассмотрим примеры простых программ на С++ с отличиями от С-про­грамм.

Пример 7.

Применение констант и встроенных функций (inline).

#include<iostream.h>

const float pi=3.14159;

inline float area (const float r) { return pi*(r)*(r); }

void main()

{ float radius;

cout << "Введите радиус круга: ";

cin >> radius;

cout << "Площадь круга= " << area (radius) << '\n';

}

Идентификатор константы ведет себя как обычная переменная (т.е. ее об­ластью действия является блок, в котором она определена) за исключением того, что она не может находиться в левой части оператора присваивания. Дирек­тива #define является морально устаревшей для С++.

Ключевое слово inline указывают компилятору, что, если возможно, следу­ет вставлять код в том месте, где он встретил обращение. Это эквивалентно ма­крорасширению и используется там, где необходимо ликвидировать накладные расходы, связанные с вызовом функции. Рекомендуется применять встроенную функцию, составленную из одной-двух строк.

Ссылки и указатели как параметры функции.

По умолчанию С и С++ передают аргументы функции, используя вызов по значению, чем создается копия аргумента, которая может быть изменена в функции, но исходное значение аргумента не меняется. Для изменения значения аргументов параметры функции объявляются как указатели (*параметр).

Пример 8.

Переставить значения двух аргументов в функции с указателями.

void swap1 (int *a, int *b)

{ int tmp;

tmp=*a; *a=*b; *b=tmp;

}

Для вызова функции необходимо задать адреса аргументов:

swap1(&i, &j); // i, j — аргументы функции

В С++ можно использовать в функции ссылки (&) на изменяемые парамет­ры, а в теле функции и при вызове применяются имена параметров и аргумен­тов:

void swap2 (int &a, int &b)

{ int tmp=a; a=b; b=tmp; }

Вызов функции:

swap2 (i, j);

Наши рекомендации