Урок №154. Базовое наследование

  Юрий  | 

  Обновл. 14 Авг 2020  | 

 23448

 ǀ   6 

Теперь, когда мы уже поговорили о наследовании в абстрактном ключе, давайте поговорим о том, как это используется в языке C++ на практике.

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

Наследование в C++ происходит между классами и имеет тип отношений «является». Класс, от которого наследуют, называется родительским (или «базовым», «суперклассом»), а класс, который наследует, называется дочерним (или «производным», «подклассом»).

В диаграмме, представленной выше, Фрукт является родительским классом, а Яблоко и Банан — дочерними классами.

В этой диаграмме Треугольник является дочерним классом (родитель — Фигура) и родительским (для Правильного треугольника) одновременно.

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

Поскольку дочерние классы являются полноценными классами, то они могут (конечно) иметь и свои собственные члены. Сейчас мы это всё рассмотрим детально.

Класс Human


Вот простой класс Human для представления Человека:

В этом классе мы определили только те члены, которые являются общими для всех объектов этого класса. Каждый Человек (независимо от пола, профессии и т.д.) имеет Имя и Возраст.

Обратите внимание, в примере, приведенном выше, мы сделали все переменные-члены и методы класса открытыми. Это сделано ради простоты примера. Обычно переменные-члены нужно делать private. О средствах контроля доступа и о том, как это работает в наследовании, мы поговорим на соответствующих уроках.

Класс BasketballPlayer

Предположим, что нам нужно написать программу, которая будет отслеживать информацию о баскетболистах. Мы можем сохранять средний уровень игры баскетболиста и количество очков.

Вот наш незавершенный класс BasketballPlayer:

Также нам нужно знать Имя и Возраст баскетболиста, а эта информация уже у нас есть: она хранится в классе Human.

У нас есть три варианта добавления Имени и Возраста в BasketballPlayer:

   Добавить Имя и Возраст в класс BasketballPlayer непосредственно в качестве членов. Это плохой вариант, так как произойдет дублирование кода, который уже существует в классе Human. Любые обновления в Human также должны быть продублированы и в BasketballPlayer.

   Добавить класс Human в качестве члена в класс BasketballPlayer, используя композицию. Но возникает вопрос: «Может ли BasketballPlayer иметь Human?». Нет, это некорректно.

   Сделать так, чтобы BasketballPlayer унаследовал необходимые атрибуты от Human. Помните, что тип отношений в наследовании — «является». Является ли BasketballPlayer Human-ом (т.е. Человеком)? Конечно! Поэтому наш выбор — наследование.

Делаем класс BasketballPlayer дочерним


Чтобы класс BasketballPlayer унаследовал информацию от класса Human, нам нужно после объявления BasketballPlayer (class BasketballPlayer) использовать двоеточие, ключевое слово public и имя класса, от которого мы хотим унаследовать. Это называется открытым наследованием:

Проиллюстрируем:

Когда BasketballPlayer наследует свойства класса Human, то BasketballPlayer приобретает методы и переменные-члены класса Human. Кроме того, BasketballPlayer имеет еще два своих собственных члена: m_gameAverage и m_points. Здесь есть смысл, так как эти свойства специфичны только для BasketballPlayer, а не для каждого Human-а.

Таким образом, объекты BasketballPlayer будут иметь 4 члена:

   m_gameAverage и m_points от BasketballPlayer;

   m_name и m_age от Human.

Полный код программы:

Результат выполнения программы:

Anton

Это работает, так как anton является объектом класса BasketballPlayer, а все объекты класса BasketballPlayer имеют переменную-член m_name и метод getName(), унаследованные от класса Human.

Дочерний класс Employee

Теперь напишем еще один класс, который также будет наследовать свойства Human. Например, класс Employee (Работник). Работник «является» Человеком, поэтому использовать наследование здесь уместно:

Работник наследует m_name и m_age от Human-а (а также два метода) и имеет еще две собственные переменные-члены и один метод. Обратите внимание, метод printNameAndWage() использует переменные как из класса, к которому принадлежит (Employee::m_wage), так и с родительского класса (Human::m_name).

Проиллюстрируем:

Обратите внимание, классы Employee и BasketballPlayer не имеют прямых отношений, хотя оба наследуют свойства класса Human.

Вот полный код:

Результат выполнения программы:

Ivan: 350

Цепочка наследований


Можно наследовать от класса, который сам наследует от другого класса. При этом ничего примечательного или чего-нибудь особенного не происходит — всё аналогично тому, что мы рассмотрели выше. Например, напишем класс Supervisor. Супервайзер — это Работник, который «является» Человеком. Мы уже написали класс Employee, поэтому будем его использовать в качестве родительского класса:

Смотрим:

Все объекты Supervisor наследуют методы и переменные от Employee и Human, а также имеют свою собственную переменную-член m_nOverseesID.

Построив такие цепочки наследований, мы можем создать набор повторно используемых классов, которые будут иметь общие свойства вверху и становиться всё более специфичными на каждом последующем уровне наследования.

Почему наследование является полезным?

Использование наследования означает, что нам не нужно переопределять информацию из родительских классов в дочерних. Мы автоматически получаем методы и переменные-члены суперкласса через наследование, а затем просто добавляем специфичные методы или переменные-члены, которые хотим. Это не только экономит время и усилия, но также является очень эффективным: если мы когда-либо обновим или изменим базовый класс (например, добавим новые функции или исправим ошибку), то все наши производные классы автоматически унаследуют эти изменения!

Например, если мы добавим новый метод в Human, то Employee и Supervisor автоматически получат доступ к нему. Если мы добавим новую переменную в Employee, Supervisor также получит доступ к ней. Это позволяет создавать новые классы более простым, интуитивно-понятным способом!

Заключение


Наследование позволяет повторно использовать классы путем наследования членов этих классов другими классами. На следующих уроках мы будем разбираться детальнее, как это всё работает.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (127 оценок, среднее: 4,97 из 5)
Загрузка...

Комментариев: 6

  1. Аватар Почемучкин:

    А теперь вопрос, как хранить список всех потомков? Как хранить в общем массиве и Людей, и Баскетболистов, и Работников?

    1. Аватар Nikita:

      Класс Human должен иметь переменную type, в которой будет храниться информация о профессии. А список хранишь в void*. Когда надо получить доступ к элементу, считываешь профессию, а потом уже приводишь к нужному типу.

  2. Аватар kmish:

    Спасибо! Все понятно, кроме одного 🙂
    Почему на всех схемах стрелки направлены к Родителю? Ведь Наследование распространяется в противоположном направлении. Или эти стрелки что-то другое нам говорят?

    1. Аватар somebox:

      Все правильно. Стрелками показано, откуда берутся переменные для работы. То есть последующий класс наследует предыдущий, а не наоборот. Не Human переходит в Employee, Employee переходит в Human и берет у него все необходимое.

  3. Аватар dshadov:

    Добрый день!
    А почему у Вас в конструкторе по умолчанию Employee у переменной wage стоит тип int, а значение присваивается double 0.0?

    1. Юрий Юрий:

      Привет.
      Опечатка. Не знаю, как пропустил, но, спасибо, исправил.

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *