своим собственным кодом. Обратите внимание на то, что проблема останется нерешенной, если символ окажется следующим после возникновения ошибки, но это маловероятно.
Обнаружив эту проблему, мы написали вариант функции
keep_window_open
, аргументом которой была строка, закрывающая окно, как только пользователь вводил ее после приглашения. Таким образом, более простое решение выглядит так:
catch (runtime_error& e) {
cerr << e.what << endl;
keep_window_open("~~");
return 1;
}
Рассмотрим
еще один пример.
+1
!1~~
Эти данные вынуждают калькулятор выдавать соответствующие сообщения об ошибках, например
Чтобы выйти, введите ~~
и не прекращать работу, пока пользователь не введет строку
~~
.
Входные данные для калькулятора вводятся с клавиатуры. Это затрудняет тестирование: каждый раз, внося улучшение, мы должны напечатать множество контрольных примеров (каждый раз заново!), чтобы убедиться, что программа по-прежнему работает. Было бы лучше, если бы контрольные примеры где-то хранились и вызывать их одной командой. Некоторые операционные системы (в частности, Unix) упрощают эту задачу, позволяя потоку
cin
считывать данные из файла без модификации программы, а потоку
cout
— направлять данные в файл. В других случаях мы должны модифицировать программу так, чтобы она использовала файл (подробнее об этом — в главе 10).
Рассмотрим примеры.
1+2; q
1+2 q
Мы хотели бы вывести результат (
3
) и выйти из программы. Забавно, что строка
1+2 q
приводит к этому результату, а более очевидная строка
1+2; q
вызывает ошибку Ожидается первичное выражение. Где следует искать эту ошибку? Конечно, в функции
main
, где обрабатываются символы ; и q. Мы добавили инструкции “печать” и “выход” просто для того, чтобы поскорее получить работающий вариант калькулятора (см. раздел 6.6), а теперь расплачиваемся за эту поспешность. Рассмотрим еще раз следующий фрагмент:
double val = 0;
while (cin) {
cout << "> ";
Token t = ts.get;
if (t.kind == 'q') break;
if (t.kind == ';')
cout << "= " << val << '\n';
else
ts.putback(t);
val = expression;
}
Если обнаруживаем точку с запятой, то вызываем функцию
expression
, не проверяя символ
q
. Эта функция в первую очередь ищет вызов функции
term
, которая вызывает функцию
primary
, обнаруживающую символ q. Буква q не является первичным выражением, поэтому получаем сообщение об ошибке. Итак, после тестирования точки с запятой мы должны обработать символ q.
В этот момент мы почувствовали необходимость несколько упростить логику, поэтому окончательный вариант функции
main
выглядит так:
int main
try
{
while (cin) {
cout << "> ";
Token t = ts.get;
while (t.kind == ';') t=ts.get; // считываем ';'
if (t.kind == 'q') {
keep_window_open;
return 0;
}
ts.putback(t);
cout << "= " << expression << endl;
}
keep_window_open;
return 0;
}
catch (exception& e) {
cerr << e.what << endl;
keep_window_open("~~");
return 1;
}
catch (...) {
cerr << "exception \n";
keep_window_open("~~");
return 2;
}
Это повышает надежность обработки ошибок. Таким образом, теперь можно искать новые пути улучшения калькулятора.
7.4. Отрицательные числа
Проверив калькулятор, легко убедиться, что он не слишком элегантно обрабатывает отрицательные числа. Например, выражение
–1/2
является ошибочным.
Для того чтобы калькулятор работал корректно, мы должны были бы написать
(0–1)/2
Однако это неприемлемо.
Обычно такие проблемы выявляются на поздних этапах отладки и тестирования. Только тогда можно увидеть, что на самом деле делает программа, и получить информацию, позволяющую уточнить исходные идеи. Планируя проект, целесообразно сэкономить время и извлечь выгоду из наших уроков. Очень часто первая версия поставляется пользователям без необходимых уточнений из-за напряженного расписания и жесткой стратегии управления, которая не позволяет вносить исправления в спецификацию на поздних этапах разработки. Поздние добавления — это кошмар менеджера. На самом деле, когда программа уже достаточно работоспособна, но еще не готова к поставке, еще не поздно внести дополнения; это самый первый момент, когда можно учесть опыт ее использования. Реалистичное расписание должно учитывать это обстоятельство.
В данном случае необходимо внести исправления в грамматику, чтобы предусмотреть унарный минус. На первый взгляд легче всего внести исправления в пункт Первичное выражение. Сейчас он выглядит так:
Первичное выражение:
Число
"("Выражение")"
Нам требуется, чтобы этот пункт выглядел примерно таким образом: