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

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




В языке С++ имеется возможность образовывать производный класс от нескольких базовых классов. Общая форма множественного наследования имеет вид:

class имя_произв_класса: имя_базового_кл 1,…,имя_базового_кл N

{ содержимое класса

};

Иерархическая структура, в которой производный класс наследует от несколько базовых классов, называется множественным наследованием. В этом случае производный класс, имея собственные компоненты, имеет доступ к protected- и public-компонентам базовых классов.

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

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

Class A

{public: void fun(){}

};

Class B

{public: void fun(){}

};

Class C: public A, public B

{ };

main()

{ C *c=new C;

c->fun(); // error C::f' is ambiguous

return 0;

}

При таком вызове функции fun() компилятор не может определить, к какой из двух функций классов A и B выполняется обращение. Неоднозначность можно устранить, явно указав, какому из базовых классов принадлежит вызываемая функция:

c->A:: fun(); или c->B::fun();

Вторая проблема возникает при многократном включении некоторого базового класса:

#include "iostream.h"

#include "string.h"

class A // БАЗОВЫЙ класс I уровня

{ char naz[20]; // название фирмы

public:

A(char *NAZ) {strcmp(naz,NAZ);}

~A() {cout << "деструктор класса А" << endl;}

void a_prnt() {cout << naz << endl;}

};

class B1: public A // ПРОИЗВОДНЫЙ класс (1 Базовый II уровня)

{ protected:

long tn;

int nom;

public:

B1(char *NAZ,long TN,int NOM): A(NAZ),tn(TN),nom(NOM) {};

~B1() {cout << "деструктор класса В1" << endl;}

void b1_prnt()

{ A::a_prnt();

cout << " таб. N " << tn <<" подразделение = " << nom <<endl;

}

};

class B2: public A // ПРОИЗВОДНЫЙ класс (2 Базовый II уровня)

{ protected:

double zp;

public:

B2(char *NAZ,double ZP): A(NAZ),zp(ZP) {};

~B2(){cout << "деструктор класса В2" << endl;}

void b2_prnt()

{ A::a_prnt();

cout << " зар/плата = " << zp << endl;

}

};

class C: public B1, public B2 // ПРОИЗВОДНЫЙ класс (III уровня)

{ char *fam;

public:

C(char *FAM,char *NAZ,long TN,int NOM,double ZP):

B1(NAZ,TN,NOM), B2(NAZ,ZP)

{ fam = new char[strlen(FAM)+1]

strcpy(fam,FAM);

};

~C() {cout << "деструктор класса С" << endl;}

void c_prnt()

{ B1::b1_prnt();

B2::b2_prnt();

cout << " фамилия " << fam<<endl;

}

};

 

void main()

{ C cc("Иванов","мастра",1234,2,555.6),*pt=&cc;

// cc.a_prnt(); ошибка 'C::a_prnt' is ambiguous

// pt->a_prnt();

 

cc.b1_prnt();

pt->b1_prnt();

 

cc.b2_prnt();

pt->b2_prnt();

 

cc.c_prnt();

pt->c_prnt();

}

В приведенном примере производный класс С имеет по цепочке два одинаковых базовых класса А (A<-B1<-C и A<-B2<-C), для каждого базового класса А строится свой объект (рис. 3, 4). Таким образом, вызов функции

cc.a_prnt();

pt->a_prnt();

некорректен, так как неизвестно, какую из двух функций (какого из двух классов А) требуется вызвать.

Виртуальное наследование

Если базовый класс (в приведенном выше примере это класс А) является виртуальным, то будет построен единственный объект этого класса (см. рис. 2).

#include ”iostream.h”

class A // базовый виртуальный класс

{ int aa;

public:

A() {cout<<"Конструктор1 класса A"<<endl;}

A(int AA): aa(AA) {cout<<"Конструктор2 класса A"<<endl;}

~A() {cout<<"Деструктор класса A"<<endl;}

};

class B: virtual public A // производный класс (1 Базовый)

{ char bb;

public:

B() {cout<<"Конструктор1 класса B"<<endl;}

B(int AA,char BB): A(AA), bb(BB)

{cout<<"Конструктор2 класса B"<<endl;}

~B() {cout<<"Деструктор класса B"<<endl;}

};

class C: virtual public A // производный класс (2 Базовый)

{ float cc;

public:

C() {cout<<"Конструктор1 класса C"<<endl;}

C(int AA,float CC): A(AA), cc(CC)

{cout<<"Конструктор2 класса C"<<endl;}

~C() {cout<<"Деструктор класса C"<<endl;}

};

class D: public C,public B // производный класс (2 Базовый II уровня)

{ int dd;

public:

D() {cout<<"Конструктор 1 класса D"<<endl;}

D(int AA,char BB,float CC,int DD):

A(AA), B(AA,BB), C(AA,CC), dd(DD)

{cout<<"Конструктор 2 класса D"<<endl;}

~D() {cout<<"Деструктор класса D"<<endl;}

};

 

void main()

{ D d(1,'a',2.3,4);

D dd;

}

Результат работы программы:

Конструктор 2 класса A (конструкторы для объекта d)

Конструктор 2 класса C

Конструктор 2 класса B

Конструктор 2 класса D

Конструктор 1 класса A (конструкторы для объекта dd)

Конструктор 1 класса C

Конструктор 1 класса B

Конструктор 1 класса D

Деструктор класса D (деструкторы для объекта d)

Деструктор класса B

Деструктор класса C

Деструктор класса A

Деструктор класса D (деструкторы для объекта d)

Деструктор класса B

Деструктор класса C

Деструктор класса A

Виртуальный базовый класс всегда инициализируется только один раз. В примере при создании объектов d и dd конструктор класса А вызывается из конструктора класса D первым и только один раз, затем - конструкторы классов B и C, в том порядке, в котором они описаны в строке наследования классов:

class D: public B, public C.

В одно и то же время класс может иметь виртуальный и невиртуальный базовые классы, например:

class A{ … };

class B1: virtual public A{ … };

class B2: virtual public A{ … };

class B3: public A{ … };

class C: public B1, public B2, public B3 { … };

В этом случае класс С имеет два подобъекта класса А, один наследуемый через классы В1 и В2 (общий для этих классов) и второй через класс В3

(рис.5, 6).

 

#include "iostream.h"

#include "string.h"

 

class A // базовый класс I уровня

{ char *naz; // название фирмы

public:

A(char *NAZ)

{ naz=new char[strlen(NAZ)+1];

strcpy(naz,NAZ);

}

~A()

{ delete naz;

cout << "деструктор класса А" << endl;

}

void a_prnt(){cout <<"марка а/м "<< naz << endl;}

};

class B1: virtual public A // производный класс (1 Базовый II уровня)

{ protected:

char *cv; // цвет а/м

int kol; // количество дверей

public:

B1(char *NAZ,char *CV,int KOL): A(NAZ),kol(KOL)

{ cv=new char[strlen(CV)+1];

strcpy(cv,CV);

}

~B1()

{ delete cv;;

cout << "деструктор класса В1" << endl;

}

void b1_prnt()

{ A::a_prnt();

cout << "цвет а/м" << cv <<" кол-во дверей " << kol <<endl;

}

};

class B2: virtual public A // производный класс (2 Базовый II уровня)

{ protected:

int pow; // мощность а/м

double rs; // расход топлива

public:

B2(char *NAZ,int POW,double RS): A(NAZ),pow(POW),rs(RS) {};

~B2(){cout << "деструктор класса В2" << endl;}

void b2_prnt()

{ A::a_prnt();

cout <<"мощность двигателя "<<pow<<" расход топлива "<<rs;

cout<<endl;

}

};

class C: public B1,public B2 // производный класс (2 Базовый II уровня)

