Бьерн Страуструп (947334), страница 56
Текст из файла (страница 56)
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.
10.4 Форматирование
Все примеры из 10.2 содержали неформатированный вывод, который являлся
преобразованием объекта в последовательность символов, задаваемую
стандартными правилами, длина которой также определяется этими
правилами. Часто программистам требуются более развитые возможности.
Так, возникает потребность контролировать размер памяти, необходимой
для операции вывода, и формат, используемый для выдачи чисел.
Точно так же допустимо управление некоторыми аспектами ввода.
10.4.1 Класс ios
Большинство средств управления вводом-выводом сосредоточены в классе
ios, который является базовым для ostream и istream. По сути здесь
находится управление связью между istream или ostream и буфером,
используемым для операций ввода-вывода. Именно класс ios контролирует:
как символы попадают в буфер и как они выбираются оттуда. Так, в классе
ios есть член, содержащий информацию об используемой при чтении или
записи целых чисел системы счисления (десятичная, восьмеричная или
шестнадцатеричная), о точности вещественных чисел и т.п., а также
функции для проверки и установки значений переменных, управляющих
потоком.
class ios {
//...
public:
ostream* tie(ostream* s); // связать input и output
ostream* tie(); // возвратить "tie"
int width(int w); // установить поле width
int width() const;
char fill(char); // установить символ заполнения
char fill() const; // вернуть символ заполнения
long flags(long f);
long flags() const;
long setf(long setbits, long field);
long setf(long);
long unsetf(long);
int precision(int); // установить точность для float
int precision() const;
int rdstate(); const; // состояния потоков, см. $$10.3.2
int eof() const;
int fail() const;
int bad() const;
int good() const;
void clear(int i=0);
//...
};
В 10.3.2 описаны функции, работающие с состоянием потока, остальные
приведены ниже.
10.4.1.1 Связывание потоков
Функция tie() может установить и разорвать связь между ostream и
istream. Рассмотрим пример:
main()
{
String s;
cout << "Password: ";
cin >> s;
// ...
}
Как можно гарантировать, что приглашение Password: появится на
экране прежде, чем выполниться операция чтения? Вывод в cout и ввод
из cin буферизуются, причем независимо, поэтому Password: появится
только по завершении программы, когда закроется буфер вывода.
Решение состоит в том, чтобы связать cout и cin с помощью
операции cin.tie(cout).
Если ostream связан с потоком istream, то буфер вывода выдается при