Выбрать главу

copy(input_iterator_begin, input_iterator_end, insert_iterator);

Данный вызов получает следующий токен слова из потока std::cin с помощью входного итератора и помещает его в контейнер std::set. После этого он инкрементирует оба итератора и проверяет, равен ли входной итератор конечному. Если это не так, то в потоке ввода еще остаются слова, так что все повторяется.

Повторяющиеся слова отбрасываются автоматически. При наличии в множестве конкретного слова повторное его добавление эффекта не возымеет. Этим контейнер std::set отличается от std::multiset, куда можно вставить повторяющиеся записи. 

Реализуем простой ОПН-калькулятор с использованием контейнера std::stack

 Класс std::stack — класс-адаптер, который позволяет помещать в себя объекты, словно в реальную стопку объектов, а также получать их. В текущем разделе на основе этой структуры данных мы создадим калькулятор, применяющий обратную польскую нотацию, ОПН (reverse polish notation, RPN).

ОПН — это нотация, которая может служить для записи математических выражений таким образом, чтобы их было проще анализировать. В ОПН выражение 1 + 2 выглядит как 1 2 +. Сначала идут операнды, а затем — оператор. Еще один пример: выражение (1 + 2) * 3 в ОПН выглядит как 1 2 + 3 *, оно уже показывает, почему подобные выражения проще анализировать, и нам не нужны скобки, чтобы определить подвыражения (рис. 2.4).

Как это делается

В этом примере мы считаем математическое выражение, записанное в формате ОПН, из стандартного потока ввода, а затем передадим его в функцию, которая вычислит его значение. Наконец, выведем на экран численный результат.

1. Мы будем использовать множество вспомогательных объектов из STL, поэтому сначала разместим несколько директив включения:

#include <iostream>

#include <stack>

#include <iterator>

#include <map>

#include <sstream>

#include <cassert>

#include <vector>

#include <stdexcept>

#include <cmath>

2. Мы также объявляем, что используем пространство имен std, чтобы сэкономить немного времени, которое ушло бы на набор текста:

using namespace std;

3. Далее немедленно начнем реализовывать наш анализатор ОПН. Он станет принимать пару итераторов, указывающих на начало и конец математического выражения, передаваемого в качестве строки, которое будет проработано токен за токеном:

template <typename IT>

double evaluate_rpn(IT it, IT end)

{

4. Пока мы итерируем по токенам, нам следует запоминать все операнды до тех пор, пока мы не встретим операцию. Для этого и нужен стек. Все числа будут проанализированы и сохранены с удвоенной точностью, поэтому мы заводим стек элементов типа double:

  stack<double> val_stack;

5. Чтобы удобным образом получить доступ к элементам стека, реализуем вспомогательную функцию. Она изменяет стек, извлекая значение с его вершины, а затем возвращает это значение. Таким образом мы сможем выполнить нашу задачу за один шаг:

  auto pop_stack ([&](){

    auto r (val_stack.top());

    val_stack.pop();

    return r;

  });

6. Еще одним приготовлением будет определение всех поддерживаемых математических операций. Мы сохраним их в ассоциативный массив, где каждый токен операции будет связан с самой операцией. Операции представлены вызываемыми лямбда-выражениями, которые принимают два операнда, а затем, например, складывают или умножают их и возвращают результат:

  map<string, double (*)(double, double)> ops {

    {"+", [](double a, double b) { return a + b; }},

    {"-", [](double a, double b) { return a - b; }},

    {"*", [](double a, double b) { return a * b; }},

    {"/", [](double a, double b) { return a / b; }},

    {"^", [](double a, double b) { return pow(a, b); }},

    {"%", [](double a, double b) { return fmod(a, b); }},

  };

7. Теперь наконец можно проитерировать по входным данным. Предположив, что входные итераторы передали строки, мы передаем данные в новый поток std::stringstream токен за токеном, поскольку он может анализировать числа:

  for (; it != end; ++it) {

    stringstream ss {*it};

8. Теперь, когда у нас есть все токены, попробуем получить на их основе значение типа double. Если эта операция завершается успешно, то у нас появляется операнд, который мы помещаем в стек: