Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
AccessAdobe photoshopAlgoritmiAutocadBaze de dateCC sharp
CalculatoareCorel drawDot netExcelFox proFrontpageHardware
HtmlInternetJavaLinuxMatlabMs dosPascal
PhpPower pointRetele calculatoareSqlTutorialsWebdesignWindows
WordXml


Implementarea C++ a POO

c



+ Font mai mare | - Font mai mic



Implementarea C++ a POO

1 Definirea claselor



Pentru a putea lucra cu obiecte in C++ trebuie, in prealabil, sa definim forma lor generala folosind in acest scop cuvantul cheie class. O clasa este similara, sintactic vorbind, cu o structura. Forma generala a unei declaratii de clasa care nu mosteneste nici o alta clasa este urmatoarea:

class <Nume_clasa> [:<Lista_claselor_de_baza>]

[<Lista de obiecte>];

<Lista de obiecte> este optionala. Daca exista, ea declara obiecte din acea clasa.

<Specificator de acces> este unul din cuvintele cheie:

-public

-private

-protected.

Implicit, functiile si datele declarate intr-o clasa sunt proprii acelei clase, doar membrii sai avand acces la ele.

<Lista_claselor_de_baza> indica, optional, clasele de la care se pot mosteni atribute informationale si comportamentale.

A Pentru datele si functiile care fac parte din definitia unei clase se obisnuiesc si denumirile de variabile membre, respectiv, functii membre sau, pur si simplu, membri.

Folosind specificatorul de acces public, permitem functiilor sau datelor membre sa fie accesibile altor sectiuni ale programului nostru. Odata utilizat un specificator, efectul sau dureaza pana cand se intalneste alt specificator de acces sau se ajunge la sfarsitul declaratiei clasei. Pentru a reveni la modul privat de declarare a membrilor, se foloseste specificatorul de acces private. Specificatorul protected are implicatii asupra vizibilitatii membrilor unei clase in cazul in care se face si mostenire. Prezentam, in continuare, un exemplu de definitie de clasa, care incapsuleaza date si operatii minimale referitoare la un salariat al unei firme( nume, matricol, salariu).

#include <conio.h>

#include <string.h>

#include <iostream.h>

// Definitie clasa fara declarare de variabile obiect.

class Persoana

// Implementare functie setfields

void Persoana:: setfields(char *n, int m, float s)

// Implementare functie afis

void Persoana:: afis()

//Implementare functie principala

void main

In exemplul prezentat am folosit specificatorul de acces public pentru a permite functiei principale, care a declarat variabila obiect pers, accesul la membrii clasei definitoare a variabilei pers. In programarea obiect orientata adevarata in C++, efectul de ascundere a membrilor unei clase este intens utilizat, interzicand anumitor categorii de utilizatori accesul la membrii clasei, definind pentru comunicatia cu alte clase si categorii de utilizatori ceea ce se numeste interfata clasei.

Evident, se poate face o oarecare analogie intre clasa si structura; analogia cade, insa, in momentul in care se pune problema manevrabilitatii instantelor celor doua tipuri de concepte sau atunci cand este vorba de mostenire si polimorfism. Un exemplu ceva mai apropiat de realitatea preocuparilor unui programator C++ poate fi programul care implementeaza o stiva de intregi, prezentat in continuare.

#include <iostream.h>

#include <conio.h>

#define SIZE 100

// Definirea clasei stack

class stack

//Implementare functie init

void stack::init()

//Implementare functie push

void stack::push(int i)

st[top]=i;

top++;

//Implementare functie pop

int stack::pop()

top--;

return st[top];

//Functia principala

void main()

clrscr();

for (i=0;i<=9;i++)

getch();

2 Clase derivate. Constructori. Destructori. Obiecte C++

Modelarea obiect orientata a solutiei unei probleme de oarecare complexitate pune, inevitabil si problema transmiterii similaritatilor intre clase care formeaza o ierarhie sau o retea. Suportul C++ pentru rezolvarea acestei probleme il reprezinta posibilitatea ca o clasa sa fie derivata din una sau mai multe clase, ceea ce inseamna posibilitatea de a abstractiza solutia cu scopul de a reutiliza cod si de a adapta codul usor la cerinte noi daca este cazul. Am vazut mai sus sintaxa pentru mostenire. Sa adaugam, in plus la cele spuse mai sus, ca in <Lista_claselor_de_baza> clasele definitoare apar precedate, eventual, de un modificator de protectie si sunt separate intre ele prin virgula.

Modificatorii de protectie utilizati in <Lista_claselor_de_baza> definesc protectia in clasa derivata a elementelor mostenite. Prezentam sub forma tabelara accesul la elementele mostenite de clasa derivata in functie de protectia fiecarui element si de modificatorul de protectie asociat in <Lista_claselor_de_baza>.

Tipul de acces al elementului in clasa de baza

Modificatorul de protectie asociat clasei de baza la definirea clasei

Accesul in clasa derivata la element

private

private

interzis

protected

private

private

public

private

private

private

public

interzis

protected

public

protected

public

public

public

Tabelul 14. Problematica accesului la membrii unei clase derivate

Concluzionand, daca in clasa de baza accesul la element este private atunci in clasa derivata accesul este interzis. In schimb, clasa derivata are acces la elementele clasei de baza aflate sub incidenta accesului protected/public.

Se mai poate observa, totodata, ca daca la definirea clasei derivate se utilizeaza modificatorul de protectie private, atunci elementele protejate in clasa de baza prin protected sau public devin protejate private in clasa derivata; deci inaccesibile unor clase care s-ar deriva eventual din clasa derivata.

Exemplu

#include <iostream.h>

class CB

void dispij()

class CD:public CB

void dispk()

void main

Mostenirea protected a clasei de baza

Este posibil sa se mosteneasca o clasa de baza ca protected. Cand se procedeaza astfel, toti membrii public si protected ai clasei de baza devin membri protected ai clasei derivate.

Exemplu

#include <iostream.h>

class CB

void dispij()

class CD:protected CB

void disptot()

void main

Constructori si destructori

Este un fapt obisnuit necesitatea ca unele elemente ale unui obiect sa fie initializate. Pentru a degreva programatorul de o asemenea sarcina de rutina, compilatorul C++ genereaza cod care permite obiectelor sa se initializeze singure. Aceasta initializare automata este efectuata prin intermediul unei functii membru speciale a clasei definitoare numita constructor.

AConstructorul este o functie care are acelasi nume cu clasa.

Un constructor al unui obiect este apelat automat la crearea obiectului.

Un constructor al unui obiect este apelat o singura data pentru obiecte globale sau pentru cele locale de tip static.

Constructorul este o functie fara tip ceea ce nu reclama, totusi cuvantul cheie void in locul tipului.

Pentru obiecte locale constructorul este apelat de fiecare data cand este intalnita declararea acestuia.

Complementul constructorului este destructorul. De multe ori un obiect trebuie sa efectueze anumite actiuni cand este distrus.

Este evident faptul ca obiectele locale sunt distruse la parasirea blocului in care apar iar obiectele globale la terminarea programului.

Cand este distrus un obiect, este apelat destructorul clasei definitoare.

ADestructorul are acelasi nume cu constructorul, dar precedat de un caracter ~.

O clasa are un singur destructor si acest destructor nu poate avea parametri formali.

Atat constructorul cat si destructorul, in C++ nu pot sa returneze valori.

De semnalat faptul ca in situatia in care programatorul nu specifica un constructor explicit la definirea unei clase, la crearea unei instante a clasei se foloseste constructorul implicit atasat de compilator fiecarei clase.

Constructorul implicit nu are parametri formali, ceea ce are drept consecinta faptul ca nu este permisa initializarea la declarare a datelor membre ale obiectelor.

De asemenea, constructorul implicit nu este generat in cazul in care clasa are atasat un alt constructor fara parametri.

Programatorul poate inzestra clasa cu o proprie functie constructor. In acest scop trebuie tinut cont de faptul ca o metoda constructor are intotdeauna numele clasei din care face parte.

Programatorul poate inzestra clasa si cu parametri formali ceea ce permite si o formula eleganta de initializare a datelor membre ale obiectelor clasei respective.

A Foarte important mi se pare sa semnalez si existenta unui constructor special, numit constructor de copiere. Acesta are rolul de a atribui datele unui obiect altuia. Mai mult, poate fi apelat chiar la definirea obiectelor. Daca programatorul nu defineste propriul constructor de copiere, compilatorul adauga automat un astfel de constructor. In exemplul de mai jos se pot urmari elemente de sintaxa si semantica care trebuie cunoscute cand se lucreaza cu constructori in diferite ipostaze.

#include<stdio.h>

#include<conio.h>

//Definire clasa

class intreg

//Constructor de copiere

intreg (intreg& v)

void dispa()

;

void main

Evident, o clasa poate avea mai multi constructori, ceea ce este tot in beneficiul programatorilor.

Crearea obiectelor in C++

Obiectele pot fi create static sau dinamic.

Asa cum s-a vazut si in exemplul de mai sus sintaxa pentru varianta statica este:

<Clasa> <Obiect>[(<Lista de valori>)];

sau

<Clasa> <Obiect>[=<Valoare>];

Sintaxa ne arata ca odata cu crearea instantei se poate face si initializarea datelor membre ale obiectului cu ajutorul constructorilor parametrizati.

Pentru a intelege alocarea dinamica a memoriei pentru obiecte trebuie sa facem o scurta prezentare a problematicii alocarii dinamice structurate a memoriei in C++.

Operatori de alocare dinamica a memoriei in C++

In C, alocarea dinamica a memoriei este realizata cu ajutorul functiior malloc() si free().

Din motive de compatibilitate si nu numai, aceste functii sunt valabile si in C++. Totodata, C++ are un sistem alternativ de alocare dinamica bazat pe operatorii new si delete.

Sintaxa generala pentru new si delete este:

<Pointer>=new <Tip>;

delete <Pointer> ;

<Pointer> este o variabila pointer, compatibila ca tip cu <Tip>. Asadar, <Pointer> poate pastra adresa catre zona de memorie in care incap date avand tipul <Tip>.

De subliniat ca operatorul delete trebuie folosit doar cu un pointer valid, alocat deja prin utilizarea operatorului new. In caz contrar, rezultatele sunt imprevizibile.

Fata de malloc()si free(), operatorii new si delete prezinta cateva avantaje:

Y new aloca automat memorie suficienta pentru a pastra obiectele de tipul specificat. Nu mai este necesara folosirea operatorului sizeof.

Y new returneaza automat un pointer de tipul specificat. Nu mai este necesar sa folosim modelatorii de tip ca in cazul functiei malloc().

Y Atat new cat si delete pot fi supraincarcati, ceea ce va permite crearea unui sistem propriu de alocare dinamica a memoriei. Despre supraincarcare vom discuta in paragrafele 4 si 7 .

In plus, operatorul new mai are si alte capabilitati:

YPermite initializarea memoriei alocate unui tip de baza cu o valoare data, utilizand sintaxa:

<Pointer>=new <Tip> (<Valoare_initiala>);

ca in exemplul:

int *p;

p=new int(10);

YOperatorul new permite alocarea de memorie pentru matrici, utilizand sintaxa:

<Ponter>=new <Tip> [Marime];

Memoria alocata unei matrici de operatorul new se elibereaza de catre delete apelat cu sintaxa:

delete [ ] <Pointer>

A Matricile nu pot fi initializate in timpul alocarii

Suntem in masura sa prezentam solutia C++ pentru problema alocarii dinamice a memoriei pentru obiecte.

Obiectelor li se poate aloca memorie dinamic folosind operatorul new. Cand procedati astfel se creeaza un obiect si se returneaza un pointer catre el. Obiectul creat dinamic se comporta ca oricare altul. Cand este creat este apelata si functia constructor. Cand este eliberat se executa si functia destructor.

Sintaxa generala:

<Pointer_la_obiect>=new <Clasa>;

Daca <Clasa> are constructor parametrizat atunci se poate folosi sintaxa:

<Pointer_la_obiect>=new <Clasa>(<Lista_valori>);

pentru a realiza si initializarea obiectului.

In sfarsit, in cazul matricilor de obiecte, carora li se poate aloca memorie dinamic, trebuie sa ne asiguram ca in cazul in care exista functii constructor, una va fi fara parametri. In caz contrar, compilatorul da eroare de sintaxa.

Modalitatea concreta de utilizare a conceptului de constructor, precum si a conceptului complementar, destructorul, poate fi desprinsa si din exemplul de mai jos care reia modelarea obiect orientata a unei stive de intregi.

#include <iostream.h>

#include <conio.h>

#define SIZE 100

// Definirea clasei stack

class stack

//Implementare constructor

stack::stack()

stack::~stack()

//Implementare functie push

void stack::push(int i)

st[top]=i;

top++;

//Implementare functie pop

int stack::pop()

top--;

return st[top];

//Functia principala

void main

clrscr();

for (i=0;i<=9;i++)

getch();

3 Functii virtuale si polimorfism

C++ asigura suport pentru polimorfism atat in timpul compilarii cat si pe timpul executiei unui program. Polimorfismul in timpul compilarii este legat de posibilitatea de a supraincarca functiile si operatorii, problema pe care o vom discuta in paragrafele 4 si 7.

Polimorfismul in timpul executiei este obtinut combinand principiile mostenirii cu mecanismul functiilor virtuale.

Functii virtuale

O functie virtuala este o functie declarata virtual in clasa de baza si redefinita intr-un lant de derivare asociat respectivei clase de baza. O functie virtuala defineste o clasa generala de actiuni. O redefinire a ei introduce o metoda specifica. In esenta, o functie virtuala declarata in clasa de baza actioneaza ca un substitut pentru pastrarea elementelor care specifica o clasa generala de actiuni, stabilind elementele de interfata. Redefinirea unei functii virtuale intr-o clasa derivata ofera operatiile efective pe care le executa functia. Utilizate static, functiile virtuale se comporta ca oricare alta functie membru a clasei . Capabilitatile functiilor virtuale ies in evidenta atunci cand sunt apelate in context dinamic.

Exemplu

#include <iostream.h>

#include <conio.h>

//Clasa de baza care are doua functii membri publici

//o functie ordinara

//cealalta functie virtuala

class CB

virtual void g() //functie virtuala

//Clasa derivata din clasa de baza

class CD:public CB

virtual void g()

//Functie care permite utilizarea polimorfismului

//Parametrul functiei este un pointer la un obiect de tip CB

//De retinut ca un parametru de tip CD este compatibil

//cu tipul lui p

void ExecPolim(CB *p);

void main

void ExecPolim(CB *p)

La prima vedere redefinirea unei functii virtuale intr-o clasa derivata pare similara cu supraincarcarea unei functii. Nu este asa, deoarece exista mai multe diferente:

YPrototipul pentru o functie virtuala redefinita trebuie sa coincida cu prototipul specificat in clasa de baza.

YFunctiile virtuale nu pot sa fie membri de tip static ai clasei din care fac parte si nu pot fi nici friend

Functiile obisnuite suporta supraincarcarea; functiile virtuale suporta suprascrierea.

In cele ce urmeaza facem o serie de precizari relativ la clauza virtual.

Clauza virtual este mostenita

Cand o functie virtuala este mostenita se mosteneste si natura sa virtuala. Astfel ca, in situatia in care o clasa derivata a mostenit o functie virtuala si este folosita drept clasa de baza pentru o alta clasa derivata, functia virtuala poate fi in continuare suprascrisa. Altfel spus, o functie ramane virtuala indiferent de cate ori este mostenita.

Functiile virtuale sunt ierarhizate

Deoarece in C++ mostenirea este ierarhizata este normal ca functiile virtuale sa fie, de asemenea, ierarhizate. Acestea inseamna ca, atunci cand o clasa derivata nu suprascrie o functie virtuala, este utilizata prima redefinire gasita in ordinea inversa derivarii.

ASa mai spunem ca exista situatii in care programatorul vrea sa se asigure ca toate clasele derivate suprascriu o functie virtuala sau programatorul defineste o clasa de baza astfel incat sa nu permita definirea unei functii virtuale in aceasta clasa. Pentru aceste doua situatii exista functiile virtuale pure

Sintaxa unei functii virtuale pure este:

virtual <Tip> <Nume_functie> [(<Lista)_de_parametri>)]=0;

O clasa care contine cel putin o functie virtuala pura se numeste clasa abstracta. O clasa abstracta nu poate fi utilizata pentru crearea de obiecte dar poate fi utilizata pentru a crea pointeri si referinte spre astfel de clase permitand astfel manifestarile polimorfice in timpul executiei.

4 Functii supraincarcate

O modalitate C++ de realizare a polimorfismului o reprezinta supraincarcarea functiilor (overloading). In C++, doua sau mai multe functii pot sa aiba acelasi nume cat timp declaratiile lor de parametri sunt diferite. In aceasta situatie se spune ca functiile cu acelasi nume sunt supraincarcate iar procesul este numit supraincarcarea functiilor.

Cand supraincarcati o functie trebuie sa respectati urmatoarele cerinte:

YListele de parametri ale diferitelor versiuni ale unei functii trebuie sa difere;

YVersiunile pot diferi si din punct de vedere al tipului returnat; acest lucru nu este, insa, obligatoriu. Odata specificate versiunile unei functii respectand restrictiile de mai sus, compilatorul este in masura sa aleaga metoda adecvata unui apel specific.

Avantajele supraincarcarii, nu neaparat in context obiect orientat, pot fi desprinse si din analiza exemplului de cod C++ de mai jos.

#include <iostream.h>

#include <conio.h>

//functia abs este definita in trei moduri diferite

int abs(int i);

double abs(double d);

long abs(long l);

void main

int abs(int i)

double abs(double d)

long abs(long l)

Acest program implementeaza si utilizeaza trei functii cu acelasi nume (abs()) dar diferite prin proprietatile listelor de parametri formali. Fiecare returneaza un mesaj specific si valoarea absoluta a argumentului. Numele abs() reprezinta actiunea generica care urmeaza sa fie efectuata, compilatorul fiind acela care alege metoda potrivita tipului de parametru actual asociat cu numele functiei generice. Este usor de banuit puternicul impact al supraincarcarii in cazul programarii generice.

5 Functii inline

Aceste functii sunt similare macrocomenzilor adresate preprocesoarelor, compilatorul inlocuind fiecare apel de functie inline cu corpul acesteia. Functiile inline sunt destinate sa sprijine implementarea eficienta a tehnicilor OOP in C++. Deoarece abordarea OOP necesita utilizarea extensiva a functiilor membre, desele apeluri de functii ar putea duce la scaderea performantelor programelor. Pentru functii al caror cod este redus se poate utiliza specificatorul inline pentru a evita acest inconvenient. Similaritatea macrocomenzi-functii inline este doar aparenta. Spre deosebire de macrocomenzi, compilatorul considera functiile inline ca functii adevarate.

O functie membru inline se declara astfel:

inline <Tip><Nume_Functie>([<Lista de parametri>]);

6 Functii prietene

Este posibil sa se permita unei functii care nu este membru al unei clase sa aiba acces la membrii privati ai clasei declarand-o ca functie prietena a clasei (friend). Asadar, o functie prietena are acces la membrii private si protected ai clasei careia ii este prietena. Sintaxa pentru declararea unei functii prietene este:

friend <Tip> >Nume_functie>[(<Lista_parametri>)]

Declararea functiei friend se face in interiorul clasei dar implementarea nu este legata de definirea clasei.

A se vedea si exemplul de mai jos pentru mai multe detalii.

#include<conio.h>

#include <iostream.h>

#define liber 0

#define ocupat 1

//Declarare anticipata clasa

class Clasa2;

class Clasa1

class Clasa2

void Clasa1::setare_stare(int val)

void Clasa2::setare_stare(int val)

int ecran_liber(Clasa1 a, Clasa2 b)

void main()

else

;

x.setare_stare(liber);

y.setare_stare(liber);

if (ecran_liber(x,y))

else

;

7 Supraincarcarea operatorilor

Polimorfismul este realizat in C++ si prin supraincarcarea operatorilor. Dupa cum s-a vazut in numeroase exemple, in C++ se pot folosi operatorii >> si << pentru a efectua operatii I/O relativ la consola. Acesti operatori pot efectua aceste operatii suplimentare (stiut fiind faptul ca pot functiona si ca operatorii de shiftare la nivel de biti) deoarece operatiile sunt supraincarcate in fisierul antet IOSTREAM.H. Cand un operator este supraincarcat, el capata o semnificatie suplimentara relativ la o anumita clasa fara sa-si piarda vreunul din intelesurile initiale. Majoritatea operatorilor din C++ pot fi supraincarcati, stabilind semnificatia lor relativ la o anumita clasa.

Limbajul C++ permite supraincarcarea numai a operatorilor existenti in limbaj. Dintre acestia nu pot fi supraincarcati operatorii: . :: ? : .

Sa mai precizam faptul ca, prin supraincarcarea operatorilor nu se poate schimba n-aritatea, prioritatea sau asociativitatea operatorilor, acestea fiind elemente predefinite pentru tipuri predefinite si deci ele se vor mentine si pentru tipuri abstracte.

Prin n-aritate intelegem ca operatorul este unar sau binar.

Supraincarcarea operatorilor se realizeaza cu ajutorul unor functii membre sau prietene speciale. Specificul acestora se afla in numele lor. El se compune din cuvantul cheie operator si unul sau mai multe caractere care definesc operatorul care se supraincarca. Intre cuvantul cheie operator si caracterele care definesc operatorul care se supraincarca se afla cel putin un spatiu alb. Felul in care sunt scrise functiile operator difera pentru cele de tip membru de cele de tip friend.

7.1 Crearea unei functii operator membru

Functiile operator membru au sintaxa de implementare:

<Tip_returnat> <Nume_clasa>::operator # (<Lista_argumente>)

Deseori functiile operator returneaza un obiect din clasa asupra careia opereaza, dar <Tip_returnat> poate fi orice tip valid.

# este o notatie pentru numele operatorului asa cum va fi folosit in program dupa redefinire. Deci, daca supraincarcam operatorul = atunci sintaxa prototipului functiei membru va fi:

<Tip_returnat> operator =(<Lista_argumente>);

Functiile operator membre au un singur parametru sau nici unul. In cazul in care au un parametru, acesta se refera la operandul din dreapta al operatorului.

Celalalt operand ajunge la operator prin intermediul pointerului special this.

Astfel stand lucrurile, trebuie sa avem grija de eventualitatea ca operandul din stanga sa fie o constanta, ceea ce inseamna ca nu mai avem context de apel pentru operatorul redefinit.

Inivitam cititorul sa urmareasca exemplul de mai jos.

#include<conio.h>

#include<iostream.h>

//Clasa complex contine functia operator +

//ca membru

//operatorul + este extins la multimea numerelor complexe

class complex

complex(float a,float b)

;

void disp_nc();

//prototipul operatorului +

complex operator+(complex &op2);

void complex::disp_nc()

//Implementare operator +

//Aceasta sintaxa transforma apelul <ob1+ob2>

//in +(ob2) ob1 fiind accesibil prin pointerul

//special <this>

complex complex::operator+(complex &op2)

void main()

7.2 Supraincarcarea operatorilor folosind o functie friend

Se poate supraincarca un operator relativ la o clasa folosind o functie friend. Deoarece un prieten nu este membru al clasei, nu are disponibil un pointer de tip

this. De aceea, unei functii supraincarcate de tip friend operator I se vor transmite explicit operanzii. Deci, daca supraincarcam un operator unar vom avea un parametru, daca supraincarcam unul binar vom avea doi parametri.

Daca supraincarcam un operator binar, operandul din stanga este pasat in primul parametruiar cel din stanga in al doilea parametru.

Invitam cititorul sa urmareasca utilizarea functiilor friend la supraincarcare in Exercitiul 6 de la Capitolul aplicativ al prezentului suport de curs.

8 Forma generala a unui program in C++

Cu toate ca stilurile individuale difera, majoritatea programelor in C++ au urmatoarea forma generala:

#include.

declaratii clase de baza

declaratii clase derivate

prototipuri de functii nemembre

main()

definitii de functii ne-membre

De remarcat, totodata, faptul ca, in majoritatea proiectelor mari, toate declaratiile de clase se pun intr-un fisier antet si sunt incluse in fiecare modul.



Politica de confidentialitate | Termeni si conditii de utilizare



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 1401
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved