5

Основы программирования на 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); // Полиморфное поведение

}

  • Умные указатели автоматически освобождают память при выходе из области видимости.


Итог

  1. Наследование позволяет создавать иерархии классов и переиспользовать код.

  2. Виртуальные функции обеспечивают полиморфное поведение.

  3. Для работы с полиморфными объектами в контейнерах используйте указатели или умные указатели .

  4. Абстрактные классы помогают определить общий интерфейс для производных классов.

  5. Композиция предпочтительна, когда отношение между классами выражается через "имеет", а не "является".

Эти принципы являются основой объектно-ориентированного программирования в C++ и позволяют создавать гибкие, расширяемые и безопасные программы.

Лига программистов

2K постов11.8K подписчиков

Правила сообщества

- Будьте взаимовежливы, аргументируйте критику

- Приветствуются любые посты по тематике программирования

- Если ваш пост содержит ссылки на внешние ресурсы - он должен быть самодостаточным. Вариации на тему "далее читайте в моей телеге" будут удаляться из сообщества

0
Автор поста оценил этот комментарий

бля ну это какая-то средненькая обучалка по юнити, таких вагон и маленькая тележка насобирается. жаль, не буду смотреть, форматирования нет, а ради пятиднеевного аккаунта самому форматировать ну такое, не стоит напрягаться.


тоже самое у тебя будет с остальными постами.

нет форматирования -- нах никому не нужно.

там же хер что поймёшь, учитель!


всего наилучшего!

раскрыть ветку (1)
Автор поста оценил этот комментарий

ну не смотри, и тебе всего хорошего

0
Автор поста оценил этот комментарий

приведи пример. наследование чего от чего и какой код удалось "не дублировать".

раскрыть ветку (1)
Автор поста оценил этот комментарий

// Базовый класс для всех игровых объектов

class GameObject {

protected:

int x, y; // Координаты

int width, height; // Размеры

bool isActive; // Активен ли объект

public:

GameObject(int x, int y, int w, int h)

: x(x), y(y), width(w), height(h), isActive(true) {}

virtual void update() = 0; // Чисто виртуальная функция

virtual void render() = 0;

void setPosition(int newX, int newY) {

x = newX;

y = newY;

}

bool checkCollision(const GameObject& other) const {

// Проверка пересечения прямоугольников

return isActive && other.isActive &&

x < other.x + other.width &&

x + width > other.x &&

y < other.y + other.height &&

y + height > other.y;

}

virtual ~GameObject() {}

};

// Класс для персонажей

class Character : public GameObject {

protected:

int health;

int speed;

public:

Character(int x, int y, int w, int h, int hp)

: GameObject(x, y, w, h), health(hp), speed(5) {}

void move(int dx, int dy) {

if (!isActive) return;

x += dx * speed;

y += dy * speed;

}

virtual void takeDamage(int amount) {

health -= amount;

if (health <= 0) {

isActive = false;

}

}

};

// Класс игрока

class Player : public Character {

private:

int score;

public:

Player(int x, int y)

: Character(x, y, 50, 50, 100), score(0) {}

void update() override {

// Обработка ввода игрока

// Движение, стрельба и т.д.

}

void render() override {

if (isActive) {

// Отрисовка игрока

}

}

void addScore(int points) {

score += points;

}

};

// Класс врага

class Enemy : public Character {

private:

int attackPower;

public:

Enemy(int x, int y)

: Character(x, y, 40, 40, 50), attackPower(10) {}

void update() override {

// Логика ИИ врага

}

void render() override {

if (isActive) {

// Отрисовка врага

}

}

void attack(Player& player) {

if (checkCollision(player)) {

player.takeDamage(attackPower);

}

}

}; В этом примере мы избежали дублирования следующего кода:

Поля и методы, общие для всех игровых объектов (координаты, размеры, проверка коллизий):

int x, y;

int width, height;

bool isActive;

bool checkCollision(const GameObject& other);

Этот код находится в базовом классе GameObject и используется всеми игровыми объектами.

Общая логика для всех персонажей (здоровье, скорость, движение, получение урона):

int health;

int speed;

void move(int dx, int dy);

void takeDamage(int amount);

Эта логика находится в классе Character и наследуется как игроком, так и врагами.

Без наследования нам пришлось бы копировать этот код в каждый класс игрового объекта, что привело бы к:

Увеличению объема кода

Сложности внесения изменений (пришлось бы менять один и тот же код во многих местах)

Рискам появления ошибок из-за несогласованных изменений

Трудностям поддержки и расширения кода

показать ответы
0
Автор поста оценил этот комментарий

ты считаешь, что это достаточный ответ?


паттерны проектирования и банда четырёх это всё читано-перечитано.


я просил реальные примеры из жизни, с которыми ты сталкивался.

раскрыть ветку (1)
Автор поста оценил этот комментарий

в Windows Forms часто применяется наследование

показать ответы
0
Автор поста оценил этот комментарий

ты считаешь, что это достаточный ответ?


паттерны проектирования и банда четырёх это всё читано-перечитано.


я просил реальные примеры из жизни, с которыми ты сталкивался.

раскрыть ветку (1)
Автор поста оценил этот комментарий

мне наследование помогало избежать дублирование кода

показать ответы
0
Автор поста оценил этот комментарий

Наследование позволяет создавать иерархии классов и переиспользовать код.

накинь пару реальных примеров из жизни, где ты применял наследование и для чего.

раскрыть ветку (1)
Автор поста оценил этот комментарий

в паттернах проектирования

показать ответы