laborator 2 - incapsularea · 2017. 12. 25. · laborator 6 – redefinirea ... trebuie declarate...
TRANSCRIPT
Programare Orientată pe Obiecte
- 1 -
Laborator 6 – Redefinirea operatorilor
Tema 6.1
Analizaţi programul din fişierele EX6.CPP, EX6.H, CLASE.CPP, CLASE.H
Consideraţii teoretice 6.1
Pentru inserarea unui element într-o poziţie oarecare într-un vector completat parţial există
următoarele posibilităţi:
0 1 2 3 4 5 6 7 8
A B C D E F
A B C nou D E
G
F G
V
V
Mutare element cu element. Pentru exemplul din figură avem 7 poziţii completate şi dorim să
inserăm în poziţia de indice 3. Codul ar putea fi:
for(k=6;k>=3;k--)
v[k+1]=v[k];
v[3]=nou;
Pentru a evita suprascrierea accidentală este esenţială copierea de la dreapta la stânga.
Utilizarea funcţiei de bibliotecă memmove. Aceasta are următorul prototip:
void *memmove(void *dest, const void *src, size_t n);
Funcţia copiază n octeţi de la adresa src la adresa dest. Rezultatul este corect chiar dacă src
şi dest se suprapun. Pentru exemplul prezentat anterior copierea s-ar face cu apelul:
memmove(&v[4],&v[3],4*sizeof(v[0]));
v[3]=nou;
Indicaţii 6.1
Atenţie la clasa nou apărută MULTIME şi metodele acesteia.
Atenţie la ordinea figurilor de pe ecran.
Tema 6.2
Să se definească printr-o metoda şi să se utilizeze pentru clasa MULTIME operatorul unar “++” cu
funcţionalitate trecerea la următorul element din mulţime.
Consideraţii teoretice 6.2
Funcţii friend
Necesitatea mecanismului de “friend” provine de la imposibilitatea unei metode de a fi membră
în mai multe clase. S-a impus existenţa unei soluţii ca o funcţie ne-membră să poată accesa
membrii ne-publici ai unei clase. Acest lucru se realizează prin prefixarea declaraţiei cu cuvântul
cheie “friend”.
Programare Orientată pe Obiecte
- 2 -
Chiar dacă sunt declarate în cadrul clasei, ele nu sunt metode, deci nu li se transmite pointerul
ascuns this. Esenţial este că ele au acces la membrii ne-publici ai clasei cu care sunt prietene.
Evident, funcţiile friend încalcă (controlat) principiul încapsulării. Nefiind membre ale clasei,
accesibilitatea lor nu este afectată de secţiunea public, protected sau private în care sunt declarate
(fapt aparent surprinzător). Încălcarea controlată a încapsulării se referă la faptul că funcţiile friend
trebuie declarate în cadrul clasei, deci de către proiectantul clasei. Clasa îşi stabileşte funcţiile care
îi sunt prietene şi nu funcţiile clasele cu care să fie prietene, adică nu putem fi prieteni cu o clasă
decât dacă proiectantul acesteia consimte acest lucru.
În exemplul următor prezentăm modul de declarare a unei funcţii Multiply de înmulţire a unui
vector cu o matrice, funcţie declarată ca şi funcţie friend:
class CMatrice;
class CVector
{
int v[10];
friend CVector Multiply(CMatrice &m,CVector& v);
// . . . . . .
};
class CMatrice
{
int m[10][10];
friend CVector Multiply(CMatrice &m,CVector& v);
// . . . . . .
};
CVector Multiply(CMatrice &m,CVector& v)
{
CVector rez;
for(int i=0;i<10;i++)
{
rez.v[i]=0; // membrul v privat
for(int j=0;j<10;j++)
rez.v[i]+=m.m[i][j]*v.v[j]; // membrii v si m privati
}
return rez;
}
În lipsa mecanismului funcţiilor friend, înmulţirea ar fi putut fi făcută doar prin:
punerea variabilelor membru ale matricii şi vectorului publice, ceea ce ar fi impus renunţarea
completă la avantajele încapsulării (inacceptabil).
dotarea claselor cu metode de acces la membri (citire şi scriere a lor) fapt care ar fi compromis
eleganţa şi performanţele de viteză (orice acces la o variabilă însemnând apeluri suplimentare
de metode).
Cum ambele soluţii prezintă dezavantaje majore mecanismul funcţiilor friend este justificat.
Remarcăm şi faptul că funcţiile friend au acces la membrii ne-publici ai tuturor obiectelor de tipul
respectiv, nu doar la variabilele primite parametru (cum, eronat, s-ar putea trage concluzia din
exemplul anterior). De cele mai multe ori însă, se folosesc parametri pentru a suplini lipsa lui this.
Redefinirea operatorilor - generalități
Pentru tipurile predefinite semnificaţia operatorilor este binecunoscută, de exemplu este evident ce
înseamnă + pentru întregi. O caracteristică importantă a programării obiectuale este aceea că
Programare Orientată pe Obiecte
- 3 -
permite programatorului să îşi definească propriile clase, în fapt propriile tipuri de date. În sensul
comportării cât mai naturale a noilor tipuri introduse, devine evidentă utilitatea definirii şi pentru
aceste noi tipuri a unor operatori.
Termenul original este acela de “Operators Overloading”, adică supraîncărcarea operatorilor,
pentru a indica dreptul programatorului de a atribui sensuri noi operatorilor existenţi. Din această
cauză se foloseşte de multe ori termenul de redefinire a operatorilor pentru a indica acest fapt.
În limbajul C++ nu există, de exemplu, un tip număr complex, cu toate că, în matematică, acest tip
este bine definit. Putem declara o clasă complex pentru care să avem definită adunarea astfel:
class complex
{
double real, imag;
// . . . . .
complex Adunare(complex &arg); // prima varianta
complex operator+(complex &arg); // a doua varianta
};
în care ambele metode adună separat partea reală şi cea imaginară. cu posibilele utilizări:
complex a,b,c;
c = a.Adunare(b); // foarte incomod
c = a.operator+(b); // ceva mai clar, dar tot incomod
c = a+b; // in sfirsit natural
În exemplul anterior remarcăm forma incomodă cu apel explicit de metodă (mai ales în cazul unor
expresii complicate, cu adunări multiple) şi ultima formă, în sfârşit naturală (ultimele două forme de
scriere sunt echivalente). A doua formă indică faptul că avem de a face de fapt cu funcţii (metode)
cu nume de operator ("operator" este cuvânt cheie al limbajului).
Un exemplu mai complet este prezentat în continuare:
#include <stdio.h>
class complex
{
public:
double real;
double imag;
complex(double re,double im){real=re;imag=im;}
complex(){real=0;imag=0;};
complex operator+(complex &arg)
{
complex rez;
rez.real=real+arg.real; // rez.real=this->real+arg.real
rez.imag=imag+arg.imag; // rez.imag=this->imag+arg.imag
return rez;
}
};
void main()
{
complex c1(1,2),c2(5,7),c3;
printf("\nc1 ( %lf , %lf )",c1.real,c1.imag);
printf("\nc2 ( %lf , %lf )",c2.real,c2.imag);
Programare Orientată pe Obiecte
- 4 -
c3 = c1 + c2; // asta ne interesa !!!
printf("\nc3 ( %lf , %lf )",c3.real,c3.imag);
}
care produce următorul rezultat:
c1 ( 1.000000 , 2.000000 )
c2 ( 5.000000 , 7.000000 )
c3 ( 6.000000 , 9.000000 )
În mod evident adunarea este acum mult mai naturală astfel încât programul poate fi înţeles mult
mai uşor şi ne putem concentra asupra prelucrării propriu-zise. Pentru o implementare reală, utilă
clasa complex trebuie completată cu o mulţime de alţi operatori.
Redefinirea operatorilor prin metode sau prin funcţii friend - echivalări
Deşi nu a fost amintit anterior, redefinirea operatorilor se poate face atât prin metode cât şi prin
funcţii friend. Apelul funcţiilor şi metodelor operator se face de către compilator conform
următoarelor echivalări:
aritate sintaxa apel pentru operator
implementat prin metodă
apel pentru operator
implementat prin funcţie friend binar xy x.operator(y) operator(x,y)
unar x x.operator() operator(x)
în care am notat generic prin orice operator binar (cu doi operanzi) şi prin orice operator
unar (cu un singur operand).
Deoarece redefinirea operatorilor are de fapt la bază funcţii (metode) cu nume de operator putem
beneficia de supraîncărcarea numelui funcţiilor, adică putem defini diferit acelaşi operator
pentru argumente (operanzi) de tipuri diferite (putem, pentru o clasă, defini adunarea într-un fel
pentru al doilea operand întreg şi în alt fel pentru al doilea operand de tip long). Această posibilitate
este valabilă, evident, doar pentru operatorii binari, unde avem libertatea alegerii tipului unuia din
operanzi, un operand fiind impus a fi de tipul clasei pentru care redefinim operatorul.
Cu toate că funcţiile operator îmbunătăţesc versatilitatea programelor, trebuie respectate anumite
restricţii:
nu se pot defini operatori noi (de aceea se şi numeşte redefinirea operatorilor).
se pot redefini toţi operatorii existenţi în C++ cu excepţia următorilor:
. selecţie directa a membrilor
.* dereferenţiere pointeri la metode
:: “scope access” / rezoluţie
?: operatorul condiţional
nu se poate schimba aritatea şi prioritatea (precedenţa) operatorilor. Operatorii unari
trebuie să rămână unari, operatorii binari trebuie să rămână binari iar precedenţa operatorilor
nu poate fi schimbată, (adică, de exemplu, * rămâne prioritar lui +).
comportamentul operatorilor pentru tipurile predefinite nu poate fi schimbat, adică un
operand al operatorului redefinit trebuie să fie o clasă (cea în discuţie).
Programare Orientată pe Obiecte
- 5 -
redefinirea unor operatori nu redefineşte şi operatorii aparent combinaţi, adică
redefinirea lui + şi a lui = nu redefineşte şi +=. Dacă îl dorim şi pe cel combinat, trebuie să îl
redefinim explicit.
operatorii =, [ ] şi -> trebuie să fie funcţii membru ne-statice.
Scopul acestor restricţii este acela de a asigura că:
compilatorul de C++ nu trebuie să îşi modifice regulile sintactice.
semnificaţia operatorilor predefiniţi nu poate fi schimbată.
Indicaţii 6.2
Warning-ul legat de forma postfixată sau prefixată se va neglija.
Tema 6.3
Să se definească printr-o metodă şi să se utilizeze pentru clasa MULTIME operatorul binar “+” care să
permită inserarea unei figuri în mulţime.
Indicaţii 6.3
Utilizaţi atât folosind forma a.operator+(b) cât şi forma a+b.
Tema 6.4
Să se modifice operatorul “+” definit anterior astfel încât să permită şi utilizarea cascadată (de
forma: m + cerc + patrat).
Consideraţii teoretice 6.4
Să considerăm o expresie de genul:
operand1 + operand2 + operand3
Avem în expresie doi operatori + care se evaluează (conform asociativităţii operatorului +) de la
stânga la dreapta. În momentul evaluării primului operator se apelează metoda respectivă. Ca şi
prim operand al celui de al doilea operator se consideră rezultatul întors de această metodă. Acesta
trebuie să aibă un asemenea tip încât să permită aplicarea următorului operator din expresie (fie el
operator standard sau redefinit).
Atragem atenţia că, în evaluarea expresiilor, se păstrează regulile privind aritatea, prioritatea şi
asociativitatea operatorilor.
Lucrurile se petrec asemănător în ceea ce priveşte rezultatul întors şi în cazul operatorilor redefiniţi
prin funcţii friend.
Pentru a evita mecanismul laborios (şi uneori periculos) de copiere a obiectelor întoarse prin valoare
de cele mai multe ori un operator redefinit întoarce rezultatele (obiectele rezultat) prin referinţă.
Indicaţii 6.4
Atenţie la ce se întâmplă dacă operatorul întoarce mulţimea prin valoare.
Pentru referirea obiectului curent (în scopul întoarcerii) ar putea fi folosit pointerul this.
Programare Orientată pe Obiecte
- 6 -
Tema 6.5
Să se verifice funcţionarea unui apel de genul: m = m + new CERC şi să se justifice.
Consideraţii teoretice 6.5
Un operator de o importanţă deosebită este operatorul = (de atribuire). El trebuie să primească
parametru o referinţă la un obiect de acelaşi tip pe care îl copiază în obiectul curent. Prezintă
următoarele particularităţi:
dacă nu este definit de către programator compilatorul generează implicit un operator =
care copiază membru cu membru (“bit cu bit”) conţinutul obiectelor.
este folosit în cazul atribuirii de obiecte de acel tip (inclusiv a obiectelor întoarse de către
funcţii/metode).
Apare un pericol datorită copierii membru cu membru a unor membrii a căror semnificaţie nu este
doar valoarea lor propriu zisă, ci ea reprezintă “identificatorul” unei resurse (de exemplu zonă de
memorie alocată, fişier deschis, etc.). Pentru a nu pierde / duplica asemenea resurse se impune
redefinirea operatorului = în cazul în care obiectul are în componenţă asemenea membri. Operatorul
= astfel redefinit trebuie să elibereze resursa veche şi să duplice resursa nouă (de obicei prin
alocarea uneia noi şi copierea caracteristicilor acesteia).
De asemenea, dacă nu este definit de către programator un constructor de copiere, compilatorul
generează implicit un constructor de copiere care copiază membru cu membru (“bit cu bit”)
conţinutul obiectelor.
Pericol asemănător (datorate copierii membru cu membru) apare şi la constructorul de copiere
soluţia fiind identică (doar că “resursa” veche nu este încă alocată deci nu trebuie întâi eliberată –
fiind constructor abia acum se alocă prima dată).
Atragem atenţia asupra diferenţei între constructorul de copiere (utilizat când = apare chiar la
definirea variabilei „destinaţie”) şi operatorul de atribuire (când = apare ulterior definirii
variabilei „destinaţie”).
Indicaţii 6.5
Dacă se copiază un obiect în el însuşi pericolul amintit anterior nu apare.
Tema 6.6
Să se introducă operatorul “+=” care să permită adăugarea unei figuri în mulţime folosind pentru
implementare o funcţie friend.
Consideraţii teoretice 6.6
Vezi consideraţiile teoretice 6.2.
Indicaţii 6.6
Atenţie că ordine parametrilor funcţiei friend este importantă.
Tema 6.7
Să se introducă şi să se utilizeze operatorul “--” care să elimine elementul curent din mulţime.
Implementarea se va face prin funcţie friend.
Programare Orientată pe Obiecte
- 7 -
Consideraţii teoretice 6.7
O problemă apărută în primele versiuni de C++ era imposibilitatea distingerii între formele
prefixate şi postfixate ale operatorilor unari. Problema s-a rezolvat în sensul că, prin convenţie:
varianta prefixată se redefineşte “normal” (majoritatea operatorilor unari au doar forma
prefixată).
varianta postfixată se redefineşte având suplimentar un parametru de tip întreg.
Parametrul suplimentar din forma postfixată nu este efectiv folosit, scopul său este doar de a asigura
o diferenţă de semnătură.
Considerând o clasă CX redefinirea operatorului ++ în formă prefixată şi în formă postfixată se fac
conform exemplului următor:
void CX::operator++(); //forma prefixata, metoda
void CX::operator++(int); //forma postfixata, metoda
friend void operator++(CX&); //forma prefixata, functie friend
friend void operator++(CX&,int); //forma postfixata, functie friend
Evident o anumită forma (prefixată / postfixată) nu se poate redefini prin ambele variante (şi prin
metodă şi prin funcţie friend) ci doar prin una dintre ele.
Indicaţii 6.7
Rezolvaţi şi eventuale warning-uri care apar.
Tema 6.8
Să se execute pas cu pas secvenţele de cod care se referă la operatori.
Consideraţii teoretice 6.8
A se revedea consideraţiile teoretice 3.7.
Programare Orientată pe Obiecte
- 8 -
Anexa 6
ex6.h
#define TAB 9
#define ESC 27
#define LEFT 75
#define RIGHT 77
#define UP 72
#define DOWN 80
#define UNU ’1’
#define DOI ’2’
#define TREI ’3’
#define PATRU ’4’
#define CINCI ’5’
#define SASE ’6’
// prototipuri de functii
void OurInitGraph(void);
ex6.cpp
#include <graphics.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include "ex6.h"
#include "clase.h"
MULTIME m; // atentie la clasa MULTIME !!!!!!
void main()
//*********
{
int gata=0;
OurInitGraph();
m.Insert( new CERC );
m.Insert( new PATRAT(500,300,75,RED,"red") );
m.Insert( new CERC(100,100,25,BLUE,"blue") );
m.Insert( new PATRAT );
m.Insert( new PATRAT(400,200,100,YELLOW,"yellow") );
m.Insert( new CERC );
m.Afiseaza();
while(!gata)
switch(getch())
{
case ESC:
gata=1;
break;
case TAB:
Programare Orientată pe Obiecte
- 9 -
m.Urmatorul();
break;
case UNU: m.Get()->Creste( +10 ); m.Afiseaza(); break;
case DOI: m.Get()->Creste( -10 ); m.Afiseaza(); break;
case TREI: if(m.NrElem()<MAX_FIGURI){
if(m.NrElem()%2)
m.Insert(new CERC);
else
m.Insert(new PATRAT);
m.Afiseaza();
} break;
case PATRU: m.Elimina(); m.Afiseaza(); break;
case 0:
switch(getch())
{
case LEFT: m.Get()->Muta( -10, 0 ); break;
case RIGHT: m.Get()->Muta( 10, 0 ); break;
case UP: m.Get()->Muta( 0,-10 ); break;
case DOWN: m.Get()->Muta( 0, 10 ); break;
}
m.Afiseaza();
}
closegraph();
}
void OurInitGraph()
//*****************
{
int gdriver = DETECT, gmode, errorcode;
initgraph(&gdriver,&gmode,""); /* initialize graphics and local variables */
errorcode = graphresult(); /* read result of initialization */
if (errorcode != grOk) /* an error occurred */
{
printf("Graphics error: %s\n", grapherrormsg(errorcode));
printf("Press any key to halt:");
getch();
exit(1); /* terminate with an error code */
}
settextjustify(CENTER_TEXT,CENTER_TEXT);
}
clase.h
class POZITIE
{
public:
POZITIE ();
POZITIE (int x0,int y0);
void Muta(int dx,int dy);
protected:
int x;
int y;
};
class FIGURA : public POZITIE
{
protected:
int r;
Programare Orientată pe Obiecte
- 10 -
int c;
char *Nume;
public:
FIGURA();
FIGURA(int x0,int y0,int r0,int c0,char *n0);
~FIGURA();
void Muta(int dx,int dy);
void Creste(int dr);
virtual void Afiseaza()=0;
virtual void Sterge()=0;
};
class CERC : public FIGURA
{
public:
CERC();
CERC(int x0,int y0,int r0,int c0,char *n0);
void Afiseaza();
void Sterge();
};
class PATRAT : public FIGURA
{
public:
PATRAT();
PATRAT(int x0,int y0,int r0,int c0,char *n0);
void Afiseaza();
void Sterge();
};
#define MAX_FIGURI 20
class MULTIME //retine multimea de figuri
{
int NrFiguri;
int FiguraCurenta;
FIGURA *pe[MAX_FIGURI]; //adresele figurilor
public:
MULTIME();
void Afiseaza();
void Urmatorul();
int Insert(FIGURA* f);
void Elimina();
FIGURA* Get();
int NrElem();
};
clase.cpp
#include <graphics.h>
#include <string.h>
#include <alloc.h>
#include "clase.h"
//~~~~~~~~~~~~~~~~~~
// clasa POZITIE
//~~~~~~~~~~~~~~~~~~
POZITIE:: POZITIE ()
//******************
Programare Orientată pe Obiecte
- 11 -
{
x = 320;
y = 240;
}
POZITIE:: POZITIE (int x0,int y0)
//*******************************
{
x = x0;
y = y0;
}
void POZITIE::Muta(int dx,int dy)
//********************************
{
x += dx;
y += dy;
}
//~~~~~~~~~~~~~~~~
// clasa FIGURA
//~~~~~~~~~~~~~~~~
FIGURA::FIGURA():POZITIE ()
//*************************
{
r = 50;
c = WHITE;
Nume = (char*)malloc(strlen("FIGURA")+1);
strcpy(Nume,"FIGURA");
}
FIGURA::FIGURA(int x0,int y0,int r0,int c0, char *n0): POZITIE (x0,y0)
//*******************************************************************
{
r = r0;
c = c0;
Nume = (char*)malloc(strlen(n0)+1); //aloca memorie suplimentara
strcpy(Nume,n0);
}
FIGURA::~FIGURA()
//***************
{
free(Nume); //elibereaza memoria suplimenatra alocata
}
void FIGURA::Muta(int dx,int dy)
//******************************
{
Sterge(); //stergere
POZITIE::Muta(dx,dy); //mutare
Afiseaza(); //afisare in noua pozitie
}
void FIGURA::Creste(int dr)
//*************************
{
Sterge(); //stergere
r+=dr; //redimensionare
Afiseaza(); //afisare cu noua dimensiune
}
Programare Orientată pe Obiecte
- 12 -
//~~~~~~~~~~~~~~~
// clasa CERC
//~~~~~~~~~~~~~~~
CERC::CERC():FIGURA()
//*******************
{
}
CERC::CERC(int x0,int y0,int r0,int c0, char *n0):FIGURA(x0,y0,r0,c0,n0)
//**********************************************************************
{
}
void CERC::Sterge()
//*****************
{
setcolor(BLACK);
circle(x,y,r);
outtextxy(x,y,Nume);
}
void CERC::Afiseaza()
//*******************
{
setcolor(c);
circle(x,y,r);
outtextxy(x,y,Nume);
}
//~~~~~~~~~~~~~~~~
// clasa PATRAT
//~~~~~~~~~~~~~~~~
PATRAT::PATRAT():FIGURA()
//***********************
{
}
PATRAT::PATRAT(int x0,int y0,int r0,int c0, char *n0):FIGURA(x0,y0,r0,c0,n0)
//**************************************************************************
{
}
void PATRAT::Sterge()
//*******************
{
setcolor(BLACK);
rectangle(x-r,y-r,x+r,y+r);
outtextxy(x,y,Nume);
}
void PATRAT::Afiseaza()
//*********************
{
setcolor(c);
rectangle(x-r,y-r,x+r,y+r);
outtextxy(x,y,Nume);
}
//~~~~~~~~~~~~~~~~~
// clasa MULTIME
//~~~~~~~~~~~~~~~~~
Programare Orientată pe Obiecte
- 13 -
MULTIME::MULTIME()
//****************
{
NrFiguri=0;
FiguraCurenta=0; //initial multimea e vida
}
void MULTIME::Urmatorul()
//***********************
{
FiguraCurenta++;
FiguraCurenta%=NrFiguri; //trecere la urmatoarea figura din multime
}
void MULTIME::Afiseaza()
//**********************
{
int k;
for(k=0;k<NrFiguri;k++) //afiseaza toate figurile din multime
pe[k]->Afiseaza();
}
FIGURA* MULTIME::Get()
//********************
{
return(pe[FiguraCurenta]); //intoarce adresa figurii curente
}
int MULTIME::NrElem()
//*******************
{
return(NrFiguri); //intoarce numarul de figuri
}
int MULTIME::Insert(FIGURA* f) //insereaza o figura in pozitia curenta
//****************************
{
if(NrFiguri==MAX_FIGURI)
return(-1); //nu a fost inserat
memmove(&pe[FiguraCurenta]+1,&pe[FiguraCurenta],
(NrFiguri-FiguraCurenta)*sizeof(pe[0]) ); //facem loc
pe[FiguraCurenta]=f; //inserare figura noua
NrFiguri++;
return(FiguraCurenta);
}
void MULTIME::Elimina() //elimina figura din pozitia curenta
//*********************
{
if(NrFiguri==1)
return; //multimea sa nu devina vida
pe[FiguraCurenta]->Sterge(); //stergere de pe ecran
delete pe[FiguraCurenta]; //distrugere element
memmove(&pe[FiguraCurenta],&pe[FiguraCurenta]+1,
(NrFiguri-FiguraCurenta)*sizeof(pe[0]) ); //mutare in multime
NrFiguri--;
if(FiguraCurenta==NrFiguri)
FiguraCurenta--;
}