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

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

*/

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

<p id="AutBody_Root126"><strong>7.7. Исправление ошибок</strong></p>

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

До сих пор все ошибки представлялись в виде исключений и обрабатывались функцией 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. Вернитесь к меловой доске!

Перейти на страницу:

Похожие книги

Programming with POSIX® Threads
Programming with POSIX® Threads

With this practical book, you will attain a solid understanding of threads and will discover how to put this powerful mode of programming to work in real-world applications. The primary advantage of threaded programming is that it enables your applications to accomplish more than one task at the same time by using the number-crunching power of multiprocessor parallelism and by automatically exploiting I/O concurrency in your code, even on a single processor machine. The result: applications that are faster, more responsive to users, and often easier to maintain. Threaded programming is particularly well suited to network programming where it helps alleviate the bottleneck of slow network I/O. This book offers an in-depth description of the IEEE operating system interface standard, POSIX (Portable Operating System Interface) threads, commonly called Pthreads. Written for experienced C programmers, but assuming no previous knowledge of threads, the book explains basic concepts such as asynchronous programming, the lifecycle of a thread, and synchronization. You then move to more advanced topics such as attributes objects, thread-specific data, and realtime scheduling. An entire chapter is devoted to "real code," with a look at barriers, read/write locks, the work queue manager, and how to utilize existing libraries. In addition, the book tackles one of the thorniest problems faced by thread programmers-debugging-with valuable suggestions on how to avoid code errors and performance problems from the outset. Numerous annotated examples are used to illustrate real-world concepts. A Pthreads mini-reference and a look at future standardization are also included.

David Butenhof

Программирование, программы, базы данных