Читаем Программирование полностью

По существу, мы заменили функцию keep_window_open() своим собственным кодом. Обратите внимание на то, что проблема останется нерешенной, если символ окажется следующим после возникновения ошибки, но это маловероятно.

Обнаружив эту проблему, мы написали вариант функции 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;

}

Это повышает надежность обработки ошибок. Таким образом, теперь можно искать новые пути улучшения калькулятора.

<p id="AutBody_Root119"><strong>7.4. Отрицательные числа</strong></p>

 Проверив калькулятор, легко убедиться, что он не слишком элегантно обрабатывает отрицательные числа. Например, выражение

–1/2

является ошибочным.

Для того чтобы калькулятор работал корректно, мы должны были бы написать

(0–1)/2

Однако это неприемлемо.

  Обычно такие проблемы выявляются на поздних этапах отладки и тестирования. Только тогда можно увидеть, что на самом деле делает программа, и получить информацию, позволяющую уточнить исходные идеи. Планируя проект, целесообразно сэкономить время и извлечь выгоду из наших уроков. Очень часто первая версия поставляется пользователям без необходимых уточнений из-за напряженного расписания и жесткой стратегии управления, которая не позволяет вносить исправления в спецификацию на поздних этапах разработки. Поздние добавления — это кошмар менеджера. На самом деле, когда программа уже достаточно работоспособна, но еще не готова к поставке, еще не поздно внести дополнения; это самый первый момент, когда можно учесть опыт ее использования. Реалистичное расписание должно учитывать это обстоятельство.

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже