1
background image

8. ПРОИЗВОДНИ КЛАСОВЕ, НАСЛЕДЯВАНЕ И ПОЛИМОРФИЗЪМ

Даден   клас   може   да   бъде   деклариран   като   производен   клас   (клас   наследник)     на   други   вече 

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

Възможността   да   се   създават   производни   класове   осигурява   следните   нови   възмжнсти   при 

създаването на програми:

1. Могат   да   се   изграждат   йерархии   от   класове,   които   най-добре   съответстват   на   разглежданите 

обекти и процеси.

2. Когато   обекти   от   различни   класове   имат   общи   компоненти,   общите   компоненти   могат   да   се 

обособят в отделен клас, базов за двата класа.

3. Когато искаме да обединим в един клас обекти, част от компонентите на които не са еднакви, могат 

да се създадат два класа, базови за трети производен клас.

4. Производните класове  са средство за реализиране  на полиформизъм  (еднотипна  обработка на 

информационни структури като масиви, списъци, дървета и графи, чиито елементи са обекти от 
различни класове)

8.1.

Деклариране на клас, като производен на базов клас

Даден клас P се декларира като производен на класа B както следва:

class P : [Атрибут за наследяване] B {

 

Компоненти на класа

};

Класът В е базов за производния клас и трябва да е деклариран преди него. Като атрибут за 

наследяване може да се посочи една от ключовите думи public, private или protected (private e 
подразбираща се). Тяхното значение ще изясним по-нататък.

С   декларирането   на   даден   клас   като   производен,   той   ще   наследи   (ще   получи   достъп)   до 

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

Наследяването се изразява в следното:

-

наследяват се член-данните и член-функциите на базовия клас;

-

член-функциите на производния клас получават достъп до някои от наследените компоненти;

-

производният клас “познава” само реализацията на базовия клас, от който произлиза.

8.2.

Достъп до наследените компоненти

В гл.7 написахме редица програми с класове и обекти, без да създаваме производни класове. Нека 

да си припомним, че компонентите на тези класове бяха  

private

  (частни, собствени, достъпни само за 

член-функциите на класа),  

public

  (публични, общи за всички функции и член-функции от програмата)  и 

protected.  При отсъствие на производни класове ключовата дума  protected  е еквивалентна на ключовата 
дума private. При наличие на производни класове обаче те не са еквивалентни, защото достъпът на член-
функциите   на   производния   клас   е   различен   за   наследените  

private

  и   наследените 

protected 

компонентите на базовия клас. 

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

Програма 8.1.

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

#include <iostream.h>

//Базов клас
class B{

 private: int b1;
 protected: int b2;

 public:
  int b3;

  void getB(){b1=11;b2=12;b3=13;}
  void displayB(){cout<<b1<<"  "<<b2<<"  "<<b3<<"  ";}

};
//Производен клас

class P:public B

//За проследяване достъпа до наследените

//class P:protected B

//компоненти при трите атрибута за

//class P:private B

//наследяване

{

 private: int p1;
 protected: int p2;

 public:

1

background image

  int p3;
  void getP(){

getB();
p1=21;

p2=22;
p3=23;

 }
 void displayP(){

cout<<p1<<"  "<<p2<<"  "<<p3<<"  ";
displayB();

 }
};

void main()

{
 B b; b.getB(); b.displayB(); cout<<endl;

 P p; p.getP(); p.displayP(); cout<<endl;

p.getB(); p.displayB(); cout<<endl;

 p.b3=1; //p.b2=1; p.b1=1;
 p.p3=1; //p.p2=1; p.p1=1;

}

В горната програма е деклариран базов клас 

B

 с три член-данни и две член-функции и производен 

клас 

Р

 също с три член-данни и две член-функции. В главната функция са дефинирани обектите 

b

 и 

p

.

Обектът 

b

 е от базовия клас 

В

. Поради това обектът 

b

 има три член данни (

b1, b2, b3

) и може да 

използва двете член-функции на класа. 

Обектът  

p

  е   от   производния   клас  

Р

.   Той   има   три   собствени   член-данни  (

p1,   p2,   p3

),  три 

наследени член-данни  (

b1, b2, b3

)   и може да използва двете член-функции на производния клас  P  и 

двете   член-функции   на   базовия   класа  

В

.   Член-функциите   на   производния   клас   нямат   пряк   достъп   до 

наследената  

private

  компонента  

p1

.   Тя   е   достъпна   само   чрез   член-функциите   на   базовия   клас. 

Наследените член-данни  

b2

  и  

b3  

са  достъпни за член-функциите на производния клас, въпреки това в 

програмата те се въвеждат и извеждат чрез наследените член-функции на базовия клас. 

Препоръчваме на читателя да проследи изпълнението на програмата при трите възможни атрибути 

за наследство и да направи съответните заключения. 

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

декларирана   тя   в   базовия   клас   (

private,   protected  

или

  public

)   и  от   атрибута   за   наследяване, 

посочен при декларирането на производнивя клас. Тази зависимост е показана в следващата таблица.

Атрибутът за

наследяване е:

Наследената компонента

в базовия клас е:

Наследената компонента

в производния клас е:

  public 

     private

     private

     protected

     protected

     public

     public

  protected

     private

     private

     protected

     protected

     public

     protected

  private

     private

     private

     protected

     private

     public

     private

Горната таблица трябва да се разбира така:
Статутът на наследените компоненти има важно значение при създаване на следващ производен 

клас.

Производният   клас   наследява   всички   компоненти   на   базовия   клас,   независимо   от   атрибута   за  

наследяване. Член-функциите на производния клас имат пряк достъп само до наследените компоненти, 
които  в базовия  клас  са  декларрирани като  

protected  

или

  public.

  До наследените  компоненти, 

които в базовия клас са  

private,

  те нямат пряк достъп, но имат непряк достъп до тях чрез

 

член-

функциите на базовия клас. 

Външна функция има достъп само до компонентите със статут

 public.

Функциите, приятели на производен клас, имат същите права на достъп като член-функциите на  

производния клас, т.е. те имат пряк достъп до всички собствени компоненти на производния клас и до  
наследените като 

public

 и 

protected

 компоненти на базовия клас. Декларацията за приятелство не  

се наследява. Функцията приятел на базовия клас не е приятел и на производния клас.

Нека да разгледаме друг вариант на горната примерна програма:

2

background image

Всеки   производен   клас   може   да   бъде   базов   клас   на   друг   производен   клас.   Достъпът   на   член-

функциите   на   последния   базов   клас   до   пряко   и   непряко   наследените   компоненти   се   определя   от 
атрибутите за достъп при всяко дефиниране на производен клас. Това може да се проследи чрез тестване 
на 9-те възможни варианти на комбинации от атрибути за наследяване в следващата примерна програма.

Програма 8.2.

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

#include <iostream.h>
// class B - базов клас

class B{
 private: int b1;

 protected: int b2;
 public:

  int b3;
  void getB(){b1=11; b2=12; b3=13;}

  void displayB(){cout<<"  "<<b1<<"  "<<b2<<"  "<<b3<<"  ";}
}; 

//class PB - производен на класа B и базов на класа Р
class PB:public B {

//За проследяване достъпа до наследените

//class PB:protected B ( //компоненти при трите атрибута за
//class PB:private B {

//наследяване

 private: int pb1;
 protected: int pb2;

 public:
  int pb3;

  void getPB(){

getB();

pb1=21; pb2=22; pb3=23;

  }

 void displayPB(){
  displayB();

  cout<<pb1<<"  "<<pb2<<"  "<<pb3<<"  ";
 }

};
//class P - производен на класа РB

class P:public PB

//За проследяване достъпа до наследените

//class P:protected PB

//компоненти при трите атрибута за

//class P:private PB

//наследяване

{

 private:
  int p1;

 protected:
  int p2;

 public:
  int p3;

  void getP(){

getPB();

p1=31;
p2=32;

p3=33;

 }

 void displayP(){
  displayPB();

  cout<<p1<<"  "<<p2<<"  "<<p3<<endl;
 }

};

void main()
{

  B b; b.getB(); b.displayB(); cout<<endl;
  PB pb; pb.getPB(); pb.displayPB(); cout<<endl;

  P p; p.getP(); p.displayP(); cout<<endl;
  //cout<<b.b1<<' '<<b.b2<<' '<<b.b3<<endl;

  //cout<<p.b1<<' '<<p.b2<<' '<<p.b3<<endl;
}

3

background image

Програма 8.3.

  За група студенти са дадени следните данни: име, ЕГН, фак. номер, оценки по 10 

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

#include <iostream.h>

//Базов клас Lice
class Lice {

  private:
    char Ime[21];  

    char EGN[11];
  public:

    void getDataL();  
    void displayL()   

//- вградена член-функция displayL

     {cout<<"\n Име: "<<Ime<<"\n ЕГН: "<<EGN;}
};

// Дефиниция на член-функция getData
void Lice::getDataL()

  {
    cin.sync();

    cout<<"Въведете име: "; cin.getline(Ime,20);
    cout<<"Въведете ЕГН: "; cin>>EGN;

  }
