- •И. А. Андрианов, д. В. Кочкин, с. Ю. Ржеуцкая
- •Учебное пособие
- •Оглавление
- •1. Основы языка 8
- •1.2.2 Простые типы данных 13
- •6. Паттерны проектирования 159
- •7. Антипаттерны 211
- •2. Работа с памятью 73
- •3. Основы объектно-ориентированного программирования 87
- •4.Обработка исключений 114
- •5. Шаблонные функции и классы. Библиотека стандартных шаблонов 130
- •9. Методы отладки и оптимизации кода 242
- •1. Основы языка
- •1.1.2 Понятие проекта
- •1.2 Простые типы данных
- •1.2.1 Понятие типа
- •1.2.2 Простые типы данных
- •1.2.3 Внутреннее представление простых типов
- •1.2.4 Ключевое слово typedef. Тип size_t
- •1.3 Константы и переменные
- •1.3.1 Литералы
- •1. Числовые константы:
- •2. Символьные константы:
- •1.3.2 Переменные
- •1.3.3 Описание переменных
- •1.4. Выражения. Преобразование типов
- •1.4.1 Операнды и операции
- •1.4.2 Приоритет операций
- •1.4.3 Преобразование типов
- •1.5 Ветвления и циклы
- •1.5.2 Циклы
- •1.6 Массивы, строки
- •1.6.1 Основные понятия
- •1.6.2 Встроенные массивы
- •1.6.3 Cтроки. Обработка строк с завершающим нулём
- •1.7 Указатели и ссылки. Связь указателей и массивов. Библиотека cstring
- •1.7.1 Понятия указателя и ссылки
- •1.7.2 Связь между массивами и указателями
- •1.7.3 Библиотека cstring
- •1.8 Использование типов vector и string
- •1.8.1 Шаблонный класс vector
- •1.8.2 Класс string
- •1.9 Структуры и объединения. Битовые поля
- •1.10.1 Понятие функции
- •1.10.2 Описание функции и прототип функции
- •1.11 Параметры функции. Способы передачи параметров
- •1.11.1 Параметры функции и глобальные переменные
- •1.11.2 Способы передачи параметров в функцию
- •1.11.3 Передача массивов в функцию
- •1.11.4 Параметры-константы
- •1.11.5 Значения параметров по умолчанию
- •1.12.1 Указатель на функцию
- •1.12.2 Функции с переменным числом параметров
- •1.12.3 Перегрузка функций
- •1.12.4 Встроенные (inline) функции
- •1.13 Рекурсивные функции
- •1.14 Пространства имён
- •1.15 Директивы препроцессора. Макросы
- •2. Работа с памятью
- •2.1 Управление выделением и освобождением памяти
- •2.1.1 Статическое и динамическое выделение памяти
- •2.1.2 Способы динамического выделения и освобождения памяти
- •2.2 Динамические структуры данных
- •2.2.1 Основные понятия
- •2.2.2 Примеры реализации динамических структур на основе указателей
- •3. Основы объектно-ориентированного программирования
- •3.1 Основные понятия ооп
- •3.2.1 Описание класса
- •3.2.2 Область видимости элементов класса. Инкапсуляция
- •3.2.3 Первые примеры
- •3.3. Конструкторы и деструкторы.
- •3.4 Указатель this
- •3.5 Перегрузка операций
- •3.6 Дружественные функции и классы
- •3.7 Статические элементы класса
- •3.8 Наследование и полиморфизм
- •3.8.1. Основные понятия
- •3.8.2 Одиночное наследование
- •3.8.3 Множественное наследование
- •3.8.4 Конструкторы и деструкторы классов-потомков
- •3.9. Полиморфизм при наследовании классов
- •3.9.1 Механизмы раннего и позднего связывания
- •3.9.2 Абстрактные классы
- •4.Обработка исключений
- •4.1 Основные понятия
- •4.2 Перехват исключений
- •4.3 Поиск обработчика исключений. Раскрутка стека.
- •4.4 Повторное возбуждение исключений
- •4.5 "Аппаратные" и "программные" исключения
- •4.6 Стандартные классы исключений
- •4.7 Спецификация исключений, возбуждаемых функцией
- •4.8 Исключения в конструкторах при наследовании
- •4.9. Исключения в деструкторах
- •5. Шаблонные функции и классы. Библиотека стандартных шаблонов
- •5.1 Шаблонные функции
- •5.2 Шаблонные классы
- •5.3 Специализация шаблонов
- •5.4 Шаблонные параметры шаблонов
- •5.6 Использование шаблонов для вычислений на этапе компиляции
- •5.5 Разработка шаблонных классов с настраиваемой функциональностью
- •5.7 Библиотека стандартных шаблонов (stl) – основные понятия
- •5.8 Последовательные контейнеры. Итераторы
- •5.9. Адаптеры контейнеров
- •5.10 Ассоциативные контейнеры
- •5.11 Алгоритмы
- •6. Паттерны проектирования
- •6.1 Порождающие шаблоны
- •6.2 Структурные шаблоны
- •6.3 Шаблоны поведения
- •6.4 Шаблон "фабричный метод" (Factory method)
- •6.5 Шаблон "одиночка" (Singleton)
- •6.6 Шаблон "итератор" (Iterator)
- •6.7 Шаблон "наблюдатель" (Observer)
- •6.8 Шаблон "пул объектов" (Object pool)
- •6.9 Шаблон "команда" (Command)
- •6. 10 Шаблон "посетитель" (Visitor)
- •6.11 Дополнительные задания
- •6.11.1 Шаблон Iterator
- •6.11.2 Шаблон Observer
- •6.11.3 Шаблоны Command и Observer
- •6.11.5 Шаблон Visitor
- •6.11.5 Разработка класса − контейнера
- •6.11.6 Оценка производительности кода
- •7. Антипаттерны
- •7.1 Программирование методом копирования и вставки (Copy-Paste Programming)
- •7.2 Спагетти-код (Spaghetti code)
- •7.3 Магические числа (Magic numbers)
- •7.4 Бездумное комментирование
- •7.5 Жесткое кодирование (Hard code)
- •7.6 Мягкое кодирование (Soft code)
- •7.7 Золотой молоток (Golden hammer)
- •7.8 Слепая вера (Blind faith)
- •7.9 Ненужная сложность (Accidental complexity)
- •7.10 Божественный объект (God Object)
- •7.11 Лодочный якорь (Boat anchor)
- •7.12 Поток лавы (Lava flow)
- •7.13 Изобретение велосипеда (Reinventing the wheel)
- •7.14 Программирование перебором (Programming by permutation)
- •8.1 Выведение типов
- •8.2 Списки инициализации
- •8.3 Улучшение процесса инициализации объектов
- •8.4 Цикл for по коллекции
- •8.5 Лямбда-функции
- •8.6 Константа нулевого указателя nullptr
- •8.7 "Умные" указатели
- •9. Методы отладки и оптимизации кода
- •9.1 Отладка кода
- •9.1.1 Основные этапы отладки
- •9.1.2 Инструменты и приёмы отладки
- •9.2 Оптимизация кода
- •9.2.1 Рекомендации по выполнению оптимизации
- •9.2.2 Методики оптимизации кода
- •Заключение
- •Библиографический список
5.10 Ассоциативные контейнеры
Ассоциативные контейнеры реализуют упорядоченные структуры данных с возможностью быстрого поиска по ключу. К ассоциативным контейнерам относятся шаблонные классы std::set, std::multiset, std::map и std::multimap. Для хранения элементов в таких контейнерах используются сбалансированные деревья, что обеспечивает логарифмическую сложность операций поиска, вставки и удаления элементов.
Начиная с C++11, в стандартную библиотеку языка были также добавлены неупорядоченные ассоциативные контейнеры, основанные на хеш-таблицах − классы std::unordered_set, std::unordered_multiset, std::unordered_map и std::unordered_multimap. Данные контейнеры обеспечивают среднюю скорость поиска, вставки и удаления O(1). Однако, в худшем случае вычислительная сложность достигает O(N), поэтому неупорядоченные контейнеры стоит использовать с осторожностью.
Шаблонный класс std::set реализует абстрактный тип данных "множество". Из его методов наиболее часто требуются следующие:
• insert − вставляет элементы в множество
• erase − удаляет элементы из множества
• find − выполняет поиск элемента и возвращает итератор на него. Если элемент не найден, возвращается set::end()
• count − возвращает количество элементов с заданным значением. Поскольку в множестве не может быть дубликатов, то результатом может быть только 0 или 1.
• lower_bound − ищет в множестве самый маленький элемент, больший или равный заданному, и возвращает итератор на него
• upper_bound − ищет в множестве самый маленький элемент, строго больший заданного, и возвращает итератор на него
• clear − очищает содержимое контейнера
• size − возвращает количество элементов
• empty − возвращает true, если множество пусто
• begin − возвращает итератор на первый элемент
• end − возвращает итератор на "псевдоэлемент" после последнего
Множество может содержать элементы любого типа, для которого определена операция сравнения '<' (меньше). Если элементами будут объекты нашего собственного класса, то мы либо должны определить в этом классе operator <, либо задать способ сравнения элементов c помощью второго параметра шаблона std::set.
Рассмотрим примеры решения задач с использованием множества. Первая задача звучит так: вводится N чисел, требуется вывести их в порядке возрастания без повторов. Для решения поместим все числа во множество, при этом повторы уберутся автоматически. Далее, поскольку множество − упорядоченный контейнер, то, пройдясь последовательно циклом по его элементам, мы как раз получим их в порядке возрастания.
// Пример 5.17 - вывод входных чисел по возрастанию без повторов
#include <iostream>
#include <set>
int main() {
int n;
std::cin >> n;
std::set<int> s;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
s.insert(x);
}
for (int x : s)
std::cout << x << " ";
}
Вторая задача чуть посложнее. Вводится N чисел. Нужно вывести те из них, которые встречаются только один раз. Для решения создадим два множества s1 и s2. Входные числа будем заносить в s1. Если обнаружилось, что число уже имеется в s1, то поместим его в s2. В конце выведем все числа из s1, которые отсутствуют в s2.
// Пример 5.18 - поиск чисел, встречающихся в одном экземпляре
#include <iostream>
#include <set>
int main() {
std::set<int> s1, s2;
int n;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
if (s1.count(x) > 0)
s2.insert(x);
s1.insert(x);
}
for (int x : s1)
if (s2.count(x) == 0)
std::cout << x << " ";
}
Третья задача является модификацией предыдущей. Пусть требуется найти такие числа, у которых последняя цифра не встречается ни в одном другом числе. Например, для входных данных 34, 245, 74, 528, 15 ответом будет 528, так как только восьмёрка на конце числа у нас встречается в одном экземпляре.
Для решения этой задачи просто изменим способ сравнения элементов − будем сравнивать числа не обычным образом, а по их последней цифре. Для этого напишем функтор с операцией сравнения по последней цифре и укажем его в качестве второго аргумента шаблонного класса std::set:
// Пример 5.19 - поиск чисел с уникальной последней цифрой
struct lastDigitLess { // функтор для сравнения по последней цифре
bool operator() (int a, int b) const {
return a % 10 < b % 10;
}
};
int main() {
std::set<int, lastDigitLess> s1, s2;
// остальная часть программы не изменилась
Примечание. Конечно, можно было бы эту задачу решить проще и эффективней, причём даже без использования множеств: например, сначала прочитать числа в вектор, при этом запомнив, сколько раз встретилась каждая цифра на конце числа, а потом пройтись по вектору и вывести ответ.
Шаблонный класс std::multiset реализует абстрактный тип данных "мультимножество". По своему использованию он очень похож на std::set. Основное отличие состоит в том, что мультимножество может содержать дубликаты элементов. Это нужно учитывать при выполнении операций − например, вызов s.erase(5) удалит все пятёрки из множества s, а не одну.
Шаблонный класс std::map реализует абстрактный тип данных "ассоциативный массив" или "словарь". Он хранит упорядоченные пары "ключ-значение" и обеспечивает быстрый поиск по ключу, а также быструю вставку и удаление элементов. Из методов данного класса наиболее часто требуются следующие:
• operator[] − возвращает ссылку на значение элемента по заданному ключу. Если элемент с таким ключом отсутствует в контейнере, то он будет автоматически создан со значением по умолчанию. Поэтому нужно с осторожностью использовать данный оператор, чтобы не получить повышенное потребление памяти.
• at − возвращает ссылку на значение элемента по заданному ключу. Если элемент с таким ключом отсутствует в контейнере, будет возбуждено исключение. Данный метод появился в C++11.
• find − ищет элемент с заданным ключом и возвращает итератор на него. Если элемент не найден, возвращается map::end()
• count − возвращает количество элементов с заданным ключом (0 или 1)
• insert − вставляет элементы в контейнер
• erase − удаляет элементы из контейнера
• clear − очищает содержимое контейнера
• size − возвращает размер контейнера (количество элементов)
• empty − возвращает true, если контейнер пуст
• begin − возвращает итератор на первый элемент
• end − возвращает итератор на "псевдоэлемент" после последнего
Как уже говорилось, элементами словаря являются пары "ключ-значение" (используется класс std::pair). Для обращения к ключу используется поле first, для обращения к значению − поле second.
В качестве примера рассмотрим следующую задачу. Вводится N чисел. Требуется найти число (или числа, если их несколько), которые повторяются наиболее часто. Для решения данной задачи создадим словарь, где ключами будут входные числа, а значениями − количество их повторений. Заполнив словарь, найдём в нём элемент с максимальным значением:
// Пример 5.20 - поиск самых часто повторяющихся чисел
#include <iostream>
#include <map>
#include <algorithm>
int main() {
int n;
std::cin >> n;
std::map<int, int> nums;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
nums[x]++;
}
int maxCount = 0;
for (auto &elem : nums)
maxCount = std::max(maxCount, elem.second);
for (auto elem : nums)
if (elem.second == maxCount)
std::cout << elem.first << " ";
}
Класс std::multimap похож на класс std::map. Основное отличие состоит в том, что в нём допускаются дубликаты ключей.
