Основы программирования на C++: Наследование и полиморфизм
Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.
Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.
Здравствуйте, мои маленькие любители программирования!
Наследование в C++
Наследование — это механизм объектно-ориентированного программирования (ООП), который позволяет создавать иерархии классов, где класс-наследник (производный класс) наследует поля и методы базового класса , изменяя их область видимости. В C++ поддерживается публичное одиночное наследование , при котором производный класс может использовать публичные и защищённые члены базового класса.
Пример наследования
class A {
private:
int x;
public:
void Func1();
void Func2();
};
class B : public A {
private:
int y;
public:
void Func2(); // Переопределение функции
void Func3();
};
Основные моменты:
Класс B включает в себя подобъект класса A.
Методы и поля класса A доступны в B, за исключением приватных полей.
Приватное поле x из A хранится внутри объекта типа B, но недоступно напрямую.
Использование:
int main() {
B b;
b.Func1(); // Унаследовано от A
b.Func2(); // Переопределено в B
b.A::Func2(); // Версия из A
b.Func3(); // Определено в B
}
Приведение типов
Объект производного класса может быть приведён к типу базового класса . Это позволяет использовать объекты производного класса там, где ожидается объект базового класса.
void DoSomething(const A&);
int main() {
B b;
DoSomething(b); // OK
}
Жизненный цикл объектов
При создании объекта производного класса сначала вызывается конструктор базового класса, затем конструктор производного. Деструкторы вызываются в обратном порядке.
class InheritedLogger : public Logger {
public:
InheritedLogger() { std::cout << "InheritedLogger()\n"; }
~InheritedLogger() { std::cout << "~InheritedLogger()\n"; }
};
int main() {
InheritedLogger x;
}
Вывод программы:
Logger(): 1
InheritedLogger()
~InheritedLogger()
~Logger(): 1
Наследование vs Композиция
Наследование (is-a):
Класс-наследник является частным случаем базового класса.
Пример: Car является Vehicle.
Композиция (has-a):
Класс содержит объект другого класса как поле.
Пример: Car имеет Engine.
Пример композиции:
class C {
private:
A a; // Композиция
int y;
public:
void Func1() { a.Func1(); } // Обёртка
void Func2();
void Func3();
const A& GetA() const { return a; }
};
Полиморфизм
Полиморфизм позволяет переопределять поведение функций базового класса в производных классах. Для этого используются виртуальные функции .
Пример:
class Animal {
public:
virtual std::string Voice() const { return "Generic voice"; }
};
class Cat : public Animal {
public:
std::string Voice() const override { return "Meow!"; }
};
class Dog : public Animal {
public:
std::string Voice() const override { return "Woof!"; }
};
Виртуальные функции:
Позволяют выбирать реализацию во время выполнения (позднее связывание ).
Если функция объявлена как чисто виртуальная , класс становится абстрактным .
class Animal {
public:
virtual std::string Voice() const = 0; // Чисто виртуальная функция
};
Создать объект абстрактного класса нельзя.
Производные классы должны реализовать все чисто виртуальные функции.
Полиморфизм и контейнеры
Для хранения объектов разных типов в контейнере используются указатели. Однако важно учитывать управление памятью.
Пример использования указателей:
std::vector<Animal*> zoo;
zoo.push_back(new Cat("Tom"));
zoo.push_back(new Dog("Buffa"));
for (Animal* animal : zoo) {
Process(*animal); // Полиморфное поведение
delete animal; // Освобождение памяти
}
Важно:
Для корректного удаления объектов деструктор базового класса должен быть виртуальным .
class Animal {
public:
virtual ~Animal() {}
};
Умные указатели
Для безопасного управления памятью лучше использовать умные указатели , такие как std::unique_ptr или std::shared_ptr.
Пример с std::unique_ptr:
std::vector<std::unique_ptr<Animal>> zoo;
zoo.push_back(std::make_unique<Cat>("Tom"));
zoo.push_back(std::make_unique<Dog>("Buffa"));
for (const auto& animal : zoo) {
Process(*animal); // Полиморфное поведение
}
Умные указатели автоматически освобождают память при выходе из области видимости.
Итог
Наследование позволяет создавать иерархии классов и переиспользовать код.
Виртуальные функции обеспечивают полиморфное поведение.
Для работы с полиморфными объектами в контейнерах используйте указатели или умные указатели .
Абстрактные классы помогают определить общий интерфейс для производных классов.
Композиция предпочтительна, когда отношение между классами выражается через "имеет", а не "является".
Эти принципы являются основой объектно-ориентированного программирования в C++ и позволяют создавать гибкие, расширяемые и безопасные программы.