Смекни!
smekni.com

Объективное программирование (стр. 7 из 10)

вложенного объекта dat2

void main ------ Строка конструктора man

{ ¦

man JOHN("John","8-9-1958","15-1-1987");

} ¦ L------ Строка передается

Строка передается конструктору объекта

конструктору объекта dat2 в объекте man

dat1 в объекте man

6.2 Производные классы

---------------------

Другой случай вложенности классов основывается на понимании

класса как совокупности данных и операций над ними. При этом

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

свойств старого "базового" класса, или "наследует" их: структура

объекта старого класса включается в новый объект, а все элементы-функции старого класса применимы к объекту нового класса, точнее к его старой составляющей.

Старый класс при этом называется базовым классом (БК), новый

- производным классом (ПК).

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

class <производный> : <базовый 1>,<базовый 2>,...<базовый n>

{

определение приватной и публичной части

производного класса

}

Перечислим основные свойства базового и производного классов:

- объект базового класса определяется в производном классе как неименованный. Это значит, что он не может быть использован в явном виде как обычный именованный объект;

- элементы данных базового класса включаются в объект производного класса (как правило, компилятор размещает их в начале объекта производного класса). Oднако приватная часть базового класса закрыта для прямого использования в производном классе;

- элементы-функции базового класса "наследуются" в производном классе, то есть вызов функции, определенной в базовом классе, для объекта производного класса возможен и понимается как вызов ее для входящего в него объекта базового класса;

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

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

class a

{

public:

void f() {}

void g() {}

}

class b : a ------------------------ базовый класс

{

public: void f() -------------------- "f" переопределяется

{ ...

a::f(); -------------- явный вызов "f" для БК

} -------------- "g" наследуется из БК

void h() {} -------------- собственная функция в ПК

}

void main()

{

a A1;

b B1;

B1.f(); --------------- вызов переопределенной b::f()

B1.g(); --------------- вызов наследуемой a::f()

}

Понятие "наследования" предполагает что при вызове в производном классе функций, наследуемых из базового, транслятор производит преобразование ссылки this на объект производного класса в

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

Взаимоотношение конструкторов и деструкторов базового и производного классов аналогичны выше описанным:

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

вызываются в обратном порядке - сначала для производного, затем

для базового;

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

явно указан вызов конструктора базового класса с параметрами. Он

может быть без имени, а может быть с именем базового класса. Если

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

классов, то в вызовы конструкторов базовых классов должны быть

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

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

--------------------------------------

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

базового класса, как из элементов-функций производного класса,

так и извне - через объекты производного класса. Здесь возможны

следующие варианты:

- приватная часть базового класса A всегда включается в приватную часть производного класса B, но при этом непосредственно

недоступна из элементовфункций класса B. Это соответствует тому

факту, что в классе B разрешается работать с базовым объектом

класса A только разрешенными в классе A средствами, то есть через элементы-функции класса A. Исключение составляет объявление всего класса B дружественным в классе A;

- по умолчанию, то есть при использовании заголовка вида

class B : A { }

публичная часть класса A попадает в приватную часть класса B. Это значит, что элементы-функции класса A доступны из элементов-функций класса B, но не могут быть вызваны извне, то есть при обращении к объектам класса B. То есть для внешнего пользователя класса B интерфейс класса A закрывается;

- в противном случае, при объявлении

class B : public A { }

публичная часть класса A попадает в публичную часть класса B, и внешний пользователь при работе с объектами класса B может применить интерфейсы как производного, так и базового классов;

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

class B : A {

...

public:

...

public A::fun;

...

}

Перечисленные варианты изображены на схеме:

class A class B

-----------¬ ----------------¬

¦ privat ======================> privat A ¦

+----------+ ¦ (недоступен B)¦

¦ public ¦ class B:A +---------------+

¦ ======================> privat B ¦

¦ ¦ ¦ (доступен B) ¦

¦ ¦ class B : public A ¦===============¦

¦ ======================> public B ¦

¦ ¦ class B : A { ... ¦ ¦

¦ ¦ public A::newadr; ¦ <---- Доступ к объектам

¦ ----------------------> производного класса

L----------- L---------------

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

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

к своим элементам личной части в производных классах. Это делается при помощи объявления защищенных (protected) элементов.

Элемент с меткой protected в базовом классе входит в приватную часть базового класса. Кроме того, он доступен и в приватной

части производного класса. Если же базовый класс включается в

производный как public, то защищенный элемент становится защищенным и в производном классе, то есть может использоваться в последующих производных классах. Сказанное поясним примером и схемой:

class A

{

int a1; // Обычный приватный элемент

protected: int a2; // Защищенный приватный элемент

public:

}

class B : A // a1,a2 в приватной части B

{

void x();

}

void B::x()

{

a1 = 5; // Ошибка: a1 недоступен в B

a2 = 3; // a2 доступен в приватной части B

}

class B : public A // a2 доступен и защищен в приватной

{ // части B, неявно имеет место

// protected: int a2;

}

class A class B

-----------¬ ----------------¬

¦ privat ======================> privat A ¦

+----------+ ¦ (недоступен B)¦

¦ protected¦ class B:A +---------------+

¦ ======================> privat B ¦

¦ ===============¬ ¦ (доступен B) ¦

+----------+ class B: public A +---------------+

¦ public ¦ L======> protected B ==========>

¦ ¦ ¦===============¦

¦ ¦ ¦ public ¦

6.4 Ссылки на объекты базового и производного классов

----------------------------------------------------

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

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

типов ссылок должны быть явнями)

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

class A

{

public: void f1();

};

class B : A

{

public: void f1(); // Переопределена в классe B

void f2(); //

};

A *pa;

B *pb;

B x;

pa = &x; // Неявное преобразование ссылки

// на объект класса B в ссылку

// на объект класса A

pa->f1(); // Вызов функции из вложенного

// объекта базового класса A::f1(),

// хотя она переопределена

Обратное преобразование от ссылки на базовый класс к ссылке

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

pb = (B*) pa; // Обратное преобразование - явное

pb ->f2(); // Корректно, если под "pa" был

// объект класса "B"

6.5 Принцип объектно-ориентированного программирования

------------------------------------------------------

Понятие производного класса является основой объектноориенированного подхода к программированию, которое можно определить

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

новые функции включают вызовы ранее определенных.

При разработке объектно-ориентированной программы программист создает производные классы, которые автоматически наследуют