Министерство образования и науки Республики Казахстан

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

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

2.1.1 Вложенные классы

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

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

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

class man {

char name[20]; // Другие элементы класса

char *address;

dat dat1; // Дата рождения

dat dat2; // Дата поступления на работу

public:

void newadr(); // Элемент-функция

man(char*); // Конструктор

}

//----- Функция "Изменить адрес проживания" ----------------

void man:: newadr() {

int n;

char s[80]; // Строка нового адреса

if (address != NULL)

delete address; // Освободить память

printf("Введите новый адрес:");

gets(s);

address = new char[strlen(s)+1];// Занять новую память

strcpy(address,s); // Заполнить поле адреса

}

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

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

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

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

class man {

char name[20]; // Другие элементы класса

dat dat1; // Дата рождения

dat dat2; // Дата поступления на работу

public:

man(char *,char *,char *); // Конструкторы

man(char *);

}

//----- Конструктор класса man с неявным вызовом конструкторов

// для dat1 и dat2 без параметров

//-----------------------------------------------------

man::man(char *p) {

}

// Конструктор класса man с явным вызовом конструкторов

// для dat1 и dat2 с параметрами

man::man(char *p,char *p1, char *p2) : dat1(p1), dat2(p2) {

// Тело конструктора

}

void main { man JOHN("John","8-9-1958","15-1-1987"); }

2.1.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 на объект производного класса в ссылку на входящий в него объект базового класса, учитывая размещение последнего в объекте производного класса.

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

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

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

2.1.3 Правила наследования

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

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

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

class A {

int i,k;

public:

A(void) { i=0; k=0; }

A(int i1,int k1=0): i(i1),k(k1) {}

int geti(void) { return i; }

void print(void) { cout << " A "<< i <<k; }

};

class B: public A {

float s;

public:

B(void): A() { s=0.; }

B(int i1,int k1=0,float s1=0.): A(i1,k1),s(s1) {}

float gets(void) { return s; }

void print(void){A::print(); cout << " s="<< s; }

};

class C: private A {

public:

C(void):A() {}

C(int i1,int k1=0):A(i1,k1) { }

int geti(void) { return A::geti(); }

void print(void) { A::print(); }

};void main() {

A a1, a2(10);

B b1,b2(20,30,40);

C c1,c2(50,60);

}

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

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

- приватная часть базового класса 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;

...

}

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

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

class A {

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

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

public: }

сlass B : A { void x();} // a1,a2 в приватной части B

