Файл: Основные понятия объектно-ориентированного программирования (Базовые понятия).pdf
Добавлен: 04.04.2023
Просмотров: 102
Скачиваний: 1
Каждая из перечисленных частей может быть опущена. Так инициализация переменной-счетчика может происходить до конструкции for. Если опущено условие, то цикл будет бесконечным и программа зависнет, если не предусмотреть выход из тела цикла. Бесконечный циклы могут быть очень полезными, но работать с ними нужно с большой осторожностью. Такой цикл также может возникнуть если опущена третья часть – тогда условие всегда будет верным. Опущены могут быть также все три части (Рисунок 8).
Рисунок 8. Цикл for без использования выражений в круглых скобках.
Очевидно, что такую конструкцию читать труднее, чем обычную. Здесь для выхода из цикла используется ключевое слово break, которое прерывает цикл.
Цикл for можно также использовать для перебора значений вектора (Рисунок 9).
Рисунок 9. Перебор элементов вектора.
Нередко бывает, что определенный код нужно выполнить только пока действительно какое-то условие. При этом неизвестно заранее, сколько повторов цикла это потребует. В этом случае используют цикл while (Рисунок 10).
Рисунок 10. Цикл while.
В данном примере в качестве условия используется считывание из потока вывода. Оно верно только в том случае, если введенный символ является целым числом. Это позволяет пользователю вводить данные без необходимости перезапускать программу.
Аналогично работает цикл do…while с тем лишь отличием, что код в блоке после ключевого слова do выполнится даже если условие неверно. Такое условие используется, когда код должен быть выполнен по крайней мере 1 раз (Рисунок 11).
Рисунок 11. Цикл do…while.
Глава 2. Объектно-ориентированное программирование
Одной из ключевых особенностей C++ с самого его создания является мощная поддержка объектно-ориентированной парадигмы. Это позволяет писать крупные проекты, такие как операционные системы, и при этом сохранять сложность работы кода на достаточном для понимания человеком уровня.
Основные понятия
Ключевым понятием в объектно-ориентированном программировании является класс. Класс представляет из себя совокупность переменных и функций. В этом случае переменные принято называть полями или свойствами класса, а функции – методами. Объект – это просто экземпляр класса. Рассмотрим все это более детально.
Описание класса начинается с ключевого слова class, за которым следует имя класса. Обычно имя класса начинается с большой буквы. Далее в фигурных скобках идет описание полей и методов класса (Рисунок 12).
Рисунок 12. Пример простого класса.
Обычно класс состоит из двух секций – private и public. Ко всем методам и полям, находящимся в секции private, нельзя получить доступ извне, только внутри методов самого класса. Поля и методы в секции public доступны для изменения и вызова извне, что и следует из названия.
Создание экземпляра класса очень похоже на создание переменной встроенных типов данных, ведь по сути классы являются типами данных создаваемыми программистами (Рисунок 13).
Рисунок 13. Создание и использование объекта своего класса.
Сперва мы записываем тип переменной – имя класса. При инициализации также используется имя класса плюс круглые скобки, как при вызове функции. Теперь мы имеем доступ к публичным полям и методам класса с помощью оператора точки (.).
В C++ есть особый тип данных, который называется указатели. В отличие от обычных переменных, сами по себе они не хранят данные, но «указывают» на адрес в оперативной памяти, где хранится значение. При использовании указателей доступ к полям объекта производится с помощью оператора -> (Рисунок 14).
Рисунок 14. Использование указателя на объект.
При объявлении переменной также нужно добавить символ *, что означает, что это не сам объект, а указатель. Также перед созданием объекта указывается ключевое слово new.
Нередко при создании объекта нужно сразу же сделать какие-то вещи. Например, присвоить значения определенным полям. Для этого используются конструкторы (Рисунок 15).
Рисунок 15. Использование конструктора.
Ключевое слово this является указателем на текущий объект. Его можно использовать, например, когда имя передаваемого параметра совпадает с именем поля. Теперь при создании объекта нужно передать строку.
Часто не лишним бывает явно удалить объект, который больше не нужен. При этом вызывается специальный метод, называемый деструктор. Достаточно часто в он используется для освобождения выделенной памяти и избегания утечек (Рисунок 16).
Рисунок 16. Использование деструктора для освобождения памяти.
Здесь поле name является указателем на соответствующую строку. Если она нам больше не понадобится, можно явно удалить её, освободив тем самым память.
Конструктор и деструктор по сути являются обычными методами, чье имя совпадает с именем класса. Перед деструктором ещё ставится знак тильда (~). Деструкторы также полезны для совершения каких-либо действий, связанных с логикой самого приложения.
Наследование. Инкапсуляция. Полиморфизм
Объектно-ориентированное программирование основывается на так называемых «трех столпах»: наследование, инкапсуляция и полиморфизм.
Наследование является очень мощным инструментом, позволяющим избежать дублирования кода. Проще всего это показать на типичном примере.
Допустим у нас есть класс, описывающий собаку (Рисунок 17).
Рисунок 17. Класс Dog.
Затем приходит заказчик и говорит, что помимо собак, в приложении будут ещё и кошки. Тогда нам нужен соответствующий класс (Рисунок 18).
Рисунок 17. Класс Cat.
Что ж, если на этом всё, то можно так и оставить. Но что, если заказчик приходит и говорит, что ещё у него будут хомячки, морские свинки и хамелеоны. Дублировать почти полностью одинаковые классы очень непрактично и создает трудности в сопровождении. Именно для таких ситуаций придумано наследование (Рисунки 18-19).
Рисунок 18. Родительский класс Pet.
Рисунок 19. Наследование от класса Pet.
Для наследования при определении дочернего класса после двоеточия указывается имя родительского класса. Ключевое слово public перед именем класса Pet позволяет дочерним объектам использовать методы родителя, как если бы они были у них самих (например, dog.sit()). Теперь в каждом классе нам нужно реализовать лишь один отличающийся метод.
Наследование открывает не только эту возможность. Допустим у нас есть функция, которая запускает процесс переноса питомца из гостиной на кухню. В качестве аргумента она принимает ссылку на объект животного. Мы могли бы реализовать функции для каждого типа животных, но это лишняя работа. Полиморфизм позволяет указать в качестве типа аргумента функции родительский объект, а не дочерний (Рисунок 20).
Рисунок 20. Использование полиморфизма.
Важным моментом является то, что такой подход работает только при использовании указателей. Также, если запустить такой код, то мы увидим, что вызывается метод класса Pet, а не классов Dog и Cat. Так происходит из-за того, что метод getVoice() не объявлен виртуальный. При использовании виртуальных методов, компилятор учитывает тип объекта, а не тип указателя, при вызове метода.
Инкапсуляция позволяется скрыть от пользователя детали реализации, оставив только ограниченное количество методов для использования. Это упрощает работу с объектом, документацию, разработку библиотек, а также снижает вероятность случайных ошибок при использовании объектов.
В C++ для указания доступности используются ключевые слова private, protected и public.
Поля и методы класса, помеченные как приватные (private), доступны только изнутри этого класса. Попытка обращения к ним приведёт к ошибке. По умолчанию все поля и методы являются приватными.
Защищенные поля и методы (protected) доступны внутри самого класса и в его наследниках. Публичные поля и методы (public) доступны для использования в любых других классах.
Считается хорошей практикой все поля помечать как private и по умолчанию в C++ они такими и являются, поэтому слово private можно опускать. Чтобы всё же иметь к ним доступ, используются методы, называемые «геттеры» (getter) и «сеттеры» (setter). Обычно они начинаются со слов get и set и, соответственно, возвращают или устанавливают какое-либо поле. При этом можно проделать с ним дополнительные манипуляции. Нередко используют только метод получения, получая таким образом поле только для чтения (Рисунок 21).
Рисунок 21. Свойство только для чтения.
Интерфейсы. Абстрактные классы
C++ как и многие другие языки программирования позволяет создавать интерфейсы – специальные конструкции, от которых можно наследоваться (Рисунок 22).
Рисунок 22. Пример интерфейса.
Интерфейс очень похож на класс, но имеет ряд существенных отличий. Во-первых, определяется он с помощью ключевого слова __interface. Во-вторых, в интерфейсе все методы являются виртуальными и открытыми. В-третьих, методы можно только объявлять. Если попытаться определить метод в интерфейсе, это ничего не даст, поскольку каждый метод обязан быть переопределен в дочернем классе. Интерфейсы, как и классы, могут наследоваться, но только от других интерфейсов.
Интерфейсы удобны, но не тогда, когда какие-то методы все же должны быть определены в родительском классе. При этом возможность создавать объекты родительского класса должно быть запрещено. Для этого можно использовать абстрактный класс (Рисунок 23).
Рисунок 23. Пример абстрактного класса.
Класс считается абстрактным, если хотя бы один член класса является абстрактным. Для того, чтобы указать, что метод абстрактный используется конструкция = 0 и отсутствуют фигурные скобки. Также обязательно должно быть указано ключевое слово virtual.
2.4 Заголовочные файлы
Классы зачастую могут быть достаточно объемными. Несколько десятков методов на десятки строк не являются исключительной ситуацией. Чтобы облегчить чтение такого класса разделяют объявление методов и их определение, используя заголовочные файлы. Обычно, заголовочный файл имеет расширение h, а файлы с определением расширение cpp. Заголовочный файл гораздо короче, чем файл с определением (Рисунок 24).
Рисунок 24. Файл Date.h.
Класс в заголовочном файле больше напоминает интерфейс и это неспроста. По сути в нем мы и объявляем интерфейс, только в данном случае это не сущность языка, а информация о том, что можно делать с объектом класса. Таким образом программист быстрее находит нужный ему метод. Даже в этом простом примере определения методов занимают почти 40 строк (Рисунок 25).
Рисунок 25. Фрагмент файла Date.cpp.
Методы определяются с помощью оператора :: и при этом должны совпадать со своим объявлением. То есть если в объявлении метод должен вернуть int, то и в определении он должен сделать тоже самое.
Такое разделение на заголовочные файлы и файлы определений позволяет отделить интерфейс класса, от его реализации, поскольку программист, использующий класс, не должен задумываться о том, как именно класс делает то, что он делает. Во всяком случае до тех пор, пока класс работает правильно.