Опубликован: 05.07.2006 | Уровень: для всех | Доступ: платный
Лекция 5:

Функции и структура программ

Давайте продолжим обсуждение этого вопроса на большом примере. Задача будет состоять в написании другой программы для калькулятора, лучшей, чем предыдущая. Здесь допускаются операции +, -, *, / и знак = (для выдачи ответа). Вместо инфиксного представления калькулятор будет использовать обратную польскую нотацию, поскольку ее несколько легче реализовать. В обратной польской нотации знак следует за операндами; инфиксное выражение типа

(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

Приведенная основная схема допускает непосредственное расширение возможностей калькулятора. Включите операцию деления по модулю /%/ и унарный минус. Включите команду "стереть", которая удаляет верхний элемент стека. Введите команды для работы с переменными. /Это просто, если имена переменных будут состоять из одной буквы из имеющихся двадцати шести букв/.

Ярослав Воробей
Ярослав Воробей
Россия
Дмитрий Левин
Дмитрий Левин
Россия