//Производен клас Student

class Student:private Lice {
  private:

    char FakNom[7];
    short Ocenki[10]; 

//Оценките по 10 дисциплини

    float Uspeh;
  public:

    void getDataS();  

//- прототип на член-функцията getData

    void displayS()   

//- вградена член-функция display

     {
       this->displayL();

       cout<<"\n Фак. номер: "<<FakNom<<"\n Оценки: ";
       for (int i=0; i<10; i++) cout<<Ocenki[i]<<' ';

       cout<<"\n  Общ успех на групата: "<<Uspeh;
     }

    float getUspeh();   //- прототип на член-функцията getUspeh
};

// Дефиниция на член-функция getData

   void Student::getDataS()
  {

    this->getDataL();
    cout<<"Въведете фак. номер: "; cin>>FakNom;

    cout<<"Въведете 10 оценки"<<endl;
    for (int i=0; i<10; i++) {cout<<i<<"-а оценка: "; cin>>Ocenki[i];}

  }
// Дефиниция на член-функция getUspeh

float Student::getUspeh()
{

  float s=0; 
  for (int i=0; i<10; i++) s+=Ocenki[i];

  Uspeh=s/10;
  return Uspeh;

}
// Главна функция

void main()
{

  Student Grupa[30]; int Br; float Suma=0;
  cout<<"\nВъвеждане на данните:\n";

  cout<<"Брой на студентите: "; cin>>Br;
  for (int i=0; i<Br; i++) Grupa[i].getDataS();

  for (i=0; i<Br; i++) Suma+=Grupa[i].getUspeh();
  float ObshtUspeh=Suma/Br;

  cout<<"\nИзвеждане на данните:\n";
  for (i=0; i<Br; i++) Grupa[i].displayS();

  cout<<"\nОбщ успех на групата: "<<ObshtUspeh<<endl;

4

background image

}

8.3.

Едноименни член-функции в производни и базови класове

Много  често   при  проектирането   на   различни   класове  се   предвиждат   еднакви   по   смисъл  операции, 

прилагащи   се   върху   различни   по   тип   данни.   Целесъобразно   член-функциите   на   различните   класове, 
реализиращи   подобни   операции,   да   бъдат   с   еднакви   имена.   Поради   локалността   на   член-функциите   в 
рамките на класовете, използуването на еднакви имена на член-функциите в различни класове не води до 
конфликт.

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

еднакви имена. Нека член-функцията 

f()

 е дефиниранa в класовете 

А

 и 

В

, като класът 

В

 е базов за класа 

А

. Тогава класът 

А

 ще притежава две член-функции с име 

f()

. Едната от тях е член-функцията на класа 

А

а другата е наследената от базовия клас  

В

. Извикването на член-функцията  

f()

  чрез обект на класа  

А 

(например 

a.f())

, където 

a

 е обект на класа 

А

 ) винаги ще води до изпълнение на член-функцията 

f()

 на 

класа  

А

. З

а да се извика наследената член-функция  

f()

, трябва да се използва нейното пълно име,  

състоящо се от името на класа, на който тя принадлежи (в случая това е класът  

В

), оператора за 

принадлежност :: и името на член-функцията.

 Следователно извикването на наследената член-функция 

f()

  се задава чрез конструкцията  

B::f()

. Използуването на едноименни член-функции в производни и 

базови класове е илюстрирано в програма 8.4. 

Програма 8.4.

 Едноименни член-функции в базов и производен клас

#include <iostream.h>
#include <string.h>

//Дефиниция на базов клас B
class B {

 protected:
  int x;

 public:
  void getData()

  {
   cout<<"Базов калс -> Въведете х: "; cin>>x;

  }
  void display()

  {
   cout<<"Базов калс -> х= "<<x<<endl;

  }
};

//Декларация на производен клас P
class P:public B {

 protected:
  int y;

 public:
  void getData()  //Дефиниция на член-функцията getData

  {
   B::getData(); //Извикване на член-функцията getData() на базовия клас

   cout<<"Производен калс -> Въведете у: "; cin>>y;
  }

   void display()
  {

   cout<<"Базов калс -> х= "<<x<<endl;
   cout<<"Производен калс -> y= "<<y<<endl;

  }
};

//Дефиниция на фунрция main
void main()

{
  P p;

  p.B::getData();//Извикване член-функцията на getData на базовия клас
  p.B::display();//Извикване член-функцията на display на базовия клас

  cout<<endl;
  p.getData();//Извикване член-функцията на getData на произв. клас

  p.display();//Извикване член-функцията на display на произв. клас
}

Резултатът от изпълнението на горната програма е следният:

Базов клас -> Въведете x: 1
Базов клас -> x: 1

Базов клас -> Въведете x: 5
Производен клас Въведете y: 10

5

background image

Базов клас -> x: 5
Производен клас -> y: 10

8.4.

Конструктори и деструктори на производни класове

Производните   класове   могат   да   имат   конструктори,   но   изпълнението   на   конструкторите   и 

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

-

Извикването  на  конструктор  на  производен клас води до извикване на конструкторите  на неговите 
базови   класове   и   след   завършване   на   тяхното   изпълнение   се   изпълнява   конструкторът   на 
производния клас.

-

Деструкторите на производния клас и на неговите базови класове се изпълняват в ред, обратен на 
реда на изпълнение на техните конструктори. Най-напред се изпълнява деструкторът на производния 
клас, а след това - деструкторите на неговите базови класове.
При използуването на конструктори и деструктори на производни и базови класове трябва внимателно 

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

protected

  в базовия клас  

B

  се използува и от производния клас  

P

. Лесно може да се 

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

P

 и веднъж в деструктора на 

B

.

8.4.1. Конструктори без аргументи

Конструкторите без аргументи се изпълняват при създаването на обекти от съответния клас и най-

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

Програма 8.5.

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

Поставяме си задачата да създадем програма, която въвежда и извежда името и датата на раждане 

на група лица при следните изисквания:

-

програмата да е с базов и производен клас;

-

данните да се въвеждат от конструктори без аргументи;

-

за всяко лице да се въвежда най-напред името и след това датата на раждане.
Ще дефинираме два класа:

-

клас Lice с една член-данна – името на лицето; 

-

клас Datа с три член-данни - D, M и G, които са компоненти на датата.

Трябва да решим, кой от двата класа да е базов и кой – производен.
Нека   да   си   припомним,   че   извикването   на   конструктор   на   производния   клас   поражда   извикване   и 
изпълнение на конструктора на базовия клас и едва след това изпълнение на производния клас. Като 
отчетем това, както и изискването на условието на задачата най-напред трябва да се въвежда името на 
лицето   и   след   това   датата   на   раждане,   следва   да   приемем   класа  Lice  за   базов,  а   класа  Data  за 
производен клас.
 

Масивът   с   данните   за   лицата   трябва   да   е   динамичен,   тъй   като   данните   се   въвеждат   от 

конструктори.

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

#include <iostream.h>
// Клас Lice

class Lice{
  public:

    char Ime[20]; 

// Име - член-данна на класа Lice

    Lice(); 

// Прототип на конструктор без аргументи

    void display(); 

// Прототип на член-функцията display

  };

// Дефиниция на член-функцията getData от класа Lice
Lice::Lice()

 {
   cin.sync();//За синхронизиране на вътр. буфер с външното устройство

   cout<<"Въведете име: "; cin.getline(Ime,20);    cin.sync();
 }

// Дефиниция на член-функцията display от класа Lice
void Lice::display()

 {
   cout<<"Име: "<<Ime<<endl;

 }
//Клас Datа

class Datа:public Lice {
  public:

    int D,M,G;      

//Член-данни на класа Datа

    Datа(); 

    

//Прототип на конструктор без аргументи

6

background image

    void writeDat();

//Прототип на член-функцията writeDat

};

// Дефиниция на член-функцията readDat от класа Datа
Datа::Datа(){

 cout<<"Въведете дата на раждане:\n";
 cout<<"- ден: "; cin>>D;

 cout<<"- месец: "; cin>>M;
 cout<<"- година: "; cin>>G;

}
// Дефиниция на член-функцията writeDat от класа Datа

void Datа::writeDat(){
  display();

  cout<<"Дата на раждане: "<<D<<'.'<<M<<'.'<<G<<endl;
}

//Главна функция
void main()

 {
   Datа *Grupa; int br;

   cout<<"\nВъвеждане на данните:\n";
   cout<<"Брой лица: "; cin>>br;

   Grupa=new Datа[br];
   cout<<"\nИзвеждане на данните:\n";

   for (int i=0;i<br;i++) Grupa[i].writeDat();
   delete []Grupa;

 }

8.4.2. Конструктори с аргументи

Използуването на конструктори на производни класове, чиито базови класове имат конструктори с 

аргументи, е свързано с някои особености. Нека да е дефиниран производен клас  

P

  с базов клас  

В

. Ако 

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

int

 и 

float

, то тези аргументи трябва 

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

