cout << "Invalid operator: " << e.what() << '\n';
}
}
15. После компиляции программы можно с ней поэкспериментировать. Входные данные "3 1 2 + * 2 /"
(3 * (1 + 2) ) / 2
, которое приложение преобразует к корректному результату:$ echo "3 1 2 + * 2 /" | ./rpn_calculator
4.5
Как это работает
Весь пример строится на помещении операндов в стек до тех пор, пока мы не найдем во входных данных операцию. В этой ситуации мы выталкиваем два последних операнда из стека, применяем к ним операцию и помещаем результат обратно в стек. Чтобы понять весь код примера, важно разобраться, как мы различаем
Работа со стеком
Мы помещаем элементы в стек с помощью функции push
std::stack
:val_stack.push(val);
Выталкивание элемента из стека выглядит чуть сложнее, поскольку нам пришлось реализовывать для этого лямбда-выражение, которое принимает ссылку на объект val_stack
auto pop_stack ([&](){
auto r (val_stack.top()); // Получаем копию верхнего значения
val_stack.pop(); // Удаляем верхнее значение
return r; // Возвращаем копию
}
);
Это лямбда-выражение необходимо для получения верхнего значения стека
std::stack
не позволяет делать это с помощью double top_value {pop_stack()};
Различаем в пользовательском вводе операнды и операторы
В основном цикле функции evaluate_rpn
double
, то данное число тоже операнд. Все остальные токены, которые нельзя легко преобразовать в число (например, "+
"), мы считаем Скелет кода для выполнения именно этой задачи выглядит следующим образом:
stringstream ss {*it};
if (double val; ss >> val) {
// Это число!
} else {
// Это что-то другое. Это операция!
}
Оператор потока >>
std::stringstream
. Затем используем способность объекта класса stringstream
преобразовать объект типа std::string
в переменную типа double
, что включает в себя разбор. Если он Выбираем и применяем нужную математическую операцию
Разобравшись, что текущий токен, полученный от пользователя, не является числом, мы предполагаем, что это операция наподобие +
*
. Затем обращаемся к ассоциативному массиву ops с целью найти требуемую операцию и получить функцию, принимающую два операнда и возвращающую сумму, произведение или другой подходящий результат.Сам тип такого массива выглядит относительно сложно:
map
Он соотносит строки и значения типа double (*)(double, double)
double
и возвращает одно». Представьте, будто часть (*
) представляет собой имя функции, как, например, double sum(double, double)
, что гораздо проще прочитать. Идея заключается в следующем: наше лямбда-выражение [](double, double) { return /* какое-то число типа double */ }
можно преобразовать в указатель на функцию, фактически соответствующий описанию этого указателя. Лямбда-выражения, которые Таким образом, это удобный способ запросить у ассоциативного массива корректную операцию:
const auto & op (ops.at(*it));
const double result {op(l, r)};