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 - публичные.
интерфейс ДОПОЛНЯЕТСЯ.
*/
Расширение и выделение общей части из разных классов.
Формировать иерархию наследования нужно в случаях:
Базовый класс выделяют в следующих случаях:
- Общая схема использования.
- Сходство между наборами операций и методов.
- Сходство реализации.
Правило: выделенное понятие должно выполнять только одну роль
Разбиваем класс в следующих случаях:
- Если один объект исполняет разные роли.
- Два подмножества операций в классе используются в разной манере.
- Методы класса имеют не связную реализацию.
- Одна сущность, но используется в разных частях программы.
Преобразование указателей
ВАЖНО! В языке С++ происходит неявное преобразование от указателя объекта производного класса к указателю на объект базового класса. То же самое касается ссылок.
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); //а вот в этих двух всё ок
}