- •Programare orientată pe obiecte
- •§1. Principiile programării orientate pe obiecte.
- •§3. Constructori şi destructori.
- •8) Aceste mesaje apar după ce se termină lucrul programului.
- •§4. Operaţii de intrare/ieşire a informaţiei.
- •§5. Moştenire simplă.
- •§6. Moştenire multiplă
- •§7. Moştenirea pe mai multe niveluri. Clase virtuale
- •§9. Definirea şi utilizarea referinţelor
- •§10. Tablouri de obiecte. Pointeri şi referinţe la obiecte. Pointeri la membrii clasei
- •§11. Dirijarea dinamică a memoriei
- •§12. Constructor de copiere
- •§13. Funcţii prietene şi clase prietene
- •§14. Supraîncărcarea operatorilor
- •§15. Supraîncărcarea operatorilor prin funcţii prietene
- •§16. Supraîncărcarea unor operatori speciali
- •Operatorii de incrementare şi decrementare
- •Operatorii de inserţie şi extragere
- •Operatorul indice
- •Operatorul funcţie
- •Operatorii new şi delete
- •Operatorul virgulă
- •Operatorul de conversie
- •§17. Funcţii-şablon şi clase-şablon
- •§18. Realizarea conceptului de polimorfism
- •§19. Clase abstracte
- •§20. Membrii statici ai clasei
- •§23. Tratarea excepţiilor
- •Bibliografie
§20. Membrii statici ai clasei
Atunci când este necesară crearea membrilor comuni pentru toate obiectele unei clase se utilizează membri statici ai clasei.
O proprietate statică se obţine cu ajutorul cuvântului static plasat înaintea membrului dat:
static tip nume_membru;
În interiorul clasei, proprietatea statică este doar declarată, iar pentru a o defini (adică a-i aloca memorie), ea trebuie să fie definită în exteriorul clasei, scriind:
tip nume_clasa::nume_membru; //fără iniţializare
caz în care membrul dat va fi iniţializat cu valoarea zero, sau:
tip nume_clasa::nume_membru=val; //cu iniţializare
caz în care membrul dat va fi iniţializat cu valoarea propusă val. Chiar dacă membrul static este de tip privat, definirea lui şi iniţializarea cu prima valoare se va face în exteriorul clasei.
În afară de proprietăţi statice, mai există şi funcţii membre statice. Ele se declară în interiorul clasei tot cu ajutorul cuvântului static:
static tip nume_functie(lista_param);
În exteriorul clasei, ea este definită asemănător cu o funcţie ne-statică:
tip nume_cl::nume_functie(lista_param)
{
. . .
}
Funcţiile statice au o serie de restricţii în utilizare:
Nu au acces la membrii ne-statici ai clasei, de aceea foarte des funcţiile statice sunt create pentru a iniţializa membrii statici ai clasei.
În cadrul funcţiilor de tip static, nu există acces la pointerul this.
Modul de apelare a funcţiilor statice se deosebeşte puţin de apelarea funcţiilor ne-statice. Ele pot fi apelate prin metoda obişnuită, adică prin intermediul unui obiect:
nume_obiect.nume_functie(lista_param)
sau poate fi apelată direct la nivel de clasă astfel:
nume_clasa::nume_functie(lista_param)
Funcţiile statice sunt deseori aplicate pentru a prelucra membrii statici ai clasei. Un exemplu de utilizare a membrilor statici poate fi realizarea unui contor al obiectelor create în baza unei clase. În continuare, este prezentat un exemplu de acest tip.
//student.hpp
class student
{
unsigned long id;
static unsigned contor;
public:
student(unsigned long i) { id=i; contor++; }
student(student &s) { id=s.id; contor++; }
~student() { contor--; }
static unsigned GetContor ();
};
unsigned student::GetContor()
{
return contor;
}
unsigned student::contor=0;//definirea membrului static
//în exteriorul clasei
Realizarea clasei este înscrisă în fişierul student.hpp şi va fi utilizată în programul student.cpp care urmează.
//student.cpp
#include <iostream.h>
#include ”student.cpp”
void creare_st()
{
student s(1759);
cout << s.GetContor() << endl;
}
void main()
{
student A(529478);
cout << A.GetContor() << endl; //afişează 1
student B(A);
cout << student::GetContor()<<endl; //afişează 2
creare_st(); //afişează 3 şi deoarece la ieşire
//din funcţie obiectul creat se
//distruge la ultima apelare a
//funcţiei GetContor() se va afişa 2
cout << student::GetContor()<<endl; //afişează 2
}
O altă aplicare a funcţiilor statice poate fi realizarea de interfeţe, atunci când diferite platforme au diferite realizări funcţionale de interacţiune cu anumite resurse tehnice sau de program şi este necesară crearea de versiuni ale aplicaţiei pentru aceste platforme. De aceea se proiectează şi se realizează o interfaţă, adică nişte funcţii cu ajutorul cărora va fi programată aplicaţia. Pentru a realiza diferite versiuni pentru diferite platforme, va fi schimbată doar realizarea funcţiilor din interfaţă, realizarea aplicaţiei rămânând neschimbată.
Ca exemplu ar putea fi luat lucrul cu regimul grafic. Pentru diferite sisteme de operare, există diferite funcţii ce realizează sarcinile de desenare a diferitelor elemente geometrice, de iniţializare şi de închidere a regimului grafic.
În continuare este propusă o clasă-interfaţă EcranGr, în care sunt realizate doar câteva funcţii statice init(), linie(), cerc(), punct(), inchidere() pentru lucru în regim grafic. Pentru a nu putea crea obiecte în baza clasei EcranGr, constructorul clasei este unul privat. În continuare este prezentată o versiune a acestei clase.
class EcranGr
{
public:
static void init ();
static void linie(int x1, int y1, int x2, int y2);
static void cerc(int x, int y, int r);
static void punct(int x, int y);
static void inchidere();
private:
EcranGr() { };
unsigned static culoare;
};
unsigned EcranGr::culoare=15;
void EcranGr::init()
{
int dr=DETECT, rg;
initgraph (&dr, &rg, ” ”);
}
void EcranGr::linie(int x1, int y1, int x2, int y2)
{
line(x1, y1, x2, y2);
}
void EcranGr::cerc(int x, int y, int r)
{
circle(x, y, r);
}
void EcranGr::punct (int x, int y)
{
putpixel (x, y, culoare);
}
void EcranGr::inchidere()
{
closegraph();
}
Funcţiile date pot fi aplicate la realizarea algoritmului necesar de desenare.
§21. Enumerări, uniuni, structuri de date în C++
Toate aceste noţiuni au semnificaţii similare cu cele din limbajul C, dar mai au şi unele trăsături caracteristice doar limbajului C++.
Din cum se ştie, o enumerare creează nişte etichete în spatele cărora sunt, de fapt, nişte constante întregi. Descrierea generală a unei enumerări este exprimată astfel:
enum [nume_enum] {nume_et1[=val1], nume_et2[=val2], …, nume_etk[=valk]} [lista_var];
În C++ enumerările se utilizează deseori pentru definirea unor constante utilizate în interiorul unei clase, de aceea astfel de enumerări sunt descrise în interiorul clasei respective. Dacă enumerarea se utilizează doar în interiorul clasei, atunci ea poate fi definită în zona privată a clasei. De exemplu:
class student
{
enum {obiecte=7, semestre=10};
int note[semestre][obiecte];
. . .
};
Dacă unele etichete vor fi utilizate şi în exteriorul clasei, atunci enumerarea este descrisă în zona publică a clasei. Pentru a utiliza una dintre etichetele enumerării în exteriorul clasei, se foloseşte următoarea sintaxă:
nume_cl::nume_et
Dacă se doreşte a defini variabile de tip enumerare în baza unei enumerări declarate în interiorul unei clase, se procedează astfel:
nume_cl::nume_enumerare var1, var2, ..., vark;
Exemplificarea se va face în baza clasei ios, în zona publică a căreia este declarată următoarea enumerare:
enum
{
skipws = 0x0001,
left = 0x0002,
right = 0x0004,
internal = 0x0008,
dec = 0x0010,
oct = 0x0020,
hex = 0x0040,
showbase = 0x0080,
showpoint = 0x0100,
uppercase = 0x0200,
showpos = 0x0400,
scientific= 0x0800,
fixed = 0x1000,
unitbuf = 0x2000,
stdio = 0x4000
};
În baza acestei enumerări se fixează o serie de fanioane utilizate la afişarea informaţiei pe ecran, de exemplu:
cout << setiosflags(ios::hex) << 100;
Forma generală de descriere a unei structuri:
struct [nume_struct]
{
tip1 cimp1;
. . .
tipN cimpN;
} [lista_var];
În C++ o structură mai poate avea şi funcţii încorporate, astfel ea devenind foarte asemănătoare cu o clasă. Deosebirile dintre structuri şi clase:
Dacă în cadrul unei clase nu este folosit modificatorul de acces, se subînţelege privat, pe când în cadrul unei structuri se subînţelege public.
Dacă în procesul moştenirii modificatorul de moştenire nu este indicat şi entitatea derivată este o structură, atunci se subînţelege modificatorul de moştenire public, iar în cazul unei clase derivate se subînţelege modificatorul de moştenire private.
O uniune este descrisă într-un mod similar cu o structură de date:
union [nume_uniune]
{
tip1 cimp1;
. . .
tipN cimpN;
} [lista_var];
În C++ o uniune poate avea şi funcţii încorporate, dispunând astfel şi de funcţionalitate. Cu referinţă la modelul obiectual, uniunile au multe restricţii şi deosebiri în comparaţie cu clasele:
Dacă în cadrul unei uniuni nu este folosit modificatorul de acces, se subînţelege public.
Uniunile nu pot fi implicate în procesul de moştenire nici în rol de entitate de bază, nici în rol de entitate derivată.
O uniune nu poate avea în calitate de membru un obiect al cărui clasă are operatorul = supraîncărcat.
Pot fi declarate uniuni globale statice anonime, ele având rolul unor zone de memorie comună pentru toate câmpurile care intră în componenţa fiecărei uniuni. Pentru a accesa câmpurile din componenţa unor astfel de uniuni, operatorul rezoluţiei :: este plasat în faţa denumirii câmpului necesar. Iată un exemplu care ilustrează cele spuse:
#include<iostream.h>
static union
{
int i;
long l;
};
void main()
{
::l=0;
::i=90;
cout<< ::l << endl;
}
Întrucât câmpurile i şi l ocupă aceeaşi zonă de memorie, rezultatul afişării va fi 90.
§22. Prelucrarea fişierelor în C++
Fluxurile de intrare/ieşire în C++ se tratează în baza unor clase şi a unor obiecte special create pentru asemenea acţiuni. Clasele date formează o ierarhie de moştenire, îmbogăţindu-se de la nivel la nivel cu noi elemente necesare în procesul de prelucrare a fluxurilor de date. În continuare, este prezentată o ierarhie a celor mai importante clase.
O componentă a interacţiunii cu fluxurile de intrare/ieşire o reprezintă interacţiunea cu fişierele. Lucrul cu fişierele este organizat în baza claselor ifstream, ofstream şi fstream. Clasa ifstream este destinată operaţiei de citire din fişiere. Clasa ofstream este menită pentru operaţia de scriere în fişiere, clasa fstream este destinată atât operaţiei de citire din fişiere, cât şi operaţiei de scriere în fişiere. Pentru a putea utiliza clasele date, este necesar a include fişierul antet fstream.h care conţine descrierile necesare referitoare la clasele date.
Pentru a accesa un fişier de citire sau scriere, mai întâi se creează obiecte fie în baza clasei ifstream, fie a clasei ofstream, fie a clasei fstream:
ifstream nume_ob;
ofstream nume_ob;
fstream nume_ob;
unde nume_ob reprezintă denumirea obiectului definit fie pentru citire, fie pentru scriere, fie pentru citire şi scriere. Obiectul definit nu este legat de oarecare fişier, de aceea, pentru crearea legăturii cu fişierul necesar, este apelată funcţia-membru open(), având următorul prototip:
void open(const char *nume_f, int mod_des);
unde parametrul nume_f reprezintă adresa şirului conţinând numele fişierului deschis, iar mod_des descrie modul de deschidere a fişierului. Modul de deschidere este descris printr-o serie de constante întregi, exprimate prin intermediul unei enumerări, care face parte din clasa ios. De exemplu, în fragmentul ce urmează
ifstream cit;
cit.open("L.cpp", ios::in);
este efectuată deschiderea fişierului L.cpp cu modul de deschidere ios::in, adică pentru citire. În continuare sunt prezentate toate valorile posibile care pot descrie modul de deschidere:
Valori posibile ale modului de deschidere |
Semantica impusă de valoare
|
ios :: in
|
deschiderea fişierului pentru citire |
ios :: out
|
deschiderea fişierului pentru scriere |
ios :: trunc
|
deschiderea fişierului cu trunchierea informaţiei |
ios :: ate
|
deschiderea fişierului cu plasarea cursorului la sfârşitul fişierului |
ios :: app
|
deschiderea fişierului cu impunerea scrierii doar la sfârşitul fişierului |
ios :: nocreate
|
deschiderea unui fişier existent, interzicându-se crearea lui, dacă fişierul nu există |
ios :: noreplace
|
deschiderea fişierului, interzicându-se înlocuirea unui fişier existent |
ios :: binary
|
deschiderea fişierelor în regim binar |
Valorile descrise în tabel pot fi combinate, obţinând efectul reunirii semanticilor a două sau a mai multe valori. Combinarea fanioanelor se face cu ajutorul operaţiei de acţiune pe binari | (sau).
În continuare sunt prezentate o serie de exemple care demonstrează unele dintre proprietăţile descrise mai sus:
deschiderea unui fişier pentru scriere
ofstream sc;
sc.open("t.txt", ios::out);
deschiderea unui fişier pentru citire şi scriere în regim binar
fstream cit_sc;
cit_sc.open("a.txt", ios::in | ios::out |
ios::binary);
Trebuie de subliniat faptul că clasele ifstream, ofstream, fstream conţin mai mulţi constructori. O variantă importantă de constructor cu parametri efectuează crearea obiectului realizând şi deschiderea fişierului asociat cu obiectul dat.
ifstream n_ob(const char *nume_f,
int mod_des=ios::in);
ofstream n_ob(const char *nume_f,
int mod_des=ios::out);
fstream n_ob(const char *nume_f, int mod_des);
În cele ce urmează, este re-scris exemplul anterior creând un obiect asociat cu fişierul ”a.txt” şi fiind destinat citirii şi scrierii în regim binar:
fstream cit_sc("a.txt", ios::in | ios::out |
ios::binary);
După ce fişierul este asociat cu obiectul necesar, pot fi efectuate o serie de operaţii. Sunt, desigur, foarte importante operaţiile de citire şi operaţiile de scriere. Operaţiile date pot fi efectuate atât cu ajutorul unor operatori, cât şi cu ajutorul unor funcţii. Sunt aplicaţi operatorii de inserţie << şi extragere >> utilizaţi şi cu obiectele cout şi cin, caracterizaţi în unul din paragrafele anterioare. Deci, pentru a face scrierea într-un fişier, obiectul asociat cu fişierul acţionează operatorul de inserţie:
nume_ob << date;
Pentru a face citirea dintr-un fişier, obiectul asociat cu fişierul acţionează operatorul de extragere:
nume_ob >> nume_var;
De exemplu, în cele ce urmează vor fi scrise într-un fişier câteva valori numerice:
ofstream scriu("val.dat", ios::out);
float f=-45.23;
. . .
scriu << f << ’ ’ << 24.3;
. . .
La necesitate, într-un mod asemănător, se poate citi din fişier:
ifstream citire("val.dat", ios::in);
float f;
. . .
citire >> f;
cout << f << endl;
. . .
Una dintre funcţiile referitoare la operaţiile de citire este funcţia getline(), caracterizată în unul dintre primele paragrafe. Ea are următorul prototip:
istream & getline(char *sir, int max,
char delim=’\n’);
Mai există o clasă de funcţii get(), a căror prototipuri sunt prezentate mai jos:
int get();
istream & get(char & car);
istream & get(char *sir, int max,
char delim=’\n’);
Varianta a treia este aproape identică cu funcţia getline(), atât doar că la citire ea nu scoate delimitatorul din flux. Primele două funcţii permit citirea unui caracter din flux.
Iată câteva exemple, utilizând funcţiile prezentate mai sus:
citirea unui sir de caractere cu funcţia getline() (delimitator este simbolul ’_’)
ifstream cit("date.dat", ios::in);
. . .
char sir[100];
cit.getline(sir, sizeof(sir), '_');
citirea unui caracter cu funcţia get()
char c;
c=cit.get( );
citirea unui caracter cu o altă variantă a funcţiei get()
char c;
cit.get(c);
O altă categorie de funcţii este reprezentată de funcţiile pentru citire/scriere neformatată. Ele permit citirea sau scrierea unui fragment de date fără oarecare transformare a lor. Prototipurile acestor funcţii sunt asemănătoare, fiind prezentate în cele ce urmează:
prototipul funcţiei de citire
int read(char *dom, int num_car);
prototipul funcţiei de scriere
int write(char *dom, int num_car);
Parametrul dom reprezintă adresa domeniului unde este citită sau de unde este scrisă informaţia, iar parametrul num_car reprezintă numărul de caractere respectiv citite sau scrise. Funcţiile returnează numărul de caractere de facto citite sau scrise. Dacă valoarea returnată este mai mică decât valoarea parametrului num_car, atunci fie s-a produs o eroare, fie s-a ajuns la sfârşitul fişierului în procesul operaţiilor de citire. În exemplul ce urmează, datele citite de la tastatură sunt scrise în fişierul Doc.txt, efectuându-se şi controlul unei scrieri corecte în fişier:
char sir[100];
int num;
ofstream sc("Doc.txt", ios :: out);
. . .
cin.getline(sir, sizeof(sir));
num=sc.write(sir, strlen(sir));
if(num < strlen(sir))
cout << ”Eroare de scriere” << endl;
O altă categorie importantă o formează funcţiile de control al poziţiei curente. Poziţia curentă este locul unde va fi scrisă sau de unde se va citi informaţia din fişier. Determinarea poziţiei curente poate fi efectuată cu ajutorul funcţiei tellg() pentru fluxurile de citire şi tellp(), pentru fluxurile de scriere. Iată prototipurile acestor funcţii:
long tellg();
long tellp();
Ambele funcţii returnează o valoare numerică cuprinsă între 0 şi lungumea fişierului minus unu.
Pentru schimbarea poziţiei curente, în cadrul fluxului este utilizată funcţia seekg() pentru fluxurile de citire şi seekp() pentru fluxurile de scriere. Aceste funcţii sunt descrise de următoarele prototipuri:
int seekg(int pozitie, int origine);
int seekp(int pozitie, int origine);
unde parametrul pozitie determină valoarea poziţiei curente, iar parametrul origine determină în raport cu ce este fixată poziţia curentă şi poate lua trei valori. Sensul acestor valori este descris în următorul tabel:
Valori posibile ale parametrului origine |
Semantica impusă de valoare
|
ios :: beg
|
poziţia curentă este fixată în raport cu începutul fişierului |
ios :: cur
|
poziţia curentă este fixată în raport cu poziţia curentă |
ios :: end
|
poziţia curentă este fixată în raport cu sfârşitul fişierului |
Pentru a determina starea fluxului de date, sunt utilizate o serie de funcţii care oferă unele informaţii necesare. Funcţia eof() permite determinarea faptului dacă s-a ajuns sau nu la sfârşitul fişierului. Funcţia fail() permite a determina dacă s-a produs sau nu o eroare nefatală. Funcţia bad() permite a determina dacă s-a produs sau nu o eroare fatală. Funcţia good() permite a determina dacă operaţia anterioară aplicată asupra fluxului s-a efectuat sau nu cu succes. Funcţiile respective au următoarele prototipuri:
int eof();
int fail();
int bad();
int good();
Funcţiile date returnează fie valoarea 0, fie valoarea 1, în dependenţă de evenimentul produs. Tabelul ce urmează descrie valorile returnate de funcţii.
Numele funcţiei |
Valoarea returnată |
Semantica valorii returnate |
eof()
|
0
|
nu s-a ajuns la sfârşitul fişierului |
1
|
s-a ajuns la sfârşitul fişierului |
|
fail()
|
0
|
nu s-a produs o eroare nefatală |
1
|
s-a produs o eroare nefatală |
|
bad()
|
0
|
nu s-a produs o eroare fatală |
1
|
s-a produs o eroare fatală |
|
good()
|
0
|
s-a produs o eroare
|
1
|
operaţie efectuată cu succes |
Urmează un exemplu în care sunt utilizate unele dintre ideile descrise anterior.
Exemplu. De alcătuit un program în care sunt realizate operaţiile de transcriere a informaţiei dintr-un fişier cu caractere majuscule şi cu caractere minuscule. Operaţiile sunt realizate prin două metode: una utilizează două fluxuri de date, unul de intrare şi altul de ieşire, iar alta utilizează doar un singur flux, dar de intrare/ieşire.
#include<fstream.h>
#include<stdlib.h>
#include<ctype.h>
#include<string.h>
#include<conio.h>
void fisierMaj(char *numef)
{
int numCar;
char linie[20];
ifstream cit(numef);
if(cit.fail())
{
cerr << "Eroare deschidere.";
exit(0);
}
ofstream scr(numef);
if(scr.fail())
{
cerr << "Eroare deschidere.";
exit(0);
}
for(;!cit.eof();)
{
cit.getline(linie,sizeof(linie));
numCar=cit.gcount();
for(int i=0; *(linie+i);
*(linie+i++)=toupper(*(linie+i)));
scr << linie;
if(numCar!=strlen(linie))
scr << ’\n’;
}
scr.close();
cit.close();
}
//-----------------
void fisierMin(char *numef)
{
long pozitie;
int numCar;
char linie[20];
fstream scr(numef,ios::out | ios::in);
if(scr.fail())
{
cerr << "Eroare deschidere.";
exit(0);
}
for(;!scr.eof();)
{
pozitie=scr.tellg();
scr.getline(linie,sizeof(linie));
numCar=scr.gcount();
for(int i=0; *(linie+i);
*(linie+i++)=tolower(*(linie+i)));
scr.seekg(pozitie, ios::beg);
scr << linie;
if(numCar != strlen(linie))
scr << ’\n’;
}
scr.close();
}
//-----------------
void main(void)
{
char nume[]=”import.txt”;
fisierMin(nume);
fisierMaj(nume);
getch();
}
Pentru a realiza scopul propus, au fost alcătuite două funcţii fisierMin() şi fisierMaj() pentru a face transcrierea respectiv cu minuscule şi cu majuscule. Prima funcţie a folosit două fluxuri, pe când a doua funcţie a folosit doar un flux, dar a mai utilizat o variabilă în care se memora la citire poziţia curentă pentru a şti locul de scriere a informaţiei transformate. Prima funcţie nu a avut necesitate în o astfel de variabilă.
