Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 35
Текст из файла (страница 35)
6.1.4. Обработка ошибок Из-за того, что наша программа чрезвычайно проста, обработка ошибок для нее не является предметом первостепенного интереса. Наша функция обработки ошибок просто подсчитывает их количество, выводит сообщение и завершает работу: 1п( по о/' еггогя; доиЫе еггог (сопи я!г1пя ь з) ( по оу еггогз-г-';) б.). Калькулятор 165 сеп« "еггог: " «в « ' тп'; гегигп 1; Поток сегг — это небуферизованный выходной поток, который обычно используется для вывода сообщений об ошибках ($21.2.1). Наша функция возвращает значение по той причине, что обычно ошибки возникают в процессе вычисления выражений, так что следует либо полностью прервать этот процесс, либо вернуть некоторое значение, которое не вызовет осложнений в дальнейшей работе.
Для нашего простого калькулятора годится второй под- хоД. Если бы 8ет гойеп() отслеживала номер строки ИсходНОГО тЕкста, фуНкцИИ еггог() имело бы смысл информировать пользователя о том, где примерно произошла ошибка. Это было бы особенно полезно в случае неинтерактивного использования программы-калькулятора (86.6(19)). Часто нужно завершать работу программы при возникновении ошибок, ибо нет разумного продолжения их работы.
Это можно сделать вызовом библиотечной функции ех!1Н, которая сначала освобождает ресурсы (вроде потоков вывода), и только после этого завершает программу, возвращая значение, совпадающее с аргументом ее вызова (89.4.1.1). Более регулярный метод обработки ошибок основан на работе с исключениями 68.3, глава 14), но для программы из 150 строк вполне достаточно уже сделанного нами. 6.1.5.
Управляющая программа Теперь нам недостает лишь управляющей программы, которая запускает и оркеструет всю работу. В нашем случае для этого достаточно одной функции та1п (): аи та(п () ( гаЬ1е ( рр ) =5. 1415924555897952585; гаЬ(е ( "е" ) =2. 7182818284590452354т 77 вводим предопределенные имена йЫ1е (с1п) ( 8ег гойеп (); тэ (сигг гоИ==ЕМР) Ьгеайт (1 (сиг гоЬ==РИ1ПТ) сот!пие; сонг« ехрг (га(ве) « ' тп '; ) гешгп по о5' еггогга ) По устоявшемуся соглашению функция тат () возвращает нуль в случае нормального завершения и ненулевое значение в противном случае (83.2).
Возврат количества ошибок отлично развивает эту идею. Вся инициализация свелась у нас к формированию таблицы предопределенных имен. В теле функции та(п () основная работа сосредоточена в цикле, где считывается выражение и выводится результат его вычисления. Последнее выполняется следующей строкой кода: Глава б. Выражения и операторы 166 сои!«вхрг ()а1ве) « ' ~п ' нй!1е(с!п) ( УУ .. гу (сит Гоо!г==РКТМТ) соидпие( сои!«вхрг((а!ве) « ' Хп '; ) эквивалентен ион'1е ( сг'и ) ( УУ ... г) (сигг гоой! =Рй))МТ) сои!«вхрг(1аае) « ' ',и ' г 6.1.6. Заголовочные файлы Наша программа-калькулятор использует средства стандартной библиотеки, нз-за чего в окончательный код нужно включить следующие директивы ()!пс!иИе препроцессора: 1!пс1иг!е «овггеат> 41пс1иг!е <в(ппя> ((!псгиде <тар> 1!пс!иИе <ссгуре> УУ 110 (вводувывод) УУ ггг(пд (строки) П тар (ассоциативные пассивы) УУ (га!РнаО, и т.д.
Все средства из перечисленных здесь заголовочных файлов определяются в пространстве имен вМ, так что имена из этих файлов нужно либо полностью квалифицировать префиксом вЫ::, либо заранее внести их в глобальное пространство имен следующим образом: ив!пд пагиеврасе вгдг Я выбрал последнее, чтобы не смешивать в одну кучу обсуждение вопросов обработки выражений и концепций модульного построения программ. В главах 8 и 9 обсуждаются способы модульной организации программы-калькулятора с приме- Это служебный символ епд-о(-Где — он генерируется клавиатурой в ответ на нажатие спе- циальной комбинации клавиш, чаще всего — Сгг)ч-Х.
— Прим. лед. Аргумент!а!ве сообщает функции ехрг(), что ей не надо вызывать яег го1(еп() для выделения лексемы. Проверка сгп для каждой итерации цикла гарантирует корректное завершение программы, если с входным потоком возникнут какие-нибудь проблемы, а сравнение с ЕМВ гарантирует корректное завершение цикла, если яег го!геп () обнаружит юнец ввода'. Оператор Ьгеа!г осуществляет принудительный выход из ближайшего оператора хиа!сй или цикла. Сравнение с РК1МТ(то есть с ' Хп' и '; ') освобождает функцию ехрг() от ответственности за обработку пустых выражений. Оператор сопггпие означает переход к следующей итерации цикла (по-другому можно сказать, что в самый конец тела цикла), так что код 167 6.1.
Калькулятор пением сегментирующих средств в виде пространств имен и набора исходных файлов. На многих системах помимо указанных выше заголовочных файлов имеются еще и их эквиваленты с расширением . й, и в которых классы, функции и т.д. определяются в глобальном пространстве имен (Э9.2.1, 99.2.4, ЭВ.З.1). 6.1.7.
Аргументы командной строки После того как я написал и отгестировал программу-калькулятор, я обнаружил, что пользоваться ею не всегда удобно; нужно ее запустить, затем ввести выражение, и в конце концов„завершить работу программы. Чаще всего я использовал калькулятор для вычисления единственного выражения. Если бы это выражение можно было представить параметром командной строки, то удалось бы сэкономить несколько нажатий клавиш.
Программа начинает свою работу с функции атаги () (э3.2, в9.4). Ей передаются два аргумента, первый из которых задает общее количество аргументов командной строки (обычно обозначается агяс), а второй — это массив аргументов (обычно обозначаемый азяя). Эти аргументы передаются функции ига!л() оболочкой, ответственной за запуск программы; если оболочка не может передать аргументы, то азжс устанавливается в нуль. Так как соглашения о вызове функции та!и() взяты из языка С, то используется массив строк в стиле С (айаг" агля(асяс+11 ). Имя программы (как оно записано в командной строке) передается с помощью агат(01. Список аргументов имеет завершающий нуль, то есть агу [асяс) == О.
Например, для командной строки 4с 150/1.1934 ее аргументы имеют следующие значения: асяс "150/1.1934" Получить доступ к аргументам командной строки несложно. Вопрос в том, как их использовать с минимальными изменениями в готовой программе. Для этого предлагается читать из командной строки так же, как мы читали входные данные из входного потока. Неудивительно, что поток для чтения из строки называется ЬМлязтгсаиг. К сожалению, отсутствует способ заставить сгя ссылаться на поток !зпзвязтгеаиг. Поэтому мы должны заставить функции ввода нашей программы-калькулятора работать с!згг!иуггеаиг.
Более того, мы должны их заставить работать либо с Ьп!яязггеаиг, либо с с)п, в зависимости от конкретного вида командной строки. Простейшим решением будет определить глобальный указатель !ирит и настроить его на нужный поток, а также заставить все процедуры ввода использовать его: 168 Глава 6. Выражения и операторы //указатель но поток ввода Ь<геат* три1 !п< та1п (<п< асяс, слог* агВг [1 ) ( зпйсЬ (агВс) ( сазе 1: !при<= ас!п < Ьгеаа < сазе 2< !при<=не<г Ь<ппяз<геат (агяг (11 ) Ьгеаа< «е~аи!« еггог ("<оо тану агВитеп<з" ) < ге<игл 1< ) <аЫе [ "р!" ) =3.
!415926535897932385; <аЫе ( "е" ] =2 . 71 82818284590452354 < <гЫ!е ( * (при<) ( Ве< <о)<еи (); 1!'(сиге <оИ==ЕПР) Ьгеаа; <1 (сиге <оа==р<<11«Т) сопппие< сои<«ехрг (уа!зе) « ' ~и '; 0 (три<! е ас<п ) <<е!е<е <при11 ге<игл по оу е<тогз< ) Поток !зитийз<геат является потоком ввода, который читает из строки, переданной в качестве аргумента (В21.5.3). По достижению конца читаемой строки поведение потока !зпти8з<геат точно такое же, как у остальных потоков ввода (83.6, а21.3.3). Чтобы использовать в программе !зити8з<геат, нужно директивой Ипс!и<(е препроцессора включить заголовочный файл <зз» еат>.
Было бы совсем нетрудно заставить функцию та!п () читать большее число аргументов командной строки, но это представляется ненужным, так как несколько выражений можно передать одним аргументом: <<с "га<е=1. 19341150/га<е <19. 75/га<е< 217<гаге" Я применил двойные кавычки, поскольку; (точка с запятой) на моей (]]Ч1Х системе служит разделителем команд. Другие операционные системы имеют свои соглашения по передаче аргументов программам при их запуске. Нельзя назвать наше решение элегантным, поскольку все процедуры ввода приходится переделывать под использование *!при< вместо с!и, с тем чтобы они могли читать из разных источников.
Этих изменений можно было избежать, если бы я заранее предвидел эту ситуацию и применил что-нибудь вроде !при< с самого начала. Более обший и полезный подход состит в том, что источник ввода должен быть параметром для модуля калькулятора. Фундаментальная проблема данной программы-калькулятора состоит в том, что так называемый «калькулятор» реализуется б.2. Обзор операций языка С++ 169 в ней просто набором функций и данных. Нет ни модуля (82.4), ни обьекта (В2.5.2), представляющих калькулятор в явном виде. Если бы я разрабатывал модуль кальку- лятора нли тип калькулятора, то, естественно, задумался бы над его параметрами (98.5[3], 810.6]16]), 6.1.8.