- •И. А. Андрианов, д. В. Кочкин, с. Ю. Ржеуцкая
- •Учебное пособие
- •Оглавление
- •1. Основы языка 8
- •1.2.2 Простые типы данных 13
- •2. Работа с памятью 73
- •3. Основы объектно-ориентированного программирования 87
- •4.Обработка исключений 114
- •5. Шаблонные функции и классы. Библиотека стандартных шаблонов 130
- •6. Паттерны проектирования 159
- •7. Антипаттерны 211
- •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.5 Разработка шаблонных классов с настраиваемой функциональностью
- •5.6 Использование шаблонов для вычислений на этапе компиляции
- •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 Методики оптимизации кода
- •Заключение
- •Библиографический список
9.2 Оптимизация кода
9.2.1 Рекомендации по выполнению оптимизации
Под оптимизацией кода понимается изменение кода для повышения эффективности его работы. Слово "эффективность", как правило, означает скорость работы программы (иногда также объём потребляемой памяти). Приведём несколько рекомендаций, соблюдение которых поможет вовсе избежать необходимости оптимизировать написанный код или значительно снизить объём работы.
1. Если производительность программы действительно важна, стоит подумать о ней ещё на ранних стадиях проектирования. Например, если разрабатывается сервер, к которому одновременно будут обращаться тысячи клиентов, стоит заранее выбрать распределённую архитектуру, при которой код будет выполняться на кластере из нескольких машин. Если предполагаются большие объёмы сложных однотипных вычислений, то стоит рассмотреть возможность использования для этого графических карт, и т.п.
2. Выбор правильных алгоритмов и структур данных при проектировании часто приводит к тому, что оптимизация кода вообще не потребуется. Например, как бы вы ни оптимизировали код сортировки пузырьком, он никогда не станет работать лучше, чем сортировка методом Хоара.
3. Прежде, чем тратить время на оптимизацию, стоит убедиться, что это действительно нужно. Производительность работы пользователей далеко не всегда связана со скоростью работы программы − например, в ней может быть просто неудобный интерфейс. Также не стоит заниматься оптимизацией кода без явной необходимости. Если скорость работы программы устраивает пользователей, то, возможно, стоит потратить своё время на другие задачи − например, добавление в программу новых возможностей.
Если у вас всё же имеется необходимость в оптимизации кода, то стоит помнить о так называемом "правиле 80/20". Применительно к программированию оно говорит о том, что на 20% кода программы приходится 80% процентов времени её выполнения. В одном из исследований Дональда Кнута получилась оценка "4/50" (примерно 4% кода выполняются 50% времени). В любом случае, здесь важно то, что нерационально тратить время на оптимизацию всего кода − сначала необходимо найти "узкие" места в программе, после чего оптимизировать именно их. Бывает, что изменение всего лишь нескольких строчек кода способно повысить скорость работы большой программы в несколько раз.
Поиск таких "узких" мест в большой программе вручную − занятие весьма непростое. Однако, для этой цели можно использовать специальные инструменты − профилировщики. Многие среды разработки содержат профилировщик в своём составе (например, Visual Studio). Профилировщик позволяет получить профиль выполнения программы − сколько и какая функция и даже конкретная строчка кода отнимают процессорного времени. После этого уже можно целенаправленно оптимизировать именно те места в коде, которые максимально влияют на производительность.
Отметим, что есть ещё одна причина, по которой не стоит пытаться оптимизировать весь код программы (кроме напрасной потери времени и возможности внести лишние ошибки). Она заключается в том, что код после оптимизации зачастую становится гораздо менее понятным, и его будет сложнее сопровождать.
Стоит также предостеречь от оптимизации кода во время написания программ. Во-первых, предугадать заранее узкие места в коде очень сложно. Во-вторых, если слишком сильно сконцентрироваться на микрооптимизации, то можно отвлечься от достижения других целей, а также упустить из виду по-настоящему важные глобальные аспекты оптимизации.
