Обработка ошибок
Поскольку программа так проста, обработка ошибок не составляет большого труда. Функция обработки ошибок просто считает ошибки, пишет сообщение об ошибке и возвращает управление обратно:
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(), которая обрывает выполнение сразу же или сразу после сохранения где-то информации для отладчика (дамп памяти); о подробностях справьтесь, пожалуйста, в вашем руководстве.
Драйвер
Когда все части программы на месте, нам нужен только драйвер для инициализации и всего того, что связано с запуском. В этом простом примере 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; }
Принято обычно, что 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 }
1 2 3 4 5 6
8 8 8
| |