C управление форматом вывода в поток. Потоковые классы
Потоки в 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 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() Std::cout
std::cin >> a >> sign >> b; |
Если бы пользователь ввел (обязательно с пробелами!) строку 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++ имеется набор классов для управления вводом-выводом.
В отличие от функций буферизованного ввода-вывода С (таких, как print f и scanf, не выполняющих никаких проверок на соответствие аргументов форматной строке) классы потоков C++ безопасны в отношении типа. Ввод-вывод использует механизм перегрузки операций, гарантирующий вызов нужной функции-операции для указанного типа данных. Это главное преимущество потоков языка C++.
Классы потоков
К классам потоков относятся следующие:
- Класс streambuf управляет буфером потока, обеспечивая базовые операции заполнения, опорожнения, сброса и прочих манипуляций с буфером.
- Класс ios является базовым классом потоков ввода-вывода.
- Классы istream и ostream — производные от ios и обеспечивают работу потоков соответственно ввода и вывода.
- Класс iоstream является производным от двух предыдущих и предусматривает функции как для ввода, так и для вывода.
- Классы ifstream, of stream и f stream предназначены для управления файловым вводом-выводом.
- Классы istrstream и ostrstream управляют резидентными потоками (форматированием строк в памяти). Это устаревшая методика, оставшаяся в C++Builder в качестве пережитка.
Для работы с потоками вам потребуется включить в программу заголовочный файл iostream.h. Кроме того, может потребоваться подключить файлы fstream.h (файловый ввод-вывод), iomanip.h (параметризованные манипуляторы) и strstream.h (форматирование ь памяти).
Предопределенные потоки
Библиотека ввода-вывода C++ предусматривает четыре предопределенных объекта-потока, связанных со стандартными входным и выходным устройствами. Ниже дана сводка этих объектов.
Таблица 9.1. Предопределенные объекты-потоки C++
Имя | Класс | Описание |
istream | Ассоциируется со стандартным вводом (клавиатурой). |
|
cout | ostream | Ассоциируется со стандартным выводом (экраном). |
cerr | ostream | Ассоциируется со стандартным устройством ошибок (экраном) без буферизации. |
clog | ostream | Ассоциируется со стандартным устройством ошибок (экраном)с буферизацией. |
Операции извлечения и передачи в поток
Основными классами ввода-вывода C++ являются istream и ostream. Первый из них перегружает операцию правого сдвига (>>), которая служит в нем для ввода данных и называется
операцией извлечения
из потока. Класс ostream перегружает соответственно операцию левого сдвига
(