{ char *mag; // название магазина

public: C(char *NAZ,char *CV,int KOL,int POW,double RS,char *MAG):

B1(NAZ,CV,KOL),B2(NAZ,POW,RS),A(NAZ)

{ mag =new char[strlen(MAG)];

strcpy(mag,MAG);

}

~C()

{ delete mag;

cout << "деструктор класса С" << endl;

}

void c_prnt()

{ A::a_prnt();

B1::b1_prnt();

B2::b2_prnt();

cout << " название магазина" << mag <<endl;

}

};

 

void main()

{ C cc("BMW","красный",100,4,8.5,"магазин 1"),*pt=&cc;

cc.a_prnt();

pt->a_prnt();

 

cc.b1_prnt();

pt->b1_prnt();

 

cc.b2_prnt();

pt->b2_prnt();

 

cc.c_prnt();

pt->c_prnt();

}

 

Перегрузка функций

Одним из подходов реализации принципа полиморфизма в языке С++ является использование перегрузки функций. В С++ две и более функций могут иметь одно и то же имя. Компилятор С++ оперирует не исходными именами функций, а их внутренними представлениями, которые существенно отличаются от используемых в программе. Эти имена содержат в себе скрытое описание типов аргументов. С этими же именами работают программы компоновщика и библиотекаря. По этой причине мы можем использовать функции с одинаковыми именами, только типы аргументов у них должны быть разными. Именно на этом и основана реализация одной из особенностей полиморфизма. Заметим, что компилятор не различает функции по типу возвращаемого значения. Поэтому для компилятора функции с различным списком аргументов – это разные функции, а с одинаковым списком аргументов, но с разными типами возвращаемого значения - одинаковые. Для корректной работы программ последнего следует избегать. Функции, имеющие одинаковые имена, но разные списки аргументов, называются перегруженными. Рассмотрим простой пример перегрузки функции sum, выполняющей сложение нескольких чисел различного типа.

#include "iostream.h"

Class cls

{ int n;

double f;

public:

cls(int N,float F): n(N),f(F) {}

int sum(int); // функция sum с целочисленнным аргументом

double sum(double); // функция sum с дробным аргументом

void see(); // вывод содержимого объекта

};

int cls:: sum(int k)

{ n+=k;

return n;

}

double cls:: sum(double k)

{ f+=k;

return f;

}

void cls:: see()

{cout <<n<<' '<<f<<endl;}

 

void main()

{ cls obj(1,2.3);

obj.see(); // вывод содержимого объекта

cout <<obj.sum(1)<<endl; // вызов функции sum с целочисл. аргументом

cout <<obj.sum(1.)<<endl; // вызов функции sum с дробным аргументом

}

Результат работы программы:

1 2.3

3.3

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

Class A

{ ...

public:

void fun(int i,long j) {cout<<i+j<<endl;}

void fun(long i,int j) { cout<<i+j<<endl;}

};

 

main()

{ A a;

a.fun(2,2); // ошибка неизвестно какая из 2 функций вызывается

}

В этом случае возникает неоднозначность вызова функции fun объекта a.

 

Перегрузка операторов

Имеется ограничение в языках С и С++, накладываемое на операции над известными типами данных (классами) char, int, float и т.д.:

char c,

int i,j;

double d,k;

В С и С++ определены множества операций над объектами c,i,j,d,k этих классов, выражаемых через операторы: i+j, j/k, d*(i+j). Большинство операторов (операций) в С++ может быть перегружено (переопределено), в результате чего расширяется диапазон применения этих операций. Когда оператор перегружен, ни одно из его начальных значений не теряет смысла. Просто для некоторого класса объектов определен новый оператор (операция). Для перегрузки (доопределения) оператора разрабатываются функции, являющиеся либо компонентами, либо friend-функциями того класса, для которого они используются. Остановимся на перегрузке пока только с использованием компонент- функций класса.

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

Поделиться:





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



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