Как узнать что стандартный поток вывода изменен. Буферизированный (потоковый) ввод-вывод

12.04.2019
02.08.2009 20:46

Предисловие
Едва ли какая-либо программа может обойтись без взаимодействия со своим окружением посредством ввода и вывода информации - иначе зачем она вообще нужна, если строго замкнута на себе, и не общается ни с пользователем, ни с окружающими ее данными и программами? Здесь мы рассмотрим, как программа, написанная на C++ может принимать информацию у пользователя и выдавать некие результаты своей работы ему же. Проще говоря, как она считывает данные с клавиатуры, и выводит их на экран.
Необходимо заметить, что подходы к выполнению этих операций менялись с течением времени. В языке C (Си) и ранних версиях C++, которые были еще во многим похожи на C, применялись функции printf, scanf и подобные им. С развитием языка C++ появились потоки, в том числе стандартные потоки ввода/вывода cin и cout. Что лучше использовать? Давайте посмотрим.
Потоки являются частью стандартной библиотеки C++ и являются мощным высокоуровневым средством. При использовании потоков ввода/вывода вы можете перегружать операторы >> и Таким образом, если вы пишите программы на современном C++ и при решении ваших задач вам нет необходимости использовать низкоуровневые средства, скорее всего, вам больше подойдут потоки.
Функции printf и scanf являются более низкоуровневыми, менее выразительными и требующими большего внимания при использовании средствами. Скорее всего, вам стоит остановить свой выбор на них, если вы используете C или же вы программируете на низком уровне и активно обращаетесь к аппаратным средствам.
Если же вы пишете в на языке C++, пользуясь устаревшей версией компилятора, то совет может быть только один - как можно скорее переходите на современные среды разработки с современными компиляторами и реализующими современную версию языка C++!

Стандартные потоки ввода/вывода
Стандартным потока ввода является поток cin (in put, ввод), а стандартным потоком вывода - cout (out put, вывод). Они определены в пространстве имен стандартной библиотеки std , поэтому для получения доступа к ним необходимо использовать префикс std:: - std::cin вместо cin и std::cout вместо cout . В качестве альтернативы можно использовать директиву using namespace std , чтобы объявить все имена из std глобальными. Однако, я бы посоветовал использовать первый вариант, если только вы не переписываете старый код на C++ или код на C для использования в новых версиях C++.
Также вам необходимо подключить заголовочный файл iostream , в котором и содержатся стандартные потоки ввода/вывода: #include .
Оператором ввода является >> ("прочесть из"), а оператором вывода - Рассмотрим теперь последовательно операции вывода и считывания информации с помощью потоков cout и cin .

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

выведет abc .
Понятное дело, что писать каждый раз std::cout

void f(int i)
{
std::cout }

Поток ввода cin
Для считывания информации, вводимой пользователем с клавиатуры, служит стандартный поток cin . Как и поток вывода, поток ввода работает с символьным представлением типов. То, как будут интерпретироваться вводимые символы, зависит от второго аргумента оператора >> (того, что справа). Соотетственно, вам не нужно указывать, какого типа данные предполагается считать, компилятор определит это исходя из типа переменной. Посмотрим в качестве примера программу, реализующую наипростейший калькулятор:

//Пример 5.1
#include

Int main()
{
int a, b, c;
char sign;

Std::cout std::cin >> a;
std::cout std::cin >> sign;
std::cout std::cin >> b;
switch(sign)
{
case "+": c = a + b; break;
case "-": c = a - b; break;
case "*": c = a * b; break;
case "/": c = a / b; break;
}
std::cout }

Выведя первое сообщение, программа ожидает, пока в потоке ввода не окажется что-нибудь, что можно будет считать в переменную типа int . Как только мы введем что-то и нажмем клавишу Enter, в поток передастся введенная нами строка. Если введенное удастся интерпретировать как символьное представление целого числа, программа благополучно запишет это число в переменную и продолжит свое выполнение. Если же была введена какая-нибудь не относящаяся к делу белиберда, то программа просто будет грязно ругаться по-английски (в зависимости от языка вашей реализации C++). Последнее, конечно, не удивительно, т.к. программа ожидает от программиста и пользователя разумного поведения - либо пользователь должен вводить все в точности как надо, либо программист должен предусмотреть "защиту от дурака".
Вернемся, однако, к нашим потокам. Итак, получив возможность считать в целую переменную соответствующее значение, программа ее таки считывает и выводит следующее сообщение, ожидая, что теперь ей введут один-единственный символ. Предположим, что пользователь действительно вводит один из символов +, -, * или / и вновь нажимает Enter. Тогда программа вновь успешно считывает этот символ в переменную sign и продолжает свою работу. Что будет дальше, вы должны уже смочь представить самостоятельно.
Следующее, что важно сказать, это то, что при считывании в переменные встроенных типов оператор >> считает концом ввода первый же встретившийся символ-разделитель . К таким символам относятся, например, пробел и символ перевода строки ("\n" , вводится нажатием клавиши Enter). Чтобы стало понятнее, что происходит, рассмотрим с этой позиции предыдущую программу.
Во-первых, ее код можно было бы переписать примерно следующим образом:

//Пример 5.2
#include

Int main()
{
int a, b, c;
char sign;

Std::cout std::cin >> a >> sign >> b;
switch(sign)
{
case "+": c = a + b; break;
case "-": c = a - b; break;
case "*": c = a * b; break;
case "/": c = a / b; break;
}
std::cout }

