Перейти к содержанию
Educora
Средний20 мин8 / 10

Наследование и полиморфизм

Создавай производные классы, строй полиморфизм с помощью `virtual`-функций и `override` и используй абстрактные классы.

Проверь себя
В этом уроке ты узнаешь
  • Создавать производный класс и вызывать конструктор базового класса
  • Переопределять методы с помощью virtual и override
  • Объяснять, как полиморфизм работает через указатели и ссылки на базовый класс
  • Создавать абстрактный класс с чисто виртуальными функциями

В игре есть воин, маг и лучник. У всех есть имя и здоровье, все атакуют — но каждый по-своему. Вместо того чтобы повторять одинаковый код в трёх классах, общее можно один раз написать в базовом классе, а различия вынести в производные классы. Это наследование. А полиморфизм позволяет обращаться со всеми одинаково: ты говоришь «атакуй!», и каждый персонаж атакует по-своему.

Наследование

Запись class Warrior : public Character означает «Warrior наследуется от Character». Производный класс получает все члены базового и может добавить свои. protected-члены базового класса закрыты для внешнего кода, но видны производным классам. Конструктор производного класса вызывает конструктор базового в списке инициализации.

C++
#include <iostream>
#include <string>

class Character {
public:
    Character(const std::string& name, int health)
        : name_(name), health_(health) {}

    void status() const {
        std::cout << name_ << " has " << health_ << " HP\n";
    }

protected:
    std::string name_;
    int health_;
};

class Warrior : public Character {
public:
    Warrior(const std::string& name) : Character(name, 150) {}

    void shieldUp() {
        health_ += 20;
        std::cout << name_ << " raises a shield\n";
    }
};

int main() {
    Warrior w("Elvin");
    w.status();
    w.shieldUp();
    w.status();
    return 0;
}
Ожидаемый результат
Elvin has 150 HP
Elvin raises a shield
Elvin has 170 HP
status() достаётся от базового класса в готовом виде, а shieldUp() есть только у Warrior.

Виртуальные функции

К производному объекту можно обращаться через ссылку или указатель на базовый класс: каждый воин — это ещё и персонаж. Но какой метод тогда вызовется? У обычного метода — версия базового класса, потому что выбор делается при компиляции по типу ссылки. Если метод объявлен virtual, выбор делается во время работы программы по настоящему типу объекта.

C++
#include <iostream>

class Base {
public:
    virtual ~Base() = default;
    void hello() const { std::cout << "Base::hello\n"; }
    virtual void greet() const { std::cout << "Base::greet\n"; }
};

class Derived : public Base {
public:
    void hello() const { std::cout << "Derived::hello\n"; }
    void greet() const override { std::cout << "Derived::greet\n"; }
};

int main() {
    Derived d;
    const Base& ref = d;
    ref.hello();
    ref.greet();
    return 0;
}
Ожидаемый результат
Base::hello
Derived::greet
hello не виртуальный — решает тип ссылки (Base); greet виртуальный — решает настоящий тип объекта (Derived).

Абстрактные классы и полиморфизм

Иногда базовый класс не знает, как выполнить метод: «площадь фигуры» в общем виде не вычислить. Такой метод объявляют чисто виртуальным с помощью = 0. Класс хотя бы с одним чисто виртуальным методом — абстрактный: создать его объект нельзя, от него можно только наследоваться. Зато разные фигуры можно хранить в одном векторе и работать с ними одинаково:

C++
#include <iostream>
#include <memory>
#include <vector>

class Shape {
public:
    virtual ~Shape() = default;
    virtual int area() const = 0;
};

class Rect : public Shape {
public:
    Rect(int w, int h) : w_(w), h_(h) {}
    int area() const override { return w_ * h_; }
private:
    int w_, h_;
};

class Triangle : public Shape {
public:
    Triangle(int b, int h) : b_(b), h_(h) {}
    int area() const override { return b_ * h_ / 2; }
private:
    int b_, h_;
};

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Rect>(4, 3));
    shapes.push_back(std::make_unique<Triangle>(6, 5));
    int total = 0;
    for (const auto& s : shapes) {
        std::cout << "Area: " << s->area() << '\n';
        total += s->area();
    }
    std::cout << "Total: " << total << '\n';
    return 0;
}
Ожидаемый результат
Area: 12
Area: 15
Total: 27
Цикл не знает, какая именно перед ним Shape, но каждый объект вызывает свой area() — это и есть полиморфизм.
ТерминЗначение
базовый класскласс с общей частью (Shape)
производный класскласс, унаследованный от базового и расширяющий его (Rect)
virtualметод выбирается по настоящему типу объекта
overrideпросит компилятор проверить, что виртуальный метод действительно переопределён
= 0чисто виртуальный метод; класс становится абстрактным

Главное

  • class B : public A — B наследуется от A и получает его члены.
  • protected-члены видны производным классам, но не внешнему коду.
  • virtual-метод, вызванный через ссылку или указатель на базовый класс, выбирается по настоящему типу объекта.
  • override ловит ошибки при компиляции; у полиморфного базового класса деструктор должен быть virtual.
  • Чисто виртуальный метод (= 0) делает класс абстрактным: объекты создать нельзя.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Как унаследовать класс Archer от Character?