Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Void monstr::draw(int x, int y, int scale, int position)

{/* отрисовка монстра*/};

//-----------------------класс daemon------------------------

class daemon: public monstr{

int brain;

public:

//----------конструкторы---------------------

daemon(int br =10){brain = br;}

daemon(color sk): monstr (sk) {brain =10;}

daemon(char * nam): monstr (nam) {brain = 10;}

daemon(daemon &M): monstr (M) {brain = M.brain;}

//----------------операции-----------------------

const daemon& operator =(daemon &M){

if (&M == this) return *this;

brain = M.brain;

monstr::operator = (M);

return *this;

};

//---методы изменяющие значения полей-----

void think();

//-------------прочие методы-------------------------

void draw(int x, int y, int scale, int position);

};

//---------------реализация класса daemon----------------------

void daemon::think(){/*... */};

void daemon::draw(int x, int y, int scale, int position){/* */};

В классе daemon введено поле brain и метод think, определены собственные конструкторы и операция присваивания, а так же переопределен метод отрисовки draw. Все поля класса monstr, операции (кроме присваивания) и методы get_health, get_ammo и change_health наследуются в классе daemon, а деструктор формируется по умолчанию.

Рассмотрим правила наследования различных методов.

Конструкторы не наследуются, поэтому производный класс должен иметь собственные конструкторы. Порядок вызова конструкторов определяется приведенными ниже правилами.

· Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор базового класса по умолчанию (т.е. тот, который можно вызвать без параметров). Это использовано в первом из конструкторов класса daemon.

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

· В случае нескольких базовых классов их конструкторы вызываются в порядке объявления.

Если конструктор базового класса требует указания параметров, он должен быть явным образом вызван в конструкторе производного класса в списке инициализации (это продемонстрировано в трех последних конструкторах).

Не наследуется и операция присваивания, поэтому ее так же требуется явно определить в классе daemon. Обратите внимание на запись функции-операции: в ее теле применен явный вызов функции – операции присваивания из базового класса. Что бы лучше представить себе синтаксис вызова, ключевое слово operator вместе со знаком операции можно интерпретировать как имя функции – операции.

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

Ниже перечислены правила наследования деструкторов.

· Деструкторы не наследуются, и если программист не описал в производном классе деструктор, он формируется по умолчанию и вызывает деструкторы всех базовых классов.

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

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

Поля, унаследованные от класса monstr, недоступны функциям производного класса, поскольку они определены в базовом классе как private. Если функциям, определенным в daemon, требуется работать с этими полями, можно либо описать их в базовом классе как protected, либо обращаться к ним с помощью функций из mostr, либо явно переопределить их в daemon с помощью операции доступа к области видимости.

Рассматривая наследование методов, обратите внимание на то, что в классе daemon описан метод draw, переопределяющий метод с тем же именем в классе monstr (поскольку отрисовка разных персонажей выполняется по-разному). Таким образом, производный класс может не только дополнять, но и корректировать поведение базового класса. (Переопределять в производном классе рекомендуется только виртуальные методы). Доступ к переопределенному методу базового класса выполняется через имя, уточненное с помощью операции доступа к области видимости (::).

Виртуальные методы.

Работа с объектами чаще всего производится через указатели. Указателю на базовый класс можно присвоить значение адреса объекта любого производного класса, например:

//описывается указатель на базовый класс

monstr *p;

//указатель ссылается на объект производного класса

p = new daemon;

Вызов методов объекта происходит в соответствии с типом указателя, а не фактическим типом объекта, на который он ссылается, поэтому при выполнении оператора, например:

p->draw(1, 1, 1, 1);

будет вызван метод класса monstr, а не класса daemon, поскольку ссылки на методы разрешаются во время компоновки программы. Этот процесс называется ранним связыванием. Что бы вызвать метод класса daemon, можно использовать явное преобразование типа указателя:

(daemon * p)->draw(1, 1, 1, 1);

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

В С++ реализован механизм позднего связывания, когда разрешение ссылок на метод происходит на этапе выполнения программы в зависимости от конкретного типа объекта, вызвавшего метод. Этот механизм реализован с помощью виртуальных методов и будет рассмотрен позднее.

Для определения виртуального метода используется спецификатор virtual, например:

virtual void draw (int x, int y, int scale, int position);

Рассмотрим правила описания и использования виртуальных методов.

· Если в базовом классе метод определен как виртуальный, метод, определенный в производном классе с тем же именем и набором параметров, автоматически становится виртуальным, а с отличающимся набором параметров - обычным.

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

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

· Виртуальный метод не может объявляться с модификатором static, но может быть объявлен как дружественный.

· Если в классе вводится описание виртуального метода, он должен быть определен хотя бы как чисто виртуальный.

Чисто виртуальный метод содержит признак = 0 вместо тела, например:

virtual void f(int) = 0;

