- •Ооп: Лекция 7. Стандартная библиотека шаблонов ч.1
- •Адаптеры stl-контейнеров
- •Категории итераторов
- •Итераторы stl-контейнеров
- •Реверс-итератор можно преобразовать в обычный итератор через метод base().
- •Примеры алгоритмов и итераторов
- •Недействительные итераторы
- •При удалении все элементы, стоящие правее, сдвигаются на одну ячейку влево. При этом, итератор, по-прежнему указывает на ту же самую позицию:
- •Затем, итератор инкрементируется, указывая уже на следующую позицию:
- •При работе с другими контейнерами также следует соблюдать осторожность, и задумываться о ситуациях, при которых могут появляться недействительные итераторы:
- •Алгоритмы стандартной библиотеки
- •Пример использования изученных элементов stl
- •Полные примеры из лекции
Примеры алгоритмов и итераторов
Представим алгоритм копирования последовательности. Такой алгоритм должен принимать интервал входных итераторов копируемой последовательности, и выходной итератор для результирующей последовательности.
template< typename _InputIt, typename _OutputIt >
OutputIt MyCopy ( InputIt _first, InputIt _last, OutputIt _outFirst )
{
InputIt current = _first;
OutputIt outputCurrent = _outFirst;
while ( current != _last )
{
* outputCurrent = * inputCurrent;
++ inputCurrent;
++ outputCurrent;
}
return _outputCurrent;
}
Отметим, что такой подход не вставляет новых элементов в целевую последовательность, а лишь перезаписывает существующие. Необходимо заранее позаботиться о соответствии между длиной копируемой последовательности и размером целевого контейнера:
int data[ 5 ] = { 1, 2, 3, 4, 5 };
std::list< int > myList( 5 ); // создаем список из 5 нулевых элементов
MyCopy( data, data + 5, myList.begin() ); // перезаписывает нулевые значения
Если же требуется обеспечить вставку новых элементов, т.е. дописывание в конец контейнера, имеет смысл реализация специального вида Output-итератора, автоматически вставляющего элемент. Подобное средство std::back_insert_iterator определено в стандартной библиотеке приблизительно следующим образом:
// Специальный итератор вставки:
// _Container - тип контейнера
template< typename _Container >
class back_insert_iterator
{
// Вспомогательный синоним, обозначающий такой же тип
typedef back_insert_iterator< _Container > Self;
// Вспомогательный синоним, обозначающий тип данных в контейнере
typedef typename _Container::value_type ValueType;
// Ссылка на целевой контейнер с правом на запись
Container & m_container;
public:
// Конструктор: принимает ссылку на целевой контейнер с правом на запись
explicit back_insert_iterator ( Container & _c )
: m_container( _c )
{}
// Оператор сравнения на равенство
bool operator == ( const Self & _it ) const
{
return & m_container == & _it.m_container ;
}
// Оператор сравнения на неравенство
bool operator != ( const Self & _it ) const
{
return !( * this == _it );
}
// Оператор разыменования с правом на запись -
// возвращает ссылку на себя, т.к. итератор можно присваивать
Self & operator * ()
{
return * this;
}
// Оператор копирующего присвоения значения - вставляет элемент в контейнер
Self & operator = ( const ValueType & _v )
{
m_container.push_back( _v );
return * this;
}
// Оператор префиксного инкремента (вызов игнонируется)
Self & operator ++ ()
{
return * this;
}
// Оператор постфиксного инкремента (вызов игнонируется)
Self operator ++ ( int )
{
return * this;
}
};
// Вспомогательная функция для создания back_insert_iterator без указания типов
template< typename _T, typename _Container >
back_insert_iterator< _T, _Container > back_inserter ( _Container & _c )
{
return back_insert_iterator< _T, _Container >*( _c );
}
// Копирование в изначально пустой список со вставкой в конец
int data[ 5 ] = { 1, 2, 3, 4, 5 };
std::list< int > myList;
MyCopy( data, data + 5, back_inserter( _c ) );
Реализация использует синоним типа value_type, который определен в каждом STL-контейнере. Декларирование синонима повышает читабельность кода при использовании в методах:
// Вспомогательный синоним, обозначающий тип данных в контейнере
typedef typename _Container::value_type ValueType;
Аналогичным образом определяются output-итераторы, вставляющие значения в начало последовательности при помощи метода push_front (std::front_inserter) и в произвольную позицию в контейнере, указываемую при создании итератора (std::inserter).
Форвардные итераторы потребуются для алгоритма, который заменяет некоторое искомое значение в последовательности на новое. В таком алгоритме требуется одновременно и читать, и модифицировать последовательность:
template< typename ForwardIt, typename T >
void MyReplace (
ForwardIt _first
, ForwardIt _last
, const T & _oldValue
, const T & _newValue
)
{
ForwardIt current = _first;
while ( current != _last )
{
if ( * current == _ oldValue )
* current = _newValue;
++ current;
}
}
Примером необходимости в двунаправленных итераторах является алгоритм, зеркально переворачивающий порядок значений в последовательности:
template< typename _BidIt >
void MyReverse ( _BidIt _first, _BidIt _last )
{
while ( _first != _last && _first != -- _last )
{
std::swap( * _first, * _last );
++ _first;
}
}
Наконец, ярким примером алгоритмов, ожидающих итераторов произвольного доступа, является семейство алгоритмов сортировки:
template< typename _RanIt >
void MyBubbleSort ( _RanIt _first, _RanIt _last )
{
int nElements = _last - _first;
for ( int i = 0; i < nElements - 1; i++ )
for ( int j = nElements - 1; j > i; j-- )
if ( *( _first + j - 1 ) > *( _first + j ) )
std::swap( * ( _first + j - 1 ), * ( _first + j ) );
}