Если бы пользователь ввел (обязательно с пробелами!) строку 12 + 34 , то произошло бы следующее: после того, как была введена строка и нажата клавиша Enter, в поток ввода cin была бы передана строка 12 + 34 . Ожидающая момента, когда этот поток станет непустым, инструкция std::cin >> a считала бы фрагмент от начала строки до первого символ-разделителя. Т.е. было бы считано "слово" 12 , переведено в числовую форму и присвоено переменной a . Затем настал бы черед второго "слова" - того, что находилось в строке перед следующим пробелом. Символ + после этого оказывается считан, наступает черед "слова" 34 . Считав и его, программа завершает ввод, т.к. больше ей ничего не нужно узнавать, а если в потоке остались еще какие-то "лишние" "слова", то они окажутся просто невостребованы.
Итак, что мы видим? При вводе строки и нажатии клавиши Enter происходит запись данных в поток ввода . После этого ожидающие непустого потока инструкции начинают считывать из потока . Поэтому то, что вы нажали Enter, не значит, что все введенное считается единой строкой. Напротив, считываться будут отдельные "слова", и это можно использовать для ввода сразу нескольких переменных - нужно только разделить их пробелом.
Если же вам нужно считать целую строку, воспользуйтесь функцией getline:

void h()
{
std::string s;
getline(std::cin, s);
}

Продолжим разбор примера 5.1. Что же у нас "во-вторых"? А во-вторых у нас то, что если бы пользователь после того, как программа вывела Введите первое число, ввел бы 12 + 34 , то программа бы считала "слово" 12 , перевела бы его в числовую форму, присвоила значение переменной a ; затем бы вывела предложение ввести знак и... тут же бы его считала, поскольку в потоке уже есть данные для последующего считывания. Считав символ, она бы вывела строку Введите второе число, после чего считала бы "слово" 34 , не утруждая больше пользователя необходимостью прикасаться к клавиатуре. После чего бы все посчитала и вывела бы результат - строковые представления первого числа, знака, второго числа, знака равенства и числа-ответа, что выглядело бы как 12+34=46 .
Внешне бы это выглядело бы довольно странно: программа дважды просит что-то ввести, но ничего не считывает. Однако, на самом деле ей и не нужно больше ничего считывать - в потоке ввода уже есть все нужные программе данные. Если, конечно, пользователь не ввел то что нужно, а не какую-нибудь ерунду.
Словом, тут мы видим очередной пример того, что компьютер делает то, что ему сказано, а вовсе не обязательно то, что от него хотят. Поэтому важно понимать механику всего происходящего, чтобы эффективно нагружать компьютер работой.

Небольшое техническое замечание
Может создаться впечатление, что для ввода/вывода данных мы используем потоки подобно функциям, как например printf и scanf: вызываем его, указываем куда/откуда считывать данные, и получаем результат. На самом деле это не совсем так. Стандартные потоки, такие как cin и cout , являются классами, т.е. типами, определяемыми пользователем (в данном случае - создателями стандартной библиотеки). К механизмам взаимодействия с клавиатурой и монитором они подключаются совершенно независимо от нас, можно считать, что они есть независимо от указания в нашем коде строк наподобие cout > , определенные для классов потоков - они принимают в качестве аргументов конкретный поток и переменную, и записывают данные из одного в другое. Поток же является совокупностью хранимой в нем информации и способов работы с этой информации, в частности, операторов > .

О семействе функций printf в следующем уроке.

Потоки в C++ отличаются от функций ввода/вывода в C , обеспечивая работу как со стандартными потоками данных, так и с типами данных, определяемыми пользователем, а также обеспечивая единообразный и понятный синтаксис. Чтение данных из потока называется извлечением, а вывод данных в поток – включением. Поток в C++ - последовательность байтов, независимых от конкретного устройства, с которого производится считывание данных.

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

  • входные потоки (позволяющие вводить данные в память)
  • выходные потоки (осуществляющие вывод данных из памяти)

В зависимости от вида устройства, с которым работает поток данных, их делят на:

  • стандартные потоки
  • файловые потоки
  • строковые потоки

Стандартные потоки предназначены для передачи данных с клавиатуры на экран (это: stdin - стандартный поток ввода данных, stdout - стандартный поток вывода данных и stderr - стандартный поток ошибок). Файловые потоки – для обмена информацией с файлами. Строковые потоки – для работы с массивами символов в оперативной памяти. Для поддержки этих потоков в C++ стандартная библиотека содержит иерархию классов, построенную на основе двух базовых классов:

  • ios – базовый класс, содержащий общие для ввода/вывода поля и методы
  • streambuf – обеспечивает буферизацию потоков и их взаимодействие с физическими устройствами.

От этих базовых классов наследуются классы istream и ostream для входных и выходных потоков соответственно. Эти потоки являются базовыми для iostream , который позволяет реализовывать двунаправленные потоки. Ниже в иерархии находятся файловые и строковые потоки:

  • isstrinstream – класс входного строкового потока
  • osstringstream – класс выходного строкового потока
  • stringstream – класс двунаправленного строкового потока
  • ifsteam – класс входных файловых потоков
  • ofstream – класс выходных файловых потоков
  • fstream – класс двунаправленных файловых потоков

Стандартный поток

Чтобы использовать стандартные потоки ввода-вывода нужно включать заголовочный файл .Заголовочный файл кроме описания потоков ввода-вывода содержит описание ещё и предопределенных объектов.


Таблица 1

Объект Класс Описание
cin istream связывается с клавиатурой (со стандартным буфером ввода)
cout ostream связывается с экраном (со стандартным буфером вывода)
cerr ostream связывается с экраном (стандартный не буферизованный вывод, куда направляются сообщения об ошибках)
clog ostream связывается с экраном (стандартный буферизованный вывод, куда направляются сообщения об ошибках)

Эти объекты создаются при включении в программу файла iostream . При этом становятся доступными средства ввода-вывода. Соответствующие операции > определены путем перегрузки операции сдвига.


#include int main() { int i; cin>>i;

cout