Скачать презентацию Программирование на С Тема 2 2 Отношения между Скачать презентацию Программирование на С Тема 2 2 Отношения между

Качесова Л.Ю.-Прогр.2--20130305084554.ppt

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

Программирование на С++ Тема 2. 2 Отношения между классами 16. 02. 2018 1 Программирование на С++ Тема 2. 2 Отношения между классами 16. 02. 2018 1

Отношения между классами • Является (is a) – Открытое (public - ) наследование; • Отношения между классами • Является (is a) – Открытое (public - ) наследование; • Содержит (has a) – Наследования нет! В качестве члена данных класс содержит объект(ы) другого класса; • Подобен (as a) – Закрытое (private или protected) наследование. 16. 02. 2018 2

Отношение между классами «содержит» class Point { int m_x, m_y; public: Point(int x, int Отношение между классами «содержит» class Point { int m_x, m_y; public: Point(int x, int y) { m_x = x; m_y = y; }. . . }; class Rect { Point m_Left. Top; // внедренный объект Point m_Right. Bottom; // внедренный объект public: . . . }; int main() { Rect r; } 16. 02. 2018 3

Ø В качестве переменной класса А может фигурировать объект другого класса В. В этом Ø В качестве переменной класса А может фигурировать объект другого класса В. В этом случае говорят, что объект класса А содержит объект класса В. Или объект класса В внедрен в объект класса А. Пример на слайде: объект класса Point внедрен в объект класса Rect. Ø Последовательность создания объекта типа Rect: Ø компилятор, заранее зная, сколько памяти потребуется для объекта Rect (вместе с внедренными объектами Point), сразу же выделяет соответствующий объем памяти; Ø если бы класс Rect был производным, компилятор сначала вызвал бы конструктор базового класса; Ø вызываются конструкторы (в нашем случае по умолчанию) внедряемых объектов Point. Ø вызывается конструктор класса Rect (в нашем случае по умолчанию). Ø Порядок разрушения объекта прямо противоположен порядку его создания. 16. 02. 2018 4

Способы передачи параметров конструктора встроенным объектам Отличие: Присваивание в теле конструктора Список инициализации 1. Способы передачи параметров конструктора встроенным объектам Отличие: Присваивание в теле конструктора Список инициализации 1. Компилятор выделяет память для объекта класса Rect (вместе с внедренными объектами класса Point). 2. Вызываются конструкторы по 2. Вызываются конструкторы умолчанию внедряемых объектов класса Point сразу с указанными параметрами. 3. Вызывается конструктор класса Rect. В теле конструктора уже Rect. проинициализированные внедренные объекты принимают новые значения. 16. 02. 2018 5

Ø Так как сначала вызываются конструкторы встроенных объектов, возникает та же проблема, что и Ø Так как сначала вызываются конструкторы встроенных объектов, возникает та же проблема, что и при передаче параметров конструктору базового класса (при создании объекта производного класса сначала вызывается конструктор базового класса, а потом производного). Ø Существуют два способа передать параметры конструктора встроенным объектам: Ø присваивание в теле конструктора; Ø список инициализации конструктора. Ø На слайде представлены отличия этих способов. Ø Для того чтобы при конструировании объекта класса Rect вызвать конструкторы встроенных объектов сразу с требуемыми значениями, нужно использовать специальную запись, аналогичную записи для передачи параметров конструктору базового класса (см. пример1 и пример2 на следующих слайдах). 16. 02. 2018 6

Способы передачи параметров конструктора встроенным объектам Пример 1: class Rect { Point m_Left. Top, Способы передачи параметров конструктора встроенным объектам Пример 1: class Rect { Point m_Left. Top, m_Right. Bottom; public: Присваивание в теле конструктора Список инициализации Rect(const Point& pt 1, const Point& pt 2) { // компилятор вызывает // конструкторы по умолчанию // для встроенных объектов m_Left. Top = pt 1; m_Right. Bottom = pt 2; // уже проинициализированным // по умолчанию объектам // присваиваются новые // значения с помощью // операции присваивания } Rect(const Point& pt 1, const Point& pt 2) : m_Left. Top(pt 1), m_Right. Bottom(pt 2) {// компилятор вызывает для // встроенных объектов // конструкторы копирования // и сразу же создает их // с требуемыми значениями 16. 02. 2018 } 7

Способы передачи параметров конструктора встроенным объектам Пример 2: class Rect { Point m_Left. Top, Способы передачи параметров конструктора встроенным объектам Пример 2: class Rect { Point m_Left. Top, m_Right. Bottom; public: Присваивание в теле конструктора Список инициализации Rect(int left, int top, int right, int bottom) { // компилятор вызывает // конструкторы по умолчанию // для встроенных объектов m_Left. Top = Point(left, top); m_Right. Bottom = Point(right, bottom); // создаются автома//тические неименованные // объекты и уже проини//циализированным по умолча//нию объектам присваиваются // новые значения } Rect(int left, int top, int right, int bottom) : m_Left. Top(left, top), m_Right. Bottom(right, bottom) {// компилятор вызывает для // встроенных объектов // конструкторы // с требуемыми значениями 16. 02. 2018 } 8

Замечания: 1. // Файл rect. h class Rect {. . . Rect(int left, int Замечания: 1. // Файл rect. h class Rect {. . . Rect(int left, int top, int right, int bottom); }; // Файл rect. cpp Rect: : Rect(int left, int top, int right, int bottom) : m_Left. Top(left, top), m_Right. Bottom(right, bottom) {. . . }; 2. Point: : Point(int x, int y) : m_x(x), m_y(y) {. . . } • class Color. Rect : public Rect { int m_color; public: Color. Rect(int left, int top, int right, int bottom, int color) : Rect(letf, top, bottom), m_color(color) 1. {. . . } 2. }; 16. 02. 2018 9

Ø Замечания: 1. Список инициализации, который начинается с синтаксической конструкции « : » , Ø Замечания: 1. Список инициализации, который начинается с синтаксической конструкции « : » , может быть указан только при определении метода. 2. Переменные базовых типов можно также инициализировать с помощью списка инициализации. Если для встроенных объектов пользовательского типа использование списка инициализации дает несомненные преимущества (уменьшает накладные расходы на лишний вызов функции), то для базовых типов особого выигрыша не получается, так как в обоих случаях низкоуровневый код генерируется одинаковый. 3. Если класс, с одной стороны, является производным, а, с другой стороны, содержит встроенные объекты, то требуется передать параметры как конструктору базового класса, так и конструкторам встроенных объектов. Все вызовы конструкторов указываются в одном списке инициализации через запятую в любом порядке ( а выполняться будут в строго определенном порядке – сначала конструктор базового класса, а потом конструкторы встроенных объектов в порядке следования их в объявлении класса). 4. Если программист явно с помощью списка инициализации не указал компилятору как следует инициализировать встроенные объекты, то они будут проинициализированы с помощью конструктора по умолчанию. 5. Для рассмотренных примеров можно было использовать список инициализации конструктора или присваивание в теле конструктора. Встречаются случаи, когда нужно обязательно использовать список инициализации конструктора: если членами класса являются константные встроенные объекты и ссылки. 6. Члены класса инициализируются в том порядке, в котором они указаны в классе, поэтому порядок их следования в списке инициализации не имеет значения! 16. 02. 2018 10

Отношение между классами «содержит» Указатели на объекты в качестве членов данных класса class Y Отношение между классами «содержит» Указатели на объекты в качестве членов данных класса class Y { X* mp. X; . . . }; 16. 02. 2018 11

Ø Замечания: 1. Как только в классе появляется указатель – скорее всего, будет динамически Ø Замечания: 1. Как только в классе появляется указатель – скорее всего, будет динамически выделяться память, поэтому необходимо обеспечить корректное значение этого указателя!!! -> в таком классе должны быть предусмотрены корректно реализованные: конструктор по умолчанию – Y() {mp. X = 0; } – вовремя обнуленный указатель избавит от ошибок, возникающих при случайном использовании неинициализированного указателя! деструктор - ~Y() {delete [] mp. X ; } и при необходимости он должен быть объявлен virtual. конструктор копирования оператор присваивания. 2. Часто в качестве члена данных класса X фигурирует указатель типа X* (на объект того же типа). Это позволяет создавать сложные структуры данных типа списков и деревьев. 16. 02. 2018 12

Защищенное наследование. Отношение между классами – «подобен» class A {. . . public: void Защищенное наследование. Отношение между классами – «подобен» class A {. . . public: void f. A(); }; class B : private /* protected*/ A {. . . }; int main() { A a; a. f. A(); // OK B b; b. f. A(); //ошибка компиляции – метод недоступен } 16. 02. 2018 13

Ø Если спецификатор наследования производного класса private или protected, public-интерфейс базового класса извне (посредством Ø Если спецификатор наследования производного класса private или protected, public-интерфейс базового класса извне (посредством объекта или указателя на объект) становится недоступен. Ø При защищенном наследовании доступ к public членам базового класса возможен только из методов производного класса. Пользователи производного класса не имеют доступа к базовой части, что позволяет разработчику производного класса приспособить базовую часть для своих целей, то есть: Ø Использовать те методы базового класса, которые удобно использовать производному классу только определенным образом; Ø А те понятия базового класса, которые «не справедливы» для производного класса, запретить. Ø Пример 3 на следующих слайдах демонстрирует защищенное наследование. Ø Замечание: при защищенном наследовании внутренние взаимоотношения между двумя классами не меняются, то есть в методах производного класса доступны protected элементы базового класса. Ø Все друзья класса Square имеют доступ к защищенным членам класса Rect, наследники нет (см. пример 3 на следующих слайдах). 16. 02. 2018 14

Защищенное наследование. Отношение между классами – «подобен» Пример 3: class Rect //прямоугольник { protected: Защищенное наследование. Отношение между классами – «подобен» Пример 3: class Rect //прямоугольник { protected: int l, r, t, b; public: Rect(int x 1, int y 1, int x 2, int y 2) {l = x 1; r = x 2; t = y 1; b = y 2; } void Inflate(int dl, int dr, int dt, int db); {l -= dl; r += dr; t -= dt; b += db; } void f() {} }; class Square : protected Rect //квадрат { public: void Inflate(int d); { Rect: : Inflate(d, d, d, d); } //метод баз. класса выз-ся // таким образом, чтобы квадрат не превратился в прямоугольник Square(int x, int y, int d) : Rect(x, y, x+d, y+d) {} friend void GF(Square& s) }; 16. 02. 2018 15

Защищенное наследование. Отношение между классами – «подобен» Пример 3: void GF(Square& s) { s. Защищенное наследование. Отношение между классами – «подобен» Пример 3: void GF(Square& s) { s. f(); //OK } int main() { Square s(1, 1, 10); s. Inflate(2); //квадрат остался квадратом s. Inflate(1, 2, 3, 4); //ошибка компиляции s. Rect: : Inflate(1, 2, 3, 4); //ошибка компиляции Rect r = s; //ошибка компиляции Rect* pr = new Square(10, 100); ; //ошибка компиляции } 16. 02. 2018 16

Защищенное наследование при построении иерархий классов class A { int m_a; protected: int m_a Защищенное наследование при построении иерархий классов class A { int m_a; protected: int m_a 1; void f. A 1(); public: void f. A 2(); }; class B : private /* protected*/ A { int m_b; protected: int m_b 1; void f. B 1(){ // разрешено обращаться к // public и protected членам A f. A 1(); m_a 1++; f. A 2(); } public: void f. B 2(); }; class С : public B { public: void f. C() {f. B 1(); 16. 02. 2018 17

Спецификатор доступа , указываемый при объявлении производного класса Изменение вида доступа к элементам базового Спецификатор доступа , указываемый при объявлении производного класса Изменение вида доступа к элементам базового класса посредством производного класса в базовом public в базовом protected в базовом private class B : public A {…} class C : public B {…} в производном public в производном protected в производном недоступен class B : protected A {…} class C : public B {…} в производном protected в производном недоступен class B : private A{…} class C : public B {…} в производном private в производном недоступен 16. 02. 2018 18

Ø В предыдущем примере (пример 3) взаимоотношения внутри классов сохранялись, но если мы продолжим Ø В предыдущем примере (пример 3) взаимоотношения внутри классов сохранялись, но если мы продолжим строить иерархию классов, то защищенная базовая часть должна быть изолирована от всех остальных не непосредственных наследников, поэтому для таких наследников появляются дополнительные ограничения области видимости (см. таблицу на слайде). 16. 02. 2018 19

Множественное наследование class A { int m_a; public: void FA(){}; A(int a) { m_a Множественное наследование class A { int m_a; public: void FA(){}; A(int a) { m_a = a; } }; class B { int m_b; C* public: void FB(){}; B(int b) { m_b = b; } }; class C : public A, public B { int m_c; public: void FС(){}; C(int a, int b, int c): A(a), B(b) { }; void main(){ C c(1, 2, 3); c. FA(); . . . } A 16. 02. 2018 B C A: : m_a B: : m_b C: : m_c = c; } 20

Ø Отношение к множественному наследованию неоднозначно: Ø с одной стороны, заманчиво, так как позволяет Ø Отношение к множественному наследованию неоднозначно: Ø с одной стороны, заманчиво, так как позволяет наследовать свойства нескольких предков (то есть зачастую моделирует реальные жизненные ситуации); Ø с другой стороны, работает медленнее, дополнительный расход памяти, труднее в реализации. Ø Замечание: не все объектно-ориентированные языки поддерживают множественное наследование. Ø На слайде представлена простая иерархия классов (без полиморфизма): производный класс С наследует свойства обоих предков А и С. Специфика: Ø порядок вызова конструкторов: А() -> B() -> C(). Конструкторы вызываются в том порядке, в котором они указаны в списке наследования!!! Ø деструкторы – в обратном порядке Ø компилятор строит объект в порядке объявления базовых классов Ø вызов методов в таком простом случае ничем не отличается от простого наследования. 16. 02. 2018 21

Проблемы при множественном наследовании class Rect {. . . protected: int left, right, top, Проблемы при множественном наследовании class Rect {. . . protected: int left, right, top, bottom; }; Rect Color. Rect Round. Rect class Color. Rect : public Rect {. . . }; Color. Round. Rect class Round. Rect : public Rect {. . . }; class Color. Round. Rect : public Color. Rect, public Round. Rect { int Get. Left() }; int Color. Round. Rect: : Get. Left() { return left; // ошибка! Объект класса Color. Round. Rect // содержит две базовых части класса Rect, // доступ к члену данных left неоднозначен! return Color. Rect: : left; // уточняем путь наследования } 16. 02. 2018 22

Ø Базовый класс может косвенно передаваться (наследоваться) производному более одного раза (см. пример на Ø Базовый класс может косвенно передаваться (наследоваться) производному более одного раза (см. пример на слайде). При такой иерархии классов (пример на слайде) базовая часть Rect будет входить в объект производного класса Color. Round. Rect дважды. Ø Объявление базового класса в списке наследования виртуальным решит эту проблему. Подобное объявление предписывает компилятору создавать единственную базовую часть базового класса. 16. 02. 2018 23

Контрольные вопросы 1. Отношение между классами «содержит» . Последовательность создания объекта класса, содержащего объект Контрольные вопросы 1. Отношение между классами «содержит» . Последовательность создания объекта класса, содержащего объект другого класса. Способы передачи параметров конструктора встроенным объектам. Примеры. 2. Отношение между классами «подобен» . Примеры. 3. Множественное наследование. Проблемы при множественном наследовании. Примеры 24