МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ
____________________________
Кафедра ____________________________
РЕФЕРАТ
на тему: «Реализация наследования классов в C++ на примере иерархии геометрических фигур»
Выполнил(а): ____________________________
Группа: ____________________________
Проверил(а): ____________________________
2026
Содержание
- 3
- 6
- 8
- 10
- 12
1. Наследование как механизм ООП
Наследование занимает центральное место среди механизмов объектно-ориентированного программирования. Суть его предельно ясна: новый класс строится на фундаменте уже существующего, перенимая его структуру и поведение. При этом производный класс не просто копирует родительский, он может добавлять собственные поля и методы, а также изменять унаследованную логику. Такой подход напрямую реализует принцип повторного использования кода, избавляя программиста от дублирования уже написанных и отлаженных решений.
Иерархическая организация классов, возникающая благодаря наследованию, отражает естественные связи между объектами реального мира. Взять хотя бы геометрические фигуры: у круга, прямоугольника и треугольника есть общие признаки. У каждой фигуры есть имя, площадь, периметр. Вместо того чтобы создавать три независимых класса с одинаковым набором методов, разумно выделить общую часть в базовый класс, например, `Figure`. А уже от него порождать классы `Circle`, `Rectangle` и `Triangle`. Речь идёт не об экономии строк кода, а о приведении структуры программы в соответствие с логикой предметной области. Разработчик мыслит категориями «фигура вообще» и «конкретная фигура», что упрощает проектирование сложных систем.
Базовый класс в такой иерархии выступает своеобразным контрактом для всех производных. Он определяет интерфейс, который гарантированно будет у каждого наследника. Благодаря этому внешний код может работать с разнородными объектами единообразно, через указатель на базовый класс, не вникая в детали конкретной реализации. Это свойство, известное как полиморфизм, тесно связано с наследованием и раскрывает его мощь в полной мере. Инкапсуляция же, в свою очередь, защищает внутренние данные каждого класса, предоставляя доступ к ним только через публичные методы. Иерархия становится более
надёжной и устойчивой к ошибкам.
Однако наследование в C++ не является монолитным механизмом. Программист может управлять степенью доступности унаследованных членов, выбирая один из трёх спецификаторов: `public`, `protected` или `private`. Самым распространённым и интуитивно понятным является публичное наследование. Оно сохраняет исходный уровень доступа всех членов базового класса в производном. Если поле было публичным в родителе, оно останется публичным и в наследнике, что соответствует отношению «является» (is-a). Например, круг действительно является фигурой, поэтому наследование `Circle` от `Figure` через `public` выглядит естественно.
Защищённое наследование (`protected`) действует тоньше. Публичные члены базового класса становятся защищёнными в производном. Это означает, что они доступны внутри самого производного класса и его наследников, но недоступны извне через объект этого класса. Такой подход полезен, когда нужно скрыть часть интерфейса от внешнего мира, но сохранить её для внутренней работы подклассов. Приватное наследование (`private`) делает все унаследованные члены приватными. Здесь отношение «является» уже не работает, скорее это «реализовано через». Этот тип используется реже, обычно для композиции, когда нужно переиспользовать код родителя, не раскрывая его интерфейс наружу.
Выбор спецификатора напрямую влияет на доступность членов класса для внешнего кода и для последующих поколений наследников. Материалы курса по C++ на Metanit и в лекциях Национального открытого университета «ИНТУИТ» сходятся во мнении, что для построения иерархий геометрических фигур применяется исключительно публичное наследование. Именно оно сохраняет полиморфную природу объектов и позволяет манипулировать ими через указатель на базовый класс. Два других типа решают более узкие задачи и в академических примерах, как правило, не фигурируют.
В итоге наследование представляет собой не просто синтаксическую конструкцию языка, а фундаментальный инструмент моделирования. Оно позволяет выстраивать гибкие и расширяемые иерархии, где общее поведение собрано в базовых классах, а специфичное распределено по производным. Применительно к геометрическим фигурам этот механизм даёт возможность описать их общие свойства ровно один раз, а затем многократно использовать эту основу для создания новых типов. Код становится лаконичным, понятным и лёгким для сопровождения.
2. Базовый класс геометрической фигуры
Проектирование иерархии геометрических фигур в C++ обычно начинается с выбора корневого элемента. Им становится абстрактный класс Figure. Его задача, зафиксировать тот минимальный набор свойств и операций, который будет присущ любой фигуре, независимо от её формы. Такой подход, описанный в материалах курса Яндекса по основам C++, позволяет вынести общую логику наверх и избежать дублирования кода в производных классах.
На этапе проектирования принимается ключевое решение: методы вычисления площади и периметра объявляются как чисто виртуальные функции. В синтаксисе C++ это выглядит как присваивание нуля при объявлении: `virtual double Area() const = 0;`. Это означает, что сам класс Figure не предоставляет реализации этих методов. Логика здесь проста. Абстрактная фигура не имеет геометрических размеров, поэтому вычислить её площадь или периметр невозможно. Объявляя метод чисто виртуальным, мы сообщаем компилятору, что конкретная реализация будет дана в классах-наследниках. Благодаря этому класс Figure становится абстрактным, и создать его экземпляр напрямую запрещено. Попытка написать `Figure fig;` вызовет ошибку компиляции.
Помимо чисто виртуальных методов, базовый класс определяет общие атрибуты. В нашем случае таким атрибутом выступает имя фигуры, хранящееся в строке. Для работы с этим полем предусмотрены стандартные методы доступа: геттер для получения значения и сеттер для его изменения. Это соответствует принципу инкапсуляции: данные защищены от прямого внешнего вмешательства, а доступ к ним контролируется. В учебных материалах Metanit и в лекциях Интуита отмечается, что именно такая структура (данные скрыты, а операции над ними явно определены) является основой надёжного объектно-ориентированного проектирования.
Главное преимущество чисто виртуальных функций проявляется при работе с полиморфизмом. Когда у нас есть указатель на базовый класс `Figure*`, мы можем хранить в нём адрес любого производного объекта. При вызове виртуального метода через этот указатель компилятор определит фактический тип объекта во время выполнения программы и вызовет соответствующую реализацию. Это позволяет писать универсальный код, который работает с любой фигурой, не зная её точного типа. Алгоритм, принимающий массив указателей на `Figure`, сможет корректно вычислить площадь и для круга, и для прямоугольника, и для треугольника, просто вызывая один и тот же метод `Area()`.
Отдельного внимания требует создание конструктора и деструктора базового класса. Конструктор `Figure` необходим для инициализации поля с именем. Поскольку производные классы будут вызывать этот конструктор при своём создании, важно, чтобы он принимал имя в качестве аргумента. Деструктор же должен быть обязательно виртуальным. Это критическое требование, которое подробно разбирается в книге Скотта Мейерса. Если деструктор базового класса не объявить виртуальным, то при удалении объекта производного класса через указатель на базовый класс вызовется только деструктор базового класса. Деструктор производного класса не будет вызван, что приведёт к утечке памяти и другим ошибкам. Объявление `virtual ~Figure() {}` гарантирует корректное разрушение всей цепочки наследования.
Базовый класс Figure, таким образом, выполняет роль контракта. Он определяет интерфейс, которому должны следовать все фигуры, и предоставляет общий механизм для их единообразного использования. Это позволяет строить расширяемые иерархии, где добавление новой фигуры не требует изменения существующего кода, работающего с базовым классом.
3. Производные классы конкретных фигур
После того как базовый класс Figure задал интерфейс с чисто виртуальными функциями Area() и Perimeter(), наступает очередь конкретных реализаций. Производные классы обязаны предоставить тела этих методов, иначе они сами останутся абстрактными. Практика показывает, что именно на этом этапе проявляется гибкость наследования. Общая структура уже готова, остаётся наполнить её специфичной для каждой фигуры математикой.
Первый производный класс, Circle, описывает окружность. Его единственное поле, double radius, объявляется в приватной секции. Конструктор принимает значение радиуса и передаёт имя фигуры в базовый класс, вызывая конструктор Figure("Circle"). Синтаксис списка инициализации здесь обязателен: сначала выполняется конструктор родителя, и только потом инициализируется собственное поле. Переопределение методов выглядит лаконично. Area() возвращает M_PI * radius * radius, а Perimeter() возвращает 2 * M_PI * radius. Никакой дополнительной логики не требуется, что делает класс удобным для тестирования базовых механизмов наследования.
Следующий класс, Rectangle, требует двух параметров: длины и ширины. Его поля double width и double height инициализируются в том же списке, что и вызов конструктора базового класса. Здесь важно следить за порядком. Члены класса инициализируются в порядке их объявления, а не в порядке следования в списке. Если сначала объявлена width, а затем height, то и в списке инициализации они должны идти именно так. Площадь вычисляется простым перемножением, периметр удвоением суммы. Класс наглядно демонстрирует, что переопределение виртуальных методов не требует ключевого слова virtual повторно: достаточно совпадения сигнатуры.
Наибольший интерес представляет Triangle. Его поля это три стороны: a, b, c. Конструктор принимает все три значения и вызывает базовый конструктор Figure("Triangle"). Но перед тем как сохранить стороны, стоит проверить неравенство треугольника. Если сумма двух меньших сторон не превышает третью, фигура не существует. В учебных примерах часто опускают эту проверку, но на практике она обязательна. Для площади здесь применяется формула Герона. Сначала вычисляется полупериметр p = (a + b + c) / 2. Затем площадь равна sqrt(p * (p - a) * (p - b) * (p - c)). Использование std::sqrt требует подключения заголовка . Периметр считается тривиальным сложением всех сторон.
Важный нюанс касается вызова конструктора базового класса. В языке C++ это действие всегда выполняется первым, ещё до входа в тело конструктора производного класса. Поэтому передача имени фигуры в Figure происходит автоматически при создании любого объекта. Это гарантирует, что поле name базового класса будет корректно инициализировано ещё до того, как производный класс начнёт работать со своими данными. В учебном материале Яндекса по основам C++ подчёркивается, что такой порядок исключает ситуации, когда производный класс пытается обратиться к неинициализированным членам родителя.
Все три класса следуют одному шаблону. Публичный конструктор с параметрами, приватные поля, переопределённые методы Area() и Perimeter(). Такая единообразная структура упрощает чтение кода. Если в будущем потребуется добавить новую фигуру, например эллипс или трапецию, достаточно будет скопировать этот каркас и заменить математику. При этом ни один из классов не заботится о том, как именно другие фигуры вычисляют свои характеристики. Это изолирует изменения: правка формулы площади круга не затрагивает треугольник.
4. Полиморфное использование иерархии
После того как иерархия классов построена, наступает этап, где она раскрывается в полной мере. Создание абстрактного класса Figure и конкретных наследников Circle, Rectangle и Triangle было лишь подготовкой. Проверка заключается в том, способна ли система обрабатывать набор разнородных объектов единообразно, не теряя их индивидуальных особенностей. Здесь в игру вступает полиморфизм, который в C++ реализуется через механизм виртуальных функций.
Самый наглядный способ продемонстрировать это на практике, создать массив указателей на базовый класс. Каждый элемент такого массива имеет тип `Figure*`, но может указывать на объект любого производного класса. Это возможно благодаря отношению «является», которое устанавливается наследованием: круг, прямоугольник и треугольник являются фигурами. В такой массив мы можем поместить динамически созданные объекты всех трёх типов, например, добавив в него `new Circle(5.0)`, `new Rectangle(4.0, 6.0)` и `new Triangle(3.0, 4.0, 5.0)`. С точки зрения компилятора, мы работаем с массивом фигур. С точки зрения исполняемой программы, каждый указатель хранит адрес объекта конкретного типа.
Теперь обратимся к главному свойству полиморфизма. Когда мы проходим по массиву в цикле и вызываем метод `Area()` через указатель `Figure*`, компилятор не знает, на какой именно объект указывает ссылка в данный момент. Вместо этого он генерирует вызов через таблицу виртуальных функций (vtable). Во время выполнения программы механизм динамической диспетчеризации определяет фактический тип объекта и передаёт управление соответствующей реализации метода. Для круга вызовется метод, вычисляющий площадь по радиусу, для прямоугольника, перемножающий стороны, а для треугольника, использующий формулу Герона. Аналогично работает и метод `Perimeter()`.
Программист пишет один цикл, а программа выполняет три разные операции. Для набора из трёх фигур с указанными параметрами результаты будут следующими: круг с радиусом 5 даст площадь около 78.54 и периметр около 31.42, прямоугольник 4×6 даст площадь 24 и периметр 20, а треугольник со сторонами 3, 4 и 5 (прямоугольный) даст площадь 6 и периметр 12. Совпадение этих значений с ручными расчётами подтверждает корректность всей цепочки наследования и переопределения методов.
Именно такой подход даёт ощутимые преимущества. Код обработки становится предельно лаконичным: вместо трёх отдельных циклов для каждого типа фигуры мы пишем один универсальный. Это упрощает чтение и поддержку программы. Система становится расширяемой. Если потребуется добавить в иерархию новый класс, например, `Ellipse`, достаточно унаследовать его от `Figure` и реализовать чистые виртуальные методы. Существующий код, работающий с массивом указателей, менять не придётся вообще. Он автоматически начнёт корректно обрабатывать новые объекты. Эта особенность соответствует принципу открытости/закрытости, сформулированному Бертраном Мейером: программные сущности должны быть открыты для расширения, но закрыты для модификации. Добавление нового типа не требует правки уже написанного и отлаженного кода.
Проведённое сравнение результатов вычислений для всех трёх фигур демонстрирует работоспособность иерархии. Полиморфизм в C++, не просто синтаксическая конструкция, а инструмент, который позволяет моделировать сложные системы, сохраняя чистоту кода и гибкость архитектуры. Единообразная работа с разнородными объектами через указатель на базовый класс, это то, ради чего стоило строить всю иерархию.
СПИСОК ЛИТЕРАТУРЫ
1. Наследование и полиморфизм в C/C++ и Arduino. Урок — https://alexgyver.ru/lessons/inheritance/
2. Наследование и полиморфизм - Основы С++ — https://education.yandex.ru/handbook/cpp/article/inheritance-and-polymorphism
3. C++: Наследование — https://metanit.com/cpp/tutorial/5.10.php
4. Применение наследования в C++ — http://mycpp.ru/cpp/book/c19.html
5. Объектно-ориентированное программирование — https://intuit.ru/studies/courses/3479/721/lecture/25562?page=7
6. C++ для начинающих. Наследование и подтипизация — http://mycpp.ru/cpp/book/c17.html
7. Глава 6 Наследование и объектно-ориентированное программирование — http://grep.cs.msu.ru/cpp.com.ru/meyers_1/ch6.html
8. Лекция 11. Наследование и полиморфизм — https://farabi.university/storage/files/2576247763690267280a856027059695_%D0%9B%D0%B5%D0%BA%D1%86%D0%B8%D1%8F%2011%20%D0%9D%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%B8%20%D0%BF%D0%BE%D0%BB%D0%B8%D0%BC%D0%BE%D1%80%D1%84%D0%B8%D0%B7%D0%BC.pdf
Нужна такая же работа по своей теме? Соберём структуру, текст и источники в этом же оформлении.