Структуры данных: общее понятие, реализация. Простейшие структуры данных: очередь, стек. Использование стека и обратная польская запись
Реализация стекового калькулятора на Си
Рассмотрим небольшой проект, реализующий стековый калькулятор на Си. Такая программа весьма полезна, поскольку позволяет проводить вычисления, не прибегая к записи промежуточных результатов на бумаге.
Программа состоит из трех файлов: "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-машина — стековый вычислитель.