Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 34
Текст из файла (страница 34)
// унпрньш' минус ге!игл -рпт (1гие), сазе ЕР ( с(оиЫе е = ехрг (1гие); !/ (сигг 1о/с!= КР) ге1игп еггог ("ожидалась )'); яе! 1о/сел (), // пропустить скобки ') ' ге1игл е, ) де/а и!1 ге1игп еггог (' ожидалось первичное выражение' ); ) ) Когда встречается МУМВЕ!! (то есть целый литерал или литерал с плаваюпсей точкой), возвращается е! о значение.
Процедура ввода де! 1о/сел () помешает значение в глобаль- резулы ат деления на ноль не определен и обычно приводит к катастрофическим последствиям. Позтому мы проверяем делитель на ноль до операции деления, и если он равен нулю, вызчяваеы функцию еггог(). Она описывается в ~ 6,1А. Переменная а!вводится в программу в том месте, где она понадобилась, и сразу же инициализируется.
Областью видимости имени, объявленного в условной инструкпии, является тело условной инструкции, а результат присваивания одновременно является и проверяемым условием Ц 6.3.2.1). Следовательно, деление и присваивавие 1е/!/=с! выполняются тогда и только то да, когда а! не равно нулю. Функция рпгл(), обрабатывающая первичные выражения, похожа на ехрг() и 1еггп (), за тем исключением, что по мере спуска по иерархии вызовов в ней уже выполняются некоторые полезные действия и пе требуется цикл: Иои Ь)е пи т Ьег оа1ие, // численное значесше я1г!ля я1пгся иа1ие; // строковое значение 152 Глава б.
Выражения и инструкции пую переменную питбег ва(ие. Использование глобальной перел|енпой в программе часто является признаком не очень четкой структуры илн проведения некоторой оптимизации. Так произошло и в нашем случае. В идеале лексема состоит пз двух частей: величины, спределяющей вид лексемы (в нашей программе это Тойеп иа1ие), и (при необходимости) значение лексемы. В нашем случае имеется только одна простая пер| мснная гигг соЬ, поэтомупотребовалась глобальная переменная питЬег иа(иедля храпения значения последнего прочитанного числа МИИВЕК Исключение этой глобальной переменной оставляется в качестве упражнения (э 6.6(211).
В действительности, нет необходимости сохранять питЬег иа1ие в локальной переменной и перед вызовом йе1 1ойеп, 11. При каждом корректном вводе калькунятор всегда использует одно и|ело в вычислениях перед тем, как ввести другое. Но в случае ошибки сохранение значения п его вывод поможет по.льзователю. Подобно тому, как значение последнего 7|111МВЕКхрапнтся в питЬег иа1ие, строковое представлснпе последнего 1лгАМЕ хранится в в1г(псг иа1ие. Прежде чем как-нибудь обработать имя, калькулятор должен заглянуть вперед и посмотреть, присваивается лп имени что-нибудь пчи оно просто используется.
В обоих случаях происходит обраше|и|е к таблице символов. Таблица символов имеет тип тар Я 3.7нк з 17А.1): тор<к|под, с1оиЫе> саЫе, Иго значит, что таблица проиндексировапа по з1г1ггд(строке), и возвращаемым значением является с(оиЫе, соответствующее строке. Например, если пользователь ввел: гадша = 6,1 78388, калькулятор вьшолппт следующее: доиЫе8 и = СаЫе('гаевик'), 0 .. ехрг() гзычиглчет значение, которое оудет г|рисооено .. и = 68 78 888, Ссылка и используется для храпения значения с(оиЫе, связанно~о с гас(1из, пока ехрг 1) вычисляет значение 6378.888 из введенной последователю|ости символов. б.1.2.
Функция ввода Сшпывание пз потока ввода — часто самая запутанная часть програмл|ы. Это происходит потому, что программа должна общаться с человеком — учитывать его капризы, привычки и совершенно псобъяснимые на первый взгляд ошибки. Попытка заставить человека поступать так, как это удобно компьютеру, обычно считается (и справедливо) оскорбительной. Задача процедуры ввода низкого уровня состоит в с |птыванни символов и составлении из них лексем более высоко~о уровня.
Затем лексемы станов|стев элел|ентамп ввода для процедур более высокого уровня. В нашем примере низкоуровневый ввод осуществляется функцией де1 1ойеп (). К счастью, написание процедур ввода низкого уровня пе является нашей ежедневной задачей. о|некие систсмы имеют стандартные функции такого рода. Я построю Ее| гойе|с (~ в два этапа. Сначала я напишу обманчиво простую версию, ксп орая всю сложность ввода переложит на пользователя, Затем я модифицирую ее в менее элегантную, но более простую в использовании процедуру. 153 5.1.
Калькулятор Задача состоит и считывании символа, определении по зтол1у снмволу вида лексемы и возвраптен|п1 соответствующего Тайен па!Ое. Начальные операторы считывают первый символ, не являющийся символом-разделителем, в сЬ и проверяют успешность ввода: ТоЬвп иа!ив ив! !оЬеп )) сЬагсЬ = О; сса»сЬ; яш!гсЬ )сЬ) ( сазе О ге!игл сигг ! оЬ = Е!4!), О присваивави е и воэвршл П<> умолчанпкт оператор» пропускаст символы-разделители (пробел, табуляция, перевод строки и т.
д.) и оставляет значение сЬ прежним, если операция ввода завершилась неуспешно. Следовательно, сЬ-=О означает конец ввода. Прпсвапвание является оператором; результатом присваивания является значение переменной слева от него. Это позволяет мне присвоить зла чешш ЕМВ переменной сшт !ОЬ и возвратить это значение в одной единственной инструкции. Использование одной инструкции вместо двух полезно при сопровождении. Если присваивание находится в одной строке, а возврат значения в другой, программист может модифицировать одну строку и забыть про вторую. Давайте рассмотрим несколько случаев отдельно до того, как строить закон"шнную функцию. Если встречается символ завершения ввода ' .
скобки плп оператор, то просто возвращается его значение: саве ',' сазе '"': саяе '1': сазе '+': сале '-' саяе ')'. саяе ')'. саяе'=' ге!игл сигг гоЬ = ТоЬеп иа!ив )сЬ); Числа обрабатываются следующим образом: сазе 'О' сазе '!', саяе '2' сазе тк саяе '4' саяе '5'. сазе '6' сазе '7': саяе '8', саяе '9'.
сияе'': с!п.ригЬасЬ )сЬ), с!и» пиш бег оа!ие; ге!игл сигг гоЬ = !ЧЮМВЕК; Расположение меток саяе горизонтально, а не вертикально — это обычно не слишком хорошо, потому что ухудшается восприятие. Однако утомительно шлсать однообразные строки о!пну за другой. "1 ак как оператор» умеет читать константы с плавающей точкой в переменную типа г1оиб1е, код становится тривиальным. Сначала первый символ !инфра или точка) помещается обратно в с1п. Затем константа читается в литбег па!не. Ил!я обрабатывается аналогичным образом: Глава б. Выражения и инструкции 154 г1е~аи11: ~',Г МАМЕ, ЛАМЕ =, иаи о~водка (Г (гка1рЬа (сй)) ( сгл ригйасй (сй); с1л» згг)пд оа)ие; ге1игп сигг гой=ЫАМЕ, еггог )'пеправильиол лексема" ); ге1игп сигг 1ойРК!ЧТ; Функция стандартной библиотеки 1за(рйа () Я 20А0.2) используется, чтобы избежать длинного списка сазе для каждого символа.
Оператор» считывает строку (в згг)пн оа1ие) до тех пор, пока во входном потоке не окажется символ-разделитель. Следовательно, пользователь должен после имени ввести символ-разделитель перед оператором, использукццпм зто имя в качестве операнда. Такой подход далек от идеала, поэтому мы вернемся к этой проблеме в З 6А.З.
И, наконец, полная функция ввода: Тойеп оа1иеде1 1ойеп () ( сйаг сй = О; с)п»сЬ; кж1сй (сй) ( сазе О; ге1игп сигг 1ой = ЕПР; сазе' . сазе'". сазе'Е: саке'ь' саяе' — ': саке '('; саке Т. саяе '='; ге1 и го сигг 1ой = Тойеп иа)ие (сй'1 сазе 'О': саяе 'Е: саке '2': саке '3' сазе '4': сазе 'БЛ сазе 'б': саке 'Т сазе 'О', саке '9'; сазе''. с)п ри1Ьасй (сй); с)п» лигпЬег иа1ие; ге1игп сигг 1ой = НтуМВЕРО с(еГаи1Е УУ Л)АМЕ, ВАМЕ = ики о~иибко О' (йяа1рйа )сй)) ( суп.ри1 Ьасй (сь); с1л => згг)пя оа(ие; ге1игп сигг 1ой = МАМЕ; еггог("иелравильлая лексема"); гегигп сигг 1ой =РЯНТ; Преобразование оператора в значение его лексемы тривиально, потому что Тайен иа(ие оператора определено в качестве целого значения оператора Я 4.8).
155 6.1. Калькулятор 6.1.3. Низкоуровневый ввод Применение калькулятора в таком виде вскрывает несколько неудобных моментов. Приходится помнить, что надо ввести точку с запятой в конце выражения, чтобы калькулятор вывел значение, а обязательное следование символа-разделителя за именем может представлять серьезную проблелсу. Напрссьсер, х=7 является единым идентификатором, а не идентификатором х за которым следуют оператор = и число 7.