8.2. Necesitatea func?iilor virtuale

8.2. Necesitatea func?iilor virtuale

?nainte de a discuta no?iunea de func?ie membr? virtual? a clasei, vom ?ncerca s? demonstr?m prin exemple necesitatea func?iilor virtuale.

Presupunem c? o problem? oarecare cere prelucrarea vectorilor (sau listelor) cu elementele ce reprezint? diferite numere ra?ionale, cum ar fi frac?ii ra?ionale pozitive, ra?ionale f?r? semn, ra?ionale arbitrare.

Cre?m clasa vector pentru acumularea numerelor ra?ionale. Ca demonstrarea noastr? s? fie mai conving?toare, presupunem c? clasa vector se creeaz? imediat dup? crearea clasei fractie_rationala, adic? atunci c?nd clasele rational_fara_semn ?i rational nu erau cunoscute. De aceea, ad?ug?m la sf?r?itul fi?ierului ”fr.cpp” urm?torul cod de declarare a clasei vector:

class vector
{
 protected:
  fractie_rationala *pel; // Pointer la vector de elemente
  int n; // num?rul de elemente ?n vector
 
 public:
  vector(int ninit) // num?rul de elemente se indic? la
                    // crearea vectorului
  {
     n = ninit;
     pel = new fractie_rationala[n]; // alocarea vectorului
  }
 
  ~vector()
  {
     delete []pel; // destructorul elibereaz? memoria
                   // ocupat? de vector
  }
 
  fractie_rationala &operator[](int i) // accesul la elemente
  {
     return *(pel+i);
  }
 
  ostream &print(ostream &stream = cout) // afi?area vectorului
  {
     for(int i=0; i<n; i++) (pel+i)->print(stream) << endl;
     return stream;
  }
 
  void sort() // sorteaz? elementele vectorului ?n ordinea
  {           // cresc?toare
     for(int i=1; i<n; i++)
       for(int j=i; (j>0) && (*(pel+j)<*(pel+j-1)); j--)
       {  // aici schimb?m cu locurile dou? elemente
          fractie_rationala t = *(pel+j);
          *(pel+j) = *(pel+j-1);
          *(pel+j-1) = t;
       }
  }
};

Clasa vector are dou? c?mpuri. C?mpul pel este un pointer la primul element al vectorului. Memoria pentru vector se aloc? dinamic la crearea obiectului, totodat? ob?ine valoare ?i pointerul pel. ?n c?mpul n se va p?stra num?rul de elemente ale vectorului. Num?rul de elemente se indic? ca argumentul constructorului la crearea obiectului.

La eliminarea obiectului vector ar fi de dorit s? eliber?m memoria alocat? pentru elementele acestui vector, ceea ce ?i efectueaz? destructorul definit ?n clasa vector.

Clasa vector asigur? accesul la elementele vectorului prin supra?nc?rcarea operatorului binar de indexare [ ]. Re?ine?i c? operatorul [ ] ?ntoarce ca rezultat o referin?? la elementul cu indicele dat i. Aceasta ne d? posibilitatea de a accesa elementele vectorului at?t pentru aflarea valorilor lor, c?t ?i pentru atribuirea valorilor noi.

De exemplu, dac? v este o instan?? a clasei vector, iar f1 ?i f2 sunt dou? frac?ii ra?ionale, atunci f1=v[0] atribuie instan?ei f1 valoarea primului element al vectorului, iar v[1]=f2 atribuie elementului cu indicele 1 al vectorului valoarea f2.

Func?ia membr? print() afi?eaz? elementele vectorului unul dup? altul ?n coloan?. Pentru afi?area elementelor, ?n bucla for se folose?te func?ia print() din clasa fractie_rationala.

Ultima func?ie membr? sort() sorteaz? prin inser?ie simpl? elementele vectorului ?n ordinea cresc?toare. Trebuie de subliniat c? compararea elementelor este posibil? datorit? operatorului de conversie (double). Memoriza?i cum se acceseaz? elementele vectorului ?n func?ia membr? a clasei vector. Mai sunt ?i alte posibilit??i.

?n continuare mai sunt prezentate dou? versiuni ale func?iei sort(). Una dintre ele folose?te pointerul ?ncorporat this ?i operatorul [ ] supra?nc?rcat ?n clasa vector. Alta folose?te operatorul standard [ ] pentru tipul fractie_rationala. Ve?i ?ncerca pe r?nd toate trei func?ii sort() ?i v? ve?i convinge c? rezultatele vor fi identice.

Pute?i introduce ?n declararea clasei vector ?n fi?ierul ”fr.cpp” toate cele trei versiuni ale func?iei sort(), ?ns? de fiecare dat? c?nd lansa?i programul dou? dintre ele trebuie s? fie comentate cu /* ?i */.

Versiunea func?iei sort() cu pointerul this arat? astfel:

void sort()
   {
     for(int i=1; i<n; i++)             
       for(int j=i; (j>0) && ((*this)[j]<(*this)[j-1]); j--)
         {  // aici schimb?m cu locurile dou? elemente
            fractie_rationala t = (*this)[j];
            (*this)[j] = (*this)[j-1];
            (*this)[j-1] = t;
         }
   }
//Versiunea func?iei sort() cu operatorul standard [] arat? astfel:
void sort()
   {
     for(int i=1; i<n; i++)             
       for(int j=i; (j>0) && (pel[j]<pel[j-1]); j--)
       {  // aici schimb?m cu locurile dou? elemente
          fractie_rationala t = pel[j];
          pel[j] = pel[j-1];
          pel[j-1] = t;
       }
    }

Aducem un exemplu care demonstreaz? cum lucreaz? clasa vector. Revenim la fi?ierul ”rfs.cpp” ?i ad?ug?m ?n el la func?ia main() urm?torul cod:

vector vr(6);
   vr[0]=fractie_rationala(2, 3);
   vr[1]=rational_fara_semn(1, 3, 5);
   vr[2]=rational(-2, 1, 3);
   vr[3]=rational_fara_semn(1, 1, 4);
   vr[4]=fractie_rationala(4, 5);
   vr[5]=rational(-1, 2, 3);
 
   cout << "Vectorul vr initial:" << endl;
   vr.print();
 
   vr.sort();
   cout << "Vectorul vr dupa sortarea:" << endl;
   vr.print();

Codul ad?ugat nu este dificil a fi ?n?eles. Am creat vectorul vr din 6 elemente – obiecte fractie_rationala. Aici trebuie s? ?n?elegem c? ini?ial toate elementele, fiind trecute prin constructorul fractie_rationala cu argumentele implicite, vor avea valoarea 0/1. Apoi, fiec?rui element aparte i se atribuie o valoare personal?.

?n unele cazuri, va fi aplicat? regula de atribuire de la clasa derivat? la clasa de baz?. ?n cazul c?nd se atribuie de la clasa rational_fara_semn, partea ?ntreag? se pierde. ?n cazul c?nd se atribuie de la clasa rational, se pierde ?i semnul. Se afi?eaz? vectorul dup? atribuiri, apoi s? sorteaz? ?i se afi?eaz? vectorul sortat. Rezultatul acestui fragment va fi urm?torul:

Vectorul vr initial:
2/3
3/5
1/3
1/4
4/5
2/3

Vectorul vr dupa sortarea:
1/4
1/3
3/5
2/3
2/3
4/5

?ntr-adev?r, vectorul vr este compus din obiecte fractie_rationala ?i nu are nimic comun cu clasele rational_fara_semn ?i rational. S-a ob?inut c? obiectele claselor derivate s-au pierdut dup? atribuire. Aici nu putem face nimic. Totul va r?m?ne a?a cum este.

Pentru a atinge scopul nostru – de a lucra simultan cu diferite clase, s? alegem alt? cale. Vom declara o alt? clas? de vectori, pe care o vom numi vectorp. Fiecare element al acestor vectori va fi nu un obiect fractie_rationala, ci un pointer spre obiect al clasei fractie_rationala. Noua clas? iar??i va fi ad?ugat? la sf?r?itul fi?ierului ”fr.cpp”. Fi?i aten?i, s? nu confunda?i fi?ierele.

class vectorp
{
 protected:
   fractie_rationala **pel; //Aten?ie! Pointer spre vectorul de pointeri
   int n; // num?rul de pointeri ?n vector
 
 public:
    vectorp(int ninit)
    {
       n = ninit;
       pel = new fractie_rationala* [n]; // aloc?m memoria pentru
                                         // vector de n pointeri
    }
 
    ~vectorp()
    {
      for(int i=0; i<n; i++) // mai ?nt?i elimin?m obiectele indicate de
         delete pel[i];      // c?tre pointeri din vectorul de pointeri
      delete []pel;          // apoi elimin?m ?nsu?i vectorul de pointeri
    }
 
    fractie_rationala* &operator[](int i)
    {
       return *(pel+i); // acum returneaz? referin?a la un
                        // pointer din vector
    }
 
    ostream &print(ostream &stream = cout)
    { // vom afi?a obiectele indicate de c?tre pointeri din vector
       for(int i=0; i<n; i++)                
          (*(pel+i))->print(stream) << endl;
       return stream;
    }
 
    void sort()
    {
       for(int i=1; i<n; i++) 
         for(int j=i; (j>0) &&(**(pel+j)<**(pel+j-1)); j--)
          {  // aici schimb?m cu locurile valorile la doi
             // pointeri din vector
             fractie_rationala *pt = *(pel+j);
             *(pel+j) = *(pel+j-1);
             *(pel+j-1) = pt;
          }
    }
};

Declara?ia clasei vectorp este comentat? destul de detaliat. Ad?ug?m doar c?teva observa?ii. ?n aceast? clas? c?mpul pel reprezint? pointer la un vector, fiecare element al c?ruia este un pointer spre o instan?? a clasei fractie_rationala. Vom considera c? instan?ele la care vor indica pointerii din vector vor fi alocate dinamic ?i ele, ?i, ?n cazul lichid?rii obiectului vectorp, trebuie s? fie eliminate.

