Вывод int из потока в c. Потоковые классы

02.08.2009 20:46

Предисловие
Едва ли какая-либо программа может обойтись без взаимодействия со своим окружением посредством ввода и вывода информации - иначе зачем она вообще нужна, если строго замкнута на себе, и не общается ни с пользователем, ни с окружающими ее данными и программами? Здесь мы рассмотрим, как программа, написанная на C++ может принимать информацию у пользователя и выдавать некие результаты своей работы ему же. Проще говоря, как она считывает данные с клавиатуры, и выводит их на экран.
Необходимо заметить, что подходы к выполнению этих операций менялись с течением времени. В языке C (Си) и ранних версиях C++, которые были еще во многим похожи на C, применялись функции printf, scanf и подобные им. С развитием языка C++ появились потоки, в том числе стандартные потоки ввода/вывода cin и cout. Что лучше использовать? Давайте посмотрим.
Потоки являются частью стандартной библиотеки C++ и являются мощным высокоуровневым средством. При использовании потоков ввода/вывода вы можете перегружать операторы >> и Таким образом, если вы пишите программы на современном C++ и при решении ваших задач вам нет необходимости использовать низкоуровневые средства, скорее всего, вам больше подойдут потоки.
Функции printf и scanf являются более низкоуровневыми, менее выразительными и требующими большего внимания при использовании средствами. Скорее всего, вам стоит остановить свой выбор на них, если вы используете C или же вы программируете на низком уровне и активно обращаетесь к аппаратным средствам.
Если же вы пишете в на языке C++, пользуясь устаревшей версией компилятора, то совет может быть только один - как можно скорее переходите на современные среды разработки с современными компиляторами и реализующими современную версию языка C++!

Стандартные потоки ввода/вывода
Стандартным потока ввода является поток cin (in put, ввод), а стандартным потоком вывода - cout (out put, вывод). Они определены в пространстве имен стандартной библиотеки std , поэтому для получения доступа к ним необходимо использовать префикс std:: - std::cin вместо cin и std::cout вместо cout . В качестве альтернативы можно использовать директиву using namespace std , чтобы объявить все имена из std глобальными. Однако, я бы посоветовал использовать первый вариант, если только вы не переписываете старый код на C++ или код на C для использования в новых версиях C++.
Также вам необходимо подключить заголовочный файл iostream , в котором и содержатся стандартные потоки ввода/вывода: #include .
Оператором ввода является >> ("прочесть из"), а оператором вывода - Рассмотрим теперь последовательно операции вывода и считывания информации с помощью потоков cout и cin .

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

выведет abc .
Понятное дело, что писать каждый раз std::cout

void f(int i)
{
std::cout }

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

//Пример 5.1
#include

Int main()
{
int a, b, c;
char sign;

Std::cout std::cin >> a;
std::cout std::cin >> 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()
{
int a, b, c;
char sign;

Std::cout std::cin >> a >> sign >> 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 }

Если бы пользователь ввел (обязательно с пробелами!) строку 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 ++.

C ++ I / O происходит в потоках, которые представляют собой последовательности байтов. Если байты поступают с устройства, такого как клавиатура, дисковод или сетевое соединение и т. д. В основную память, это называется операцией ввода, и если байты поступают из основной памяти в устройство, такое как экран дисплея, принтер, дисковод, или сетевое соединение и т. д., это называется операцией вывода .

Файлы заголовков библиотеки ввода-вывода

Для программ на C ++ важны следующие файлы заголовков -

Этот файл определяет объекты cin, cout, cerr и clog , которые соответствуют стандартным входным потокам, стандартным потокам вывода, потоку стандартной буферизации без буферизации и потоку стандартной буферизации, соответственно.

Этот файл объявляет услуги, полезные для выполнения отформатированного ввода / вывода с помощью так называемым параметризованным потоком манипуляторами, такие как setw и setprecision .

Этот файл объявляет службы для обработки файлов, управляемых пользователем. Мы обсудим это подробно в главе «Файл и поток».

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

Предопределенный объект cout является экземпляром класса ostream . Сообщается, что объект cout «подключен к» стандартным устройствам вывода, который обычно является экраном дисплея. СоиЬ используется в сочетании с оператором вставки потока, который записывается в виде << которые являются два меньше, чем знаки, как показано в следующем примере.

#include using namespace std; int main() { char str = "Hello C++"; cout << "Value of str is: " << str << endl; }

Value of str is: Hello C++

Компилятор C ++ также определяет тип данных переменной, подлежащей выводу, и выбирает соответствующий оператор вставки потока для отображения значения. Оператор << перегружен для вывода элементов данных встроенных типов integer, float, double, string и значений указателя.

Оператор ввода << может использоваться более одного раза в одном выражении, как показано выше, и endl используется для добавления новой строки в конце строки.

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

Предопределенный объект cin является экземпляром класса istream . Говорят, что объект cin прикреплен к стандартным устройствам ввода, которые обычно являются клавиатурой. CIN используется в сочетании с оператором экстракции потока, который записывается как >> , которые являются два больше, чем знаки, как показано в следующем примере.

#include using namespace std; int main() { char name; cout << "Please enter your name: "; cin >> name; cout << "Your name is: " << name << endl; }

Когда вышеуказанный код скомпилирован и выполнен, он предложит вам ввести имя. Вы вводите значение, а затем нажмите Enter, чтобы увидеть следующий результат -

Please enter your name: cplusplus Your name is: cplusplus

Компилятор C ++ также определяет тип данных введенного значения и выбирает соответствующий оператор извлечения потока для извлечения значения и сохранения его в данных переменных.

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

Cin >> name >> age;

Это будет эквивалентно следующим двум утверждениям:

Cin >> name; cin >> age;

Стандартный поток ошибок (cerr)

Предопределенный объект cerr является экземпляром класса ostream . Говорят, что объект cerr прикреплен к стандартным устройству ошибок, которое также является экраном дисплея, но объект cerr не забуферирован, и каждая вставка потока в cerr вызывает немедленный вывод его вывода.

Сегг

#include using namespace std; int main() { char str = "Unable to read...."; cerr << "Error message: " << str << endl; }

Когда приведенный выше код компилируется и выполняется, он производит следующий результат:

Стандартный поток журнала (засорение)

Предопределенный объект clog является экземпляром класса ostream . Объект clog, как говорят, прикреплен к стандартному устройству ошибок, который также является экраном дисплея, но затвор объекта буферизуется. Это означает, что каждая вставка для блокировки может привести к тому, что ее вывод будет удерживаться в буфере до заполнения буфера или до тех пор, пока буфер не будет сброшен.

Засорить также используется в сочетании с оператором вставки потока, как показано в следующем примере.

#include using namespace std; int main() { char str = "Unable to read...."; clog << "Error message: " << str << endl; }

Когда приведенный выше код компилируется и выполняется, он производит следующий результат:

Error message: Unable to read....

Вы не сможете увидеть какие-либо различия в cout, cerr и clog с этими небольшими примерами, но при написании и выполнении больших программ разница становится очевидной. Поэтому хорошей практикой является отображение сообщений об ошибках с использованием потока cerr и при отображении других сообщений журнала, которые следует использовать.

файла, устройства или записывания на него этих данных;
  • Средствами программы, созданной прикладным программистом, происходит чтение или запись информации (символов) в этот буфер;
  • Средствами операционной системы осуществляется "синхронизация" этого буфера ("потока данных") с файлом или устройством;
  • При создании или открытии файла для него выделяется буфер в оперативной памяти компьютера, а после закрытия файла этот буфер очищается.
    • Стандартный поток ввода (обозначение: stdin , cin и др.) - используется для ввода символьных данных в программу. По-умолчанию этот поток закреплён за клавиатурой компьютера;
    • Стандартный поток вывода (обозначается как: stdout , cout и др.) - используется для вывода символьной информации, полученной в результате работы программы в "штатном режиме". По-умолчанию этот поток закреплён за экраном дисплея;
    • Стандартный поток ошибок (обозначение: stderr , cerr и др.) - используется для вывода символьных диагностических сообщений , ошибок и предупреждений, возникших в результате работы программы. По-умолчанию этот поток закреплён за экраном дисплея;

      Примечание: стандартный поток и поток ошибок разделены в связи с тем, что при перенаправлении вывода часто совсем не нужно записывать в результаты работы программы диагностические сообщения. Эти сообщения будут лишними, например, при формировании таблицы базы данных в виде текстового файла;

    • Стандартный поток печати (обозначение: stdprn и др.) - используется для вывода результатов работы программы на печать. По-умолчанию этот поток закреплён за текущим принтером в системе, подключённым к порту LPT1 . В настоящее время этот поток почти не используется, поскольку чаще проще и безопаснее перенаправить стандартный поток вывода на принтер, чем разделять потоки отдельно для экрана и отдельно для принтера.

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

    6.2. Ввод со стандартного потока ввода

    6.2.1. Ввод средствами языка Си

    Ввод со стандартного потока в Си осуществляется при помощи следующих функций:

    • для ввода одиночного символа - функцию getchar ;
    • для ввода строки символов без ограничения на длину - функцию gets ;
    • для "форматированного ввода" символов и их преобразования в "двоичные значения" переменных - функцию scanf ;

    С синтаксисом и правилами использования этих функций можно ознакомиться в приложении №I к данной лекции. Автор хочет отметить, что использование функций ввода со стандартного потока является небезопасным, а, следовательно, и нежелательным способом ввода данных из стандартного потока. По возможности, заменяйте эти функции функциями потокового чтения данных с "явным" указанием потоков и "длины" прочтённой строки.

    Пример 6.1

    /* Файл ex06001.c */ /* Функция иллюстрирует потоковый ввод-вывод (как это делать нельзя)*/ #include #define STR_LENGTH 3 // Длина строки 3 символа void main() { char str; // Текстовый буфер char *s; // Временная переменна s = gets(str); puts(s); }

    Например, функция, приведённая в примере 6.1, при вводе строки: "aaaaaaaa\n" , - вызовет аварийное завершение с ошибкой "переполнение буфера". Поскольку длина текстового буфера - 2 символа (последний символ - "нулевой", знак окончания строки "\0" ), то лишние символы, введённые с клавиатуры, попадают в "запредельную" системную область данных. В этом случае происходит операция (прерывание) "отказ системы", и адрес этой системной области оказывается доступной "взломщику системы". Функции, приведённые в примерах 6.2 и 6.3, лишены этих недостатков;

    Пример 6.2

    /* Файл ex06002.c */ /* Функция иллюстрирует потоковый ввод-вывод (как это делать надо)*/ /* Пример тестировался в системе программировани Borland C/C++ 3.10 */ #include #include #define BUF_LENGTH 7 // Длина строки 3 символа #define STR_LENGTH 2 // Длина буфера без дескриптора // Всё те же 2 символа void main() { char str; // Текстовый буфер char *s, c = "\0"; // Временная переменная. int icsize; // Временная переменная. icsize = BUF_LENGTH - 2; str = (char) icsize; s = cgets(str); puts(s); puts("\nPress any key to continue..."); while(!(c = getch())); // Цикл пока не нажата клавиша }

    Пример 6.3

    /* Файл ex06003.c */ /* Функция иллюстрирует потоковый ввод-вывод (как это делать надо)*/ /* Пример тестировался в системе программировани Borland C/C++ 3.10 */ #include #include #include #define STR_LENGTH 3 // Длина строки 3 символа void main() { char str; // Текстовый буфер char *s, c = "\0"; // Временная переменная. int icsize; // Временная переменная. memset(str, "\0", STR_LENGTH); // Обнуляем буфер icsize = STR_LENGTH; s = fgets(str, icsize, stdin); // Читаем не более 2 символов с входного потока // (вместе с нулевым символом) puts(s); puts("\nPress any key to continue..."); while(!(c = getch())); // Цикл пока не нажата клавиша }

    6.2.2. Ввод средствами языка C++

    Консольный потоковый ввод в языке C++ осуществляется при помощи операторов ">>" , а также при помощи функций get , peek и ignore . Стандартный поток ввода в языке C++ обозначается как cin (см. таблицу 6.1).

    Таблица 6.1. Обозначение потоков в разных языках программирования.
    Обозначения потоков
    Поток Ввод (стандартный) Вывод (стандартный) Вывод ошибок Передача на линию Печать
    Значение дескриптора 0 1 2 3 4
    Обозначение в Си stdin stdout stderr stdaux stdprn
    Обозначение в C++ cin cout cerr нет нет
    Обозначение в Java System.in System.out ? нет нет
    Обозначение в Perl STDIN STDOUT STDERR нет нет
    Обозначение в Python sys.stdin sys.stdout sys.stderr нет нет
    Обозначение в VBScript StdIn StdOut StdErr нет нет

    Описание функций для потокового ввода средствами языка C++ смотри в приложении №III к данной лекции.

    C помощью оператора ">>" можно осуществить как неформатированный, так и форматированный ввод-вывод. Однако автор советует для ввода использовать неформатированный ввод из потока с помощью функции cin.get , а затем "выделить и ввести" нужные двоичные значения при помощи функции sscanf (см. пример 6.4.).

    Пример 06.004 .

    /* File ex06004.cpp */ /* Пример ввода-вывода средствами C++*/ /* Пример тестировался в системе программировани Borland C/C++ 3.10 */ #include #include #include #include #ifndef STR_LENGTH #define STR_LENGTH 35 // Длина строки 34 символа + завершающий символ "\0" #endif #ifndef INT_WIDTH #define INT_WIDTH 5 // Ширина под поле целого числа - 4 символов #endif void main(void) { const char format_in = "%5d"; // Формат ввода const char format_out = "\nВведённое значение: %5d"; // Формат вывода char str, c="\0"; int iValue; (void) memset(str, "\0", STR_LENGTH); // Обнуляем строку; cout << "\nВведите число: "; cin.get(str, INT_WIDTH); if(!sscanf(str, format_in, &iValue)) // Если ошибка ввода { cerr << "\nНичего не введено или неправильный формат данных!\nПрограмма завершена с ошибкой"; return; } (void) memset(str, "\0", STR_LENGTH); // Обнуляем строку; if(sprintf(str, format_out, iValue) == EOF) // Если ошибка вывода { cerr << "\nНичего не выведено или неправильный формат данных!\nПрограмма завершена с ошибкой"; return; } cout << str; // Выводим сформированную строку cerr << "\nПрограмма завершена нормально"; cerr << "\nPress any key to continue..."; while(!(c = getch())); // Цикл пока не нажата клавиша } Листинг.

    В этом примере для ввода символьной строки использовалась функция cin.get , для вывода - оператор "<<" , а для форматных преобразований - функции sprintf и sscanf .

    Описание функций sscanf и sprintf см. в приложении №I к данной лекции.

    Замечание: из личного опыта автора родился следующий совет: при работе с консолью, когда не требуется программирование фильтров и перенаправления потоков , лучше использовать консольные функции ввода-вывода. Для программирования фильтров, наоборот, требуется ввод-вывод исключительно файловыми потоковыми функциями. В обоих случаях для форматного преобразования необходимо использовать только функции sprintf и sscanf , предварительно проверив (т.н. "валидация") формат читаемых и записываемых символов. Во всех случаях нужно жёстко контролировать итоговую длину строки ввода-вывода.

    До сих пор в программных примерах мы пользовались только функциями стандартной библиотеки С. Однако в 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 перегружает соответственно операцию левого сдвига (<<); она применяется для вывода и называется операцией передачи в поток.

    Примечание

    Нужно сказать, что стандартной русской терминологии как таковой в C++ не существует. Каждый изобретает свою собственную; иногда удачно, иногда — нет.

    Вот простейшие операторы ввода и. вывода на стандартных потоках:

    #include

    int main()

    char name [ 8.0] ;

    cout<< "Enter your name: ";

    cin>> name;

    cout <<"Hello " << name << "!";

    return 0;

    Как видите, при действиях над потоками возможно последовательное сцепление операций, подобно последовательному присваиванию. Как вы уже знаете, такая форма записи обеспечивается благодаря тому, что функции-операции извлечения и передачи возвращают ссылку на свой объект.

    Перегруженные операции для встроенных типов

    Классы istream и ostream перегружают операции соответственно извлечения и передачи в поток для всех встроенных типов. Это позволяет единообразно применять эти операции для чтения и вывода символов, целых, вещественных чисел (т. е. с плавающей точкой) и строк. Вот небольшая иллюстрация, где попутно показан еще простейший прием проверки на ошибку при вводе:

    #include

    void check(void) {

    if (!cin.good())

    // Либо просто if (!cin) {

    cout << "Error detected!";

    exit (1);

    int main(void)

    double d;

    long 1;

    cout << "Enter a floating point value: ";

    cin >> d;

    check () ;

    cout << "You entered: " << d << "n";

    cout << "Enter an integer value: ";

    cin >> 1;

    check () ;

    cout << "You entered: " << 1 << "n";

    return 0;

    Примечание

    Операции извлечения и передачи в поток (соответственно для классов istream и ostream) можно перегрузить таким образом, чтобы можно было применять их для ввода или вывода объектов класса, определенного пользователем. Приведенный ниже пример демонстрирует эту методику. Вообще-то в подобных случаях совершенно необходимо предусмотреть детектирование и обработку ошибок ввода, но здесь мы этого не сделали.

    #include

    class Point { int x, у;

    public:

    Point(int xx = 0, int yy = 0) {

    X = xx; у = yy;

    Friend istream &operator>>(istream&, Points);

    friend ostream &operator“(ostream&, Points);

    istream &operator”(istream &is, Point &p)

    // При вводе точка представляется просто парой чисел,

    // разделенных пробелом.

    // is >> р.х > р.у;

    return is;

    ostream &operator<<(ostream &os.Point &p) {

    // Вывод в виде (х, у).

    os<< " ("<< р. х<< ", "<< р. у<<") " ;

    return os;

    int main() {

    Point р;

    cout<< "Enter point coordinates: ";

    cin>> р;

    cout<< "The point values are " << р;

    return 0;

    Форматирование

    Библиотека ввода-вывода предусматривает три способа форматирования: посредством вызова форматирующих функций-элементов, с помощью манипуляторов или путем установки или сброса флагов потока.

    Форматирующие функции-элементы

    Эти функции являются элементами класса ios и перегружены таким образом, чтобы можно было либо читать, либо устанавливать значение соответствующего атрибута потока. Если аргумент в вызове отсутствует, функция возвращает текущее значение атрибута. Если аргумент указан, функция устанавливает новое и возвращает предыдущее значение атрибута.

    long width(long)

    Эта функция предназначена для чтения или установки атрибута ширины поля.

    • Применяемая ко входному потоку, функция позволяет задать максимальное число вводимых символов.
    • На выходном потоке функция задает минимальную ширину поля вывода.
    • Если действительное поле вывода меньше установленной ширины, выводятся дополнительные заполняющие символы. Символ заполнения определяется специальным атрибутом потока.
    • Если действительное поле вывода больше установленной ширины, ее значение игнорируется.
    • Значением ширины по умолчанию является 0 (ширина поля определяется выводимыми данными).
    • Ширина поля сбрасывается в 0 после каждой передачи в поток.

    char fill(char)

    По умолчанию символ заполнения — пробел.

    long precision(long)

    • Точность по умолчанию равна шести цифрам.
    • Если установлен флаг scientific или fixed, точность задает число цифр после десятичной точки.
    • Если ни один из этих флагов не установлен, точность задает общее число значащих цифр.

    Пример

    Ниже приводится программа, демонстрирующая форматирование потока с помощью функций-элементов класса ios.

    Листинг 9.1. Демонстрация форматирующих функций потока

    ///////////////////////////////////////////////

    // Format.срр: Форматирующие функции-элементы ios.

    #include

    #pragma hdrstop

    #include

    #pragma argsused

    // Ширина поля при вводе и выводе.

    cnar sir ;

    cout<< "Enter something: ";

    cin.width(16); // Ввод не более 15 символов. cin>> str;

    cout.width(32); // Вывести в поле шириной 32. cout << str<< "nn";

    // Заполняющий символ и ширина поля. Ширина сбрасывается

    // после каждой операции, поэтому она устанавливается

    // для каждого числа.

    int h = 7, m = 9, s = 0; // Выводятся в виде hh:mm:ss.

    cout.fill("0"); cout << "Time is ";

    cout.width (2); cout << h << " : " ; cout.width (2) ;

    cout<< m<< " : " ;

    cout.width (2) ;

    cout<< s<< ".nn";

    cout.fill (" "); // Восстановить пробел.

    // Точность.

    double d = 3.14159265358979;

    float f = 27182.81828;

    cout.precision (5);

    cout << f << "n"; . // Выводит "27183" .

    cout << d << "n"; " // Выводит "3.1416".

    cout .precision (4) ;

    cout << f << "n"; // Выводит "2.718е+04".

    cout.setf(ios::fixed); // Установить флаг fixed.

    cout<< f<<"n"; // Выводит "27182.8184".

    return 0;

    Манипуляторы

    Манипуляторы потоков являются по существу функциями, которые можно вызывать непосредственно в цепочке операций извлечения или передачи в поток. Различают простые и параметризованные манипуляторы. У простых манипуляторов аргументы отсутствуют. Параметризованные манипуляторы имеют аргумент.

    Ниже приводится сводка имеющихся манипуляторов, как простых, так и параметризованных. Они Перечислены в алфавитном порядке.

    Таблица 9.2. Простые и параметризованные манипуляторы

    Манипулятор

    Описание

    Задает десятичную базу преобразования.

    end1

    Передает в поток символ новой строки и сбрасывает поток.

    ends

    Передает в поток символ завершающего строку нуля.

    flush

    Сбрасывает выходной поток.

    Задает шестнадцатеричную базу преобразования.

    lock(ios Sir)

    Блокирует дескриптор файла потока ir.

    Задает восьмеричную базу преобразования.

    resetiosflags(int f)

    Сбрасывает флаги, биты которых установлены в f.

    setbase(int b)

    Устанавливает базу преобразования (0, 8, 10 или 16).

    setiosflags(int f)

    Устанавливает флаги, биты которых установлены в f.

    setfill(int c)

    Задает символ заполнения (аналогичен функции

    fiilO).

    setprecision(long p)

    Задает точность (аналогичен функции precision ()).

    setw(iong w)

    Задает ширину поля (аналогичен функции width ()).

    lunlock(ios &ir)

    Разблокирует дескриптор файла для потока ir.

    Исключает начальные пробельные символы.

    Вот пример использования некоторых манипуляторов (мы создали один свой собственный):

    Листинг 9.2. Форматирование с помощью манипуляторов

    /////////////////////////////////////////////////

    // Manip.cpp: Демонстрация некоторых манипуляторов.

    #include

    #pragma hdrstop

    #include

    //////////////////////////////////////////////////

    // Манипулятор, определенный пользователем - звонок.

    ostream shell(ostream &os)

    return os<< "a";

    #pragma argsused

    int main(int argc, char* argv)

    cout “ bell; // Тестирование манипулятора bell.

    // Манипуляторы базы преобразования.

    long 1 = 123456;

    cout<< "Hex: "<< hex<< 1<< end1

    <<"Oct: "<< oct<< 1<< end1

    << "Dec: " << dec << 1 << end1;

    // Параметризованные манипуляторы.

    int h=12, m=5, s=0; // To же, что в примере

    // Format.cpp. cout << "The time is " << setfill("0")

    << setw(2) << h << ":"

    << setw(2) << m << ":"

    << setw(2) << s << setfillC ") << end1;

    return 0;

    Как видите, очень несложно определить свой собственный простой манипулятор. Это всего лишь функция, возвращающая ссылку на переданный ей в параметре поток.

    Примечание

    Создать параметризованный манипулятор не так просто. Существуют различные способы сделать это, но наиболее очевидный из них — реализация манипулятора через класс эффектора. Идея состоит вот в чем. Нужно определить для манипулятора собственный класс с конструктором, принимающим нужные параметры, .и перегрузить для этого класса операцию передачи (извлечения) соответствующего потока. После этого конструктор можно вызывать в качестве параметризованного манипулятора. Создается временный объект, который выводится в поток перегруженной операцией и удаляется. Ниже показан манипулятор, который выводит в поток свой аргумент типа unsigned в двоичной форме.

    #include

    // Класс эффектора.

    class Bin {

    int val;

    public:

    Bin(unsigned arg) { val = arg; }

    friend ostream &operator“(ostreams. Bin);

    // Вывод числа в двоичной форме.

    ostream &ooerator<<(ostream &os. Bin b) {

    int cb = 1; // Контрольный бит для отсчета циклов.

    do {

    if (b.val <0) // Если val < 0, то старший бит = 1. os << 1;

    else

    os<< 0;

    } while (b.vai<<= 1, cb<<= 1) ;

    return os;

    int main ()

    unsigned n = Ox00ff0f34;

    cout<< "Some binary: "<< Bin(n)<< end1;

    return 0;

    Рис. 9.1 Манипулятор, выводящий свой аргумент в двоичной форме

    Форматирующие флаги

    Флаги управления форматированием являются битовыми полями, хранящимися в переменной типа fmtflags (псевдоним int). Для их чтения и/или модификации могут применяться следующие функции-элементы класса ics:

    • int flags (), int flags (int). Без параметра возвращает текущее состояние флагов. При указанном параметре устанавливает новые значения флагов и возвращает их прежнее состояние.
    • int setf(int), long setf(int, int). Первая форма устанавливает флаги, биты которых установлены в параметре. Вторая форма модифицирует флаги, биты которых установлены во втором параметре. Значения этих флагов задаются первым параметром. Возвращает прежнее состояние всех флагов.
    • void unsetf(int). Сбрасывает флаги, биты которых установлены в параметре.

    Помимо функций, для управления флагами можно пользоваться манипуляторами setiosflags (аналог setf() с одним параметром) и reset-iosflags (аналог unsetf ()).

    В таблице 9.3 описаны форматирующие флаги потоков.

    Таблица 9.3. Форматирующие флаги класса ios

    Флаг

    Описание

    internal

    Если установлен, при выводе чисел знак выводится на левом краю поля вывода, а само число выравнивается по правому краю поля. Промежуток заполняется текущим символом заполнения.

    Устанавливает десятичное представление чисел. Принимается по умолчанию.

    Устанавливает восьмеричное представление чисел.

    Устанавливает шестнадцатеричное представление чисел.

    showbase

    Если установлен, то при восьмеричном и шестнадцатеричном представлении чисел выводит индикатор основания (0 для восьмеричных и Ох для шестнадцатеричных чисел).

    showpoint

    Если установлен, для вещественных чисел всегда выводится десятичная точка.

    uppercase

    Если установлен, шестнадцатеричные цифры от А до F, а также символ экспоненты Е выводятся в верхнем регистре.

    boolalpfa

    Если установлен, булевы значения выводятся как слова “true/false”. В противном случае они представляются соответственно единицей и нулем.

    showpos

    Выводит + для положительных чисел.

    scientific

    Если установлен, вещественные числа выводятся в научной (экспоненциальной) нотации.

    fixed

    Если установлен, вещественные числа выводятся в десятичном формате (с фиксированной точкой).

    unitbuf

    Если установлен, поток сбрасывается после каждой операции передачи.

    Несколько замечаний относительно перечисленных в таблице флагов.

    • Флаги left, right и internal являются взаимоисключающими. В данный момент времени может быть установлен только один из них.
    • Взаимоисключающими являются также флаги dec, oct и hex.
    • При модификации базы представления в качестве второго параметра setf() можно использовать константу ios: :basefield.
    • При модификации выравнивания в поле можно аналогичным образом использовать константу ios: :adjustfield.
    • При модификации формы представления (нотации) чисел с плавающей точкой можно использовать константу ios: : floatfield. Ниже мы приводим листинг программы, демонстрирующей применение различных флагов форматирования.

    Примечание

    Имена перечисленных выше флагов и других констант принадлежат к области действия класса ios. Вне этого класса нужно либо воспользоваться разрешением области действия (ios: : scientific), либо обращаться к ним, как к элементам существующего объекта (cout. scientific). Мы поедпочитаем первый способ.

    Листинг 9.3. форматирующие флаги потоков

    ////////////////////////////////////////////////////

    // Flags.срр: Форматирующие флаги потоков.

    #include

    #include #pragma hdrstop

    #include

    #pragma argsused

    int main(int argc, char* argv)

    // Демонстрация флага skipws. Если его сбросить, то при

    // наличии начальных пробелов при вводе возникает ошибка.

    long 1;

    cout<< "Enter an integer: ";

    cin.unsetf(ios::skipws);

    cin >> 1;

    if (cin) // При ошибке потока

    cin == NULL. cout<< "You entered "<< 1<< endl;

    else {

    cout << "Incorrect input."<< endl;

    cin .clear (); // Обнуление битов ошибки.

    } cout<

    // Демонстрация флагов основания и знака.

    // Задается основание 16, вывод индикатора и знака +.

    1 = 8191;

    cout.setf(ios::hex, ios::basefield);

    cout.setf(ios::showbase | ios::showpos);

    << " dec: " << 1 << endl;

    cout << endl;

    // Демонстрация флагов формата вещественных чисел.

    double dl = 1.0е9, d2 = 34567.0;

    cout <<"Default: " << dl << " "<

    // Вывод десятичной точки. cout.setf(ios::showpoint);

    cout << "Decimal: " << dl<< " " << d2 << endl;

    // Нотация с фиксированной точкой.

    // Заодно сбросим вывод знака +.

    cout.setf(ios :: fixed, ios::floatfield | ios :: showpos);

    cout << "Fixed: " << dl << " " << d2 << endl;

    cout<< endl;

    // Вывод булевых значений как "true/false".

    bool b = true;

    cout.setf(ios::boolalpha) ;

    cout << "Boolean values: " << b << "" << !b endl;

    return 0;

    Рис. 9.2 Демонстрация флагов форматиоования потока

    Состояние потока

    Состояние объекта класса ios (и производных от него) содержится в его закрытом элементе _state в виде набора битов. Следующая таблица перечисляет имеющиеся биты состояния потока.

    Таблица 9.4. Биты состояния потока

    Бит

    Описание

    goodbit

    С потоком все в порядке (на самом деле это не какой-то бит, а 0 — отсутствие битов ошибки).

    eofbit

    Показывает, что достигнут конец файла.

    failbit

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

    badbit

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

    Для опроса или изменения состояния потока в классе ios имеется ряд функций и операций.

    • int rdstate() ; Возвращает текущее состояние.
    • bool eof() ; Возвращает true, если установлен eofbit.
    • bool good () ; Возвращает true, если не установлен ни один из битов ошибки.
    • bool fail () ; Возвращает true, если установлен failbit или bad-bit.
    • bool bad() ; Возвращает true, если установлен badbit.
    • void clear (int =0); Сбрасывает биты ошибки (по умолчанию) или устанавливает состояние потока в соответствии с аргументом.
    • void setstate(int) ; Устанавливает состояние битов ошибки с соответствии с аргументом.
    • operator void*() ; Возвращает нулевой указатель, если установлен какой-либо из битов ошибки.
    • bool operator! () ; Возвращает true, если установлен какой-либо из битов ошибки.

    Примечание

    Функция operator void*() неявно вызывается, если поток сравнивается с нулем (как cin в примере из листинга),

    Файловые потоки

    Файловые потоки библиотеки ввода-вывода реализуют объектно-ориентированную методику работы с дисковыми файлами. Имеется три класса таких потоков:

    • ifstream специализирован для ввода из дисковых файлов.
    • of stream специализирован для записи дисковых файлов.
    • fstream управляет как вводом, так и записью на диск.

    Эти классы выводятся соответственно из istream, ostream и iostream. Таким образом, они наследуют все их функциональные возможности (перегруженные операции << и>>” для встроенных типов, флаги форматирования и состояния, манипуляторы и т. д.).

    Чтобы работать с файловым потоком, нужен, во-первых, объект потока, а во-вторых, открытый файл, связанный с этим объектом.

    Конструирование объекта потока

    Каждый из трех классов файловых потоков имеет четыре конструктора.

    • Конструктор, создающий объект без открытия файла:

    ifstream () ;

    of stream();

    fstream () ;

    • Конструктор, создающий объект, открывающий указанный файл и закрепляющий этот файл за потоком. Аргументами являются имя файла, режим открытия и режим защиты (в Windows не используется):

    if stream(const char *name,

    int mode = ios::in, long prot = 0666);

    ofstream(const char *name,

    int mode = ios::out, long prot = 0666);

    fstream (const char *name, int mode, long prot = 0666);

    • Конструктор, создающий объект и связывающий с ним уже открытый файл. В качестве аргумента передается дескриптор файла:

    ifstreamfint file);

    ofstream(int file);

    fstream (int file) ;

    • Конструктор, создающий объект и связывающий с ним уже открытый файл; объект ассоциируется указанным буфером:

    ifstream(int file, char *buf, int len)

    of stream(int file, char *buf, int len)

    fstream (int file, char *buf, int len)

    Режимы открытия файла

    Параметр mode, который имеет вторая форма конструктора, задает режим открытия файла. Для значений параметра класс ios определяет символические константы, перечисленные в таблице 9.5.

    Таблица 9.5. Константы класса ios для режимов открытия файла

    Константа

    Описание

    арр

    Открытие для записи в конец файла.

    При открытии позиционирует указатель на конец файла.

    binary

    Файл открывается в двоичном (не текстовом) режиме.

    Файл открывается для ввода.

    Файл открывается для вывода.

    trunc

    Если файл существует, его содержимое теряется.

    Константы можно комбинировать с помощью поразрядного OR. Для конструкторов классов if stream и ofstream параметр mode имеет значения по умолчанию — соответственно ios: : in и ios: : out.

    Закрытие файла

    В классах файловых потоков имеется функция close (), которая сбрасывает содержимое потока и закрывает ассоциированный с ним файл.

    Кроме того, деструктор потока автоматически закрывает файл при уничтожении объекта потока.

    При ошибке закрытия файла устанавливается флаг failbit.

    Примеры файловых потоков

    Следующий пример (листинг 9.4) демонстрирует различные режимы и способы открытия потока.

    Листинг 9.4. Примеры открытия файловых потоков

    /////////////////////////////////////////////////////////

    // Filemode.срр: Режимы открытия файлов.

    #include

    #include

    #pragma hdrstop

    #include

    char *data = {"It"s the first line of test data.",

    "Second ,line.",

    "Third line.",

    "That"s enough!"};

    // Функция для распечатки содержимого файла. //

    int Print(char *fn) {

    char buf ;

    ifstream ifs(fn) ;

    if (!ifs) {

    cout <

    return -1;

    } while (ifs) {

    ifs.getline(buf, sizeof(buf)) ;

    if (ifs)

    cout << buf<< end1;

    } return 0;

    #pragma argsused

    int main(int argc, char* argv)

    char name= "Newfile.txt";

    fstream fs(name, ios::in);

    if (fs) { // Файл уже существует. cout “ name “ " - File already exists." << endl;

    } else { // Создать новый файл.

    cout<< name<< " - Creating new file."<< endl;

    fs.open(name, ios::out);

    for (int i=0; i<3; i++) fs << data[i] << endl;

    fs.close () ;

    cout << end1;

    // Файл либо уже существовал, либо мы его только что

    // создали. Распечатаем его.

    // Print(name);

    cout << endl;

    // Допишем строку в конец файла.

    // fs.open(name, ios::app);

    if (rs) {

    fs M<< data<< endl;

    fs.close ();

    } Print(name);

    return 0;

    Рис. 9.3 Результат работы программы Filemode

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

    Бесформатный ввод-вывод

    До сих пор речь у нас шла почти исключительно о вводе-выводе с использованием операций извлечения/передачи данных. Эти операции перегружены для всех встроенных типов и выполняют соответствующие преобразования из внутреннего представления данных в текстовое и из текстового во внутреннее (машинное).

    Однако в библиотеке C++ имеется немало функций бесформатного ввода-вывода, которые часто применяют для чтения и записи двоичных (не-текстовых) файлов.

    Двоичный режим ввода-вывода

    Двоичный режим открытия файла (с установленным битом binary) означает, что никакой трансляции данных при передаче из файла в поток и обратно производиться не будет. Речь здесь идет не о форматных преобразованиях представления данных. При текстовом режиме (он принимается по умолчанию) при передаче данных между файлом и потоком производится замена пар символов CR/LF на единственный символ LF (" n ") и наоборот. Это происходит до преобразований представления, которые выполняются операциями извлечения/передачи. Двоичный ввод-вывод означает всего-навсего, что такой замены происходить не будет; тем не менее двоичный режим необходим при работе с сырыми данными, т. е. данными в машинной форме без преобразования их в текстовый формат.

    Чтобы открыть файл в двоичном режиме, нужно, как уже упоминалось, установить в параметре mode конструктора потока или функции open() бит ios::binary.

    Чтение и запись сырых данных

    Чтение сырых данных производится функцией read () класса istream:

    istream &read(char *buf, long len);

    Здесь buf — адрес буфера, в который будут читаться данные, а len — число символов, которые нужно прочитать.

    Запись сырых данных производится функцией write () класса ostream. Она выглядит точно так же, как функция read () :

    ostream &write(char *buf, long len);

    Здесь buf — адрес буфера, в котором содержатся данные, а len — число символов, которые должны быть записаны в поток.

    ostream os (...);

    os.write(...).write (...).write(...) ;

    Вот небольшой пример записи и чтения сырых данных:

    #include

    #include

    int main(void) {

    char name = "testfile.dat";

    int i = 1234567;

    double d = 2.718281828;

    // Открытие выходного потока в двоичном режиме

    //и запись тестовых данных.

    ofstream ofs(name, ios::out | ios::binary);

    if (ofs) {

    ofs.write((char*)&i, sizeof(i)); // Целое.

    ofs.write((char*)&d, sizeof(d)); // Вещественное.

    ofs.write(name, sizeof(name)); // Строка. ofs.close ();

    // Открытие входного потока в двоичном режиме.

    // if stream ifs(name, ios::in | ios::binary) ;

    i = 0; //

    d = 0; // Уничтожить данные.

    name = "\0"; //

    if (ifs) {

    ifs.read((char*)&i, sizeof (i));

    ifs.read((char*)&d, sizeof(d));

    ifs.read(name, sizeof(name));

    ofs.close () ;

    } //

    // Проверка - напечатать прочитанные данные. //



    Есть вопросы?

    Сообщить об опечатке

    Текст, который будет отправлен нашим редакторам: