Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

chast2

.pdf
Скачиваний:
9
Добавлен:
10.06.2015
Размер:
431.28 Кб
Скачать

Как показывает пример рис 4.1 производный класс может быть базовым для другого класса, поэтому необходимо иметь в виду сл принятую в С++ терминологию:

в иерархии наследования для класса С1 базовыми яв классы В1 и А, но при этом класс В1 называют прямым базовым,а класс А - косвенным базовым.

Определениебазовых классов должны быть даны впрограмме раньше определений производных классов.

Пользуясь правилами яз С++ программист может реализовать различные стратегии наследования. Язык в этом отношении очень гибок и допускает почти любые мыслимые варианты, из-за этого иногда начинающие программисты создают сложную и запутанную,не адекватную иерархию классов.

На простом примере проиллюстрируем 2 крайних варианта стратегии наследования при формировании производного класса.

1.образуя производные классы можно расширять спектр свойств объектов класса (стратегия движенияот частного к общему)

2.образуя производные классы можно сужать спектр свойств объектов класса (стратегия движения от частного к общему)

Примером реализуемой первой стратегии являетсяопределение в начале класса описывающего точку на плоскости,где некоторые поля объектов могут содержать координаты и другие характеристики точки. Далее в качестве производного класса можно можно определить класс описывающий окружность, при этом базовым для него можно сделать именно класс "точка",от которого класс "окружность" унаследует указанные поля, причем в объектах класса "окружность" эти поля будут предназначены для хранения характеристик центра окружности. Дополнительнов классе потомке ("окружность") объявляется поле для хранения радиуса окружности.

Таким образом производный класс будет иметь более широкий спектр свойств, а значит будет обладать меньшей степенью абстрактности.

Примером реализации второй стратегии является противоположный порядок определения класса: Вначале определяем класс "окружность", а затем производный класс "точка", в котором в качестве базового используется класс "окружность", причем конструктор класса "точка" обязательно инициализирует поле "радиус" нулем,т.е. точка это окружность нулевого радиуса.

Мнение создателя С++ "Всякий самолет имеет двигатель,поэтому новичкам часто приходит в голову мысль определить базовый

класс "двигатель", а класс "самолет" сделать производным, добавив в этом классе к свойствам двигателя еще ряд свойств описывающих плоскости самолета, его оперение и т.д. Это очень плохая идея (так же как и идея сделать класс "двигатель" производным от класса "самолет"). Поскольку самолет не является двигателем,он включает в себя двигатель как составную часть,т.е. здесь лучше применить не отношение наследования, а отношение включения." Страуструп.

Страуструп говорит: "Отношение наследования как и отношение включения сдедует применять почти исключительно для описания отношений четко определенных в проекте,т.е. в предметной области для моделирования которой создается програмный комплекс."

В идеале для построения объектно ориентированной программы желательно выполнять правила постановки (Лисков):

"любой объект производного класса должен быть пригоден для употребления в каждом месте в котором может быть применим объект базового класса."

__________________________________________________________________________________

60.Синтаксисопределения производного класса.Уровни доступности унаследованных элементов в производном классе (из п.4.2.1.выбрать часть материала, необходимую дляраскрытия перечисленных вопросов).

___________________________________________________________________________________

Простое наследование предполагает что при определение производного класса в заголовке указывается только один базовый класс.

Синтаксис определения производного класса при простом наследовании имеет сл вид:

<ключ производного класса><имя производного класса>:[<спецификатор базового класса>] <имя базового класса> {<тело класса>}

ключ производного класса это одно из служебных слов class или stract (слово union здесь недопустимо) ":"-обязательно

Спецификатор базового класса не обязателен,а если он есть,то это одно из служебных слов public, private, protected.

Если спецификатор опущен, а ключ производного класса слово class, то спецификатор по умолчанию полагается равным private,а иначе, если stract, то по умолчанию подразумевается public.

Иногда спецификатор базового класса называют ключом доступа к базовому классу.

Главные вопросы которые должен решить программист разрабатывающий производный класс, таковы:

-"что" и "как"(по каким правилам) производный класс наследует от предка

-видоизменяет ли он какие-то элементы (поля и методы) предка, и если да,то как

-как он (производный класс) дополняет совокупность элементов предка

В начале выполним как нужно понимать тот факт, что в данном классе элементы попадают под действием конкретного спецификатора доступа, при этом не важно объявлен ли спецификатор явно или он неявно подразумевается синтаксисом языка.

