PVOID - Базовый ввод-вывод

Базовый ввод-вывод

Этот раздел является переводом туториала C++ Language


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

Поток - это объект, и программа может вставлять или извлекать символы в / из него. Нет необходимости знать подробности о внутреннем строении потока, или о любых его внутренних спецификациях. Все, что нам нужно знать, это то, что потоки являются источником / адресатом символов и что эти символы предоставляются / принимаются последовательно (то есть один за другим).

 Стандартная библиотека определяет несколько потоков, которые могут быть использованы для доступа к стандартным объектам из окружения во время работы программы:

 поток   описание 
 cin  стандартный поток ввода 
 cout  стандартный поток вывода 
 cerr  стандартный поток ошибок (вывод) 
 clog  стандартный поток журналирования (вывод) 

 

   Мы рассмотрим более подробно только cout и cin (стандартные потоки вывода и ввода). cerr и clog также являются потоками вывода, поэтому они по сути работают как cout, с той лишь разницей, что они идентифицируют потоки для определенных целей: сообщения об ошибках и журналирование; они во многих случаях фактически делают одно и то же: они печатают на экране, хотя они также могут быть индивидуально перенаправлены.

 

Стандартный вывод (cout)


   В большинстве программных сред стандартный поток вывода по умолчанию выводится на экран, объектом C++ для доступа к нему является cout.

Для операций форматированного вывода cout используется вместе с оператором вставки, который записывается как << (т.е. два знака "меньше").

cout << "Output sentence"; // печатает Output sentence на экран
cout << 120; // печатает число 120 на экран
cout << x; // печатает значение x на экран

 Оператор << вставляет данные, которые следуют за ним, в поток, который предшествует ему. В примере выше, он вставляет строковый литерал Output sentence, число 120 и значение переменной x в стандартный поток вывода cout. Заметьте, что предложение в первом выражении заключено в двойные кавычки, потому что оно является строковым литералом, в отличие от x. Когда текст заключен в двойные кавычки, он печатается как литерал; если кавычек нет, текст интерпретируется как идентификатор переменной и вместо текста печатается значение переменной. Например, такие два выражения приведут к совершенно различным результатам:

cout << "Hello";  // печатает Hello
cout << Hello;    // печатает содержимое переменной Hello 

   Можно использовать несколько операций вставки в одном выражении:

cout << "This " << " is a " << "single C++ statement";

 Это выражение напечатает текст This is a single C++ statement. Связывание вставок особенно полезно для смешивания литералов и переменных в одном выражении:

cout << "I am " << age << " years old and my zipcode is " << zipcode;

   Допустим, переменная age содержит значение 24, а переменная zipcode содержит 90064, результатом предыдущего выражения будет:

I am 24 years old and my zipcode is 90064

Чего не делает cout автоматически - не добавляет разрывы строк в конце выражения, если это не указано. Например, возьмем следующие два выражения, вставляемые в cout:

cout << "This is a sentence.";
cout << "This is another sentence.";

  Вывод будет представлять одну строку без каких-либо разделителей. Что-то вроде:

This is a sentence.This is another sentence.

   Чтобы вставить разрыв строки, символ новой строки должен быть вставлен точно в ту позицию, в которой должна быть разбита строка. В C++ символ новой строки может быть указан как \n (т.e. символ обратной косой черты, за которым следует строчная n). Например:

cout << "First sentence.\n";
cout << "Second sentence.\nThird sentence.";

   Результатом будет следующий вывод:

First sentence.
Second sentence.
Third sentence.

   Также для разрыва строки можно использовать манипулятор endl. Например:

cout << "First sentence." << endl;
cout << "Second sentence." << endl;

   Это отобразит:

First sentence.
Second sentence.

   Манипулятор endl создает символ новой строки, что эквивалентно вставке  символа '\n', однако он имеет дополнительное поведение: буфер потока (если таковой имеется) очищается, что означает, что вывод запрашивает физическую запись на устройство, если ее еще не было. Это затрагивает в основном полностью буферизованные потоки, а cout (в общем случае) не является полностью буферизованным потоком. Тем не менее, вообще-то неплохо использовать endl только тогда, когда очистка потока необходима, а '\n' в противном случае. Операция очистки приводит к определенным накладным расходам и на некоторых устройствах это может вызвать задержку.

 

Стандартный ввод (cin)


  В большинстве программных сред стандартным вводом по умолчанию является клавиатура, а объект потока C++, определенный для доступа к нему, - cin.

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

int age;
cin >> age;

   В первом выражении объявляется переменная age типа int, а во втором из cin извлекается значение, которое будет в ней храниться. Эта операция приводит к ожиданию ввода из cin. Обычно это означает, что программа будет ожидать, пока пользователь введет некоторую последовательность с клавиатуры. В этом случае стоит отметить, что введенные символы будут переданы программе после нажатия клавиши ENTER (или RETURN). При достижении операции извлечения из cin, программа будет ждать настолько долго, насколько это необходимо, пока не будет произведен ввод.

  Операция извлечения из cin использует тип переменной после >> для определения того, как интерпретировать символы из ввода; если это число, ожидаемым форматом ввода будем последовательность цифр, если строка, то последовательность символов и т.д.

// пример ввода/вывода

#include <iostream>
using namespace std;

int main ()
{
  int i;
  cout << "Please enter an integer value: ";
  cin >> i;
  cout << "The value you entered is " << i;
  cout << " and its double is " << i*2 << ".\n";
  return 0;
}
Please enter an integer value: 702
The value you entered is 702 and its double is 1404.

   Как вы видите, извлечение из cin делает задачу ввода данных из стандартного ввода довольно простой. Но этот метод имеет также большой недостаток. Что произойдет в примере выше, если пользователь введет что-то, что не может быть интерпретировано как целое число? В этом случае операция извлечения завершится неудачно. И это, по умолчанию, позволяет программе продолжить работу, не устанавливая значение переменной i, что приведет к неопределенному результату, если значение i используется позже.

Это плохое поведение программы. Большинство программ должны вести себя так, как ожидается, независимо от того, что вводит пользователь, занимаясь обработкой неправильных значений. Только очень простые программы могут зависеть от значений, введенных из cin, без соответствующей проверки. Немного позже мы увидим, как stringstream можно использовать для контроля за пользовательским вводом.

   Извлечением из cin можно запрашивать более одного элемента в одном выражении:

cin >> a >> b;

 Что эквивалентно:

cin >> a;
cin >> b;

   В обоих случаях от пользователя требуется ввод двух значений: одно для переменной a, а другое для переменной b. Для разделения двух последовательных операций ввода используется любой тип разделителей. Это могут быть пробелы, символы табуляции или новой строки.

 

cin и строки


   Оператор извлечения может быть применен к cin для извлечения строк символов так же, как и для фундаментальных типов данных:

string mystring;
cin >> mystring;

   Однако извлечение из cin считает разделители (пробелы, символы табуляции  и новой строки...) окончанием извлекаемого значения. Таким образом, извлечение строки означает извлечение одного слова, а не фразы или предложения. 

Для получения из cin целой строки имеется функция getline, которая принимает поток (cin) первым аргументом и строковую переменную вторым. Например:

// cin with strings
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string mystr;
  cout << "What's your name? ";
  getline (cin, mystr);
  cout << "Hello " << mystr << ".\n";
  cout << "What is your favorite team? ";
  getline (cin, mystr);
  cout << "I like " << mystr << " too!\n";
  return 0;
}
What's your name? Homer Simpson
Hello Homer Simpson.
What is your favorite team? The Isotopes
I like The Isotopes too!

   Обратите внимание на то, что в обоих вызовах getline мы используем один и тот же идентификатор (mystr). Во втором случае программа просто замещает предыдущее содержимое новым, которое введено.

Стандартным поведением, которое большинство пользователей ожидают от консольной программы, это запрос ввода от пользователя каждый раз, пользователь заполняет поле и затем нажимает ENTER (RETURN). Поэтому, если у вас нет веской причины не делать этого, вы всегда должны использовать getline для получения ввода в консольных программах вместо извлечения из cin.

 

stringstream


  Стандартный заголовочный файл <sstream> определяет тип stringstream, который позволяет обрабатывать строку как поток, и это позволяет использовать операции вставки/извлечения в/из строки так же, как если бы она была потоком cin или cout. Эта возможность особенно полезна для преобразования строк в числовые значения и наоборот. Например, чтобы извлечь целое число из строки, мы можем написать:

string mystr ("1204");
int myint;
stringstream(mystr) >> myint;

   Здесь объявляется переменная типа string и инициализируется значением "1204", и переменная типа int. Затем в третьей строке эта переменная используется для извлечения из потока, созданного из строки. Эта часть кода сохраняет числовое значение 1204 в переменной myint.

// stringstreams
#include <iostream>
#include <string>
#include <sstream>
using namespace std;

int main ()
{
  string mystr;
  float price=0;
  int quantity=0;

  cout << "Enter price: ";
  getline (cin,mystr);
  stringstream(mystr) >> price;
  cout << "Enter quantity: ";
  getline (cin,mystr);
  stringstream(mystr) >> quantity;
  cout << "Total price: " << price*quantity << endl;
  return 0;
}
Enter price: 22.25
Enter quantity: 7
Total price: 155.75

В этом примере мы получаем числовые значения из стандартного ввода косвенно: вместо извлечения числовых значений непосредственно из cin мы получаем строки из него в строковый объект (mystr), затем мы извлекаем значения из этой строки в переменные price и quantity. Так как это числовые значения, с ними можно производить арифметические операции, такие как умножение, для получения итоговой цены.

Используя этот подход для получения целых строк и извлечения их содержимого, мы отделяем процесс получения пользовательского ввода от его интерпретации как данных, позволяя процессу ввода быть тем, чего ожидает пользователь, и в то же время получить больше контроля над преобразованием его содержимого в полезные данные.