Наследование классов c: beginner, intermediate, advanced / Хабр

Содержание

C++. Наследование. Общие понятия. Использование модификаторов private, protected, public

Наследование. Общие понятия. Использование модификаторов private, protected, public при наследовании


Содержание

  • 1. Повторное использование кода. Наследование
  • 2. Синтаксис объявления классов, образующих иерархию. Базовый класс и производный (унаследованный) класс
  • 3. Передача характеристик наследуемому классу. Варианты реализации. Ограничение и расширение доступа. Ключевые слова private, protected, public
    • 3.1. Взаимодействие двух классов. Доступ из унаследованного класса к элементам базового класса
    • 3.2. Взаимодействие двух классов. Доступ из экземпляра (объекта) унаследованного класса к элементам базового класса с использованием модификаторов доступа private и protected
    • 3.3. Взаимодействие двух классов. Доступ к элементам базового класса из производного класса. Модификатор доступа public
  • 4. Наследование 3-х и более классов. Доступ из унаследованных классов
    • 4.1. Доступ к private-базовому классу из производных классов
    • 4.2. Доступ к protected и public базовому классу из производных классов
  • Связанные темы

Поиск на других ресурсах:

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

Идея наследования в программировании взята из природы и начинается еще с 60-х годов 20 века. В языке C++ концепция классов существенно усиливается благодаря внедрению наследованию в классах. В природе наследование позволяет добавлять к родительским качествам новые навыки. В программировании наследование – это свойство класса получать программный код (навыки) другого (базового) класса, добавляя к нему свой собственный код, тем самым расширяя его возможности.

С помощью механизма наследования можно без ограничений изменять любой класс, разработанный собственно программистом или другими программистами. При этом не следует перестраивать структуру этого класса. В базовый класс добавляются новые возможности. Возможности базового класса в унаследованном классе могут быть расширены, изменены, сужены, уничтожены или оставлены без изменений.

Наследование свойственно только классам и их характеристикам, а не переменным или функциям.

Проекты на языке C++ базируются на использовании конкретных классов, решающих поставленные задачи. Благодаря использованию наследования классы строятся постепенно, от базовых простых классов до специализированных классов, постепенно детализирующих решение. В итоге классы образуют иерархию классов. В этих иерархиях классы верхних уровней (базовые классы) описывают некоторые общие для всех характеристики, которые на нижних уровнях детализируются в унаследованных классах. Завершенный программный проект есть работоспособной системой, состоящей из иерархически связанных между собой классов. Количество классов в некоторых проектах может составлять десятки или сотни.

В отличие от некоторых языков программирования, в языке C++ разрешено множественное наследование.

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

В контексте понятия наследования определяется понятие повторного использования кода, которое в программировании на C++ определено двумя аспектами:

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

Порождение кода от базового класса является эффективным способом использования уже написанного кода для собственных нужд. Наследование является одной из частей повторного использования кода.

  ⇑

2. Синтаксис объявления классов, образующих иерархию. Базовый класс и производный (унаследованный) класс

Для реализации наследования требуется наличие как минимум двух классов. Если в программе класс с именем B должен быть унаследован от класса с именем A, то объявление классов выглядит следующим образом:

class A
{
  // Элементы класса A
  // .
.. }; class B : A { // Составляющие элементы класса B // ... };

В вышеприведенном объявлении класс B наследует часть характеристик или все характеристики класса A. Из класса B может быть унаследован другой (третий) класс, который получит часть или все характеристики классов A и B. Кроме того, из класса A могут быть унаследованы один или несколько классов, которые по отношению к классу B образуют параллельную ветвь иерархии. На рисунке 1 представлен один из возможных вариантов образования классами древовидной иерархии наследования.

 

Рисунок 1. Наследование. Дерево наследования, созданное классами

  ⇑

3. Передача характеристик наследуемому классу. Варианты реализации. Ограничение и расширение доступа. Ключевые слова private, protected, public

Важным при наследовании является вопрос: как передать характеристики базового класса в унаследованный класс? Здесь C++ дает широкий спектр возможных вариантов реализации.

Для указания того, какие элементы базового класса должны быть доступны в унаследованном классе, используется механизм инкапсуляции. Основой этого механизма является использование ключевых слов private, protected, public для предоставления доступа или запрета доступа к элементам базового класса.

  ⇑

3.1. Взаимодействие двух классов. Доступ из унаследованного класса к элементам базового класса

Элемент базового класса (переменная, функция) может быть объявлен с одним из трех модификаторов доступа:

  • private (скрытый). В этом случае доступа к этому элементу из унаследованного класса нет;
  • protected (защищенный) – позволяет использовать элемент базового класса в унаследованном классе;
  • public (общедоступный) – в унаследованном классе действует так же, как protected.

На рисунке 2 показаны все 3 вида доступа для двух классов, образующих иерархию наследования. Демонстрируется доступ к переменной a базового класса A из функции Func() унаследованного класса B. Те же правила действуют не только для переменных, но и для функций базового класса A.

Рисунок 2. Наследование для двух классов A и B. К private-членам базового класса доступ из унаследованного класса запрещен. К protected— и public— членам базового класса доступ разрешен

  ⇑

3.2. Взаимодействие двух классов. Доступ из экземпляра (объекта) унаследованного класса к элементам базового класса с использованием модификаторов доступа private и protected

Если рассматривать доступ из экземпляра (объекта) унаследованного класса к элементам базового класса, то отличие от предыдущего случая (пункт 3.1) заключается в использовании ключевого слова protected и способе наследования класса. Класс может быть унаследован как private, protected или public. Если класс унаследован как private, то ключевое слово private указывать не обязательно (см. рисунок 2).

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

Рисунок 3. Модификаторы доступа private, protected запрещают доступ к элементам класса из любого экземпляра класса

  ⇑

3.3. Взаимодействие двух классов. Доступ к элементам базового класса из производного класса. Модификатор доступа public

Если в базовом классе A некий элемент объявляется с модификатором доступа public, то экземпляр унаследованного класса B:

  • имеет доступ к элементам базового класса A, если производный класс B наследует класс A с модификатором доступа public;
  • не имеет доступа к элементам базового класса A, если производный класс B наследует класс A как private— или protected-.

На рисунке 4 наглядно показаны все возможные варианты такого доступа.

Рисунок 4. Доступ к public-элементам базового класса: 1) базовый класс унаследован как private-класс; 2) базовый класс унаследован как protected-класс; 3) базовый класс унаследован как public-класс.

  ⇑

4. Наследование 3-х и более классов. Доступ из унаследованных классов

Как было показано выше, базовый класс может быть унаследован путём указания перед его именем модификаторов private, protected, public. Отличие в использовании этих модификаторов проявляется в классе, являющемся производным от унаследованного класса. То есть, если дано три класса с именами A, B, C, последовательно наследующими друг друга, то влияние модификаторов будет именно на класс C.

  ⇑

4.1. Доступ к private-базовому классу из производных классов

На рисунке 5 изображен доступ к базовому классу A из производного класса C. Класс A в классе B наследуется как private-класс (скрытый). Это означает, что доступ к любому элементу класса A из производного класса C запрещен.

Рисунок 5. Запрещен доступ к элементам private-класса A из унаследованного класса C

  ⇑

4.
2. Доступ к protected— и public— базовому классу из производных классов

Если базовый класс A унаследован с модификаторами protected или public, то элементы этого класса доступны в унаследованных классах B и C.

 

Рисунок 6. Модификаторы protected и public перед именем базового класса. Открытие доступа к protected— и public-элементам базового класса

  ⇑


Связанные темы

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

  ⇑


 

Наследование. Урок 4 курса «Объектно-ориентированное программирование на Python»

Наследование – важная составляющая объектно-ориентированного программирования. Так или иначе мы уже сталкивались с ним, ведь объекты наследуют атрибуты своих классов. Однако обычно под наследованием в ООП понимается наличие классов и подклассов. Также их называют супер- или надклассами и классами, а также родительскими и дочерними классами.

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

Простое наследование методов родительского класса

В качестве примера рассмотрим два класса столов. Класс Table – родительский по отношению к DeskTable (письменные столы). Независимо от своего типа все столы имеют длину, ширину и высоту. Пусть для письменных столов также важна площадь поверхности. Общее вынесем в класс, частное – в подкласс.

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

class Table:
    def __init__(self, l, w, h):
        self.length = l
        self.width = w
        self.height = h
 
 
class DeskTable(Table):
    def square(self):
        return self. width * self.length
 
 
t1 = Table(1.5, 1.8, 0.75)
t2 = DeskTable(0.8, 0.6, 0.7)
print(t2.square())  # вывод: 0.48

В данном случае у класса DeskTable нет своего конструктора, поэтому он наследует его от родителя. При создании объектов передавать аргументы необходимо в обоих случаях. Попытка вызова DeskTable с пустыми скобками приведет к ошибке.

С другой стороны, экземпляры надкласса Table, согласно неким родственным связям, не наследуют метод square своего подкласса.

В этом смысле терминология «родительский и дочерний класс» не совсем верна. Наследование в ООП – это скорее аналог систематизации и классификации наподобие той, что есть в живой природе. Все млекопитающие имеют четырехкамерное сердце, но только носороги – рог.

Полное переопределение метода надкласса

Рассмотрим вариант программы с «цепочкой наследования». Пусть дочерний по отношению к Table класс DeskTable в свою очередь выступит родительским по отношению к ComputerTable (компьютерные столы):

class Table:
    def __init__(self, l, w, h):
        self. length = l
        self.width = w
        self.height = h
 
 
class DeskTable(Table):
    def square(self):
        return self.width * self.length
 
 
class ComputerTable(DeskTable):
    def square(self, monitor=0.0):
        return self.width * self.length - monitor
 
 
t3 = ComputerTable(0.8, 0.6, 0.7)
print(t3.square(0.3))  # вывод: 0.18

Допустим, по задумке разработчиков рабочая поверхность компьютерного стола может вычисляться за вычетом площади, которую занимает монитор. В результате метод square в ComputerTable имеет отличия.

Определив в дочернем классе метод, одноименный методу родительского, мы тем самым переопределяем метод родительского класса. При вызове square на экземпляры ComputerTable будет вызываться метод из этого класса, а не из родительского класса DeskTable.

В то же время ComputerTable наследует конструктор класса от своей «бабушки» – класса Table.

Дополнение, оно же расширение, метода

Часто требуется не столько заменить, то есть полностью переопределить, метод родительского класса в дочернем, сколько дополнить, то есть расширить, код метода родительского класса в дочернем.

В таких случаях решением является вызов метода надкласса в теле соответствующего метода подкласса. Обычно после этого в теле метода подкласса пишется дополнительный код.

Пусть имеется подкласс кухонных столов, для которых необходимо еще одно поле – количество посадочных мест. Из-за этой детали мы вынуждены в дочернем классе переопределить конструктор родительского.

class Table:
    def __init__(self, l, w, h):
        self.length = l
        self.width = w
        self.height = h
 
 
class KitchenTable(Table):
    def __init__(self, l, w, h, p):
        self.length = l
        self.width = w
        self.height = h
        self.places = p
 
 
t4 = KitchenTable(1.5, 2, 0.75, 6)

Поскольку существенная часть кода конструктора подкласса является такой же как в надклассе, правильнее будет вызвать метод другого класса, а не дублировать код:

class Table:
    def __init__(self, l, w, h):
        self.length = l
        self.width = w
        self.height = h
 
 
class KitchenTable(Table):
    def __init__(self, l, w, h, p):
        Table. __init__(self, l, w, h)
        self.places = p
 
 
t4 = KitchenTable(1.5, 2, 0.75, 6)

Здесь в теле конструктора KitchenTable мы вызываем метод __init__ через объект-класс Table, а не через объект-экземпляр. Вспомним, что в таких случаях метод вызывается как обычная функция (объект, к которому применяется метод, не передается в качестве первого аргумента). Поэтому в конструктор надкласса мы «вручную» передаем текущий экземпляр (self), записывая его перед остальными аргументами.

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

В Python c целью улучшения так называемой обслуживаемости кода можно использовать встроенную в язык функцию super. Наиболее распространенным вариантом ее применения является вызов метода родительского класса из метода подкласса:

class KitchenTable(Table):
    def __init__(self, l, w, h, p):
        super(). __init__(l, w, h)
        self.places = p

В данном случае аргумент self в скобках вызываемого родительского метода указывать явно не требуется.

Параметры со значениями по умолчанию у родительского класса

Рассмотрим случай, когда родительский класс имеет параметры со значениями по умолчанию, а дочерний – нет:

class Table:
    def __init__(self, l=1, w=1, h=1):
        self.length = l
        self.width = w
        self.height = h
 
 
class KitchenTable(Table):
    def __init__(self, p, l, w, h):
        Table.__init__(self, l, w, h)
        self.places = p

При таком определении классов можно создать экземпляр от Table без передачи аргументов для конструктора:

t = Table()

Можем ли мы создать экземпляр от KitchenTable, передав значение только для параметра p? Например, вот так:

k = KitchenTable(10)

Возможно ли, что p будет присвоено число 10, а l, w и h получат по единице от родительского класса? Невозможно, будет выброшено исключение по причине несоответствия количества переданных аргументов количеству требуемых конструктором:

. ..
    k = KitchenTable(10)
TypeError: __init__() missing 3 required
 positional arguments: 'l', 'w', and 'h'

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

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

class Table:
    def __init__(self, l=1, w=1, h=1):
        self.length = l
        self.width = w
        self.height = h
 
 
class KitchenTable(Table):
    def __init__(self, l=1, w=1, h=0.7, p=4):
        Table.__init__(self, l, w, h)
        self.places = p

Параметр p, которого нет у родительского класса, мы делаем последним не просто так. Бывает, объекты разных родственных классов создаются или обрабатываются в одном цикле, то есть по одному алгоритму. При этом у них должны быть одинаковые «интерфейсы», то есть одинаковое количество передаваемых в конструктор аргументов.

Поэтому лучше, когда методы родственных классов принимают одинаковое число параметров. А если разное, то у «лишних» должны быть значения по-умолчанию, чтобы при вызове конструктора их можно было бы не передавать. Если такие параметры находятся еще и в конце, передачу аргументов для предстоящих параметров можно выполнять без ключей.

Другой вариант – отказаться от конструктора в дочернем классе, а значение для поля places устанавливать отдельным вызовом метода:

class Table:
    def __init__(self, l=1, w=1, h=1):
        self.length = l
        self.width = w
        self.height = h
 
 
class KitchenTable(Table):
    places = 4
 
    def set_places(self, p):
        self.places = p

Здесь у всех кухонных столов по-умолчанию будет 4 места. Если мы хотим изменить значение поля places, можем вызвать метод set_places(). Хотя в случае Python можем сделать это напрямую, присвоив полю. При этом у экземпляра появится собственное поле places.

k = KitchenTable()
k.places = 6

Поэтому метод set_places() в общем-то не нужен.

В любом случае произвольное количество мест будет устанавливаться не в конструкторе, а отдельно. Если все же требуется указывать места при создании объекта, это можно сделать и в конструкторе родителя:

class Table:
    def __init__(self, l=1, w=1, h=1):
        self.length = l
        self.width = w
        self.height = h
        if isinstance(self, KitchenTable):
            p = int(input("Сколько мест: "))
            self.places = p

С помощью функции isinstance() проверяется, что создаваемый объект имеет тип KitchenTable. Если это так, то у него появляется поле places.

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

Практическая работа

Разработайте программу по следующему описанию.

В некой игре-стратегии есть солдаты и герои. У всех есть свойство, содержащее уникальный номер объекта, и свойство, в котором хранится принадлежность команде. У солдат есть метод «иду за героем», который в качестве аргумента принимает объект типа «герой». У героев есть метод увеличения собственного уровня.

В основной ветке программы создается по одному герою для каждой команды. В цикле генерируются объекты-солдаты. Их принадлежность команде определяется случайно. Солдаты разных команд добавляются в разные списки.

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

Отправьте одного из солдат первого героя следовать за ним. Выведите на экран идентификационные номера этих двух юнитов.

Курс с примерами решений практических работ:
pdf-версия, android-приложение


Типы наследования: Множественное наследование в C++

Наследование — одна из наиболее важных особенностей объектно-ориентированного программирования (ООП). Способность класса получать свойства и характеристики от другого класса называется наследованием.
Это процесс, в котором пользователи могут создавать новые классы из существующего класса.
Новый класс известен как производный класс, также известный как дочерний класс.
Принимая во внимание, что существующий класс определяется как базовый класс или родительский класс.
Наследование позволяет пользователю повторно использовать код. Во время наследования члены данных базового класса копируются в производный класс, и к ним можно получить доступ в зависимости от видимости.
Порядок наследования доступности всегда в порядке убывания. От общего к защищенному.

Что такое дочерний и родительский классы?

Дочерний класс: Дочерний класс определяется как класс, который наследует функции другого класса. Также известен как производный класс. Количество дочерних классов, которые могут быть унаследованы от класса с одним родителем, зависит от типа наследования.

Родительский класс: Класс, от которого дочерний класс наследует свои функции и свойства, называется родительским классом. Также известен как базовый класс. Класс с одним родителем может быть производным от нескольких дочерних классов, что может быть типом иерархического наследования. Это также зависит от различных типов наследования.

Синтаксис наследования:

Базовый синтаксис для определения дочернего и родительского классов во всех типах наследования в C++.

 класс parent_class
{
    //определение класса родительского класса
};
класс child_class : режим видимости parent_class
{
   //определение класса дочернего класса
}; 

Синтаксис Описание:

  • Parent_class: Имя родительского класса или базового класса.
  • Child_name: Имя дочернего класса или производного класса.
  • Visibility_mode: режим видимости указывает, как элементы данных дочернего класса наследуются от родительского класса.

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

Свойства наследования делают программирование более эффективным и в основном используются из-за их преимуществ.
Повторное использование кода: Одна из прекрасных и основных причин использования наследования заключается в том, что вы можете легко повторно использовать свой код.
Переходный характер: Наследование также используется из-за его транзитивного характера.
Всегда полезно повторно использовать уже существующий код, а не пытаться создать уже существующий.
Экономит время и повышает читаемость кода.

Режимы наследования:

Существует 3 типа режимов наследования:

  • Открытый режим: В открытом режиме мы можем легко получить доступ ко всем членам базового класса.
  • Частный режим: В частном режиме члены базового класса становятся частными в производном классе.
  • Защищенный режим: В этом защищенном режиме члены базового класса становятся защищенными в производном классе, и теперь члены

Доступен производному классу и его функциям-членам.

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

БАЗОВЫЙ КЛАСС ПРОИЗВОДНЫЙ КЛАСС ПРОИЗВОДНЫЙ КЛАСС ПРОИЗВОДНЫЙ КЛАСС
ОБЩЕСТВЕННЫЙ ЗАЩИЩЕННЫЙ ЧАСТНЫЙ
ОБЩЕСТВЕННЫЙ Общественный Защищенный Частный
ЗАЩИТА Защищенный Защищенный Частный
ЧАСТНЫЙ Не унаследовано / остается частным Не унаследовано / остается частным Не унаследовано / остается частным

Типы наследования в C++

Ниже приведены 5 типов наследования в C++

  • Одиночное наследование
  • Множественное наследование
  • Многоуровневое наследование
  • Иерархическое наследование
  • Гибридное наследование

Одиночное наследование:

При одиночном наследовании классу разрешено наследовать только от одного класса в соответствии со структурами данных в С++. . т. е. один подкласс наследуется только одним базовым классом.

Синтаксис одиночного наследования в C++:

 class sub_class : access_mode base_class
 {
     //тело подкласса
 }; 

Пример одиночного наследования в C++:

// базовый класс
класс транспортного средства {
    общественность: автомобиль () {
        cout << "Это транспортное средство" << endl;
    }
};
// подкласс, производный от двух базовых классов
класс Автомобиль: общественное транспортное средство { };

// основная функция
интервал основной () {
// создание объекта подкласса вызовет конструктор базовых классов
Объект автомобиля;
вернуть 0; }
 

Результат
Это транспортное средство

Множественное наследование:

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


Синтаксис множественного наследования в C++

 class sub_class : access_mode base_class1, base_class2
{
   //тело подкласса
}; 

Пример множественного наследования в C++:

класс А {
  публичный:
  А() {
       cout << "Вызван конструктор A" << endl;
      }
  };
  класс Б {
    публичный:
    Б () {
         cout << "Вызван конструктор B" << endl;
        }
   };
  class C: public B, public A { // Обратите внимание на порядок
    публичный:
    С () {
         cout << "Вызван конструктор C" << endl;
         }
    };
  интервал основной () {
     С с;
     вернуть 0;
  }
 

Вывод:
Конструктор B вызывается
Конструктор A вызывается
Конструктор C вызывается

Многоуровневое наследование:

В этом типе наследования производный класс создается из другого производного класса. Возьмем пример: есть 3 класса. A — базовый класс, производный от класса B. Следовательно, B — производный класс A. А класс C — производный класс B. Следовательно, B — базовый класс для класса C.

Пример многоуровневого наследования:

класс А {
    публичный:
    А() {
         cout << "Вызван конструктор A" << endl;
        }
    };
    класс B: общественность A {
       публичный:
       Б () {
            cout << "Вызван конструктор B" << endl;
            }
       };
    класс C: общественность B {
      публичный:
      С () {
          cout << "Вызван конструктор C" << endl;
          }
      };
    интервал основной () {
       С с;
       вернуть 0;
   }
 

Вывод:
Конструктор A называется
Конструктор B вызывается
Конструктор C вызывается

Иерархическое наследование:

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

Пример иерархического наследования:

// базовый класс
класс А {
    публичный:
    А() {
       cout<<"Это конструктор A"<< endl;
    }
};
// первый подкласс
класс B: общедоступный {};
// второй подкласс
класс C: общедоступный {};
// основная функция
интервал основной () {
    /* создание объекта подкласса вызовет конструктор базового класса */
  Б объект1;
  С объект2;
 вернуть 0;
}
 

Вывод:
Это конструктор A
Это конструктор B

Гибридное наследование:

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

Пример гибридного наследования:

// базовый класс
класс А {
    публичный:
    А() {
         cout << "Это А" << endl;
        }
  };
//базовый класс
класс Б {
    публичный:
    Б () {
         cout<<"Это B " <

Вывод:
Это A
Это B

Наследование в C++ – часто задаваемые вопросы

1.

Есть ли в C множественное наследование?

Нет, как мы знаем, наследование является свойством ООП, т.е. объектно-ориентированного программирования. Поэтому в C нет поддержки наследования на уровне компилятора.

2. В чем разница между множественным и многоуровневым наследованием?

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

При многоуровневом наследовании производный класс создается из другого производного класса. Возьмем пример: есть 3 класса. A — это базовый класс, производный от класса B. Следовательно, B — производный класс от A. А класс C — производный от класса B. Следовательно, B — это базовый класс для класса C.

3. Что такое надкласс и подкласс?

Суперкласс определяется как класс, из которого можно создать множество подклассов. Он также известен как родительский класс и базовый класс.
: Подклассы могут наследовать свойства суперклассов. Он наследует свойства суперкласса и создает атрибуты для себя.

4. Каковы преимущества наследования?

Преимущества наследования следующие:
Наследование позволяет повторно использовать код.
Это экономит время, так как нам не нужно переписывать основной код.
Структуру наследования легко понять.
Наследование также улучшает читаемость кода.

5. Каковы особенности наследования?

Основные черты наследования
При наследовании мы можем легко создавать новые классы из существующего класса.
Мы можем повторно использовать элементы данных родительского класса.

6. В чем разница между наследованием и полиморфизмом?

Наследование:
Способность класса получать свойства и характеристики от другого класса называется Наследованием.
Наследование поддерживает концепцию повторного использования кода.
Наследование может быть пяти типов, которые подробно обсуждались выше.
Полиморфизм:
Полиморфизм — это то, что можно определить несколькими способами. Полиморфизм позволяет объекту решать, какую форму функции реализовать во время компиляции, а также во время выполнения.
Оба они используются при разработке моделей.

Итак, в этом блоге мы попытались объяснить различные типы наследования в C++. Если вы хотите улучшить свои знания любого языка, включая C, C++, Java, Python, вы можете пройти по этой ссылке Prepbyte Courses.

Какие существуют типы наследования в C++?

Обзор

Наследование является одним из четырех столпов и наиболее важных функций объектно-ориентированного программирования на C++. Как следует из названия, наследование — это не что иное, как способность класса наследовать все свойства и характеристики другого класса.

Область применения статьи

  • Мы подробно рассмотрим наследование в объектно-ориентированном программировании.
  • Мы поймем различные типы наследования в объектно-ориентированном программировании и его использование с кодом.

Введение

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

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

В зависимости от того, как дочерний класс наследуется от родительского класса или сколько родительских классов наследует дочерний класс, в С++ существуют следующие типы наследования, а именно:

  • Одиночное наследование
  • Множественное наследование
  • Многоуровневое наследование
  • Иерархическое наследование
  • Гибридное наследование

Одиночное наследование в C++:

Одиночное наследование — это один из самых простых типов наследования среди других типов наследования в C++, в котором дочерний класс является производным только от одного родительского класса.

Как видно из изображения выше, класс B является производным от класса A с одним родителем.

Давайте посмотрим на синтаксис для выполнения одиночного наследования в C++:

 
 класс A
{
    
};
класс B: режим видимости A
{


}
 

Здесь класс A — родительский класс, класс B — дочерний класс, а класс B унаследован от класса A.

Рассмотрим пример одиночного наследования:

 
 // базовый класс
класс Животное
{
публичный:
    // конструктор базового класса
    Животное()
    {

        cout << "Я животное.\n\n";
    }

};

 // производный класс
класс Собака: общественное животное
{

публичный:
    // конструктор производного класса
    Собака()
    {

        cout << "Я собака.\n\n";
    }

};
основной ()
{
    // создаем объект дочернего класса
    Собака объект;
}
 

Вывод приведенного выше примера:

 
 Я животное.
Я собака.
 

В приведенном выше примере дочерний класс Dog наследует родительский класс Animal в режиме публичной видимости. Таким образом, все общедоступные и защищенные функции-члены и данные-члены класса Animal напрямую доступны в дочернем классе Dog.

Множественное наследование в C++

Множественное наследование — это еще один тип наследования в C++, при котором дочерний класс наследует свои свойства более чем от одного базового класса. Это означает, что дочерний класс наследуется от нескольких родительских классов, поэтому дочерний класс может получить объединенные функции всех этих классов. Дочерний класс получает доступ ко всем элементам данных базового класса в соответствии с используемым режимом видимости.

Как видно из изображения выше, класс C является производным от обоих базовых классов A и B.

Давайте посмотрим на синтаксис для выполнения множественного наследования в cpp:

 
 класс A
{
    
};
класс Б
{

};
класс C: режим видимости_1 A, режим видимости_2 B
{

};
 

Здесь класс A и класс B являются родительскими классами, а класс C является дочерним классом, унаследованным от родительских классов A и B.

Рассмотрим пример множественного наследования:

 
 // базовый класс
класс Животное
{
публичный:
    // конструктор базового класса
    Животное()
    {

        cout << "Я животное.\n\n";
    }

};

класс говорить
{
публичный:
    // конструктор базового класса
    Говорить()
    {

        cout << "Я могу говорить.\n\n";
    }

};

 // производный класс
класс Dog: public Animal, public Speak
{

};

основной ()
{
    // создаем объект дочернего класса
    Собака объект;
}
 

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

 
 Я животное.
Я могу говорить.
 

В приведенном выше примере дочерний класс Dog наследует родительские классы Animal и Speak в режиме публичной видимости. Таким образом, все общедоступные и защищенные функции-члены и данные-члены базовых классов напрямую доступны в дочернем классе. Кроме того, когда вы создаете объект для производного класса, вызываются конструкторы для двух базовых классов, Animal и Speak.

Многоуровневое наследование в C++:

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

Как видно из изображения выше, класс C является производным от класса B, который, в свою очередь, является производным от класса A.

Давайте посмотрим на синтаксис для выполнения многоуровневого наследования в cpp:

 
 класс A
{
    
};
класс B: режим видимости A
{


};
класс C: режим видимости B
{

};
 

Здесь класс A — это родительский класс, класс B — это дочерний класс, который наследуется от родительского класса A, а класс B — это родительский класс для класса C, который наследуется от класса B.

Посмотрим на примере многоуровневого наследования:

 
класс Животное
{
публичный:
    // конструктор базового класса
    Животное()
    {

        cout << "Я животное.\n\n";
    }

};


класс Собака: общественное животное
{

публичный:
    // конструктор производного класса
    Собака()
    {

        cout << "Я собака.\n\n";
    }

};
класс Мопс: публичная собака
{

публичный:
    мопс()
    {

        cout << "Я мопс. \n\n";
    }

};


основной ()
{
    // создаем объект дочернего класса
    Собака объект;
}
 

Вывод приведенного выше примера:

 
 Я животное.
Я собака.
Я мопс.
 

Здесь, в примере, объект производного класса Мопс может напрямую обращаться к членам класса Животное и Собака.

Иерархическое наследование в C++

При иерархическом наследовании несколько дочерних классов могут наследовать свойство от одного родительского класса.

Как видно из изображения выше, дочерние классы B, C и D являются производными от одного базового класса A.

Давайте посмотрим на синтаксис для выполнения иерархического наследования в cpp:

 
 класс A
{
    
};
класс B: режим видимости A
{


};
класс C: режим видимости A
{


};
класс D: режим видимости A
{


};
 

Здесь класс A является родительским классом, а классы B, C и D являются дочерними классами.

Рассмотрим пример иерархического наследования:

 
 // базовый класс
класс Животное
{
публичный:
    // конструктор базового класса
    Животное()
    {

        cout << "Я животное. \n\n";
    }

};

 // производный класс
класс Собака: общественное животное
{

};
 // производный класс
класс Свинья: общественное животное
{

};
 // производный класс
класс Кошка: общественное животное
{

};
основной ()
{
    // создаем объект дочернего класса
    Собака obj1;
    Свинья pbj2;
    Кот obj3;
}
 

Вывод приведенного выше примера:

 
 Я животное.
Я животное.
Я животное.
 

Здесь, в примере, все производные классы могут получить доступ к открытым членам базового класса Animal. Когда он создает объекты этих трех производных классов, он вызывает конструктор базового класса для всех трех объектов.

Гибридное наследование в C++

Как следует из названия гибрид, он объединяет два или более типов наследования. Из-за смешения нескольких типов наследования он является одним из самых сложных.

Как видно из изображения выше, гибридное наследование представляет собой комбинацию иерархического и множественного наследования.

Давайте посмотрим на синтаксис для выполнения иерархического наследования в cpp:

 
 класс A
{
    
};
класс B: режим видимости A
{


};
класс C: режим видимости A
{


};
класс D: режим видимости B, режим видимости C
{


};
 

Здесь класс A — родительский класс, классы B и C — дочерние классы, а классы B и C — родительский класс для класса D, который наследует свои свойства от классов B и C.

Давайте посмотрим на пример гибридного наследования:

 
 // базовый класс
класс Животное
{
публичный:
    // конструктор базового класса
    Животное()
    {

        cout << "Я животное.\n\n";
    }

};

 // производный класс
класс Собака: общественное животное
{

};
 // производный класс
класс Свинья: общественное животное
{

};
 // производный класс
класс Милый: публичная собака, публичная свинья
{

};
основной ()
{
    // создаем объект дочернего класса
    Симпатичный объект;
}
 

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

 
 Я животное. 
Я животное.
 

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

Что такое дочерний и родительский классы?

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

  • Дочерние классы :

    Дочерний класс — это класс, который наследует свойства другого класса, также известного как производный класс. Дочерний класс может получить доступ к элементам данных и функциям-членам родительского класса в соответствии с режимом видимости, указанным во время объявления дочернего класса.

  • Родительские классы :

    Родительский класс — это класс, от которого происходит и наследуется дочерний; он также известен как базовый класс. Класс с одним родителем может быть производным от нескольких дочерних классов, а классы с несколькими родителями могут наследовать класс с одним дочерним элементом в зависимости от применяемого типа наследования.

 
 класс А
{ // Код
};
класс B: общедоступный A
{
// Код
};
 

Здесь класс B наследуется от класса A, тогда класс B будет вести себя как дочерний класс, а класс A будет действовать как родительский класс или базовый класс.

Что такое наследование?

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

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

Узнайте больше о наследовании

Зачем и когда использовать наследование?

Наследование используется из-за преимуществ, которые оно предоставляет; наиболее важные преимущества наследования обсуждаются ниже:

  1. Код Повторное использование :

    Одним из основных преимуществ является повторное использование кода; мы можем снова использовать тот же код, наследуя функции и свойства одного класса другому.

  2. Уменьшить избыточность кода :

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

  3. Переходный характер наследования :

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

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

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

Режимы видимости

Важной особенностью наследования является знание того, как производный класс наследует базовый класс и какие члены базового класса будут приобретены и доступны производному классу. Для этого у нас есть три типа режимов видимости (также известных как модификаторы доступа) для всех типов наследования в C++:

  • Режим публичной видимости :

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

    Публичные члены родителя доступны дочернему классу и всем другим классам. Защищенные члены базового класса доступны только внутри производного класса и его унаследованных классов. Однако закрытые члены недоступны для дочернего класса.

Синтаксис использования режимов видимости следующий:

 
 класс имя_дочернего_класса :: режим_видимости имя_родительского_класса
{
   
}
 

Режим видимости может быть общедоступным, частным или защищенным.

Давайте рассмотрим пример, чтобы понять концепцию режима публичной видимости:

 
 class Parent
{
    частный:
     число х1;
     
    защищено:
     интервал х2;
     
    публичный:
     число х3;
};
дочерний класс: общедоступный родитель
{

};
 

Защищенная переменная x2 будет унаследована от класса Parent и доступна в классе Child. Точно так же публичная переменная x3 будет унаследована от родительского класса и доступна в дочернем классе, но приватная переменная x1 не будет доступна в дочернем классе.

  • Частный режим видимости:

    В режиме частной видимости, когда мы наследуем дочерний класс от родительского класса, все члены базового класса (общедоступные, частные и защищенные члены) станут частными в производном классе.

    Доступ к этим членам за пределами производного класса ограничен, и функции-члены производного класса могут получить к ним доступ только.

Давайте рассмотрим пример, чтобы понять концепцию режима частной видимости:

 
 класс Родительский
{
    частный:
     число х1;
     
    защищено:
     интервал х2;
     
    публичный:
     число х3;
};
дочерний класс: частный родитель
{

};
 

Поскольку режим видимости является закрытым, все члены базового класса стали закрытыми в производном классе. Ошибка возникает, когда объект производного класса пытается получить доступ к этим членам за пределами производного класса.

  • Защищенный режим видимости:

    В защищенном режиме видимости, когда мы наследуем дочерний класс от родительского класса, все члены базового класса станут защищенными членами производного класса.

    Из-за этого эти члены теперь доступны только производному классу и его функциям-членам. Эти члены также могут быть унаследованы и доступны для унаследованных подклассов этого производного класса.

Давайте рассмотрим пример, чтобы понять концепцию защищенного режима видимости:

 
 class Parent
{
    частный:
     число х1;
     
    защищено:
     интервал х2;
     
    публичный:
     число х3;
};
класс Дочерний: защищенный Родитель
{

};
 

Поскольку режим видимости защищен, защищенные и общедоступные члены класса Parent становятся защищенными членами класса Child.

Защищенная переменная x2 будет унаследована от класса Parent и доступна в классе Child. Точно так же общедоступная переменная x3 будет унаследована от родительского класса как защищенный член и будет доступна в дочернем классе, но приватная переменная x1 не будет доступна в дочернем классе.

Данная таблица суммирует три вышеуказанных режима наследования в C++:

ПРИМЕЧАНИЕ: Если режим видимости не указан, то по умолчанию рассматривается приватный режим.

Алмазная задача

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

Алмазная проблема возникает, когда два класса B и C наследуются от класса A, а затем класс D наследуется от классов B и C, поэтому, если в классе A определен метод, который классы B и C переопределили, то класс D сталкивается с неоднозначность того, какую версию метода наследует класс D: версию B или версию C?

Эта проблема известна как проблема алмаза из-за формы диаграммы наследования классов в этой ситуации, как вы можете видеть на изображении выше.

Следующий пример поможет проиллюстрировать неоднозначную ситуацию, вызванную множественным наследованием:

 
 // базовый класс
класс А
{
публичный:
    целая переменная1;
};

// класс 1
класс B: общедоступный A
{

публичный:
    целая переменная2;
};

// класс 2
класс C: общедоступный A
{

публичный:
    интервал переменная3;
};

// производный класс 3
класс D: общедоступный B, общедоступный C
{

публичный:
    интервал переменная4;
};

основной ()
{
    // создаем объект производного_класса
    Д обж;

    обж. вар1 = 10; // двусмысленный
    обж.вар2 = 20;
    obj.var3 = 30;
    obj.var4 = obj.var1 + obj.var2 + obj.var3;
    
    cout << obj.var4 << endl;
}
 

В приведенном выше примере будет выдана ошибка, утверждающая, что переменная var1 неоднозначна и вызвана неоднозначностью. Это происходит потому, что существует две копии члена данных var1 базового класса A, одна для класса B и одна для класса C. Когда объект obj дочернего класса D пытается получить доступ к этой переменной-члену, не указано, какая копия переменной var1. Следовательно, возникает двусмысленность, вызванная ошибкой.

Есть два способа решения проблемы алмаза и устранения двусмысленности:

  • Использование оператора разрешения области действия :

Давайте рассмотрим тот же пример, что и выше, и посмотрим, как оператор разрешения прицела может помочь справиться с проблемой алмаза.

 
 // базовый класс
класс А
{
публичный:
    целая переменная1;
};

// класс 1
класс B: общедоступный A
{

публичный:
    целая переменная2;
};

// класс 2
класс C: общедоступный A
{

публичный:
    интервал переменная3;
};

// производный класс 3
класс D: общедоступный B, общедоступный C
{

публичный:
    интервал переменная4;
};

основной ()
{
    // создаем объект производного_класса
    Д обж;

    obj. A::var1 = 10; // теперь однозначно
    обж.вар2 = 20;
    obj.var3 = 30;
    obj.var4 = obj.A::var1 + obj.var2 + obj.var3;
    
    cout << obj.var4 << endl;
}
 

Вывод вышеуказанной программы будет:

Ошибка неоднозначности будет удалена, поскольку оператор разрешения области указывает, какая копия var1 будет использоваться.

 
 obj.A::var1;
 

Здесь осуществляется доступ к версии var1 для A.

  • Использование виртуального ключевого слова :

С помощью оператора разрешения области нам удалось убрать ошибку неоднозначности, но все равно есть две копии родительского класса. Если вам требуется только одна копия базового класса, есть другое решение, использующее ключевое слово virtual.

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

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

 
// базовый класс
класс А
{
публичный:
    целая переменная1;
};

класс B: виртуальный публичный A
{

публичный:
    целая переменная2;
};

класс C: виртуальный публичный A
{

публичный:
    интервал переменная3;
};

класс D: общедоступный B, общедоступный C
{

публичный:
    интервал переменная4;
};

основной ()
{
    // создаем объект производного_класса
    Д обж;

    обж.вар1 = 10; // теперь он однозначен, и мы можем получить к нему доступ обычным способом
    обж.вар2 = 20;
    obj.var3 = 30;
    obj.var4 = obj.var1 + obj.var2 + obj.var3;

    cout << obj.var4 << endl;

}

 

Вывод приведенной выше программы будет следующим:

В приведенном выше примере классы B и C наследуют базовый класс A как виртуальный, поэтому будет создана только одна копия базового класса A. Итак, теперь Объект производного класса D имеет только одну копию элементов данных базового класса. Следовательно, будет создана только одна копия переменной var1, что делает ее безошибочной и однозначной.

Заключение:

  • Наследование — одна из основных особенностей объектно-ориентированного программирования в CPP, позволяющая нам наследовать свойства другого класса.
  • В С++ в основном существует пять типов наследования: одиночное наследование, множественное наследование, многоуровневое наследование, гибридное наследование и иерархическое наследование.
  • Режимы видимости используются, чтобы узнать, как производный класс наследует базовый класс
  • В режиме публичной видимости все члены базового класса также сохраняют имя в производном классе.
  • В режиме частной видимости все члены базового класса станут частными в производном классе.
  • В защищенном режиме видимости все члены базового класса станут защищенными членами производного класса.
Оставить комментарий

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

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