void B:: x() {

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

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

class B : public A{ } // a2 доступен и защищен в приватной части B, //неявно имеет место protected: int a2;

2.2 Правила доступа для объектов и классов

· Элементы, объявленные как private, доступны функциям-членам и друзьям класса.

· Элементы, объявленные как protected, доступны функциям-членам, друзьям класса и функциям-членам производных классов.

· Элементы, объявленные как public, доступны любым функциям.

· Когда класс используется как открытый (public) базовый класс, его открытые элементы становятся открытыми элементами производного класса, а защищенные (protected) элементы - защищенными элементами производного класса.

· Когда класс используется как закрытый (private) базовый класс, его открытые и защищенные элементы становятся закрытыми элементами производного класса.

class Base {

private:

type data1;

returntype function1();

protected:

type data2;

returntype function2();

public:

type data3;

returntype function3();

};

class PublicDerived: public Base {

private:

type data4;

returntype function4();

protected:

type data5;

returntype function5();

public:

type data6;

returntype function6();

};

class PrivateDerived: private Base {

private:

type data7;

returntype function7();

protected:

type data8;

returntype function8();

public:

type data9;

returntype function9();

};

main() {

Base base;

PublicDerived pub;

PrivateDerived priv;

}

Доступ к протоколу класса

Описание класса имеет доступ ко всем своем собственным частям. Любая функция-член в закрытой (private), защищенной(protected) или открытой (public) части описания класса имеет прямой доступ ко всем полям данных и всем функциям-членам описания класса. Таким образом, любая функция-член в классе Base имеет прямой доступ к data1, data2, data3, function1, function2, function3. Аналогично, любая функция-член класса PublicDerived имеет прямой доступ к data4, data5, data6, function4, function5, function6. Любая функция в классе PrivateDerived имеет прямой доступ к data7, data8, data9, function7, function8, function9.

Доступ через протокол производного класса

Производные классы наследуют все протоколы (включая все поля данных и функции-члены) от своего базового класса. Однако производный класс не имеет прямого доступа к полям данных или функциям-членам, определенным в закрытой части его базового класса. Классы PublicDerived и PrivateDerived наследуют data1, function1 от своего базового класса (Base), но не имеют прямого доступа к ним. Доступ можно обеспечить через функцию, определенную в защищенной или открытой части класса Base. Эта функция будет наследоваться открытыми и защищенными производными классами и будет доступна им.

Доступ через объекты класса

Объекты класса не имеют доступа к защищенной части описания класса. Таким образом, объект аBase не имеет доступа к своим собственным закрытым элементам data1, function1.

Доступ через объекты public-производного класса

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

aPublicDerived.privateAccessFunction(newValue);

Однако объекту aPublicDerived недоступны его собственные закрытые элементы data4, function4. Чтобы сделать их доступными необходимо добавить метод доступа в открытой части класса PublicDerived.

Доступ через объекты private-производного класса

Объекты private-производного класса не имеют доступ к закрытой части их базового класса. Элементы data1, function1 недоступны объекту aPrivateDerived. Даже добавление функции privateAccessFunction к открытой части Base не обеспечит этот доступ, так как объектам private-производного класса недоступны элементы открытой части их базового класса.

Защищенная часть класса интерпретируется как открытая для его протокола и протоколов производных классов (public-, private-производных классов). Защищенная часть интерпретируется как закрытая для доступа через объекты класса и объекты производных классов (public, private - производных классов).

Доступ через протокол класса

Протокол класса имеет доступ ко всем его частям. Любая функция-член класса Base имеет прямой доступ к data2,function2.

Доступ через протокол производного класса

Протокол производного класса имеет прямой доступ к защищенным частям его базового класса и ко всем частям его собственного протокола. Класс PublicDerived1 имеет прямой доступ к data2, data5, function2, function5. Класс PrivateDerived1 имеет прямой доступ к data2, data8, function2, function8.

Доступ через объекты класса

Объекты класса не имеют прямого доступа к защищенным частям протокола. Через объекты aBase нельзя получить прямой доступ к data2, function2. Возможно осуществить непрямой доступ через определение метода доступа в открытой части класса Base.

Доступ через объекты public-производного класса

Нельзя осуществить доступ к защищенной части любого производного класса через его объекты. Объект aPublicDerived1 не имеет прямого доступа к data2,data5,function2, function5.

Доступ через объекты private-производного класса

Нельзя осуществить доступ к защищенной части любого производного класса через его объекты. Объект aPrivateDerived1 не имеет прямого доступа к data2,data8,function2, function8.

Открытая часть классов

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

Доступ через протокол класса

Любая функция-член описания класса имеет прямой доступ к его открытой части. Протокол класса Base имеет прямой доступ к data3, function3

Доступ через протокол производного класса

Любая функция-член производного класса имеет прямой доступ к открытой части его базового класса. Это справедливо как для public-,так и для private-производных классов. Кроме того, производный класс имеет прямой доступ к собственной открытой части. Протокол класса PublicDerived имеет прямой доступ к data3,function3, data6,function6. Протокол класса PrivateDerived1 имеет прямой доступ к data3,data9, function3, function9.

Доступ через объекты класса

Все объекты какого-либо класса имеют прямой доступ к открытой части протокола своего класса. Объект aBase имеет прямой доступ к data3, functuon3

Доступ через объекты public-производного класса

Объекты public-производного класса имеют прямой доступ к открытой части своего класса и его базового класса. Объекты aPubcliDerived имеет прямой доступ к data3, data6, function3, function6.

Доступ через объекты private-производного класса

Объекты private-производного класса имеют доступ к открытой части своего класса, но не имеют доступа к открытой части базового класса. Объект aPrivateDerived имеет прямой доступ к data9, function9, но не имеет прямого доступа к data3, function3.

Обобщение правил доступа

· Элементы, объявленные как закрытые (private), могут использоваться функциями-членами и друзьями класса.

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

· Элементы, объявленные как открытые (public), могут использоваться любой функцией.

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

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