Читаем Программирование. Принципы и практика использования C++ Исправленное издание полностью

      left += term;    // вычисляем Терм и добавляем его

      t = get_token;

      break;

    case '–':

      left –= term;    // вычисляем Терм и вычитаем его

      t = get_token;

      break;

    default:

      return left;       // финал: символов + и – нет;

                         // возвращаем ответ

    }

  }

}

Обратите внимание на то, что — за исключением цикла — этот вариант напоминает первый (см. раздел 6.5.2.1). Мы просто удалили вызов функции expression в функции expression и заменили его циклом. Другими словами, перевели Выражение в грамматическом правиле в цикл поиска Терма, за которым следует символ + или .

<p id="AutBody_Root106"><strong>6.5.3. Термы</strong></p>

Грамматическое правило для Терма очень похоже на правило для Выражения.

Терм:

  Первичное выражение

  Терм '*' Первичное выражение

  Терм '/' Первичное выражение

  Терм '%' Первичное выражение

Следовательно, программный код также должен быть похож на код для Выражения. Вот как выглядит его первый вариант:

double term

{

  double left = primary;

  Token t = get_token;

  while(true) {

    switch (t.kind) {

    case '*':

      left *= primary;

      t = get_token;

      break;

    case '/':

      left /= primary;

      t = get_token;

      break;

    case '%':

      left %= primary;

      t = get_token;

      break;

    default:

      return left;

    }

  }

}

  К сожалению, этот код не компилируется: операция вычисления остатка (%) для чисел с плавающей точкой не определена. Компилятор вежливо предупредит нас об этом. Когда мы утвердительно ответили на вопрос 5 из раздела 6.3.5 — “Следует ли позволить ввод чисел с плавающей точкой?”, — мы не думали о таких последствиях и просто поддались искушению добавить в программу дополнительные возможности. Вот так всегда! Что же делать? Можно во время выполнения программы проверить, являются ли оба операнда операции % целыми числами, и сообщить об ошибке, если это не так. А можно просто исключить операцию % из возможностей нашего калькулятора. Эту функцию всегда можно добавить позднее (см. раздел 7.5). Исключив операцию %, получим вполне работоспособную функцию: термы правильно распознаются и вычисляются. Однако опытный программист заметит нежелательную деталь, которая делает функцию term неприемлемой. Что произойдет, если ввести выражение 2/0? На нуль делить нельзя. Если попытаться это сделать, то аппаратное обеспечение компьютера обнаружит это и прекратит выполнение программы, выдав сообщение об ошибке. Неопытный программист обязательно столкнется с этой проблемой. По этой причине лучше провести проверку и выдать подходящее сообщение.

double term

{

  double left = primary;

  Token t = get_token;

  while(true) {

    switch (t.kind) {

    case '*':

      left *= primary;

      t = get_token;

      break;

    case '/':

    { double d = primary;

      if (d == 0) error("деление на нуль");

      left /= d;

      t = get_token;

    break;

    }

    default:

      return left;

    }

  }

 }

Почему мы поместили обработку операции / внутри блока? На этом настоял компилятор. Если мы хотим определить и инициализировать переменные в операторе switch, то должны поместить ее в блоке.

<p id="AutBody_Root107"><strong>6.5.4. Первичные выражения</strong></p>

Грамматическое правило для первичных выражений также простое.

Первичное выражение:

  Число

  '('Выражение')'

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

double primary

{

  Token t = get_token;

  switch (t.kind) {

  case '(': // обработка варианта '('выражение')'

    { double d = expression;

    t = get_token;

    if (t.kind != ')') error("')' expected");

    return d;

    }

  case '8':         // используем '8' для представления числа

    return t.value; // возвращаем значение числа

  default:

    error("ожидается первичное выражение");

  }

}

По сравнению с функциями expression и term в этом программном коде нет ничего нового. В нем используются те же самые языковые конструкции и методы, и объекты класса Token обрабатываются точно так же.

<p id="AutBody_Root108"><strong>6.6. Испытание первой версии</strong></p>
Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже