Практическая работа №12
ООП: Полиморфизм и перегрузка методов (C++)
Section titled “ООП: Полиморфизм и перегрузка методов (C++)”Цель работы: Изучить и закрепить принципы полиморфизма в C++, понять различие между статической (компиляционной) и динамической (времён выполнения) перегрузкой, освоить виртуальные функции, чисто виртуальные методы (абстрактные классы), а также реализовать примеры перегрузки методов в рамках классов.
Основные теоретические положения
Section titled “Основные теоретические положения”1. Что такое полиморфизм
Section titled “1. Что такое полиморфизм”Полиморфизм — это способность объектов разных типов отвечать на одинаковые сообщения (вызовы методов) по-разному. В C++ полиморфизм достигается двумя основными способами:
- Компиляторный (статический) полиморфизм — перегрузка функций/методов (function/method overloading) и шаблоны (templates). Разрешается на этапе компиляции.
- Динамический (время выполнения) — реализуется через указатели/ссылки на базовый класс и виртуальные функции (virtual). Решение, какую реализацию вызвать, принимается во время выполнения (dynamic dispatch).
2. Виртуальные функции и динамический полиморфизм
Section titled “2. Виртуальные функции и динамический полиморфизм”- Объявление метода с ключевым словом
virtualв базовом классе позволяет производным классам переопределять этот метод. - Для корректного освобождения ресурсов в иерархии классов базовый класс должен иметь виртуальный деструктор.
- Чисто виртуальные функции (
virtual void f() = 0;) делают класс абстрактным — от него нельзя создать объект, он служит интерфейсом.
Пример ключевых моментов:
class Base {public: virtual void doWork(); // виртуальная функция virtual ~Base(); // виртуальный деструктор};
class Derived : public Base {public: void doWork() override; // переопределение};3. Перегрузка методов (method overloading)
Section titled “3. Перегрузка методов (method overloading)”- Перегрузка функций/методов — наличие в пределах одной области видимости нескольких функций с одинаковым именем, но разными параметрами (количество/типы/порядок).
- Перегрузка разрешается на этапе компиляции по сигнатурам функций. Возвращаемый тип сам по себе не является критерием перегрузки.
Пример перегрузки методов в классе:
class Printer {public: void print(int x); void print(double x); void print(const std::string& s);};4. Отличие перегрузки и переопределения
Section titled “4. Отличие перегрузки и переопределения”- Перегрузка (overload) — несколько методов с одним именем, разными сигнатурами, определяется в пределах одного класса (или наследуемых классов, но сигнатуры различны).
- Переопределение (override) — производный класс даёт свою реализацию виртуального метода базового класса (одинаковая сигнатура).
Рекомендации по работе со ссылками и указателями
Section titled “Рекомендации по работе со ссылками и указателями”- Для достижения полиморфного поведения обычно используют ссылки или указатели на базовый класс:
Base* p = new Derived();p->doWork(); // вызывает Derived::doWork, если doWork виртуальная- Если использовать объект по значению (
Base b = Derived();), срезание (object slicing) отрежет часть объекта производного класса — полиморфизм утрачивается.
Алгоритмы и шаблоны реализации
Section titled “Алгоритмы и шаблоны реализации”1. Алгоритм динамического полиморфизма (виртуальные функции)
Section titled “1. Алгоритм динамического полиморфизма (виртуальные функции)”- Определить абстрактный/базовый класс с виртуальным методом (интерфейс).
- Создать несколько производных классов, переопределить виртуальные методы.
- Хранить объекты через указатели/ссылки на базовый класс.
- Вызвать метод через базовый указатель — будет выполнена реализация производного класса.
2. Алгоритм перегрузки методов
Section titled “2. Алгоритм перегрузки методов”- Внутри класса объявить несколько методов с одинаковым именем и разными параметрами.
- Реализовать каждую версию методa.
- Вызывать соответствующую версию — компилятор выберет подходящую по типам аргументов.
Примеры реализации (C++)
Section titled “Примеры реализации (C++)”Пример 1 — Иерархия фигур (Shape) — полиморфизм
Section titled “Пример 1 — Иерархия фигур (Shape) — полиморфизм”#include <iostream>#include <vector>#include <memory>#include <cmath>
class Shape {public: virtual double area() const = 0; // чисто виртуальная — Shape абстрактен virtual void printInfo() const { std::cout << "Shape: area = " << area() << "\n"; } virtual ~Shape() = default; // виртуальный деструктор};
class Circle : public Shape { double r;public: Circle(double radius) : r(radius) {} double area() const override { return M_PI * r * r; } void printInfo() const override { std::cout << "Circle: r = " << r << ", area = " << area() << "\n"; }};
class Rectangle : public Shape { double w, h;public: Rectangle(double width, double height) : w(width), h(height) {} double area() const override { return w * h; } void printInfo() const override { std::cout << "Rectangle: w = " << w << ", h = " << h << ", area = " << area() << "\n"; }};
int main() { std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>(2.0)); shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));
for (const auto& s : shapes) { s->printInfo(); // полиморфный вызов }
return 0;}Пример 2 — Перегрузка методов в классе
Section titled “Пример 2 — Перегрузка методов в классе”#include <iostream>#include <string>
class Logger {public: void log(int value) { std::cout << "[int] " << value << "\n"; } void log(double value) { std::cout << "[double] " << value << "\n"; } void log(const std::string& value) { std::cout << "[string] " << value << "\n"; }};
int main() { Logger lg; lg.log(10); lg.log(3.14); lg.log(std::string("hello")); return 0;}Пример 3 — Полиморфизм + перегрузка
Section titled “Пример 3 — Полиморфизм + перегрузка”#include <iostream>#include <memory>
class Animal {public: virtual void speak() const { std::cout << "Animal sound\n"; } // Перегруженный метод eat void eat() const { std::cout << "Animal eats generic food\n"; } void eat(int grams) const { std::cout << "Animal eats " << grams << " grams\n"; } virtual ~Animal() = default;};
class Dog : public Animal {public: void speak() const override { std::cout << "Woof!\n"; } // eat перегрузки унаследуются; можно добавить свои void eat(const std::string& food) const { std::cout << "Dog eats " << food << "\n"; }};
int main() { std::unique_ptr<Animal> a = std::make_unique<Dog>(); a->speak(); // вызывает Dog::speak() a->eat(); // вызывает Animal::eat() a->eat(200); // вызывает Animal::eat(int)
// Через прямой объект Dog можно вызвать свои версии Dog d; d.eat(std::string("bone"));
return 0;}Замечание: если в производном классе объявлена перегрузка метода с тем же именем, но иные сигнатуры, компилятор может скрыть (name hiding) базовые версии. Чтобы избежать этого, в производном классе можно использовать
using Base::methodName;.
Общие требования
Section titled “Общие требования”-
Написать программу на языке C++ (стандарт C++11 или выше).
-
Реализовать:
- Абстрактный базовый класс с хотя бы одной чисто виртуальной функцией.
- Два класса-наследника с переопределением виртуальных методов.
- Класс, демонстрирующий перегрузку методов (минимум три версии одного метода).
- Корректную работу с указателями/ссылками на базовый класс и виртуальным деструктором.
-
Обработка ввода/вывода данных через
std::cin/std::coutдля пользовательского взаимодействия. -
Комментарии в коде, поясняющие ключевые места:
virtual,override,=0,using,unique_ptrи т.д.
Вывод программы (что должно отображаться)
Section titled “Вывод программы (что должно отображаться)”- Список объектов и результат их полиморфных вызовов (например,
printInfo,speak). - Демонстрация вызовов перегруженных методов с разными наборами аргументов.
- Пояснение о срезании объектов (если демонстрируется) и корректный пример с использованием указателей/ссылок.
Задания для выполнения
Section titled “Задания для выполнения”Базовый уровень
Section titled “Базовый уровень”| № | Задача | Описание |
|---|---|---|
| 1 | Абстрактный интерфейс | Создайте базовый абстрактный класс Shape с чисто виртуальным методом area() и два наследника Circle, Rectangle. Выведите площади. |
| 2 | Виртуальный деструктор | Продемонстрируйте важность виртуального деструктура создавая объекты через Shape* и корректно удаляя их. |
| 3 | Перегрузка методов | Реализуйте класс Logger с тремя перегруженными методами log(...). Проверьте работу с разными типами аргументов. |
Повышенный уровень
Section titled “Повышенный уровень”| № | Задача | Описание |
|---|---|---|
| 4 | Полиморфизм и контейнер | Соберите разные фигуры в контейнер std::vector<std::unique_ptr<Shape>> и сложите площади всех фигур. |
| 5 | Name hiding и using | Создайте пример, когда в производном классе свои перегрузки скрывают методы базового класса; исправьте поведение с помощью using. |
| 6 | Комбинация | Создайте иерархию Animal с виртуальной функцией speak() и перегруженным eat(...). Напишите меню для создания животных и вызова разных eat-версий. |
Контрольные вопросы
Section titled “Контрольные вопросы”- Что такое виртуальная функция и зачем она нужна?
- В чём отличие между перегрузкой и переопределением метода?
- Почему важен виртуальный деструктор в базовом классе?
- Что такое “object slicing” (срезание объектов) и как его избежать?
- Как
usingпомогает при проблеме name hiding?
Критерии оценки
Section titled “Критерии оценки”- Полная реализация базовых требований: 60%.
- Корректная демонстрация динамического полиморфизма и виртуального деструктора: 20%.
- Перегрузка методов и примеры вызовов: 10%.
- Чистота кода и комментарии: 10%.
Рекомендуемая литература
Section titled “Рекомендуемая литература”- Бьерн Страуструп — Язык программирования C++. (Глава про классы и полиморфизм)
- Скотт Мейерс — Effective C++ (советы по использованию виртуальных функций и управлению ресурсами)
- Шилдт Г. — C++: Полное руководство (разделы по классам, наследованию и перегрузке)