Чисто виртуальный метод должен переопределяться в производном классе (возможно, опять как чисто виртуальный).

Если определить метод draw в классе monstr как виртуальный, решение о том, метод какого класса вызывать, будет приниматься в зависимости от типа объекта, на который ссылается указатель:

monstr *r, *p;

r = new monstr; //создается объект класса monstr

p = new daemon; //создается объект класса daemon

r->draw(1, 1, 1, 1); //вызывается метод monstr::draw

p->draw(1, 1, 1, 1); //вызывается метод daemon::draw

p->monstr::draw(1, 1, 1, 1); //обход механизма виртуальных методов

 

Если объект класса daemon будет вызывать метод draw не непосредственно, а косвенно (т.е. из другого метода, определенного в классе monstr), будет вызван метод draw класса daemon.

Итак, виртуальным называется метод, ссылка на который разрешается на этапе выполнения программы- virtual в данном значении - «фактический», т.е. ссылка разрешается по факту выполнения.

Механизм позднего связывания

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

Каждый объект содержит скрытое дополнительное поле ссылки на vtbl, называе­мое vptr. Оно заполняется конструктором при создании объекта (для этого ком­пилятор добавляет в начало тела конструктора соответствующие инструкции).

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

Рекомендуется делать виртуальными деструкторы для того, чтобы гарантиро­вать правильное освобождение памяти из-под динамического объекта, поскольку в этом случае в любой момент времени будет выбран деструктор, соответствую­щий фактическому типу объекта. Деструктор передает операции delete размер объекта, имеющий тип size_t. Если удаляемый объект является производным и в нем не определен виртуальный деструктор, передаваемый размер объекта мо­жет оказаться неправильным.

Четкого правила, по которому метод следует делать виртуальным, не существует. Можно только дать рекомендацию объявлять виртуальными методы, для кото­рых есть вероятность, что они будут переопределены в производных классах. Методы, которые во всей иерархии останутся неизменными или те, которыми производные классы пользоваться не будут, делать виртуальными нет смысла. С другой стороны, при проектировании иерархии не всегда можно предсказать, каким образом будут расширяться базовые классы (особенно при проектирова­нии библиотек классов), а объявление метода виртуальным обеспечивает гиб­кость и возможность расширения.

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

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

Абстрактные классы

Класс, содержащий хотя бы один чисто виртуальный метод, называется абст­рактным. Абстрактные классы предназначены для представления общих понятий, которые предполагается конкретизировать в производных классах. Абстрактный класс может использоваться только в качестве базового для других классов — объ­екты абстрактного класса создавать нельзя, поскольку прямой или косвенный вы­зов чисто виртуального метода приводит к ошибке при выполнении.

При определении абстрактного класса необходимо иметь в виду следующее:

· абстрактный класс нельзя использовать при явном приведении типов, для описания типа параметра и типа возвращаемого функцией значения;

· допускается объявлять указатели и ссылки на абстрактный класс, если при инициализации не требуется создавать временный объект;

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

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

Множественное наследование

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

class monstr {

public:

int get_health() const {return health;}

…};

class hero {

public:

int get_health() const {return health;}

};

class ostrich: public mostr, public hero {

};

int main(){

ostrich A;

cout << A.monstr::get_health();

cout << A.hero::get_health();

}

Как видно из примера, для вызова метода get_health требуется явно указывать класс, в котором он описан. Использование обычной для вызова метода класса конструкции А.get_health () приведет к ошибке, поскольку компилятор не в состоянии разобраться, к методу какого из базовых классов требуется обра­титься.

Если у базовых классов есть общий предок, это приведет к тому, что производ­ный от этих базовых класс унаследует два экземпляра полей предка, что чаще всего является нежелательным. Чтобы избежать такой ситуации, требуется при наследовании общего предка определить его как виртуальный класс:

class monstr {

};

class daemon: virtual public monstr {

};

class lady: virtual public monstr {

};

class baby: public daemon, public lady {

…};

Класс baby содержит только один экземпляр полей класса monstr. Если базовый класс наследуется и как виртуальный, и обычным образом, в производном классе будут присутствовать отдельные экземпляры для каждого невиртуального вхож­дения и еще одни экземпляр для виртуального.

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

Отличия структур и объединений от классов

Структуры (struct) и объединения (union) представляют собой частные случаи классов.

По определению структура - это класс, все члены которого общие, т.е. описание

struct s {

...

};

это просто краткая форма описания

class s {

Public:...

};

Поименованное объединение определяется как структура, все члены которой имеют один и тот же адрес. Если известно, что в каждый момент времени используется значение только одного члена структуры, то объявив ее объединением, можно сэкономить память. Например, можно использовать объединение для хранения лексем транслятора С:

union tok_val {

char* p; // строка

char v[8]; // идентификатор (не более 8 символов)

Long i; // значения целых

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...