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

Скачать презентацию Глава 2. Наследование.   Полиморфизм Скачать презентацию Глава 2. Наследование. Полиморфизм

Л8.Ч2.Наследование.Конструкторы.Деструкторы.ppt

  • Количество слайдов: 32

>  Глава 2. Наследование.   Полиморфизм Из существующих классов можно строить новые Глава 2. Наследование. Полиморфизм Из существующих классов можно строить новые классы, которые будут наследовать все свойства этих классов, но будут дополнены своими новыми специфическими свойствами.

>  п 1. Базовый и порожденный    классы Пусть определен класс п 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: Определим новый класс 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 - порожденный. Говорят, что класс B НАСЛЕДУЕТ ВСЕ член-данные и член-функции класса A независимо от того, в какой части базового класса они находятся. Таким образом, класс B - это расширенный класс A. + все член-функции классов A и B.

>  Но это наследование имеет особенность такого рода: член-функции класса 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: Функции вывода в классах A и B могут иметь и одинаковые имена: void A: : Print(){cout<

>   Доступ protected Однако не всегда удобно использовать приватные данные базового класса Доступ protected Однако не всегда удобно использовать приватные данные базового класса в порожденном таким косвенным способом, то есть через его член-функции. Можно разрешить член-функциям порожденного класса использовать член- данные базового класса непосредственно. Таким данным присваивается тип доступа protected (зашищенный).

>class A  { int d 1;   protected : int d 2, class A { int d 1; protected : int d 2, d 3; public: . . . void Print() {cout<

>В этом случае член-функциям класса B (только им и наследникам класса B) разрешен прямой В этом случае член-функциям класса B (только им и наследникам класса B) разрешен прямой доступ к член- данным из части protected класса A. Например, в классе B можно определить такую функцию int B : : FF(){ return d 2*d 4; }

>   : : Операция : : очень широко используется при наследовании: не : : Операция : : очень широко используется при наследовании: не только член- функции могут иметь одинаковые имена в базовом и порожденном классах, но и член-данные (в нашем примере d 3) и тогда конфликт неоднозначности разрешается этой операцией.

>Например, int B: : Sum 3() { return A: : d 3 + d Например, 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. Примеры 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 является приватным для любых внешних функций! Заметим еще раз Тип доступа protected является приватным для любых внешних функций! (в частности для main)

>  Список порождения Чем отличаются порождения class B: public A{. . . }; Список порождения Чем отличаются порождения class B: public A{. . . }; class B: protected A{. . . }; class B: private A{. . . }; ?

>  3 способа наследования public - это способ наследования, при котором типы доступа 3 способа наследования public - это способ наследования, при котором типы доступа к член-данным и член-функциям базового класса в порожденном сохраняются, protected – понижает тип доступа public в базовом до protected в порожденном, private - это способ наследования, при котором типы доступа public и protected базового класса становятся private в порожденном (и могут использоваться только в нем, далее нет). Это делается в частных случаях, чтобы скрыть некоторые классы от дальнейших порождений.

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

>Иерархии порождения Иерархии порождения

> Рассмотрим простое наследование Пусть в каждом классе определена функция с одинаковым именем F() Рассмотрим простое наследование Пусть в каждом классе определена функция с одинаковым именем F() A: : F(); в части public. Имеется определение объекта B: : F(); D d; и вызов C: : F(); d. F(); Из какого класса будет работать D: : F(); функция F()? Действует правило ДОМИНИРОВАНИЯ: одинаковое имя в порожденном классе подавляет имя в базовом.

>  Простое наследование То есть в нашем примере будет работать функция D: : Простое наследование То есть в нашем примере будет работать функция D: : F(). Таким образом, дерево наследования при поиске функции просматривается снизу вверх и находится функция, ближайшая к классу D (т. е. к классу, который является типом объекта).

>A: :  F();  B: :  F();  C: :  F(); 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. Конструкторы порожденного класса Если объявляется объект порожденного класса, то за п 2. Конструкторы порожденного класса Если объявляется объект порожденного класса, то за инициализацию базовой части объектов должен отвечать конструктор порожденного класса.

>  Конструкторы классов A и B Конструктор порожденного класса имеет специальную форму (рассмотрим Конструкторы классов 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 : : B(int a, int b, int c, int d, int r) : A(a, b, c), d 3(d), d 4(r) { } (2)

>  Конструктор по умолчанию   класса A Если в базовом классе задан Конструктор по умолчанию класса 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 может быть задан, например, таким необычным образом 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; }

>   Деструкторы, наоборот, сначала разрушают член-данные порожденного класса, затем базового. Деструкторы, наоборот, сначала разрушают член-данные порожденного класса, затем базового.