
- •Учебное пособие
- •Введение
- •Объектно-ориентированный подход
- •Объектно-ориентированное программирование Абстрактные типы данных
- •Базовые принципы объектно-ориентированного программирования
- •Простейший ввод и вывод
- •Объект cout
- •Манипуляторы hex и oct
- •Другие манипуляторы
- •Объект cin
- •Операторы для динамического выделения и освобождения памяти (new и delete)
- •Базовые конструкции объектно-ориентированных программ Объекты
- •Понятие класса
- •Конструктор копирования
- •Конструктор explicit
- •Указатели на компоненты класса
- •Встроенные функции (спецификатор inline)
- •Организация внешнего доступа к локальным компонентам класса (спецификатор friend)
- •Вложенные классы
- •Static-члены (данные) класса
- •Указатель this
- •Компоненты-функции static и const
- •Proxi-классы
- •Параметры ссылки
- •Независимые ссылки
- •Практические приемы ограничения числа объектов класса
- •Наследование (производные классы)
- •Конструкторы и деструкторы при наследовании
- •Виртуальные функции
- •Абстрактные классы
- •Виртуальные деструкторы
- •Множественное наследование
- •Виртуальное наследование
- •Перегрузка функций
- •Перегрузка операторов
- •Перегрузка бинарного оператора
- •Перегрузка унарного оператора
- •Дружественная функция operator
- •Перегрузка оператора []
- •Перегрузка оператора ()
- •Перегрузка операторов new и delete
- •Преобразование типа
- •Явные преобразования типов
- •Преобразования типов, определенных в программе
- •Шаблоны Параметризированные классы
- •Передача в шаблон класса дополнительных параметров
- •Шаблоны функций
- •Совместное использование шаблонов и наследования
- •Шаблоны класса и friend
- •Некоторые примеры использования шаблона класса Реализация smart-указателя
- •Классы поддерживающие транзакции
- •Задание значений параметров класса по умолчанию
- •Пространства имен
- •Ключевое слово using как директива
- •Ключевое слово using как объявление
- •Псевдоним пространства имен
- •Организация ввода-вывода
- •Состояние потока
- •Строковые потоки
- •Организация работы с файлами
- •Организация файла последовательного доступа
- •Создание файла произвольного доступа
- •Основные функции классов ios, istream, ostream
- •Основы обработки исключительных ситуаций
- •Перенаправление исключительных ситуаций
- •Исключительная ситуация, генерируемая оператором new
- •Генерация исключений в конструкторах
- •Задание собственной функции завершения
- •Спецификации исключительных ситуаций
- •Задание собственного неожиданного обработчика
- •Иерархия исключений стандартной библиотеки
- •Стандартная библиотека шаблонов (stl) Общее понятие о контейнере
- •Общее понятие об итераторе
- •Категории итераторов
- •Основные итераторы
- •Вспомогательные итераторы
- •Операции с итераторами
- •Контейнерные классы Контейнеры последовательностей
- •Контейнер последовательностей vector
- •Контейнер последовательностей list
- •Контейнер последовательностей deque
- •Ассоциативные контейнеры
- •Ассоциативный контейнер multiset
- •Ассоциативный контейнер set
- •Ассоциативный контейнер multimap
- •Ассоциативный контейнер map
- •Адаптеры контейнеров
- •Адаптеры stack
- •Адаптеры queue
- •Адаптеры priority_queue
- •Пассивные и активные итераторы
- •Алгоритмы
- •Алгоритмы сортировки sort, partial_sort, sort_heap
- •Алгоритмы поиска find, find_if, find_end, binary_search
- •Алгоритмы fill, fill_n, generate и generate_n
- •Алгоритмы equal, mismatch и lexicographical_compare
- •Математические алгоритмы
- •Алгоритмы работы с множествами
- •Алгоритмы swap, iter_swap и swap_ranges
- •Алгоритмы copy, copy_backward, merge, unique и reverse
- •Примеры реализации контейнерных классов Связанные списки
- •Реализация односвязного списка
- •Реализация двусвязного списка
- •Реализация двоичного дерева
- •Литература
- •Вопросы по курсу ооп
- •220013, Минск, п.Бровки, 6.
Вспомогательные итераторы
Вспомогательные итераторы названы так потому, что они выполняют вспомогательные операции по отношению к основным.
Реверсивные итераторы. Некоторые классы-контейнеры спроектированы так, что по хранимым в них элементам данных можно перемещаться в заданном направлении. В одних контейнерах это направление от первого элемента к последнему, а в других - от элемента с самым большим значением к элементу, имеющему наименьшее значение. Однако существует специальный вид итераторов, называемых реверсивными. Такие итераторы работают "с точностью до наоборот", то есть если в контейнере итератор ссылается на первый элемент данных, то реверсивный итератор ссылается на последний. Получить реверсивный итератор для контейнера можно вызовом метода rbegin(), а реверсивное значение "за пределом" возвращается методом rend(). Следующий пример использует нормальный итератор для вывода значений от 1 до 5 и реверсивный итератор для вывода этих же значений, но в обратном порядке:
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
main(void)
{
const int init[] = {1, 2, 3, 4, 5};
vector<int> v(5);
copy(init, init + 5, v.begin());
copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
copy(v.rbegin(), v.rend(), ostream_iterator<int>(cout, " ")); }
Итераторы потоков. Важную роль в STL играют итераторы потоков, которые делятся на итераторы потоков ввода и вывода. Практически во всех рассмотренных примерах имеется итератор потока вывода для отображения данных на экране. Суть применения потоковых итераторов в том, что они превращают любой поток в итератор, используемый точно так же, как и прочие итераторы: перемещаясь по цепочке данных, он считывает значения объектов и присваивает им другие значения.
Итератор потока ввода - это удобный программный интерфейс, обеспечивающий доступ к любому потоку, из которого требуется считать данные. Конструктор итератора имеет единственный параметр - поток ввода. А поскольку итератор потока ввода представляет собой шаблон, то ему передается тип вводимых данных. Вообще-то должно передаваться четыре типа, но последние три имеют значения по умолчанию. Каждый раз, когда требуется ввести очередной элемент информации, используйте оператор ++ точно так же, как с основными итераторами. Считанные данные можно узнать, если применить разыменовывание (*).
Итератор потока вывода весьма схож с итератором потока ввода, но у его конструктора имеется дополнительный параметр, которым указывают строку-разделитель, добавляемую в поток после каждого выведенного элемента. Ниже приведен пример программы, читающей из стандартного потока cin числа, вводимые пользователем и дублирующие их на экране, завершая сообщение строкой - "last entered value". Работа программы заканчивается при вводе числа 999:
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
main(void)
{ istream_iterator<int> is(cin);
ostream_iterator<int> os(cout, " – введенное значение \n");
int input;
while((input = *is) != 999)
{ *os++ = input;
is++ ;
}
}
Потоковые итераторы имеют одно существенное ограничение: в них нельзя возвратиться к предыдущему элементу. Единственный способ сделать это - заново создать итератор потока.
Итераторы вставки. Появление итераторов вставки (insert iterator) было продиктовано необходимостью. Без них просто невозможно добавить значения к цепочке объектов. Так, если в массив чисел, на которые ссылается итератор вывода, вы попытаетесь добавить пару новых значений, то итератор вывода попросту запишет новые значения на место старых и они будут потеряны. Любой итератор вставки: front_inserter, back_inserter или inserter - выполнит ту же операцию вполне корректно. Первый из них добавляет объекты в начало цепочки, второй - в конец. Третий итератор вставляет объекты в заданное место цепочки. При всем удобстве итераторы вставки имеют довольно жесткие ограничения. Так, front_inserter и back_inserter не могут работать с наборами (set) и картами (map), а front_inserter к тому же не умеет добавлять данные в начало векторов (vector). Зато итератор вставки inserter добавляет объекты в любой контейнер. Одной интересной особенностью обладает front_inserter: данные, которые он вставляет в цепочку объектов, должны передаваться ему в обратном порядке.
Рассмотрим пример программы, в которой создается список (list) с двумя значениями, равными нулю. После этого в начало и конец списка добавляются значения 1, 2, 3. Третья последовательность 1-1-1 вставляется в середину списка между нулями. Итак, после описания заголовочных файлов мы создаем массивы, необходимые для работы, и контейнер типа "список" из двух элементов:
#include <algorithm>
#include <iostream>
#include <list>
using namespace std;
main(void)
{
int init[] = {0, 0};
int init1[] = {3, 2, 1};
int init2[] = {1, 2, 3};
int init3[] = {1, 1, 1};
list<int> l(2);
Затем список инициализируется нулями из массива init и его значения отображаются на экране:
copy(init, init + 2, l.begin());
copy(l.begin(), l.end(), ostream_iterator<int>(cout, " "));
cout << " - before front_inserter" << endl;
Итератором вставки в начало списка в обратном порядке добавляются значения массива init1, и производится повторный показ данных из списка на экране:
copy(init1, init1 + 3, front_inserter(l));
copy(l.begin(), l.end(), ostream_iterator<int>(cout, " "));
cout << " - before back_inserter" << endl;
Теперь итератор вставки в конец добавит элементы массива init2 в "хвост" списка:
copy(init2, init2 + 3, back_inserter(l));
copy(l.begin(), l.end(), ostream_iterator<int>(cout, " "));
cout << " - before inserter" << endl;
Сложнее всего обстоит дело с итератором inserter. Для него, кроме ссылки на сам контейнер, нужен итератор, указывающий на тот объект в контейнере, за которым будет произведена вставка элементов массива init3. С этой целью мы создаем переменную типа "итератор", инициализируя ее итератором, указывающим на начало списка:
list<int>::iterator& itr = l.begin();
Теперь специальной операцией advance делаем приращение переменной итератора так, чтобы она указывала на четвертый объект в цепочке данных списка:
advance(itr, 4);
Остается добавить данные в цепочку посредством inserter и отобразить содержимое ”списка” на дисплее:
copy(init3, init3 + 3, inserter(l, itr));
copy(l.begin(), l.end(), ostream_iterator<int>(cout, " "));
cout << " - the end!" << endl;
}
Константный итератор. Последний итератор, который мы рассмотрим, - константный (constant iterator). Он образуется путем модификации основного итератора. Константный итератор не допускает изменения данных, на которые он ссылается. Можно считать константный итератор указателем на константу. Чтобы получить константный итератор, можно воспользоваться типом const_iterator, предопределенным в различных контейнерах. К примеру, так можно описать переменную типа константный итератор на список:
list<int>::const_iterator c_itr;