Глава 2. Наследование. Полиморфизм
































Л8.Ч2.Наследование.Конструкторы.Деструкторы.ppt
- Количество слайдов: 32
Глава 2. Наследование. Полиморфизм Из существующих классов можно строить новые классы, которые будут наследовать все свойства этих классов, но будут дополнены своими новыми специфическими свойствами.
п 1. Базовый и порожденный классы Пусть определен класс A class A { private: int d 1, d 2, d 3; public: A() {d 1 = d 2 = d 3 = 0; } /* конструктор по умолчанию */ A(int x, int y, int z) {d 1 = x; d 2 = y; d 3 = z; } // конструктор с аргументами void Print. A(){ cout<
Определим новый класс B тип порождения класс B class B: public A[, private A 1, . . . ] порожден из класса A (, A 1) список порождения { int d 3, d 4; public: B(); // конструктор без аргументов B(int, int); // конструктор с аргументами int Sum 3(); void Print. B(); …};
Класс A - базовый, класс B - порожденный. Говорят, что класс B НАСЛЕДУЕТ ВСЕ член-данные и член-функции класса A независимо от того, в какой части базового класса они находятся. Таким образом, класс B - это расширенный класс A. + все член-функции классов A и B.
Но это наследование имеет особенность такого рода: член-функции класса B не имеют прямого доступа к член-данным из части private базового класса. При попытке задать такую функцию в классе B void B: : Print. B() { cout<
Косвенный доступ В данном случае наследование понимается как возможность доступа член-функциями класса B к приватным данным базового класса A косвенно, т. е. - через член-функции класса A из части public. Таким образом, вывести член-данные из приватной части класса A можно, используя функцию Print. A() класса A void B: : Print. B() { Print. A(); cout<<’ ‘<
Функции вывода в классах A и B могут иметь и одинаковые имена: void A: : Print(){cout<
Доступ protected Однако не всегда удобно использовать приватные данные базового класса в порожденном таким косвенным способом, то есть через его член-функции. Можно разрешить член-функциям порожденного класса использовать член- данные базового класса непосредственно. Таким данным присваивается тип доступа protected (зашищенный).
class A { int d 1; protected : int d 2, d 3; public: . . . void Print() {cout<
В этом случае член-функциям класса B (только им и наследникам класса B) разрешен прямой доступ к член- данным из части protected класса A. Например, в классе B можно определить такую функцию int B : : FF(){ return d 2*d 4; }
: : Операция : : очень широко используется при наследовании: не только член- функции могут иметь одинаковые имена в базовом и порожденном классах, но и член-данные (в нашем примере d 3) и тогда конфликт неоднозначности разрешается этой операцией.
Например, int B: : Sum 3() { return A: : d 3 + d 3; } А член-функция вывода в классе B может быть определена так: void B: : Print(){ cout<< Get 1() <<’ ‘<< d 2 << ’ ‘<< A: : d 3 <<’ ‘<< d 4; } d 1 приватное член-данное базового класса – используется в порожденном косвенно.
Правила доступа Перечислим типы доступа для объявленных классов: а) член-функции класса A имеют доступ ко всем собственным член-данным d 1, d 2, d 3; б) объекты класса A используют член-функции и член-данные только из части public (но не из protected!); в) член-функции класа B используют собственные член-данные d 3, d 4 + d 2, d 3 из части protected класса A и всё из части public классов A и B; г) объекты класса B имеют доступ к член- данным и к член-функциям только из части public классов A и B.
Примеры void main() { A x; B y; x. d 1 = 3; // Ошибка: d 1 - из части private класса A x. d 2 = 5; // Ошибка: protected - это не public! x. Print(); /* Это верно - функция Print() из части public класса A */ int k = x. Get 1(); // Верно: Get 1() из части public A: k = x. d 1 int j = y. Get 1(); /* Тоже верно: Get 1 берет d 1 из базовой части объекта y: j = y. d 1 */ y. Print (); /*Верно: функция B: : Print() выведет член-данные базовой и порожденной части объекта y */ y. d 2 = 7; /* Ошибка: член-данное d 2 сохраняет тот же тип доступа protected в порожденном классе и функции main() недоступно */ }
Заметим еще раз Тип доступа protected является приватным для любых внешних функций! (в частности для main)
Список порождения Чем отличаются порождения class B: public A{. . . }; class B: protected A{. . . }; class B: private A{. . . }; ?
3 способа наследования public - это способ наследования, при котором типы доступа к член-данным и член-функциям базового класса в порожденном сохраняются, protected – понижает тип доступа public в базовом до protected в порожденном, private - это способ наследования, при котором типы доступа public и protected базового класса становятся private в порожденном (и могут использоваться только в нем, далее нет). Это делается в частных случаях, чтобы скрыть некоторые классы от дальнейших порождений.
Иерархии порождения Порожденный класс, в свою очередь, может быть базовым для другого класса. Возможны различные иерархии порождения. Например,
Иерархии порождения
Рассмотрим простое наследование Пусть в каждом классе определена функция с одинаковым именем F() A: : F(); в части public. Имеется определение объекта B: : F(); D d; и вызов C: : F(); d. F(); Из какого класса будет работать D: : F(); функция F()? Действует правило ДОМИНИРОВАНИЯ: одинаковое имя в порожденном классе подавляет имя в базовом.
Простое наследование То есть в нашем примере будет работать функция D: : F(). Таким образом, дерево наследования при поиске функции просматривается снизу вверх и находится функция, ближайшая к классу D (т. е. к классу, который является типом объекта).
A: : F(); B: : F(); C: : F(); D: : F(); D d; d. F(); C c; c. F();
В ООП можно всё, т. к. есть операция : : ! Вызвать функцию F() можно из любого класса, используя вновь операцию : : . Например, Но, конечно, нельзя d. B: : F(); // функция F() из класса B C c; или c. D: : F(); ! D *pd = new D; // Работает конструктор по умолчанию pd->C: : F(); /* вызывается функция F() из класса C для объекта класса D, определенного в динамической области */ B y; y. Print(); // Вывод всех полей y. d 1, y. d 2, y. A: : d 3, y. B: : d 3, y. d 4 y. A: : Print(); // Вывод только y. d 1, y. d 2, y. A: : d 3
Ограничения наследования Наследование порожденным классом свойств и методов базового имеет следующие ограничения - наследованию не подлежат • конструкторы; • деструкторы; • операции new и ‘=’ , перегруженные в базовом классе; • отношения дружественности.
Тэд Фейсон: «Это как в реальной жизни – друзья ваших родителей не обязательно ваши друзья и наоборот» . Т. е. отношения дружественности не передаются ни вверх, ни вниз по дереву наследования. Например, перегружаем потоковый вывод << для класса B вместо B: : Print(). Операцию << надо сделать «другом» и классу A и классу B, если в ней используются член-данные из частей private и protected.
п 2. Конструкторы порожденного класса Если объявляется объект порожденного класса, то за инициализацию базовой части объектов должен отвечать конструктор порожденного класса.
Конструкторы классов A и B Конструктор порожденного класса имеет специальную форму (рассмотрим на примере наших классов A и B) B: : B(int a, int b, int c) : A(a, b, c) список инициализации { d 3 = d 4 = b; } В список инициализации может быть включена инициализация член-данных порожденного класса. Например, B: : B(int a, int b, int c): A(a, b, c), d 3(b), d 4(b) { } (1)
Заметим, что для нашего примера инициализация может выглядеть и другими способами: B : : B(int a, int b, int c, int d, int r) : A(a, b, c), d 3(d), d 4(r) { } (2)
Конструктор по умолчанию класса A Если в базовом классе задан конструктор по умолчанию, то в списке инициализации конструктора порожденного класса он может отсутствовать. То есть конструктор класса B может иметь вид B: : B(int a, int b): A() {d 3 = a; d 4 = b; } или A() B: : B(int a, int b) {d 3 = a; d 4 = b; } Однако и в первом и во втором случае конструктор по умолчанию класса A работать будет!
Порядок работы конструкторов При инициализации объектов порожденного класса сначала работают конструкторы базового, а затем порожденных классов и так по всей иерархии порождения. Этот факт позволяет при инициализации член- данных порожденного класса использовать уже проинициализированные член-данные базового класса.
Необычный конструктор B: : Поэтому конструктор класса B может быть задан, например, таким необычным образом B: : B(int a, int b, int c) : A(a, b, b) // A : : d 1 = a; A : : d 2 = b; A : : d 3 = b { d 3 = d 2*A: : d 3; d 4 = c; }
Деструкторы, наоборот, сначала разрушают член-данные порожденного класса, затем базового.

