Бьерн Страуструп - Язык программирования С++. Главы 9-10
Страница 24. Ввод



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(), которая возвращает символ в
поток, и он становится первым подлежащим чтению.

 
« Предыдущая статья   Следующая статья »