- •И. А. Андрианов, д. В. Кочкин, с. Ю. Ржеуцкая
- •Учебное пособие
- •Оглавление
- •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 Методики оптимизации кода
- •Заключение
- •Библиографический список
3.4 Указатель this
В каждом методе класса (кроме статических методов, которые будут рассмотрены далее) доступен специальный неявный параметр this, который содержит адрес текущего объекта (то есть объекта, для которого был вызван этот метод). Указатель this неявно добавляется при работе с любым полем − например, re и this->re в теле методов класса complex_t будут означать одно и то же.
Рассмотрим пример, в котором используется указатель this. Пусть требуется в классе complex_t реализовать метод plus1, который прибавляет единицу к действительной части числа и при этом возвращает ссылку на изменённое значение. Такую функцию можно будет использовать как часть выражения. Возвращать ссылку более эффективно, чем новое значение, так как не тратится время на создание копии.
struct complex_t {
...
complex_t& plus1() {
re++;
return *this;
}
};
int main() { // демонстрация структуры complex_t
complex_t a(2, 3), c;
c = a.plus1();
a.show(); cout << endl; c.show();
system("pause"); return 0;
}
В данном примере метод plus1 написан целиком внутри тела класса complex_t − для небольших методов это допустимо.
3.5 Перегрузка операций
При разработке классов можно переопределять стандартные операции. В этом случае для каждой операции пишут специальный метод-оператор, который называется operator операция. Например, вместо метода plus1 в предыдущем примере можно было бы переопределить операцию '++', реализовав метод operator++. Переопределение операций стоит использовать с осторожностью, чтобы не потерять общепринятый смысл знаков операций.
В языке C++ возможна перегрузка почти всех операций, при этом имеются следующие ограничения: нельзя изменить приоритет операции, количество аргументов и порядок выполнения (слева направо или справа налево).
В каждом классе уже имеется операция = (операция присваивания), которая выполняет простое копирование всех полей одного объекта в другой. Если требуется при присваивании выполнить более сложные действия (обычно они требуются при работе с динамической памятью), её можно переопределить. Другие операции переопределяются, если в этом есть необходимость.
Напомним, что все операции в C++ делятся на унарные, бинарные и тернарные. Унарные операции обычно переопределяются как методы без параметров, бинарные требуют одного параметра (при этом в качестве одного из параметров предполагается текущий объект). Можно также переопределять операции в виде отдельных функций (а не методов класса) − тогда бинарная функция будет иметь один параметр, а бинарная − два. Поясним сказанное примерами.
Определим постфиксную и префиксную формы операции ++.
struct complex_t {
...
// префиксная операция ++
complex_t& operator ++() {
re++;
return *this;
}
// постфиксная операция ++
complex_t operator ++(int) {
complex_t tmp(*this);
re++;
return tmp;
}
...
};
Чтобы компилятор мог отличить префиксную форму операции ++ от постфиксной, во втором варианте в операторный метод добавляется дополнительный параметр типа int – так требуется по стандарту. Для этого фиктивного параметра можно даже имя не указывать, что мы и сделали.
Реализация префиксной формы операции ++ практически совпадает с реализацией функции plus1. Поясним код для постфиксного варианта. Нам нужно, чтобы в выражении наподобие c = a++; в объект c записалось старое значение объекта a, а не новое. Для этого в операторной функции мы сохраняем старое значение во временной переменной tmp, которую и возвращаем в качестве результата. Заметим, что будет ошибкой возвращать ссылку, поскольку локальная переменная tmp уничтожается при выходе из функции.
Приведём пример перегрузки операции вычитания.
struct complex_t {
...
complex_t operator - (const complex_t &other) const {
return complex_t(re - other.re, im - other.im);
}
...
};
int main() { // демонстрация структуры complex_t
complex_t a(3, 4), b(2, 1);
complex_t c = a - b;
c.show();
system("pause"); return 0;
}
Обратите внимание на два спецификатора const в объявлении функции operator -. Первый const относится к параметру other, второй − к неявному параметру this (то есть функция не будет изменять текущий объект).
В заключение этого раздела приведём ещё один пример класса (немного посложнее) – на этот реализуем класс массива с заданными границами индекса.
Класс - массив в стиле языка Pascal (с произвольной левой границей)
//
#include <iostream>
class pas_array{
// тип int легко заменить на любой другой
typedef int type_of_data;
int left, right; // левая (нижняя) и правая (верхняя) границы индекса
type_of_data *data; // указатель на начало массива
public:
pas_array(int left, int right) : // параметры - границы индекса
left(left), right(right),
data (new elem_type [right-left+1])
{}
pas_array& operator = (const pas_array &other) { // операция =
if (this != &other) {
if (right - left < other.right - other.left) {
delete[] data;
data = new type_of_data[right - left + 1];
}
left = other.left;
right = other.right;
for (int i = 0; i <= right - left; i++)
data[i] = other.data[i];
}
return *this;
}
pas_array(const pas_array &other) : // конструктор копирования
left(other.left), right(other.right) {
data = new type_of_data[right - left + 1];
for (int i = 0; i <= right - left; i++)
data[i] = other.data[i];
}
~pas_array() {
delete[] data;
}
// операция индексирования
type_of_data & operator [] (int index) {
return data[index - left];
}
};
int main() {
pas_array a(-3, 3);
for (int i = -3; i <= 3; i++)
a[i] = i;
pas_array b(a);
pas_array c(1, 2);
c = b;
for (int i = -3; i <= 3; i++)
std::cout << c[i] << " ";
}
