Documente Academic
Documente Profesional
Documente Cultură
Dorin Bocu
2002-2003
În loc de introducere
Autorul
2
I BAZELE C++. LIMBAJUL C
3
de dezvoltare complex, la care putem spune că a participat întreaga comunitate
preocupată de ridicarea performanţelor în munca de programare. De fapt, strămoşi direcţi
ai limbajului C pot fi considerate limbajele BCPL (creat de Martin Richards) şi limbajul B
(inventat de Ken Thompson). În anii ’70, limbajul B a influenţat în cel mai înalt grad
specificarea limbajului C. Mult timp, standardul de facto pentru C a fost versiunea ce
însoţea sistemul de operare UNIX. Acest standard a fost descris pentru prima dată în
cartea The C Programming Language, scrisă de Brian Kernigan şi Dennis Ritchie,
apărută în 1978 la editura Prentice Hall. Odată cu creşterea popularităţii calculatoarelor
personale, au fost create numeroase implementări de C, ceea ce a generat o problemă
nouă: compatibilitatea implementărilor. Pentru rezolvarea acestei probleme, în vara
anului 1983, a fost înfiinţat un comitet pentru crearea unui standard ANSI (American
National Standard Institute) care avea ca sarcină specificarea definitivă a limbajului C.
Standardul ANSI C a fost adoptat în decembrie 1989, primele copii devenind disponibile la
începutul lui 1990. La ora actuală, toate compilatoarele C/C++ se aliniază la standardul
ANSI C. Totodată standardul ANSI C este o bază pentru propunerea de standard ANSI
C++(Bazat pe limbajul C, C++ adaugă extinderi care permit programarea obiect
orientată). Evident, vom reveni asupra topicii C++.
Despre un sistem soft spunem că este portabil dacă în momentul în care este
scris pe o anumită maşină, sub un anumit mediu de operare, poate fi uşor adaptat
pe altă maşină, eventual cu alt sistem de operare.
4
Numim programare la nivel de sistem activitatea de scriere a codului pentru
anumite componente ale sistemului de operare sau care sunt accesorii ale
sistemelor de operare.
C are doar 32 cuvinte-cheie (27 din standardul Kernigan & Ritchie şi cinci
adăugate de comitetul de standardizare ANSI C). Limbajele de nivel înalt depăşesc cu
mult oferta limbajului C. Faptul că are atât de puţine cuvinte cheie ar trebui să fie benefic
pentru procesul de învăţare a limbajului. Ceea ce se învaţă greu în C este
“democraţia” prea mare în comparaţie, de exemplu, cu stilul autoritar în care
compilatorul Pascal asistă scrierea de programe corecte.
5
{
gotoxy(12,I);
cout<<”Valoarea curentă a lui I:”<<I;
}
.
Blocul de cod este delimitat de acolade.
L-am evidenţiat folosind efectul de umbrire (shading) a textului.
6
Toate programele C constau din una sau mai multe funcţii a căror sintaxă şi
semantică o vom prezenta în secţiunile următoare.
7
2 Expresii în C
În această secţiune vom trece în revistă propunerile limbajului C în ceea ce proveşte
posibilitatea de a utiliza expresii. Toate limbajele de programare acordă o atenţie
specială noţiunii de expresie deoarece, în ultimă analiză, orice transformare a datelor de
intrare ale unui program este realizată prin intermediul uneia sau a mai multor expresii.
Aşa cum vom descoperi pe parcurs, C este un limbaj mult mai flexibil decât alte limbaje în
materie de definire şi utilizare a expresiilor. Ceea ce este foarte limpede în acest moment
este faptul că expresiile sunt realizate cu ajutorul elementelor atomice numite date
şi operatori. Datele pot fi reprezentate cu ajutorul constantelor sau prin intermediul
variabilelor. Ajungem, evident, la problema tipurilor de date suportate de C.
8
Formatul exact al valorilor în virgulă mobilă depinde de modul lor de introducere. Întregii
corespund, în general, mărimii normale a unui cuvânt pe calculatorul respectiv.
Valorile de tip char sunt, în general, utilizate pentru a memora valori definite de setul de
caractere ASCII. Valorile care ies din acest domeniu sunt tratate în mod diferit de
compilatoare.
Domeniul valoric pentru float şi double va depinde de metoda folosită pentru a
reprezenta numere în virgulă mobilă. Standardul ANSI C indică domeniul valoric pentru
virgulă mobilă de la 1E-37 pâna la 1E+37. Evident, vor exista deosebiri atât în ceea ce
priveşte ordinul de mărime cât şi în ceea ce priveşte precizia între cele două specii de
virgulă mobilă. Numărul minim de cifre din punct de vedere al preciziei este precizat în
Tabelul 2.
Tipul void declară explicit că o funcţie nu returnează nici o valoare sau crează
pointeri generici.
9
adaptare mai precisă la diferite situaţii. După cum rezultă şi din Tabelul 2, lista
specificatorilor de conversie este:
signed
unsigned
long
short
În mod evident, diferenţa între întregii cu semn şi întregii fără semn constă în modul în
care se interpretează bitul de pe poziţia cea mai semnificativă. Dacă, de exemplu, indicaţi
un întreg cu semn, compilatorul generează cod care presupune că bitul cel mai
semnificativ va fi utilizat ca bit de semn (0 dacă numărul este pozitiv, 1 dacă numărul este
negativ). Să mai menţionăm faptul că, în general, pentru reprezentarea întregilor negativi
se foloseşte codul complementar (complementul faţă de 2).
Detalii referitoare la caracteristicile codului complementar se pot obţine consultând lucrări
de specialitate în care se dezbate problema codificării datelor numerice în sistemele de
calcul. În acest sens se poate consulta lucrarea [4] care face o introducere accesibilă în
problematica mai sus menţionată.
Corecţi Incorecţi
număr_de pagini 1_valoare_returnată
_limita1 ok!
ok_ switch..conversie
Standardul ANSI C stipulează că identificatorii pot avea orice lungime. Totuşi, nu toate
caracterele sunt obligatoriu semnificative. Dacă identificatorul este implicat într-un proces
de editare de legături externe, vor conta cel mult şase caractere. Aceşti identificatori,
denumiţi nume externe, includ numele funcţiilor şi ale variabilelor globale care aparţin
mai multor fişiere. Dacă fişierul nu este utilizat într-un proces de editare de legaturi
externe, atunci vor fi semnificative cel mult 31 de caractere. Acest tip de identificator este
denumit nume intern şi include, de exemplu, nume de variabile locale. De remarcat faptul
ca în C++ nu există limite ale lungimii unui identificator şi toate caracterele sunt
semnificative. Această diferenţă începe să conteze în momentul în care doriţi să convertiţi
un program din C în C++.
Pare evident, dar facem precizarea că un identificator C nu poate fi identic cu un
cuvânt-cheie şi nu trebuie să aibă acelaşi nume ca o funcţie din biblioteca C sau
C++.
2.4 Variabile C
Aşa cum s-a aflat şi în alte împrejurări, o variabilă este numele unei locaţii de memorie
utilizată pentru a păstra o valoare care poate fi modificată de program.
10
Înainte de a fi utilizate în program, variabilele trebuie declarate. Declararea unei variabile
în C are forma:
<Tip> <Listă_de_variabile>;
<Tip> trebuie să fie un tip de dată valid (predefinit sau definit de utilizator) precedat,
eventual, de un specificator de conversie.
<Listă_de_variabile> poate consta dintr-un nume de identificator sau mai multe nume de
identificatori separate prin virgulă.
Exemple de declaraţii de variabile:
int nr_pag;
char opt,ch;
unsigned i, j;
“Variabilele locale există cât timp se execută blocul de cod în care sunt declarate”.
Altfel spus, o variabilă locală este creată la începerea execuţiei blocului său şi este
distrusă la încheierea execuţiei acestui bloc.
Blocul de cod cel mai uzual în care se declară variabile este funcţia.
Deoarece noţiunea de funcţie este centrală pentru fizionomia unui program C, prezentăm,
în continuare, o viziune asupra unui program C care include şi noţiunea de funcţie.
Directive preprocesor
Declaraţii globale
Declaraţie funcţie_1
:
Declaraţie funcţie_n
int main()
{
11
Declaraţii de date ale programului principal
Instrucţiuni program principal
Orice program C conţine obligatoriu o funcţie al cărei nume este main
}
Implementare funcţie_1
:
Implementare funcţie_n
<Antet funcţie>
{
<Declaratii de date>
<Instructiuni executabile>
}
void f1(void)
{
int x;
x=12;
}
void f2(void)
{
int x;
12
x=-128;
}
Variabila x este declarată atât în f1 cât şi în f2. Variabila x din f1 nu are nici o influenţă
asupra variabilei x din f2 deoarece fiecare variabilă este cunoscută doar în blocul de cod
gazdă. Din obişnuinţă şi din respect faţă de tradiţie, majoritatea programatorilor declară
variabilele folosite de o funcţie imediat după acolada deschisă a funcţiei şi înainte de orice
instrucţiune executabilă. De fapt, se pot declara variabile în orice bloc de cod,
obţinându-se chiar efecte benefice pentru calitatea codului rezultat. Astfel putem avea:
void fexemplu(void)
{
int i;
printf(”Introduceti un numar:”);
scanf(“%d”,&i);
if (i>0)
{
char nume [40];
printf(”Numele Dvs.:”);
gets(nume);
}
}
Din exemplul de mai sus reţinem doar faptul că, în situaţia în care numărul preluat de la
tastatură (cu ajutorul rutinei de uz general scanf ) este mai mare decât zero, atunci devine
activ blocul de cod în care se declară variabila nume. Această variabilă este distrusă la
ieşirea din acest bloc de cod.
Prin urmare, un avantaj potenţial: variabilei nume i se alocă memorie numai dacă se
activează blocul de cod, ceea ce înseamnă economie de memorie.
Este absolut evident faptul că se declară într-un bloc de cod una sau mai multe variabile
strict necesare în acest bloc de cod; în acest mod prevenim, într-o oarecare măsură,
apariţia unor efecte secundare. Mai semnalăm o diferenţă importantă între modul de
declarare a variabilelor locale în C faţă de C++.
În C trebuie declarate toate variabilele locale la începutul blocului în care intenţionăm să le
definim, înainte de orice instrucţiune executabilă. În C++ nu mai există această restricţie.
Astfel că secvenţa de cod C:
void f(void)
{
int i;
i=10;
int j;
j=20;
}
13
Deoarece variabilele locale sunt create şi distruse la fiecare intrare, respectiv ieşire din
blocul în care au fost declarate, conţinutul lor se pierde odată cu părăsirea blocului. Acest
lucru este important să se ştie când apelăm o funcţie. La apelarea ei sunt create
variabilele locale iar la încheierea ei acestea sunt distruse, deci variabilele locale nu
păstrează valorile lor între apelări. Putem determina compilatorul să păstreze aceste
valori utilizând specificatorul de stocare static asupra căruia vom reveni.
14
Modelatorul volatile informează compilatorul că valoarea unei variabile poate fi
modificată pe căi nedeclarate explicit de program. Aceasta înseamnă că este posibil
următorul scenariu: ”Adresa unei variabile este transmisă unei rutine rezidente în
memoria sistemului, această rutină fiind abilitată să modifice valoarea variabilei”. Evident,
pentru programatori acesta este un mecanism extrem de practic şi interesant. Modelatorul
volatile poate fi combinat cu modelatorul const făcându-se plauzibil scenariul: “Valoarea
unei variabile poate fi modificată de condiţii externe, fiind protejată de modificări
accidentale în programul în care este declarată”.
extern
Deoarece C/C++ permit secţiunilor separate ale unui program să fie compilate
independent şi să li se editeze legăturile împreună, trebuie să existe o modalitate de a
comunica tuturor fişierelor variabilele globale necesare programului. Scenariul căruia
trebuie să îi facem faţă este următorul: “Cum putem informa toate fişierele care compun
un program C despre variabilele globale utilizate?”. Soluţia este următoarea : Se declară
variabilele globale într-un fişier, care este de preferat să conţină şi funcţia principală a
programului, aceleaşi variabile declarându-se şi în toate fişierele care folosesc variabilele
globale, însoţite de specificatorul extern.
static
Variabilele de tip static sunt variabile permanente în interiorul funcţiei sau fişierului în
care se găsesc. Spre deosebire de variabilele globale, ele nu sunt cunoscute în afara
funcţiei sau fişierului, dar îşi păstrează valoarea între două apelări. Această caracteristică
este folositoare la scrierea de funcţii generice şi de bibliotecă , utilizabile de alte
programe. Specificatorul static are efecte diferite asupra variabilelor locale şi globale.
15
Variabile globale statice
Aplicând specificatorul static unei variabile globale, cerem compilatorului să creeze o
variabilă globală care este cunoscută doar în fişierul în care a fost declarată. Aceasta
înseamnă că, deşi variabila este globală, rutine din alte fişiere nu au acces la ea şi nu îi
pot modifica conţinutul.
register
Specificatorul de stocare register se aplică, prin tradiţie, doar variabilelor de tip int şi
char. Totuşi standardul ANSI C îi dă definiţia astfel încât poate fi folosit pentru orice tip de
variabilă. Iniţial, register cerea compilatorului să păstreze valoarea unei variabile într-un
registru din CPU, nu în memoria RAM, unde variabilele sunt stocate, de regulă. Scopul
unei astfel de cereri: spor de viteză în timpul operaţiilor asupra variabilei. Actualmente,
definiţia specificatorului register a fost mult extinsă, acesta putând fi aplicat oricărui tip de
variabilă. Standardul ANSI C stipulează, simplu, că register este o indicaţie dată
compilatorului că obiectul vizat va fi utilizat preferenţial din punct de vedere al vitezei.
Specificatorul se poate aplica doar variabilelor locale şi parametrilor formali. Prin urmare,
nu sunt permise variabile globale de tip register.
auto
Specificatorul auto este, practic, nefolosit, fiind reclamat doar de motive de compatibilitate
între codul C şi codul C++.
Exemple:
char ch=’A’;
int media=0;
float bilant=0;
Variabilele globale şi cele statice locale sunt iniţializate doar la începutul execuţiei
programului. Variabilele locale (cu excepţia celor de tip static, sunt iniţializate de fiecare
dată când este întâlnit blocul în care sunt declarate.
16
Constante C
Constantele se referă la valori fixe pe care programul nu poate să le modifice.
Constantele pot fi de oricare din tipurile fundamentale de date. Modul în care se
reprezintă în program fiecare constantă depinde de tipul său.
Constantele de tip caracter sunt incluse între ghilimele simple. ‘A’ şi ‘%’ sunt exemple de
constante de tip caracter. C defineşte şi caractere multioctet, în mediile care nu utilizează
limba engleză. O situaţie asemănătoare se întâlneşte şi în Delphi.
Constantele de tip întreg sunt specificate ca numere fără parte fracţionară. De exemplu, 3
şi -15 sunt exemple de constante întregi.
Constantele în virgulă mobilă cer punctul zecimal urmat de partea zecimală a numărului.
0.75 este un exemplu de constantă care apelează la virgula mobilă. Limbajul C permite şi
notaţia ştiinţifică, cunoscută şi în alte limbaje.
De asemenea, C stabileşte pentru o constantă numerică cel mai scurt tip de date
compatibil care o poate păstra. Astfel că, -10 este implicit un int, 60.000 este unsigned
int iar 120.000 este un long int. Tipul constantei numerice poate fi specificat şi explicit ca
în exemplele din Tabelul 3.
După cum se vede, constantele în virgulă mobilă sunt asimilate implicit tipului double.
17
C admite, cum era şi firesc şi constanta de tip şir de caractere. O constantă şir este o
succesiune de caractere delimitată de ghilimele. A nu se confunda şirurile de caractere cu
caracterele. ‘a’ şi “a” sunt constante de tipuri diferite( disticţie care, în Pascal, nu este
operată sintactic, vorbind.
Codul Semnificaţia
\b backspace
\f form feed (=Salt la pagină nouă)
\n CR+LF
\r CR
\t tab orizontal
\’’ ghilimele
\’ apostrof
\0 Null
\\ backslash
\v tabulare verticală
\a alertă
\N constantă în octal; N este constanta
\xN constantă în hexazecimal; N este constanta
Tabelul 4 Coduri backslash în C
2.11 Operatori în C
C dispune de foarte mulţi operatori. De fapt, C acordă acestora o importanţă mult mai
mare în comparaţie cu alte limbaje. C defineşte, în esenţă, patru clase de operatori:
aritmetici, relaţionali, logici şi de acţiune la nivel de bit. Pentru anumite sarcini, C are
operatori suplimentari.
Operatorul de atribuire
Operatorul de atribuire poate fi folosit, în C, în cadrul oricărei expresii valide, lucru care nu
este permis în majoritatea limbajelor de programare (inclusiv Pascal), care tratează
operatorul de atribuire ca pe un caz de instrucţiune specială. Sintaxa de aplicare a
operatorului de atribuire în C este:
<Nume_variabilă> = <Expresie>;
18
Conversii de tip la atribuire
Când variabilele de un anumit tip sunt amestecate cu variabile de alt tip, au loc conversii
de tip. Într-o instrucţiune de atribuire, regula de conversie este simplă: valoarea din
membrul drept (al expresiei) al instrucţiunii de atribuire este convertită la tipul din membrul
stâng. Pentru o mai bună înţelegere a “fenomenului”, fie exemplul de mai jos.
int nri;
char car;
float nrr;
void functie(void)
{
car=nri; /* Linia 1 */
nri=nrr; /* Linia 2 */
nrr=car; /* Linia 3 */
nrr=nri; /* Linia 4 */
}
În Linia 1, în variabila car ajung primii 8 biţi cei mai puţin semnificativi ai variabilei nri.
Dacă nri este cuprins între 0 şi 255, car şi nri vor avea valori identice ş.a.m.d..
Presupunând că sistemul de calcul are cuvântul de 16 biţi, Tabelul 5 prezintă posibilele
pierderi de informaţie care pot să apară la conversia de tip în atribuiri.
Atribuiri multiple
C permite atribuirea aceleeaşi valori mai multor variabile prin utilizarea atribuirii multiple
într-o singură instrucţiune de atribuire. De exemplu instrucţiunea:
x = y = z = 0;
19
<Variabilă>=<Variabilă>+<Expresie> (1)
este echivalentă semantic cu sintaxa:
<Variabilă>+=<Expresie> (2)
Deşi s-ar putea să vină “peste mână” unor programatori, aceştia trebuie să ştie că pentru
compilator sintaxa (2) este de preferat sintaxei (1) din punct de vedere al calităţii codului
generat.
Aşadar,
x=x+1
este totuna cu:
x+=1.
Operatori aritmetici
Tabelul 6 prezintă operatorii aritmetici din C.
Operator Acţiune
- Scădere, de asemenea şi minus unar
+ Adunare
* Înmulţire
/ Împărţire
% Modul
-- Decrementare
++ Incrementare
Tabelul 6 Operatori aritmetici în C
20
incrementare şi decrementare (cod mai bun decât cel obţinut prin utilizarea atribuirii
clasice echivalente). Pe acest considerent, recomandarea de a utiliza aceşti operatori de
câte ori este posibil este firească. În sfârşit, ordinea de precedenţă a operatorilor aritmetici
este:
Operatori relaţionali
Operator Acţiune
> Mai mare decât
>= Mai mare sau egal
< Mai mic decât
<= Mai mic sau egal
== Egal
!= Diferit
Operatori logici
Operator Acţiune
&& AND (ŞI)
|| OR (SAU)
! NOT (NEGAT)
Tabelul 7 Operatorii relaţionali şi logici în C.
21
Evident, parantezele pot fi utilizate pentru a modifica ordinea firească de evaluare a unei
expresii relaţionale şi/sau logice.
Operator Acţiune
& AND
| OR
^ OR exclusiv (XOR)
~ Complement faţă de 1 (NOT)
>> Deplasare la dreapta
<< Deplasare la stânga
Tabelul 9 Operatorii de acţiune pentru biţi
<Operand_1> <Operator><Operand_2>
Acţiunea operatorului este aplicată bit cu bit operanzilor, rezultând o configuraţie de biţi
asupra cărora se pot continua alte prelucrări.
Operatorii de deplasare a biţilor >> şi << deplasează toţi biţii dintr-o variabilă la dreapta
sau la stânga. Sintaxa asociată este:
Sensul operaţiei de shiftare este cel precizat la un curs de Bazele logice ale sistemelor
de calcul.
♦
Precizări referitoare la operatorii suplimentari pot fi găsite în [1].
Operatorul ?
C propune un operator unar foarte puternic şi util care poate înlocui anumite instrucţiuni
de forma “dacă – atunci – altfel”.
22
Operatorul ? apare în expresii având forma generală:
Se evaluiază <Expresie_1>;
Dacă <Expresie_1> este adevărată se evaluiază <Expresie_2> şi rezultatul
evaluării ei i se atribuie expresiei globale;
Dacă <Expresie_1> este falsă. atunci se evaluiază <Expresie_3> şi rezultatul
evaluiării acesteia este atribuit expresiei globale.
Această semantică este evidenţiată şi de exemplul de mai jos de utilizare a
operatorului ?.
X=24;
X=X<24 ? 1:X+1;
X=24;
if (X<24) X=1
else X=X+1;
Operatorii & şi *
Un pointer este adresa din memorie a unei variabile. O variabilă de tip pointer este
declarată explicit pentru a reţine un pointer către un obiect de un tip specificat.
Cunoaşterea adresei unei variabile poate fi de mare utilitate în anumite situaţii. În C
pointerii au trei funcţii principale.
O atribuire de tipul :
adr=&Numar_Elemente;
este utilă pentru a obţine în variabila adr adresa din memorie a variabilei
Numar_Elemente. Această adresă este adresa locaţiei din memorie începând de la care
se păstrează conţinutul variabilei Numar_Elemente.
23
Al doilea operator pentru pointeri este * , oarecum complementar operatorului &. Şi
operatorul * este unar, returnând valoarea din variabila localizată la adresa specificată.
Astfel , după execuţia secvenţei:
adr=&Numar_Elemente;
nr=*adr ;
Exemplu
#include <stdio.h>
void main(void)
{
int destinatar,sursa;
int *m;
sursa=10;
m=&sursa;
destinatar=*m;
printf (“%d”, destinatar);
}
sizeof ( <Nume_Variabila>)
sau
24
De remarcat faptul că în cazul unei variabile este permisă şi sintaxa:
sizeof <Nume_Variabila>
struct tangajat
{
char nume[80];
int varsta;
float salariu;
} angajat;
struct tangajat *pangajat=&angajat;
angajat . salariu:=1100000;
pangajat->salariu=1100000;
Exemple de comentarii:
sau
25
clrscr() /* Stergere ecran în mod text */
Acest tip de comentariu nu poate fi imbricat.
Compilatoarele C++ acceptă şi comentarii care încep cu secvenţa //, dar se pot întinde pe
o singură linie, astfel:
26
Instrucţiuni de etichetare
Instrucţiuni bloc
Instrucţiunile de selecţie cuprind enunţurile if şi switch.
În categoria instrucţiunilor iterative intră enunţurile while, for şi do-while.
Instrucţiunile de salt desemnează în C instrucţiunile break, continue, goto şi return.
Instrucţiunile de etichetare includ enunţurile case şi default (implicate, esenţial, în sintaxa
şi semantica instrucţiunii switch şi etichetele (discutate relativ la instrucţiunea goto.
Problematica instrucţiunilor expresie am discutat-o, pe larg, în secţiunea precedentă. De
asemenea, blocul de cod este, deja, o noţiune cu care ne-am familiarizat. De altfel, aşa
cum în Pascal, secvenţa de cod cuprinsă între begin şi end era numită şi instrucţiune
compusă, standardul ANSI C++ propune, aceeaşi denumire, ca denumire alternativă
pentru blocul de cod.
Să atragem atenţia asupra faptului că C++ adaugă elemente sintactice specifice
pentru tratarea excepţiilor de către programator. Nu toate compilatoarele implementează,
însă, acest suport important pentru a realiza aplicaţii robuste şi fiabile în C++.
Propunerea de standard ANSI C++ defineşte un tip de dată boolean numit bool (care
poate să aibă doar valorile adevărat şi fals. Ceea ce nu înseamnă, însă, că nu se
păstrează punctul de vedere al limbajui C relativ la adevăr şi fals.
if
Sintaxa generală a instrucţiunii if este următoarea:
27
<Expresie> trebuie să returneze o valoare scalară (un întreg, un caracter, un pointer sau
un număr real în virgulă mobilă). Un număr real în virgulă mobilă se utilizează “cu
reţinere” pentru a controla o instrucţiune de selecţie deoarece încetineşte semnificativ
execuţia programului. Aceasta deoarece sunt necesare mai multe instrucţiuni pentru a
efectua o operaţie în virgulă mobilă decât pentru a executa operaţii la nivel de caracter
sau cu întregi.
De semnalat faptul că, în situaţia în care una din instrucţiunile asociate condiţiilor true sau
false ale unui if este bloc de instrucţiuni atunci sintaxa corectă este:
if (<Expresie>) {…}
else {…};
#include <stdio.h>
#include <conio.h>
#include <string.h>;
void main()
{
float A,B,X;
clrscr();
gotoxy(20,12);
printf("____________________________________");
gotoxy(20,13);
printf(" Rezolvarea unei ecuatii de gradul I");
28
gotoxy(20,14);
printf(" A=");
gotoxy(20,15);
printf(" B=");
gotoxy(20,17);
printf("____________________________________");
gotoxy(20+strlen(" A="),14);
scanf("%f",&A);
gotoxy(20+strlen(" B="),15);
scanf("%f",&B);
gotoxy(20,17);
printf("_____________________________________");
if (A==0)
if (B==0)
{
gotoxy(20,16);
printf("Ecuatie nedetrminata!!!");
getch();
}
else
{
gotoxy(20,16);
printf("Ecuatie imposibila!!!"); getch();
}
else
{
X=B/A;
gotoxy(20,16);
printf("Solutia ecuatiei este:%f",X); getch();
}
}
if (<Expresie_1>)
<Instrucţiune_1>;
else if (<Expresie_2>)
<Instrucţiune_2>;
else if (<Expresie_3>)
<Instrucţiune_3>;
:
:
else if (<Expresie_k>)
<Instrucţiune_k>;
:
29
Reamintim că, în anumite situaţii, putem utiliza operatorul ? pentru a înlocui instrucţiunea
if-else de forma:
if (<Expresie>)
<Expresie_1>;
else
<Expresie_2>;
cu o construcţie de forma:
<Expresie>?<Expresi_1>:<Expresie_2>;
switch
C permite reprezentarea structurilor alternative cu mai multe ramuri utilizând în acest scop
instrucţiune switch având sintaxa:
switch (<Expresie>) {
case <Constanta_1>:
<Secvenţa_de_instrucţiuni_1>
break;
case <Constanta_2>:
<Secvenţa_de_instrucţiuni_2>
break;
:
case <Constanta_k>:
<Secvenţa_de_instrucţiuni_k>
break;
default
<secvenţa_de_instrucţiuni>
}
30
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
void main()
{
int x;
char Ras;
clrscr();
x=random(2);
switch(x+2){
case 3:
{
gotoxy(20,10);
printf("Switch atins...");
break;
}
default:
{
gotoxy(20,10);
printf("Switch ocolit...");
}
}
Ras=getch();
}
31
for (<Iniţializare>;<Condiţie>;<Increment>) <Instrucţiune>;
O astfel de sintaxă permite multe variante constructive ale buclei for. În general vorbind,
însă:
-<Iniţializare> este o instrucţiune de atribuire utilizată pentru a iniţializa variabila de
control a buclei;
-<Condiţie> este o expresie relaţională care determină condiţia de ieşire din buclă;
-<Increment> defineşte modul în care se modifică variabila de control a buclei de
fiecare dată când aceasta se repetă.
De remarcat faptul că cele trei secţiuni trebuie separate prin punct şi virgulă. Aşadar,
bucla for se execută cât timp <Condiţie> este adevărată. Dacă <Condiţie> devine falsă,
atunci execuţia programului continuă cu instrucţiunea care urmează construcţiei for, dacă
aceasta există.
Maniera clasică de utilizare a buclei for în C, o prezentăm în exemplul de mai jos.
#include <stdio.h>
#include <iostream.h>
#include <conio.h>
long int fact( int n);
void main()
{
clrscr();
gotoxy(20,12);
int nr;
cout<<"Introduceti numarul:";
cin>>nr;
gotoxy(20,14);
cout<<"Factorial("<<nr<<")="<<fact(nr);
getch();
}
long int fact(int n)
{
long int f;
int i;
f=1;
/*-------------------------------------------------------------------
/* Bucla for clasică cu o singură variabilă de control
/*-------------------------------------------------------------------
for (i=2;i<=n;i++)
f=f*i;
return f;
}
Exemplul de cod C prezentat mai sus permite calculul factorialului pentru un număr
natural dat. Exemplul arată, anticipând, modul de utilizare a conceptului de funcţie în C++.
32
Sintaxa C permite variante dintre cele mai neaştepate de definire a unor bucle for.
Rigiditatea buclei for din Pascal este înlocuită în C cu o sintaxă care emană mai multă
putere, flexibilitate şi aplicabilitate în situaţii specifice de programare.
Una dintre cele mai folosite variaţiuni foloseşte operatorul virgulă, pentru a permite ca
bucla să fie controlată de două sau mai multe variabile. De exemplu, variabilele x şi y
controlează următoarea buclă şi amândouă sunt iniţializate în interiorul instrucţiunii for.
:
for (x=0,y=0;x+y<10;++x
{
y+=1; //Această scriere reste echivalentă cu y=y+1;
printf(“Y= %d”,y);
}
:
Prezentăm, mai jos, un exemplu practic de utilizare a unei bucle for controlată de două
variabile: afişarea unui şir de caractere începând de la ambele capete, mergând către
mijlocul şirului. Se observă prezenţa fişierului header <dos.h> în care se află prototipul
funcţiei delay().
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>;
//Functia principala
void main()
{
clrscr();
gotoxy(20,12);
converg(12,"Acesta este un test pentru functia converg().");
}
for (i=ls,j=cs;i<=j;i++,j--)
{
gotoxy(i,linie);printf("%c",mesaj[i-ls]);
delay(50); //prototipul in fisierul antet <dos.h>
gotoxy(j,linie);printf("%c",mesaj[strlen(mesaj)-1-cs+j]);
}
33
getch();
}
for ( ; ; )
#include <stdio.h>
#include <conio.h>
#include <ctype.h> //Contine prototipul functiei toupper
void main()
{
int sw;
char ras;
sw=1;
/* Bucla while în acţiune…
while(sw)
{
gotoxy(20,12);
printf("Continuam(D,N):");
ras=getch();
if (toupper(ras)=='N')
sw=0;
}
}
34
do {
<Instrucţiune>;
} while <Condiţie>;
Bucla do-while se repetă până când <Condiţie> devine falsă. Dăm, mai jos, un exemplu
practic de utilizare a buclei do-while (afişarea/ selectarea opţiunilor unui program C).
:
char prelopt()
{
int c;
clrscr();
gotoxy(20,9);
printf( "Optiunile programului...");
gotoxy(20,11);
printf("1-Preluare elemente vector");
gotoxy(20,12);
printf("2-Determinare suma");
gotoxy(20,13);
printf("3-Terminare program");
gotoxy(20,15);
printf("Optiunea Dvs.:");
/* Utilizare buclă do-while…
do
c=getch();
while ((c!='1')&&(c!='2')&&(c!='3'));
return c;
};
:
Alte instrucţiuni C…
Aşa cum rezultă şi din exemplele prezentate, în C mai sunt intens folosite următoarele
instrucţiuni: return, exit, break , continue şi goto.
Instrucţiunea return este utilizată pentru întoarcerea dintr-o funcţie. Este considerată ca
instrucţiune de salt deoarece determină execuţia programului să revină la prima
instrucţiune după funcţia apelată în care apare return. Forma generală a instrucţiunii
return este:
return [<Expresie>]
Această sintaxă arată că dacă <Expresie> este prezentă atunci rezultatul evaluării
expresiei este valoarea returnată de funcţie. O funcţie void nu trebuie să returneze nimic,
deci return poate să apară fără <Expresie>. Într-o funcţie return poate să apară de câte
ori este necesar. În C++ o funcţie care nu este void trebuie să returneze o valoare.
Funcţia exit()
35
Este utilizată pentru ieşirea imediată dintr-un program, returnând, eventual un cod de
retur. Sintaxa de apel este:
exit (<Cod_de_retur>);
Instrucţiunea break
Are două utilizări. Poate fi folosită, după cum am văzut deja pentru a încheia un case
dintr-o instrucţiune switch, sau pentru a determina încheierea imediată a unei bucle.
Instrucţiunea continue
Forţează trecerea la următoarea iteraţie a unei bucle, determinând ignorarea restului
codului iteraţiei în care se află.
Nu facem menţiuni speciale referitor la instrucţiunea goto.
36
4 Operaţii I/O relativ la perifericele standard
Ne propunem în acest modul să prezentăm funcţiile C care fac parte din sistemul I/O
referitor la efectuarea operaţiilor de introducere a datelor de la tastatură şi afişare a
acestora pe ecranul monitorului.
Funcţia getch() este utilizată pentru a citi un caracter de la tastatură fără ecou pe ecranul
monitorului , analog mecanismului readkey din Pascal.
Funcţia getche() este utilizată pentru a citi un caracter de la tastatură cu ecou pe ecranul
monitorului.
După cum sugerează prototipul, getch() returnează un întreg. Totuşi, funcţia poate
returna această valoare unei variabile de tip caracter deoarece caracterul este conţinut în
octetul de ordin inferior.
Aceeaşi observaţie este valabilă şi pentru funcţia getche().
37
Deşi din alt punct de vedere, şi funcţia putchar() poate fi apelată cu argument de tip
caracter deoarece ieşirea pe ecran se referă, oricum, doar la octetul de ordin inferior.
În cazul apariţiei unei erori, putchar() returnează EOF. Funcţia macro EOF, este definită în
stdio.h şi, în general este egală cu –1. Exemplul pe care îl prezentăm permite citirea de
caractere de la tastatură şi, dacă sunt litere mici, convertirea lor la litere mari. Citirea se
termină în momentul în care se tastează caracterul ESC al cărui cod backslash este \033.
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
void main()
{
char car;
do
{
car=getche();
car=toupper(car);
putchar(car);
}
while (car!='\033');
}
Prototipul funcţiei toupper() se află (aşa cum am mai spus-o) în fişierul antet ctype.h. Se
observă că citirea se face cu ecou pe ecranul monitorului. Este cazul să spunem că
programatorul în C/C++ are de făcut o serie de descoperiri folositoare în ceea ce priveşte
oferta numeroaselor fişiere antet livrate odată cu compilatorul. Prezentarea tuturor
acestor funcţii, fie şi numai prina antet, nu este de loc o treabă uşoară, din punct de
vedere al volumului. De aceea. în acest suport de curs adresăm cititorului invitaţia de a
descoperi, cu ajutorul help-ului on line şi a unor cărţi care nu fac economie de spaţiu,
potenţialul structurat în fişierele antet.
#include <stdio.h>
38
#include <conio.h>
void main()
{
char s[30];
clrscr();
gotoxy(20,10);
printf("Introduceti un sir de caractere:");
gets(s);
gotoxy(20,11);
printf("Sirul introdus :");
puts(s);
getch();
clrscr();
}
În exemplul de mai jos se arată modul de utilizare al funcţiilor gets() şi puts() când
argumentul lui gets() este o variabilă pointer la un şir de caractere.
#include <stdio.h>
#include <conio.h>
void main()
{
char s;
char *p;
clrscr();
gotoxy(20,10);
p=&s
printf("Introduceti un sir de caractere:");
gets(p);
gotoxy(20,11);
printf("Sirul introdus :");
puts(p);
getch();
clrscr();
}
Operaţii I/O cu şiruri de caractere se pot efectua şi cu funcţiile scanf() şi printf(), mult mai
versatile decât gets() şi puts() , după cum vom vedea în continuare.
39
int scanf( const char *format [, address, ...]); {Fişierul antet gazdă :stdio.h}
int printf(const char *format [, argument, ..]); {Fişierul antet gazdă :stdio.h}
printf()
Returnează, cu destinaţia ecran, numărul de caractere scrise sau, dacă apare o eroare, o
valoare negativă. Parametrul format se compune din două tipuri de simboluri. Primul tip îl
formează caracterele care vor fi afişate pe ecran. Al doilea tip se referă la specificatorii de
format cu ajutorul cărora se stabileşte modul în care sunt afişate argumentele care
urmează. Un specificator de format începe cu un semn % şi este urmat de un cod de
format. Trebuie să existe acelaşi număr de argumente ca şi acela al specificatorilor de
format şi, totodată, specificatorii de format şi argumentele se asociază în ordinea de la
stânga la dreapta.
Cod Format
%c Caracter
%d Numere întregi în baza 10, cu semn
%I Numere întregi în baza 10, cu semn
%e Notaţie ştiinţifică (cu litera e)
%E Notaţie ştiinţifică (cu litera E)
%f Număr zecimal în virgulă mobilă
%g Foloseşte %e sau %f , anume, care din ele este mai mic
%G Foloseşte %E sau %f , anume, care din ele este mai mic
%o Număr în octal, fără semn
%s Şir de caractere
%u Numere întregi zecimale fără semn
%x Numere hexazecimale fără semn (cu litere mici)
%X Numere hexazecimale, fără semn, cu litere mari
%p Afişează un pointer
%n Argumentul asociat este un pointer de tip întreg în care a fost
plasat numărul de caractere scrise până atunci.
%% Afişează un semn %
Pentru a înţelege modul deosebit în care acţionează codul %n urmăriţi exemplul de mai
jos.
#include <stdio.h>
#include <conio.h>
void main()
{
40
int numara;
printf("Acesta%n este un test...\n",&numara);
printf("%d",numara);
getch();
}
#include <stdio.h>
#include <conio.h>
void main()
{
int numara;
clrscr();
printf("Acesta%n este un test...\n",&numara);
printf("Aliniere la dreapta.......\n");
printf("%10d\n",numara);
printf("Aliniere la stanga .......\n");
printf("%-d\n",numara);
printf("Completare cu zerouri.....\n");
printf("%010d\n",numara);
getch();
}
scanf()
Este o rutină de uz general pentru intrări de la consolă. Ea poate să citească toate tipurile
de date încorporate şi să facă automat conversia numerelor în format intern corect. Se
aseamănă mult cu complementara ei printf(). Ca funcţie, scanf() returnează numărul de
elemente cărora li s-a atribuit cu succes o valoare. Dacă apare o eroare scanf()
returnează EOF. Argumentul format determină modul în care vor fi citite valorile în
variabilele din lista de argumente. Codurile de format disponibile pentru scanf() sunt
prezentate în tabelul de mai jos.
Cod Format
%c Citeşte un singur caracter
%d Citeşte un număr întreg în baza 10
%I Citeşte un număr întreg în baza 10
%e Citeşte un număr în virgulă mobilă
%E Citeşte un număr în virgulă mobilă
%f Citeşte un număr în virgulă mobilă
%g Citeşte un număr în virgulă mobilă
%o Citeşte un număr în octal
41
%s Citeşte un şir de caractere
%u Numere întregi zecimale fără semn
%x Citeşte un număr în hexazecimal
%p Citeşte un pointer
%n Argumentul asociat este un pointer de tip întreg în care a fost
plasat numărul de caractere citite până atunci.
%u Citeşte un întreg fără semn
%[] Caută un set de caractere
Tabelul 10. O parte din codurile pentru formatarea operaţiilor I/I relativ la periferice
standard
Primul exemplu de cod prezentat mai jos ilustrează ideea de scanset posibilă la utilizarea
funcţiei scanf(). Definirea unui scanset înseamnă, de fapt că citirea corespunzătoare unei
variabile este validată cât timp caracterele citite se potrivesc celor definite în scanset.
Al doilea exemplu este conceput în ideea că valoarea variabilei n2 va fi citită numai după
introducerea de la tastatură a două caractere virgulă. Acest mecanism permite, printre
altele, definirea ca separator la introducerea datelor a altui caracter decât <CR>.
#include <stdio.h>
#include <conio.h>
void main()
{
char sir[3];
clrscr();
#include <stdio.h>
#include <conio.h>
void main()
{
int n1,n2;
clrscr();
scanf("%d,,%d",&n1,&n2);
printf("%d\n",n1);
printf("%d\n",n2);
getch();
}
42
În categoria “alte facilităţi pentru lucrul cu consola” includem, cu prioritate, funcţiile clrscr(),
clreol(), gotoxy(col,lin) ale căror valori de întrebuinţare sunt identice celor din Borland
Pascal. Chiar şi sintaxa funcţiei gotoxy(col,lin) este concordantă în ceea ce priveşte
ordinea parametrilor după care se face poziţionarea cursorului.
Aşadar:
Aceste moduri text pot fi selectate cu ajutorul funcţiei textmode(), al cărei antet se află tot
în conio.h şi face parte tot din arsenalul C de lucru cu ecranul în mod text. Tot în mod text,
putem controla culorile textului şi ale fondului (background-ul). Sintaxa funcţiilor cu care
realizăm controlul culorilor este:
textcolor(<Culoare>);
textbackground(<Culoare>);
Parametrul <Culoare> poate lua una din valorile prezentate în tabelul de mai jos:
43
LIGHTBLUE 9 Nu Da
LIGHTGREEN 10 Nu Da
LIGHTCYAN 11 Nu Da
LIGHTRED 12 Nu Da
LIGHTMAGENTA 13 Nu Da
YYELLOW 14 Nu Da
WHITE 15 Nu Da
BLINK 128 Nu ***
Tabelul 12. Valori admise pentru culoare text si fond. Moduri video text standard
*** Codul BLINK se adaugă la culoarea textului pentru a obţine efectul de blinking.
window(css,lss,cdj,ldj);
Defineşte coordonatele ferestrei text active.
wherex();
Returnează coloana curentă a cursorului.
wherey();
Returnează linia curentă a cursorului.
gettext() şi puttext()
Permit citirea/scrierea memoriei video în mod text, ca în exemplul de mai jos.
#include <conio.h>
44
cprintf("Apasati o tasta pentru a sterge ecranul...");
getch();
//Ştergere ecran
clrscr();
gotoxy(1, 25);
cprintf(" Apasati o tasta pentru a restaura ecranul...");
getch();
gotoxy(1, 25);
cprintf("Apasati o tasta pentru a termina...");
getch();
return 0;
}
#Include “facilcrt.h”
#include <conio.h>
#include <stdio.h>
#include <string.h>
//Activare video-invers
void avideo()
{
textcolor(BLACK);
textbackground(WHITE);
}
//dezactivare video-invers
void dvideo()
{
textcolor(WHITE);
textbackground(BLACK);
}
45
{
int sw;
int col;
sw=(ls>=1) && (ls<=79) && (ld<=80) && (ld>=2) && (ls<ld);
sw=sw && ((ld-ls+1)>=strlen(sir));
if (sw)
{
col=ls+(ld-ls+1-strlen(sir))/2;
gotoxy(col,linia);
cprintf(sir);
}
}
46
modularizare a codului, orientată pe clasificarea tipurilor de capabilităţi de prelucrare în
fişiere speciale numite fişiere antet. Fişierele antet sunt incluse în codurile sursă ale
programelor noastre de câte ori avem nevoie de o capabilitate al cărei prototip se află în
acele fişiere antet.
5 Matrice şi şiruri
O matrice este o colecţie de variabile de acelaşi tip, apelate cu acelaşi nume. Accesul la
un anumit element al matricei se face cu ajutorul unui indice. În C toate matricile constau
în locaţii de memorie contigue. Cel mai mic indice corespunde primului element iar cel
mai mare ultimului element. Matricele pot avea una sau mai multe dimensiuni. Ca şi în
alte limbaje, cea mai simplă matrice este şirul. În C şirul este o matrice de caractere
terminate cu un caracter NULL. Această caracteristică oferă limbajului C mai multă putere
şi eficienţă decât posedă alte limbaje.
Semnalăm, de asemenea, faptul că în C, există o strânsă legătură între matrice şi pointeri.
<Tip> <Nume_variabilă>[<Dimensiune>];
<Tip> declară tipul de bază al matricei, care este tipul fiecărui element al său.
<Dimensiune> indică numărul maximal de elemente pe care le poate conţine
matricea.
Total_octeţi = sizeof(<Tip>)*<Dimensiune>
C nu controlează limitele unei matrice. Puteţi depăşi ambele margini ale unei
matrice şi scrie în alte variabile sau peste codul programului. Riscurile şi
responsabilităţile sunt de partea programatorilor.
47
float sir[10];
float *p;
float sir[10];
p=sir;
<Tip> <Nume_variabilă>[<Dimensiune_1>][<Dimensiune_2>];
int matr[10][20];
48
Adresarea elementelor matricei se bazează pe premiza că, după fiecare dimensiune,
indexarea începe de la 0. De asemenea, facem precizarea că, atunci când o matrice
bidimensională este utilizată ca un argument pentru o funcţie, se transmite doar un pointer
către primul element al matricei. Însă, parametrul care primeşte o matrice bidimensională
trebuie să definească cel puţin numărul de coloane, necesare compilatorului pentru a
indexa corect matricea. De urmărit exemplul de mai jos de cod C care permite calculul
sumei elementelor strict pozitive de pe diagonala principala a unei matrice patratice .
#include<stdio.h>
#include<conio.h>
49
}
<Tip> <Nume_variabilă>[<Dim_1>][<Dim_2>]…[Dim_n];
#include<stdio.h>
#include<conio.h>
#include "facilcrt.h"
#include<stdlib.h>
//Directiva #define permite specificarea unor macrouri
#define lmaxs 100
#define optiuni "1234"
//Solutie modularizata C pentru problema determinrii
//ortogonalitatii a doi vectori reali
char prelopt();
void prelvec();
int ortogon();
float sir1[lmaxs],sir2[lmaxs];
int dims;
void main()
{
for(; ;)
{
switch (prelopt())
{
case '1':
{ prelvec();
break;
}
case '2':
{ ortogon();
break;
};
case '3': exit(0);
}
}
}
char prelopt()
50
{
char opt;
clrscr();
gotoxy(20,9);
cprintf("Optiunile programului....");
gotoxy(20,10);
cprintf("1- Preluare vectori");
gotoxy(20,11);
cprintf("2- Determinare ortogonalitate....");
gotoxy(20,12);
cprintf("3- Terminare program");
makewin2(20,9,55,12);
gotoxy(20,15);
cprintf("Optiunea Dvs.:");
makewin2(20,15,55,15);
do
{
gotoxy(20+strlen("Optiunea Dvs.:"),15);
opt=getch();
}
while (strchr(optiuni,opt)==NULL);
return opt;
}
void prelvec()
{
int i,col;
clrscr();
gotoxy(20,9);
cprintf("Dimensiune vectori:");
makewin2(20,9,20+strlen("Dimensiune vectori:"),9);
gotoxy(22+strlen("Dimensiune vectori:"),9);
col=strlen("Dimensiune vectori:");
makewin2(22+col,9,30+col,9);
gotoxy(23+col,9);
cscanf("%d",&dims);
clrscr();
gotoxy(20,9);
cprintf("Preluarea componentelor primului vector...");
makewin2(20,9,20+strlen("Preluarea componentelor primului vector..."),12);
for (i=0;i<dims;i++)
{
gotoxy(20,10);
cprintf("Elementul [ %d ]=",i);
cscanf("%f",&sir1[i]);
}
51
clrscr();
gotoxy(20,9);
cprintf("Preluarea componentelor celui de-al doilea vector...");
makewin2(20,9,20+strlen("Preluarea componentelor celui de-al doilea vector..."),12);
for (i=0;i<dims;i++)
{
gotoxy(20,10);
cprintf("Elementul [ %d ]=",i);
cscanf("%f",&sir2[i]);
}
}
int ortogon()
{
float suma;
int i;
suma=0;
for (i=0;i<dims;i++)
suma=suma+sir1[i]*sir2[i];
clrscr();
if (suma)
{
gotoxy(20,10);
textcolor(RED+BLINK);
cprintf("Vectorii nu sunt ortogonali!!");
makewin2(20,10,21+strlen("Vectorii nu sunt ortogonali!!"),10);
getch();
textcolor(WHITE);
}
else
{
gotoxy(20,10);
textcolor(GREEN+BLINK);
cprintf("Vectorii sunt ortogonali!!");
makewin2(20,10,21+strlen("Vectorii sunt ortogonali!!"),10);
getch();
textcolor(WHITE);
};
}
52
#define <Nume_macrou> <Secvenţă_de_caracter>
6 Pointeri
Un pointer este o variabilă care poate conţine o adresă de memorie. Această adresă este
localizarea în memorie a unui alt obiect (de regulă o altă variabilă). De exemplu, dacă o
variabilă conţine adresa alteia, despre prima se spune că este un pointer la cea de-a
doua. O variabilă de tip pointer se declară în C astfel:
<Tip> * <Nume>;
53
unde <Tip> este tipul de bază al pointerului iar <Nume> este numele variabilei pointer.
Tipul de bază al pointerului defineşte tipul de variabilă către care indică pointerul. Practic,
orice tip de pointer poate să indice orice în memorie. Problema este, însă, că aritmetica
pointerilor este integral raportată la tipul de bază al pointerului.
Expresii cu pointeri
În general, expresiile care implică pointeri se conformează aceloraşi reguli ca şi celelalte
expresii. Există, totuşi, o serie de particularităţi pe care le vom evidenţia în acest paragraf
în ceea ce priveşte aritmetica pointerilor.
Astfel:
Atribuirile sunt permise între pointeri concordanţi ca tip.
Pointerilor li se poate aplica operatorul ++ (incrementare) ca mai jos
:
int *p;
int mat[10];
:
p=&mat[0];
:
p++; // Operatorul de incrementare aplicat pointerului p
p- -;
:
În urma aplicării operatorului de incrementare pointerului p, care conţinea adresa primului
element al tabloului unidimensional mat, offset-ul acestuia este incrementat cu 2, adică
atât cât este lungimea în octeţi a tipului de bază al pointerului p.
Pointerilor li se poate aplica operatorul - - (decrementare) .
În urma aplicării operatorului de decrementare pointerului p, care conţinea adresa celui
de-al doilea element al tabloului unidimensional mat, offset-ul acestuia este decrementat
cu 2, adică atât cât este lungimea în octeţi a tipului de bază al pointerului p.
De asemenea, putem aduna sau scădea întregi la, sau din pointeri. De exemplu:
:
p=p+10;
:
face ca p să indice al 10-lea element de acelaşi tip cu tipul de bază al lui p, relativ la
elementul curent. În exemplul de mai jos ilustrăm utilitatea aritmeticii pointerilor în
contextul lucrului cu matrice.
#include<stdio.h>
#include<conio.h>
int matr[10][10]; //matrice de intregi bidimensionala
int *p; // pointer la intregi
int dimm;
54
void main()
{
int i,j,el;
clrscr();
gotoxy(20,10);cprintf("Dimensiune matrice :");
scanf("%d",&dimm);
clrscr();
for(i=0;i<dimm;i++)
for(j=0;j<dimm;j++)
{
gotoxy(20,12);
cprintf("Elementul[ %d , %d ]=",i,j);
scanf("%d",&el);
matr[i][j]=el;
};
p=&matr[0][0]; //p refera primul element al matricei
for (i=0;i<dimm;i++)
cprintf("\r\n Ref_poin=%d",*(p+i)); //modificare offset p in expresie
getch();
p=&matr[0][0];
for (i=0;i<dimm;i++)
{
cprintf("\r\n Ref_poin=%d",*(p));
p=p+1; //modificare offset p prin incrementare
}
getch();
p=&matr[0][0];
for (i=0;i<dimm;i++)
{
cprintf("\r\n Ref_poin=%d",*(p));
p++; //aplicare operator de incrementare
}
getch();
}
if (p<q) cprintf (“p indica o memorie de adresa mai mica decat q\n”);
55
timpul execuţiei. După cum se ştie, variabilelor globale li se alocă memorie în timpul
compilării. Variabilele locale folosesc memoria de tip stivă. În mod cert, nici variabilele
globale nici cele locale nu pot fi adăugate în timpul execuţiei programului. Există situaţii în
care necesarul real de memorie este cunoscut de-abia în timpul execuţiei programului.
Chiar dacă C++ acceptă pe deplin sistemul de alocare dinamică al lui C, el îşi
defineşte propriul sistem, care conţine mai multe îmbunătăţiri faţă de cele din C.
Memoria alocată de funcţiile de alocare dinamică din C este obţinută din HEAP- zona de
memorie liberă situată între zona permanentă a memoriei programului şi stivă.
Nucleul sistemului de alocare din C constă din funcţiile malloc() şi free().
Aceste “instrumente” de alocare lucrează în pereche, folosind zona de memorie liberă
pentru a stabili şi a păstra o listă cu memoria disponibilă.
Funcţia malloc() alocă memorie, având următorul prototip:
În acest prototip, numar_de_octeţi este numărul de octeţi din memorie pe care dorim să-l
alocăm programului. Funcţia malloc() returnează un pointer de tipul void, ceea ce
înseamnă că îl puteţi atribui oricărui tip de pointer.
Funcţia free returnează în sistem memoria alocată anterior pointerului p. Este esenţial să
nu se apeleze free cu un argument impropriu; deoarece acest fapt poate aduce prejudicii
gestiunii de către sistem a memoriei libere. Exemplul de mai jos arată modul efectiv de
utilizare a funcţiilor malloc şi free, precum şi modul de utilizare a funcţiei memcpy, în
situaţia în care se doreşte copierea conţinutului memoriei de la o anumită adresă la altă
adresă. De remarcat, totodată, faptul că bibliotecile C au numeroase alte funcţii pentru a
satisface cerinţele de alocare/ manipulare dinamică a memoriei în programele utilizator.
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<string.h>
void main()
{
char c[20];
int *p;
//pointer generic
void *x;
int nr=10000;
p=&nr;
clrscr();
56
gotoxy(20,10);
7 Structuri
O structură este, pragmatic vorbind, un grup de variabile reunite sub acelaşi nume, ceea
ce permite un mod convenabil de manipulare a unor date care au afinităţi semantice între
ele. O declarare de structură formează un şablon care poate fi folosit pentru a crea
structuri efective. În C, variabilele care fac parte din structură se numesc membri ai
structurii. Uzual, membrii structurii se mai numesc şi elemente sau câmpuri. Aşa cum
vom vedea mai jos, o structură poate încapsula şi metode de prelucrare a datelor, dacă
programatorul doreşte să abstractizeze tipuri de date cu suport struct.
Sintaxa generală pentru declararea unei structuri este:
struct <Nume_generic>
{
<Tip> <Nume_membru_1>;
<Tip> <Nume_membru_2>;
:
<Tip> <Nume_membru_n>;
:
<Tip returnat> <Nume functie>(<Lista de parametri>);
57
} <Variabila_1>,[…<Variabila_k>];
În cazul în care în definiţia structurii este încapsulată şi o metodă atunci la implementare
se foloseşte sintaxa:
După cum se vede deja, numele variabilei structurate, urmat de un punct şi numele
membrului permite adresarea acelui membru. Adică, în general vorbind:
<Variabila_structurată> . <Nume_membru>
În codul de mai jos se arată modul concret de declarare a unei structuri şi de asociere a
acestei declaraţii cu un nume de tip prin intermediul cuvântului cheie typedef.
#include<conio.h>
#include<iostream.h>
#include<string.h>
void main()
{
typedef struct TPers{
char nume[30];
float salariu;
58
} TPers;
TPers pers;
strcpy(pers.nume,"Test typedef + struct….");
pers.salariu=1200000;
clrscr();
cout<<pers.nume<<"\r\n"<<pers.salariu;
getch();
}
#include<conio.h>
#include<iostream.h>
#include<string.h>
void main()
{
typedef struct TPers{
char nume[30];
float salariu;
} TPers;
TPers *pers;
#include<stdio.h>
#include<conio.h>
#include<string.h>
59
typedef struct
{
unsigned bit :1;
unsigned :7;
unsigned :7;
unsigned bits:1;
} TOctet;
TOctet Octet;
int nr=1;
int *pointer;
void main()
{
int i;
pointer=&nr;
memcpy(&Octet,pointer,sizeof(int));
clrscr();
printf("Bitul 0 : %i\n",Octet.bit);
printf("Bitul de semn: %i\n",Octet.bits);
getch();
}
60
8 Uniuni
O uniune este o locaţie de memorie care este partajată în momente diferite între două sau
mai multe variabile diferite.
Sintaxa generala şi modul de utilizare pot fi deduse din exemplul de mai jos.
#include<stdio.h>
#include<conio.h>
#include<string.h>
typedef union
{
int codsal;
float salar;
} TUSal;
void main()
{
clrscr();
TUSal vunion;
vunion.codsal=10;
printf("Cod confidential salariu:%i\n",vunion.codsal);
vunion.salar=1000.50;
printf("Salariu :%f",vunion.salar);
getch();
}
61
II Programarea C++
62
1 Introducere
Programarea orientată pe obiecte (POO) este expresia, în materie de codificare a
proiectelor, a paradigmei care presupune modelarea orientată pe obiecte a sistemelor
soft.
POO este o paradigmă care câştigă tot mai mulţi aderenţi datorită calităţilor pe care
le au produsele şi sistemele soft realizate în spiritul conceptelor şi principiilor
promovate de aceasta. Rezultantă a unor direcţii diverse de cercetare şi
experimentare (programare structurată, programare modulară, programare
orientată pe structuri abstracte, reprezentarea cunoştinţelor în sisteme expert, etc.)
POO, aplicată corect poate rezolva, parţial sau integral, multe din problemele
obsedante ale ingineriei softului, în genere: reutilizarea codului,
extinderea/modificarea cu minim de efort a sistemelor soft, ascunderea detaliilor
de implementare faţă de anumite categorii de utilizatori ai sistemelor soft. În acest
mod platforma POO poate ajuta programatorii şi companiile de soft să realizeze
produse şi sisteme soft performante, în timp util şi la un preţ scăzut.
1. 1 Concepte POO
Orice demers de modelare orientată pe obiecte apelează la o serie de concepte specifice
paradigmei POO.
Astfel, pentru modelarea unui sistem soft se operează frecvent cu:
1. Concepte ale teoriei generale a sistemelor (sistem, subsistem, descompunere,
agregare, structură, etc.)
2. Concepte care provin din arsenalul conceptual al modelării în genere a
sistemelor şi produselor soft (modul, modularizare, interfaţă, tip de dată, structură de date,
ascunderea informaţiei, etc.).
Tuturor acestora li se adaugă principiile cu ajutorul cărora aceste concepte devin
operaţionale.
Paradigma POO a intrat în competiţia pentru modernizarea şi îmbunătăţirea reală a
procesului de realizare a unui sistem soft cu un set propriu de concepte şi principii.
Prezentăm mai întâi conceptele cheie ale paradigmei POO.
Conceptul de clasă, prin care se desemnează o colecţie de obiecte (de natură materială
sau spirituală) care au în comun faptul că pot fi caracterizate similar din punct de vedere
informaţional şi comportamental.
Este evident faptul că identificarea unei clase este în mod normal, rezultatul unui demers
cognitiv care presupune caracterizarea unui obiect prin însuşirile lui (informaţionale şi
63
comportamentale) care îi definesc apartenenţa la o anumită clasă de obiecte. Aşadar,
conceptul de clasă adună laolaltă datele şi metodele de prelucrare a acestora.
Interfaţa unui obiect desemnează mulţimea metodelor unei clase cu ajutorul cărora
obiectele clase comunică cu ambianţa în care acestea există. Comunicarea între obiecte
se realizează, dogmatic vorbind, prin mesaje.
64
Este evident faptul că obiectul care emite mesajul trebuie să cunoască protocolul de
comunicaţie al obiectului receptor.
De asemenea, se subânţelege faptul că mesajul trimis de obiectul emiţător va provoca o
reacţie(= un răspuns) din partea obiectului receptor.
1. 2 Principiile POO
Noutatea POO, ca paradigmă este ilustrată şi de principiile pe care le promovează pentru
a completa potenţialul oferit de concepte. Promovarea sistematică a principiilor pe
care le prezentăm în continuare promite, dar nu garantează, realizarea unor
produse, sisteme sau platforme soft remarcabile din punct de vedere al
performanţelor şi al efortului de întreţinere.
65
Se poate spune că moştenirea se referă la mecanismul de a transmite atribute şi operaţii
de-a lungul unei relaţii de generalizare. Evident, simpla moştenire a proprietăţilor unei
clase nu rezolvă nici o problemă din punct de vedere conceptual. Utilitatea principiului
moştenirii este reliefată în condiţiile aplicării operatorului de specializare, prin care
subclasa rafinează (specializează) superclasa.
Implementarea moştenirii nu creează probleme deosebite de înţelegere şi utilizare.
66
<Specificator de acces>:
<Date si functii>
<Specificator de acces>:
<Date si functii>
:
:
<Specificator de acces>:
<Date si functii>
} [<Lista de obiecte>];
<Lista de obiecte> este opţională. Dacă există, ea declară obiecte din acea clasă.
<Specificator de acces> este unul din cuvintele cheie:
-public
-private
-protected.
Implicit, funcţiile şi datele declarate într-o clasă sunt proprii acelei clase, doar membrii săi
având acces la ele.
<Lista_claselor_de_bază> indică, opţional, clasele de la care se pot moşteni
atribute informaţionale şi comportamentale.
Pentru datele şi funcţiile care fac parte din definiţia unei clase se obişnuiesc
şi denumirile de variabile membre, respectiv, funcţii membre sau, pur şi simplu,
membri.
Folosind specificatorul de acces public, permitem funcţiilor sau datelor membre să fie
accesibile altor secţiuni ale programului nostru. Odată utilizat un specificator, efectul său
durează până când se întâlneşte alt specificator de acces sau se ajunge la sfârşitul
declaraţiei clasei. Pentru a reveni la modul privat de declarare a membrilor, se foloseşte
specificatorul de acces private. Specificatorul protected are implicaţii asupra vizibilităţii
membrilor unei clase în cazul în care se face şi moştenire. Prezentăm, în continuare, un
exemplu de definiţie de clasă, care încapsulează date şi operaţii minimale referitoare la un
salariat al unei firme( nume, matricol, salariu).
#include <conio.h>
#include <string.h>
#include <iostream.h>
67
// Implementare functie setfields
void Persoana:: setfields(char *n, int m, float s)
{
strcpy(nume,n);
matricol=m;
salariu=s;
}
pers.setfields("Radu Vasile",1,10000);
pers.afis();
}
#include <iostream.h>
#include <conio.h>
#define SIZE 100
68
int st[SIZE];
int top;
public:
void init();
void push(int i);
int pop();
};
//Functia principala
void main()
{
int i;
stack st1,st2;
69
st1.init();
st2.init();
for (i=0;i<=9;i++)
{
st1.push(i);
st2.push(9-i);
}
clrscr();
for (i=0;i<=9;i++)
{
gotoxy(35,wherey()+1);
cout<<st1.pop()<<"*****";
cout<<st2.pop()<<endl;
}
getch();
}
Concluzionând, dacă în clasa de bază accesul la element este private atunci în clasa
derivată accesul este interzis. În schimb, clasa derivată are acces la elementele clasei de
bază aflate sub incidenţa accesului protected/public.
70
Se mai poate observa, totodată, că dacă la definirea clasei derivate se utilizează
modificatorul de protecţie private, atunci elementele protejate în clasa de bază prin
protected sau public devin protejate private în clasa derivată; deci inaccesibile unor
clase care s-ar deriva eventual din clasa derivată.
Exemplu
#include <iostream.h>
class CB
{
protected:
int i,j;
//Pentru un program care utilizează această clasă
//sau pentru altă clasă care nu se află în relaţie
//de derivare cu CB I,j sunt private din punct de
//vedere al accesului. i,j sunt accesibile unei clase derivate.
public:
void setij(int a, int b) {i=a;j=b}
void dispij() { cout<<i<<” “<<j<<endl;}
}
class CD:public CB
{
int k;
public:
void setk(){k=I*j;}
void dispk(){cout<<k<<endl;}
}
void main()
{
CD ob;
ob.setij(2,3); //OK!
ob.dispij(); //OK
ob.setk();
ob.dispk ();
}
Exemplu
#include <iostream.h>
71
class CB
{
protected:
int i,j;
//Pentru un program care utilizează această clasă
//sau pentru altă clasă care nu se află în relaţie
//de derivare cu CB i, j sunt private din punct de
//vedere al accesului. i,j sunt accesibile unei clase derivate.
public:
void setij(int a, int b) {i=a;j=b}
void dispij() { cout<<i<<” “<<j<<endl;}
}
class CD:protected CB
{
int k;
public:
void setk(){setij(10,20);k=I*j;}
void disptot(){cout<<k<<” “;dispij();}
}
void main()
{
CD ob;
ob.setij(2,3); //ilegal!
ob.setk(); //OK!, membru public în CD
ob.setk();
ob.disptot (); //OK!
ob.dispij(); //ilegal! Membru protected în CD
}
Constructori şi destructori
Este un fapt obişnuit necesitatea ca unele elemente ale unui obiect să fie iniţializate.
Pentru a degreva programatorul de o asemenea sarcină de rutină, compilatorul C++
generează cod care permite obiectelor să se iniţializeze singure. Această iniţializare
automată este efectuată prin intermediul unei funcţii membru speciale a clasei definitoare
numită constructor.
72
Complementul constructorului este destructorul. De multe ori un obiect trebuie să
efectueze anumite acţiuni când este distrus.
Este evident faptul că obiectele locale sunt distruse la părăsirea blocului în care apar iar
obiectele globale la terminarea programului.
Când este distrus un obiect, este apelat destructorul clasei definitoare.
#include<stdio.h>
#include<conio.h>
73
};
};
void main()
{
clrscr();
//Declarare obiect static cu iniţializare;
intreg x=100; x.dispa(); //Declarare obiect static cu iniţializare prin copiere intreg y=x;
y.dispa();}
Evident, o clasă poate avea mai mulţi constructori, ceea ce este tot în beneficiul
programatorilor.
Pentru a înţelege alocarea dinamică a memoriei pentru obiecte trebuie să facem o scurtă
prezentare a problematicii alocării dinamice structurate a memoriei în C++.
<Pointer>=new <Tip>;
delete <Pointer> ;
<Pointer> este o variabilă pointer, compatibilă ca tip cu <Tip>. Aşadar, <Pointer> poate
păstra adresa către zona de memorie în care încap date având tipul <Tip>.
De subliniat că operatorul delete trebuie folosit doar cu un pointer valid, alocat deja prin
utilizarea operatorului new. În caz contrar, rezultatele sunt imprevizibile.
Faţă de malloc()şi free(), operatorii new şi delete prezintă câteva avantaje:
new alocă automat memorie suficientă pentru a păstra obiectele de tipul
specificat. Nu mai este necesară folosirea operatorului sizeof.
new returnează automat un pointer de tipul specificat. Nu mai este necesar să
folosim modelatorii de tip ca în cazul funcţiei malloc().
Atât new cât şi delete pot fi supraîncărcaţi, ceea ce vă permite crearea unui
sistem propriu de alocare dinamică a memoriei. Despre supraîncărcare vom discuta în
paragrafele 2.4 şi 2.7 .
74
În plus, operatorul new mai are şi alte capabilităţi:
Permite iniţializarea memoriei alocate unui tip de bază cu o valoare dată,
utilizând sintaxa:
int *p;
p=new int(10);
Memoria alocată unei matrici de operatorul new se eliberează de către delete apelat cu
sintaxa:
delete [ ] <Pointer>
<Pointer_la_obiect>=new <Clasa>;
<Pointer_la_obiect>=new <Clasa>(<Lista_valori>);
În sfârşit, în cazul matricilor de obiecte, cărora li se poate aloca memorie dinamic, trebuie
să ne asigurăm că în cazul în care există funcţii constructor, una va fi fără parametri. În
caz contrar, compilatorul dă eroare de sintaxă.
#include <iostream.h>
#include <conio.h>
75
#define SIZE 100
//Implementare constructor
stack::stack()
{
top=0;
gotoxy(20,24);
cout<<"Stiva este initializata";
getch();
clrscr();
}
stack::~stack()
{
gotoxy(20,24);
cout<<"Stiva este distrusa!!";
getch();
clrscr();
}
76
{
if(top==0)
{
gotoxy(20,24);
cout<<"Depasire inferioara stiva!!!";
getch();
return 0;
};
top--;
return st[top];
}
//Functia principala
void main()
{
int i;
stack st1,st2;
for (i=0;i<=9;i++)
{
st1.push(i);
st2.push(9-i);
}
clrscr();
for (i=0;i<=9;i++)
{
gotoxy(35,wherey()+1);
cout<<st1.pop()<<"*****";
cout<<st2.pop()<<endl;
}
getch();
}
Funcţii virtuale
O funcţie virtuală este o funcţie declarată virtual în clasa de bază şi redefinită într-un lanţ
de derivare asociat respectivei clase de bază. O funcţie virtuală defineşte o clasă
generală de acţiuni. O redefinire a ei introduce o metodă specifică. În esenţă, o funcţie
virtuală declarată în clasa de bază acţionează ca un substitut pentru păstrarea
elementelor care specifică o clasă generală de acţiuni, stabilind elementele de interfaţă.
Redefinirea unei funcţii virtuale într-o clasă derivată oferă operaţiile efective pe care le
77
execută funcţia. Utilizate static, funcţiile virtuale se comportă ca oricare altă funcţie
membru a clasei . Capabilităţile funcţiilor virtuale ies în evidenţă atunci când sunt apelate
în 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
{
public:
void f()
{
cout<<"CB::f()"<<endl;
getch();
}
virtual void g() //funcţie virtuală
{
cout<<"CB::g()"<<endl;
getch();
}
};
void main()
{
78
CB *pb=new CB;
CD *pd=new CD;
clrscr();
pb->f();
pb->g();
pd->f();
pd->g();
clrscr();
cout<<"Apel polimorfic in context CB"<<endl;
ExecPolim(pb);
cout<<"Apel polimorfic in context CD"<<endl;
ExecPolim(pd);
delete pb;
delete pd;
};
La prima vedere redefinirea unei funcţii virtuale într-o clasă derivată pare similară cu
supraîncărcarea unei funcţii. Nu este aşa, deoarece există mai multe diferenţe:
Prototipul pentru o funcţie virtuală redefinită trebuie să coincidă cu prototipul
specificat în clasa de bază.
Funcţiile virtuale nu pot să fie membri de tip static ai clasei din care fac parte şi nu
pot fi nici friend
Funcţiile obişnuite suportă supraîncărcarea; funcţiile virtuale suportă suprascrierea.
În cele ce urmează facem o serie de precizări relativ la clauza virtual.
79
Sintaxa unei funcţii virtuale pure este:
O clasă care conţine cel puţin o funcţie virtuală pură se numeşte clasă abstractă. O clasă
abstractă nu poate fi utilizată pentru crearea de obiecte dar poate fi utilizată pentru a crea
pointeri şi referinţe spre astfel de clase permiţând astfel manifestările polimorfice în timpul
execuţiei.
#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()
{
clrscr();
cout<<abs(-10);
cout<<abs(-10.0);
cout<<abs(-10L);
getch();
}
int abs(int i)
{
gotoxy(20,10);cout<<"Varianta int !!! :";
return i<0 ?-i:i;
}
double abs(double d)
{
80
gotoxy(20,12);cout<<"Varianta double!!! :";
return d<0 ?-d:d;
}
long abs(long l)
{
gotoxy(20,14);cout<<"Varianta long !!! :";
return l<0 ?-l:l;
}
Acest program implementează şi utilizează trei funcţii cu acelaşi nume (abs()) dar diferite
prin proprietăţile listelor de parametri formali. Fiecare returnează un mesaj specific şi
valoarea absolută a argumentului. Numele abs() reprezintă acţiunea generică care
urmează să fie efectuată, compilatorul fiind acela care alege metoda potrivită tipului de
parametru actual asociat cu numele funcţiei generice. Este uşor de bănuit puternicul
impact al supraîncărcării în cazul programării generice.
81
public:
void setare_stare(int val);
friend int ecran_liber(Clasa1 a,Clasa2 b);
};
class Clasa2
{
//<ocupat> daca o metoda a clasei a scris pe ecran
//<liber> in caz contrar
int stare;
public:
void setare_stare(int val);
friend int ecran_liber(Clasa1 a,Clasa2 b);
};
void main()
{
Clasa1 x;
Clasa2 y;
x.setare_stare(ocupat);
y.setare_stare(liber);
if (ecran_liber(x,y))
{
clrscr();
gotoxy(20,12);
cout<<"Ecranul este liber...";
getch();
}
else
82
{
clrscr();
gotoxy(20,12);
cout<<"Ecranul este ocupat...";
getch();
};
x.setare_stare(liber);
y.setare_stare(liber);
if (ecran_liber(x,y))
{
clrscr();
gotoxy(20,12);
cout<<"Ecranul este liber...";
getch();
}
else
{
clrscr();
gotoxy(20,12);
cout<<"Ecranul este ocupat...";
getch();
};
}
2.7 Supraîncărcarea operatorilor
Polimorfismul este realizat în C++ şi prin supraîncărcarea operatorilor. După cum s-a
văzut în numeroase exemple, în C++ se pot folosi operatorii >> şi << pentru a efectua
operaţii I/O relativ la consolă. Aceşti operatori pot efectua aceste operaţii suplimentare
(ştiut fiind faptul că pot funcţiona şi ca operatorii de shiftare la nivel de biţi) deoarece
operaţiile sunt supraîncărcate în fişierul antet IOSTREAM.H. Când un operator este
supraîncărcat, el capătă o semnificaţie suplimentară relativ la o anumită clasă fără să-şi
piardă vreunul din înţelesurile iniţiale. Majoritatea operatorilor din C++ pot fi
supraîncărcaţi, stabilind semnificaţia lor relativ la o anumită clasă.
Limbajul C++ permite supraîncărcarea numai a operatorilor existenţi în limbaj. Dintre
aceştia nu pot fi supraîncărcaţi operatorii: . :: ? : .
Să mai precizăm faptul că, prin supraîncărcarea operatorilor nu se poate schimba
n-aritatea, prioritatea sau asociativitatea operatorilor, acestea fiind elemente
predefinite pentru tipuri predefinite şi deci ele se vor menţine şi pentru tipuri abstracte.
Prin n-aritate înţelegem că operatorul este unar sau binar.
Supraîncărcarea operatorilor se realizează cu ajutorul unor funcţii membre sau prietene
speciale. Specificul acestora se află în numele lor. El se compune din cuvântul cheie
operator şi unul sau mai multe caractere care definesc operatorul care se supraîncarcă.
Între cuvăntul cheie operator şi caracterele care definesc operatorul care se
supraîncarcă se află cel puţin un spaţiu alb. Felul în care sunt scrise funcţiile operator
diferă pentru cele de tip membru de cele de tip friend.
83
Funcţiile operator membru au sintaxa de implementare:
//Operaţii specifice
Deseori funcţiile operator returnează un obiect din clasa asupra căreia operează, dar
<Tip_returnat> poate fi orice tip valid.
# este o notaţie pentru numele operatorului aşa cum va fi folosit in program după
redefinire. Deci, dacă supraîncărcăm operatorul = atunci sintaxa prototipului funcţiei
membru va fi:
Funcţiile operator membre au un singur parametru sau nici unul. În cazul în care au un
parametru, acesta se referă la operandul din dreapta al operatorului.
Celălalt operand ajunge la operator prin intermediul pointerului special this.
Astfel stând lucrurile, trebuie să avem grijă de eventualitatea ca operandul din stânga să
fie o constantă, ceea ce înseamnă ca nu mai avem context de apel pentru operatorul
redefinit.
Inivităm cititorul să urmărească exemplul de mai jos.
#include<conio.h>
#include<iostream.h>
84
void disp_nc();
//prototipul operatorului +
complex operator+(complex &op2);
};
void complex::disp_nc()
{
cout<<x<<"+i*"<<y;
};
//Implementare operator +
//Aceasta sintaxa transforma apelul <ob1+ob2>
//in +(ob2) ob1 fiind accesibil prin pointerul
//special <this>
complex complex::operator+(complex &op2)
{
complex temp;
temp.x=op2.x+x;
temp.y=op2.y+y;
return temp;
};
void main()
{
complex tamp1,tamp2;
complex *pod1,*pod2;
complex ob1(10,10),ob2(11,11);
clrscr();
gotoxy(20,10);cout<<"Primul numar complex ->:";
ob1.disp_nc();
getch();
gotoxy(20,11);cout<<"Al doilea numar complex->:";
ob2.disp_nc();
getch();
ob1=ob1+ob2;
gotoxy(20,13);cout<<"Suma numerelor complexe->:";
ob1.disp_nc();
getch();
pod1=new complex(200,200);
pod2=new complex(300,300);
tamp1=*pod1;
clrscr();
gotoxy(20,10);cout<<"Al treilea numar complex ->:";
85
tamp1.disp_nc();
tamp2=*pod2;
gotoxy(20,11);cout<<"Al patrulea numar complex ->:";
tamp2.disp_nc();
#include…
declaraţii clase de bază
declaraţii clase derivate
prototipuri de funcţii nemembre
main()
{
:
}
definiţii de funcţii ne-membre
86
3 Bazele sistemului I/O în C++
Pe lângă faptul că permite sistemul de I/O din C, C++ defineşte propriul său sistem I/O
orientat pe obiecte. Ca şi sistemul de I/O din C, cel din C++ este complet integrat. Aceasta
înseamnă că diferitele tipuri de operaţii I/O sunt doar perspective diferite ale aceluiaşi
mecanism. Această perspectivă integratoare asupra operaţiilor I/O are la bază, atât în C
cât şi în C++, conceptul de flux (stream).
87
exemplu, un caracter de linie nouă poate fi înlocuit cu perechea început de rând-linie
nouă. Acesta este motivul pentru care nu există o relaţie biunivocă între caracterele care
sunt scrise sau citite şi cele de la echipamentul extern.
Fluxuri binare
Un flux binar este o secvenţă de octeţi într-o corespondenţă biunivocă cu cei de la
echipamentul extern.
Fişiere
În C/C++ un fişier poate să fie: un fişier de pe disc, tastatura, ecranul monitorului,
imprimanta,etc. Un flux se asociază cu un anumit fişier efectuând o operaţie de
deschidere. Odată deschis fişierul, este posibil schimbul de date între el şi programul
utilizator care l-a deschis.
De observat faptul, trivial pentru cunoscători, că nu toate fişierele au aceleaşi posibilităţi.
De exemplu, un fişier de pe disc poate să admită un acces aleator la datele stocate în el,
în timp ce imprimanta nu o poate face. Astfel că, putem concluziona, pentru claritate:
Pentru sistemul I/O din C/C++ toate fluxurile sunt la fel dar nu şi fişierele.
88
Flux Semnificaţie Echipament implicit
cin Intrare standard Tastatura
cout Ieşire standard Ecran
cerr Ieşire standard pentru eroare Ecran
clog Versiune cu memorie tampon pentru cerr Ecran
Tabelul 15. Fluxurile predefinite C++
Fluxurile cin, cout, cerr corespund fluxurilor stdin, stdout, stderr din C. Implicit, fluxurile
standard sunt folosite pentru a comunica cu consola. Însă, în mediile care admit
redirecţionarea I/O, fluxurile standard pot fi redirecţionate spre alte echipamente sau
fişiere.
89
setbase(int baza) cout<<setbase(10); Stabileşte baza de
cin>>setbase(8); conversie la argumentul
întreg (trebuie să fie 0,8,10
sau 16). Valoarea 0 este
baza implicită.
setfill(int ch) cout<<setfill(‘.’); Stabileşte caracterul folosit
cin>>setfill(‘ ‘); pentru completarea
câmpurilor de mărime
specificată
setiosflags(long f) cout<<setiosflags(ios::dec); Stabileşte biţii de
cin>> setiosflags(ios::hex); formatare specificaţi de
argumentul întreg de tip
long
setprecision(int p) cout<<setprecision(6); Stabileşte precizia
cin>>setprecision(10); conversiei în virgulă mobilă
la numărul specificat de
zecimale
setw(int w) cout<<setw(6)<<var; Stabileşte mărimea unui
cin>>setw(24)>>buf câmp la numărul specificat
de caractere
ws cin ws; Elimină spaţiile libere din
fluxul de intrare
Tabelul 16. Manipulatori de formatare a operaţiilor I/O în C++
90
Notaţia ios :: <Nume_indicator> este folosită pentru a identifica indicatorul ca pe un
membru al clasei ios.
Exemplificăm cele spuse mai sus prin codul C++ de mai jos.
#include<iostream.h>
#include<iomanip.h>
#include<conio.h>
#include<stdio.h>
void main()
{
double nr;
clrscr();
gotoxy(10,6);
nr=7./3.;
gotoxy(5,6);
cout<<"Afisare numar in virgula mobila/ format implicit...";
gotoxy(10,7);
cout<<nr;
gotoxy(5,9);
cout<<"Afisare numar in virgula mobila/ cu precizia specificata...";
gotoxy(10,10);
cout<<setprecision(10)<<nr;
gotoxy(5,12);
cout<<"Afisare numar in virgula mobila/ format virgula fixa...";
gotoxy(10,13);
cout.setf(ios::fixed);
cout<<setprecision(10)<<nr;
gotoxy(5,15);
cout<<"Afisare numar in virgula mobila/ format virgula fixa...";
gotoxy(10,16);
cout.setf(ios::scientific);
cout<<setprecision(10)<<nr;
gotoxy(5,18);
cout<<"Afisare numar in virgula mobila/ format virgula fixa...";
gotoxy(10,19);
cout.setf(ios::scientific|ios::showpos);
cout<<setprecision(10)<<nr;
getch();
}
91
Chiar dacă abordarea operaţiilor I/O din C++ formează un sistem integrat, operaţiile cu
fişiere (altele decât cele predefinite), sunt suficient de specializate pentru a fi necesar să
la discutăm separat.
Pentru a efectua operaţii I/O cu fişiere conform paradigmei C++, trebuie să includeţi în
programul Dvs. fişierul antet FSTREAM.H. Acesta defineşte mai multe clase, printre care
ifstream, ofstream şi fstream. Aceste clase sunt derivate din istream şi, respectiv, din
ostream la care ne-am referit şi mai sus.
nume_fisier este un nume extern de fişier care poate include şi specificarea căii de
acces.
Valoarea parametrului mod determină modul de deschidere a fişierului. Parametrul mod
poate avea una sau mai multe din valorile prezentate în tabelul de mai jos.
Puteţi specifica mai mult de un mod de lucru pentru un fişier, folosind operatorul pe biţi
SAU cu modurile respective. De exemplu, pentru deschiderea unui fişier pentru ieşire şi
poziţionarea pointerului la sfârşitul lui se folosesc modurile ios::out şi ios::ate astfel:
92
ceea ce ne arată al doilea procedeu de deschidere a unui fişier, utilizând constructorul
clasei ofstream sau, de ce nu, ifstream, dacă este cazul.
Pentru a închide un fişier, folosiţi funcţia membru close(). Această funcţie nu preia nici un
parametru şi nu returnează nici o valoare. De analizat utilizarea funcţiei close() în
exemplele care vor urma.
#include <fstream.h>
#include <stdlib.h>
#include<conio.h>
char c;
clrscr();
while (in.get(c))
{
if (wherey()>20)
{
gotoxy(20,24);
cout<<"Press any key to continue...";
getch();
clrscr();
}
cout<<c;
93
}
in.close();
}
Funcţia get() citeşte un singur caracter din streamul asociat şi memorează valoarea sa în
ch. De asemenea, se mai observă că funcţia returnează o referinţă către flux. Funcţia
put() scrie ch în flux şi returnează fluxului o referinţă.
Ca un exemplu de utilizare a funcţiilor get() şi put() prezentăm codul de mai jos, care
realizează copierea unui fişier în altul. Dacă numele executabilului asociat acestui cod
este copyf, atunci sintaxa de lansare în execuţie a programului este:
#include<stdlib.h>
#include <fstream.h>
void main(int argc, char *argv)
{
//Verific daca sunt suficiente argumente
if(argc<3)
{
cerr<<"Mod de utilizare : filecopy <fisier sursa> <fisier destinatie>\n";
exit(0);
}
//Deschide fisierul de intrare si il conecteaza la fluxul ins
ifstream ins(argv[1]);
if(!ins)
{
cerr<<"Nu pot deschide fisierul sursa:"<<argv[1];
exit(1);
}
//Deschide fisierul de iesire si il conecteaza la fluxul outs
ofstream outs(argv[2]);
if(!outs)
{
cerr<<"Nu pot deschide fisierul sursa:"<<argv[2];
exit(1);
}
94
//Citeste din sursa si scrie in destinatie
char c;
while(ins.get(c) && outs) outs.put(c);
}
A doua modalitate de a citi şi scrie blocuri de date în binar este folosirea funcţiilor din C++
read() şi write(). Prototipurile lor sunt:
Funcţia read() citeşte numar octeţi din fluxul asociat şi îl pune în buffer-ul indicat de buf .
Funcţia write() scrie în fluxul asociat numar octeţi citiţi din buffer-ul spre care indică buf.
Detectarea EOF
Puteţi să detectaţi sfârşitul fişierului folosind funcţia membru eof() ,care are acest prototip:
int eof();
Ea returnează o valoare nenulă când a fost atins sfârşitul fişierului; altfel, returnează zero.
Utilizarea funcţiei eof() şi alte elemente legate de lucrul cu fişiere, prezentăm în codul de
mai jos, care realizează afişarea conţinutului unui fişier atât în hexazecimal cât şi în cod
ASCII, atunci când codul asociat este printabil.
#include <fstream.h>
#include <ctype.h>
#include <iomanip.h>
#include <stdlib.h>
#include<conio.h>
void main(int argc,char *argv[])
{
if (argc!=2)
{
cerr<<"Mod de utilizare : lishex <nume fisier CPP>\n";
exit(0);
}
ifstream in(argv[1],ios::in|ios::binary);
if (!in)
{
cerr<<"Fisierul nu poate fi deschis!";
exit(0);
}
register int i,j;
int count=0;
char c[16];
cout.setf(ios::uppercase);
clrscr();
while(!in.eof())
95
{
for(i=0;i<16 && !in.eof();i++)
{
in.get(c[i]);
}
if (i<16) i--;
for(j=0;j<i;j++)
cout<<setw(3)<<hex<<(int) c[j];
for(;j<16;j++)
cout<<"\t";
for(j=0;j<i;j++)
if(isprint(c[j])) cout<<c[j];
else cout<<".";
cout<<endl;
count++;
if(count==16)
{
count=0;
cout<<"Press ENTER to continue!";
getch();
clrscr();
cout<<endl;
}
}
in.close();
}
streamoff este un tip definit în IOSTREAM.H, capabil să conţină cea mai mare valoare
validă pe care o poate avea offset, iar seek_dir este o enumerare care are aceste valori:
ios::beg
ios::cur
ios::end
Sistemul de I/O din C++ operează cu doi pointeri asociaţi unui fişier. Unul este pointerul
de get , care specifică unde va apărea următoarea operaţie de intrare în fişier. Celălalt
este pointerul de put şi specifică unde va avea loc următoarea operaţie de ieşire. După
fiecare operaţie de intrare sau ieşire, pointerul corespunzător este avansat automat,
secvenţial. Dar, folosirea funcţiilor seekg() şi seekp() permite un acces nesecvenţial la
datele din fişier.
96
seekg() şi seekp() deplasează pointerul de înregistrare cu offset octeţi faţă de origine.
Cu menţiunea că lucrul cu fişiere în C++ are nenumărate alte faţete pentru a căror
prezentare nu dispunem de timpul şi spaţiul necesar, încheiem această scurtă excursie în
problematica fişierelor. În fine, pentru curioşi facem şi precizarea că, însuşi bătrânul C are
propria filozofie, extrem de puternică, în ceea ce priveşte lucrul cu fişiere.
97
În programarea profesională apar nenumărate situaţii în care reutilizarea codului
presupune o soluţie de un anumit tip pentru o problemă dată. Situaţia la care ne referim în
această secţiune este, potenţial vorbind, următoarea: Ce putem face pentru a
comprima codul sursă în situaţia în care structuri de date, diferite ca tip, suportă
prelucrări similare.
Soluţia acestei probleme de stil de programare o reprezintă programarea generică.
Exprimându-ne în termenii limbajului C, o funcţie generică defineşte un set general de
operaţii care vor fi aplicate unor tipuri de date diferite.
Ca un exemplu, o soluţie generică pentru modelarea unei stive este un pretext ideal
pentru precizarea ideilor principale ale programării generice.
Altfel spus, dacă dorim o stivă, în care, de la caz la caz, să putem păstra numere întregi,
numere reale sau şiruri de caractere (deci tipuri de date diferite), operaţiile fiind aceleaşi (
push() şi pop() ), este clar că ne aflăm în situaţia în care avem nevoie de suport pentru
scrierea de cod cu proprietăţi generice.
Dacă în Pascal programarea generică se baza pe tipuri procedurale şi programarea la
nivel de octet, în C++ există suport evoluat pentru programare generică, sub forma
şabloanelor. Cu un şablon, în C++ se poate crea o funcţie generică sau o clasă generică.
Funcţii TEMPLATE
O funcţie template este o funcţie şablon, având unul sau mai mulţi parametri formali de un
tip generic. În funcţie de nevoile de utilizare a acestei funcţii, compilatorul generează
funcţii propriu-zise, înlocuind tipul generic cu un tip concret. Tipul concret poate fi orice tip
fundamental, derivat sau clasă predefinită.
Considerăm un exemplu. Fie funcţia max(x,y) care returnează valoarea maximă a
argumentelor sale. Tipul variabilelor x şi y trebuie, obligatoriu, specificat în momentul
compilării. Soluţia clasică constă în redefinirea (over-loading) funcţiei max pentru fiecare
tip al argumentelor x şi y (de observat şi cele spuse la paragraful 2.2 relativ la funcţii
supraîncărcate în C++).
Trebuie, aşadar, să definim mai multe versiuni ale funcţiei max.
Mecanismul template permite definirea o singură dată a şablonului de funcţii, după care
se generează automat funcţiile propriu-zise în concordanţă cu necesităţile de utilizare, dar
,evident, în faza de compilare.
98
Nume_şablon
definiţie_şablon
#include <conio.h>
#include<iostream.h>
clrscr();
i=10;
j=2;
k=13;
l=-7;
99
getch();
}
Prezentăm, totodată, un exemplu de funcţie generică pentru compararea unor date după
valoarea unei chei încapsulate în aceste date.
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <ctype.h>
void main()
{
clrscr();
int i=0;
//***************************************
//Citire persoane de la tastatura
do
{
gotoxy(20,12);
cout<<"Numele persoanei:";clreol();
cin>>pers[i].np;
gotoxy(20,13);
100
cout<<"Matricola:";clreol();
cin>>pers[i].key;
gotoxy(20,14);
cout<<"Mai aveti(D,N):";
i++;
}
//***************************************
//Listare persoane pe ecranul monitorului
//in ordinea citirii de la tastatura
while(toupper(getch())!='N');
clrscr();
cout<<"Listare persoane in ordinea citirii de la tastatura..."<<endl;
cout<<"_______________________________________________"<<endl;
for(int j=0;j<i;j++)
{
if (wherey()>10)
{
cout<<"______________________________________________"<<endl;
cout<<"Pentru continuare apasati o tasta..."<<endl;
getch();
clrscr();
cout<<"Listare persoane in ordinea citirii de la tastatura..."<<endl;
cout<<"_______________________________________________"<<endl;
};
cout.width(30);cout.setf(ios::left);
cout<<pers[j].np<<" "<<pers[j].key<<endl;
};
getch();
//*************************************
//Sortare persoane
int sortat;
do
{
sortat=1;
for(int j=0;j<i-1;j++)
{
switch(comp(pers[j],pers[j+1]))
{
case 1: {
tam=pers[j];
pers[j]=pers[j+1];
pers[j+1]=tam;
sortat=0;
};
};
};
101
}
while(!sortat);
//****************************************
//Listare persoane dupa sortare in ordinea
//crescatoare a matricolelor
clrscr();
cout<<"Listare persoane dupa sortare........................."<<endl;
cout<<"____________________________________________"<<endl;
for(int k=0;k<i;k++)
{
if (wherey()>10)
{
cout<<"_____________________________________________"<<endl;
cout<<"Pentru continuare apasati o tasta..."<<endl;
getch();
clrscr();
cout<<"Listare persoane dupa sortare........................."<<endl;
cout<<"______________________________________________"<<endl;
};
cout.width(30);cout.setf(ios::left);
cout<<pers[k].np<<" "<<pers[k].key<<endl;
};
getch();
}
Clase TEMPLATE
O clasă template defineşte un şablon pe baza căruia se pot genera clase propriu-zise. Din
acest motiv, o clasă template se mai numeşte şi clasă generică , clasă generator sau
metaclasă.
Astfel că, o clasă template devine o clasă de clase, reprezentând cel mai înalt
nivel de abstractizare admis de programarea obiect orientată.
În cadrul clasei şablon se pot declara atribute informaţionale de un tip ambiguu, care sunt
particularizate în cadrul clasei generată pe baza şablonului. Evident, şi în acest caz,
generarea se face în faza de compilare în concordanţă cu cerinţele clientului.
Sintaxa la specificarea clasei:
102
template <class T> Tip returnat nume_clasa <T>::nume_funcţie(…)
{
:
};
#include <iostream.h>
#include <stdlib.h>
#include <conio.h>
//***************************************
//Definire clasa matrice generica
template <class ATip>
class genmat
{
ATip a[SIZE];
public:
genmat();
ATip &operator [ ](int i); //Supraîncărcare operator [ ]
};
//***************************************
//Implementare constructor clasa generica
template <class ATip> genmat<ATip>::genmat()
{
register int i;
for(i=0;i<SIZE;i++)
a[i]=i;
};
//***************************************
//Implementare supraincarcare operator [ ]
template <class ATip> ATip &genmat<ATip>::operator[ ](int i)
{
if(i<0 ||i>SIZE-1)
{
cerr<<"Valoare indice eronata...";
getch();
exit(1);
};
return a[i];
};
//***************************************
103
//Functia principala
void main()
{
genmat<int> intob;
genmat<double> doubob;
int i;
clrscr();
cout<<"Matrice de intregi..."<<endl;
for(i=0;i<SIZE;i++)
intob[i]=i;
for(i=0;i<SIZE;i++)
cout<<intob[i]<<endl;
getch();
clrscr();
cout<<"Matrice de reali dubla precizie..."<<endl;
for(i=0;i<SIZE;i++)
doubob[i]=(double)i/3;
for(i=0;i<SIZE;i++)
cout<<doubob[i]<<endl;
getch();
clrscr();
intob[100]=100;
};
104
};
T pop()
{
return *-p
}
}
După ce o astfel de clasă template a fost declarată şi definită, se poate trece deja la
instanţierea de obiecte. Singura deosebire faţă de folosirea unei clase obişnuite constă în
faptul că trebuie specificat tipul concret care înlocuieşte tipul generic T. Ca un exemplu,
să instanţiem un obiect stivă (CStack) , în care încap maximum 100 elemente de tip char.
CStack<char> sc(100);
În acest caz, compilatorul generează clasa ce rezultă prin înlocuirea lui T cu char, după
care instanţiază obiectul sc. Constructorul acestuia primeşte ca argument valoarea 100.
Pentru mai multe detalii urmăriţi exemplul de mai jos.
#include <iostream.h>
#include <conio.h>
~CStack() //destructor
{
delete [ ] v;
}
T pop()
105
{
return *--top; //Notatie prescurtata echibvalenta cu - -top; return *top
}
};
//Functia principala
void main()
{
int i;
//Primul exemplu de instantiere a stivei generice ;numere întregi
CStack<int> st1(20);
//Încarcare stiva
for (i=0;i<=9;i++)
{
st1.push(i);
}
//Încarcare stiva
for (i=65;i<75;i++)
{
st2.push((char) i);
}
106
4 Tratarea excepţiilor
Programatorii adevăraţi trebuie să ia în calcul şi posibilitatea de a crea programe robuste,
care fac faţă atât cerinţelor specificate dar nerafinate suficient, cât şi cerinţelor
nespecificate dar formulate de utilizator, din diverse motive. Programele care au aceste
calităţi se numesc robuste.
În programarea clasică soluţia acestei probleme se putea numi, destul de exact spus,
programare defensivă. Seamănă puţin cu conducerea preventivă din şoferie dacă ne
gândim că programând defensiv, în fond punem răul înainte, deci nu ne bazăm pe
cumsecădenia şi buna pregătire a utilizatorului.
Pentru a face faţă cerinţelor legate de problema tratării excepţiilor (aşa se numesc în
jargon profesional erorile care apar în timpul execuţiei programelor) anumite limbaje de
programare oferă suport adecvat. Aş include aici limbaje precum Delphi, C++, Java,
Visual C.
Nu toate compilatoarele de C++ oferă suport, dar standardul ANSI C++ cere acest lucru în
mod explicit. Compilatoarele din familia Borland incepând cu versiunea 4.0 oferă acest
suport.
Esenţialul din punctul de vedere al programatorului C++ este ca el să-şi formeze abilitatea
de a scrie în jurul aplicaţiilor cod C++ care îndeplineşte funcţia de handler de excepţii.
Suportul sintactic C++ pentru tratarea excepţiilor se rezumă la trei cuvinte cheie, a căror
semantică preliminară o prezentăm în Tabelul xxxxx.
107
Cuvântul cheie Semnificaţie
try Delimitează o porţiune de cod în care se instituie
controlul sistemului asupra excepţiilor în timpul rulării.
throw Lansează o excepţie de un anumit tip
catch Captează o excepţie lansată
Tabelul 18 Cuvintele cheie ale limbajului C++ referitoare la tratarea excepţiilor
try{
//blocul try
//if(eroare) throw valoare_excepţie;
}
catch (Tip_excepţie Nume_variabilă ){
//Prelucrarea excepţiei
}
În cadrul acestei forme generalizate, valoarea valoare_excepţie lansată trebuie să
corespundă tipului Tip_excepţie .
#include <iostream.h>
void main()
{
cout<<"Start"<<endl;
try
{
cout<<"In interiorul blocului try…"<<endl;
throw 100;
cout<<"Nu se va executa…";
}
catch (int i)
{
cout<<"Am captat o excepţie --valoarea este:";
cout<<i <<endl;
}
cout<<"Sfarsit…";
};
108
Programul de mai sus implementează un bloc try-catch simplu. În loc să se aştepte ca
programul să eşueze datorită unei erori, se utilizează instrucţiunea throw pentru lansarea
erorii prezumtive. După ce blocul try lansează eroarea, blocul catch o captează şi
prelucrează valoarea transmisă de instrucţiunea throw. Este evident şi din acest exemplu
că mecanismul try-throw-catch oferă suport pentru rezolvarea problemei tratării
excepţiilor dar nu rezolvă de la sine această problemă. Altfel spus, tratarea corectă a
excepţiilor unui program este o problemă de atitudine ca proiectant şi ca
programator.
#include <iostream>
void XHandler(int test)
{
cout<<"Inauntrul functiei XHandler, test are
valoarea:"<<test<<endl;
if(test) throw test;
};
void main()
{
cout<<"Start:"<<endl;
try
{
cout<<"Inauntrul blocului try…"<<endl;
XHandler(1);
XHandler(2);
XHandler(0);
}
catch(int i)
{
cout<<"Am captat o exceptie. Valoarea este:";
cout<<i<<endl;
};
cout<<"Sfarsit";
};
109
Atunci când se plasează un bloc try într-o funcţie C++ reiniţializează blocul de fiecare dată
când intraţi se intră în acea funcţie. Programul următor ilustrează cele spuse.
#include <iostream.h>
void XHandler(int test)
{
try
{
if(test) throw test;
}
catch(int i)
{
cout<<"Am captat exceptia nr.: "<<i<<endl;
}
};
void main()
{
cout<<"Start: "<<endl;
XHandler(1);
XHandler(2);
XHandler(0);
XHandler(3);
cout<< "Sfarsit";
};
try
{
//instrucţiuni
}
catch (tip1)
{
//tratare excepţie 1
}
catch(tip2)
{
//tratare excepţie 2
}
110
:
catch(tipn)
{
//tratare excepţie n
}
Cu acest amendament sintactic deducem că instrucţiunile catch pot capta orice tip
returnat, nu numai tipurile de bază acceptate de C++. Acest "fenomen" este ilustrat în
codul de mai jos.
#include <iostream.h>
void XHandler(int test)
{
try
{
if(test==0)
throw test;
if(test==1)
throw "Sir de caractere…";
if(test==2)
throw 121.25;
}
catch(int i)
{
cout<<"Am captat exceptia #:"<<i<<endl;
}
catch(char *sir)
{
cout<<"Am captat exceptia de tip sir de caractere:"<<sir<<endl;
}
catch(double d)
{
cout<<"Am captat exceptia #:"<<d<<endl;
}
};
void main()
{
XHandler(0);
XHandler(1);
XHandler(2);
cout<<"Sfarsit";
};
111
Programele scrise de dumneavoastră pot capta excepţii din cadrul mai multor blocuri try
(de exemplu un bloc try care incapsuleaza mai multe functii care lanseaza exceptii diferite
din blocuri try diferite sau să utilizeze mai multe instrucţiuni catch într-un singur bloc try.
C++ permite, de asemenea, utilizarea operatorului puncte de suspensie (…) pentru a
capta orice tip de eroare care apare într-un singur bloc try. Sintaxa care permite captarea
tuturor erorilor care apar într-un bloc try este prezentată mai jos.
try
{
//Instructiuni
}
catch(…)
{
//tratarea exceptiei
}
#include <iostream.h>
void XHandler(int test)
{
try
{
if(test==0)
throw test;
if(test==1)
throw 'a';
if(test==2)
throw 121.25;
}
catch(…)
{
cout<<"Am captat o exceptie"<<endl;
}
};
void main()
{
cout<<"Start:"<<endl;
XHandler(0);
XHandler(1);
XHandler(2);
cout<<"Sfarsit";
};
112
Evident, prelucrările din cadrul blocului catch generic trebuie să fie independente de tipul
erorii.
Mecanismul captării excepţiilor explicite poate fi combinat cu mecanismul excepţiilor
generice ca în exemplul de mai jos.
#include <iostream.h>
void XHandler(int test)
{
try
{
if(test==0)
throw test;
if(test==1)
throw 'a';
if(test==2)
throw 121.25;
}
catch(int i)
{
cout<<"Am captat o exceptie de tip intreg…"<<endl;
}
catch(…)
{
cout<<"Am captat o exceptie generica"<<endl;
}
};
void main()
{
cout<<"Start:"<<endl;
XHandler(0);
XHandler(1);
XHandler(2);
cout<<"Sfarsit";
};
Restrictionarea exceptiilor
Pe măsură ce programele dumneavoastră devin mai complexe, ele vor apela frecvent
funcţii din cadrul unui bloc try. Atunci când programele dumneavoastră apelează funcţii
dintr-un bloc try, puteţi restricţiona tipurile de excepţii pe care funcţia apelată le poate
lansa. De asemenea puteţi preveni lansarea oricărei excepţii dintr-o anumită funcţie.
113
{
//Cod functie
}
Este bine să subliniem că atunci când declaraţi o funcţie cu clauza throw ea poate să
lanseze doar acele tipuri precizate în listă. Dacă funcţia lansează orice al tip programul
este abortat.
Un exemplu în continuare.
#include <iostream.h>
void XHandler(int test) throw(int, char, double)
{
if(test==0)
throw test;
if(test==1)
throw 'a';
if(test==2)
throw 121.25;
}
void main()
{
cout<<"Start:"<<endl;
try
{
XHandler(0);
}
catch(int i)
{
cout<<"Am captat un intreg…"<<endl;
}
catch(char c)
{
cout<<"Am captat un caracter…"<<endl;
}
catch(double d)
{
cout<<"Am captat un double…"<<endl;
}
114
cout<<"Sfarsit";
};
#include <iostream.h>
void XHandler(void)
{
try
{
throw "Salve…";
}
catch(char *)
{
cout<<"Am captat char* in XHandler… "<<endl;
throw;
}
void main()
{
cout<<"Start…"<<endl;
try
{
XHandler();
}
catch(char *)
{
cout<<"Am captat char * in main…"<<endl;
}
cout<<"Sfarsit…";
};
#include <iostream.h>
void div (double a, double b)
115
{
try
{
if(!b) throw b;
cout<<"a/b="<<a/b<<endl;
}
catch(double b)
{
cout<<"Nu se poate imparti la zero…"<<endl;
}
}
void main()
{
double i,j;
do
{
cout<<Introduceti numaratorul (0 pentru stop):"<<endl;
cin i;
cout<<Introduceti numitorul :"<<endl;
cin j;
div(i,j);
} while (i!=0);
};
116
III Câteva aplicaţii care “au în spate” C
sau C++…
117
1 Sortarea elementelor unui şir de numere reale prin metoda bulelor. Program
pretext pentru modularizare. Se utilizează fişierul antet facilcrt.h prezentat în
continuarea acestei secţiuni.
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include"facilcrt.h"
#include<string.h>
void prelsir();
char prelopt();
void sort();
int dims;
float x[ _lms];
//Functia principala..
void main()
{
char op;
do
{
op=prelopt();
switch (op)
{
case '1': prelsir();
break;
118
case '2': sort();
break;
}
}
while (op!='3');
}
{
char c;
do
{
textcolor(RED);
textbackground(CYAN);
clrscr();
gotoxy(20,8) ;cprintf("Optiunile programului sunt:");
gotoxy(20,9) ;cprintf("1-Preluare elemente sir");
gotoxy(20,10);cprintf("2-Sortare/afisare sir");
gotoxy(20,11);cprintf("3-Terminare program");
makewin2(20,8,60,11);
makewin2(20,14,60,14);
gotoxy(20,14);cprintf("Optiunea Dvs.:");
c=getch();
}
while (strchr(_optiune,c)==NULL);
return c;
};
119
}
};
//Determinare maxim...
void sort()
{
float aux;
int i,sortat;
120
2 Fşier antet care încapsulează câteva facilităţi de lucru în mod text, utile pentru
realizarea unor interfeţe agreabile ale programelor cu utilizatorii. Numit în exemplul
precedent prin facilcrt.h.
#include <conio.h>
#include <stdio.h>
#include <string.h>
#define css 1
for (i=ls,j=cs;i<=j;i++,j--)
{
gotoxy(i,linie);
printf("%c",mesaj[i-ls]);
delay(50); //prototipul in fisierul antet <dos.h>
gotoxy(j,linie);
printf("%c",mesaj[strlen(mesaj)-1-cs+j]);
}
getch();
}
//Activare video-invers
void avideo()
{
textcolor(BLACK);
textbackground(WHITE);
121
}
//dezactivare video-invers
void dvideo()
{
textcolor(WHITE);
textbackground(BLACK);
}
122
}
gotoxy(ass-1,oss-1);
printf("\311");
gotoxy(adj+1,oss-1);
printf("\273");
gotoxy(ass-1,odj+1);
printf("\310");
gotoxy(adj+1,odj+1);
printf("\274");
}
else
{
gotoxy(1,24);
printf("Coordonate ecran eronate!!!");
getch();
}
}
123
3 Aplicaţie pretext pentru lucru cu tablouri, folosind pointeri.
Mai precis, codul prezentat mai jos rezolvă următoarele probleme:
-alocarea dinamică a memoriei pentru un tablou bidimensional cu număr de
linii şi coloane precizate;
-citirea elementelor unui tablou bidimensional;
-transpunerea unei matrice;
-afişarea elementelor unei matrice;
124
for(i=0;i<nl;i++)
{
for(j=0;j<nc;j++)
cout<<p[i][j]<<" | ";
cout<<endl;
};
}
//Transpunerea matricei
void transpun(int **p,int **q,int nl,int nc)
{
for(i=0;i<nl;i++)
for(j=0;j<nc;j++)
q[j][i]=p[i][j];
};
//Functia principala
void main()
{
int **pmat;
int **q;
int nl=6,nc=2;
clrscr();
almemmat(pmat,nl,nc);
almemmat(q,nc,nl);
citmat(pmat,nl,nc);
transpun(pmat,q,nl,nc);
afismat(q,nc,nl);
getch();
};
125
4 Sortarea unui şir de numere întregi prin metoda HEAP_SORT. Se utilizează
reprezentarea liniară a unui arbore binar.
#include<iostream.h>
#include<conio.h>
126
{
int y;
element temp;
y=n/2-1;
while (y>=0)
{
walk_down(list,y,n);
--y;
}
y=n;
while(y>0)
{
temp=list[0];
list[0]=list[y-1];
list[y-1]=temp;
--y;
walk_down(list,0,y);
}
}
void main()
{
list_type list;
int i,nrel;
clrscr();
gotoxy(20,10);
cout<<"Numar de elemente in lista:";
cin>>nrel;
clrscr();
for(i=0;i<nrel;i++)
{
gotoxy(20,11);
clreol();
cout<<"Elementul[ "<<i<<" ]=";
cin>>list[i];
}
heap_sort(list,nrel);
clrscr();
for(i=0;i<nrel;i++)
cout<<list[i]<<endl;
getch();
}
127
5 Rasfoirea unui fişier text şi contorizarea apariţiilor unui caracter într-un fisier text
utilizând fluxuri binare.
#include <fstream.h>
#include <stdlib.h>
#include "facilcrt.h"
char optiuni[]="1234";
char *nf;
int sfs;
128
case '4': exit(0);
}
}
while (2>1);
}
129
gotoxy(1,24);
textcolor(RED+BLINK);
cprintf("Selectati mai intai fisierul...");
getch();
textcolor(WHITE);
}
}
130
char ch;
while(in)
{
//Citesc un caracter din flux
in.get(ch);
if (wherey()>20)
{
gotoxy(1,24);
cout<<"Pentru continuare apasati o tasta...";
getch();
clrscr();
}
cout<<ch;
}
getch();
}
}
else
{
gotoxy(1,24);
textcolor(RED+BLINK);
cprintf("Selectati mai intai fisierul...");
getch();
textcolor(WHITE);
}
}
131
do
op=getch();
while (strchr(optiuni,op)==NULL);
return op;
}
#ifndef _stdio_h
#include<stdio.h>
#define _stdio_h
#endif
#ifndef _conio_h
#include<conio.h>
#define _conio_h
#endif
#ifndef _string_h
#include<string.h>
#define _string_h
#endif
#define startcol1 16
#define startcol2 36
#define startcol3 56
#include<dos.h>
#include<iostream.h>
#include<stdlib.h>
132
void scrie_sir(int x,int y,char *p,int atrib);
133
for(i=y;*p;i++)
{
*v++=*p++;
*v++=atrib;
}
}
134
lin=1;
col++;
}
switch(col)
{
case 1:
{
cc=startcol1;
break;
}
case 2:
{
cc=startcol2;
break;
}
case 3:
{
cc=startcol3;
break;
};
}
lin++;
gotoxy(cc,nrcan+lin);printf("%i",i);
gotoxy(cc+7,nrcan+lin);printf("%c",(char)i);
};
gotoxy(1,23);
for(j=0;j<78;j++)
printf("\xf");
gotoxy(1,24);
printf("Pentru terminare apasati o tasta....");
getch();
}
135
return parer;
}
#include
<stdio.h>#include<string.h>//************************************
*****************//Clasa modeleaza lucrul cu siruri de caractere in
C++
//class string
{
int lsir; //lungimea sirului
char *psir; //tablou de caractere alocat dinamic
public:
136
int operator<(string & x);
//Destructor clasa
~string();
//*****************************************************
//Implementare functii membre
137
psir=new char[lsir];
strcpy(psir,sir);
}
138
};
//Implementare destructor
string::~string()
{
delete psir;
}
139
return a;
};
140
8 Aplicatie care ilustreaza lucrul cu fisiere cu tip in C++
Din nou o problemă pretext, care se referă la actualizarea unui fişier în care se
păstrează întrebările unui test şi răspunsurile corecte, presupunând că o întrebare
are mai multe alternative de răspuns.
char optiuni[]="1234567";
char *nf;
int sfs,nri;
//Tipul inregistrare
typedef struct struct_test
{
int nr_intrebare;
int alt_corecta;
} t_inr_fis;
//Variabila inregistrare
t_inr_fis inr;
141
do
{
op=prelopt();
switch(op)
{
case '1': nf=selnumef();
break;
case '2': crefis();
break;
case '3': adinfis();
break;
case '4': {
clrscr();
gotoxy(10,12);
cout<<"Numar intrebare inregistrare de
sters:";
cin>>nri;
steinfis(nri);
break;
};
case '5': vizfis();
break;
case '6': {
clrscr();
gotoxy(10,12);
cout<<"Numar inregistrare de selectat:";
cin>>nri;
selin(nri);
break;
};
case '7': break;
}
}
while (op!='7');
};
142
gotoxy(10,12);
cout<<"Nr. intrebare :";
cout<<inr.nr_intrebare;
gotoxy(10,13);
cout<<"Alternativa corecta:";
cout<<inr.alt_corecta;
getch();
ftestin.close();
};
}
else
{
gotoxy(1,24);
cout<<"Selectati sau creati mai intai un fisier!....";
getch();
}
};
//Implementare crefis()
void crefis()
{
143
ofstream ftestout;
clrscr();
gotoxy(20,10);
cout<<"Nume fisier de creat:";
cin>>nf;
ftestout.open(nf,ios::out|ios::binary);
sfs=1;
char ras;
do
{
clrscr();
gotoxy(20,10);cout<<"Numar intrebare:";
cin>>inr.nr_intrebare;
gotoxy(20,11);cout<<"Raspuns corect :";
cin>>inr.alt_corecta;
ftestout.write((unsigned char *) &inr,
sizeof(t_inr_fis));
gotoxy(20,12);cout<<"Mai aveti de introdus(D,N):";
ras=getch();
}
while (!(toupper(ras)=='N'));
ftestout.close();
}
//Implementare adinfis()
void adinfis()
{
if (sfs)
{
ofstream ftestout;
clrscr();
gotoxy(20,10);
ftestout.open(nf,ios::app|ios::binary);
char ras;
do
{
clrscr();
gotoxy(20,10);cout<<"Numar intrebare:";
cin>>inr.nr_intrebare;
gotoxy(20,11);cout<<"Raspuns corect :";
cin>>inr.alt_corecta;
ftestout.write((unsigned char *) &inr,
sizeof(t_inr_fis));
gotoxy(20,12);cout<<"Mai aveti de introdus(D,N):";
ras=getch();
}
while (!(toupper(ras)=='N'));
144
ftestout.close();
}
else
{
gotoxy(1,24);
cout<<"Selectati sau creati mai intai un fisier!....";
getch();
}
}
//Implementare vizfis()
void vizfis()
{
if (sfs)
{
ifstream ftestin;
ftestin.open(nf,ios::in|ios::binary);
while (!ftestin.eof())
{
clrscr();
ftestin.read((unsigned char*)&inr,sizeof(t_inr_fis));
if(!ftestin.eof())
{
gotoxy(10,12);
cout<<"Nr. intrebare :";
cout<<inr.nr_intrebare;
gotoxy(10,13);
cout<<"Alternativa corecta:";
cout<<inr.alt_corecta;
getch();
};
};
ftestin.close();
}
else
{
gotoxy(1,24);
cout<<"Selectati sau creati mai intai un fisier!....";
getch();
}
}
//Implementare selnumef()
char *selnumef()
{
145
char *n;
sfs=1;
clrscr();
gotoxy(20,10);
cout<<"Numele fisierului de lucru:";
do
{
strcpy(n,"");
gotoxy(20+strlen("Numele fisierului de lucru:"),10);
gets(n);
}
while(strlen(n)==0);
return n;
}
//Implementare prelopt()
char prelopt()
{
clrscr();
gotoxy(20,8);
cout<<"Optiunile programului...";
gotoxy(20,9);
cout<<"__________________________________________";
gotoxy(20,11);
cout<<"1-Selectare fisier structura test";
gotoxy(20,12);
cout<<"2-Creare fisier structura test";
gotoxy(20,13);
cout<<"3-Adaugare date in fisierul structura test";
gotoxy(20,14);
cout<<"4-Stergerea unei inregistrari din fisier";
gotoxy(20,15);
cout<<"5-Vizualizare date fisier structura";
gotoxy(20,16);
cout<<"6-Afisare inregistrare de numar specificat";
gotoxy(20,17);
cout<<"7-Terminare program";
gotoxy(20,19);
cout<<"__________________________________________";
gotoxy(20,20);
cout<<" Alegerea Dvs.:";
gotoxy(20+strlen(" Alegerea Dvs.:"),20);
char op;
do
op=getch();
while (strchr(optiuni,op)==NULL);
return op;
146
};
BIBLIOGRAFIE MINIMALĂ
147
[1]. N. Barkakati Borland C++ 4, Ghidul programatorului,
Editura Teora, 1997
[2]. K., Jamsa, L. Klander Totul despre C şi C++, Editura Teora, 2000
[3]. I. Muşlea C++ pentru avansaţi, Editura
Microinformatica, 1994
[4]. L. Negrescu Limbajele C şi C++ pentru începători,
Limbajul C++, volumul 2, Editura Albastră,
Cluj-Napoca, 2000
[5]. L. Negrescu Limbajele C şi C++ pentru începători,
Limbajul C, volumul 1, Editura Albastră,
Cluj-Napoca, 2000
[6]. D. M. Popovici, C++. Tehnologia orientată pe obiecte.
I. M. Popovici, Aplicaţii,
I. Tănase Teora, 1996
Lista cărţilor din care se poate învăţa câte ceva despre C++ si lumea POO este
deschisă, la fel de deschisă ca mintea oricărui student care nu “şi-a greşit
culoarul” pe care aleargă pentru a deveni un profesionist.
Operatori în C/C++.
Tablouri C/C++.
Uniuni.
PARADIGMA POO
149
Principii în programarea obiect orientată.
Fluxuri C/C++.
150
programării în C, cea de-a doua lucrare urmărind verificarea cunoştinţelor
referitoare la programarea în spirit obiect orientat utilizând suportul C++.
Conţinutul lucrărilor va fi prezentat şi discutat în cadrul tutorialelor planificate.
...Lucrarea unui student este o “mică operă” care vorbeşte despre limitele
autorului ei în momentul în care acesta a realizat-o. Limitele acestei opere
nu se referă, exclusiv, la ortografie, ci şi la:
• Modul în care este structurat “discursul” de răspuns la fiecare
subiect în parte.
• Calitatea viziunii punctuale a studentului, pentru fiecare subiect
în parte.
• Calitatea viziunii sistemice a studentului; conexiunile între
subiect şi restul universului nu fac decât să arate, odată în plus,
inteligenţa acestuia.
Aşadar, ideea este că, pentru un profesror normal, fiecare student este un
potenţial creator de “bijuterii” în materie de cugetare pe teme impuse,
151
condiţie necesară pentru a realiza în viitor “bijuterii” în materie de
cugetare şi de altă natură, pe teme liber alese sau impuse de viitoarea
profesie.
152
Subiect teoretic
Suportul C++ pentru programarea generică
Subiect aplicativ
153
Subiect teoretic
Fluxuri C/C++. Lucrul cu fişiere în C++.
Subiect aplicativ
154