Чтение онлайн

ЖАНРЫ

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

Ввод из потока с in; вывод в поток cout.

Грамматика для ввода:

Инструкция:

Выражение

Печать

Выход

Печать:

;

Выход:

q

Выражение:

Терм

Выражение + Терм

Выражение – Терм

Терм:

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

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

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

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

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

Число

(Выражение)

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

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

Число:

литерал_с_плавающей_точкой

Ввод из потока cin через поток Token_stream с именем ts.

*/

Здесь мы использовали блок комментариев, который начинается символами

/*
и заканчивается символами
*/
. В реальной программе история пересмотра может содержать сведения о том, какие именно изменения были внесены и какие улучшения были сделаны. Обратите внимание на то, что эти комментарии помещены за пределами кода. Фактически это несколько упрощенная грамматика: сравните правило для Инструкции с тем, что на самом деле происходит в программе (например, взгляните на код в следующем разделе). Этот комментарий ничего не говорит от цикле в функции
calculate
, позволяющем выполнять несколько вычислений в рамках одного сеанса работы программы. Мы вернемся к этой проблеме в разделе 7.8.1.

7.7. Исправление ошибок

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

До сих пор все ошибки представлялись в виде исключений и обрабатывались функцией

main
. Если мы хотим исправить ошибку, то функция
calculate
должна перехватывать исключения и попытаться устранить неисправность прежде, чем приступить к вычислению следующего выражения.

void calculate

{

while (cin)

try {

cout << prompt;

Token t = ts.get;

while (t.kind == print) t=ts.get; // сначала

// игнорируем все

//
инструкции
"печать"

if (t.kind == quit) return;

ts.putback(t);

cout << result << expression << endl;

}

catch (exception& e) {

cerr << e.what << endl; //
выводим сообщение об ошибке

clean_up_mess;

}

}

Мы просто поместили цикл

while
в блоке
try
, который выводит сообщения об ошибке и устраняет неисправности. После этого работу можно продолжать по-прежнему. Что означает выражение “устранить неисправность”? В принципе готовность к выполнению вычислений после исправления ошибки означает, что все данные находятся в полном порядке и вполне предсказуемы. В калькуляторе единственные данные за пределами отдельных функций находятся в потоке
Token_stream
. Следовательно, мы должны убедиться, что в потоке нет лексем, связанных с прекращенными вычислениями и способных помешать будущим вычислениям.

Рассмотрим пример.

1++2*3; 4+5;

Эти выражения вызывают ошибку, и лексемы

2*3; 4+5
останутся в буферах потоков
Token_stream
и
cin
после того, как второй символ
+
породит исключение.

У нас есть две возможности.

1. Удалить все лексемы из потока

Token_stream
.

2. Удалить из потока все лексемы

Token_stream
, связанные с текущими вычислениями.

В первом случае отбрасываем все лексемы (включая

4+5;
), а во втором — отбрасываем только лексему
2*3
, оставляя лексему
4+5
для последующего вычисления. Один выбор является разумным, а второй может удивить пользователя. Обе альтернативы одинаково просто реализуются. Мы предпочли второй вариант, поскольку его проще протестировать. Он выглядит проще. Чтение лексем выполняется функцией
get
, поэтому можно написать функцию
clean_up_mess
, имеющую примерно такой вид:

void clean_up_mess // наивно

{

while (true) { // пропускаем,

// пока не обнаружим инструкцию "печать"

Token t = ts.get;

if (t.kind == print) return;

}

}

К сожалению, эта функция не всегда работает хорошо. Почему? Рассмотрим следующий вариант:

1@z; 1+3;

Символ

@
приводит нас к разделу
catch
в цикле
while
. Тогда для выявления следующей точки с запятой вызываем функцию
clean_up_mess
. Функция
clean_up_mess
вызывает функцию
get
и считывает символ
z
. Это порождает следующую ошибку (поскольку символ
z
не является лексемой), и мы снова оказываемся в блоке
catch
внутри функции
main
и выходим из программы. Ой! У нас теперь нет шансов вычислить лексему
1+3
. Вернитесь к меловой доске!

Поделиться с друзьями: