Опубликован: 16.09.2005 | Уровень: для всех | Доступ: платный | ВУЗ: Московский государственный университет имени М.В.Ломоносова
Лекция 11:

Структуры данных: общее понятие, реализация. Простейшие структуры данных: очередь, стек. Использование стека и обратная польская запись

Реализация стекового калькулятора на Си

Рассмотрим небольшой проект, реализующий стековый калькулятор на Си. Такая программа весьма полезна, поскольку позволяет проводить вычисления, не прибегая к записи промежуточных результатов на бумаге.

Программа состоит из трех файлов: "streal.h", "streal.cpp" и "stcalc.cpp". Первые два файла реализуют стек вещественных чисел, эта реализация уже рассматривалась ранее. Файл "stcalc.cpp" реализует стековый калькулятор на базе стека. Для сборки программы следует объединить все три файла в один проект. Команды построения программы зависят от операционной системы и компилятора, например, в системе Unix с компилятором "gcc" программу можно собрать с помощью команды

g++ -o stcalc -lm stcalc.cpp streal.cpp

в результате которой создается файл "stcalc" с программой, готовой к выполнению.

Ниже приведено содержимое файла "stcalc.cpp". Функция main, описанная в этом файле, организует диалог с пользователем в режиме команда-ответ. Пользователь может ввести число с клавиатуры, это число просто добавляется в стек. При вводе одного из четырех знаков арифметических операций +, -, *, / программа извлекает из стека два числа, выполняет указанное арифметическое действие над ними и помещает результат обратно в стек. Значение результата отображается также на дисплее. Кроме арифметических операций, пользователь может ввести название одной из стандартных функций: sin, cos, exp, log (натуральный логарифм). При этом программа извлекает из стека аргумент функции, вычисляет значение функции и помещает его обратно в стек. При желании список стандартных функций и возможных операций можно расширить. Наконец, можно выполнять еще несколько команд:

pop удалить вершину стека;
clear очистить стек;
= напечатать вершину стека;
show напечатать содержимое стека;
help напечатать подсказку;
quit завершить работу программы.

Каждая команда стекового калькулятора реализуется с помощью отдельной функции. Например, вычитание реализуется с помощью функции onSub():

static void onSub() {
    double y, x;
    if (st_size() < 2) {
        printf("Stack depth < 2.\n");
        return;
    }
    y = st_pop();
    x = st_pop();
    st_push(x - y);
    display();
}

В начале функции проверяется, что глубина стека не меньше двух. В противном случае, выдается сообщение об ошибке, и функция завершается. Далее из стека извлекаются операнды y и x операции вычитания. Элементы извлекаются из стека в порядке, обратном их помещению в стек, поэтому y извлекается раньше, чем x. Затем вычисляется разность x-y, ее значение помещается обратно в стек и печатается на дисплее, для печати вершины стека вызывается функция display.

Приведем полный текст программы.

// Файл "stcalc.cpp"
// Реализация стекового калькулятора на базе стека
//
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include "streal.h" // Интерфейс исполнителя "стек"

// Прототипы функций, реализующих команды калькулятора:
// Арифметические операции
static void onAdd();
static void onSub();
static void onMul();
static void onDiv();

// Добавить число в стек(вх: текстовая запись числа)
static void onPush(const char* line);

// Вычисление математических функций
static void onSin();     // sin
static void onCos();     // cos
static void onExp();     // Экспонента
static void onLog();     // Натуральный логарифм
static void onSqrt();    // Квадратный корень

// Другие команды
static void onPop();     // Удалить вершину стека
static void onClear();   // Очистить стек
static void display();   // Напечатать вершину стека
static void onShow();    // Напечатать содержимое стека
static void printHelp(); // Напечатать подсказку

int main() {
    char line[256]; // Буфер для ввода строки
    int linelen;    // Длина строки

    st_init(1024);  // Стек.начать работу(1024)
                    //   1024 — макс. глубина стека
    printHelp();    // Напечатать подсказку

    while (true) {          // Цикл до бесконечности
        scanf("%s", line);      // ввести строку
        linelen = strlen(line); // длина строки
        if (linelen == 0)
            continue;

        // Разобрать команду и вызвать реализующую
        // ее функцию
        if (strcmp(line, "+") == 0) {
            onAdd();
        } else if (strcmp(line, "-") == 0) {
            onSub();
        } else if (strcmp(line, "*") == 0) {
            onMul();
        } else if (strcmp(line, "/") == 0) {
            onDiv();
        } else if (strcmp(line, "sin") == 0) {
            onSin();
        } else if (strcmp(line, "cos") == 0) {
            onCos();
        } else if (strcmp(line, "exp") == 0) {
            onExp();
        } else if (strcmp(line, "log") == 0) {
            onLog();
        } else if (strcmp(line, "sqrt") == 0) {
            onSqrt();
        } else if (strcmp(line, "=") == 0) {
            display();
        } else if (         // Если это число
            isdigit(line[0]) || (
                linelen > 1 &&
                (line[0] == '-' || line[0] == '+') &&
                isdigit(line[1])
            )
        ) {
            onPush(line);   // Добавить число в стек
        } else if (strcmp(line, "pop") == 0) {
            onPop();
        } else if (strcmp(line, "clear") == 0) {
            onClear();
        } else if (strcmp(line, "show") == 0) {
            onShow();
        } else if (strcmp(line, "quit") == 0) {
            break;       // Завершить работу
        } else {         // Неправильная команда =>
            printHelp(); //     напечатать подсказку
        }
    }
    return 0;
}