De aceea, ?n destructorul clasei vectorp, ?n compara?ie cu destructorul clasei vector, a ap?rut o bucl? for ?n care se elibereaz? memoriile ocupate de c?tre instan?ele indicate de c?tre pointerii din vector. Acum operatorul de indexare [ ] ?ntoarce referin?e la pointeri - elementele vectorului, pe c?nd func?ia print() afi?eaz? instan?ele indicate de c?tre ace?ti pointeri.

Algoritmul func?iei sort() a r?mas acela?i, numai c? ?n cazul schimbului se modific? adresele ce se con?in ?n pointeri. Instan?ele indicate de c?tre pointeri r?m?n pe loc. Dup? sortare, primul pointer din vector va indica spre instan?a cu cea mai mic? valoare, iar ultimul pointer din vector va indica instan?a cu cea mai mare valoare.

Pentru verificarea clasei vectorp, ad?ug?m la func?ia main() ?n fi?ierul ”rfs.cpp” urm?torul cod:

   vectorp vrp(6);
   vrp[0] = new fractie_rationala(2, 3);
   vrp[1] = new rational_fara_semn(1, 3, 5);
   vrp[2] = new rational(-2, 1, 3);
   vrp[3] = new rational_fara_semn(1, 1, 4);
   vrp[4] = new fractie_rationala(4, 5);
   vrp[5] = new rational(-1, 2, 3);
 
   cout << "Vectorul vrp initial:" << endl;
   vrp.print();
 
   vrp.sort();
   cout << "Vectorul vrp dupa sortarea:" << endl;
   vrp.print();

Aici momentul cel mai important este crearea dinamic? cu operatorul new a obiectelor de diferite clase. ?n toate cazurile va lucra operatorul new supra?nc?rcat ?n clasa fractie_rationala, fiindc? el nu a mai fost suprascris ?n clasele rational_fara_semn ?i rational. Aminti?i-v? c? acest operator, pe l?ng? repartizarea memoriei, mai afi?eaz? ?i un mesaj de control.

Dar nu aceasta este important. La urma urmei, pute?i ?terge acest mesaj sau chiar ?i supra?nc?rcarea operatorului new din clasa fractie_rationala (?n ultimul caz va trebui s? ?terge?i ?i supra?nc?rcarea operatorului delete din clasa fractie_rationala). Important este faptul c? diferite obiecte create cu new nu s-au pierdut ?i adresele lor s-au memorizat ?n vectorul de pointeri vrp. Totu?i, rezultatul afi?at de c?tre ultimul fragment v? va descuraja la prima vedere:

Sunt in new pentru fr!
Sunt in new pentru fr!
Sunt in new pentru fr!
Sunt in new pentru fr!
Sunt in new pentru fr!
Sunt in new pentru fr!
Vectorul vrp initial:
2/3
3/5
1/3
1/4
4/5
2/3

Vectorul vrp dupa sortarea:
1/4
1/3
3/5
2/3
2/3
4/5
Sunt in delete pentru fr!
Sunt in delete pentru fr!
Sunt in delete pentru fr!
Sunt in delete pentru fr!
Sunt in delete pentru fr!
Sunt in delete pentru fr!

El nu difer? deloc de cel precedent. Afi??rile de control de tipul ”Sunt in …” nu le lu?m ?n considera?ie.

De ce totu?i s-a primit a?a? Foarte simplu, ?n func?ia print(), membr? a clasei vectorp, ?n bucla for se apeleaz? func?ia print(), membr? a clasei fractie_rationala. Aceast? din urm? func?ie print() „nu ?n?elege” c? pointerul vrp[1] indic? spre obiectul cu valoarea 1,3/5, ea „nici nu are grij? de aceasta”, fiindc? a fost creat? c?nd clasele derivate nici nu existau.

Ea „consider?” c? are un obiect fractie_rationala ?i afi?eaz? 3/5, la fel ?i pentru celelalte obiecte derivate. Nu putem „reeduca” func?ia print() membr? a clasei fractie_rationala, ea cum „nu a ?tiut” nimic despre clasele derivate la momentul c?nd a fost creat?, a?a ?i nu va ?ti despre ele. ?ns?, putem proceda ca ?n cazul ?n care pointerul vrp[i] indic? spre o instan?? fractie_rationala s? fie apelat? func?ia print() din aceast? clas?.

?n cazul c?nd pointerul vrp[i] indic? spre o instan?? rational_fara_semn s? fie apelat? func?ia print() din aceast? clas?, iar ?n cazul c?nd pointerul vrp[i] indic? spre o instan?? rational s? fie apelat? func?ia print() membr? a clasei rational. Aceasta se face cu ajutorul func?iilor membre virtuale.
_________________________
Autorul: dr.conf. S. Pereteatcu

 

рассказать друзьям и получить подарок

Оставить комментарий

Ваш email не будет опубликован. Обязательные поля отмечены *

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Translate Переводчик

Подписка на новости

SmartResponder.ru
Ваш e-mail: *
Ваше имя: *

Хостинг для Wordpress сайтов