Потоки
10.3 Ввод
Ввод во многом сходен с выводом. Есть класс istream, который реализует операцию ввода >> ("ввести из" - "input from") для небольшого набора стандартных типов. Для пользовательских типов можно определить функцию operator>>.
10.3.1 Ввод встроенных типов
Класс istream определяется следующим образом:
class istream : public virtual ios {
//...
public:
istream& operator>>(char*); // строка
istream& operator>>(char&); // символ
istream& operator>>(short&);
istream& operator>>(int&);
istream& operator>>(long&);
istream& operator>>(float&);
istream& operator>>(double&);
//...
};Функции ввода operator>> определяются так:
istream& istream::operator>>(T& tvar)
{
// пропускаем обобщенные пробелы
// каким-то образом читаем T в`tvar'
return *this;
}Теперь можно ввести в VECTOR последовательность целых, разделяемых пробелами, с помощью функции:
int readints(Vector<int>& v)
// возвращаем число прочитанных целых
{
for (int i = 0; i<v.size(); i++)
{
if (cin>>v[i]) continue;
return i;
}
// слишком много целых для размера Vector
// нужна соответствующая обработка ошибки
}Появление значения с типом, отличным от int, приводит к прекращению операции ввода, и цикл ввода завершается. Так, если мы вводим
1 2 3 4 5. 6 7 8.
то функция readints() прочитает пять целых чисел
1 2 3 4 5
Символ точка останется первым символом, подлежащим вводу. Под пробелом, как определено в стандарте С, понимается обобщенный пробел, т.е. пробел, табуляция, конец строки, перевод строки или возврат каретки. Проверка на обобщенный пробел возможна с помощью функции isspace() из файла <ctype.h>.
В качестве альтернативы можно использовать функции get():
class istream : public virtual ios {
//...
istream& get(char& c); // символ
istream& get(char* p, int n, char ='n'); // строка
};В них обобщенный пробел рассматривается как любой другой символ, и они предназначены для таких операций ввода, когда не делается никаких предположений о вводимых символах.
Функция istream::get(char&) вводит один символ в свой параметр. Поэтому программу посимвольного копирования можно написать так:
main()
{
char c;
while (cin.get(c)) cout << c;
}Такая запись выглядит несимметрично, и у операции >> для вывода символов есть двойник под именем put(), так что можно писать и так:
main()
{
char c;
while (cin.get(c)) cout.put(c);
}Функция с тремя параметрами istream::get() вводит в символьный вектор не более n символов, начиная с адреса p. При всяком обращении к get() все символы, помещенные в буфер (если они были), завершаются 0, поэтому если второй параметр равен n, то введено не более n-1 символов. Третий параметр определяет символ, завершающий ввод. Типичное использование функции get() с тремя параметрами сводится к чтению строки в буфер заданного размера для ее дальнейшего разбора, например так:
void f()
{
char buf[100];
cin >> buf; // подозрительно
cin.get(buf,100,'\n'); // надежно
//...
}Операция cin>>buf подозрительна, поскольку строка из более чем 99 символов переполнит буфер. Если обнаружен завершающий символ, то он остается в потоке первым символом подлежащим вводу. Это позволяет проверять буфер на переполнение:
void f()
{
char buf[100];
cin.get(buf,100,'\n'); // надежно
char c;
if (cin.get(c) && c!='\n') {
// входная строка больше, чем ожидалось
}
//...
}Естественно, существует версия get() для типа unsigned char.
В стандартном заголовочном файле <ctype.h> определены несколько функций, полезных для обработки при вводе:
int isalpha(char) // 'a'..'z' 'A'..'Z'
int isupper(char) // 'A'..'Z'
int islower(char) // 'a'..'z'
int isdigit(char) // '0'..'9'
int isxdigit(char) // '0'..'9' 'a'..'f' 'A'..'F'
int isspace(char) // ' ' '\t' возвращает конец строки
// и перевод формата
int iscntrl(char) // управляющий символ в диапазоне
// (ASCII 0..31 и 127)
int ispunct(char) // знак пунктуации, отличен от приведенных выше
int isalnum(char) // isalpha() | isdigit()
int isprint(char) // видимый: ascii ' '..'~'
int isgraph(char) // isalpha() | isdigit() | ispunct()
int isascii(char c) { return 0<=c && c<=127; }Все они, кроме isascii(), работают с помощью простого просмотра, используя символ как индекс в таблице атрибутов символов. Поэтому вместо выражения типа
(('a'<=c && c<='z') || ('A'<=c && c<='Z')) // буквакоторое не только утомительно писать, но оно может быть и ошибочным (на машине с кодировкой EBCDIC оно задает не только буквы), лучше использовать вызов стандартной функции isalpha(), который к тому же более эффективен.
В качестве примера приведем функцию eatwhite(), которая читает из потока обобщенные пробелы:
istream& eatwhite(istream& is)
{
char c;
while (is.get(c)) {
if (isspace(c)==0) {
is.putback(c);
break;
}
}
return is;
}В ней используется функция putback(), которая возвращает символ в поток, и он становится первым подлежащим чтению.
10.3.2 Состояния потока
С каждым потоком ( istream или ostream ) связано определенное состояние. Нестандартные ситуации и ошибки обрабатываются с помощью проверки и установки состояния подходящим образом.
Узнать состояние потока можно с помощью операций над классом ios:
class ios { //ios является базовым для ostream и istream
//...
public:
int eof() const; // дошли до конца файла
int fail() const; // следующая операция будет неудачна
int bad() const; // поток испорчен
int good() const; // следующая операция будет успешной
//...
};Последняя операция ввода считается успешной, если состояние задается good() или eof(). Если состояние задается good(), то последующая операция ввода может быть успешной, в противном случае она будет неудачной. Применение операции ввода к потоку в состоянии, задаваемом не good(), считается пустой операцией. Если произошла неудача при попытке чтения в переменную v, то значение v не изменилось (оно не изменится, если v имеет тип, управляемый функциями члена из istream или ostream ). Различие между состояниями, задаваемыми как fail() или как bad() уловить трудно, и оно имеет смысл только для разработчиков операций ввода. Если состояние есть fail(), то считается, что поток не поврежден, и никакие символы не пропали; о состоянии bad() ничего сказать нельзя.
Значения, обозначающие эти состояния, определены в классе ios:
class ios {
//...
public:
enum io_state {
goodbit=0,
eofbit=1,
filebit=2,
badbit=4,
};
//...
};Истинные значения состояний зависят от реализации, и указанные значения приведены только, чтобы избежать синтаксически неправильных конструкций.
Проверять состояние потока можно следующим образом:
switch (cin.rdstate()) {
case ios::goodbit:
// последняя операция с cin была успешной
break;
case ios::eofbit:
// в конце файла
break;
case ios::filebit:
// некоторый анализ ошибки
// возможно неплохой
break;
case ios::badbit:
// cin возможно испорчен
break;
}В более ранних реализациях для значений состояний использовались глобальные имена. Это приводило к нежелательному засорению пространства именования, поэтому новые имена доступны только в пределах класса ios. Если вам необходимо использовать старые имена в сочетании с новой библиотекой, можно воспользоваться следующими определениями:
const int _good = ios::goodbit; const int _bad = ios::badbit; const int _file = ios::filebit; const int _eof = ios::eofbit; typedef ios::io_state state_value ;
Разработчики библиотек должны заботиться о том, чтобы не добавлять новых имен к глобальному пространству именования. Если элементы перечисления входят в общий интерфейс библиотеки, они всегда должны использоваться в классе с префиксами, например, как ios::goodbit и ios::io_state.
Для переменной любого типа, для которого определены операции << и >>, цикл копирования записывается следующим образом:
while (cin>>z) cout << z << '\n';
Если поток появляется в условии, то проверяется состояние потока, и условие выполняется (т.е. результат его не 0) только для состояния good(). Как раз в приведенном выше цикле проверяется состояние потока istream, что является результатом операции cin>>z. Чтобы узнать, почему произошла неудача в цикле или условии, надо проверить состояние. Такая проверка для потока реализуется с помощью операции приведения (7.3.2).
Так, если z является символьным вектором, то в приведенном цикле читается стандартный ввод и выдается для каждой строки стандартного вывода по одному слову (т.е. последовательности символов, не являющихся обобщенными пробелами). Если z имеет тип complex, то в этом цикле с помощью операций, определенных в 10.2.2 и 10.2.3, будут копироваться комплексные числа. Шаблонную функцию копирования для потоков со значениями произвольного типа можно написать следующим образом:
complex z; iocopy(z,cin,cout); // копирование complex double d; iocopy(d,cin,cout); // копирование double char c; iocopy(c,cin,cout); // копирование char
Поскольку надоедает проверять на корректность каждую операцию ввода- вывода, то распространенным источником ошибок являются именно те места в программе, где такой контроль существенен. Обычно операции вывода не проверяют, но иногда они могут завершиться неудачно. Потоковый ввод- вывод разрабатывался из того принципа, чтобы сделать исключительные ситуации легкодоступными, и тем самым упростить обработку ошибок в процессе ввода-вывода.
10.3.3 Ввод пользовательских типов
Операцию ввода для пользовательского типа можно определить в точности так же, как и операцию вывода, но для операции ввода существенно, чтобы второй параметр имел тип ссылки, например:
istream& operator>>(istream& s, complex& a)
/*
формат input рассчитан на complex; "f" обозначает float:
f
( f )
( f , f )
*/
{
double re = 0, im = 0;
char c = 0;
s >> c;
if (c == '(') {
s >> re >> c;
if (c == ',') s >> im >> c;
if (c != ')') s.clear(ios::badbit); // установим состояние
}
else {
s.putback(c);
s >> re;
}
if (s) a = complex(re,im);
return s;
}Несмотря на сжатость кода, обрабатывающего ошибки, на самом деле учитывается большая часть ошибок. Инициализация локальной переменной с нужна для того, чтобы в нее не попало случайное значение, например '(', в случае неудачной операции. Последняя проверка состояния потока гарантирует, что параметр a получит значение только при успешном вводе.
Операция, устанавливающая состояние потока, названа clear() (здесь clear - очистить), поскольку чаще всего она используется для восстановления состояния потока как good() ; значением по умолчанию для параметра ios::clear() является ios::goodbit.