На этом шаге мы рассмотрим особенности помещения данных в поток и извлечения данных из потока.
Обратите внимание, что операции << и >> обеспечивают связи с потоками только в том случае, если они употребляются справа от имен потоковых объектов. В противном случае они как обычно обозначают операции сдвига. В соответствии с синтаксисом языка операции сдвига << и >> имеют невысокий приоритет. Им "предшествуют", например, все арифметические oпeрации, преобразования типов, скобки и др. Использование операций << и >> для обозначения передач данных в потоки и из потоков не изменяет их приоритета. Поэтому допустима, например, такая запись:
cout << 2+3+4;
В результате выполнения этого оператора на экран будет выведено значение 9. Таким образом, арифметические выражения можно без скобок использовать в качестве правых операндов с операцией включения (вывода) данных в поток.
Чтобы вывести в поток значение выражения, содержащего операции более низкого ранга чем <<, требуется применение скобок:
cout << (а + b < с) ;
Так как условная операция ?: и операции сравнения имеют более низкий приоритет, чем операция сдвига <<, то следующий оператор для отрицательного значения х:
cout << х < 0 ? -х : х;
никогда не сможет вывести абсолютное значение переменной х. Правильная запись:
cout << (х < 0 ? -х : х) ;
Операции сдвига можно использовать в выводимых выражениях, но обязательно должны быть использованы скобки. Следующий оператор:
cout << (2 << 1);
выведет в поток (и на экран) значение 4.
Выражения, в которые входят операции << и >>, должны иметь значения. В соответствии с определением, находящимся в файле iostream. h, значением выражения:
cout << выражение
является ссылка на объект cout, т.е. операция включения << возвращает ссылку на тот потоковый объект, который указан слева от нее в выражении. Следовательно, к результату выполнения операции включения можно вновь применить операцию <<, как и к объекту cout. Таким образом рационально применять "цепочки" операций вывода в поток. Например, так:
cout << "\nx * 2 = " << х * 2;
С помощью скобок можно следующим образом пояснить порядок вычисления этого выражения:
(cout << "\nx * 2 = ") << х * 2;
Если значением х служит 33, то в результате выполнения любого из этих операторов с новой строки на экран будет выведено:
x * 2 = 66 .
Для k равного 1 после выполнения оператора:
cout << "\n k * 2 = " << (k << 1) << " k << 2 = " << (k << 2) ;
результат на экране дисплея будет таким:
k * 2 = 2 k << 2 = 4 .
При записи цепочек операторов вывода нужно не забывать о приоритете операций и помнить, что правила трактовки могут зависеть от реализации компилятора. Например, после выполнения следующих операторов:
int k = 1;
cout << "\nk++ = " << k++ <<
" (k += 3) = " << (k += 3) << " k++ = " << k++;
k++ =5 (k += 3) = 5 k++ = 1 .
Зависимость от компилятора результатов выполнения цепочки операций включения и необходимость аккуратно учитывать приоритеты операций приводят к следующей нарушенной в этом примере рекомендации: изменяемая переменная не должна появляться в цепочке вывода более одного раза.
"Цепочки" операций обмена можно формировать и при вводе данных (при их извлечении, т.е. чтении) из потока. Например, для int i, j, k, l; следующий оператор:
cin >> i >> j >> k >> l;
обеспечивает ввод последовательности целых значений переменных i, j, k, l. Элементы этой последовательности во входном потоке должны разделяться обобщенными пробельными символами (пробелами, знаками табуляции, символами перевода строк). Исходные данные можно ввести либо размещая их на экране в одну строку (извлечение из потока cin происходит только после сигнала от клавиши Enter):
1 2 3 4 <Enter>
либо помещая каждое значение на отдельной строке, т.е. нажимая клавишу Enter после каждого вводимого значения:
1 <Enter> 2 <Enter> 3 <Enter> 4 <Enter>
Отметим, что при вводе "в одну строку", когда значения разделяются обобщенными пробельными символами, количество чисел, набранных на клавиатуре, может превышать количество операций извлечения <<. Hапример, тот же результат будет получен (введен), если набрать:
1 2 3 4 5 6 7 8 9 <Enter>
При вводе и выводе значений элементов массивов необходимо явно использовать их обозначения в виде индексированных переменных. Например, попытка выполнить операторы:
float real[3] = { 10.0, 20.0, 30.0 } ;
cout << real;
приведет к выводу на экран только адреса первого элемента массива, ибо имя массива воспринимается как указатель со значением адреса начала массива. Чтобы вывести значения элементов, их нужно явно обозначить с помощью индексирования:
cout << real[0] << " " << real[l] << " " << real[2];
При попытке ввести значения элементов массива с помощью оператора:
cin >> real;
получим сообщение об ошибке на этапе компиляции.
Для операторов:
double e[5]; for (int i = 0; i < 5; i++) cin << e[i];
на клавиатуре необходимо набрать, например, такую последовательность значений:
0.01 0.02 0.03 0.04 0.05 <Enter>
При вводе-выводе целых чисел существуют ограничения на длины внешнего и внутреннего представлений. Например, если при выполнении операторов:
int i; cin >> i; cout << "\ni = " << i;
набрать на клавиатуре число 123456789, то результатом, выведенным на экран дисплея, будет (в конкретном случае):
i = -13035
Та же последовательность цифр 123456789, набираемая на клавиатуре для длинного целого long g, будет безболезненно воспринята и выведена операторами:
cin >> g; cout << "\ng = " << g;
При выводе в стандартный поток правым операндом может быть любая константа, допустимая реализацией компилятора. Например при выполнении оператора:
cout << 123456789;
все будет в порядке - именно такое значение будет выведено на экран. Оператор с недопустимой константой, например, такой:
cout << 98765432100;
приведет к выводу неверного числового значения. В соответствии с ограничениями реализации компилятора ВС++ справа от знака включения << можно записывать целые константы от 0 до 4294967295.
При вводе целочисленного значения его можно набрать на клавиатуре в восьмеричном или шестнадцатеричном виде, например, при выполнении операторов:
int N; cin >> N; cout << "\nN = " << N;
можно ввести восьмеричное значение 077777 и получить ответ в десятичном виде:
N = 32767
Введя шестнадцатеричное значение -0x7FFF, получим:
N = -32767
Значения указателей (т.е. адреса) выводятся в стандартный поток в шестнадцатеричном виде. Вывод числового значения, например типа int, в шестнадцатеричном или восьмеричном виде по умолчанию не выполняется. Для смены десятичного основания необходимо специальное "воздействие" на поток. Средства такого управления потоком будут рассмотрены позже.
При вводе вещественных чисел допустимы все их формы записи, т.е. они могут быть представлены во входном потоке (введены с клавиатуры) в форме с фиксированной точкой либо с указанием мантиссы и порядка, т.е. в форме с плавающей точкой. Например, операторы:
float pi; cin >> pi; cout << "pi = " << pi;
при вводе числового значения в виде 3.141593, или 3.141593e0, или +3.141593, или 0.3141593е+1 всегда приведут к печати:
pi = 3.141593
Если при вводе вещественного значения набрать на клавиатуре:
3.1415926535897932385 <Enter>
pi = 3.141593
Если вещественное значение слишком мало и при записи с фиксированной точкой значащая часть выходит за разрядную сетку, то вывод происходит в форме с плавающей точкой. Для последовательности:
float at; cin >> at; cout << "\nat = " " at;
0.00000000000000000000001 <Enter>
at = 1е-23
Тот же результат будет выведен, если на клавиатуре набрать 1.0e-23.
Наличие буфера в стандартном входном потоке создает некоторые особенности. В процессе набора данных на клавиатуре они отображаются на экране, но не извлекаются из потока. Это дает возможность исправлять допущенные ошибки во вводимых данных до того, как значения будут выбраны из входного потока. Извлечение данных из потока, т.е. собственно выполнение операции cin >> происходит только после нажатия клавиши Enter. При этом вся набранная строка переносится в буфер ввода, и именно из буфера ввода начинается "чтение". При извлечении числовых данных игнорируются начальные пробельные символы. Чтение начинается с первого непробельного символа и заканчивается при появлении не числового символа. При вводе целых читаются символы + , - , десятичные цифры и, если число вводится в шестнадцатеричном виде (признак 0x), то буквы A, а, B, b, C, c, D, d, Е, e, F, f. Для вещественных чисел дополнительно может появиться символ Е или e, как обозначение экспоненциальной части числа, и точка, отделяющая целую часть от дробной. Выборка из входного потока прекращается, как только очередной символ окажется недопустимым. Например, если для операторов:
int К; float Е; cin >> К >> Е; cout << "К = " << К << " Е = " << Е;
1234.567 89 <Enter>
К = 1234 Е = 0.567
Здесь точка явилась недопустимым символом для K, но корректно воспринята при вводе значения Е. Символы 89 из входного потока проигнорированы, так как извлечение закончилось при достижении пробела, а больше операция >> не применяется.
При вводе из стандартного потока вещественного значения можно набрать на клавиатуре большое целое число. Например, для операторов:
double D; cin >> D; cout << "D = " " D;
112233445566778899 <Enter>
D = 1.122334е+17
Ввод-вывод массивов и символьных массивов-строк - это различающиеся процедуры. Как известно, символьная строка всегда представляется как массив типа char, последним значащим элементом в котором является литера '\0'. Именно до этой литеры оператор вывода переносит в выходной поток символы из памяти:
char H[] = "Qui pro quo - путаница";
cout << "\n" << Н;
Qui pro quo - путаница
Если в той же программе добавить операторы:
char *pH = H;
cout << "\n" << pH;
то результат не изменится - будет выведена строка, с которой связан указатель рH. Операция вывода <<, "настроенная" на операнд типа char *, всегда выводит строку, а не значение указателя, связанного с этой строкой. Чтобы вывести не значение строки, а значение указателя, необходимо явное приведение типа. Например, оператор:
cout << "\nукаэатвль = " << (void *)рН;
выведет не значение строки, начало которой есть значение указателя, а собственно адрес. Для указателя, не связанного с символьной строкой (т.е. не имеющего типа char *), и вывод указателя, и вывод результата его приведения к типу (void *) будут одинаковы. Например, выполнение операторов:
int *pi, i = 6; pi = &i; cout << "\npi = " << pi; cout << "\n(void *)pi = " << (void *)pi;
pi = 0x106е
(void*)pi = 0x106е
char сc[5] = { 'а', 'b' , 'с', 'd', 'e' };
cout << "\ncc = " << сc;
сc = abcde
При вводе строки на клавиатуре набираются любые символы до тех пор, пока не будет нажата клавиша Enter. Например, для операторов:
char line[255], stroka[80];
cin << line << stroka;
на клавиатуре может набираться любая последовательность символов, пока не появится код от клавиши Enter. Система ввода-вывода переносит эту последовательность в буфер входного потока, а из буфера при выполнении каждой операции >> извлечение происходит до ближайшего пробела. Вместо пробельного символа заносится код '\0', тем самым завершая строку. Если при выполнении этого оператора ввода набрать на клавиатуре:
ERGO (следовательно) (лат.)
Так как операции <<, >> "настроены" на заранее определенные типы данных, то иногда возникают несоответствия, которых могло бы не быть при использовании библиотечных функций ввода scanf() и вывода printf() из стандартной библиотеки языка С. Например, рассмотрим попытку вывести на экран (в поток cout) значение символа, получаемого от клавиатуры с помощью библиотечной функции getch(), описанной в файле conio.h:
cout << "От клавиатуры поступил символ: " << getch();
Если на клавиатуре нажать клавишу B, то на экране получим:
От клавиатуры поступил символ: 66
Дело в том, что функция getch() ввода символа от клавиатуры без отображения на экране возвращает значение типа int, т.е. имеет прототип:
int getch(void);
Поэтому печатается не изображение символа B, а его целочисленный код. Чтобы получить изображение символа, необходимо явное приведение типа:
cout << (char)getch();
На следующем шаге мы расмотрим форматирование данных при обменах с потоками.