4. Наследование в С . Построение иерархии классов. Выделение общей части группы классов. Расщепление классов. - keykranz/oop_ex GitHub Wiki

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

Наследование – создание нового класса на основе старого. В с++ на классах возможно множественное наследование.

Идея наследования: мы можем развивать нашу программу за счет наследования.

Полиморфизм неразрывно связан с наследованием.

Различают прямое (А наследуется от В) и косвенное (А наследуется от В, которое наследуется от С; А и С косвенные) наследование

Схемы наследования

В C ++ есть 3 схемы наследования:

  • публичный (public)- все открытые члены базового класса становятся открытыми членами производного класса, защищенные члены становятся защищенными членами, закрытые элементы базового класса остаются закрытыми в этом классе и недоступны для членов производного класса. (расширение интерфейса)

публичные (public) и защищенные (protected) данные наследуются без изменения уровня доступа к ним; (доступ к унаследованным данным не меняется))

  • защищенный (protected) — все открытые и защищенные члены базового класса становятся защищенными членами производного класса.
  • приватный (private) — открытые и защищенные члены базового класса становятся закрытыми членами производного класса. Они все еще доступны из членов производного класса, но из других частей программы к ним обратиться нельзя. (происходит полная замена интерфейса)
class A
{
 private:
  int a;
 protected:
  int b;
 public:
  int f();
};

class B: public A //публичное наследование
{
 private:
  int c;
 protected:
  int d;
 public:
  int g();
};

B obj;
/* приватное наследование
а не наследуется. c, b, f- приватные, d - протектная, g - публичная
извне можно получить доступ только к g (интерфейсу). Г имеет доступ к d,c,b,f. Ф имеет доступ к b,a
происходит смена интерфейса Ф на Г

протектное наследование
а не наследуется. c - приватная, b,d,f - протектные, g - публичная
-''-.
смена интерфейса, но в данном случае интерфейс Ф БУДЕТ ДОСТУПЕН к классам, наследующимся из класса Б

публичное наследование
все члены сохраняют свой уровень доступа базового класса
а не наследуется.
c - приватная, b,d - протектные, f,g - публичные.
интерфейс ДОПОЛНЯЕТСЯ.
*/

Расширение и выделение общей части из разных классов.

Формировать иерархию наследования нужно в случаях:

Базовый класс выделяют в следующих случаях:

  1. Общая схема использования.
  2. Сходство между наборами операций и методов.
  3. Сходство реализации.

Правило: выделенное понятие должно выполнять только одну роль

Разбиваем класс в следующих случаях:

  1. Если один объект исполняет разные роли.
  2. Два подмножества операций в классе используются в разной манере.
  3. Методы класса имеют не связную реализацию.
  4. Одна сущность, но используется в разных частях программы.

Преобразование указателей

ВАЖНО! В языке С++ происходит неявное преобразование от указателя объекта производного класса к указателю на объект базового класса. То же самое касается ссылок.

A *p = new B;
// работая с указателем на базовый класс мы можем подменять один объект на другой (Неявное преобразование (пример с указателями))
B obj;
A& alias = obj; // тоже самое с ссылкой (Неявное преобразование (пример со ссылкой))

Иерархия

Встает необходимость в формировании иерархии наследования.

Набор классов, связанных отношением наследования, называют иерархией.

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

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

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

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

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

Конструкторы базовых объектов вызываются в порядке наследования (от более базового к более специализированному), деструкторы в обратном.

Два понятия:

  • Прямая база - относительно которой наследуемся. При наследовании может входить только 1 раз
  • Косвенная база - прямая база прямой базы. При наследовании - сколько угодно раз
class A
{
public:
	int a; //арара! объявление публичного поля!
	int (*b)(); //указатель на функцию! тоже публичное поле!
	//более того, указателей на ФУНКЦИИ ВООБЩЕ не должно быть! максимум - на методы
	int f();
	int f(int);
	int g();
}
class B
{
	int a; //допускается - приватные поля
	int b;
public:
	int f();
	int g; //арара публичное свойство!
	int h();
	int h(int);
}

class C: public A, public B {};

//метод рандомного класса
X::f(C *pc)
{
	pc->a = 1; //еггог! ошибка доступа
	pc->b(); //то же самое!
	pc->f(); //йух пойми какой Ф вызывать, то ли из А, толи из Б
	pc->f(1) //перегрузка не произойдёт
	pc->g = 1; //функция или переменная!?
	pc->h();
	pc->h(1); //а вот в этих двух всё ок
}