P

 трябва да бъде дефиниран по следния начин:

P::P ( int х, float y, int z ) : B(x,y) 

{
   Оператори

}

Първите   два   от   аргументите   на   конструктора   на   класа  

P

  са   необходими,   само   за   да   бъдат 

предадени   на   конструкторa   на   базовия   клас.   Аргументите   се   предават   чрез   явното   извикване   на 
конструктора   на   базовия   клас.   Забележете,   че   извикването   им   не   е   в   тялото   на   конструктора   на 
производния клас 

P

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

това   е   обстоятелството,   че   конструкторът   на   базовия   клас   се   изпълнява   след   извикването,   но   преди 
изпълнението на конструктора на производния клас.
Използуването на конструктори и деструктори на производни класове е илюстрирано в програма 8.5. 

Програма 8.6.

 Кoнcтpyктopu на производни и базови класове с аргументи.

#include <iostream.h>

#include <string.h>
//Декларация на клас Kola

class Kola{ 

      //Базов клас Kola

    char *Marka;

    int GodPro;
  public:

    Kola(char*,int);

  

// Кoнcтpyктop

    void displayKola();

    ~Kola() {delete Marka; cout<<"Изтрита е марката.\n";}//Дecтpyктop
};

//Дефиниция на конструктора
Kola::Kola(char *m,int g)

{
  Marka=new char[strlen(m)+1];

  strcpy(Marka,m);
  GodPro=g;

}
//Дефиниция на член-функцията displayKola

void Kola::displayKola()

7

background image

{
  cout<<"Mapкa: "<<Marka<<endl;

  cout<<"Година: "<<GodPro<<endl;
}

//Декларация на производния клас Kolata
class Kolata:public Kola {

    char *RegNom;
  public:

    Kolata(char *,int,char *); //Кoнcтpyктop
    ~Kolata(){delete RegNom;cout<<"Изтрит рег. номер.\n";}//Дecтpyктop

    void displayKolata();
};

//Дефиниция на кoнcтpyктopa на производния клас Kolata
Kolata::Kolata(char *m,int g,char *rn):Kola(m,g)

{
   RegNom=new char[strlen(rn)+1];

   strcpy(RegNom,rn);
}

//Дефиниция на член-функцията displayKolata
void Kolata::displayKolata()

{
  displayKola();

  cout<<"Peгиcтpационен номер: "<<RegNom<<'\n';
}

//Дефиниция на функция main
void main()

{
  //Аргументи на кoнcтpуктора 

  Kolata Cola("Мерцедес",2005,"P 2367");
  Cola.displayKolata();

}

8.5.

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

Производен клас може да има няколко базови класа. В този случай той наследява компонентите на 

всички базови класове и казваме, че наследяването е множествено.  

Даден клас 

Р

 се декларира като производен на класовете B1, B2, . . . , Bn както следва:

class P:[public|private|protected]  B1,...,[public|private|protected] Bn{

   Декларации на компонентите на класа

};

Пред всяко от имената на базовите класове може да се постави една от ключовите думи public, private 

или  protected.  Достъпът  до  наследените  компоненти   се  подчинява   на  правилата,   разгледани  в  т.8.2  на 
настоящата глава.
В примерната програма 8.6 се използва производен клас, който има два базови класа.

Програма 8.7.

 Производен клас с два базови класа. 

#include <iostream.h>

#include <string.h>
//Декларация на клас Kola

class Kola {
   private:

    char Marka[21];    //Марка на автомобила
    int GodPro;    

//Година на производство на автомобила

  public:
    void getDataKola();    //Член-функция

    void displayKola();    //Член-функция
};

//Дефиниция на член-функцията getDataKola на класа Kola
void Kola::getDataKola()

{
 cout<<"Mарка на автомобила: "; cin.getline(Marka,20);

 cout<<"Година на производство на автомобила: "; cin>>GodPro;
 cin.sync();

}
//Дефиниция на член-функция displayKola на класа Kola

void Kola::displayKola()
{

  cout<<"Mapкa на автомобила: "<<Marka<<endl;

8


Това е само предварителен преглед!

Производни класове, наследяване и полиморфизъм

Възможността да се създават производни класове осигурява следните нови възмжности при създаването на програми...

Производни класове, наследяване и полиморфизъм

Предмет: Програмиране, Информатика, ИТ
Тип: Лекции
Брой страници: 26
Брой думи: 7322
Брой символи: 40736
Изтегли
Този сайт използва бисквитки, за да функционира коректно
Ние и нашите доставчици на услуги използваме бисквитки (cookies)
Прочети още Съгласен съм