Потоки и система ввода-вывода
Все инструменты для работы с системой ввода-вывода и потоками в языке С++ определены в стандартной библиотеке. Заголовочный файл iostream определяет следующие базовые типы для работы с потоками:
istream и wistream : читают данные с потока
ostream и wostream : записывают данные в поток
iostream и wiostream : читают и записывают данные в поток
Для каждого типа определен его двойник, который начинается на букву w и который предназначен для поддержки данных типа wchar_t.
Эти типы являются базовыми для других классов, управляющих потоками ввода-вывода.
Объект типа ostream получает значения различных типов, преобразует их в последовательность символов и передает их через буфер в определенное место для вывода (консоль, файл, сетевые интерфейсы и т.д.)
Поток istream получает через буфер из определенного места последовательности символов (с консоли, из файла, из сети и т.д.) и преобразует эти последовательности в значения различных типов. То есть когда мы вводим данные (с той же клавиатуры в консоли), сначала данные накапливаются в буфере и только затем передаются объекту istream.
По умолчанию в стандартной библиотеке определены объекты этих классов — cout , cin , cerr , которые работают с консолью.
Запись в поток
Для записи данных в поток ostream применяется оператор << . Этот оператор получает два операнда. Левый операнд представляет объект типа ostream, а правый операнд — значение, которое надо вывести в поток.
К примеру, по умолчанию стандартная библиотека C++ предоставляет объект cout , который представляет тип ostream и позволяет выводить данные на консоль:
Так как оператор << возвращает левый операнд — cout, то с помощью цепочки операторов мы можем передать на консоль несколько значений:
Чтение данных
Для чтения данных из потока применяется оператор ввода >> , который принимает два операнда. Левый операнд представляет поток istream, с которого производится считывание, а правый операнд — объект, в который считываются данные.
Для чтения с консоли применяется объект cin , который представляет тип istream.
Однако такой способ не очень подходит для чтения строк с консоли особенно когда считываемая строка содержит пробельные символы. В этом случае лучше использовать встроенную функцию getline() , которая в качестве параметра принимает поток istream и переменную типа string, в которую надо считать данные:
Пример работы программы:
По умолчанию признаком окончания ввода служит перевод на другую строку, например, с помощью клавиши Enter. Но также можно задать свой признак окончания ввода с помощью дополнительного параметра функции getline() . Для этого надо передать символ, который будет служить окончанием ввода:
В данном случае ввод завершится, когда пользователь введет символ *. Таким образом, мы можем ввести многострочный текст, но при вводе звездочки ввод завершится. Пример работы программы:
Вывод ошибок
Для вывода сообщения об ошибке на консоль применяется объект cerr , который представляет объект типа ostream:
Потоки символов wchar_t
Для работы с потоками данных типов wchar_t в стандартной библиотеке определены объекты wcout (тип wostream), wcerr (тип wostream) и wcin (тип wistream), которые являются аналогами для объектов cout, cerr и cin и работают аналогично
# C++ Streams
(opens new window) is a class whose objects look like an output stream (that is, you can write to them via operator<< ), but actually store the writing results, and provide them in the form of a stream.
Consider the following short code:
creates such an object. This object is first manipulated like a regular stream:
Following that, though, the resulting stream can be obtained like this:
(the string result will be equal to "the answer to everything is 42" ).
This is mainly useful when we have a class for which stream serialization has been defined, and for which we want a string form. For example, suppose we have some class
To get the string representation of a foo object,
Then result contains the string representation of the foo object.
# Printing collections with iostream
# Basic printing
std::ostream_iterator allows to print contents of an STL container to any output stream without explicit loops. The second argument of std::ostream_iterator constructor sets the delimiter. For example, the following code:
# Implicit type cast
std::ostream_iterator allows to cast container’s content type implicitly. For example, let’s tune std::cout to print floating-point values with 3 digits after decimal point:
and instantiate std::ostream_iterator with float , while the contained values remain int :
so the code above yields
despite std::vector holds int s.
# Generation and transformation
std::generate , std::generate_n and std::transform functions provide a very powerful tool for on-the-fly data manipulation. For example, having a vector:
we can easily print boolean value of "x is even" statement for each element:
or print the squared element:
Printing N space-delimited random numbers:
# Arrays
As in the section about reading text files, almost all these considerations may be applied to native arrays. For example, let’s print squared values from a native array:
# Reading a file till the end
# Reading a text file line-by-line
A proper way to read a text file line-by-line till the end is usually not clear from ifstream documentation. Let’s consider some common mistakes done by beginner C++ programmers, and a proper way to read the file.
# Lines without whitespace characters
For the sake of simplicity, let’s assume that each line in the file contains no whitespace symbols.
ifstream has operator bool() , which returns true when a stream has no errors and is ready to read. Moreover, ifstream::operator >> returns a reference to the stream itself, so we can read and check for EOF (as well as for errors) in one line with very elegant syntax:
# Lines with whitespace characters
ifstream::operator >> reads the stream until any whitespace character occurs, so the above code will print the words from a line on separate lines. To read everything till the end of line, use std::getline instead of ifstream::operator >> . getline returns reference to the thread it worked with, so the same syntax is available:
Obviously, std::getline should also be used for reading a single-line file till the end.
# Reading a file into a buffer at once
Finally, let’s read the file from the beginning till the end without stopping at any character, including whitespaces and newlines. If we know the exact file size or upper bound of the length is acceptable, we can resize the string and then read:
Otherwise, we need to insert each character to the end of the string, so std::back_inserter is what we need:
Alternatively, it is possible to initialize a collection with stream data, using a constructor with iterator range arguments:
Note that these examples are also applicable if ifs is opened as binary file:
# Copying streams
A file may be copied to another file with streams and iterators:
or redirected to any other type of stream with a compatible interface. For example Boost.Asio network stream:
# Arrays
As iterators might be thought of as a generalization of pointers, STL containers in the examples above may be replaced with native arrays. Here is how to parse numbers into array:
Beware of buffer overflow, as arrays cannot be resized on-the-fly after they were allocated. For example, if the code above will be fed with a file that contains more than 100 integer numbers, it will attempt to write outside the array and run into undefined behavior.
# Parsing files
# Parsing files into STL containers
istream_iterator s are very useful for reading sequences of numbers or other parsable data into STL containers without explicit loops in the code.
Using explicit container size:
or with inserting iterator:
Note that the numbers in the input file may be divided by any number of any whitespace characters and newlines.
# Parsing heterogeneous text tables
As istream::operator>> reads text until a whitespace symbol, it may be used in while condition to parse complex data tables. For example, if we have a file with two real numbers followed by a string (without spaces) on each line:
it may be parsed like this:
# Transformation
Any range-manipulating function may be used with std::istream_iterator ranges. One of them is std::transform , which allows to process data on-the-fly. For example, let’s read integer values, multiply them by 3.14 and store the result into floating-point container:
# Remarks
Default constructor of std::istream_iterator constructs an iterator which represents the end of the stream. Thus, std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(), . means to copy from the current position in ifs to the end.
Ostream — зачем нужен?
Извините, подскажите пожалуйста, что такое ostream?
Не могу понять, зачем он нужен, хотя бы простой пример с какой целью он применяется, я конечно абстрактно понимаю, что для записи в поток, но что это в данном случае значит и чем к примеру ostream отличается от ofstream, что то так и не понял.
friend ostream &operator<<(ostream &stream, MyClass o);
Что означает данная строчка которую обычно пишут в конце класса? friend ostream.
Зачем нужен extern?
Знаю, что в интернете полно объяснений зачем он, но я не могу понять. Везде пишут, что он.
, каков его физический смысл так сказать ?
Почему я тогда не могу сделать так же по аналогии:
Сообщение от Optimus11
Сообщение от Optimus11
Сообщение от Optimus11
Потому что std::ostringstream не наследнки basic_streambuf, в отличие от boost::asio::streambuf.
Конструктор от basic_streambuf описан в справке: https://en.cppreference.com/w/. ic_ostream
Сообщение от Optimus11
Физический смысл простой. Создает объект потока, который связывается с кастомным буфером потока из boost::asio.
Добавлено через 1 минуту
Сообщение от Optimus11
Потому что буферу нужен объект потока, чтобы работать так, как ты показал. Такой дизайн.
Добавлено через 2 минуты
Идея в том, чтобы написать один раз класс потока. А затем связывать его с разными буферами для получения разного поведения: буфер консоли, файловый буфер, можно сделать буфер в компорт или сетевой сокет, как в asio.
Повторное использование кода, полиморфизм, все дела.
Зачем нужен const?
Добрый вечер, разбираюсь с классом стек, подскажите пожалуйста, для чего здесь нужен const? .
Зачем нужен конструктор?
Всем доброго дня. Ребят такая проблема объясните мне на пальцах для чего нужен конструктор в.
Зачем нужен inline
Скажите пожалуйста, зачем нужна команда inline? Изучаю классы, и там говорится мимолётом, что эта.
23.3 – Вывод данных с помощью ostream и ios
В этом разделе мы рассмотрим различные аспекты класса библиотеки iostream для вывода данных ( ostream ).
Примечание. Все функции ввода/вывода в этом уроке находятся в пространстве имен std . Это означает, что все объекты и функции ввода/вывода должны иметь префикс std:: , или должна быть использована инструкция using namespace std; .
Оператор вставки
Оператор вставки используется для записи данных в выходной поток. В C++ уже есть предопределенные операции вставки для всех встроенных типов данных, а как перегрузить оператор вставки для пользовательских классов, мы рассмотрели в уроке «13.4 – Перегрузка операторов ввода/вывода».
В уроке о потоках вы видели, что и istream , и ostream были производными от класса с именем ios . Одна из задач ios (и ios_base ) – управлять параметрами форматирования вывода.
Форматирование
Есть два способа изменить параметры форматирования: флаги и манипуляторы. Вы можете думать о флагах как о логических переменных, которые можно включать и выключать. Манипуляторы – это объекты, помещенные в поток, которые влияют на способ ввода и вывода.
Чтобы включить флаг, используйте функцию setf() с соответствующим флагом в качестве параметра. Например, по умолчанию C++ не выводит знак + перед положительными числами. Однако, используя флаг std::ios::showpos , мы можем изменить это поведение:
Это приводит к следующему выводу:
Можно включить сразу несколько флагов ios с помощью оператора ИЛИ ( | ):
Чтобы отключить флаг, используйте функцию unsetf() :
Это приводит к следующему выводу:
Есть еще одна хитрость при использовании setf() , о которой следует упомянуть. Многие флаги принадлежат группам, называемым группами форматирования. Группа форматирования – это группа флагов, которые управляют аналогичными (иногда взаимоисключающими) параметрами форматирования. Например, группа форматирования с именем basefield содержит флаги oct , dec и hex , которые управляют основанием целочисленных значений. По умолчанию установлен флаг dec . Следовательно, если мы сделаем это:
То получим такой вывод:
Не сработало! Причина в том, что setf() только включает флаги – этого недостаточно, чтобы отключить взаимоисключающие флаги. Следовательно, когда мы включили std::hex , std::ios::dec всё еще был включен, а std::ios::dec явно имеет приоритет. Есть два способа обойти эту проблему.
Во-первых, мы можем отключить std::ios::dec , чтобы был установлен только std::hex :
Теперь мы получаем ожидаемый результат:
Второй способ – использовать другую форму setf() , которая принимает два параметра: первый параметр – это флаг, который нужно установить, а второй – это группа форматирования, к которой он принадлежит. При использовании этой формы setf() все флаги, принадлежащие группе, отключаются, и включается только переданный флаг. Например:
Это также дает ожидаемый результат:
Использование setf() и unsetf() может быть неудобным, поэтому C++ предоставляет второй способ изменения параметров форматирования: манипуляторы. В манипуляторах хорошо то, что они достаточно умны, чтобы включать и выключать соответствующие флаги. Вот пример использования некоторых манипуляторов для изменения основания чисел:
Эта программа создает следующий вывод:
В общем, пользоваться манипуляторами намного проще, чем устанавливать и сбрасывать флаги. Многие параметры доступны как через флаги, так и через манипуляторы (например, изменение основания числа), однако некоторые параметры доступны только через флаги или только через манипуляторы, поэтому важно знать, как использовать и то, и другое.
Полезные инструменты форматирования
Вот список некоторых наиболее полезных флагов, манипуляторов и функций-членов. Флаги находятся в классе std::ios , манипуляторы находятся в пространстве имен std , а функции-члены живут в классе std::ostream .
Группа | Флаг | Назначение |
---|---|---|
std::ios::boolalpha | Если установлен, логические значения выводят как " true " или " false ". Если не установлен, логические значения выводят как 0 или 1. |
Манипулятор | Назначение |
---|---|
std::boolalpha | Логические значения выводят как " true " или " false ". |
std::noboolalpha | Логические значения выводят как 0 или 1 (по умолчанию). |
Группа | Флаг | Назначение |
---|---|---|
std::ios::showpos | Если установлен, перед положительными числами ставится +. |
Манипулятор | Назначение |
---|---|
std::showpos | Добавляет к положительным числам префикс + |
std::noshowpos | Не ставит перед положительными числами знак + |
Группа | Флаг | Назначение |
---|---|---|
std::ios::uppercase | Если установлен, используются буквы верхнего регистра. |
Манипулятор | Назначение |
---|---|
std::uppercase | Использует буквы верхнего регистра |
std::nouppercase | Использует буквы нижнего регистра |
Группа | Флаг | Назначение |
---|---|---|
std::ios::basefield | std::ios::dec | Печатает значения в десятичном формате (по умолчанию) |
std::ios::basefield | std::ios::hex | Печатает значения в шестнадцатеричном формате |
std::ios::basefield | std::ios::oct | Печатает значения в восьмеричном формате |
std::ios::basefield | (нет) | Печатает значения в соответствии с начальными символами значения |
Манипулятор | Назначение |
---|---|
std::dec | Печатает значения в десятичном формате |
std::hex | Печатает значения в шестнадцатеричном формате |
std::oct | Печатает значения в восьмеричном формате |
К этому моменту вы должны увидеть взаимосвязь между настройкой форматирования с помощью флагов и с помощью манипуляторов. В будущих примерах мы будем использовать манипуляторы, если они доступны.
Точность, формат записи и десятичные точки
Используя манипуляторы (или флаги), можно изменить точность и формат отображения чисел с плавающей запятой. Есть несколько вариантов форматирования, которые комбинируются сложным образом, поэтому мы рассмотрим их подробнее.
Группа | Флаг | Назначение |
---|---|---|
std::ios::floatfield | std::ios::fixed | Использует десятичную запись для чисел с плавающей запятой |
std::ios::floatfield | std::ios::scientific | Использует экспоненциальную запись для чисел с плавающей запятой |
std::ios::floatfield | (нет) | Использует десятичную запись для чисел с несколькими цифрами, в противном случае – экспоненциальную запись |
std::ios::floatfield | std::ios::showpoint | Всегда показывать десятичную точку и завершающие нули для значений с плавающей запятой |
Манипулятор | Назначение |
---|---|
std::fixed | Использует десятичную запись значений |
std::scientific | Использует экспоненциальную запись значений |
std::showpoint | Показывает десятичную точку и завершающие нули для значений с плавающей запятой |
std::noshowpoint | Не показывать десятичную точку и завершающие нули для значений с плавающей запятой |
std::setprecision(int) | Устанавливает точность чисел с плавающей запятой (определен в iomanip.h ) |
Функция-член | Назначение |
---|---|
std::precision() | Возвращает текущую точность чисел с плавающей запятой |
std::precision(int) | Устанавливает точность чисел с плавающей запятой и возвращает старую точность |
Если используется фиксированная или экспоненциальная запись, точность определяет, сколько знаков отображается в дробной части. Обратите внимание, что если точность меньше количества значащих цифр, число будет округлено.
Дает следующий результат:
Если не используется ни фиксированная, ни экспоненциальная запись, точность определяет, сколько следует отображать значащих цифр. Опять же, если точность меньше количества значащих цифр, число будет округлено.
Дает следующий результат:
Используя манипулятор или флаг showpoint , вы можете заставить поток записывать десятичную точку и завершающие нули.
Дает следующий результат:
Вот сводная таблица с еще несколькими примерами:
Тип записи | Точность | 12345.0 | 0.12345 |
---|---|---|---|
Обычный | 3 | 1.23e+004 | 0.123 |
4 | 1.235e+004 | 0.1235 | |
5 | 12345 | 0.12345 | |
6 | 12345 | 0.12345 | |
Показывать десятичную точку |
3 | 1.23e+004 | 0.123 |
4 | 1.235e+004 | 0.1235 | |
5 | 12345. | 0.12345 | |
6 | 12345.0 | 0.123450 | |
Фиксированная запись |
3 | 12345.000 | 0.123 |
4 | 12345.0000 | 0.1235 | |
5 | 12345.00000 | 0.12345 | |
6 | 12345.000000 | 0.123450 | |
Экспоненциальная запись |
3 | 1.235e+004 | 1.235e-001 |
4 | 1.2345e+004 | 1.2345e-001 | |
5 | 1.23450e+004 | 1.23450e-001 | |
6 | 1.234500e+004 | 1.234500e-001 |
Ширина, символы заполнения и выравнивание
Обычно при печати чисел числа печатаются без учета пространства вокруг них. Однако печать чисел можно выровнять по левому или правому краю. Для этого мы должны сначала определить ширину поля, которая определяет количество выходных мест печати символов, которые будут заниматься печатаемым значением. Если фактическое напечатанное число меньше ширины поля, оно будет выровнено по левому или правому краю (как указано). Если фактическое число больше ширины поля, оно не будет усечено – оно приведет к переполнению поля.
Группа | Флаг | Назначение |
---|---|---|
std::ios::adjustfield | std::ios::internal | Выравнивает знак числа по левому краю, а значение – по правому краю |
std::ios::adjustfield | std::ios::left | Выравнивание знака и значения по левому краю |
std::ios::adjustfield | std::ios::right | Выравнивает знак и значение по правому краю (по умолчанию) |
Манипулятор | Назначение |
---|---|
std::internal | Выравнивает знак числа по левому краю, а значение – по правому краю |
std::left | Выравнивание знака и значения по левому краю |
std::right | Выравнивает знак и значение по правому краю |
std::setfill(char) | Устанавливает параметр как символ заполнения (определен в iomanip.h ) |
std::setw(int) | Устанавливает ширину поля для ввода и вывода в значение параметра (определен в iomanip.h ) |
Функция-член | Назначение |
---|---|
std::fill() | Возвращает текущий символ заполнения |
std::fill(char) | Устанавливает символ заполнения и возвращает старый символ заполнения |
std::width() | Возвращает текущую ширину поля |
std::width(int) | Устанавливает новую ширину поля и возвращает старую ширину поля |
Чтобы использовать любое из этих средств форматирования, мы сначала должны установить ширину поля. Это можно сделать с помощью функции-члена width(int) или манипулятора setw() . Обратите внимание, что по умолчанию используется выравнивание по правому краю.
Это дает следующий результат:
Следует отметить, что setw() и width() влияют только на следующую инструкцию вывода. Они не постоянны, как некоторые другие флаги/манипуляторы.
Теперь давайте установим символ заполнения и выполним тот же пример:
Это дает следующий результат:
Обратите внимание, что все пустые места в поле заполнены символом заполнения.
Класс ostream и библиотека iostream содержат функции вывода, флаги и манипуляторы, которые могут быть полезны в зависимости от того, что вам нужно сделать. Как и в случае с классом istream , эти темы больше подходят для учебника или книги, посвященной стандартной библиотеке.