Смекни!
smekni.com

лена их областью действия и временем существования. Автома-

тические переменные являются внутренними по отношению к фун-

кциям; они возникают при входе в функцию и исчезают при вы-

ходе из нее. Внешние переменные, напротив, существуют посто-

янно. Они не появляютя и не исчезают, так что могут сохра-

нять свои значения в период от одного обращения к функции до

другого. В силу этого, если две функции используют некоторые

общие данные, причем ни одна из них не обращается к другой ,

то часто наиболее удобным оказывается хранить эти общие дан-

ные в виде внешних переменных, а не передавать их в функцию

и обратно с помощью аргументов.

Давайте продолжим обсуждение этого вопроса на большом

примере. Задача будет состоять в написании другой программы

для калькулятора, лучшей,чем предыдущая. Здесь допускаются

операции +,-,*,/ и знак = (для выдачи ответа).вместо инфикс-

ного представления калькулятор будет использовать обратную

польскую нотацию,поскольку ее несколько легче реализовать.в

обратной польской нотации знак следует за операндами; инфик-

сное выражение типа

(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&bsol;N”);

CLEAR();

RETURN(0);

/)

/)

DOUBLE POP() /* POP TOP VALUE FROM STEACK */

/(

IF (SP > 0)

RETURN(VAL[--SP]);

ELSE /(

PRINTF(“ERROR: STACK EMPTY&bsol;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 как

сигнал о том, что выбрано число.

Процедура существенно усложняется, если стремиться пра-