Язык программирования Си++ (1119468), страница 5
Текст из файла (страница 5)
Ассоциация• Ассоциация – наиболее общий вид взаимосвязиНа первых стадиях проектирования вид этой связи никакне конкретизируется, а утверждается лишь наличиенекоторой связи, которая затем может проявиться в одномиз других видов межобъектных связей• Ассоциация всегда является бинарной и можетсуществовать между двумя разными сущностями илимежду сущностью и ею же самой (рекурсивная связь)• При последующем проектировании связь, выраженная какассоциация, превращается в другую, более точновыраженную связь89Взаимодействие и иерархияклассов. Агрегация• Агрегация выражает отношение между классами поформуле “часть-целое” (“has a”, “содержит”)• При агрегации объект одного класса внутри себя содержитобъекты других классов, как объект “Треугольник”содержит три объекта “Вершина”, а объект “Самолёт”содержит четыре объекта “Двигатель”• Различают строгую (сильную) и нестрогую агрегацию.
Пристрогой агрегации внутренний компонент существуеттолько одновременно с объектом. Строгую агрегациюиногда называют композицией (в этом случае нестрогуюагрегацию называют агрегацией без каких-либо уточнений)90Взаимодействие и иерархияклассов.
Агрегация• Нестрогая агрегация на языке Си++class ShareHolder { ... Share * asserts; ... }; // Акции могут исчезнуть,// но объект будет существоватьАкционерАкция• Строгая агрегация или композиция на языке Си++struct A { /* ... */ };struct B { struct A a; /* Класс B содержит класс A */ };BAclass Triangle { Point v1, v2, v3; ...
}; // Вершины не могут исчезнуть,// пока существует экземпляр класса TriangleТреугольникВершина91Взаимодействие и иерархияклассов. Использование• Имя одного класса используется в прототипе(профиле) метода другого класса• В теле метода одного класса создаётся локальныйобъект другого класса• Метод одного класса обращается к методу другогоклассаИспользующий(клиент)Используемый(сервер)92Взаимодействие и иерархияклассов.
Инстанциирование• Инстанциирование – проведение настройки шаблона спомощью типового параметраТип (класс) или функцияТипШаблонПараметр шаблонаРезультат генерациипо шаблонуФактическийпараметр шаблона93Пример иерархии.Одиночное наследование• Концепция наследования: один класс являетсяразновидностью (“частным случаем”) другого• Иерархия наследования образует иерархию структурклассов• Иерархия структур классов определяет отношение“обобщение/специализация” (“is a”, “есть”)• В иерархии классов вышестоящая абстракция являетсяобобщением, а нижестоящая – специализацией94Наследование в Си++• Цель – формирование иерархических связей междупользовательскими типами путём расширения базовыхклассов классами-наследниками• Наследование выражает отношение вида “частноеобщее” (“is a”, “есть”, “роза” есть “цветок”):объекты производных классов рассматриваются какчастные случаи объектов базовых классов• Наследование – механизм создания нового класса(производного) из старого (базового):“уточняется” определение базового класса,расширяется его представление и поведение95Наследование в Си++• Состояния и поведение базового и производного классовсвязаны между собой Производный класс наследует состояние (набор данных) и поведение(набор методов) от уже существующего базового класса Производный класс может расширять состояние и поведение базовогокласса, то есть дополнять унаследованную структуру данных иунаследованный набор методов Производный класс может переопределять поведение базового класса, тоесть содержание унаследованных функций Производный класс может корректировать доступ к членам базовогокласса Базовый класс ничего не знает о наличии производного класса, но можетпредполагать его наличие и принимать меры по облегчению его создания• Возникающий при наследовании класс есть подтип базовогокласса, объекты класса-наследника могут использоваться везде,96где могут использоваться объекты базового классаНаследование в Си++struct A { int x; int y;}; A a;struct B: A { int z; };B b;b.x = 1;b.y = 2;a = b;B:: A:: int x;int y;b.z = 3;int z;• Объект производного типа B наследует свойства базового типа A• Допустимо присваивание a = b• Обратное присваивание b = a неверно: у объекта a нетдостаточной информации для заполнения полей объекта b• Классы A и B могут служить базовыми классами для другихклассов без ограничения их числа:struct C: A { int t; };struct D: B { int t; };97Наследование в Си++• Иерархия классов, описывающих данные о студентах:studentstudentstudent3cstudent2cstudent2cstudent4cstudent2cSPdep• Самый младший наследник изображается внизу иерархии• Стрелка направляется от производных классов к базовому• Число входящих стрелок для любого базового класса может бытьсколь угодно большим, от этого наследование для каждого изпорождаемых классов в отдельности не перестаёт бытьодиночным98Видимость при наследовании• Наследование контролируется с помощьюограничивающих видимость спецификаторов доступа• Спецификаторы доступа могут только уменьшитьвидимость, но не расширить её• По умолчанию структуры наследуют своим базовымклассам открытым способом, а классы — закрытым:class Cstruct Sclass CC:class CS:struct SC:struct SS:class CC:class CS:{ public: int c; };{int s; };C { CC () { c = 1; /* ОШИБКА! */ }S { CS () { s = 1; /* ОШИБКА! */ } };C { SC () { c = 1; } };S { SS () { s = 1; } };public C { CC () { c = 1; } };public S { CS () { s = 1; } };// класс наследует закрытым//образом,// а структура – открытым// так надо исправить ошибки!99Видимость при наследовании• Порождённый класс наследует все члены базового класса иимеет свободный доступ к открытым (public) и защищённым(protected) членам базового класса• Причиной введения защищённых членов класса являетсянеобходимость открыть доступ к некоторым из элементовбазовых классов, недоступным для посторонних• Защищённые члены базового класса ведут себя как открытые поотношению к методам производных классов и как закрытые поотношению к другим функциям• Закрытые (private) элементы базового класса недоступны длявсех, включая методы производных классов100Видимость при наследованииметоды идрузьясамогоклассапосторонниепользователиметоды идрузьяпроизводногоклассаоткрытые (public)защищённые (protected)закрытые (private)101Видимость при наследовании• Ограничение видимости действует только на данныйпроизводный класс и на его возможных потомковstruct A { int x; int y; }; class D: protected A { int t; };D d, * pd = & d; // строится указатель на производный классpd -> t;// ОШИБКА: доступ к закрытому полю D::t (d.t)pd -> x;// ОШИБКА: доступ к защищённому полю D::x• Работа с указателем происходит в контексте правдоступа этого указателя:A * pa = (A*) pd; // строится указатель на базовый классpa -> x;// правильно: поле A::x – открытое• Каждый класс создаёт собственную область видимости102Видимость при наследовании• Если в производном классе объявлен метод, одноимённыйметоду базового класса с совпадающим профилем, то имеетместо перекрытие методаstruct M { void f (int x); };struct N: public M { int x; void f (int x); };M m, *pm;N n, *pn;pn = &n;pn -> f (1);// вызывается N::f (1)pm = (M*) pn;pm -> f (1);// вызывается M::f (1)• Если метод некоторого класса должен вызвать другой метод,описанный в одном из базовых классов, используется явнаяоперация разрешения области видимости ‘::’pn -> N::f (1);pm -> M::f (1);pn -> M::f (1);// вызывается N::f (1)// вызывается M::f (1)• Явное использование объекта всегда трактуется однозначно:n.f (1);m.f (1);// вызывается N::f (1)// вызывается M::f (1)103Видимость при наследованииint x = 7; void f (int);class P{ int x; public: void f (int);};class Q: public P {void f (int); void g (); };void Q::g(){f (1);// Вызов Q :: f (1)P :: f (1);// Вызов P :: f (1):: f (1);// Вызов глобальной void f (1):: x = 1;// Изменение глобальной переменной xx = 2;// Кажется, что: ≡ ::x = 2, так как P :: x скрыто в базовом классе,// Но ОШИБКА, так как х класса P перекрывает глобальную х,// и в классе Q глобальная переменная х недоступна!}• При создании объекта типа Q вызывается конструктор P::P (),затем собственный конструктор Q::Q ()• При разрушении объекта типа Q вызывается деструктор Q::~Q (),104затем деструктор базового класса P::~P ()Создание и уничтожениеобъектов при наследовании• При создании объекта производного класса сначаласоздаются элементы данных базового класса, самбазовый класс, потом элементы данных производногокласса, наконец, сам производный класс• Уничтожение объектов проходит в обратном порядке:сначала уничтожается производный класс, егоэлементы, базовый класс, элементы базового класса• Элементы данных создаются в порядке объявления вклассе и уничтожаются в обратном порядке105Специальные методыпри наследовании• Конструкторы, деструкторы и операции присваивания(функции operator=()) не наследуются, в каждомклассе их определяют заново:struct E { E & operator = (int i) { /* ...
*/ }E (int i){ /* ... */ } /* ... */};struct F: E { /* ... */ };void f () { F x (1); // ОШИБКА: конструктор не наследуется// и не проникает в производный классx = 2; // ОШИБКА: операция присваивания}// не наследуется106Создание, инициализация иуничтожение подобъектов• Объекты внутри объектов других классов:class Point { int x; int y; public: Point (); Point (int, int); };class Z{ Point p; int z; public: Z (int); };• Объекты создаются в порядке вхождения вложенныхобъектов в составной объект• Конструктор составного объект вызывается, когда всеподобъекты уже существуют и инициированы:Z * z = new Z (1);//Point (); Z (1);• Деструкторы вызываются в обратном порядке:delete z;//~Z (); ~Point ();107Список инициализации• Инициализировать вложенный объект до вызова собственногоконструктора с помощью списка инициализации:class Point{ int x; int y; public: Point (); Point (int, int); };class Z{ Point p; int z; public: Z (int); };Z::Z (int c): p (1, 2) { z = c; } // есть аналогия с Point p (1, 2)Z::Z (int c): p (1, 2), z (c) {} // но нельзя делать одно определение// в составе другого определения• Если задан список инициализации собственных полей, будутвызваны конструкторы из списка с заданными параметрами• Если для члена класса инициализация в списке не указана, дляинициализации используется конструктор умолчания• Конструкторы из списка вызываются в порядке включенияэлементов в состав класса, но не списка!108Инициализация и присваивание• Инициализация объекта отличается от присваивания этомуобъекту нового значения• Присваивание может повторяться много раз,инициализация выполняется лишь однажды• Присваивание константам невозможно, инициализацияконстанты – процесс естественный• Инициализация выполняется для ничем не заполненнойпамяти, присваивание должно правильно работать с ужесозданным объектом, значение которого заменяетсяновым• В инициализаторах членов класса возможно заданиеначальных значений ссылкам и константам109Инициализация и присваивание• В инициализаторах членов класса возможно задание начальныхзначений ссылкам и константам:};class error { int i; const int ci; int & ri;public: error (int ii) { i = ii;ci = ii; // ОШИБКА – нельзя присваивать константеri = i; } // ОШИБКА – ссылка должна быть инициирована• Следует изменить конструктор и записать инициализацию такимобразом, чтобы она не смешивалась с присваиванием:error::error (int ii): ci (ii), ri (i) // здесь нет ошибок!{ i = ii; }// это обычное присваивание110Инициализация полей данных• Инициализации полей, унаследованных производнымиклассами от базовых классов, можно не делать, если для этихполей достаточно вызывать конструкторы по умолчанию• В список инициализации, имеющийся у конструкторапроизводного класса, необходимо включать обращение кконструктору базового класса с параметрами, обычностроящимися на основе значений параметров соответствующегоконструктора производного класса:class T { int n; double х; /* ...
*/public: T (int i, double y): n (i), x (y) { /* ... */ }};class U: public T { bool b; /* ... */public: U (bool t, int i, double y): b (t), T (i, y) { /* ... */ }};111Инициализация полей данных• Имена полей базового класса использовать в спискеинициализации конструктора производного класса нельзя:class T { int n; double х; /* ... */public: T (int i, double y): n (i), x (y) { /* ... */ }};class U: public T { bool b; /* ... */public: U (bool t, int i, double y): b (t),n (i), x (y) { /* … */ } // ОШИБКА// так можно инициализировать только собственные поля};112Одиночное наследование в Си++• Определим базовый класс, описывающий атрибуты студентов,этот класс послужит основой создания некоторого производногокласса, описывающего только студентов 2 курсаclass student { protected: char * name; int year; // год обученияdouble avb;// средний баллint student_id;public: student (char* nm, int y, double b, int id):year (y), avb(b), student_id (id){ name = new char [strlen (nm) + 1];strcpy (name, nm); }char * get_name () const { return name; }void print ();~student () { delete [] name; }113};Одиночное наследование в Си++• Студент второго курса обладает всеми атрибутами студента –именем, номером года обучения, средним баллом наэкзаменах, у него есть и свои, только ему присущие атрибуты,например, наименование темы практической работы по Си++ иимя руководителя практикой:class student2c: public student { // указание на базовый классprotected: char* pract; char* tutor;public: student2c (char* n, double b, int id,char* p, char* t) : student (n, 2, b, id){ pract = new char [strlen (p) + 1]; strcpy (pract, p);tutor = new char [strlen (t) + 1]; strcpy (tutor, t); }void print ();~student2c () { delete [] pract; delete [] tutor; }114};Одиночное наследование в Си++• У нового класса, уточняющего ранее введённый класс, должныбыть свои конструкторы и деструктор• Функция print () производного класса скрывает одноимённуюфункцию базового класса (из другой области видимости)• Полностью унаследован производным классом и может в нёмиспользоваться селектор get_name ()• В новом классе можно заново строить все методы (может быть,копируя фрагменты текста из определения базового класса), номожно воспользоваться уже сформированными методамибазового класса, как сделано в этом примере, где конструкторпроизводного класса обращается к конструктору базового классадля инициализации унаследованных полей, которые нельзяинициировать поимённо115Одиночное наследование в Си++• Производный класс может устанавливать дополнительныеограничения на доступ к унаследованным от базового классаэлементам, которые будут распространяться и на него самого ина все те классы, которые станут его наследниками• Все открытые и защищённые элементы базового классарассматриваются в производном классе как защищённые, а всезакрытые элементы остались бы закрытыми:class student3с: protected student { /* ...