Шаг 107.
Библиотека STL.
Модифицирующие алгоритмы. "Удаление" элементов (окончание)

    На этом шаге мы изменим приложение предыдущего шага.

    Алгоритм remove() возвращает итератор, указывающий на новый конец коллекции. Это значение позволяет прочитать полученный интервал, уменьшить размер коллекции или узнать количество удаленных элементов. Рассмотрим слегка измененную версию примера из предыдущего шага:

//---------------------------------------------------------------------------

#include <vcl.h>
#include <iostream>
#include <iterator>
#include <list>
#include <string>
#include <algorithm>
#include <conio.h> //необходимо для getch()
#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
using namespace std;
std::string ToRus(const std::string &in)
{
  char *buff = new char [in.length()+1];
  CharToOem(in.c_str(),buff);
  std::string out(buff);
  delete [] buff;
  return out;
}

int main(int argc, char* argv[])
{
  list<int> coll;
  // Вставка элементов со значениями от 6 до 1 и от 1 до 6
  for (int i=1; i<=6; ++i) {
    coll.push_front(i);
    coll.push_back(i);
  }
  // Вывод всех элементов коллекции
  cout << ToRus("Список чисел:\n");
  copy (coll.begin(), coll.end(), // Источник
    ostream_iterator<int>(cout," ")); // Приемник

  cout << endl;

  // Удаление всех элементов со значением 3
  // - сохранение нового конца коллекции
  list<int>::iterator end =  remove (coll.begin(), coll.end(),   // Интервал
                                      3);  // Значение

    // Вывод всех элементов коллекции
  cout << ToRus("Список чисел:\n");
  copy (coll.begin(), end, // Источник
    ostream_iterator<int>(cout," ")); // Приемник

  cout << endl;

  // Вывод количества "удаленных" элементов
  cout << ToRus("Количество удаленных элементов: ")
    << distance(end,coll.end()) << endl;

  // Стирание "удаленных" элементов
  coll.erase(end,coll.end());

  // Вывод всех элементов модифицированной коллекции
  cout << ToRus("Измененный список чисел:\n");
  copy (coll.begin(), coll.end(), // Источник
    ostream_iterator<int>(cout," ")); // Приемник

  cout << endl;

  getch();
  return 0;
}
//---------------------------------------------------------------------------
Текст этого примера можно взять здесь.

    В новой версии программы значение, возвращаемое алгоритмом remove(), присваивается итератору end:

  list<int>::iterator end =  remove (coll.begin(), coll.end(),   // Интервал
                                      3);  // Значение

    Итератор end определяет "логический" конец модифицированной коллекции после "удаления" элементов. Полученное значение определяет конечную границу интервала при последующих операциях:

  copy (coll.begin(), end, // Источник
    ostream_iterator<int>(cout," ")); // Приемник

    Другой способ основан на обработке количества "удаленных" элементов, вычисляемого как расстояние между "логическим" и "физическим" концами коллекции:

  cout << ToRus("Количество удаленных элементов: ")
    << distance(end,coll.end()) << endl;

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

    Чтобы "удаленные" элементы были действительно удалены из коллекции, следует вызвать соответствующую функцию контейнера. Для таких целей контейнеры поддерживают функцию erase(), которая удаляет все элементы из интервала, определяемого переданными аргументами:

  coll.erase(end,coll.end());


Рис.1. Результат работы приложения

    Если вы предпочитаете удалять элементы одной командой, воспользуйтесь следующей конструкцией:

  coll.erase (remove(coll.begin(),coll.end(),
    3), coll.end());

    Почему сами алгоритмы не вызывают функцию erase()? Ответ на этот вопрос снова напоминает о том, что гибкость STL не дается даром. STL отделяет структуры данных от алгоритмов, используя итераторы в качестве интерфейсов. Но как отмечалось выше, итератор является абстракцией для представления позиции в контейнере. В общем случае итератор просто не знает свой контейнер. Таким образом, алгоритм, работающий с элементами контейнера через итератор, не может вызывать функции контейнера.

    Подобная архитектура имеет важные последствия, поскольку благодаря ей алгоритмы работают с абстрактными интервалами, а не только со "всеми элементами контейнера". Например, интервал может состоять из подмножества элементов коллекции. Более того, некоторые контейнеры (например, обычные массивы) не поддерживают функцию erase(). Следовательно, стремление сохранить максимальную гибкость алгоритмов не позволяет требовать, чтобы итераторы располагали информацией о своих контейнерах.

    Наконец, необходимость в уничтожении "удаленных" элементов возникает не так уж часто. Часто проще использовать новый "логический" конец контейнера вместо "физического", в частности, при последующем вызове всех алгоритмов.

    На следующем шаге мы рассмотрим модифицирующие алгоритмы и ассоциативные контейнеры.




Предыдущий шаг Содержание Следующий шаг