Лекция 12. Наследование
Наследование — один из основополагающих принципов объектно-ориентированного программирования. Под наследованием понимают возможность объявления производных типов на основе ранее объявленных типов.
Прежде всего, следует различать наследование и встраивание. Встраивание предполагает возможность объявления в классе отдельных членов класса на основе ранее объявленных классов. В классе можно объявлять как данные-члены основных типов, так и данные-члены ранее объявленных производных типов.
В случае же наследования новый класс в буквальном смысле создается на основе ранее объявленного класса, НАСЛЕДУЕТ, а возможно и модифицирует его данные и функции. Объявленный класс может служить основой (базовым классом) для новых производных классов. Производный класс наследуют данные и функции своих базовых классов и добавляют собственные компоненты.
В C++ количество непосредственных «предков» производного класса не ограничено. Класс может быть порожден от одного или более классов. В последнем случае говорят о множественном наследовании. Наследование в C++ реализовано таким образом, что наследуемые компоненты не перемещаются в производный класс, а остаются в базовом классе. Производный класс может переопределять и доопределять функции-члены базовых классов. Но при всей сложности, наследование в C++ подчиняется формальным правилам. А это означает, что, во-первых, существует фиксированный набор алгоритмов, которые позволяют транслятору однозначно различать базовые и производные компоненты классов, а во-вторых, множество вариантов наследования ограничено.
Для начала рассмотрим пример объявления нескольких классов. В этом примере задаются отношения наследования между тремя классами (классы A, B, C). При этом C наследует свойства класса B, который, в свою очередь, является наследником класса A.
class A {public: A() {printf("A");} ~A(){printf("~A");} int x0; int f0 () {return 1;}}; class B : public A {public: B(){printf("B");} ~B(){printf("~B");} int x1; int x2; int xx; int f1 () {return 100;} int f2 () {return 200;}}; class C : public B {public: C(){printf("C");} ~C(){printf("~C");} int x1; int x2; int x3; int f1 () {return 1000;} int f3 () {return 3000;}};void main () {C MyObject;}Перед нами пример простого наследования. Каждый производный класс при объявлении наследует свойства лишь одного базового класса. Слово public перед именем базового класса означает, что все поля и методы класса-родителя, объявленные как public или protected, в производном классе будут открыты. Если public отсутствует, то все члены класса-родителя в производном классе закрыты.
В C++ различаются непосредственные и косвенные базовые классы. Непосредственный базовый класс упоминается в списке баз производного класса. Косвенным базовым классом для производного класса считается класс, который является базовым классом для одного из классов, упомянутых в списке баз данного производного класса.
В нашем примере для класса C непосредственным базовым классом является B, косвенным — A.
Итак, выполнение оператора определения
C MyObj;
приводит к появлению в памяти объекта под именем MyObj.
Перед нами объект сложной структуры, в буквальном смысле собранный на основе нескольких классов. В его создании принимали участие несколько конструкторов. Порядок их вызова строго регламентирован. Вначале вызываются конструкторы базовых классов. Следом вызываются конструкторы производных классов.
Благодаря реализации принципа наследования, объект представляет собой цельное сооружение. Из объекта можно вызвать функции-члены базовых объектов. Эти функции наследуются производным классом от своих прямых и косвенных базовых классов. Непосредственно от объекта возможен доступ ко всем данным-членам. Данные-члены базовых классов также наследуются производными классами.
Если переопределить деструкторы базовых и производных классов таким образом, чтобы они сообщали о начале своего выполнения, то за вызовом деструктора производного класса C непосредственно из объекта MyObj:
MyObj.~C();
последует серия сообщений о выполнении деструкторов базовых классов. Разрушение производного объекта сопровождается разрушением его базовых компонентов. Причем порядок вызова деструкторов противоположен порядку вызова конструкторов.
А вот вызвать деструктор базового класса из объекта производного класса невозможно:
MyObj.~B(); // Так нельзя. Это ошибка!
Частичное разрушение объекта в C++ не допускается. БАЗОВЫЕ ДЕСТРУКТОРЫ НЕ НАСЛЕДУЮТСЯ. Таков один из принципов наследования.
К моменту начала разбора структуры производного класса, транслятору становятся известны основные характеристики базовых классов. Базовые классы включаются в состав производных классов в качестве составных элементов. Это означает, что в производном классе (в его функциях) можно обращаться к данным-членам и вызывать функции-члены базовых классов.
Нам остается рассмотреть, каким образом транслятор соотносит члены класса непосредственно в объекте. Для этого переопределим функцию main():
int main(int argc, char* argv[]){ C MyObj; MyObj.x0 = 1; MyObj.B::x0 = 2; MyObj.C::x0 = 3; printf("\n%d %d %d\n", MyObj.x0, MyObj.B::x0, MyObj.C::x0); //3 3 3 printf("%d\n", MyObj.f0()); //1 printf("%d\n",MyObj.A::f0()); //1 printf("%d\n",MyObj.C::f0()); //1 printf("%d\n",MyObj.B::f1()); //100 printf("%d\n",MyObj.C::f1()); //1000 printf("%d\n",MyObj.f1()); //1000 //Так нельзя // MyObj.A::f1(); // MyObj.A::f2(); // MyObj.A::f3(); // MyObj.B::f3(); getchar(); return 0;}Таким образом, корректное обращение к членам класса в программе обеспечивается операцией разрешения области видимости. Квалифицированное имя задает область действия имени (класс), в котором начинается (!) поиск данного члена класса. Принципы поиска понятны из ранее приведенного примера.
Пример. На основе класса PasSet создадим класс — «множество символов» PasSetChar.
class PasSetChar : public PasSet {public: void print();};void PasSetChar::print(){ for (int i=0; i<256; i++) if (InSet((unsigned char)i)) printf("%c ", (unsigned char)i); printf("\n");}Дата добавления: 2016-07-27; просмотров: 1545;