лена их областью действия и временем существования. Автома-
тические переменные являются внутренними по отношению к фун-
кциям; они возникают при входе в функцию и исчезают при вы-
ходе из нее. Внешние переменные, напротив, существуют посто-
янно. Они не появляютя и не исчезают, так что могут сохра-
нять свои значения в период от одного обращения к функции до
другого. В силу этого, если две функции используют некоторые
общие данные, причем ни одна из них не обращается к другой ,
то часто наиболее удобным оказывается хранить эти общие дан-
ные в виде внешних переменных, а не передавать их в функцию
и обратно с помощью аргументов.
Давайте продолжим обсуждение этого вопроса на большом
примере. Задача будет состоять в написании другой программы
для калькулятора, лучшей,чем предыдущая. Здесь допускаются
операции +,-,*,/ и знак = (для выдачи ответа).вместо инфикс-
ного представления калькулятор будет использовать обратную
польскую нотацию,поскольку ее несколько легче реализовать.в
обратной польской нотации знак следует за операндами; инфик-
сное выражение типа
(1-2)*(4+5)=
записывается в виде
12-45+*=
круглые скобки при этом не нужны
· 82 -
Реализация оказывается весьма простой.каждый операнд по-
мещается в стек; когда поступает знак операции,нужное число
операндов (два для бинарных операций) вынимается,к ним при-
меняется операция и результат направляется обратно в
стек.так в приведенном выше примере 1 и 2 помещаются в стек
и затем заменяются их разностью, -1.после этого 4 и 5 вво-
дятся в стек и затем заменяются своей суммой,9.далее числа
· 1 и 9 заменяются в стеке на их произведение,равное -9.опе-рация = печатает верхний элемент стека, не удаляя его (так что промежуточные вычисления могут быть проверены).
Сами операции помещения чисел в стек и их извлечения
очень просты,но, в связи с включением в настоящую программу
обнаружения ошибок и восстановления,они оказываются доста-
точно длинными. Поэтому лучше оформить их в виде отдельных
функций,чем повторять соответствующий текст повсюду в прог-
рамме. Кроме того, нужна отдельная функция для выборки из
ввода следующей операции или операнда. Таким образом, струк-
тура программы имеет вид:
WHILE( поступает операция или операнд, а не конец
IF ( число )
поместить его в стек
еLSE IF ( операция )
вынуть операнды из стека
выполнить операцию
поместить результат в стек
ELSE
ошибка
Основной вопрос, который еще не был обсужден, заключает-
ся в том,где поместить стек, т. Е. Какие процедуры смогут
обращаться к нему непосредственно. Одна из таких возможнос-
тей состоит в помещении стека в MAIN и передачи самого стека
и текущей позиции в стеке функциям, работающим со стеком. Но
функции MAIN нет необходимости иметь дело с переменными, уп-
равляющими стеком; ей естественно рассуждать в терминах по-
мещения чисел в стек и извлечения их оттуда. В силу этого мы
решили сделать стек и связанную с ним информацию внешними
переменными , доступными функциям PUSH (помещение в стек) и
POP (извлечение из стека), но не MAIN.
Перевод этой схемы в программу достаточно прост. Ведущая
программа является по существу большим переключателем по ти-
пу операции или операнду; это, по-видимому, более характер-
ное применеие переключателя, чем то, которое было продемонс-
трировано в главе 3.
#DEFINE MAXOP 20 /* MAX SIZE OF OPERAND, OPERАTOR *
#DEFINE NUMBER '0' /* SIGNAL THAT NUMBER FOUND */
#DEFINE TOOBIG '9' /* SIGNAL THAT STRING IS TOO BIG *
· 83 -
MAIN() /* REVERSE POLISH DESK CALCULATOR */
/(
INT TUPE;
CHAR S[MAXOP];
DOUBLE OP2,ATOF(),POP(),PUSH();
WHILE ((TUPE=GETOP(S,MAXOP)) !=EOF);
SWITCH(TUPE) /(
CASE NUMBER:
PUSH(ATOF(S));
BREAK;
CASE '+':
PUSH(POP()+POP());
BREAK;
CASE '*':
PUSH(POP()*POP());
BREAK;
CASE '-':
OP2=POP();
PUSH(POP()-OP2);
BREAK;
CASE '/':
OP2=POP();
IF (OP2 != 0.0)
PUSH(POP()/OP2);
ELSE
PRINTF(“ZERO DIVISOR POPPED\N”);
BREAK;
CASE '=':
PRINTF(“\T%F\N”,PUSH(POP()));
BREAK;
CASE 'C':
CLEAR();
BREAK;
CASE TOOBIG:
PRINTF(“%.20S ... IS TOO LONG\N”,S)
BREAK;
/)
/)
#DEFINE MAXVAL 100 /* MAXIMUM DEPTH OF VAL STACK */
·
84 -
INT SP = 0; /* STACK POINTER */
DOUBLE VAL[MAXVAL]; /*VALUE STACK */
DOUBLE PUSH(F) /* PUSH F ONTO VALUE STACK */
DOUBLE F;
/(
IF (SP < MAXVAL)
RETURN(VAL[SP++] =F);
ELSE /(
PRINTF(“ERROR: STACK FULL\N”);
CLEAR();
RETURN(0);
/)
/)
DOUBLE POP() /* POP TOP VALUE FROM STEACK */
/(
IF (SP > 0)
RETURN(VAL[--SP]);
ELSE /(
PRINTF(“ERROR: STACK EMPTY\N”);
CLEAR();
RETURN(0);
/)
/)
CLEAR() /* CLEAR STACK */
/(
SP=0;
/)
Команда C очищает стек с помощью функции CLEAR, которая
также используется в случае ошибки функциями PUSH и POP. к
функции GETOP мы очень скоро вернемся.
Как уже говорилось в главе 1, переменная является внеш-
ней, если она определена вне тела какой бы то ни было функ-
ции. Поэтому стек и указатель стека, которые должны исполь-
зоваться функциями PUSH, POP и CLEAR, определены вне этих
трех функций. Но сама функция MAIN не ссылается ни к стеку,
ни к указателю стека - их участие тщательно замаскировано. В
силу этого часть программы, соответствующая операции = , ис-
пользует конструкцию
PUSH(POP());
для того, чтобы проанализировать верхний элемент стека, не
изменяя его.
Отметим также, что так как операции + и * коммутативны,
порядок, в котором объединяются извлеченные операнды, несу-
щественен, но в случае операций - и / необходимо различать
левый и правый операнды.
· 85 -
Упражнение 4-3.
Приведенная основная схема допускает непосредственное
расширение возможностей калькулятора. Включите операцию де-
ления по модулю /%/ и унарный минус. Включите команду “сте-
реть”, которая удаляет верхний элемент стека. Введите коман-
ды для работы с переменными. /Это просто, если имена пере-
менных будут состоять из одной буквы из имеющихся двадцати
шести букв/.
4.5. Правила, определяющие область действия.
Функции и внешние переменные, входящие в состав
“C”-программы, не обязаны компилироваться одновременно;
программа на исходном языке может располагаться в нескольких
файлах, и ранее скомпилированные процедуры могут загружаться
из библиотек. Два вопроса представляют интерес:
Как следует составлять описания, чтобы переменные пра-
вильно воспринимались во время компиляции ?
Как следует составлять описания, чтобы обеспечить пра-
вильную связь частей программы при загрузке ?
4.5.1. Область действия.
Областью действия имени является та часть программы, в
которой это имя определено. Для автоматической переменной,
описанной в начале функции, областью действия является та
функция, в которой описано имя этой переменной, а переменные
из разных функций, имеющие одинаковое имя, считаются не от-
носящимися друг к другу. Это же справедливо и для аргументов
функций.
Область действия внешней переменной простирается от точ-
ки, в которой она объявлена в исходном файле, до конца этого
файла. Например, если VAL, SP, PUSH, POP и CLEAR определены
в одном файле в порядке, указанном выше, а именно:
INT SP = 0;
DOUBLE VAL[MAXVAL];
DOUBLE PUSH(F) {...}
DOUBLE POP() {...}
CLEAR() {...}
то переменные VAL и SP можно использовать в PUSH, POP и
CLEAR прямо по имени; никакие дополнительные описания не
нужны.
С другой стороны, если нужно сослаться на внешнюю пере-
менную до ее определения, или если такая переменная опреде-
лена в файле, отличном от того, в котором она используется,
то необходимо описание EXTERN.
· 86 -
Важно различать описание внешней переменной и ее опреде-
ление. описание указывает свойства переменной /ее тип, раз-
мер и т.д./; определение же вызывает еще и отведение памяти.
Если вне какой бы то ни было функции появляются строчки
INT SP;
DOUBLE VAL[MAXVAL];
то они определяют внешние переменные SP и VAL, вызывают от-
ведение памяти для них и служат в качестве описания для ос-
тальной части этого исходного файла. В то же время строчки
EXTERN INT SP;
EXTERN DOUBLE VAL[];
описывают в остальной части этого исходного файла переменную
SP как INT, а VAL как массив типа DOUBLE /размер которого
указан в другом месте/, но не создают переменных и не отво-
дят им места в памяти.
Во всех файлах, составляющих исходную программу, должно
содержаться только одно определение внешней переменной; дру-
гие файлы могут содержать описания EXTERN для доступа к ней.
/Описание EXTERN может иметься и в том файле, где находится
определение/. Любая инициализация внешней переменной прово-
дится только в определении. В определении должны указываться
размеры массивов, а в описании EXTERN этого можно не делать.
Хотя подобная организация приведенной выше программы и
маловероятна, но VAL и SP могли бы быть определены и инициа-
лизированы в одном файле, а функция PUSH, POP и CLEAR опре-
делены в другом. В этом случае для связи были бы необходимы
следующие определения и описания:
в файле 1:
INT SP = 0; /* STACK POINTER */
DOUBLE VAL[MAXVAL]; /* VALUE STACK */
в файле 2:
EXTERN INT SP;
EXTERN DOUBLE VAL[];
DOUBLE PUSH(F) {...}
DOUBLE POP() {...}
CLEAR() {...}
так как описания EXTERN 'в файле 1' находятся выше и вне
трех указанных функций, они относятся ко всем ним; одного
набора описаний достаточно для всего 'файла 2'.
· 87 -
Для программ большого размера обсуждаемая позже в этой
главе возможность включения файлов, #INCLUDE, позволяет
иметь во всей программе только одну копию описаний EXTERN и
вставлять ее в каждый исходный файл во время его компиляции.
Обратимся теперь к функции GETOP, выбирающей из файла
ввода следующую операцию или операнд. Основная задача прос-
та: пропустить пробелы, знаки табуляции и новые строки. Если
следующий символ отличен от цифры и десятичной точки, то
возвратить его. В противном случае собрать строку цифр /она
может включать десятичную точку/ и возвратить NUMBER как
сигнал о том, что выбрано число.
Процедура существенно усложняется, если стремиться пра-