Ввод

<!--StartFragment -->

Ввод аналогичен выводу. Имеется класс istream, который предоставляет операцию >> (\"взять из\") для небольшого множества стандартных типов. Функция operator >> может определяться для типа, определяемого пользователем.

Ввод Встроенных Типов

Класс istream определяется так:


class istream {
// ...
public:
istream& operator>>(char*); // строка
istream& operator>>(char&); // символ
istream& operator>>(short&);
istream& operator>>(int&);
istream& operator>>(long&);
istream& operator>>(float&);
istream& operator>>(double&);
// ...
};


Функции ввода определяются в таком духе:


istream& istream::operator>>(char& c);
{
// пропускает пропуски
int a;
// неким образом читает символ в \"a\"
c = a;
}


Пропуск определяется как стандартный пропуск в C, через вызов isspase() в том виде, как она определена в (пробел, табуляция, символ новой строки, перевод формата и возврат каретки).

В качестве альтернативы можно использовать функции get():


class istream {
// ...
istream& get(char& c); // char
istream& get(char* p, int n, int =\"\\n\"); // строка
};


Они обрабатывают символы пропуска так же, как остальные символы. Функция istream::get(char) читает один и тот же символ в свой параметр; другая istream::get читает не более n символов в вектор символов, начинающийся в p. Необязательный третий параметр используется для задания символа остановки (иначе, терминатора или ограничителя), то есть этот символ читаться не будет. Если будет встречен символ ограничитель, он останется как первый символ потока. По умолчанию вторая функция get будет читать самое большее n символов, но не больше чем одну строку, \"\\n\" является ограничителем по умолчанию. Необязательный третий параметр задает символ, который читаться не будет. Например:

cin.get(buf,256,\"\\t\");

будет читать в buf не более 256 символов, а если встретится табуляция (\"\\t\"), то это приведет к возврату из get. В этом случае следующим символом, который будет считан из cin, будет \"\\t\".

Стандартный заголовочный файл определяет несколько функций, которые могут оказаться полезными при осуществлении ввода:


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 isspase(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(c)

Состояния Потока

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

Поток может находиться в одном из следующих состояний:

enum stream_state { _good, _eof, _fail, _bad };

Если состояние _good или _eof, значит последняя операция ввода прошла успешно. Если состояние _good, то следующая операция ввода может пройти успешно, в противном случае она закончится неудачей. Другими словами, применение операции ввода к потоку, который не находится в состоянии _good, является пустой операцией. Если делается попытка читать в переменную v, и операция оканчивается неудачей, значение v должно остаться неизменным (оно будет неизменным, если v имеет один из тех типов, которые обрабатываются функциями членами istream или ostream). Отличия между состояниями _fail и _bad очень незначительно и представляет интерес только для разработчиков операций ввода. В состоянии _fail предполагается, что поток не испорчен и никакие символы не потеряны. В состоянии _bad может быть все что угодно.

Состояние потока можно проверять например так:

switch (cin.rdstate()) {
case _good:
// последняя операция над cin прошла успешно
break;
case _eof:
// конец файла
break;
case _fail:
// некоего рода ошибка форматирования
// возможно, не слишком плохая
break;
case _bad:
// возможно, символы cin потеряны
break;
}

Для любой переменной z типа, для которого определены операции << и >>, копирующий цикл можно написать так:

while (cin>>z) cout << z << "\\n";

Например, если z - вектор символов, этот цикл будет брать стандартный ввод и помещать его в стандартный вывод по одному слову (то есть, последовательности символов без пробела) на строку.

Когда в качестве условия используется поток, происходит проверка состояния потока и эта проверка проходит успешно (то есть, значение условия не ноль) только если состояние _good. В частности, в предыдущем цикле проверялось состояние istream, которое возвращает cin>>z. Чтобы обнаружить, почему цикл или проверка закончились неудачно, можно исследовать состояние. Такая проверка потока реализуется операцией преобразования.

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

Ввод Типов, Определяемых Пользователем

Ввод для пользовательского типа может определяться точно так же, как вывод, за тем исключением, что для операции ввода важно, чтобы второй параметр был ссылочного типа. Например:
 

 


istream& operator>>(istream& s, complex& a)
/*
форматы ввода для 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(_bad); // установить state
}
else {
s.putback(c);
s >> re;
}
 
 
if (s) a = complex(re,im);
return s;
}
 

 


Несмотря на то, что не хватает кода обработки ошибок, большую часть видов ошибок это на самом деле обрабатывать будет. Локальная переменная c инициализируется, чтобы ее значение не оказалось случайно "(" после того, как операция окончится неудачно. Завершающая проверка состояния потока гарантирует, что значение параметра a будет изменяться только в том случае, если все идет хорошо.
Операция установки состояния названа clear() (очистить), потому что она чаще всего используется для установки состояния потока заново как _good.

_good является значением параметра по умолчанию и для istream::clear(), и для ostream::clear().

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

Инициализация Потоков Ввода

Естественно, тип istream, так же как и ostream, снабжен конструктором:
 

 


class istream {
// ...
istream(streambuf* s, int sk =1, ostream* t =0);
istream(int size, char* p, int sk =1);
istream(int fd, int sk =1, ostream* t =0);
};
 

 


Параметр sk задает, должны пропускаться пропуски или нет. Параметр t (необязательный) задает указатель на ostream, к которому прикреплен istream. Например, cin прикреплен к cout; это значит, что перед тем, как попытаться читать символы из своего файла, cin выполняет

cout.flush(); // пишет буфер вывода

С помощью функции istream::tie() можно прикрепить (или открепить, с помощью tie(0)) любой ostream к любому istream. Например:
 

 


int y_or_n(ostream& to, istream& from)
/*
"to", получает отклик из "from"
*/
{
ostream* old = from.tie(&to);
for (;;) {
cout << "наберите Y или N: ";
char ch = 0;
if (!cin.get(ch)) return 0;
 
 
if (ch != "\\n") { // пропускает остаток строки
char ch2 = 0;
while (cin.get(ch2) && ch2 != "\\n") ;
}
switch (ch) {
case "Y":
case "y":
case "\\n":
from.tie(old); // восстанавливает старый tie
return 1;
case "N":
case "n":
from.tie(old); // восстанавливает старый tie
return 0;
default:
cout << "извините, попробуйте еще раз: ";
}
}
}

Когда используется буферизованный ввод (как это происходит по умолчанию), пользователь не может набрав только одну букву ожидать отклика. Система ждет появления символа новой строки. y_or_n() смотрит на первый символ строки, а остальные игнорирует.

Символ можно вернуть в поток с помощью функции istream::putback(char). Это позволяет программе "заглядывать вперед" в поток ввода.



Опубликовал admin
23 Мар, Вторник 2004г.



Программирование для чайников.