|
Функции и структура программ
Давайте продолжим обсуждение этого вопроса на большом примере. Задача будет состоять в написании другой программы для калькулятора, лучшей, чем предыдущая. Здесь допускаются операции +, -, *, / и знак = (для выдачи ответа). Вместо инфиксного представления калькулятор будет использовать обратную польскую нотацию, поскольку ее несколько легче реализовать. В обратной польской нотации знак следует за операндами; инфиксное выражение типа
(1-2)*(4+5)=
записывается в виде
12-45+*=
Круглые скобки при этом не нужны
Реализация оказывается весьма простой. Каждый операнд помещается в стек ; когда поступает знак операции, нужное число операндов (два для бинарных операций) вынимается, к ним применяется операция и результат направляется обратно в стек. Так в приведенном выше примере 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, operator */ #define number '0' /* signal that number found */ #define toobig '9' /* signal that string is too big */ 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 */ 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());
для того, чтобы проанализировать верхний элемент стека, не изменяя его.
Отметим также, что так как операции + и * коммутативны, порядок, в котором объединяются извлеченные операнды, несущественен, но в случае операций - и / необходимо различать левый и правый операнды.
Упражнение 4-3
Приведенная основная схема допускает непосредственное расширение возможностей калькулятора. Включите операцию деления по модулю /%/ и унарный минус. Включите команду "стереть", которая удаляет верхний элемент стека. Введите команды для работы с переменными. /Это просто, если имена переменных будут состоять из одной буквы из имеющихся двадцати шести букв/.