|
ГЛАВА 6ОПЕРАТОРЫ ЯЗЫКА СИ И УПРАВЛЕНИЕ ИХ ИСПОЛНЕНИЕМВ теории программирования доказано, что любая программа может быть закодирована с помощью комбинаций трех конструкций: последовательности операторов, выбора и итерации [Bohm и Jacopini]. Вы уже знакомы с несколькими различными операторами языка Си, которые реализуют эти конструкции. К ним относятся выражения, операторы if и while. В действительности этих операторов вполне достаточно, чтобы закодировать любую программу. Однако существуют и другие операторы» которые помогают облегчить программирование. Они будут представлены и продемонстрированы на примерах в настоящей главе. ![]() ![]() В ч. 1 этой главы будет представлен синтаксис операторов, сопровождаемый некоторыми примерами. В ч. 2 с помощью вновь представленных операторов будет "подчищена" и расширена программа вычисления выражений. ЧАСТЬ 1В табл. 6.1 показаны различные операторы языка Си. Можно заметить, что некоторые из определений операторов сами содержат операторы. Они служат примерами рекурсивных определений. Далее рассмотрим каждый оператор в отдельности. 6.1. ПУСТОЙ ОПЕРАТОР Пустой оператор состоит из одного символа - точки с запятой. Для чего он нужен? Сам по себе он обычно не встречается в программе на языке Си. Пустой оператор используется как заполнитель в других более сложных операторах, например в операторах for, if и while. Поэтому обсуждение пустого оператора будет включено в те разделы, которые посвящены этим и им подобным операторам. 6.2. ОПЕРАТОРЫ-ВЫРАЖЕНИЯ Вы уже встречались с операторами-выражениями и пользовались ими. Операторы-выражения представляют собой просто выражения, за которыми следует точка с запятой. Хотя многие операции (следовательно, и виды выражений) языка Си мы пока еще не рассматривали, в этой главе ограничимся только теми операциями и выражениями, с которыми Вы уже знакомы. Глава 7 посвящена обсуждению остальных операций и выражений в языке Си. ![]() 6.3. ОПЕРАТОРЫ BREAK И CONTINUE Операторы break и continue подобно пустому оператору используются в составе других операторов, например switch, while и do-while. Как и в случае пустого оператора, будем касаться их по мере обсуждения тех операторов, в состав которых они входят. 6.4. БЛОК ОПЕРАТОРОВ Как показано на рис.4.4, блок (иногда называемый составным оператором) состоит из определений и объявлений данных, за которыми следует последовательность операторов. Блок заключается в фигурные скобки. Здесь стоит отметить, что в описаниях синтаксиса языка Си всюду, где указан "оператор", вместо него можно указывать блок операторов. Таким образом, если Вы пользуетесь такими операторами, как for, if и т.д., блок можно использовать в любом месте, где в определении синтаксиса указан нетерминальный оператор. 6.5. ОПЕРАТОР RETURN Напомним, что Вам уже приходилось пользоваться оператором return. Следует обратить внимание на то, что этот оператор имеет следующие две основные формы: return; или return(выражение); Первая форма обеспечивает простую передачу управления из текущей функции обратно в вызывающую функцию. Вторая форма обеспечивает не только передачу управления, но еще и возвращение значения выражения в вызывающую функцию. 6.6. ОПЕРАТОР IF Мы уже пользовались оператором if, и достаточно интенсивно. Его синтаксис имеет следующий вид: if (выражение) оператор_1 else оператор_2 Вначале вычисляется заключенное в скобки выражение. Если его значение отлично от нуля ("истина"), то выполняется оператор_1. Если использовано служебное слово else (иначе) и значение выражения равно нулю ("ложь"), то выполняется оператор, указанный после служебного слова else. Если значение выражения равно нулю, но служебное слово else отсутствует, то управление передается следующему оператору программы. Как обычно, в качестве операторов оператор_1 и оператор_2 можно использовать блоки операторов. Операторы if могут также быть вложенными, как это сделано в функции do_select программы управления портфелем акций (рис.6.1). ![]() Каждому из операторов if (кроме последнего) соответствует свое служебное слово else, поэтому показанный на рис.6.1 исходный код воспринимается вполне однозначно. Рассмотрим, однако, следующий пример: ![]() Какому именно из операторов if соответствует первое служебное слово else? Чтобы ответ на этот вопрос не вызывал затруднений, слово else должно набираться на одном уровне с тем оператором if, которому оно соответствует. Язык Си следует обычному правилу, согласно которому служебное слово else связывается с последним оператором if, с которым еще не было связано else. Иной порядок может быть установлен с помощью фигурных скобок. 6.7. ОПЕРАТОР WHILE Оператор while (пока) также уже знаком Вам. Его синтаксис следующий: while (выражение) оператор Выполнение оператора while начинается с вычисления выражения. Если его значение отлично от нуля ("истина"), то выполняется оператор, указанный вслед за выражением, и весь этот процесс повторяется заново. Повторное выполнение оператора осуществляется в течение всего времени, пока выражение имеет ненулевое значение. Это выражение называется условием цикла. Конечно, одним из результатов выполнения оператора, составляющего тело цикла, должно быть изменение условия цикла; в противном случае оператор while будет выполняться бесконечно. Обычно тело цикла представляет собой блок операторов. Это позволяет обеспечить циклическое выполнение группы операторов. Однако иногда может понадобиться, чтобы ни один оператор не выполнялся. Предположим, к примеру, что при работе с программой вычисления выражений было ошибочно введено следующее выражение: (5X9)+(3*2) Входящий в него символ X не принадлежит к числу допустимых элементов выражения. Было бы бессмысленно продолжать разбор и выполнение выражения после того, как этот символ был обнаружен, поскольку никакими средствами нельзя установить, что же на самом деле хотел ввести пользователь. Одним из путей игнорирования оставшейся части выражения являются повторные вызовы функции scanner с помощью оператора while до тех пор, пока не будет обнаружен конец выражения. Эту идею можно реализовать следующим образом: /-------Пустой оператор while (scanner() != END); В этом операторе вся работа осуществляется за счет выполнения условия цикла. Не требуется никаких других операторов. Однако для удовлетворения синтаксическим правилам должна быть указана завершающая точка с запятой. Она порождает пустой оператор. Другие примеры пустых операторов будут приведены при обсуждении оператора for. Еще одним специальным примером использования оператора while служит преднамеренное создание "бесконечного" цикла. Следующий цикл while эквивалентен предыдущему примеру: while(l) { if (scanner() = = END) break; } Условие цикла всегда отлично от нуля, поэтому цикл может исполняться неограниченное число раз. В действительности он бы исполнялся бесконечно, если бы не наличие в его теле оператора break. Оператор break вызывает завершение самого внутреннего включающего его оператора while, do-while, for или switch. В нашем примере оператор break будет исполнен, если возвращаемое функцией scanner значение станет равным END. Как только это произойдет, управление будет передано оператору программы, находящемуся непосредственно за концом оператора while. Можно дать и другой способ решения поставленной здесь задачи: игнорировать ошибочный элемент и попытаться продолжить разбор выражения. Попробуем в случае выражения (5Х9)+(3*2) просто пропустить символ X. Если разбор выражения выполнялся с помощью оператора while, то при обнаружении ошибочного элемента следует пропустить оставшиеся операторы цикла, не завершая его выполнение (как это делает оператор break), что может быть сделано с помощью оператора if следующим образом: while (...) { /* Обрабатывать только допустимые элементы, игнорируя остальные */ if (scanner() != ER { /* Разобрать выражение */ В языке Си предусмотрен оператор, который позволяет пропускать оставшуюся часть цикла и начинать новую итерацию. Он имеет подходящее название: continue (продолжить). Следующий пример обеспечивает те же действия, что и предыдущий: while (...) if (scanner() = = ER) continue; /* Разобрать выражение */ } Если возвращаемое функцией scanner значение равно ER, то будет исполнен оператор continue. В результате оставшиеся операторы цикла (представленные многоточием) будут на этой итерации пропущены и управление будет передано в конец цикла. В данном примере за выполнением оператора continue последует вычисление условия цикла, и цикл будет продолжен, если значение условия отлично от нуля. По своему действию операторы break и continue эквивалентны операторам перехода с ограниченным диапазоном передачи управления. Действие операторов break и continue в том случае, когда они используются в составе оператора while, отражено на следующей диаграмме: ![]() 6.8. ОПЕРАТОР SWITCH Вернемся к показанному на рис. 6.1 исходному коду: if (choice = = '1') buy(); else if (choice = = '2') sell(); else if (choice = = '3') current(); else if (choice = = '4') gain_loss(); else if (choice ,= = '5') rem_stock(); Этот код трудно читать и еще труднее сопровождать. В подобной ситуации, когда требуется передавать управление одному из нескольких операторов в зависимости от значения выражения, может быть использован оператор switch (переключить). Его синтаксис следующий: switch (выражение) { case константное_выражение_1: оператор; . . . сазе константное_выражение_2: оператор; case константное_выражение_n: оператор; default : оператор; } Выполнение оператора switch начинается с вычисления заключенного в скобки выражения, которое должно давать целый результат. Затем просматриваются друг за другом префиксы case (случай), указанные после служебного слова case константные выражения вычисляются и результаты сравниваются с результатом вычисления выражения, указанного после служебного слова switch. Если эти результаты совпали, то управление передается оператору, следующему за служебным словом case. Если ни одного совпадения не произошло, и при этом указано (необязательное) служебное слово default (по умолчанию), та управление передается оператору, следующему за служебным словом default. Если ни одного совпадения не произошло, а служебное слово default отсутствует, то ни один из операторов блока не выполняется. После передачи управления в блок исполнение указанных в нем операторов продолжается до конца блока, если только последовательность выполнения операторов не будет изменена операторами break или goto. Это означает, что будут выполняться все операторы, указанные под низлежащими служебными словами case, если только последовательность исполнения операторов не будет явным образом изменена. Заметьте, что выражение, указанное вслед за служебным словом case, должно быть константным (т.е. после префикса case не допускается использование переменных). Кроме того, порядок значений этих выражений несуществен (т.е. не требуется порядка ни по возрастанию, ни по убыванию этих значений). На рис.6.2 показан исходный код, который можно получить из кода, приведенного на рис.6.1, если применить оператор switch. switch (choice) { case '1': buy(); break; case '2': sell(); break; case '3': current(); break; case '4': gain_loss(); break; case '5': rem_stock(); break; default: printf("Ошибочный выбор\n"); } Рис.6.2 Обратите внимание на использование оператора break. Префиксы case и default не изменяют порядка исполнения операторов в блоке оператора switch. Чтобы выполнялись только те операторы, которые соответствуют выбранной альтернативе, необходимо использовать оператор break, который досрочно завершит выполнение оператора switch (если это необходимо). В результате выполнения оператора break управление будет передано оператору, находящемуся вслед за закрывающей фигурной скобкой блока оператора switch: ![]() Если бы оператора break не было, то управление передавалось бы от одной альтернативы к следующей низлежащей. Иногда это желательно. Например, в программе вычисления выражений функция scanner должна выполнять разбор чисел с плавающей точкой. ![]() Однако числа с плавающей точкой могут начинаться как с цифры, так и с десятичной точки. Определяя, какое из условий имеет место, можно связать два служебных слова case с одной и той же группой операторов (пример показан на рис.б.З). Прежде чем обсудить код, содержащийся в блоке оператора switch, заметим, что одной и той же группе операторов присвоены два префикса case. Если значение выражения в операторе switch равно DОТ или NUMBER, то будет исполняться один и тот же код. Обработка вариантов DOT и NUMBER одинакова. Обратите внимание на то, что оператор break тем не менее используется, поскольку необходимо отделить код обработки вариантов DOT и NUMBER от кода обработки остальных вариантов (default). Посмотрим на код, приведенный на рис.6.3. Назначением этого фрагмента программы является преобразование строки цифр, в которую может также входить десятичная точка, в число с плавающей точкой. Попробуем проследить ход его выполнения. Переменная value будет содержать числовое значение разбираемой строки. Объект divisor будет содержать масштабирующий коэффициент (показатель степени при 10) дробной части числа. Но каково назначение следующего оператора? value = value*10 + (ch - '0'); После извлечения очередной цифры (содержащейся в переменной ch) текущее значение числа (содержащееся в переменной value) умножается на 10 для того, чтобы освободилось место для этой цифры. Затем к результату умножения прибавляется очередная цифра. Но зачем надо вычитать '0' из значения переменной ch? Напомним, что ch содержит ASCII-код извлеченной цифры, т.е. значение в диапазоне от 48 (код '0') до 57 (код '9'). Вычитая '0' (т.е. 48), сдвигаем эти значения из диапазона 48-57 в диапазон 0-9 и тем самым получаем числовое значение для цифры, представленной ASCII-кодом. Для примера "разберем" число 98.6 (символом ^ отмечен текущий обрабатываемый символ): ![]() Наконец, последним оператором рассматриваемого варианта обработки является value = value/divisor; после выполнения которого в переменной value образуется значение 98.6. В ч. 2 этот фрагмент вставим в программу вычисления выражений. 6.9. ОПЕРАТОР DO-WHILE Оператор do-while нередко называют просто оператором do. Его синтаксис следующий: do оператор while (выражение); Примечание: точка с запятой обязательна / Оператор do-while исполняет оператор, следующий за служебным словом do, до тех. пор, пока значение выражения не станет равным нулю, Оператор do-while похож на оператор while, но в отличие от последнего в нем условие цикла (выражение) вычисляется и проверяется после очередного исполнения оператора. Это означает, что следующий за служебным словом do оператор будет в отличие от цикла while выполнен не менее одного раза. Для изменения хода исполнения операторов, составляющих тело цикла do-while, можно воспользоваться операторами break и continue. Действие этих операторов на ход исполнения цикла показано на следующей диаграмме: ![]() Как и в случае оператора while, действие операторов break и continue эквивалентно действию операторов перехода с ограниченным диапазоном передачи управления. 6.10. ОПЕРАТОР FOR Вы могли заметить, что следующий типичный пример хорошо укладывается в рамки оператора while; 8ыражение._1; . /* Инициализировать условие цикла */ while (выражение_2) /* Проверить условие цикла */ { выражение_3; /* изменить условие цикла */ } Например, следующий цикл копирует содержимое символьного массива name в массив co_name: i=0; /* Инициализации */ while name[i] != '\0') /* Условие цикла, */ { со_name[ i ] = name[i]; i=i+1; /* Модификация условия */ } со_name[i] = '\0'; Этот пример настолько типичен, что и язык Си был включен оператор for, который один реализует все три операции: инициализацию счетчика цикла, проверку его значения и его модификацию. Синтаксис оператора for следующий: ![]() Выражение_1 вычисляется один, и только один раз перед проверкой условия цикла. Выражение_2 задает условие продолжения цикла. Если его значение отлично от нуля ("истина"), то будет выполнен оператор, составляющий тело цикла. После этого будет вычислено выражение _3, указанное в операторе for. Хотя оно может иметь произвольный характер, обычно его используют для модификации условия продолжения цикла. Затем снова вычисляется условие продолжения цикла и весь процесс повторяется заново. Оператор выполняется, пока значение условия цикла остается отличным от нуля. (Обратите внимание, что выражение_2 и выражение_3 вычисляются на каждом проходе цикла, в то время как выражение_1 вычисляется только перед его началом.) Приведем пример оператора for, эквивалентного предыдущему примеру с оператором while и предназначенного для копирования содержимого массива name в массив co_name: for (i = 0; name[i] != '\0'; i = i + 1) co_name[i] = name[i]; co_name[i] = '\0'; Оператор for удобен и легче для восприятия, чем оператор while. Это обусловлено тем, что все три выражения, связанные с организацией цикла (инициализация, проверка и модификация) собраны вместе. При этом не приходится просматривать исходный код в поисках выражений, обеспечивающих инициализацию и модификацию, как пришлось бы при применении оператора while. Пустой оператор может быть использован для пропуска любого из трех выражений, входящих в состав оператора for. Например, вырожденный оператор for вида for ( ; ;) оператор /* Исполняется бесконечно */ эквивалентен оператору while(1). А в операторе for( ; scanner() != END; ) опущены выражения инициализации и модификации. Как и в случае операторов while и do-while, последовательность передачи управления в операторе for может быть изменена с помощью операторов break и continue. Действие этих операторов показано на следующей диаграмме: ![]() — > 6.11. ОПЕРАТОР GOTO И МЕТКИ ОПЕРАТОРОВ Операторы break и continue описывались как операторы перехода с ограниченным диапазоном передачи управления. В дополнение к ним язык Си предоставляет программисту и нашумевший оператор перехода goto. Оператор goto обеспечивает передачу управления любому оператору функции. Оператор, которому предполагается передать управление, должен быть снабжен меткой. Эта метка указывается в операторе goto. В синтаксисе оператора goto goto идентификатор идентификатор является именем метки. Синтаксис метки имеет вид идентификатор: Строго говоря, при написании программ применение оператора goto не является необходимым. Как уже упоминалось в начале этой главы, любая программа может быть написана с помощью применения трех конструкций: последовательности операторов, выбора и итерации. Однако можно найти ситуации, когда оператор goto удобен [Knuth], и поэтому он был включен в состав языка Си. Обычно оператор goto служит для передачи управления в конец функции в случае обнаружения ошибки, особенно если ошибка может возникать во многих местах программы. Тем самым может быть исключено применение флагов. Рассмотрим следующий пример: Структурированное использование оператора goto buy () { if (get_trans() = = FAILURE) goto error; if (update_current() = = FAILURE) goto error; if (update_ytd() = = FAILURE) goto error; return(SUCCESS); error: /* коды для обработки ошибок */ return(FAILURE); } Заметьте, что этот код мог бы быть написан без оператора goto, например, за счет применения вложенных операторов if и флага состояния. Однако, как будет показано далее, ограниченное применение оператора goto иногда может упростить исходный код. Другим примером применения оператора goto может служить выход из глубоко вложенных циклов. Поскольку оператор break выводит управление только из самого внутреннего цикла, то для выхода наружу из вложенных циклов может быть использован оператор goto. В следующем примере оператор goto должен выполняться в случае возникновения ошибочной ситуации, при которой необходим выход сразу из обоих операторов while: while (выражение) while (выражение) { if (/* ошибка */) goto loop_end; } } loop_end: /* Остальной код */ Хотя в отдельных случаях применение оператора goto достаточно безопасно, в этой книге его использовать не будем. часть 2 В этой части сосредоточимся на программе вычисления выражений. Сделаем в ней добавления, обеспечивающие получение действующего варианта программы, Программа управления портфелем акций будет развиваться в последующих главах. 6.12. ПРОГРАММА ВЫЧИСЛЕНИЯ ВЫРАЖЕНИЙ Основные изменения в этой версии программы вычисления выражений коснутся модулей SCAN.C, EXECUTE.C и EVAL.C. К функции scanner, находящейся в модуле SCAN.C, будет добавлен код для разбора числа (см. рис.6.3). Модуль EXECUTE.C будет расширен таким образом, чтобы имелась возможность выполнять четыре действия, задаваемые в таблице переходов. Наконец, функция eval, находящаяся в модуле EVAL, будет расширена так, чтобы действительно выполнялись операции сложения, вычитания, умножения и деления. При изучении этих функций помните, что в качестве операндов выполняемой программой операции используются два верхних значения стека результатов. Результат ее выполнения возвращается обратно в стек результатов. Приведем новый исходный код программы вычисления выражений: /* Содержание файла CALC.H */ /* * Определить возвращаемые значения общих функций. */ #define TRACE /* Включает в программу отладочный код */ #define SUCCESS 1 #define FAILURE 0 #define DONE 0 /* * Ниже приводятся определения кодов элементов выражения и классов * символов, используемых функцией scanner. * Эти значения используются как индексы в таблице переходов. */ «define END 0 /* Конец выражения */ #define EMPTY 0 /* Пустой стек */ #define LPAREN 1 /* ( */ #define RPAREN 6 /* ) */ #define MULT 4 /* * */ «define PLUS 2 /* + */ «define MINUS 3 /* - */ «define DOT 8 /* . (десятичная точка) */ «define DIVIDE 5 /* / */ «define NUMBER 7 /* Числовая константа */ «define WHITE 9 /* Пробельные элементы */ #define ER 20 /* Остальные (нежелательные) символы */ /* Содержание модуля CALC.C */ /* * Этот файл содержит функцию main программы вычисления выражений. */ #include "CALC.H" /* Объявить внешние функции */ extern exs_init(), ops_init(); /* STACK.С */ extern int getexpr(); . /* SCAN.C */ extern int execute(); /* EXECUTE.C */ /** * Это основная управляющая функция программы вычисления * выражений. **/ main() { exs_init(); ops_init(); /* Инициализировать стеки */ getexpr(); execute() } /* Содержание модуля EXECUTE.С */ /* * Этот файл содержит Функцию execute программы вычисления выражений. #include "CALC.H" extern int scanner().; /* SCAN.С */ extern float value; /* Число, извлеченное функцией scanner */ extern int exs_push(); /* STACK. С */ extern float exs_pop(); /* STACK.С */ extern int ops_top(}; /* STACK.С */ extern int ops_push{); /* STACK.С */ extern int ops_pop(); /* STACK.С */ /* Текущий элемент */ Конец ( + - /. ) */ /* Определить таблицу переходов */ ![]() /** * Эта функция выполняет разбор и вычисление выражения. **/ execute() } int token; /* Текущий элемент */ int action = 1; /* Из таблицы переходов */ token = scanner(); /* Извлечь первый элемент */ while,(action i= DONE) { if (token = = NUMBER) /* Поместить число в стек результатов */ { exs_push(value); token = scanner(); } else { /* Определить индекс в таблице переходов, используя текущий элемент и верхнюю операцию из стека операций */ action = trans_table[ops_top()][token]; #ifdef TRACE printf("Действие = %d\n".action); #endif switch (action) { case 0: /* конец выражения */ /* изобразить результат */ printf ("%f\n" ,exs_pop( 5); break; case 1: ops_push(token); token = scanner(); break; case 2: eval(); /* выполнить верхнюю операцию */ ops_push(token); token = scanner(); break; case 3: ops_pop(); /* извлечь операцию из стека * token = scanner(); break; case 4: eval(); /* выполнить верхнюю операцию */ break; case 10: printf("Ошибка в выражении\n"); action = 0; break; } } } #ifdef TRACE printf("Выполнение эавершено\n"); #endif return; } /* Содержание модуля SCAN.С */ /* * Этот файл содержит функции scanner и getexpr программы вычисления * выражений. */ #include "CALC.H" #define MAX_LINE 81 static char line[MAX_LINE]; /* Содержит текущее выражение */ static int position; /* Индекс массива line */ float value; /* Число, извлеченное функцией scanner */ /* * Следующий массив будет использован для сопоставления каждому * символу выражения определенного класса, соответствующего типу * элемента, в состав которого входит этот символ. Каждому эле- * менту приписан числовой код в диапазоне от 0 до 20 (20 озна- * чает наличие в элементе недопустимого символа). Этот массив * рассчитан на подмножество смежных символов от пробела до цифры * 9. */ static char token_codes[26] = { WHITE,ER,ER,ER.ER,ER,ER,ER, /* Пробел,! ,",#,$,%,&,'*/ LPAREN.RPAREN,MULT,PLUS, /* (,),*,+ */ ER,MINUS,DOT,DIVIDE, /* , ,-,.,/*/ NUMBER,NUMBER,NUMBER,NUMBER, /* 0,1,2,3 */ NUMBER,NUMBER,NUMBER,NUMBER, /* 4,5,6,7 */ NUMBER,NUMBER /* 8,9 */ }; /** Эта функция отвечает за поиск очередного элемента выражения. Элементами могут быть операции * +,-,* или / или операнд (в данной версии мы ограничимся одной цифрой). **/ int scanner() { char ch; /* Текущий символ выражения */ int token; /* Текущий элемент выражения */ int index; /* Индекс массива token_codes */ float divisor; /* Масштабный коэффициент для дробной части числа */ ch = line[position]; if (ch = = '\0') return(END); /* Конец выражения */ if (ch < ' ') return(ER); /* Недопустимый символ: < пробел */ if (ch > '9') return(ER); /* или > 9 */ /* Использовать символ как индекс в массиве. * Вначале масштабировать его так, чтобы пробелу * соответствовал нулевой индекс. */ index = ch - ' '; token = token_codes[index]; switch (token) { case DOT: token = NUMBER; case NUMBER: /* Разобрать число */ value = 0; divisor = 1; while (token_codes[ch - ' '] = = NUMBER) { value = value*10 + (ch - '0'); position = position + 1; ch = line[position]; } if (ch = = '.') { position = position + 1; ch = line[position]; while (token_codes[ch - ' '] = = NUMBER) { divisor = divisor*10; value = value*10 + (ch - '0'); position = position + 1; ch = line[position]; } } value = value/divisor; break; default: position = position + 1; break; . } #ifdef TRACE printf("Элемент = %d\n".token); #end if return(token); } /** Эта функция выдает пользователю приглашение к вводу выражения и считывает вводимое выражение. **/ getexpr() { int ch; /* Текущий введенный символ */ extern getline(); /* GETLINE.C */ printf("* "); getline(line,MAX_LINE); position =0; /* Установить значение внешнего индекса на начало строки */ return; } /* Содержание модуля GETLINE.C */ /* * Этот файл содержит функцию getline. */ #include "stdio.h" /* Может понадобиться функция getchar */ /** * Эта функция считывает строку данных с клавиатуры и запоминает * ее в заданном массиве символов. Она возвращает число считанных * символов. **/ getline(array, size) char array[]; /* Массив символов соответствующей длины */ int size; { register int i; /* Позиция а массиве */ int с; /* Введенный символ */ i=0; с = getchar(); /* Игнорировать ведущие символы перехода на новую строку */ while (с = ='\n'} с = getchar(); /* Считывать символы, пока не будет обнаружен возврат каретки * while (с := '\n') { /# "Не переполнять массив -- оставить место для нулевого байта */ if (i < (size - 1)) { array[i] = с; i=i+1; } с = getchar(); } array[i] = '\0'; return(i); } /* Содержание модуля EVAL.C */ /* * Этот файл содержит функцию eval, программы вычисления * выражений. */ #include "CALC.H" extern float exs_pop{); /* STACK. С */ extern int ops_рор(), exs_push(); /* STACK.С */ /** * Эта функция выполняет заданную oпeрацию над двумя числами, * которые снимаются с верха стека операндов. **/ eval() { int operator; float left, right, result; /* Извлечь операцию и операнды из стеков */ operator = орs_рор(); right - ехs_рор{); left = exs_pop() ; switch (operator) {• case PLUS; result - left + right; exs_push(result); break; сазе MINUS: result - left - right; exs_push(result); break; case MULT: result = left*right; exs_push(result); break; case DIVIDE: if (right != 0.0) /* Проверить делитель на нуль */ { result = left/right; exs_push(result) ; } else { printf("Ошибка: деление на нуль\n"); } break; } #ifdef TRACE printf("eval: op - %d, left = %f, right = %f, result = %f\n", operator,left,right,result); #end if return; } /* Содержание модуля STACK.С */ /* Этот файл содержит функции для работы со стеком программы * вычисления выражений. */ #include "CALC.H" /** * Этот файл содержит группу функций для выполнения операций над * стеком. Программе потребуется два стека: один для хранения * значений (т.е. операндов) и другой для хранения операций, * которые снимаются с верха стека операндов. * В состав этой группы входят следующие функции: * Стек результатов Стек операций Назначение * exs_init ops_init инициализация стека * exs_push ops_push поместить в стек * ехs_рор орs_рор извлечь из стека * exs_top ops_top посмотреть содержимое * вершины стека **/ '* Определить стеки и индексы стеков */ #define MAX_STACK 20 /* Максимальный размер стека */ '* * Стеки содержат на один элемент больше максимального размера * для удобства проверки пустоты и переполнения. */ static float ex_stack[MAX_STACK+l]; /* Стек результатов (содержит числа) */ static int op_stack[MAX_STACK+l]; /* Стек операций (содержит символы) */ static int ex_index, op_index; /** * Инициализировать стек результатов и указатель стека (индекс). **/ exs_init() { ex_index = 0; ex_stack[0] = EMPTY; /* Присвоить значение EMPTY ("пуст") */ return; 1 /** * Поместить "значение" в стек результатов. Если все нормально, * возвратить SUCCESS, если стек полон, возвратить FAILURE. **/ int exs_push(value) float value; { int result = SUCCESS; if (ex_index < MAX_STACK) { ex_index = ex_index + 1; ex_stack[ex_index] = value; } else { ' result - FAILURE; } return(result); } •/** Извлечь значение, помещенное в стек последним. Возвратить * это значение. Если стек пуст, то возвратить EMPTY. **/ float exs_pop() { float result; result = ex_stack[ex_index]; if (ex_index > 0) ex_index = ex_index - 1; return(result); } /** Возвратить содержимое вершины стека (не изменяя значение * указателя стека). **/ float exs_top() { return(ex_stack[ex_index]); } /** * Инициализировать стек операций и указатель стека (индекс). **/ ops_init() { op_index = 0; op_stack[0] = EMPTY; /* Присвоить EMPTY ("пуст") */ return; } /** Поместить "операцию" в стек операций. Если все нормально, * возвратить SUCCESS, если стек полон, возвратить EMPTY. **/ int ops_push(operator) int operator; { int result = SUCCESS; if (op_index < MAX_STACK) { op_index = op_index + 1; op_stack[op_index] = operator; } else { result = FAILURE; return(result); } /** * Извлечь значение, помещенное в стек последним. Возвратить * это значение. Если стек пуст, то возвратить EMPTY. **/ int ops_pop() { int result; result = op_stack[op_index]; if (op_index > 0) op_index = op_index - 1; return(result); } /** * Возвратить содержимое вершины стека (не изменяя значение * указателя стека). **/ int ops_top() { return(op_stack[op_index]); } Перед проверкой программы откомпилируйте заново измененные модули, а затем загрузите исполняемый файл. ЛИТЕРАТУРА Bohm, С., G. Jacopini, "Flow Diagrams, Turing Machines, and Languages with Only Two Formation Rules", Communications of the ACM, Vol. 9, No. 5 (May 1969), pp. 366-371. Knuth, D., "Structured Programming with go to Statements", in Classics in Software Engineering. New York:Yourdon Press, 1979: pp. 259-321. |