-если элемент имеет спецификацию public (общедоступный), то к нему можно обращаться внутри определения класса, в методах класса и в дружественных классу функциях и классах, а так же через объекты класса вне методов класса.

-если элементы имеют спецификацию privated (закрытый,частный),то к нему можно обращаться только внутри определения класса, в методах класса и в дружественных классу функциях и классах.

-если элементы имеют спецификацию pritected (защищенный), то правила обращения к нему такие же как и в случае спецификатора private. Особые свойство спецификатора проявляются только в реализации собственно механизма наследования.

Рассмотрим теперь как устроен механизм наследования. Конкретно речь пойдет об уровне доступа к элементам унаследованным от базового класса. Факторами правил определяющих уровень доступа яв спецификатор базового класса явно или неявно указанный в определении производного класса и явно или неявно объявленные в базовом классе уровни доступа к его элементам.

Основные правила задаются в табл 4.1 (фото втелефоне)

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

class point { public:

double x,y;//координаты точки protected:

double vx,vy;//скорость точки private:

double ax,ay;//ускорение точки public:

point(double x1=0.0,double y1=0.0,double vx1=0.0,double vy1=0.0,double ax1=0.0,double ay1=0.0):x(x1),y(y1),vx(vx1),vy(vy1),ax(ax1),ay(ay1) {}//это конструктор общего вида играющий роль конструктора умолчания и к.приведения типа

//к.копирования и операцию присваивания пусть создает компелятор

friend ostream& operator <<(ostream & out,point p);//прототип функции операции для перегрузки операции вывода "<<"

void print();//прототип метода для вывода на экранполя ах };//конец определения класса point

Реализация функции-операции "<<" для класса point

ostream& operator <<(ostream & out,point p){ out<<"x="<<p.x<<"\ty="<<p.y<<"\tvx="<<p.vx<<"\tvy="<<p.vy<<"\tax="<<p.ax<<"\tay=" <<p.ay<<endl; return out;}

//реализуем метод класса point

void point::point(){ cout<<"ax="<<ax<<endl;}

//в качестве производного определим класс ellips,объекты которого описывают эллипс и при этом унаследованные от point поля x,y,vx,vy,ax,ay будут описывать центр эллипса. Объявление класса имеет вид:

class ellips:public point {//для базового класса используем спецификатор public private:

double rgor,rvert;//горизонтальная и вертикальнаяоси эллипса public:

ellips(double x1=0,double y1=0,double vx1=0,double vy1=0,double ax1=0,double ay1=0, double rgor1=0,double rvert1=0)

point (x1,y1,vx1,vy1,ax1,ay1),rgor(rgor1),rvert(rvert1)//к.№1 в котором центр эллипса задан отдельными полями. Обратить внимание на форму инициализации полей унаследованных от базового класса point: используется конструктор общего вида класса point. Необходимо учитывать,что удалось инициализировать все унаследованные поля именно благодаря работе с доступным конструктором класса point, иначе к полям ах и ау добраться не удалось бы, т.к. в базовом классе они закрыты, а значит в производном классе они недоступны (см табл 4.1). Альтернативный вариантработы класса: в базовом классе определяем поле ахи ау либо под спецификацией public,либо protected, тогда инициализация станет возможной например в поле конструктора через операцию присваивания ах=ах1 {}//конец к.№1

ellips (point$p,double rgor1,double rvert1):rgor(rgor1),rvert(rvert1)//это к.№2 в котором центр окружности задан как объект типа point

{x=p.x

y=p.y//к полям х и у поля р можно обратиться т.к. они относятся к спецификатору public. В полях vx,vy,ax,ay так можно было бы обратиться если бы они тоже были под спецификатором public

}//конец к.№2

friend ostream & operator <<(ostream&out,ellips E1);//это прототип функции операции для перегрузки операции вывода <<

void print();//прототип метода класса ellips для вывода на экран полей ах и ау };//конец определения класса ellips

//реализация функции-операции "<<" для класса ellips. Здесь мы обойдемсябез аналогичной перегруженной операции для класса point и в связи с этим возникает ограничение, а именно: не все поля объекта мы сможем передать в консольное окно.

ostream & operator <<(ostream & out,ellips E1){

out<<"центр эллипса: "<<"E1.x="<<E1.x<<"\tE1.y="<<E1.y<<endl;

out<<"начальная скорость эллипса: "<<"E1.vx="<<E1.vx<<"\tE1.vy="<<E1.vy<<endl;//поля vx vy доступны т.к. они определены в базовом классе как protected, значит в производном классе ellips учитывая что в нем базовый класс специфицирован как public, эти поля так же будут относиться к области protected.

Если бы в классе point поля vx vy были объявлены как private, то в классе ellips они были бы недоступны непосредственно как поля ax и ay.

out<<"полуоси эллипсаЖ "<<"E1.rgor="<<E1.rgor<<"\tE1.rvert="<<E1.rvert<<endl; return out;

}//конец определения функции-операции,поля ах ау вывести не удалось

//реализация метода класса ellips преднозначенного для вывода на экран полей ах и ау

void ellips::print(){

point::print();//обращение к одноименному методуprint базового класса point и в результате мы сможем вывести поле ах которое из элементов самого класса ellips непосредственно недосткпно cout<<"vx="vx<<endl;}//поле vx доступно непосредственно (неявно используется указатель this)

Пример работы с объектами этих классов

point pcentre(3.3,4.4);//остальные поля заполняются значениями по умолчанию (см структуру конструктор умолчания)

pcentre.print();//от имени объекта вызываем методprint

ellips El1;//создаем объект ellips и будем использовать к.№1, значения полей будут заданы по умолчанию

ellips El2(1.1,2.2,3.33,4.44,5.55,6.66,10,5);//объектыкласса ellips, будет использован к.№1, значения полей явно заданы

ellips El3(pcentre,20,10);//к.№2, значения всех полей заданы явно

cout<<El1<<endl;//с помощью перегруженной операции << выводим в консоль значение El1 El2.print();//от имени объекта класса,класс ellips вызывает метод print

cout<<El3.x<<tndl;//с помощью обычной операции << выводим значение поля х, убеждаемся что к этому унаследованному полю можно обращаться от имени объекта, к полю vx так обратиться нельзя (см табл4.1)

//при создании объектов класса ellips использование к.№1 более предпочтительно, т.к. он обеспечивает большую управляемость полями объектов чем к.№2.

__________________________________________________________________________________

61.Общие особенности механизма простого наследования классов.

___________________________________________________________________________________

Далее дано обобщение и дополнение материалов предыдущего пункта.

1.Базовый класс не имеет доступа к элементам производного класса.

2.Производный класс может обращаться ко всем тем элементам базового класса которые по правилам наследования (таб 4.1) считаются доступными.

Кроме того объекты производного класса могут непосредственно обращаться к тем элементам базового класса которые унаследованны со спецификаторомpublic.

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

Для того что бы их сделать доступными для объектов производного класса необходимо в секции public производного класса указать, что имя такого элемента относится к пространству имен базового класса. Например в определении производного класса может появиться такая фраза:

public:using point::x;//поле х будет доступнымдля объектов производного класса, даже если спецификатором класса point в определении класса ellipsбудет protected или private.

4.Если в производном классе имя какого-то элемента базового класса использованно в других целях,то этот элемент базового класса можно сделать доступнымв производном классе (устранить конфликт имен) с помощью операции указания области видимости. Этот прием использован внутри метода point класса ellips для обращения к одноименному методу point класса point.

point::point();//мы используем метод point т.к. он в классе point.

5.Как следует из таб 4.1,если элемент базового класса специфицирован как private, то независимо от спецификациибазового класса, этот элемент будетнедоступен в производном классе. В результате создания ситеации, когда объект производного класса наследует какое-то поле, оно будет реально занимать место в памяти, но добраться до него нельзя (непосредственно). Вполне возможно что такое положение соответствует логике выстраеваемой иерархии классов, т.е. иногда логично какие-то поля или методы, работающие в объектахбазового класса, делать полностью закрытыми в потомках.

Рассмотрим случай когда это необходимо для разработчика. Покажем как обеспечить возможность добраться до поля ах класса ellips.

Определим в классе point сл открытые или защищенные методы.

double getaxP();//открытый метод для чтения поля ах объекта типа point {return ax;}

void setaxP(double axP);//открытый метод для установки значения поля ах объекта типа point

{ax=axP;}

//в классе ellips определяем сл открытые методы

double getaxE();//метод для чтения поля ах объектатипа point {return point::getaxP();}

void setaxE(double axE);//метод для установки значения поля ax {setaxP(axE);}

В результате мы можем менять значение поля ах объекта типа ellips и читать его.

El2.setaxE(77.77);

cout<<El2.getaxE(),,endl;

6. Отдельно рассмотрим случай,когда производный класс наследует какую-нибудь функцию, например, метод базового класса void F(). При этом в зависимости от особенностей определения производного класса возможны различные ситуации:

-пусть в производном классе имя F не используется ни в каких других целях, тогда в производном классе и вне его (т.е. через объекты производного класса) унаследованный метод F может использоваться в точном соответствии со спецификацией доступа которую он имеет по правилам наследованиябез дополнительных ограничений.

Для примера добавим в определение класса point открытый метод moveP, сдвигающий точку из предыдущего положения в новое за время dt в соответствии с заданными скоростью точки в предыдущем положении и ускарением. Таким образом этот метод должен изменять значения полей х,у,vx,vy (ускорение мы считаем постоянным ах и ау)

void moveP(double dt) {x+=vx*dt+ax*dt*dt/2; y+=vy*dt+ay*dt*dt/2; vx+=ax*dt*dt; vy+=ay*dt*dt;}

Поскольку в классе ellips нет элементов имеющих имя moveP, то мы можем использовать метод movePдля передвижения центра эллипса.

El2.moveP(1);

-пусть в проиводном классе определен одноименный метод F имеющий ту же спецификацию параметров , что и метод F в базовом классе. Тогда при непосредственном обращении к методу F от имени объекта производного класса компелятор будет иметь ввиду метод производного класса. Говорят, что метод производного класса экранирует действия одноименного базового метода (иногда говорят о "переопределении" базового метода производнымклассом). В рассмотренном выше примере такая ситуация иллюстрируется одноименными методами point которые были определены в базовом классе point и в производном ellips.

При этом до базового метода тоже можно добраться, если использовать операцию доступа к области видимости "::" (см. тело метода point в классе ellips).

-пусть в производном классе опредедлена одноименная функция F (это может быть метод или дружественная функция), но имеющая другую спецификацию параметров по сравнению с функцией F в базовом классе.

Тогда при непосредственном обращении к имени F компелятор будет выбирать либо функциюбазового класса либо функцию производного в соответствии с контекстом обращения (набор фактических параметров),т.е. в данном случае мы имеем дело с перегрузкой фукции и здесь работают обшие правила перегрузки. В рассмотренном примере такая ситуация иллюстрируется одноименными дружественными функциями-операциями "<<" которые обеспечивают вывод данных объектов данного и производного классов в поток cout.

7. Терминология.

Если при определении производного класса, базовый класс явно или неявно имеет спецификатор public, то говорят об "открытом наследовании". Если protected - о "защищенном наследовании". Если private - о "закрытом наследовании".

Приведенный выше анализ не исчерпывает особенностей наследования методов.

__________________________________________________________________________________

62.Множественные наследования.

___________________________________________________________________________________

Рассмотрим его кратко обращая внимание на некоторые схемые особенности множественного наследования.

Наследование является множественным,если при определении производного класса указывают 2 или более прямых базовых класса.

Синтаксис такого определения поясним абстрактным примером.

class x1{...}; class x2{...};

class x3{...};// базовые классы

class y:public x1,protected x2,x3{...};//производный класс

Здесь класс У унаследует элементы 3х классов. Каждый из трех базовых классов должен указываться со своим спецификатором, может бытьнеявно,как в классе х3 (private).

Схема этой иерархии классов такова

рис 4.2 пример множественного наследования

Один и тот же класс может быть непрямым базовым классом у данного производного (дублирование непрямого базового класса)

Пример

class x{long double ax}; class y1:public x{double ay1}; class y2:public x{int ay2}; class z:public y1,public y2{};

Схема этой иерархии классов такова

Рис 4.3 Пример множественного наследования с многократным использования класса х в качестве непрямого базового.

Соответственно объемы объектов этих классов будут такими: size(x)=10(long double)

size(y1)=18(long double+double) size(y2)=14(ld+int) size(z)=32(ld+ld+d+i)

Таким образом поле ax класса х будет дважды повторятся в классе z. Поскольку имя поле в обеих ветвях наследования одно и тоже,то при обращении к конкретному полю необходимо указывать по какой ветви прошло наследование.

z::y1::x::ax//обращение к полю ах унаследованному через класс у1 z::y2::x::ax//...через класс у2

Если по запланированной логики построения иерархии классов повторение поля ах является лишним,то необходимо объявить, что класс х наследуется как виртуальный, т.е. объявление должно иметь вид: class y1:virtual public x{double ay1}

class y2:virtual public x{int ay2}

объявление классов х и z не меняются.

На схемах такой вид наследования изображается с помощью ромбовидных диаграмм.

Пример виртуального наследования класса х

рис 4.4

Исключение дублирования полей при виртуальномнаследовании обеспечиваетсяблагодаря тому, что компилятор неявно добавляет в объекты производных классов у1 и у2 указатель на виртуальныйбазовый класс. Зная этот указатель компилятор исключает дублирование полей непрямого базового класса. поэтому размеры объектов классов, участвующих в такой схеме наследованиябудут такими:

size(x)=10(ld)

size(y1)=22(ld+d+x*)//добавился указатель типа х size(y2)=18(ld+i+x*)

size(z)=30(ld+d+i+x*+x*)//лишнее поле объема ld исчезло, но появилось два указательных поля, унаследованных от прямых базовых классов у1 и у2.

Таким образом в общем случае объект производного класса определенного с виртуальным наследованием включает в себя поля базового класса, указатель на объект базового класса и поля определенные явно в самом производном классе. Если в свою очередь 2 или более производных классов (у1 и у2) становятся базовыми для класса 3-его уровня (z),то поля непрямого базового класса не дублируются, но указатели наследуются от всех прямых базовых классов (в примере это от у1 и у2).

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

Пример

class x{long double ax};

class y1:virtual public x{double ay1}; class y2:virtual public x{double ay2}; class y3:public x{int ay3};

class z:public y1,public y2,public y3{};

Пример этой иерархии классов такой:

рис 4.5 Пример схемного наследования класса х

Размеры объектов этих классов: size(x)=10(ld) size(y1)=22(ld+d+x*) size(y2)=18(ld+i+x*) size(y3)=14(ld+i) size(z)=44(ld+d+i+x*+x*+ld+i)

_____________________________________________________________________________________

63.Перечень специальных методов класса.Особенности определенияи применения конструкторови деструкторов в производных классах.

_____________________________________________________________________________________

В языке С++ к специальным относится следующие виды методов:

-конструктор умолчания

-конструктор копироания

-деструктор

-функция-операция присваивания

Общим для этих методов является тот факт,что если программист явно не определяет эти методы, то компелятор создает их автоматически,другая общая их особенность в том,что они не наследуются, однако при этом аналогичный метод базового клпасса вызывается по специальным правилам при вызове метода производного класса.

Конструкторы производных классов.

___________________________________________________________________________________

При создании объекта производного класса из его конструктора всегда явно или неявно вызывается к. базового класса,причем к. базового класса выполняется до использования тела производного класса.

В примере 4.2 в к. класса ellips к. базового класса point вызывается явно в списке инициализации. При этом параметры необходимые к. базового класса беруться из списка параметров к. производного класса.

ellips(double x1=0,double y1=0,double vx1=0,double vy1=0,double ax1=0,double ay1=0,double rgor1=1, double rvert1=1):

point(x1,y1,vx1,vy1,ax1,ay1),rgor(rgor1),rvert(rvert1)// здесь вызывается к. базового класса point

Надо учитывать, что к. производного класса не имеет право непосредственно инициализировать поля унаследованные от базового класса, даже если они имеют уровень доступа public.

Т.к. в базовом классе может быть несколько к. ,т.е. какой конкретно к.базового класса будет вызван в списке инициализации к. производного класса, определяется контекстом, т.е. в соответствии с правилами вызова перегруженных функций.

Если в списке специализации к-ра производного класса, к-р базовогоь класса явно не вызывается, то неявно будет вызван к. умолчания.

Здесь надо вспомнить, что к. умолчания либо явноопределяется программистом, либо неявнео создается компелятором, но такое неявное создание реализуется тольк в том случае, если в классе явно не создан никакой к. (пункт 2.3.1). Таким образом возможна ситуация когда нет к. умолчания,тогда к. производного класса пытается неявно вызвать несуществующий к. умолчания, в результате призойдет аварийная остановка на этапе компеляции.

Приведем схему последовательности вызовов к-ов иерархии классов при создании объектов производного класса для иерархии из трех уровней.

-С - производный класс

-В - прямой базовый класс для С и производный для А

-А - прямой базовый класс для В и косвенный для С

рис 4.6

Предположим. что в рамках этой иерархии каждыйкласс не только наследует элементы предка, но и создает свои,причем среди своих полей имеются поля объекты каких-то других классов х,у... Смотри "отношение включения классов" (пункт 3).

пусть создается объект класса С, тогда:

-выделяется участок памяти для объекта класса С.

-вызывается к. класса С и внутри него вызывается к. класса В, внутри которого вызывается к. класса А.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]