Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#15| Dragon God
Demon's Souls |#14| Flamelurker
Demon's Souls |#13| Storm King
Demon's Souls |#12| Old Monk & Old Hero

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Страустрап Б. Весь текст 579.17 Kb

Язык С++

Предыдущая страница Следующая страница
1 ... 9 10 11 12 13 14 15  16 17 18 19 20 21 22 ... 50
обрабатываются так:

  switch (ch) {
  case ';':
  case '\n':
      cin >> WS;    // пропустить пропуск
      return curr_tok=PRINT;

Пропуск  пустого   места  делать  необязательно,  но  он  позволяет
избежать повторных  обращений к  get_token(). WS  - это стандартный
пропусковый объект,  описанный в ; он используется только
для сброса  пропуска. Ошибка  во вводе  или конец  ввода  не  будут
обнаружены до следующего обращения к get_token(). Обратите внимание
на то,  как можно  использовать несколько  меток case (случаев) для
одной и  той же  последовательности операторов,  обрабатывающих эти
случаи. В  обоих случаях  возвращается лексема PRINT и помещается в
curr_tok.
  Числа обрабатыватся так:

  case '0': case '1': case '2': case '3': case '4':
  case '5': case '6': case '7': case '8': case '9':
  case '.':
      cin.putback(ch);
      cin >> number_value;
      return curr_tok=NUMBER;

  Располагать метки  случаев case  горизонтально, а не вертикально,
не очень  хорошая мысль,  поскольку читать  это гораздо труднее, но
отводить по одной строке на каждую цифру нудно.
  Поскольку операция  >> определена  также и  для чтения констант с
плавающей точкой  в double,  программирование этого  не  составляет
труда: сперва начальный символ (цифра или точка) помещается обратно
в cin, а затем можно считывать константу в number_value.
  Имя, то  есть лексема  NAME, определяется  как буква,  за которой
возможно следует несколько букв или цифр:

                             - стр 84 -

  if (isalpha(ch)) {
      char* p = name_string;
      *p++ = ch;
      while (cin.get(ch) && isalnum(ch)) *p++ = ch;
      cin.putback(ch);
      *p = 0;
      return curr_tok=NAME;
  }

  Эта часть  строит в  name_string строку,  заканчивающуюся  нулем.
Функции isalpha()  и   isalnum() заданы  в ; isalnum(c) не
ноль, если c буква или цифра, ноль в противном случае.
  Вот, наконец, функция ввода полностью:

  token_value get_token()
  {
      char ch;

      do {    // пропускает пропуски за исключением '\n'
          if(!cin.get(ch)) return curr_tok = END;
      } while (ch!='\n' && isspace(ch));

      switch (ch) {
      case ';':
      case '\n':
          cin >> WS;    // пропустить пропуск
          return curr_tok=PRINT;
      case '*':
      case '/':
      case '+':
      case '-':
      case '(':
      case ')':
      case '=':
          return curr_tok=ch;
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
      case '.':
          cin.putback(ch);
          cin >> number_value;
          return curr_tok=NUMBER;
      default:            // NAME, NAME= или ошибка
          if (isalpha(ch)) {
              char* p = name_string;
              *p++ = ch;
              while (cin.get(ch) && isalnum(ch)) *p++ = ch;
              cin.putback(ch);
              *p = 0;
              return curr_tok=NAME;
          }
          error("плохая лексема");
          return curr_tok=PRINT;
      }
  }

                             - стр 85 -

  Поскольку     token_value  (значение   лексемы)   операции   было
определено  как  целое  значение  этой  операции*,  обработка  всех
операций тривиальна.

     3.1.3 Таблица имен

  К таблице имен доступ осуществляется с помощью одной функции

  name* look(char* p, int ins =0);

  Ее второй  параметр указывает,  нужно ли сначала поместить строку
символов  в  таблицу.  Инициализатор  =0  задает  параметр,  кторый
надлежит использовать по умолчанию, когда look() вызывается с одним
параметром. Это  дает удобство записи, когда look("sqrt2") означает
look("sqrt2",0), то  есть просмотр,  без помещения в таблицу. Чтобы
получить  такое   же  удобство  записи  для  помещения  в  таблицу,
определяется вторая функция:

  inline name* insert(char* s) { return look(s,1);}

  Как уже отмечалось раньше, элементы этой таблицы имеют тип:

  srtuct name {
      char* string;
      char* next;
      double value;
  }

Член next используется только для сцепления вместе имен в таблице.
  Сама таблица - это просто вектор указателей на объекты типа name:

  const TBLSZ = 23;
  name* table[TBLSZ];

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

  int ii = 0;        // хэширование
  char* pp = p;
  while (*pp) ii = ii<<1 ^ *pp++;
  if (ii < 0) ii = -ii;
  ii %= TBLSZ;

То есть, с помощью исключающего ИЛИ каждый символ во входной строке
"добавляется"  к  ii  ("сумме"  предыдущих  символов).  Бит  в  x^y
устанавливается   единичным    тогда   и    только   тогда,   когда
соответствующие биты  в x и y различны. Перед применением в символе
исключающего ИЛИ,  ii  сдвигается  на  один  бит  влево,  чтобы  не

____________________
  * знака этой операции. (прим. перев.)

                             - стр 86 -

использовать в  слове только  один байт.  Это можно было написать и
так:

  ii <<= 1;
  ii ^= *pp++;

Кстати, применение  ^ лучше  и быстрее,  чем  +.  Сдвиг  важен  для
получения приемлемого хэш-кода в обоих случаях. Операторы

  if (ii < 0) ii = -ii;
  ii %= TBLSZ;

обеспечивают, что  ii будет лежать в диапазоне 0...TBLSZ-1; % - это
операция взятия по модулю (еще называемая получением остатка).
  Вот функция полностью:

  extern int strlen(const char*);
  extern int strcmp(const char*, const char*);
  extern int strcpy(const char*, const char*);

  name* look(char* p, int ins =0)
  {
      int ii = 0;        // хэширование
      char* pp = p;
      while (*pp) ii = ii<<1 ^ *pp++;
      if (ii < 0) ii = -ii;
      ii %= TBLSZ;

      for (name* n=table[ii]; n; n=n->next)    // поиск
          if (strcmp(p,n->string) == 0) return n;

      if (ins == 0) error("имя не найдено");

      name* nn = new name;                     // вставка
      nn->string = new char[strlen(p)+1];
      strcpy(nn->string,p);
      nn->value = 1;
      nn->next = table[ii];
      table[ii] = nn;
      return nn;
  }

  После вычисления  хэш-кода ii  имя находится  простым  просмотром
через поля  next. Проверка  каждого name  осуществляется с  помощью
стандартной функции  strcmp(). Если строка найдена, возвращается ее
name, иначе добавляется новое name.
  Добавление нового  name включает в себя создание нового объекта в
свободной  памяти   с  помощью   операции  new  (см.  #3.2.6),  его
инициализацию,  и   добавление  его   к  списку   имен.   Последнее
осуществляется просто путем помещения нового имени в голову списка,
поскольку это  можно делать  даже не  проверяя, имеется список, или
нет. Символьную  строку для  имени тоже нужно сохранить в свободной
памяти. Функция strlen() используется для определения того, сколько
памяти нужно,  new -  для выделения  этой памяти,  и strcpy() - для
копирования строки в память.

                             - стр 87 -

     3.1.4 Обработка ошибок

  Поскольку программа  так проста,  обработка ошибок  не составляет
большого труда.  Функция обработки  ошибок просто  считает  ошибки,
пишет сообщение об ошибке и возвращает управление обратно:

  int no_of_errors;

  double error(char* s) {
      cerr << "error: " << s << "\n";
      no_of_errors++;
      return 1;
  }

  Возвращается значение  потому, что  ошибки обычно  встречаются  в
середине  вычисления  выражения,  и  поэтому  надо  либо  полностью
прекращать вычисление,  либо возвращать  значение, которое  по всей
видимости  не  должно  вызвать  последующих  ошибок.  Для  простого
калькулятора  больше   подходит  последнее.   Если  бы  get_token()
отслеживала  номера   строк,   то   error()   могла   бы   сообщать
пользователю, где  приблизительно обнаружена  ошибка. Это наверняка
было бы полезно, если бы калькулятор использовался неитерактивно.
  Часто бывает  так, что  после появления  ошибки программа  должна
завершиться,  поскольку  нет  никакого  разумного  пути  продолжить
работу. Это  можно сделать с помощью вызова exit(), которая очищает
все вроде  потоков вывода  (#8.3.2), а  затем  завершает  программу
используя свой параметр в качестве ее возвращаемого значения. Более
радикальный  способ  завершения  программы  -  это  вызов  abort(),
которая обрывает  выполнение сразу  же или  сразу после  сохранения
где-то информации  для  отладчика  (дамп  памяти);  о  подробностях
справьтесь, пожалуйста, в вашем руководстве.

     3.1.5 Драйвер

  Когда все  части программы на месте, нам нужен только драйвер для
инициализации и  всего того, что связано с запуском. В этом простом
примере main() может работать так:

  int main()
  {
      // вставить предопределенные имена:
      insert("pi")->value = 3.1415926535897932385;
      insert("e")->value = 2.7182818284590452354;

      while (cin) {
          get_token();
          if (curr_tok == END) break;
          if (curr_tok == PRINT) continue;
          cout << expr() << "\n";
      }

      return no_of_errors;
  }

                             - стр 88 -

  Принято  обычно,   что  main()  возвращает  ноль  при  нормальном
завершении программы  и не  ноль в  противном случае,  поэтому  это
прекрасно может  сделать возвращение  числа ошибок. В данном случае
оказывается,  что   инициализация   нужна   только   для   введения
предопределенных имен в таблицу имен.
  Основная работа  цикла -  читать выражения  и писать  ответ.  Это
делает строка:

  cout << expr() << "\n";

  Проверка cin  на каждом  проходе  цикла  обеспечивает  завершение
программы в  случае, если с потоком ввода что-то не так, а проверка
на END  обеспечивает корректный  выход из  цикла, когда get_token()
встречает конец  файла.  Оператор    break  осуществляет  выход  из
ближайшего содержащего  его оператора  switch или  цикла (то  есть,
оператора for, оператора while или оператора do). Проверка на PRINT
(то есть,  на '\n'  или  ';')  освобождает  expr()  от  обязанности
обрабатывать  пустые   выражения.  Оператор   continue   равносилен
переходу к самому концу цикла, поэтому в данном случае

  while (cin) {
      // ...
      if (curr_tok == PRINT) continue;
      cout << expr() << "\n";
  }

эквивалентно

  while (cin) {
      // ...
      if (curr_tok == PRINT) goto end_of_loop;
      cout << expr() << "\n";
      end_of_loop
  }

  Более подробно циклы описываются в #с.9.

     3.1.6 Параметры командной строки

  После того,  как  программа  была  написана  и  оттестирована,  я
заметил, что  часто набирать выражения  на клавиатуре в стандартный
ввод надоедает,  поскольку обычно использование программы состоит в
вычислении одного  выражения. Если  бы можно  было представлять это
выражение как  параметр командной  строки, не  приходилось  бы  так
много нажимать на клавиши.
  Как уже  говорилось, программа  запускается вызовом main(). Когда
это происходит,  main() получает  два параметра:  указывающий число
параметров, обычно  называемый argc,  и вектор  параметров,  обычно
называемый argv.  Параметры -  это символьные  строки, поэтому argv
имеет  тип  char*[argc].  Имя  программы  (так,  как  оно  стоит  в
командной строке)  передается  в  качестве  argv[0],  поэтому  argc
всегда не меньше единицы. Например, в случае команды

  dc 150/1.1934

                             - стр 89 -

параметры имеют значения:

  argc        2
  argv[0]        "dc"
  argv[1]        "150/1.1934"

  Научиться пользоваться  параметрами  командной  строки  несложно;
сложность   состоит    в   том,    как    использовать    их    без
перепрограммирования.  В   данном  случае  это  оказывается  совсем
просто, поскольку поток ввода можно связать с символьной строкой, а
не с файлом (#8.5). Например, можно заставить cin читать символы из
Предыдущая страница Следующая страница
1 ... 9 10 11 12 13 14 15  16 17 18 19 20 21 22 ... 50
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (4)

Реклама