11. Наследование и полиморфизм
Что такое наследование
Наследование — это механизм, позволяющий одному классу (наследнику) использовать функциональность другого класса (родителя). Это позволяет не дублировать код и создавать более гибкие структуры.Пример из жизни: у вас есть класс «Животное». От него можно унаследовать «Кошку», «Собаку», «Птицу». Все они умеют двигаться и дышать, но делают это по-своему.
Основной синтаксис
class Parent {
public:
void sayHello() {
cout << "Привет из базового класса!" << endl;
}
};class Child : public Parent {
public:
void sayBye() {
cout << "Пока из дочернего класса!" << endl;
}
};
int main() {
Child c;
c.sayHello(); // метод унаследован от Parent
c.sayBye(); // собственный метод Child
return 0;
}
Результат
Привет из базового класса! Пока из дочернего класса!
Типы наследования
В C++ существуют три типа наследования:- public — открытое: публичные члены родителя остаются доступными.
- protected — защищённое: члены становятся доступными только внутри наследников.
- private — закрытое: наследник получает копию, но не даёт доступ дальше.
Пример
class Base {
public:
int x = 10;
};class Derived : protected Base {
public:
void show() {
cout << "x = " << x << endl;
}
};
int main() {
Derived d;
d.show();
// cout << d.x; // Ошибка! x защищённый.
return 0;
}
Конструкторы и наследование
Конструктор базового класса вызывается автоматически при создании объекта наследника.
#include <iostream>
using namespace std;class Animal {
public:
Animal() {
cout << "Создано животное" << endl;
}
};
class Dog : public Animal {
public:
Dog() {
cout << "Создана собака" << endl;
}
};
int main() {
Dog d;
return 0;
}
Создано животное Создана собака
Переопределение методов
Иногда нужно, чтобы дочерний класс переопределял поведение родителя.
class Animal {
public:
void speak() {
cout << "Животное издаёт звук" << endl;
}
};class Cat : public Animal {
public:
void speak() {
cout << "Кошка говорит: Мяу!" << endl;
}
};
int main() {
Cat c;
c.speak(); // Мяу!
return 0;
}
Проблема без виртуальных функций
Если вы используете указатель на базовый класс, переопределённый метод может не вызываться:
Animal* a = new Cat();
a->speak(); // Выведет: Животное издаёт звук (а не Мяу!)
Это поведение можно исправить с помощью виртуальных функций.
Полиморфизм и виртуальные функции
Полиморфизм — это возможность вызывать разные реализации одного метода через один интерфейс.
#include <iostream>
using namespace std;class Animal {
public:
virtual void speak() {
cout << "Животное издаёт звук" << endl;
}
};
class Cat : public Animal {
public:
void speak() override {
cout << "Кошка говорит: Мяу!" << endl;
}
};
class Dog : public Animal {
public:
void speak() override {
cout << "Собака говорит: Гав!" << endl;
}
};
int main() {
Animal* zoo[2];
zoo[0] = new Cat();
zoo[1] = new Dog();
for (int i = 0; i < 2; i++) {
zoo[i]->speak();
}
for (int i = 0; i < 2; i++) delete zoo[i];
return 0;
}
Результат
Кошка говорит: Мяу! Собака говорит: Гав!
Ключевые слова
- virtual — объявляет метод виртуальным, позволяя переопределять его в наследниках.
- override — указывает, что метод переопределяет родительский.
- final — запрещает дальнейшее переопределение.
Чисто виртуальные функции и абстрактные классы
Иногда базовый класс задаёт только общий интерфейс, но не содержит реализацию. Такие классы называются абстрактными.
class Shape {
public:
virtual void draw() = 0; // чисто виртуальная функция
};class Circle : public Shape {
public:
void draw() override {
cout << "Рисуем круг" << endl;
}
};
int main() {
Circle c;
c.draw();
return 0;
}
Абстрактный класс нельзя создать напрямую — только через его наследников.
Мини-проект: зоопарк
#include <iostream>
#include <vector>
#include <memory>
using namespace std;class Animal {
public:
virtual void speak() = 0;
virtual ~Animal() {}
};
class Cat : public Animal {
public:
void speak() override { cout << "Мяу!" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Гав!" << endl; }
};
int main() {
vector<unique_ptr<Animal>> zoo;
zoo.push_back(make_unique<Cat>());
zoo.push_back(make_unique<Dog>());
for (auto& animal : zoo) {
animal->speak();
}
return 0;
}
Что здесь происходит
- Базовый класс Animal содержит чисто виртуальную функцию speak().
- Классы Cat и Dog переопределяют её по-своему.
- Мы используем unique_ptr — умные указатели для безопасного управления памятью.
Типичные ошибки новичков
- Забывают ключевое слово virtual в базовом классе, и методы не переопределяются.
- Создают объект абстрактного класса (что невозможно).
- Не делают виртуальный деструктор в базовом классе, что может привести к утечкам памяти.
Советы новичкам
- Начинайте с простого: наследование — это "родитель-ребёнок" в коде.
- Всегда добавляйте virtual в базовый класс, если собираетесь использовать указатели.
- Используйте override — это помогает компилятору находить ошибки.
- Учитесь думать абстрактно: определяйте общие действия, а детали реализуйте в наследниках.
Итоги урока
Теперь вы знаете:- Что такое наследование и как оно помогает избегать дублирования кода.
- Как работает полиморфизм и виртуальные функции.
- Что такое абстрактные классы и чисто виртуальные методы.
- Как создавать гибкие, масштабируемые программы с использованием ООП.