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

ОГЛАВЛЕНИЕ

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