- •Содержание
- •ВВЕДЕНИЕ
- •1.ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ПОДХОД
- •2. ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ
- •2.1. Абстрактные типы данных
- •2.2. Базовые принципы объектно-ориентированного программирования
- •2.3. Основные достоинства языка С++
- •2.4. Особенности языка С++
- •2.4.1. Ключевые слова
- •2.4.2. Константы и переменные
- •2.4.3. Операции
- •2.4.4. Типы данных
- •2.4.5. Передача аргументов функции по умолчанию
- •2.5.1. Объект cin
- •2.5.2. Объект cout
- •2.5.3. Манипуляторы
- •3.1. Объекты
- •3.2. Понятие класса
- •3.3. Конструктор копирования
- •3.4. Конструктор explicit
- •3.5. Указатель this
- •3.6. Встроенные функции (спецификатор inline)
- •3.7. Организация внешнего доступа к локальным компонентам класса (спецификатор friend)
- •3.8. Вложенные классы
- •3.9. Static-члены (данные) класса
- •3.10. Компоненты-функции static и const
- •3.11. Proxi-классы
- •3.12. Ссылки
- •3.12.1. Параметры ссылки
- •3.12.2. Независимые ссылки
- •3.13. Пространства имен
- •3.13.3. Ключевое слово using как объявление
- •3.13.4. Псевдоним пространства имен
- •3.14. Практические приемы ограничения числа объектов класса
- •4. НАСЛЕДОВАНИЕ
- •4.1.1. Конструкторы и деструкторы при наследовании
- •4.2. Виртуальные функции
- •4.3. Абстрактные классы
- •4.4. Виртуальные деструкторы
- •4.6. Виртуальное наследование
- •5.2. Перегрузка операторов
- •5.2.2. Перегрузка унарного оператора
- •5.2.3. Дружественная функция operator
- •5.2.4. Особенности перегрузки операции =
- •5.2.5. Перегрузка оператора []
- •5.2.6. Перегрузка оператора ()
- •5.2.7. Перегрузка оператора ->
- •5.2.8. Перегрузка операторов new и delete
- •5.3. Преобразование типа
- •5.3.1. Явные преобразования типов
- •6. ШАБЛОНЫ
- •6.1. Параметризированные классы
- •6.2. Передача в шаблон класса дополнительных параметров
- •6.3. Шаблоны функций
- •6.4. Совместное использование шаблонов и наследования
- •6.5. Шаблоны класса и friend-функции
- •6.6. Некоторые примеры использования шаблона класса
- •6.6.1. Реализация smart-указателя
- •6.6.2. Задание значений параметров класса по умолчанию
- •7.2. Состояние потока
- •7.3. Строковые потоки
- •7.4. Организация работы с файлами
- •7.5. Организация файла последовательного доступа
- •7.6. Создание файла произвольного доступа
- •7.7. Основные функции классов ios, istream, ostream
- •8. ИСКЛЮЧЕНИЯ В С++
- •8.2. Перенаправление исключительных ситуаций
- •8.3. Исключительная ситуация, генерируемая оператором new
- •8.6. Спецификации исключительных ситуаций
- •8.7. Задание собственного неожиданного обработчика
- •9. СТАНДАРТНАЯ БИБЛИОТЕКА ШАБЛОНОВ (STL)
- •9.3. Категории итераторов
- •9.4. Операции с итераторами
- •9.5. Контейнеры последовательностей
- •9.5.2. Контейнер последовательностей list
- •9.5.3. Контейнер последовательностей deque
- •9.6. Ассоциативные контейнеры
- •9.6.1. Ассоциативный контейнер multiset
- •9.6.2. Ассоциативный контейнер set
- •9.6.3. Ассоциативный контейнер multimap
- •9.7.1. Адаптер stack
- •9.7.2. Адаптер queue
- •9.7.3. Адаптер priority_queue
- •9.8. Алгоритмы
- •9.8.1. Алгоритмы сортировки sort, partial_sort, sort_heap
- •9.8.2. Алгоритмы поиска find, find_if, find_end, binary_search
- •9.8.3. Алгоритмы fill, fill_n, generate и generate_n
- •9.8.4. Алгоритмы equal, mismatch и lexicographical_compare
- •9.8.6. Алгоритмы работы с множествами
- •9.8.7. Алгоритмы swap, iter_swap и swap_ranges
- •9.8.8. Алгоритмы copy, copy_backward, merge, unique и reverse
- •10. ПРИМЕРЫ РЕАЛИЗАЦИИ КОНТЕЙНЕРНЫХ КЛАССОВ
- •10.1. Связанные списки
- •10.1.1. Реализация односвязного списка
- •10.2. Реализация бинарного дерева
- •11. ПРОГРАММИРОВАНИЕ ДЛЯ WINDOWS
- •11.1. Система, управляемая сообщениями
- •11.2. Управление графическим выводом
- •11.3. Контекст устройства
- •11.3.1. Экран
- •11.3.2. Принтер
- •11.3.3. Объект в памяти
- •11.3.4. Информационный контекст
- •11.4. Архитектура, управляемая событиями
- •11.5. Исходный текст программы
- •11.7. Некоторые новые типы данных
- •11.8. Венгерская нотация
- •11.9. Точка входа программы
- •11.11. Создание окна
- •11.12. Цикл обработки сообщений
- •11.13. Оконная процедура
- •11.14. Обработка сообщений
- •11.15. Обработка сообщений функцией DefWindowProc
- •11.16. Синхронные и асинхронные сообщения
- •11.17. Еще один метод получения описателя контекста устройства
- •11.19. Полосы прокрутки
- •Литература
10.ПРИМЕРЫ РЕАЛИЗАЦИИ КОНТЕЙНЕРНЫХ КЛАССОВ
10.1.Связанные списки
Связанные списки − это способ размещения данных, при котором одни данные ссылаются на другие. Списки представляют собой пример контейнер- ных классов. В общем, список напоминает массив с тем отличием, что его раз- меры не фиксированы: он может расти и уменьшаться по мере работы с ним.
Очередной элемент списка размещается в памяти динамически и связывается с остальной частью списка посредством указателей. В случае если некоторый элемент списка более не нужен, то освобождается память, отведенная под этот
элемент. Частным случаем списков являются стеки, очереди и кольца. |
Р |
||||||||||
10.1.1. Реализация односвязного списка |
|
|
У |
||||||||
Односвязный список предполагает организацию хранения данных в памя- |
|||||||||||
|
|
|
|
|
|
|
|
|
Г |
|
|
ти, при которой перемещение может быть выполнено только в одномИнаправле- |
|||||||||||
нии (от начала списка в конец). Расположение в памяти элементов односвязного |
|||||||||||
списка можно изобразить следующим образом (рис. 10). |
|
|
|
||||||||
В настоящем пособии реализация односвязного списка не приводится |
|||||||||||
|
|
|
|
|
|
|
а |
|
|
|
|
(предлагается выполнить самостоятельно, основываясь на информации о реали- |
|||||||||||
зации двусвязного списка, приводимого ниже). |
Б |
|
|
||||||||
|
|
|
|
|
|
к |
|
|
|
|
|
|
|
|
|
|
е |
|
|
|
|
|
|
Голова |
и |
т |
|
Эл-т вне списка |
|
|
|||||
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
||
|
|
Р с. 10. Одн связный список |
|
|
|
|
|||||
|
|
полож |
|
|
|
|
|
|
|
|
|
10.1.2. Реализац я двусвязного списка |
|
|
|
|
|
||||||
Двусвязный список − это организация хранения данных в памяти, позво- |
|||||||||||
|
б |
|
|
|
|
|
|
|
|
|
|
ляющая выполнять перемещение в обоих направлениях (от начала списка в ко- |
|||||||||||
и |
|
|
ение в памяти двусвязного списка можно изобразить |
||||||||
нец и наоборот). Рас |
|
||||||||||
следующ м о разом (рис. 11). |
|
|
|
|
|
|
|
||||
Б |
|
|
|
|
|
|
|
|
|
|
|
Голова |
|
|
|
|
Эл-т вне списка |
|
|
Рис. 11. Двусвязный список
В приведенном ниже примере для доступа к элементам списка использу-
230
ется разработанный класс, реализующий простые итераторы.
#include <iostream> #include <cassert> using namespace std; template <class T>
|
class D_List |
|
|
|
|
|
|
|
|
|
{private: |
|
|
|
|
|
|
|
|
|
class D_Node |
|
|
|
|
|
Р |
||
|
{ public: |
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
||
|
|
D_Node *next; |
// указатель на следующий узел |
|
|||||
|
|
D_Node *prev; |
|
|
|
|
И |
||
|
|
// указатель на предыдущий узел |
|
||||||
|
|
T val; |
|
// поле данного |
У |
|
|||
|
|
D_Node(T node_val) : val(node_val) { } // конструктор |
|||||||
|
|
D_Node() {} |
|
|
Г |
|
|
||
|
|
|
|
// конструктор |
|
|
|||
|
|
~D_Node(){} |
|
|
// деструктор |
|
|
||
|
|
|
|
|
|
Б |
|
|
|
|
|
// для вывода элементов, тип которых определен пользователем, |
|||||||
|
|
// неодходимо перегрузить опер цию << |
|
operator<<( ). |
|||||
|
|
|
|
|
а |
|
|
|
|
|
|
void print_val( ) { cout << val << " "; } |
|
|
|
||||
|
}; |
|
|
к |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public: |
|
е |
|
|
|
|
|
|
|
class iterator |
|
|
|
|
|
|||
|
|
|
|
|
|
|
|||
|
{private: |
|
|
|
|
|
|
|
|
|
|
friend class D_List<T>; |
|
|
|
|
|
||
|
|
|
о |
|
|
|
|
|
|
|
|
D_Node * the node; |
|
|
|
|
|
||
|
public: |
|
|
|
|
|
|
|
|
|
|
и |
|
|
|
|
|
|
|
|
|
iterator( ) : the тnode(0) { } |
|
|
|
|
|
||
|
|
л |
|
|
|
|
|
|
|
|
|
iterator(D Node * dn) : the_node(dn) { } |
|
|
|
||||
|
|
// Copy constructor |
|
|
|
|
|
||
и |
iterator(const iterator & it) : the_node(it.the_node) { } |
|
|||||||
iterator& operator=(const iterator& it) |
|
|
|
||||||
Б |
б{ the_node = it.the_node; |
|
|
|
|
|
return *this;
}
bool operator==(const iterator& it) const { return (the_node == it.the_node); }
bool operator!=(const iterator& it) const { return !(it == *this); }
iterator& operator++( ) { if ( the_node == 0 )
231
throw "incremented an empty iterator"; if ( the_node->next == 0 )
throw "tried to increment too far past the end"; the_node = the_node->next;
return *this;
} |
|
|
|
|
|
|
|
|
|
|
|
|
iterator& operator--( ) |
|
|
|
|
|
|
|
|
||||
{ if ( the_node == 0 ) |
|
|
|
|
|
|
|
|
||||
throw "decremented an empty iterator"; |
|
|
|
Р |
||||||||
if ( the_node->prev == 0 ) |
|
|
|
|
|
|
||||||
throw "tried to decrement past the beginning"; |
|
|
|
|||||||||
the_node = the_node->prev; |
|
|
|
|
|
|
||||||
return *this; |
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
И |
||||
} |
|
|
|
|
|
|
|
|
|
|
||
T& operator*( ) const |
|
|
|
|
|
|
||||||
|
|
|
|
|
У |
|
||||||
{ if ( the_node == 0 ) |
|
|
|
|
|
|
||||||
throw "tried to dereference an empty iterator"; |
Г |
|
|
|||||||||
return the_node->val; |
|
|
|
|
|
|
||||||
|
|
|
Б |
|
|
|
||||||
} |
|
|
|
|
|
|
|
|
|
|
||
}; |
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
а |
|
|
|
|
|
private: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// указатель на началоксписка |
|
|
|
|
||||||
D_Node *head; |
|
|
|
|
||||||||
D_Node *tail; |
// указа ель на эл |
нт вне списка |
|
|
|
|||||||
|
|
|
|
|
|
ме |
|
|
|
|
|
|
D_List & operator=(const D List &); |
|
|
|
|
|
|
||||||
D_List(const D_List &); |
т |
|
|
|
|
|
|
|||||
iterator head_iterator; |
|
|
|
|
|
|
||||||
iterator tail_iterator; |
о |
|
|
|
|
|
|
|
||||
public: |
|
|
и |
|
|
|
|
|
|
|
|
|
D_List( ) |
|
|
|
|
|
|
|
|
|
|||
л |
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
||
{ head = tail = new D_Node; |
|
|
|
|
|
|
|
|||||
tail->next = 0; |
|
|
|
|
|
|
|
|
|
|
||
|
б |
|
|
|
|
|
|
|
|
|
|
|
tail->prev = 0; |
|
|
|
|
|
|
|
|
|
|
||
headиiterator = iterator(head); |
|
|
|
|
|
|
|
|||||
tail iterator = iterator(tail); |
|
|
|
|
|
|
|
|||||
Б} |
|
|
|
|
|
|
|
|
|
|
|
|
// конструктор (создание списка, содержащего один элемент) D_List(T node_val)
{head = tail = new D_Node; tail->next = 0;
tail->prev = 0;
232
// деструктор |
|
~D_List( ) |
|
{ D_Node *node_to_delete = head; |
|
for (D_Node *sn = head; sn != tail;) |
|
{ sn = sn->next; |
Р |
delete node_to_delete; |
|
node_to_delete = sn; |
|
} |
|
delete node_to_delete; |
|
|
} |
|
|
|
|
|
|
|
|
И |
|
bool is_empty( ) {return head == tail;} |
|
||||||||
|
iterator front( ) { return head_iterator; } |
У |
||||||||
|
iterator rear( ) { return tail_iterator; } |
|||||||||
|
Г |
|
||||||||
|
void add_front(T node_val) |
|
|
|||||||
|
{ D_Node *node_to_add = new D Node(nodeБ_val); |
|
||||||||
|
|
node_to_add->next = head; |
а |
|
|
|||||
|
|
node_to_add->prev = 0; |
|
|
|
|||||
|
|
head->prev = node_to add; |
|
|
||||||
|
|
head = node_to add; |
|
к |
|
|
||||
|
|
|
|
|
|
|
||||
|
|
head_iterator = iterator(head); |
|
|
||||||
|
} |
|
|
|
|
е |
|
|
|
|
|
// добавление нового элемента в начало списка |
|||||||||
|
|
|||||||||
|
void add |
rear(T nodeтval) |
|
|
|
|
||||
|
{ if ( is |
|
empty(о) ) // список не пустой |
|
|
|||||
|
|
add |
|
front(node_val); |
|
|
|
|
||
|
|
else |
и |
|
|
|
|
|
||
|
|
л// не выполняется для пустого списка, т.к. tail->prev =NULL |
||||||||
|
б |
|
// и, следовательно, tail->prev->next бессмысленно |
|||||||
|
|
|
|
|
|
|
|
|
||
и |
|
|
|
|
|
|
|
|
|
|
Б |
|
|
|
|
|
|
|
|
|
|
{ D_Node *node_to_add = new D_Node(node_val); node_to_add->next = tail;
node_to_add->prev = tail->prev; tail->prev->next = node_to_add; tail->prev = node_to_add; tail_iterator = iterator(tail);
}
}
bool remove_it(iterator & key_i)
233
{ for ( D_Node *dn = head; dn != tail; dn = dn->next )
if ( dn == key_i.the_node ) // найден узел для удаления
{dn->prev->next = dn->next; dn->next->prev = dn->prev;
|
delete dn; |
|
|
// удаление узла |
|
|
|
|
||
|
key_i.the_node = 0; |
|
|
|
|
|
|
|||
|
return true; |
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
return false; |
|
|
|
|
|
|
|
Р |
|
// поиск итератора по значению узла |
|
|
|
|||||||
|
|
|
|
|||||||
iterator find(T node_val) const |
|
|
|
|
||||||
{ |
for ( D_Node *dn = head; dn != tail; dn = dn->next ) |
У |
||||||||
|
if ( dn->val == node_val ) return iterator(dn); |
Г |
И |
|||||||
} |
return tail_iterator; |
|
|
|
||||||
|
|
|
|
|
||||||
// подсчет числа элементов очереди |
|
|
|
|
||||||
|
|
|
|
|
||||||
int size( ) const |
|
|
|
а |
|
|
|
|
||
{ |
int count = 0; |
|
|
|
|
|
|
|
||
|
for ( D_Node *dn = head; dn != tail; dn = dn->nextБ) ++count; |
|
|
|||||||
} |
return count; |
|
|
|
к |
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
// вывод содержимого списка |
|
|
|
|
|
|||||
} |
|
|
|
|
|
|||||
|
|
|
|
т |
|
|
|
|
|
|
void print( ) const |
|
|
|
|
|
|
|
|||
{ for ( D_Node *dn = head; dn е!= tail; dn = dn->next ) |
|
|
|
|||||||
}; |
dn->print_val( ); |
о |
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|||
|
cout << endl; |
и |
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
||
|
|
л |
|
|
|
|
|
|
|
|
В файле d_list.cpp содержится main-функция, демонстрирующая некото- |
||||||||||
|
б |
|
|
|
|
|
|
|
|
|
рые примеры ра оты со списком. |
|
|
|
|
|
|||||
|
и |
|
|
|
|
|
|
|
|
|
#include "dlist 2.h" |
|
|
|
|
|
|
|
|||
typedef int tip; |
|
|
|
|
|
|
|
|
||
D |
List<tip> the_list; |
// создается пустой список |
|
|
|
|
||||
int main() |
|
|
|
|
|
|
|
|
|
|
Б{ int ret = 0; |
|
|
|
|
|
|
|
|
|
D_List<tip>::iterator list_iter;
//занесение значений 0 1 2 3 4 в список for (int j = 0; j < 5; ++j) the_list.add_front(j);
//вывод содержимого списка, используя компоненту-функцию
234
// вывод содержимого списка в обратном порядке |
|
||||||||||||
|
for ( list_iter = the_list.rear( ) ; list_iter != the_list.front( ) ; )Р |
||||||||||||
|
{ |
|
--list_iter; |
// декремент итератора |
|
|
|
||||||
|
|
|
cout << *list_iter << " "; |
|
|
|
|
И |
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
У |
|
|
cout << endl; |
|
|
|
|
Г |
|
||||||
|
|
|
|
Б |
|
|
|||||||
|
the_list.remove_it(the_list.find(3)); |
|
|
||||||||||
|
|
|
|
|
|||||||||
|
the_list.print( ); |
|
|
|
|
|
|
||||||
|
cout<<the_list.size( )<<endl; |
а |
|
|
|
||||||||
|
return 0; |
|
|
|
|
|
|
|
|||||
} |
|
|
|
к |
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Результат работы программы: |
|
|
|
|
|
||||||||
3 |
2 |
1 |
0 |
|
|
|
е |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||
3 |
2 |
1 |
0 |
|
|
|
|
|
|
|
|
|
|
0 |
1 |
2 |
3 |
4 |
о |
|
|
|
|
|
|
||
2 |
1 |
0 |
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
||||
4 |
|
|
|
ли |
|
|
|
|
|
|
|||
Итератор реа |
з тван как открытый вложенный класс D_List::iterator. Так |
||||||||||||
как класс открытый, в main может быть создан его объект. Класс iterator объяв- |
|||||||||||||
|
|
л |
|
|
|
|
|
|
|
|
|||
ляется дружественным классу D_List, чтобы функция remuv_it класа D_List |
|||||||||||||
имела возможность обращаться к private-члену the_node класса iterator. |
|||||||||||||
В допо нение к стандартным указателям на голову и хвост списка (адрес |
|||||||||||||
и |
|
|
|
|
|
|
|
|
|
|
|
|
|
за пределами списка) в программе (в d_list.h) объявлены итераторы head_iterator |
|||||||||||||
Б |
|
|
|
|
|
|
|
|
|
|
|
|
|
tail бiterator, также ссылающиеся на голову и хвост списка.
Использование итератора позволяет скрыть единственный элемент дан- ных класса D_List:
D_Node * the_node.
В классе iterator выполнена перегрузка некоторых операций, позволяю- щих манипулировать узлом в строго определенных правилах (табл. 5).
Для поддержки использования итераторов в качестве аргументов или воз- вращаемых значений определен конструктор копирования.
В программе функция find(T) возвращает не bool, а iterator, который мо- жет быть передан другим функциям, принимающим параметры-итераторы, для
235