23.2 – Ввод данных с помощью istream
Библиотека iostream
довольно сложна, поэтому в данных руководствах мы не сможем охватить ее полностью. Однако мы покажем вам наиболее часто используемые функции. В этом разделе мы рассмотрим различные аспекты класса для входных данных (istream
).
Оператор извлечения
Как видно из многих уроков, для чтения информации из входного потока мы можем использовать оператор извлечения (>>
). В C++ есть предопределенные операции извлечения для всех встроенных типов данных, и вы уже видели, как можно перегрузить оператор извлечения для своих собственных классов.
При чтении строк одна из распространенных проблем с оператором извлечения заключается в том, как не допустить переполнения буфера входными данными. Рассмотрим следующий пример:
char buf[10];
std::cin >> buf;
Что произойдет, если пользователь введет 18 символов? Буфер переполнится, и случится неприятное. Вообще говоря, делать какие-либо предположения о том, сколько символов введет ваш пользователь, – плохая идея.
Один из способов решения этой проблемы – использование манипуляторов. Манипулятор – это объект, который используется для изменения потока при применении с извлечением (>>
) или вставкой (setw
(в заголовке iomanip.h), и который можно использовать для ограничения количества символов, считываемых из потока. Использовать setw()
просто – укажите максимальное количество символов для чтения в качестве параметра и вставьте его в свою инструкцию ввода, например:
#include <iomanip.h>
char buf[10];
std::cin >> std::setw(10) >> buf;
Эта программа теперь будет читать только первые 9 символов из потока (оставляя место для завершающего нуля). Любые оставшиеся символы будут оставлены в потоке до следующего извлечения.
Извлечение и пробелы
Единственное, что мы пока не упомянули, это то, что оператор извлечения работает с «отформатированными» данными, то есть пропускает пробелы (пробелы, табуляции и символы новой строки).
Рассмотрим следующую программу:
int main()
{
char ch;
while (std::cin >> ch)
std::cout << ch;
return 0;
}
Когда пользователь вводит следующее:
Hello my name is Alex
Оператор извлечения пропускает пробелы и символ новой строки. Следовательно, на выходе получается:
HellomynameisAlex
Часто вам нужно, чтобы пользователь вводил данные, но чтобы пробелы не отбрасывались. Для этого класс istream
предоставляет множество функций.
Одна из наиболее полезных – функция get()
, которая просто получает символ из входного потока. Вот та же программа, что и выше, с использованием get()
:
int main()
{
char ch;
while (std::cin.get(ch))
std::cout << ch;
return 0;
}
Теперь, когда мы вводим следующее:
Hello my name is Alex
Результат будет таким:
Hello my name is Alex
std::get()
также имеет строковую версию, которая принимает максимальное количество символов для чтения:
int main()
{
char strBuf[11];
std::cin.get(strBuf, 11);
std::cout << strBuf << '\n';
return 0;
}
Если мы введем:
Hello my name is Alex
Результат будет следующим:
Hello my n
Обратите внимание, что мы считываем только первые 10 символов (нам пришлось оставить один символ для завершающего нуля). Остальные символы остались во входном потоке.
Важно отметить, что функция get()
не читает символы новой строки! Это может привести к неожиданным результатам:
int main()
{
char strBuf[11];
// Прочитать до 10 символов
std::cin.get(strBuf, 11);
std::cout << strBuf << '\n';
// Прочитать еще до 10 символов
std::cin.get(strBuf, 11);
std::cout << strBuf << '\n';
return 0;
}
Если пользователь вводит:
Hello!
Программа напечатает:
Hello!
А затем завершится! Почему она не запросила еще 10 символов? Ответ в том, что сначала get()
прочитал до новой строки, а затем остановился. Второй вызов get()
увидел, что в потоке cin
всё еще есть входные данные, и попытался их прочитать. Но первым символом был символ новой строки, поэтому он сразу завершился.
Следовательно, есть еще одна функция, называемая getline()
, которая работает точно так же, как get()
, но также читает и символ новой строки.
int main()
{
char strBuf[11];
// Прочитать до 10 символов
std::cin.getline(strBuf, 11);
std::cout << strBuf << '\n';
// Прочитать еще до 10 символов
std::cin.getline(strBuf, 11);
std::cout << strBuf << '\n';
return 0;
}
Этот код будет работать так, как вы и ожидаете, даже если пользователь вводит строку с символом новой строки.
Если вам нужно знать, сколько символов было извлечено последним вызовом getline()
, используйте gcount()
:
int main()
{
char strBuf[100];
std::cin.getline(strBuf, 100);
std::cout << strBuf << '\n';
std::cout << std::cin.gcount() << " characters were read" << std::endl;
return 0;
}
Специальная версия getline()
для std::string
Существует специальная версия getline()
, которая находится вне класса istream
и используется для чтения в переменные типа std::string
. Эта специальная версия не является членом ни ostream
, ни istream
и включена в заголовок string
. Вот пример ее использования:
#include <string>
#include <iostream>
int main()
{
std::string strBuf;
std::getline(std::cin, strBuf);
std::cout << strBuf << '\n';
return 0;
}
Еще несколько полезных функций istream
Есть еще несколько полезных функций ввода, которые вы, возможно, захотите использовать:
ignore()
отбрасывает первый символ в потоке;ignore(int nCount)
отбрасывает первыеnCount
символов;peek()
позволяет прочитать символ из потока, не удаляя его из потока;unget()
возвращает последний прочитанный символ обратно в поток, чтобы его можно было прочитать снова при следующем вызове;putback(char ch)
позволяет вам поместить выбранный вами символ обратно в поток для чтения при следующем вызове.
istream
содержит множество других функций и вариаций показанных выше функций, которые могут быть полезны в зависимости от того, что вам нужно сделать. Однако эти темы больше подходят для учебника или книги, посвященной стандартной библиотеке.