Introducere in C
-----------
------
#include <stream.h>
main()
#include <stream.h> - include declaratii pentru facilitatile standard de intrare/iesire aflate in stream.h.
Operatorul << scrie cel de al doilea operand al sau peste primul.
Compilare
Se apeleaza cu litere mari CC. Daca programul este in fisierul hello.c,
atunci se compileaza si se executa ca mai jos:
$CC hello.c
$a.out
Hello, world
$
Intrare
#include <stream.h> main() //converteste inch in cm
Exemplu de executie
$a.out
inches = 12
12 in = 30.48 cm
$
Ultimii 4 operatori pot fi scrisi:
cout << inch << 'in=' << inch*2.54 << 'cmn';
1.2 Comentariu
Incepe prin /* si se termina prin */.
Comentariu poate incepe prin // si se termina la sfirsitul liniei respective.
Tipuri si Declaratii
Fiecare nume si fiecare expresie are un tip care determina operatiile
care pot fi facute asupra lor.
O declaratie este o instructiune care introduce un nume intr-un program. O declaratie specifica un tip pentru acel nume. Un tip defineste utilizarea numelui sau a unei expresii. Operatiile de forma +, -, * si / se definesc pentru intregi. Dupa ce s-a inclus stream.h, un int poate fi cel de al doilea operand pentru << cind primul argument este ostream.
Tipul unui obiect determina nu numai care operatii pot fi aplicate asupra lui, ci de asemenea intelesul acelor operatii. De 959h72j exemplu, instructiunea:
cout << inch << 'in=' << inch*2.54 << 'cmn';
trateaza corect cele 4 valori de iesire care sint diferite.
C++ are citeva tipuri de baza si diferite moduri de a crea altele noi.
Tipuri fundamentale
char short int long float double sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(float) <= sizeof(double) const float pi = 3.14; const char plus = '+';
Operatori aritmetici:
+ - (unari si binari ambii)
* / %
Operatori de comparare ca in C.
double d = 1; int i = 1;
d = d + i; i = d + i;
Tipuri derivate
-> pointer
*const -> pointer constant
& -> adresa
[] -> vector
() -> functie
char* p;
char *const q;
char v[10];
char c;
//
p = &c; // p pointeaza spre c
1.4 Expresii si Instructiuni
~ &(si) ^ | << >> se aplica la intregi
= op=
x = sqrt (a = 3*x)
++ --
Cea mai frecventa forma a unei instructiuni este o instructiune expresie; ea consta dintr-o expresie urmata de un punct si virgula.
a = b*3+c;
cout << 'go go go';
lseek(fd, 0, 2);
Instructiunea VIDA:
;
Blocuri:
Instructiunea IF:
#include <stream.h>
main() //converteste din inch in cm si invers
else
if(ch=='c')
else
in = cm = 0; cout << in << 'in=' << cm << 'cmn';
}
Instructiunea SWITCH:
switch(ch)
Instructiunea WHILE:
while(*p!=0)
*q = 0;
while(*p)
*q++ = *p++;
*q = 0; while(*q++ = *p++);
Instructiunea FOR:
for(int i=0; i<10; i++)
q[i] = p[i];
Declaratii:
for(int i=1; i<MAX; i++)
1.5 Functii
-------
O functie este o parte denumita a programului care poate fi apelata din alte parti ale programului atit de des, cit este nevoie.
extern float pow(float, int);
// pow este definita in alta parte main()
float pow(float x, int n)
}
overload pow;
int pow(int, int);
double pow(double, double);
//.
x = pow(2, 10);
y = pow(2.0, 10.0);
Declaratia overload pow informeaza compilatorul ca se intentioneaza sa se foloseasca numele pow pentru mai mult decit o singura functie.
Daca o functie nu returneaza o valoare trebuie sa se declare void:
void swap(int* p, int* q)
Structura programului
Un nume care se utilizeaza ca sa refere acelasi lucru in doua fisiere sursa trebuie sa fie declarat ca extern:
extern double sqrt(double);
extern istream cin;
Este bine ca aceste declaratii sa se plaseze intr-un fisier si apoi acesta sa se includa. De exemplu, daca declaratia pentru sqrt() este in math.h
atunci putem scrie:
#include <math.h>
//..
x = sqrt(4);
Daca este intre paranteze unghiulare se include de obicei din /usr/include/CC. Altfel se folosesc ghilimele.
#include 'math1.h'
#include '/usr/bs/math2.h'
Mai jos un sir se defineste intr-un fisier si se scrie in altul.
//header.h
extern char* prog_name;
extern void f();
Fisierul main.c este programul principal:
#include 'header.h' char* prog_name = 'silly, but complete'; main()
si fisierul f.c imprima sirul:
#include <stream.h>
#include 'header.h'
void f() La executie se obtine textul:
$CC main.c f.c -o silly
$silly
silly, but complete
$
Clase
Sa vedem cum putem defini tipul ostream. Pentru a simplifica aceasta sarcina, presupunem ca s-a definit tipul streambuf pentru buferarea caracterelor. Un streambuf este in realitate definit in <stream.h> unde se gaseste de asemenea definitia reala a lui ostream.
Definitia tipului utilizator (numit clasa in C++) contine o specificatie a datei necesare pentru a reprezenta un obiect de acest tip si o multime de operatii pentru a manevra astfel de obiecte. Definitia are doua parti: o parte privata ce pastreaza informatia care poate fi utilizata numai de implementatorul ei si o parte publica ce reprezinta o interfata cu utilizatorul:
class ostream;
Declaratiile dupa eticheta public specifica interfata; utilizatorul poate apela cele 3 functii put(). Declaratiile ce se gasesc inaintea etichetei public specifica reprezentarea unui obiect al clasei ostream. Numele buf si state pot fi utilizate numai prin functiile put() declarate in partea public.
O clasa defineste un tip si nu un obiect data, asa ca pentru a utiliza un ostream noi trebuie sa declaram unul (in acelasi mod in care noi declaram variabilele de tip int):
ostream my_out;
Presupunind ca my_out a fost deja initializat in mod corespunzator, el poate fi utilizat acum astfel:
my_out.put('Hello, worldn');
Operatorul se foloseste pentru a selecta un membru al clasei pentru un obiect dat al acelei clase. Aici functia membru put() se apeleaza pentru obiectul my_out.
Functia poate fi declarata astfel:
void ostream::put(char* p)
unde sputc() este o functie care pune un caracter in streambuf. Prefixul ostream este necesar pentru a distinge put() a lui ostream de alte apeluri ale lui put().
Pentru a apela o functie membru, un obiect al clasei trebuie sa fie specificat. In functia membru, acest obiect poate fi implicit referentiat asa cum se face in ostream::put() de mai sus; in fiecare apel, buf se refera la membrul buf al obiectului pentru care se apeleaza functia.
Este de asemenea posibil sa ne referim explicit la acel obiect printr-un pointer numit this. Intr-o functie membru al unei clase X, acesta este implicit declarat ca X* (pointer spre X) si initializat cu un pointer spre obiectul pentru care functia este apelata. Definitia lui ostream::put() ar putea fi scrisa astfel:
void ostream::put(char* p)
Operatorul -> se utilizeaza pentru a selecta un membru al unui obiect.
Operatorul overloading
Clasa reala ostream defineste operatorul << pentru a-l face convenabil sa scrie diferite obiecte cu o singura instructiune.
Pentru a defini @, unde @ este orice operator C++ pentru un tip definit de utilizator, noi definim o functie numita operator@ care are argumente de tip corespunzator. De exemplu:
class ostream;
ostream ostream::operator<<(char* p)
defineste operatorul <<, ca membru al clasei ostream, asa ca s<<p se interpreteaza ca s.operator<<(p) cind s este un ostream si p este un pointer spre caractere. Operatorul << este binar, dar functia operator<<(char*) pare la prima vedere sa aiba un singur argument; el totusi are de asemenea argumentul standard implicit this.
Revenind din ostream ni se permite sa aplicam << la rezultatul unei operatii de iesire. De exemplu, s<<p<<q se interpreteaza (s.operator<<(p)).operator<<(q). Acesta este modul in care operatiile sint furnizate pentru tipuri predefinite.
Utilizind setul de operatii furnizate cu membri publici ai clasei ostream, noi putem acum defini << pentru tipuri utilizator cum ar fi cel complex, fara a modifica declaratia clasei ostream:
ostream operator<<(ostream s, complex z) // un complex are doua parti: real si imaginar // se scrie un complex ca (real, imag)
Intrucit operator<<(ostream, complex) nu este o functie membru, este necesar sa explicitam argumentele ca sa fie binare. Se vor scrie valorile in ordine corecta caci <<, ca si majoritatea operatorilor C++ se grupeaza de la stinga la dreapta; adica a<<b<<c inseamna (a<<b)<<c.
Compilatorul cunoaste diferenta dintre functiile membru si nemembru cind interpreteaza operatorii. De exemplu, daca z este o variabila complexa, s<<z va fi expandata utilizind apelul functiei standard (nemembru) operator<<(s,z).
Referinte
Ultima versiune a lui ostream din nefericire contine o eroare serioasa. Problema este ca ostream este copiat de doua ori pentru fiecare utilizare a lui <<: odata ca un argument si odata ca valoare returnata. Aceasta lasa starea nemodificata dupa fiecare apel. Este nevoie de o facilitate pentru a pasa un pointer la ostream mai degraba decit sa se paseze insasi ostream.
Aceasta se poate realiza utilizind referintele. O referinta actioneaza ca un nume pentru un obiect; T& inseamna referinta la T. O referinta trebuie initializata si devine un nume alternativa pentru obiectul cu care este initializat. De exemplu:
ostream& s1 = my_out; ostream& s2 = cout;
Referintele s1 si my_out pot fi utilizate acum in acelasi mod si cu acelasi inteles. De exemplu, atribuirea:
s1 = s2;
copiaza obiectul referit prin s2 (adica cout) in obiectul referit prin s1 (adica my_out). Membri se selecteaza utilizind operatorul punct:
s1.put('don't use ->');
si daca utilizam operatorul adresa, primim adresa obiectului referit:
&s1 == &my_out
Prima utilizare evidenta a referintei este ca sa ne asiguram ca adresa unui obiect, mai degraba decit obiectul insusi, este pasata la o functie de iesire (aceasta se numeste in anumite limbaje apel prin referinta):
ostream& operator<<(ostream& s, complex z)
Corpul functiei este neschimbat dar asignarea facuta lui s va afecta acum obiectul dat ca argument. In acest caz, returnindu-se o referinta de asemenea se imbunatateste eficienta, intru- cit modul evident de implementare a unei referinte este un pointer si un pointer este mai ieftin sa fie transferat decit o structura mare.
Referintele sint de asemenea esentiale pentru definirea sirurilor de intrare deoarece operatorului input i se da variabila in care se citeste ca operand. Daca referintele nu sint utilizate, utilizatorul ar trebui sa paseze pointeri expliciti functiilor de intrare:
class istream;
Sa observam ca se folosesc doua operatii separate pentru a citi intr-o zona long si intr-o zona int si numai una pentru scriere. Motivul este ca un int poate fi convertit spre long prin regulile implicite de conversie.
Constructori
Definirea lui ostream ca si clasa, face ca datele membru sa fie private. Numai o functie membru poate accesa membri privati, asa ca noi trebuie sa furnizam una pentru initializare. O astfel de functie se numeste constructor si se distinge avind acelasi nume ca si al clasei lui:
class ostream;
Aici se furnizeaza doi constructori. Unul ia un streambuf pentru o iesire reala iar celalalt ia o dimensiune si un pointer spre caractere pentru formatarea sirului. Intr-o declaratie, argumentul lista necesar pentru un constructor se adauga la nume. Noi putem declara acum streamuri astfel:
ostream my_out(&some_stream_buffer); char xx[256]; ostream xx_stream(256,xx);
Declaratia lui my_out seteaza nu numai cantitatea corespunzatoare de memorie ci de asemenea apeleaza si constructorul ostream::ostream(streambuf*) pentru a-l initializa cu argumentul &some_stream_buffer, care este un pointer spre un obiect potrivit al clasei streambuf. Declaratia functiei xx_stream() se trateaza similar, dar utilizeaza celalalt constructor. Declarind constructori pentru o clasa nu furnizam numai un mod de a initializa obiecte, ci de asemenea se asigura ca toate obiectele clasei vor fi initializate. Cind s-a declarat un constructor pentru o clasa, nu este posibil sa se declare o variabila a acelei clase fara a apela un constructor. Daca o clasa are un constructor care nu ia argumente, acel constructor va fi apelat daca nu se da nici un argument in declaratie.
Vectori
Conceptul de vector construit in C++ a fost proiectat pentru a permite o eficienta maxima la executie si memorie minima. Este de asemenea, mai ales cind se utilizeaza impreuna cu pointerii, un instrument puternic pentru construirea unor facilitati de nivel inalt. Noi putem, totusi, sa ne plingem de faptul ca dimensiunea unui vector trebuie sa fie specificata ca o constanta, ca nu exista verificarea depasirii limitelor
vectorilor, etc.. Un raspuns la aceste plingeri este: noi insine putem programa acest lucru. Sa vedem daca acesta este un raspuns rezonabil, cu alte cuvinte, sa testam facilitatile de abstractizare ale lui C++ incercind sa furnizam aceste caracteristici pentru tipurile de vectori proiectati de noi si sa observam dificultatile implicate, costurile implicate si comoditatea utilizarii tipurilor de vectori rezultate.
class vector
void set_size(int);
int& operator[](int);
int& elem(int i)
};
Functia size() returneaza numarul de elemente al vectorului; adica, indicii trebuie sa fie in domeniul 0..size()-1. Functia set_size() este furnizata pentru a schimba acea dimensiune, elem() furnizeaza acces la membri fara a verifica indexul, iar operator[] furnizeaza acces cu verificarea limitelor.
Ideea este de a avea clasa ca o structura de dimensiune fixa care controleaza accesul la memoria reala a vectorului, care este alocata prin constructorul vectorului utilizind operatorul new de alocare de memorie.
vector::vector(int s)
Noi putem declara vectori foarte asemanator cu vectorii care sint construiti in limbajul insusi:
vector v1(100);
vector v2(nelem*2-4);
Operatia de acces poate fi definita ca:
int& vector::operator[](int i)
Returnind o referinta se asigura ca notatia [] poate fi utilizata de ambele parti a unei atribuiri:
v1[x] = v2[y];
Functia ~vector() este un destructor; adica este o functie declarata pentru a fi apelata implicit cind obiectul unei clase iese in afara domeniului. Destructorul pentru o clasa C se numeste ~C. Daca noi o definim astfel:
vector::~vector()
ea va fi utilizata pentru a sterge operatorul si pentru a dezaloca spatiul alocat prin constructor, asa ca atunci cind un vector iese afara din domeniu, tot spatiul lui este eliberat si poate fi reutilizat.
Expandare inline
O functie membru nu este mai costisitoare la apel decit o functie nemembru cu acelasi numar de argumente (sa ne amintim ca o functie membru totdeauna are cel putin un argument), iar apelul functiilor C++ este aproximativ tot atit de eficient ca si in alte limbaje. Totusi, pentru functiile extrem de mici, apelul poate sa iasa in evidenta. Daca este asa, noi am putea dori sa specificam o functie care sa expandeze in linie. In caz afirmativ, compilatorul va genera cod propriu pentru functie in locul
apelului. Semanticile apelului ramin neschimbate. De exemplu, daca size() si elem() sint substituite in linie:
vector s(100);
//.
i = s.size();
x = elem(i-1);
este echivalent cu
i = 100; x = s.v[i-1];
Compilatorul este destul de abil pentru a genera un cod care este tot atit de bun ca si cel care se obtine direct prin macro expandare. Evident, compilatorul are nevoie uneori sa foloseasca variabile temporare si alte citeva abilitati pentru a prezerva semanticile.
Noi dorim o indicatie a faptului ca functia se expandeaza inline care sa preceada definitia ei. Aceasta este cuvintul cheie inline sau pentru o functie membru, pur si simplu prin includerea definitiei functiei in declaratiile clasei, asa cum s-a facut pentru size() si elem() in exemplul precedent.
Cind se utilizeaza bine, functiile inline maresc simultan viteza de executie si descresc dimensiunea codului obiect. Totusi, functiile inline din declaratiile clasei pot incetini compilarea, asa ca ele trebuie sa fie eliminate cind ele nu sint necesare. Pentru ca substitutia inline sa fie un beneficiu semnificativ pentru o functie, functia trebuie sa fie foarte mica.
Clase derivate
Acum sa definim un vector pentru care un utilizator poate defini limitele
indexului.
class vec:public vector;
Definind vec ca public vector inseamna inainte de toate ca un vec este un vector. Adica tipul vec are toate proprietatile tipului vector si in plus cele specific declarate pentru el. Clasa vector se spune ca este clasa de baza pentru vec si invers vec se spune ca este derivat din vector.
Clasa vec modifica clasa vector furnizind un constructor diferit care cere utilizatorului sa specifice cele doua limite ale indexului in schimbul dimensiunii si produce accesul propriu functiilor elem(int) si operator[](int).elem() a lui vec se exprima usor in termenii lui elem() al lui vector:
int& vec::elem(int i)
Scopul operatorului :: este de a elimina o recursivitate infinita calculind vec::elem() din el insusi. Unarul :: se poate folosi pentru a ne referi la nume nelocale. Ar fi rezonabil sa declaram vec::elem() inline din motive de eficienta, dar nu este necesar, sau este posibil sa-l scriem asa ca el sa utilizeze direct membrul privat V al clasei vector. Functiile unei clase derivate nu au nici un acces special la membri privati ai clasei de baza propri. Constructorul poate fi scris astfel:
vec::vec(int lb, int hb) : (hb-lb+1)
Constructia: (hb-lb+1) se utilizeaza pentru a specifica lista argument pentru constructorul clasei de baza vector::vector(). Acest constructor se apeleaza inaintea corpului lui vec::vec(). Iata un mic exemplu care poate fi executat daca se compileaza cu restul declaratiilor lui vector:
#include <stream.h>
void error(char* p)
void vector::set_size(int)
int& vec::operator[](int i)
main()
cout << 'n';
vec b(10, 19);
for(i=0; i<b.size(); i++)
b[i+10] = a[i]; for(i=0; i<b.size(); i++)
cout << b[i+10] << ' '; cout << 'n';
}
Acesta produce:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
Mai mult despre operatori
O alta directie a dezvoltarii este de a furniza vectori cu operatii:
class Vec::public vector Vec(Vec&);
~Vec() void operator=(Vec&); void operator*=(Vec&); void operator*=(int);
//
};
Observam modul in care constructorul pentru clasa derivata Vec::Vec() este definit pentru a transfera argumentele lui la constructorul pentru clasa de baza vector::vector(). Operatorul de atribuire este supraincarcat si poate fi definit astfel:
void Vec::operator=(Vec& a)
Atribuirea de Vec-uri acum copiaza elemente, in timp ce atribuirea de vectori copiaza pur si simplu structura care controleaza accesul la elemente.
Totusi, ultima se intimpla cind se copiaza un vector fara utilizarea explicita
a operatorului de atribuire:
cind un vector este initializat prin atribuirea unui alt vector;
cind un vector se paseaza ca argument;
cind un vector se paseaza ca valoare returnata de la o functie.
Pentru a cistiga control in aceste cazuri pentru vectorii Vec, noi definim constructorul Vec(Vec&):
Vec::Vec(Vec& a) : (a.size())
Acest constructor initializeaza un Vec ca o copie a altuia si va fi apelat in cazurile mentionate precedent. Pentru operatori de forma = si +=, expresia din stinga este evident speciala si se pare natural ca ei sa se implementeze ca operatii asupra obiectelor notate prin acea expresie. In particular, este posibil pentru ei sa se schimbe valoarea primului lor operand. Pentru operatori de forma + si -, operandul sting nu necesita o atentie speciala. Noi am putea, de exemplu, sa transferam ambele argumente prin valoare si totusi sa capatam o implementare corecta a adunarii vectorilor. Vectorii pot fi mari, asa ca, pentru a elimina copierea, operanzii lui + se transfera operatorului operator+() prin referinta:
Vec operator+(Vec& a, Vec& b)
Iata un mic exemplu care poate fi executat daca se compileaza cu declaratiile de vector prezentate anterior:
#include <stream.h>
void error(char* p)
void vector::set_size(int)
void vec::operator[](int i)
main()
Prieteni (Friends)
Functia operator+() nu opereaza direct asupra reprezentarii unui vector; intr-adevar, nu ar putea, deoarece nu este un membru. Totusi, uneori este de dorit ca sa se admita ca functii nemembru sa aiba acces la partea privata a unui obiect de clasa. De exemplu, neexistind functia cu acces 'neverificat', vector:: elem(), noi ar trebui sa fortam verificarea indexului i fata de limitele vectorului de trei ori de fiecare data cind se executa ciclul. Aceasta problema a fost eliminata aici, dar ea este tipica, asa ca exista un mecanism pentru o clasa care sa accepte accesul la partea sa privata pentru o functie nemembru.
O declaratie a unei functii precedate prin cuvintul cheie friend este pur
si simplu plasata in declaratia clasei. De exemplu, dindu-se:
class Vec; // Vec este un nume de clasa
class vector;
noi putem scrie:
Vec operator+(Vec a, Vec b)
Un aspect particular util al mecanismului de prieten (friend) este ca o functie poate fi prieten a doua sau mai multe clase. Pentru a vedea aceasta, sa consideram definirea unui vector si a unei matrici si apoi definirea functiei de inmultire.
Vectori generici
Noi am dori, de exemplu, unul din acei vectori pentru tipul matrice pe care l-am definit. Din nefericire, C++ nu furnizeaza o facilitate pentru a defini o clasa vector cu tipul elementelor ca argument. Un mod de a proceda ar fi sa se copieze atit definitia clasei cit si functiile membru. Acest lucru nu este ideal, dar adesea este acceptabil. Noi putem utiliza macroprocesor pentru a mecaniza acel task. De exemplu, clasa vector este o versiune simplificata a unei clase care poate fi gasita intr-un fisier header standard. Noi am putea scrie:
#include <vector.h>
declare(vector, int);
main()
Fisierul vector.h defineste macrouri asa ca declare(vector, int) se expandeaza spre declaratia unei clase vector foarte asemanatoare cu cea definita, iar implement(vector, int) se expandeaza spre definitiile functiilor acelei clase.Intrucit implement(vec- tor, int) se expandeaza in definitii de functii, el poate fi utilizat numai odata intr-un program, in timp ce declare(vector, int) trebuie sa fie utilizat odata in fiecare fisier care manipuleaza acest fel de vectori intregi.
declare(vector, int);
//
implement(vector, char);
da un tip (separat) 'vector de caractere'.
Vectori polimorfici
O alta varianta ar fi ca sa definim vectorul nostru si cu alte clase container in termenii unor pointeri la obiectele unei anumite clase:
class common;
class vector;
Sa observam ca deoarece pointerii si nu obiectele insasi sint memorati intr-un astfel de vector, un obiect poate fi 'in' diferiti astfel de vectori in acelasi timp. Aceasta este o caracteristica foarte utila pentru clasele container de felul vectorilor, listelor inlantuite, multimilor, etc.. Mai mult decit atit, un pointer la o clasa derivata poate fi atribuit la un pointer spre clasa ei de baza, asa ca cvector de mai sus poate fi utilizat pentru a pastra pointeri spre obiectele tuturor claselor derivate din common. De
exemplu:
class apple : public common;
class orange : public common;
class apple_vector : public cvector;
Totusi, tipul exact al unui obiect intr-o astfel de clasa container nu mai este cunoscut de compilator. De exemplu, in exemplul precedent noi stim ca un element al vectorului este un common, dar este un apple sau un orange ? In mod obisnuit, tipul exact trebuie sa fie descoperit mai tirziu pentru a putea utiliza corect obiectul. Pentru a face aceasta, noi trebuie sau sa memoram o anumita forma a tipului de informatie in obiectul insusi sau sa ne asiguram ca numai obiectele unui tip dat se pun in container. Ultima varianta este atinsa trivial utilizind o clasa derivata. De exemplu, noi am putea face un vector de pointeri apple:
class apple_vector : public cvector
// };
utilizind notatia de type_casting.
common*& (o referinta la pointer spre common) returnat prin cvector::elem
spre apple*&. Aceasta utilizare a claselor derivate furnizeaza o alternativa a
claselor generice. Este putin mai greu sa scriem in acest fel (daca nu sint utilizate macrouri asa incit clasele derivate sa fie de fapt utilizate pentru a implementa clase generice), dar are avantajul ca toate clasele derivate au in comun o singura copie a functiilor clasei de baza.
Pentru o clasa generica de felul lui vector(type), trebuie sa se faca o noua copie a acelor functii (prin implement()) pentru fiecare tip nou utilizat.
Alternativa de a memora identificatorul tipului in fiecare obiect ne conduce spre un stil de programare adesea referit ca bazat sau orientat spre obiect.
Functii virtuale
Sa consideram scrierea unui program pentru afisarea formelor pe un ecran. Atributele comune ale formelor se reprezinta prin clasa shape, atribute specificate prin clase derivate specifice:
class shape
point where() virtual void draw(); virtual void rotate(int);
//..
};
Functiile care pot fi definite fara cunostinte despre forma specifica (de exemplu move si where), pot fi declarate in mod obisnuit. Restul se declara virtual, adica se vor defini intr-o clasa derivata. De exemplu:
class circle : public shape
//.
};
Acum daca shape_vec este un vector de forme, noi putem scrie:
for(int i=0; i<no_of_shapes; i++)
shape_vec[i].rotate(45); pentru a roti (si redesena) toate formele cu 45 de grade.
Acest stil este deosebit de util in programe interactive cind obiectele de tipuri diferite sint tratate uniform de catre softwareul de baza.
|