static void onAdd() {
    double y, x;
    if (st_size() < 2) {
        printf("Stack depth < 2.\n");
        return;
    }
    y = st_pop();
    x = st_pop();
    st_push(x + y);
    display();
}

static void onSub() {
    double y, x;
    if (st_size() < 2) {
        printf("Stack depth < 2.\n");
        return;
    }
    y = st_pop();
    x = st_pop();
    st_push(x - y);
    display();
}

static void onMul() {
    double y, x;
    if (st_size() < 2) {
        printf("Stack depth < 2.\n");
        return;
    }
    y = st_pop();
    x = st_pop();
    st_push(x * y);
    display();
}

static void onDiv() {
    double y, x;
    if (st_size() < 2) {
        printf("Stack depth < 2.\n");
        return;
    }
    y = st_pop();
    x = st_pop();
    st_push(x / y);
    display();
}

static void onPush(const char* line) {
    double x = atof(line);
    st_push(x);
}

static void onSin() {
    double x;
    if (st_empty()) {
        printf("Stack empty.\n");
        return;
    }
    x = st_pop();
    st_push(sin(x));
    display();
}

static void onCos() {
    double x;
    if (st_empty()) {
        printf("Stack empty.\n");
        return;
    }
    x = st_pop();
    st_push(cos(x));
    display();
}

static void onExp() {
    double x;
    if (st_empty()) {
        printf("Stack empty.\n");
        return;
    }
    x = st_pop();
    st_push(exp(x));
    display();
}

static void onLog() {
    double x;
    if (st_empty()) {
        printf("Stack empty.\n");
        return;
    }
    x = st_pop();
    st_push(log(x));
    display();
}

static void onSqrt() {
    double x;
    if (st_empty()) {
        printf("Stack empty.\n");
        return;
    }
    if (st_top() < 0.0) {
        printf("Arg. of square root is negative.\n");
        return;
    }
    x = st_pop();
    st_push(sqrt(x));
    display();
}

static void onPop() {
    st_pop();
}

static void onClear() {
    st_clear();
}

static void display() {
    if (!st_empty()) {
        printf("=%lf\n", st_top());
    } else {
        printf("stack empty\n");
    }
}

static void onShow() {
    int d = st_size();
    printf("Depth of stack = %d.", d);
    if (d > 0)
        printf(" Stack elements:\n");
    else
        printf("\n");

    for (int i = 0; i < d; i++) {
        printf("  %lf\n", st_elementAt(i));
    }
}

static void printHelp() {
    printf(
        "Stack Calculator commands:\n"
        "    <number>    Push а number in stack\n"
        "    +, -, *, /  Ariphmetic operations\n"
        "    sin, cos,   Calculate a function\n"
        "    exp, log,   \n"
        "    sqrt        \n"
        "    =           Display the stack top\n"
        "    pop         Remove the stack top\n"
        "    show        Show the stack\n"
        "    clear       Clear the stack\n"
        "    quit        Terminate the program\n"
    );
}
// Конец файла "stcalc.cpp"

Пример работы программы "stcalc". Пусть нужно вычислить выражение (3*3 + 4*4)1/2

Запишем выражение, используя обратную польскую запись:

3, 3, *, 4, 4, *, +, sqrt

(через sqrt обозначается операция извлечения квадратного корня). Последовательно отдаем соответствующие команды стековому калькулятору. При работе программы stcalc получается следующий диалог:

Stack Calculator commands:
    <number>    Push а number in stack
    +, -, *, /  Ariphmetic operations
    sin, cos,   Calculate a function
    exp, log,
    sqrt
    =           Display the stack top
    pop         Remove the stack top
    show        Show the stack
    clear       Clear the stack
    quit        Terminate the program
3 3 *
=9.000000
4 4 *
=16.000000
+
=25.000000
sqrt
=5.000000

Обратная польская запись формул оказалась исключительно удобной при работе с компьютерами. Для вычислений используется стек, что позволяет работать с выражениями любой степени сложности. Реализация стекового вычислителя не представляет никакого труда. Имеется также простой алгоритм преобразования выражения из обычной записи, в которой знак операции указывается между аргументами, в ее обратную польскую запись. Все это привело к тому, что многие компиляторы языков высокого уровня используют обратную польскую запись в качестве внутренней формы представления программы. Рассмотрим, к примеру, язык программирования Java. Как всякий объектно-ориентированный язык, он является интерпретируемым, а не компилируемым языком. Это означает, что компилятор Java преобразует исходную Java-программу не в машинные коды, а в промежуточный язык, предназначенный для выполнения (интерпретации) на специальной Java-машине. В случае Java этот промежуточный язык называют байткодом. Компилятор Java помещает байткод в файл с расширением ".class". Байткод представляет собой, упрощенно говоря, обратную польскую запись Java-прогаммы, а Java-машина — стековый вычислитель.

Кирилл Юлаев
Кирилл Юлаев
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Анатолий Федоров
Анатолий Федоров
Россия, Москва, Московский государственный университет им. М. В. Ломоносова, 1989