Программирование. Принципы и практика использования C++ Исправленное издание, стр. 84
Что же мы сделали неправильно с точки зрения программирования? Этот вопрос следует задавать себе каждый раз, когда обнаружите ошибку. Именно так мы можем избежать повторения одних и тех же ошибок. По существу, мы просто просмотрели программный код и угадали правильное решение. Это редко срабатывает! Мы должны понять, как работает программа, и объяснить, почему она работает правильно.
Анализ ошибок — часто лучший способ найти правильное решение. В данном случае функция
expression()Выражение: Терм Терм '+' Выражение // сложение Терм '–' Выражение // вычитаниеОтличие от нашей грамматики заключается именно в том, что выражение 1–2–3 должно трактоваться как Выражение 1–2, за которым следует символ – и Терм 3, а на самом деле функция интерпретирует выражение 1–2–3 как Терм 1, за которым следует символ – и Выражение 2–3. Иначе говоря, мы хотели, чтобы выражение 1–2–3 было эквивалентно (1–2)–3 , а не 1–(2–3).
Да, отладка утомительна, скучна и требует много времени, но в данном случае мы действительно работаем с правилами, известными со школьной скамьи, и не должны испытывать больших затруднений. Проблема заключается лишь в том, чтобы научить этим правилам компьютер, а он учится намного медленнее нас.
Обратите внимание на то, что мы могли бы определить выражение 1–2–3 как 1–(2–3), а не (1–2)–3 и вообще избежать этой дискуссии. Довольно часто самые трудные программистские проблемы возникают тогда, когда мы работаем с привычными для людей правилами, которые изобрели задолго до компьютеров.
6.5.2.3. Выражения: третья попытка (удачная)
Итак, что теперь? Еще раз взгляните на грамматику (правильная грамматика приведена в разделе 6.5.2): любое Выражение начинается с Терма, за которым может следовать символ + или –. Следовательно, мы должны найти Терм, проверить, следует ли за ним символ + или –, и делать это, пока символы “плюс” и “минус” не закончатся. Рассмотрим пример.
double expression(){ double left = term(); // считываем и вычисляем Терм Token t = get_token(); // получаем следующую лексему while (t.kind=='+' || t.kind=='–') { // ищем + или – if (t.kind == '+') left += term(); // вычисляем Терм и добавляем его else left –= term(); // вычисляем Терм и вычитаем его t = get_token(); } return left; // финал: символов + и – нет; возвращаем ответ}Этот вариант немного сложнее: мы ввели цикл для поиска символов + и –. Кроме того, дважды повторили проверку символов + и –, а также дважды вызвали функцию
get_token()double expression(){ double left = term(); // считываем и вычисляем Терм Token t = get_token(); // получаем следующую лексему while(true) { switch(t.kind) { case '+': left += term(); // вычисляем Терм и добавляем его t = get_token(); break; case '–': left –= term(); // вычисляем Терм и вычитаем его t = get_token(); break; default: return left; // финал: символов + и – нет; // возвращаем ответ } }}Обратите внимание на то, что — за исключением цикла — этот вариант напоминает первый (см. раздел 6.5.2.1). Мы просто удалили вызов функции
expression()expression()6.5.3. Термы
Грамматическое правило для Терма очень похоже на правило для Выражения.
Терм: Первичное выражение Терм '*' Первичное выражение Терм '/' Первичное выражение Терм '%' Первичное выражениеСледовательно, программный код также должен быть похож на код для Выражения. Вот как выглядит его первый вариант:
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: