Documente Academic
Documente Profesional
Documente Cultură
Cuprins
Cuprins .......................................................................................................................... 4
1. Structura general a unui program C.................................................................. 9
1.1. Istoric, concepie , evoluie ........................................................................................... 9
1.2. Conceptul de funcie ................................................................................................... 10
1.2.1. Definiia unei funcii............................................................................................. 11
1.2.2. Antet i prototip ..................................................................................................... 11
1.2.3. Corpul unei funcii................................................................................................ 11
1.3. Construciile de baz ale limbajului.......................................................................... 12
1.3.1. Caractere................................................................................................................. 12
1.3.2. Nume ...................................................................................................................... 12
1.3.3. Cuvinte cheie ......................................................................................................... 12
1.3.4. Tipuri de baz ....................................................................................................... 13
1.3.5. Constante ................................................................................................................ 13
1.3.6. Variabile simple..................................................................................................... 15
1.3.7. Tablouri ................................................................................................................... 15
1.3.8. Comentariu .............................................................................................................. 16
1.4. Preprocesare .................................................................................................................. 17
1.4.1. Includerea unui fiier surs.................................................................................. 17
1.4.2. Constante simbolice............................................................................................... 18
2. Clase de variabile (de memorie)........................................................................ 19
2.1. Variabile globale .......................................................................................................... 19
2.2. Variabile locale ............................................................................................................ 20
2.3. Variabile registru ........................................................................................................ 21
2.4. Iniializare ..................................................................................................................... 22
3. Expresii, operanzi, operatori ................................................................................. 24
3.1. Expresii ......................................................................................................................... 24
3.2. Operanzi ......................................................................................................................... 24
3.3. Operatori ....................................................................................................................... 24
3.3.1. Operatori aritmetici................................................................................................ 25
3.3.2. Operatori relaionali............................................................................................... 25
3.3.3. Operatori de egalitate............................................................................................ 26
3.3.4. Operatori logici...................................................................................................... 26
3.3.5. Operatori logici pe bii......................................................................................... 27
3.3.6. Operatori de atribuire............................................................................................ 28
3.3.7. Operatori de incrementare i decrementare ........................................................ 29
3.3.8. Operatorul de conversie explicit (expresie cast)................................................ 30
3.3.9. Operatorul dimensiune (sizeof) ............................................................................ 30
3.3.10. Regula conversiilor implicite............................................................................... 31
3.3.11. Operatori condiionali.......................................................................................... 32
3.3.12. Operatorul virgul................................................................................................. 32
4. Intrri / ieiri standard ............................................................................................ 33
4.1. Funcia standard printf ................................................................................................ 33
4.2. Funcia standard scanf................................................................................................ 35
4.3. Funcia standard putchar ............................................................................................. 38
4.4. Funcia standard getchar ............................................................................................. 38
4.5. Funciile standard getche i getch ............................................................................. 39
5. Instruciuni .............................................................................................................. 40
5.1. Scurt istoric al metodelor de programare................................................................. 40
5.1.1. Programare artizanal ............................................................................................ 40
4
PARTEA I
Limbajul C
directive de preprocesare
2)
directive de preprocesare
implementare funcia f1
...
implementare funcia fn
void main(void)
{ declaraii
instruciuni
}
void main(void)
{ declaraii
instruciuni
}
implementare funcia f1
...
implementare funcia fn
Funcia principal main este obligatorie pentru orice program celelalte elemente fiind
optionale. Pentru ca o anumit funcie s poat fi apelat e necesar ca ea s aib declarat
prototipul dac implementarea (definiia) ei se afl dup funcia main (exemplul 1). Dac
funcia principal se afl la sfritul fiierului atunci nu mai e necesar prototipul funciei
apelate ci doar implementarea ei (exemplul 2). Comparnd structura unui program C cu
structura unui program PASCAL se observ nivelul de imbricare diferit. n PASCAL apare o
imbricare a procedurilor i funciilor pe cnd n C nu exist o astfel de imbricare.
PASCAL
...
10
reprezint tipul valorii returnate de funcie sau dac funcia nu returneaz nici
o valoare se pune cuvntul cheie void;
nume_funcie reprezint un identificator clasic format dintr-un mixaj de litere
i cifre, primul caracter fiind obligatoriu liter;
printre litere se numr i liniua de subliniere(_);
lista_parametrilor_formali nume de variabile sau expresii separate prin virgule.
Exemple:
1) double radical (double x)
// calculeaza radacina patrata din x si returneaza valoarea gasita
2) double radical_n (double x, int n)
// calculeaza radacina de ordinul n din x
Prototipul unei funcii este asemntor antetului dar la sfrit se pune caracterul ;
11
1.3.2. Nume
n limbajul C, un nume este o succesiune de litere i eventual cifre, care ncepe cu o
liter, deci un nume este un identificator. Ca lungime un nume poate fi orict de lung dar
numai primele 32 de caractere se iau n considerare. Folosind notaia BNF (vezi anexa) un
nume se poate defini recursiv, astfel:
<nume> :: = <litera> | <nume><litera> | <nume><cifra>
<litera> :: = A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Z|
a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|z|_
<cifra> :: = 0|1|2|3|4|5|6|7|8|9|
Numele se folosesc pentru denumirea variabilelor, tablourilor, funciilor, etc.
Exemple:
A, _start, a_, matrice, matrice_patratica.
Dm i cteva contraxemple:
&B - conine caracterul &;
x+y - conine caracterul +;
Un cuvnt cheie este un cuvnt mprumutat din limba englez, care are un neles
predefinit. Aceste cuvinte se scriu cu litere mici. Un cuvnt cheie nu poate avea alt utilizare
ntr-un program C dect cea care i-a fost predefinit. Fiind o succesiune de litere, un cuvnt
cheie este un nume. Lista cuvintelor cheie se d n anexa de la sfritul crii. Sensul fiecrui
cuvnt cheie va rezulta la definirea construciei n care se utilizeaz.
Exemple:
for, do, if, while, else, break, return, int, long, double, static, extern.
Cuvnt cheie
int
short
long
unsigned
char
float
double
Mulimea valorilor
Reprezentarea intern
Lungime (bii)
Formatul intern
16
16
32
16
8
32
64
[-3.4*10-38; 3.4*1038]
[-1.7*10-308; 1.7*10308]
reprezentare binar
reprezentare binar
reprezentare binar
reprezentare binar
cod ASCII
virgul flotant simpl precizie
virgul flotant dubl precizie
1.3.5. Constante
O constant are un tip i o valoare. Att tipul ct i valoarea unei constante se
definesc prin caracterele care compun constanta respectiv.
Constant ntreag zecimal
O constant ntreag este un ir de cifre care eventual este prefixat de un semn (+ sau -) i
postfixat de litera L sau l. O constant ntreag se reprezint n binar pe 16 sau 32 de bii.
Sufixul L sau l foreaz reprezentarea pe 32 de bii.
Constant ntreag octal
O constant ntreag octal este un ir de cifre octale (cifre cuprinse ntre 0 i 7) precedat de
un zero nesemnificativ.
Exemple:
Reprezentare
12345
-12345
12345L
012345
0xabcd
12345678
Interpretare
ntreg zecimal reprezentat n binar pe 16 bii
ntreg zecimal reprezentat n binar pe 16 bii
ntreg zecimal reprezentat n binar pe 32 bii
ntreg octal reprezentat n binar pe 16 bii (o cifr octal pe 3 bii)
ntreg hexa reprezentat n binar pe 16 bii (o cifr pe 4 bii)
ntreg zecimal reprezentat pe 32 de bii
Constant flotant
O constant flotant este un numr raional care se compune din urmtoarele elemente:
- un semn (+ sau -) care poate lipsi pentru numerele pozitive;
- o parte ntreag care poate fi i vid;
- o parte fracionar care poate fi i vid;
- un exponent care poate fi i vid.
Prezena prii fracionare este suficient pentru ca numrul respectiv s reprezinte o
constant flotant. Absena prii fracionare implic prezena prii ntregi i a exponentului.
Partea ntreag reprezint o succesiune de cifre zecimale. Partea fracionar se
compune din caracterul punct urmat de o succesiune de cifre zecimale, care poate fi i vid.
Exponentul se compune din litera e sau E urmat de un + sau -, opional, i un ir de cifre
zecimale.
Constantele flotante se pstreaz n format flotant dubl precizie.
Exemple:
3.14; 0.314e1; 3.1415926; 2.71828; 2.; 271828E-5.
Constant caracter
O constant caracter reprezint un caracter i are ca valoare codul ASCII al
caracterului respectiv. O constant caracter grafic se poate scrie incluznd caracterul
respectiv ntre caractere apostrof.
Exemple:
Constant caracter
A
B
a
0
9
*
Caracter
Backspace
Retur de car
Newline
Apostrof
Backslash
Tabulator vertical
Salt pagin imprimant
Carcterul NUL
\b
\r
\n
\
\\
\v
\f
\0
8
13
10
39
92
11
12
0
Constant ir de caractere
O constant ir de caractere este o succesiune de zero sau mai multe caractere delimitate
prin ghilimele. Ghilimelele nu fac parte din irul de caractere.
Exemple:
123; Limbajul C; sir de caractere; sir\; (irul vid).
Constanta ir de caractere se reprezint n memoria calculatorului printr-o
succesiune de octei n care se pstreaz codurile ASCII ale caracterelor irului, iar ultimul
octet conine caracterul NUL care marcheaz sfritul irului de caractere.
Dup cum se observ din exemplele date putem s folosim i convenia cu backslash.
De reinut c X i X reprezint construcii diferite.
//
//
//
//
1.3.7. Tablouri
Un tablou ca orice variabil simpl trebuie declarat nainte de a fi utilizat. Dac
referirea la elementele tabloului se face cu un singur indice se spune c tabloul este
15
unidimensional (se mai numete vector); dac referirea se face cu doi indici tabloul se
numete bidimensional (matrice); iar cu n indici tabloul este n-dimensional.
Declaraia unui tablou n forma cea mai simpl este:
tip nume[l1][l2]...[ln];
unde:
l1, l2, ... ln sunt expresii constante care au valori ntregi ce pot fi evaluate de compilator
la ntlnirea lor.
Evident c se pot declara mai multe tablouri deodat i atunci numele de tablouri se
pot nirui ntr-o list, fiecare separat prin virgul.
Exemple:
int t[5];
float a[5][3];
t[1]
t[2]
t[3]
t[4]
1.3.8. Comentariu
n limbajul C un comentariu se scrie ntre /* i */ pe oricte rnduri. ntre /* i */ se
poate scrie o succesiune arbitrar de caractere, care ns nu poate s conin secvena de
terminare (adic */). Comentariul poate fi inserat ntr-un program n orice poziie n care este
legal s apar un caracter alb. El constituie o explicaie pentru programator sau pentru
16
1.4. Preprocesare
Un program surs C poate fi prelucrat nainte de a fi compilat. O astfel de prelucrare
se numete preprocesare sau precompilare. Acest lucru se realizeaz printr-un program
special numit preprocesor. Acesta este apelat automat nainte de a ncepe compilarea.
Preprocesorul limbajului C realizeaz urmtoarele:
- includeri de alte fiiere (de obicei fiiere surs);
- definiii i apeluri de macrouri simple;
- compilare condiionat.
17
#define PROCENT 10
...
#define PROCENT 15
...
#undef PROCENT
...
2)
18
int i;
float f;
void main(void)
{ i = 10;
...
f = 3.14;
...
}
void functie(. . .)
{ extern int i;
extern double f;
...
f = f*i;
...
}
Variabilele i i f sunt declarate n afara funciei main i n afara oricrei funcii, deci
sunt variabile globale. Ele pot fi folosite n toate funciile din fiierul surs care conine
definiiile lor. Pentru a le utiliza n funcii situate n alte fiiere surs dect n cel n care sunt
definite ele sunt declarate ca externe. Deci variabilele i i f sunt declarate ca externe n funcia
functie din al doilea fiier surs. Cele dou fiiere surs care pot fi scrise n momente i de
persoane diferite se pot asambla ntr-un singur program cu ajutorul directivei de preprocesare
include.
19
Mai nti se aloc parametrul i ntr-un registru, apoi se aloc c i j n ali doi regitri.
21
2.4. Iniializare
Variabilelor li se pot atribui valori iniiale. Pentru variabilele globale valorile iniiale
se atribuie prin definiiile lor, iar n cazul celorlalte variabile se pot atribui valori prin
declaraiile lor. Pentru c de multe ori am folosit cuvintele definiia variabilelor sau
declaraiile varibilelor precizm c ele au neles distinct n anumite contexte. O variabil
global se definete o singur dat i se poate declara ori de cte ori e necesar utilizarea ei
n alte fiiere (evident declarat extern). n cazul celorlalte tipuri de variabile definiiile i
declaraiile coincid. Totodat definiia i declaraia (prototipul) unei funcii nu coincid.
O variabil simpl se poate iniializa printr-o declaraie de forma:
tip nume=expresie;
Variabilelor globale i statice li se atribuie valori iniiale la lansarea programului.
Expresia utilizat n cazul acestor variabile trebuie s fie o expresie constant care s poat fi
evaluat de compilator la ntlnirea ei. Aceasta, deoarece variabilele globale i statice se
iniializeaz prin valori definite la compilare.
Variabilele automatice se iniializeaz la execuie, de fiecare dat cnd se activeaz
funcia n care sunt declarate. Din aceast cauz, nu mai este necesar ca expresia s fie o
expresie constant. Totui, la ntlnirea ei, trebuie s fie definii operanzii expresiei de
iniializare.
Exemplu:
void f(int n)
{ int i=10;
int k=i+n;
...
}
22
23
3.2. Operanzi
Un operand n limbajul C poate fi una din urmtoarele elemente:
- o constant;
- o constant simbolic;
- numele unei variabile simple;
- numele unui tablou;
- numele unei structuri;
- numele unei funcii;
- referirea la elementul unui tablou (variabil cu indici);
- referirea la elementul unei structuri;
- apelul unei funcii;
- o expresie inclus n paranteze rotunde.
Unele dintre elementele de mai sus nu au fost nc definite, ele se vor prezenta n leciile
viitoare.
Exemple:
9876
- constant ntreag;
x
- variabil simpl;
t[i][3]
- variabil cu indici;
0xabcd
- constant hexazecimal;
t
- nume de tablou;
(expresie)
- expresie inclus n paranteze rotunde;
f1
- numele unei funcii.
3.3. Operatori
Operatorii limbajului C pot fi grupai n mai multe clase, dar oricum ei pot fi folosii
mpreun ntr-o aceeai expresie. Operatorii au ariti diferite: unari, binari, ternari i totodat
o anumit prioritate implicit care e redat n tabelul de mai jos. Operatorii de aceeai
prioritate se afl trecui n aceeai linie. Liniile tabelulul conin operatorii limbajului C n
ordinea descresctoare a prioritilor. Astfel n prima linie se afl operatorii de prioritate
maxim, iar n ultima linie operatorul virgul cu prioritatea cea mai mic. Cu excepia
operatorilor ., ->,&,*, a parantezelor rotunde (folosite la definiia i apelul funciilor)
i a parantezelor drepte (folosite la variabilele cu indici) ceilali operatori vor fi explicai n
aceast lecie.
( ) [ ] . ->
24
a= 4 i b= -5
atunci
a>0
a<=0
a+b>0
a>=b
a<0
a+b>=b-a
a+b>=(b-a)*(b-a)
are valoarea 1;
are valoarea 0;
are valoarea 0;
are valoarea 1;
are valoarea 0;
are valoarea 1;
are valoarea 0.
are valoarea 0;
are valoarea 1;
are valoarea 1.
&& 0 1
0
0 0
1
0 1
|| 0 1
0 0 1
1 1 1
sau exclusiv 0 1
0
0 1
1
1 0
26
Chiar dac pentru sau exclusiv nu exist operator el se poate realiza prin expresia
urmtoare aplicat operanzilor a i b: !a&&b||!b&&a sau folosind parantezele rotunde ((!a)
&&b)||((!b)&&a).
Operatorii logici se evalueaz de la stnga la dreapta. Dac la evaluarea unei expresii
se ajunge ntr-un punct n care se cunoate valoarea ntregii expresii, atunci restul expresiei nu
se mai evalueaz.
Dac a=0 i b=1 atunci expresia ! a||b are valoarea 1 pentru c !a are deja valoarea 1.
0 1
0 0
0 1
| 0 1
0 0 1
1 1 1
^
0
1
0 1
0 1
1 0
Observaii:
1o. Operanzii care nu ocup un cuvnt (16 bii) se extind la un cuvnt. De exemplu expresia
~0 are ca rezultat un cuvnt cu toi bii egali cu 1.
2o. Operatorii logici pe bii se execut bit cu bit spre deosebire de operatorii logici care se
evalueaz global. De exemplu dac x=2 i y=1 sunt variabile de tipul int atunci:
x&&y
are valoarea 1 pentru c ambii operanzi sunt diferii de 0.
x&y
are valoarea 0 conform schemei de mai jos
x=
0000 0000 0000 0010
y=
0000 0000 0000 0001
x&y= 0000 0000 0000 0000
3o. Operatorul & se folosete frecvent pentru a anula bii din configuraia unui cuvnt, iar
operatorul | pentru a seta (pune) bii ntr-un anumit mod.
4o. Operanzii trebuie s fie ntregi (de tipul int sau long).
5o. Atenie la deplasri nu se modific valoarea operandului; deci trebuie s facem o atribuire;
de exemplu a = a << 3 va modifica valoarea lui a pe cnd a << 3 nu modific valoarea lui a.
Exemple:
1) Fie declaraia:
27
int i;
atunci expresia i >> 8 & 255 are ca rezultat valoarea celui mai semnificativ octet a lui i; i
>> 8 deplaseaz octetul mai semnificativ al lui i n poziia mai puin semnificativ; se face
apoi un I logic pe bii cu masca 255 care pstreaz octetul mai puin semnificativ.
2) Fie expresia: (x >> 6) & ~(~ 0 << 3)
S presupunem c x are valoarea n bii: 1010 1011 1000 1101.
Atunci x>>6 are valoarea:
1111 1110 1010 1110
Al doilea operand pregtete o masc astfel:
~0
~0<<3
~(~0<<3)
op=
unde prin op se nelege unul din operatorii binari aritmetici sau logici pe bii, adic unul din
urmtorii:
% / * - + & ^ | << >>
Acest mod de construcie se folosete pentru a compacta un anumit tip de atribuire. Astfel
expresia:
v op = expresie;
este identic cu expresia de atribuire:
v = v op expresie;
Exemple:
int i, j;
double x, y;
int v[10];
i=5;
j=10;
x=y=10.01;
i +=1;
x*=3;
j<<=10;
v[i]*=i
x /= x-y
//
//
//
//
//
echivalenta
echivalenta
echivalenta
echivalenta
echivalenta
cu i=i+1 si cu i++
cu x=x*3
cu j=j<<10
cu v[i]=v[i]*i
cu x = x/(x-y)
29
double x,y;
int vector [5];
j=i++;
y=--x;
i=++vector[j]
Dac vom converti operanzii i i j spre tipul double se va obine rezultatul corect adic 1.6.
Deci:
int i,j;
double y;
i=8; j=5;
y=(double) i / (double) j;
Construcia (tip) este un operator unar prin care se expliciteaz conversia dorit. Are aceeai
prioritate ca restul operatorilor unari.
Exemple:
int i;
long l;
float f;
double d;
char c;
int itablou[5];
double dtablou[5];
sizeof (i)
sizeof (l)
// are valoarea 2;
// are valoarea 4;
30
sizeof (f)
sizeof (d)
sizeof (c)
sizeof (itablou[1])
sizeof (dtablou[1])
sizeof (itablou)
sizeof (dtablou)
//
//
//
//
//
//
//
are valoarea 4;
are valoarea 8;
are valoarea 1;
are valoarea 2;
are valoarea 8;
are valoarea 10;
are valoarea 40.
expresii
conversii
i-j/k
a/b
x+y
i+a
i-3.14
nu
a spre double; b spre double
nu
a spre double; i spre double
i spre double
expresii
conversii
i+3
i+x
i-c
x+10
p-10
r*5
(double)(i/j)
nu
i spre double
c spre int
10 spre double
10 spre unsigned
5 spre long
se realizeaz mprirea ntreag ntre
i i j i rezultatul se convertete spre double
31
tipul expresiei
int
double
double
double
double
tipul expresiei
int
double
int
double
unsigned
long
double
Se execut pe rnd cele dou atribuiri de la stnga la dreapta din parantezele rotunde apoi se
face suma i+j i n final se atribuie aceast sum lui k.
32
g
G
34
void main(void)
{ int i=10; long j=11;
float a=1.2, b=1.3;
double A=1.4; B=1.5;
clrscr();
// inceputul instructiunilor executabile; se sterge ecranul
printf ("\ni*j = %d",i*j);
// incep afisarile
printf ("\ni*j = %5d",i*j);
printf ("\ni*j = %-5d",i*j);
printf ("\ni*j = %5.5d",i*j);
printf ("\ni*j = %05d",i*j);
printf ("\na*b = %10.1f",a*b);
printf ("\nA*B = %10.5lf",A*B);
printf ("\nradical(a*b) = %lf",sqrt((double) a*b));
printf ("\nradical(A*B) = %15.10lf",sqrt(A*B));
printf ("\nradical(A*B) = %25.17lf",sqrt(A*B));
printf ("\nradical(A*B) = %25.19lf",sqrt(A*B));
getche();
// asteapta citirea unui caracter de la terminal
}
Rezultatele execuiei programului sunt:
i*j = 110
i*j = 110
i*j = 110
i*j = 00110
i*j = 00110
a*b =
1.6
A*B = 2.10000
radical(a*b) = 1.249000
radical(A*B) = 1.4491376746
radical(A*B) =
1.44913767461894372
radical(A*B) = 1.4491376746189437200
2)
#define sir PC WORLD
void main (void)
{ printf(*%10s*,sir);
printf(*%-10s*,sir);
printf(*%10.5s*,sir);
printf(*%-10.5s*,sir);
}
Funcia scanf returneaz numrul de cmpuri citite corect din fiierul stdin. Dac
apare o eroare la citire (din diverse motive de exemplu neconcordan dintre specificatorul de
format i datele din fiierul stdin) atunci funcia nu va returna numrul total de cmpuri;
citirea se va ntrerupe n locul detectrii erorii i scanf va returna numrul de cmpuri citite
pn n acel moment. Deci de multe ori pentru a valida formal intrarea datelor (atenie nu e o
validare calitativ) se va folosi o construcie de forma:
nr=scanf(...)
Prin construcia de mai sus se poate pune n eviden sfritul de fiier, deoarece n
acest caz scanf returneaz valoarea EOF. Sfritul de fiier se poate genera de la tastatur
acionnd n acelai timp tastele CTRL i Z (deci CTRL / Z). Deorece funcia scanf citete
datele de la intrarea standard prin intermediul unei zone de memorie intermediare (numit
zon tampon sau buffer), ea are acces la caracterele din zona tampon numai dup acionarea
tastei ENTER (RETURN). De aceea dup acionarea combinaiei CTRL / Z se va tasta i
ENTER. Totodat aceast intermediere face posibil eventuale corecturi nainte de a aciona
tasta ENTER.
Exemplu:
Vom citi un ntreg de cinci cifre de la intrarea standard i vom afia cifrele respective
precedate fiecare de dou spaii.
void main (void)
{ int c1,c2,c3,c4,c5;
// c1,c2,c3,c4,c5 sunt cifrele intregului citit
scanf(%1d %1d %1d %1d %1d, &c1,&c2,&c3,&c4,&c5);
// se citesc cifrele intregului in cele 5 variabile cu %1d
printf(%3d%3d%3d%3d%3d,c1,c2,c3,c4,c5);
}
Pentru a citi iruri de caractere se va folosi funcia scanf fr a mai pune operatorul de
adres n faa numelui irului de caractere, deoarece numele unui tablou reprezint un pointer
(deci conine o adres i anume adresa primului element de tablou).
Exemplu:
Vom citi numele i prenumele unei persoane i le afim pe ecran.
#define MAX 20
void main(void)
{ char nume[MAX+1], prenume[MAX+1];
//declararea tablourilor de caractere
scanf (%20s %20s,nume, prenume);
//nu s-a mai pus operatorul de adresa
printf (\nnumele: %s, prenumele: %s,nume,prenume);
}
37
2) exemplul urmtor testeaz dac s-a citit o liter mare i numai n acest caz aplic
transformarea ei n liter mic. n cazul n care la intrare nu se afl o liter mare se rescrie
caracterul respectiv.
#include <stdio.h>
void main (void)
{ intc c;
putchar(((c = getchar() )>= A && c<= Z) ? c-A+a :c);
}
38
39
5. Instruciuni
5.1. Scurt istoric al metodelor de programare
Vom prezenta n continuare cteva metode de programare dar nu exhaustiv, nefiind
aici cadrul adecvat (eventual ntr-un curs de Software Engineering). O clasificare cronologic
a ceea ce vrem s prezentm ar fi urmtoarea:
a) programarea artizanal;
b) programarea procedural;
c) programarea modular;
d) programarea structurat;
e) programarea prin abstractizarea datelor;
f) programarea orientat spre obiecte.
40
b) iteraia pretestat;
c) alternativa simpl.
D-structurile se regsesc n toate limbajele procedurale. Corespondena ar fi:
a) secvena este echivalent cu execuia instruciunilor n ordinea scrierii lor
programul surs;
b) iteraia pretestat echivalent cu WHILE ... DO;
c) alternativa simpl echivalent cu IF ... THEN.
da
S1
C
nu
S2
da
S
C
S
nu
Sn
S-a demonstrat ulterior (Bohm i Jacopini) c orice algoritm se poate descrie doar cu
D-structurile dar pentru o mai bun lizibilitate i nelegere a programelor surs s-au adugat
i iteraia postestat (REPEAT ... UNTIL), iteraia cu numr prestabilit de ciclri (FOR ...
DO), alternativa complet (IF ... THEN ... ELSE) i alternativa generalizat (CASE ... OF).
n unele limbaje se folosesc i alte structuri pe lng cele de mai sus pentru o ct mai fidel
reflectare a algoritmului.
42
Dreptunghi
Romb
Ptrat
Dac paralelogramul se afl n vrful ierarhiei atunci pe nivelul imediat inferior se aeaz
dreptunghiul (paralelogramul cu un unghi drept) dar i rombul (paralelgramul cu 2 laturi
alturate congruente). Apoi ptratul se poate defini fie ca un dreptunghi cu laturile
congruente fie ca un romb cu un unghi drept. Conceptul de pe fiecare nivel se observ c
motenete proprietile conceptului imediat superior din care este derivat.
La ora actual, toate ramurile cunoaterii tiinfice sunt pline de ierarhii rezultate n urma
clasificrii cunotinelor acumulate n perioada lung de observare a fenomenelor i formelor
de existen a lumii materiale i spirituale. Clasificrile ierarhice ale cunotinelor pot fi
ntlnite att n domeniile care pleac de la cele mai concrete forme ale lumii materiale, cum
sunt botanica, zoologia, biologia, etc ct i n domenii care studiaz concepte dintre cele mai
abstracte, cum ar fi matematica sau filozofia.
Aceste ierarhii sunt rezultatul definirii conceptelor dup regula includerii genul
proxim i diferena specific.
Limbajul C dispune de un set bogat de instruciuni care permit scrierea de:
- programe structurate,
- programe flexibile,
- programe compacte.
43
{ int i;
float f;
double d;
i=10;
i++;
f=i;
d=++f;
putchar(a);
//
//
//
//
//
instructiune de atribuire
i se mareste cu 1
instructiune de atribuire (valoarea lui i se converteste in float)
incrementare lui f si atribuirea valorii lui d
instructiune de apel
44
Pot lipsi declaraiile sau instruciunle dar nu n acelai timp. Dac declaraiile sunt
prezente, atunci ele definesc variabile care sunt valabile numai n instruciunea compus
respectiv.
Exemplu:
...
{ int i=100;
i++;
printf (i=%d\n,i);
}
Observaii:
1o. Dup acolada inchis a unei instruciuni compuse nu se pune ;.
2o. Corpul unei funcii are aceeai structur ca i instruciunea compus, deci o funcie are
formatul:
antetul funciei
instruciune compus
5.5. Instruciunea if
Instruciunea if permite s realizm o ramificare a execuiei n funcie de valoarea unei
expresii. Ea are dou formate ce permit aplicarea structurii de alternativ simpl i compus.
Formatul 1:
if (expresie) instructiune;
Efectul:
1) se evalueaz expresia din paranteze;
2) dac valoarea expresiei este diferit de zero (deci conform conveniei are valoarea
adevrat), atunci se execut instructiune, altfel se trece la instruciunea urmtoare.
Formatul 2:
if (expresie)
else
instructiune_1;
instructiune_2;
Efectul:
1) se evalueaz expresia din paranteze;
2) dac valoarea expresiei este diferit de zero (deci conform conveniei are valoarea
adevrat), atunci se execut instructiune_1, altfel se execut instructiune_2; apoi n
ambele cazuri se trece la instruciunea urmtoare.
Observaii:
1o. Se pot folosi instruciuni if imbricate, nivelul de imbricare fiind oarecare (deci nu exist o
limitare a numrului de imbricri).
2o. Pentru mai multe imbricri se folosete regula asocierii if-lui cu else astfel:
un else se pune n coresponden cu primul if care se afl naintea lui n textul surs i nu este
inclus n instruciunea care l precede pe el i nici nu i corespunde deja un else.
45
Exemple
void main (void)
{ float x,y,a;
x=-5;
y=10;
if (x<0)
if (y<0)
else
else a=0;
}
void main (void)
{ float x,y,a;
x=-5;
y=10;
if (x<0)
{ if (y<0) a=1; }
else a=0;
}
a=1;
a=2;
46
Exemplu:
Vom crea un program care citete un ntreg n i scrie n!. Algoritmul n pseudocod:
Citeste n
f=1
i=2
CtTimp i<=n execut
f=f*i;
i=i+1
SfritCtTimp
Scrie n,f
Programul n C este:
#include<stdio.h>
void main (void)
{ int n,i;
double f;
f=1.0;
i=2;
printf(\n dati n= );
scanf(%d,&n);
while (i<=n)
{ f=f*i;
i++;
}
printf(\nn=%d, iar n!=%g\n,n,f);
}
2o. Expresiile din antetul instruciunii for pot fi i vide; totui caracterele ; vor fi ntotdeauna
prezente.
3o. Comparnd instruciunile for i while observm c instruciunea for cu formatul anterior se
poate realiza cu secvena urmtoare folosind while:
exp1;
while (exp2)
{ instructiune;
exp3;
}
Invers, o instruciune while de forma: while (exp) instructiune este echivalent cu
urmtoarea instruciune for:
for(; exp; ) instructiune.
Autorii limbajului C propun ca instruciunea for s se foloseasc cu prioritate pentru
ciclurile care au pas.
Exemple:
Vom da o secven de instruciuni care nsumeaz elementele unui tablou:
s=0;
for(i=0; i<n; i++) s=s+tab[i];
sau scris mai compact:
for (s=0, i=0; i<n; i++) s+=tab[i];
n continuare vom da un mic program care afieaz numrul caracterelor citite de la
intrarea standard stdin.
#include <stdio.h>
void main(void)
{ long n;
for (n=0; getchar()!=EOF; n++);
printf (\nnumarul caracterelor citite =%ld\n,n);
}
3) dac valoarea expresiei este zero se trece la instruciunea urmtoare instruciunii dowhile; altfel se revine i se execut din nou instructiune.
Observaii:
1o. Structura realizat de instruciunea do-while poate fi realizat printr-o secven n care
se folosete instruciunea while astfel:
instructiune;
while (exp) instructiune;
o
2 . Se observ c n cazul instruciunii do-while, corpul ciclului se execut cel puin odat,
spre deosebire de ciclurile while i for unde corpul ciclului poate s nu se execute niciodat.
Exemplu:
Vom da un program care calculeaz rdcina ptrat dintr-un numr real a>=0.
#include<stdio.h>
#define EPS 1e-10
void main (void)
{ double x1,x2,y,a;
clrscr();
// sterge ecranul
printf(\ndati un numar real pozitiv a=);
if (scanf(%lf,&a) !=1 || a<0) printf (numarul citit nu este pozitiv\n);
else {
x2 = 1.0;
do { x1 = x2;
x2 = 0.5 *(x1+a/x1);
// formula de iteratie
if ((y=x2-x1) < 0) y = -y;
}
while (y >= EPS);
printf (radacina patrata din:%g este: %.2lf\n,a,x2);
// 2 zecimale
} //sfirsit else
}
sir1
break;
sir2
break;
sirn
break;
sir
}
unde:
Efectul:
1) se evalueaz expresia din parantez;
49
50
rezultat = 0;
} else rezultat = op1 / op2;
break;
default : printf (operator eronat\n);
rezultat = 0;
} // sfarsit switch
printf (%d %c %d %d\n, op1, operator, op2, rezultat);
} else
printf (expresie eronat\n);
// sfarsit if
// sfarsit while
51
(*)
unde:
-
52
O funcie poate fi apelat dac ea este definit n fiierul surs nainte de a fi apelat.
Dup cum am prezentat n lecia 1 nu ntotdeauna este posibil acest lucru i n astfel de cazuri
apelul funciei trebuie s fie precedat de prototipul ei.
Prototipul unei funcii are ca scop s informeze compilatorul despre:
- tipul valorii returnate de funcie;
- tipurile parametrilor.
n felul acesta, la apelul unei funcii, compilatorul poate face teste cu privire la tipul
expresiilor care reprezint parametrii efectivi, precum i unele conversii necesare asupra
valorii returnate de funcie.
Observaii:
1o. Tipurile parametrilor pot s lipseasc. n acest caz, compilatorul nu controleaz tipurile
parametrilor efectivi, singura informaie coninut de prototip fiind tipul valorii returnate de
funcia respectiv.
2o. Absena att a prototipului unei funcii, ct i a definiiei funciei nainte de a fi apelat
este posibil; n acest caz se presupune c funcia returneaz o valoare de tip int.
3o. n practic se recomand utilizarea prototipurilor pentru toate funciile nainte de a fi
apelate. n acest scop, ele vor fi scrise la nceputul fiierelor surs.
Formatele posibile ale unui prototip sunt:
formatul 1:
formatul 2:
formatul 3:
formatul 4:
Formatul 2 este cel mai utilizat. Formatul 3 se poate folosi pentru orice funcie care nu
are parametri. Formatul 4 se poate folosi pentru orice funcie la al crei apel nu se doresc teste
referitoare la tipul parametrilor efectivi.
Funciile din biblioteca standard a limbajului C au prototipurile definite n fiierele de
tip .h.
dup execuia ultimei sale instruciuni, adic a instruciunii care precede acolada
nchis ce termin corpul funciei respective.
Instruciunea return are dou formate:
return;
sau
return expresie;
Primul format se utilizeaz cnd funcia nu returneaz o valoare, iar cel de-al doilea
cnd funcia returneaz o valoare. n acest ultim caz, funcia returneaz valoarea expresiei
specificate.
Observaie:
1o. Cnd revenirea se face dup execuia ultimei instruciuni a funciei nu se returneaz o
valoare; revenirea n acest caz, se face ca i cum acolada nchis de la sfritul corpului
funciei ar fi precedat de instruciunea return.
Exemplu: vom da un exemplu de apel al funciei care determin rdacina ptratic dintr-un
numr nenegativ.
#include<stdio.h>
double radacina_2 (double);
void main (void)
// prototipul functiei
// functia principala care citeste d
// si afiseaza radacina patrata din d
{ double d;
clrscr();
// sterge ecranul
if (scanf (%lf,&d) != 1 || d<0)
printf (numarul dat este eronat\n);
else
printf (d=%f, radacina patrata = %.10g\n, d, radacina_2(d));
#define EPS 1e-10
double radacina_2 (double x)
{ double x1,x2,y;
x2 = 1.0;
do { x1 = x2;
x2 = 0.5 *(x1+x/x1);
if ((y=x2-x1) < 0) y = -y;
}
while (y >= EPS);
return x2;
}
// formula de iteratie
Observaie:
1o. Limbajul C dispune de o bibliotec matematic n care sunt incluse o serie de funcii
pentru calculul valorilor funciilor elementare. Exist o funcie numit sqrt (cu prototipul
double sqrt (double);). Fiierul care conine biblioteca matematic se numete math.h i
trebuie inclus n fiierul surs de lucru dac se dorete utilizarea funciilor definite n el.
54
6. Pointeri
Un pointer este o variabil care are ca valori adrese. Pointerii se folosesc pentru a face
referire la date cunoscute prin adresele lor. Astfel, dac p este o variabil de tip pointer care
are ca valoare adresa zonei de memorie alocat pentru variabila ntreag x atunci construcia
*p reprezint chiar valoarea variabilei x.
n construcia de mai sus, *p, caracterul * se consider ca fiind un operator unar care
furnizeaz valoarea din zona de memorie a crei adres este coninut n p. Operatorul unar *
are aceeai prioritate ca i ceilali operatori unari din limbajul C.
Dac p conine adresa zonei de memorie alocat variabilei x, vom spune c p
pointeaz spre x sau c p conine adresa lui x.
Pentru a atribui unui pointer adresa unei variabile, putem folosi operatorul unar &.
Astfel, dac dorim ca p s pointeze spre x, putem utiliza construcia:
p = &x;
n limba romn se utilizeaz i alte denumiri pentru noiunea de pointer: referin,
localizator; reper; indicator de adres.
Exist cazuri n care dorim ca un pointer s fie utilizat cu mai multe tipuri de date. n
acest caz, la declararea lui nu dorim s specificm un tip anume. Aceasta se realizeaz
folosind cuvntul cheie void:
void *nume;
Exemple:
1)
void main (void)
{ int x,y;
int *p;
y=x+10;
// aceast atribuire este echivalenta cu secventa urmatoare
p=&x;
y=*p+100;
x=y;
p=&x;
*p=y;
l=lungime(tablou);
unde tablou este de tip caracter.
Antetul funciei lungime poate fi schimbat n felul urmtor:
unsigned lungime (char *x);
Cele dou declaraii sunt identice deoarece declaraia:
char x[ ];
definete pe x ca numele unui tablou de tip caracter; dar atunci el este un pointer spre
caractere deci se poate declara prin:
char *x;
expresia p+n mrete valoarea lui p cu n*nr_tip, unde nr_tip este numrul de
octei necesari pentru a memora o dat de tipul tip spre care pointeaz p;
analog expresia p-n micoreaz valoarea lui p cu n*nr_tip.
57
Dac x este un tablou de tipul tip, atunci x este pointer, deci o expresie de forma:
x+n;
este corect i deoarece x este un pointer spre primul su element x[0], x+n va fi un pointer
spre elementul x[n]. Rezult c valoarea elementului x[n] se poate reprezenta prin expresia:
*(x+n);
Astfel variabilele cu indici se pot nlocui prin expresii cu pointeri. Aceasta permite ca
la tratarea tablourilor s se foloseasc expresii cu pointeri n locul variabilelor cu indici.
Versiunile cu pointeri sunt de obicei optime n raport cu cele realizate prin intermediul
indicilor.
p!=j
p= =q.
Observaii:
1o. Pointerii nu pot fi comparai dect n condiiile amintite mai sus (deci dac sunt pointeri
spre elementele aceluiai tablou).
2o. Operatorii = = i != permit compararea unui pointer i cu o constant simbolic special
avnd numele NULL. Aceste comparaii permit s stabilim dac un pointer conine o adres
sau nu. Astfel, dac expresia:
p= = NULL
este adevrat, p nu conine o adres. Dac expresia respectiv are valoarea fals atunci p
conine o adres. Constanta simbolic NULL este definit n fiierul stdio.h
6.3.5. Exemple
Vom da cteva funcii asupra irurilor de caractere:
funcia lungime
58
funcia copiaza
void copiaza(char *x, char *y)
{ while(*x++ = *y++); }
funcia concateneaza
void concateneaza (char *x, char *y)
// concateneaza sirul de adresa y la sfarsitul sirului
// de adresa x
{ while (*x) x++;
// avans de adresa pana la sfarsitul sirului x
while (*x++= *y++);
}
funcia compara
int compara (char *x, char *y)
{ while (*x= = *y)
{ if (*x= = \0) return 0;
x++;
y++;
return *x - *y;
// daca diferenta caracterelor este
// negativa atunci x<y altfel x>y
}
}
Funcia free elibereaz o zon de memorie alocat prin malloc. Prototipul ei este:
void free (void *p);
unde p este pointerul returnat de malloc la alocare, deci este pointerul spre nceputul zonei
care se elibereaz.
Exemplu:
Funcia memchar memoreaz un ir de caractere ntr-o zon de memorie alocat prin funcia
malloc. Ea returneaz adresa de nceput a zonei n care s-a salvat irul de caractere, deci
returneaz un pointer spre tipul char.
#include <stdio.h>
#include <alloc.h>
#include <string.h>
char *memchar (char *s)
{
char *p;
if ((p=(char *)malloc(strlen(s)+1) ) != NULL)
{
strcpy (p,s);
return p;
} else
return NULL;
}
Observaii:
1o. n fiierul stdio.h exist definiia constantei NULL.
2o. Fiierul alloc.h s-a inclus deoarece conine prototipul funciei malloc.
3o. Fiierul string.h conine prototipurile funciilor strlen i strcpy.
4o. Funcia malloc se apeleaz pentru a rezerva strlen(s)+1 octei; strlen returneaz numrul
de octei ocupai de caracterele proprii ale lui s (fr caracterul NUL). Cum n zona de
memorie rezervat prin malloc se pstreaz i caracterul NUL, lungimea returnat de funcia
strlen s-a mrit cu 1.
5o. Pointerul returnat de malloc a fost convertit spre char *, deoarece el este de tip void *.
Acest pointer se atribuie lui p, deci p pointeaz spre nceputul zonei de memorie alocate prin
apelul funciei malloc. Se testeaz dac acest pointer este diferit de NULL (deci dac s-a putut
aloca memoria de dimensiunea cerut). n caz afirmativ, se transfer irul prin apelul funciei
strcpy, returnndu-se apoi valoarea pointerului p.
Exemplu:
Un exemplu matematic n care este nevoie de un astfel de transfer este cel cu privire la
calculul aproximativ al integralelor definite. S presupunem c dorim s calculm integrala
definit din funcia f(x), ntre limitele a i b, folosind formula trapezului:
I= h((f(a)+f(b))/2 +f(a+h)+f(a+2h)+. . . +f(a+(n-1)h)
unde
h=(b-a)/h.
n continuare construim o funcie care calculeaz partea dreapt a acestei relaii.
Numim aria_trapez aceast funcie.
Observaii:
1o. Deoarece funcia f(x) din relaia de mai sus nu este definit n acest moment, ea trebuie s
figureze printre parametrii funciei aria_trapez, mpreun cu limitele de integrare i valoarea
lui n.
2o. Funcia aria_trapez returneaz valoarea aproximativ a integralei i ea se va apela printr-o
expresie de atribuire, de exemplu:
aria=aria_trapez (a, b, n, f);
3o. Funcia aria_trapez returneaz o valoare flotant n dubl precizie. De asemenea, i funcia
f(x) returneaz o valoare flotant n dubl precizie. De aici rezult c prototipul funciei
aria_trapez este urmtorul:
double aria_trapez (double a, double b, int n, double (*f)());
sau
double aria_trapez (double, double, int , double (*)());
4o. Este necesar ca naintea apelului funciei aria_trapez funcia f(x) s fie definit sau s fie
prezent prototipul ei , de exemplu:
double f();
5o. Construcia double (*f) () se interpreteaz n felul urmtor:
- *f
nseamn c f este un pointer;
- (*f)()
nseamn c f este un pointer spre o funcie;
- double (*f) () nseamn c f este un pointer spre o funcie care returneaz o valoare
flotant n dubl precizie.
6o. Trebuie s se includ *f ntre paranteze, deoarece construcia double *f(); este corect,
dar nseamn altceva, parantezele rotunde fiind prioritare operatorului unar *. n acest caz, se
declar f ca o funcie ce returneaz un pointer spre o valoare flotant n dubl precizie.
7o. Ultimul parametru formal al funciei aria_trapez corespunde parametrului efectiv f i deci
el trebuie declarat ca i pointer spre o funcie ce returneaz o valoare flotant n dubl
precizie. Conform observaiei 5), dac p este numele parametrului formal ce corespunde
parametrului efectiv f, atunci p se declar astfel:
double (*p)();
8o. n corpul funciei aria_trapez va trebui s apelm funcia f(x) pentru a calcula valorile:
f(a), f(b), f(a+h), . . . , f(a+(n-1)h).
n momentul programrii funciei aria_trapez, nu se cunoate numele funciei concrete, ci
numai pointerul p spre ea. De aceea, vom nlocui numele funciei prin *p, deci vom folosi
apelurile:
61
Vom utiliza funcia aria_trapez pentru a calcula integrala definit din funcia sin(x 2) pe
intervalul [0,1], cu o eroare mai mic dect 10-8. Vom nota cu In urmtoare sum:
In= h((f(a)+f(b))/2 +f(a+h)+f(a+2h)+. . . +f(a+(n-1)h)
Paii algoritmului sunt urmtorii:
Pasul 1. Se alege o valoare iniial pentru n, de exemplu 10.
Pasul 2. Se calculeaz In.
Pasul 3. Se calculeaz I2n prin dublarea lui n.
Pasul 4. Dac |In-I2n| < 10-8, algoritmul se ntrerupe i valoarea integralei, cu precizia admis,
este I2n; altfel se dubleaz n, se pune In=I2n; n, i se trece la pasul 3.
#define A 0.0
#define B 1.0
#define N 10
#define EPS 1e-8
#include <stdio.h>
#include <math.h>
double sinxp(double);
// prototipul functiei sin(x*x)
double aria_trapez(double, double, int, double (*)());
void main (void)
// functia principala
{ int n=N;
double in, i2n, vabs;
in=aria_trapez (A, B, n, sinxp);
do {
n=n*2;
i2n=aria_trapez(A, B, n, sinxp);
if ((vabs= in-i2n) < 0)
vabs = -vabs;
in=i2n;
} while (vabs >= EPS);
printf (valoarea integralei este : %g.10\n,i2n);
}
double aria_trapez(double x, double y, int m, double(*p)());
{
double h,s;
int i;
h=(y-x)/m;
for (i=1, s=0.0; i<m; i++) s+=(*p)(x+i*h);
s+=((*p)(x) + (*p)(y))/2;
s=h*s;
return s;
}
double sinxp (double x)
{ return sin (x*x); }
62
*s= 1;
*(s+1)=2
char const *s=aprilie;
6.8. Stiva
Prin stiv (stack n englez) nelegem o mulime ordonat de elemente la care accesul
se realizeaz conform principiului ultimul venit primul servit. n englez stiva se mai
numete i list LIFO (Last In First Out).
O modalitate simpl de a implementa o stiv este pstrarea elementelor ei ntr-un tablou
unidimensional. n acest tablou se vor pstra elementele stivei unul dup altul. De asemenea,
ele se pot scoate din tablou n ordinea invers pstrrii lor. La un moment dat se poate scoate
ultimul element pus pe stiv i numai acesta.
Despre ultimul element pus n stiv se spune c este vrful stivei, iar despre primul
element c este baza stivei.
Accesul este permis doar la vrful stivei:
- un element se poate pune pe stiv numai dup elementul aflat n vrful stivei i
dup aceast operaie el ajunge vrful stivei;
- se poate scoate de pe stiv numai elementul aflat n vrful stivei i dup aceast
operaie n vrful stivei rmne elementul care a fost pus pe stiv naintea lui.
Vom numi stack tablou de tip int afectat stivei i next variabila care indic prima
poziie liber din stiv. Deci stack[0] este baza stivei iar stack[n] va fi vrful stivei. Vom
defini mai multe funcii asociate tabloului stack:
- push funcia care pune un element n stiv;
- pop funcia care scoate un element din stiv;
- clear funcia de iniializare a stivei; dup apelul ei stiva devine vid;
#define MAX 1000
static int stack[1000];
static next = 0;
void push(int x)
// pune pe stiva valoarea lui x
{ if (next < MAX)
stack [next++]=x;
else
printf (stiva este depasita\n);
}
int pop()
{ if (next >0)
else
}
void clear(void)
{
next=0;
}
// videaza stiva
65
7. Recursivitate
Spunem c o funcie C este recursiv dac ea se autoapeleaz nainte de a se reveni
din ea. Funcia se poate reapela fie direct, fie indirect (prin intermediul altor funcii).
La fiecare apel al unei funcii, parametrii i variabilele locale se aloc pe stiv ntr-o
zon independent. De asemenea, orice apel recursiv al unei funcii va conduce la o revenire
din funcie la instruciunea urmtoare apelului respectiv. La revenirea dintr-o funcie se
realizeaz curarea stivei, adic zona de pe stiv afectat la apel parametrilor i variabilelor
automatice se elibereaz.
Un exemplu simplu de funcie apelata recursiv este funcia de calcul al factorialului.
Putem defini recursiv funcia factorial astfel:
factorial(n)= 1,
dac n=0
factorial(n)=n*factorial(n-1), dac n>0
n limbajul C avem :
double factorial (int)
{ if (n= = 0)
else
}
return 1.0;
return n*factorial(n-1);
Observaii:
1o. n general, o funcie recursiv se poate realiza i nerecursiv, adic fr s se autoapeleze.
2o. De obicei, recursivitatea nu conduce nici la economie de memorie i nici la execuia mai
rapid a programelor. Ea permite ns o descriere mai compact i mai clar a funciilor.
Acest lucru rezult i din exemplul de mai sus de calcul al factorialului.
3o. n general, funciile recursive sunt de preferat pentru procese care se definesc recursiv.
Exist i excepii. De exemplu algoritmul de generare a permutrilor de n obiecte poate fi
descris recursiv astfel: avnd n memorie toate cele (n-1)! permutri, atunci permutrile de n
obiecte se genereaz nsernd pe n n toate poziiile posibile ale fiecrei permutri de n-1
obiecte. Dar ne aducem aminte c 10!=3628800 i capacitatea stivei se depete repede.
Exemple:
1) Programul determin recursiv cmmdc (algoritmul lui Euclid) a dou numere ntregi (de tip
long):
cmmdc (a,b) = b,
dac a%b =0 (restul mpririi lui a la b e zero)
cmmdc (a,b) = cmmdc (b,a%b),
n caz contrar.
#include <iostream.h>
#include <conio.h>
long cmmdc(long a, long b)
{ if (!(a % b))
return b;
else
return cmmdc(b, a % b);
}
void main(void)
{ long x,y;
clrscr();
cout << "dati un numar natural=";
cin >> x;
cout << "dati alt numar natural=";
66
cin >> y;
cout << "cmmdc(" << x << "," << y << ")=" << cmmdc (x,y);
}
Am folosit funciile de intrare / ieire cin i cout, imediat se observ modul lor de
utilizare.
2) Programul determin recursiv suma unor elemente de tablou unidimensional
a[1]+a[2]+ . . . + a[n]
#include <iostream.h>
#define MAX 100
int a[MAX];
// suma(n)= 0,
// suma(n)=suma(n-1) + a[n]
int suma(int n)
{ if (!n) return 0;
else return a[n]+suma(n-1);
}
daca n=0
daca n>0
void main(void)
{int n,i;
cout << "dati n= ";
cin >> n;
for (i=1; i<=n; i++)
{
cout << "a[" << i << "]= ";
cin >> a[i];
}
cout << "suma numerelor este " << suma(n);
}
3) Programul determin recursiv termenul al n-lea din irul lui Fibonacci definit dup cum
urmeaz:
fibonacci[0]=0
fibonacci[1]=1
fibonacci[n]=fibonacci[n-1]+fibonacci[n-2], dac n>1
#include<iostream.h>
long fibonacci (long n)
{if (!n)
return 0;
else if (n==1) return 1;
else
return fibonacci(n-1) + fibonacci(n-2);
}
void main (void)
{ long n;
cout << "dati n = ";
cin >> n;
cout << "fibo(" << n << ") =" << fibonacci (n);
}
M(n)= a[1],
dac n=1
M(n)= max { M(n-1), a[n] }, dac n>1
#include<iostream.h>
#define MAX(x,y) (x > y ? x : y)
int a[100];
int M(int n)
{ if (n= =1)
else
}
return a[1];
return MAX (M(n-1), a[n]);
void main(void)
{int n,i;
cout << "dati n=";
cin >> n;
for (i=1; i<=n; i++)
{
cout << "a[" << i << "]= ";
cin >> a[i];
}
cout << "maximul este " << M(n);
}
68
69
}
void main(void)
{ clrscr();
citire();
clrscr();
printf ("\n\nsir initial\n");
tipsir();
quik(1,n);
printf ("\n\nsir sortat\n");
tipsir();
getche();
}
70
{ int ziua;
char luna[11];
int anul;
} dc1, dc2, dc[13];
Formatul 3:
struct { lista_declaraii
} lista_variabile;
Acest format se foloseste dac nu vrem sa dm nume noului tip structurat i totodat dac nu
mai vrem s-l folosim. Deci nu vom mai pute declara alte date de tipul structurat nou introdus
pentru c tipul nu are nume.
Exemplu:
struct { int ziua;
char luna[11];
int anul;
} dc1, dc2, dc[13];
S-au declarat varibilele dc1, dc2 i tabloul dc avnd noul tip structurat utilizator dar nu se mai
dorete s declarm alte date de acest tip.
Observaii:
1o. Dac se folosete formatul 1 atunci pentru a declara date de tipul utilizator nou introdus se
folosete o construcie de forma:
struct nume_ structura lista_variabile;
Compilatorul aloc memorie varibilelor din lista de variabile, tratnd aceast construcie
ca i declaraiile obinuite.
2o. Componentele unei structuri pot fi ele nsele date structurate. O component care nu este
structurat se numete component elementar.
3o. Ca i n cazul celorlalte tipuri de variabile se pot defini structuri globale, statice sau
automatice. Structurile statice se declar precednd declaraiile lor prin cuvntul static, iar
cele externe prin cuvntul cheie extern.
4o. Elementele unei date de tip structur pot fi iniializate dup modelul iniializrii
variabilelor care au tipuri predefinite.
Exemple:
1) Introducem tipul utilizator data_calendaristica astfel:
struct data_calendaristica
{ int ziua;
char luna[11];
int anul;
};
pentru a iniializa o dat de tipul data_calendaristic vom scrie:
struct data_calendaristica dc1={31, martie, 1956};
2) Dac declarm un nou tip date_personale i n care vrem s folosim tipul
data_calendaristica, vom scrie:
72
struct date_personale
{ char nume[30];
char adresa[50];
struct data_calendaristica data_nasterii, data_angajarii;
};
int i, j, tablou[10];
Analog:
REAL x, y, z;
este identic cu declaraia:
float x, y, z;
2) typedef struct data_calendaristica
{
int
ziua;
char luna[11];
int
anul;
} DC;
Prin aceast declaraie se atribuie denumirea DC tipului structurat data_calendaristica.
n continuare putem declara date de tip DC:
DC data_nasterii, data_angajarii;
DC data_curenta ={31,august,1998};
3) typedef int *PI;
Prin aceast declaraie se introduce un sinonim pentru tipul pointer spre ntregi: int *.
Putem s declarm n continuare pointeri spre ntregi astfel:
PI p;
care este echivalent cu:
int *p;
4) Declaraia
typdef struct
{
double real;
double imaginar;
} COMPLEX;
introduce numele COMPLEX pentru datele de tip complex.
Funcia urmtoare returneaz modulul unui numr complex:
typedef struct
{
double real;
double imaginar;
} COMPLEX;
#include <math.h>
double modul (COMPLEX *x)
8.4. Uniune
75
// se foloseste us.uu.i
// se foloseste us.uu.f
// se foloseste us.uu.d
dreptunghi;
ptrat;
triunghi.
Programul citete datele pentru o figur geometric, calculeaz aria figurii respective
i scrie rezultatul:
La intrare se folosesc urmtoarele formate:
- pentru cerc
C raza;
- pentru dreptunghi D lungime laime;
- pentru ptrat
P latur;
- pentru triunghi
T latur latur latur;
- sfrit fiier
EOF.
n cazul triunghiului, se utilizeaz formula lui HERON pentru calculul ariei:
aria = sqrt (p*(p-a)(p-b)(p-b))
unde p este semiperimetrul, iar a, b, c sunt cele 3 laturi.
#include <stdio.h>
#include <math.h>
#define PI 3.14159265
#define EROARE -1
#define CERC 1
#define PATRAT 2
#define DREPT 3
#define TRIUNGHI 4
typedef struct
{
int tip;
union
{ double raza
double lp ;
double ld[2] ;
double lt[3] ;
} fig;
}FIG;
// tipul figurii
//
//
//
//
cerc
patrat
dreptunghi
triunghi
78
}
zfig.tip = CERC;
break;
case 'P':
// patrat
printf("se cere latura patratului in flotanta\n");
i = scanf("%lf",&zfig.fig.lp);
if( i !=1)
{ printf("se cere latura patratului in flotanta\n");
break;
}
zfig.tip = PATRAT;
break;
case 'D':
// dreptunghi
printf("se cer laturile dreptunghiului in flotanta\n");
i = scanf("%lf %lf",&zfig.fig.ld[0],&zfig.fig.ld[1]);
if(i != 2)
{ printf("se cer laturile dreptunghiului in flotanta\n");
break;
}
zfig.tip = DREPT;
break;
case 'T':
// triunghi
printf("se cer laturile triunghiului in flotanta\n");
i = scanf("%lf %lf %lf", &zfig.fig.lt[0], &zfig.fig.lt[1],&zfig.fig.lt[2]);
if(i != 3)
{ printf("se cer laturile triunghiului in flotanta\n");
break;
}
zfig.tip =TRI;
break;
printf("laturile nu formeaza un triunghi\n");
break;
default:
printf("se cere una din literele urmatoare\n");
printf("C pentru cerc\n");
printf("P pentru patrat\n");
printf("D pentru dreptunghi\n");
printf("T pentru triunghi\n");
} // sfarsit switch
switch (zfig.tip)
{case CERC:
// aria cercului
printf("raza=%g aria=%g\n", zfig.fig.raza, PI*zfig.fig.raza*zfig.fig.raza);
break;
case PATRAT:
// aria patratului
printf("latura =%g aria=%g\n",zfig.fig.lp, zfig.fig.lp*zfig.fig.lp);
break;
case DREPT:
// aria dreptunghiului
printf("lungimea =%g latimea =%g\n", zfig.fig.ld[0], zfig.fig.ld[1]);
printf("aria=%g\n", zfig.fig.ld[0]*zfig.fig.ld[1]);
break;
case TRIUNGHI:
// aria triunghiului
p=(zfig.fig.lt[0] + zfig.fig.lt[1] + zfig.fig.lt[2])/2;
if(p>zfig.fig.lt[0] && p>zfig.fig.lt[1] && p>zfig.fig.lt[2])
{p=p*(p-zfig.fig.lt[0])*(p-zfig.fig.lt[1])* (p-zfig.fig.lt[2]);
printf("a=%g b=%g c=%g\n", zfig.fig.lt[0], zfig.fig.lt[1], zfig.fig.lt[2]);
printf("aria = %g\n",sqrt(p));
}
else { printf ( laturile nu formeaza un triunghi);
break;
79
}
default :
8.5. Cmp
Limbajul C permite utilizatorului definirea i prelucrarea datelor pe bii. Utilizarea
datelor pe bii este legat de folosirea indicatorilor care de obicei sunt date care iau numai
dou valori 0 sau 1.
Nu este justificat ca un astfel de indicator s fie pstrat ca un ntreg pe 16 bii i nici
mcar pe un octet. Indicatorul poate fi pstrat pe un singur bit. n acest scop, limbajul C ofer
posibilitatea de a declara date care s se aloce pe bii (unul sau mai muli bii). Acest lucru i
gsete aplicare n programele de sistem. Astfel, de exemplu, atributele variabilelor dintr-o
tabel de simboluri pot fi pstrate pe bii, ceea ce conduce la o economisire substanial a
memoriei ocupate de tabela respectiv.
Prin cmp nelegem un ir de bii adiaceni coninui ntr-un cuvnt calculator.
Cmpurile se grupeaz formnd o structur.
Un cmp se declar ca i o component a unei structuri i el are tipul unsigned (ntreg
fr semn). Totodat n declaraia cmpului se indic i dimensiunea lui n bii.
n general, o structur cu componente cmpuri are forma:
struct
{ camp1;
...
campn;
} nume;
unde campi (i=1,...,n) are unul din formatele de mai jos:
unsigned nume : lungime_n_bii
sau
: lungime_n_bii
Exemplu:
struct
{ unsigned a:1;
unsigned b:1;
unsigned c:2;
unsigned d:2;
unsigned e:3;
} indicatori;
Data indicatori se aloc ntr-un cuvnt calculator, adic pe 16 bii. Componentele ei
sunt:
-
a un bit;
b un bit;
c doi bii;
d doi bii;
e trei bii.
80
indicatori.a
indicatori.b
indicatori.c
indicatori.d
indicatori.e
a
b
c
d
e
Observaii:
1o. Dac un cmp nu poate fi alocat n limitele unui cuvnt, el se aloc n ntregime n
cuvntul urmtor.
2o. Nici un cmp nu poate avea o dimensiune mai mare dect 16 bii.
3o. Formatul fr nume (al doilea format) pentru cmp se folosete pentru cadraje. Acest lucru
este util atunci cnd sunt zone de bii neutilizate n cadrul unui cuvnt. De asemenea,
utilizarea formatului cu lungime egal cu zero permite ca alocarea cmpurilor urmtoare lui s
se fac n cuvntul urmtor.
4o. O structur care are i componente cmpuri poate avea i componente obinuite.
5o. Nu se pot defini tablouri de cmpuri.
6o. Unui cmp nu i se poate aplica operatorul adres.
Cmpurile se utilizeaz frecvent la scrierea unor programe de sistem, cum ar fi : drivere
pentru periferice, compilatoare, etc.
Utilizarea cmpurilor poate conduce la programe cu o portabilitate redus. Totodat,
accesul la date pe bii conduce la creterea numrului de operaii, fiind necesare deplasri i
operaii pe bii suplimentare, fapt ce poate conduce att la creterea timpului de execuie a
programelor, ct i la creterea memoriei utilizate. Ori datele pe bii se folosesc chiar n ideea
de a economisi memorie.
ian ia valoarea 1
feb ia valoarea 2
...
dec ia valoarea 12
A doua construcie declar data luna_calendaristica de tipul luna. Ei i se pot atribui
valori prin expresii de atribuire de forma:
luna_calendaristica = mar sau
luna_calendaristica = mai + 4
2) typedef enum {luni, marti,miercuri,joi,vineri,sambata,duminica} ZI;
ZI z;
Variabila z este de tip ZI. Se poate utiliza n expresii de forma:
z=marti;
if(z<sambata)
// trateaza ziua de lucru
else
// trateaza zi de odihna
83
9. Liste
9.1. Date structurate definite recursiv
Limbajul C permite definirea de tipuri structurate recursiv (autorefereniate). Acest
lucru se face cu ajutorul pointerilor, i anume un element al structurii poate s fie un pointer
spre tipul de dat introdus prin structura respectiv:
struct nume
{ declaratii
struct nume *p;
declaratii
};
Un tip definit ca mai sus se spune c este un tip autoreferit sau recursiv. O dat
structurat declarat printr-un astfel de tip se spune c este autoreferit sau recursiv. Datele
structurate recursive au numeroase aplicaii n prelucrarea listelor nlnuite i arborescente.
Dou tipuri structurate t1 i t2 pot s conin fiecare un pointer spre celalalt. n acest
caz se va proceda ca mai jos:
struct t1;
struct t2
{ declaratii
struct t1 *pt1;
declaratii
};
struct t1
{ declaratii
struct t2 *pt2;
declaratii
};
capul
capul
capul
b)
NOD *creaza(void)
// functia de creare, intoarce adresa capului
{ NOD *cap,*p,*pc;
int i,lung;
printf("\n\n\n\ creare lista simplu inlantuita\n\n");
lung = sizeof(NOD);
pc=(NOD *)malloc(lung);
// pc este un pointer curent, in el se vor pune adresel noi
cap=pc;
printf("dati informatia elementului : ");
scanf ("%d",&i);
while (i>0)
{ p=pc;
p->nr=i;
86
p->patrat=i*i;
pc=(NOD *)malloc(lung);
// se cere o noua adresa de memorie
p->leg=pc;
// se leaga pointerul leg la noua adresa
printf("dati informatia elementului : ");
scanf ("%d",&i);
}
p->leg=NULL;
free(pc);
return cap;
}
void afisare(NOD *p)
// functia de afisare a listei
{
while (p != NULL)
// cat timp n-am ajuns la ultimul nod
{ printf ("\n numarul %d si patratul sau %d", p->nr,p->patrat);
p=p->leg;
// trecerea la urmatorul nod al listei
}
}
void main (void)
{ NOD *capul;
clrscr();
capul=creaza();
afisare(capul);
getch();
}
// functia principala
87
88
Exemple:
1)
char nume_fisier[ ]=fis1.dat;
int df;
df = open (nume_fisier, O_RDONLY);
Prin apelul de mai sus se deschide n citire fiierul fis1.dat din directorul curent.
2)
int df;
df = open (c:\\borlandc\\help.txt,O_APPEND);
Funcia folosit pentru operaia de citire dintr-un fiier n memorie se numete read i
are prototipul urmtor:
int read (int df, void *buf, unsigned lung);
unde:
- df este descriptorul de fiier a crui valoare a fost definit la deschidere;
- buf este pointerul spre zona de memorie n care se recepioneaz nregistrarea care se
citete;
- lung este lungimea n octei a inregistrrii citite.
Observaii:
1o. La fiecare apel funcia returneaz nregistrarea curent. La primul apel se citete prima
nregistrare din fiier, la al doilea apel se citete a doua, etc. Ordinea nregistrrilor n fiier
este cea definit la crearea fiierului.
2o. La un apel al funciei read se citesc cel mult lung octei nregistrarea avnd definit
lungimea la scrierea n fiier. Funcia rentoarce numrul de octei citii, 0(zero) la sfrit de
fiier, sau 1 la eroare. De obicei se folosesc frecvent nregistrri de 512 octei sau chiar mai
mari.
3o. Funcia read poate fi folosit pentru a citi de la intrarea standard. n acest caz,
descriptorul de fiier este 0 (stdin are 0, stdout are 1, stderr are 2 stdprn are 3 stdaux are 4).
Programatorul nu trebuie s deschid fiierele standard deoarece ele sunt deschise automat la
lansarea n execuie a programului.
4o. Utilizarea funciei read, presupune includerea fiierului io.h.
origine
Observaii:
1o. Funcia returneaz poziia capului de citire/scriere fa de nceputul fiierului n numr de
octei sau 1L la eroare.
2o. Funcia nu realizeaz nici un transfer de informaie ci doar poziioneaz capul de
citire/scriere n fiier. Deci pentru transfer e nevoie de funciile read sau write.
3o. Utilizarea funciei presupune includerea fiierului io.h.
4o. Apelul lseek (df, 0L, 0) permite o poziionare la nceput de fiier,
iar apelul lseek (df, 0L, 2) permite o poziionare la sfrit de fiier.
// se inchide fisierul
91
3) programul urmtor copiaz intrarea standard la ieierea standard folosind o zon tampon
de 80 de caractere:
#define LZT 80
#include <io.h>
void main (void)
// copiaza intrarea standard la iesirea standard
{ char zona_tampon[LZT];
int i;
while ((i = read (0, zona_tampon, LZT)) > 0) write (1, zt, i);
}
2o. Dac se deschide un fiier existent cu modul w, atunci coninutul vechi al fiierului se
pierde i se va crea unul nou cu acelai nume.
3o. Menionm c, stdin, stdout, stderr, stdaux i stdprn sunt pointeri spre tipul FILE i
permit ca funciile de nivel superior de prelucrare a fiierelor s poat trata intrarea standard i
ieirile standard pe terminal i imprimant la fel ca i fiierele pe celelalte suporturi. Singura
deosebire const n aceea c aceste fiiere nu se deschid i nici nu se nchid de ctre
programator. Ele sunt deschise automat la lansarea n execuie a programului i se nchid la
apelul funciei exit.
4o. Apelul funciei se realizeaz prin construcia:
FILE *pf;
pf = fopen (FIS1.DAT,w);
Fiierele pot fi scrise i citite caracter cu caracter, folosind dou funcii simple:
putc pentru scriere;
getc pentru citire.
Funcia putc are prototipul:
93
1) Programul urmtor copiaz intrarea standard la ieirea standard stdout, folosind funciile
getc i putc.
#include <stdio.h>
void main (void)
{ int c;
while (( c = getc (stdin)) != EOF) putc (c, stdout);
}
3) Programul urmtor scrie la ieirea stdout caracterele unui fiier a crui cale este
argumentul din linia de comand. Dac nu exist un argument n linia de comand, atunci
se citete de la intrarea standard.
#include <stdio.h>
void main (int argc, char *argv[ ] )
{ FILE *pf;
int c;
if (argc = = 1)
pf = stdin;
// nu exista argument in linia de comanda
else
// se deschide fisierul a carui cale se afla in argv[1]
if (( pf = fopen (*++argv,r)) = = NULL)
{ printf (nu se poate deschide fisierul %s\n,*argv);
exit (1);
}
while (( c = getc (pf)) != EOF)
putchar(c);
exit (0);
}
unde :
- pf este un pointer spre tipul FILE i valoarea lui a fost definit prin apelul funciei fopen;
definete fiierul din care se face citirea;
- ceilali parametri sunt identici cu cei utilizai la apelul funciei scanf.
94
Funcia fscanf, ca i funcia scanf, returneaz numrul cmpurilor citite din fiier. La
ntlnirea sfritului de fiier se returneaz valoarea EOF definit n fiierul stdio.h. Pentru pf
= stdin, funcia fscanf este identic cu scanf.
Funcia fprintf , ca i funcia printf, returneaz numrul caracterelor scrise n fiier
sau 1 n caz de eroare. Pentru pf = stdout, funcia fprintf este identic cu printf. De
asemenea, se pot utiliza pointerii standard obisnuii: stderr, stdaux, stdprn.
Exemplu:
Vom scrie un program cu ajutorul cruia se va crea un fiier cu numele "FIS.DAT" i care
conine nregistrri cu numele, prenumele i adresa unor persoane.
#include <stdio.h>
void main(void)
{
int n=0, i=1;
// n este numarul de nregistrari ce se va scrie in fisier
char nume[25], prenume[30], adresa[50];
FILE *pf;
printf("\n Dati numarul de inregistrari n= ");
scanf("%d",&n);
pf=fopen("FIS.DAT","w");
if (pf = = NULL)
{ printf ("Eroare la deschidere");
return;
}
do
{
printf("\n Nume : ");
scanf("%s",nume);
printf("\n Prenume : ");
scanf("%s",prenume);
printf("\n Adresa : ");
scanf("%s",adresa);
fprintf(pf,"%s %s %s", nume, prenume, adresa);
i++;
} while (i<=n);
fclose(pf);
}
95
Funcia fputs scrie ntr-un fiier un ir de caractere care se termin prin \0. Ea are
prototipul:
int fputs (const char *s, FILE *pf);
unde:
- s este pointerul spre zona care conine irul de caractere care se scrie;
- pf este pointerul spre zona care conine irul de caractere care se scrie.
Funcia fputs returneaz codul ASCII al ultimului caracter scris sau 1 n caz de
eroare.
Aceste funcii sunt realizate folosind funcia getc pentru fgets i putc pentru fputs.
Pentru a citi de la intrarea standard stdin, se poate folosi funcia gets, care nu mai are
parametrii pf i n. Parametrul pf este implicit stdin. Funcia gets citete caracterele de la
intrarea standard pn la ntlnirea caracterului \n care nu mai este pstrat n zona spre care
pointeaz s. irul de caractere citit se termin i n acest caz prin \0.
n mod analog, pentru a scrie la ieirea standard stdout se poate folosi funcia puts,
care nu mai are parametrul pf, acesta fiind implicit stdout. n rest, funcia puts este la fel ca i
funcia fputs.
c au o lungime fix. n mod analog, la scriere se transfer din zona tampon un numr de
articole de lungime fix. Cele dou funcii au prototipurile de mai jos:
unsigned fread (void *ptr, unsigned dim, unsigned nrart, FILE *pf);
unde:
- ptr
- dim
- nrart
- pf
este pointerul spre zona tampon ce conine articolele citite (nregistrarea citit);
este un ntreg ce reprezint lungimea unui articol;
este un ntreg ce reprezint numrul articolelor care se transfer;
este un pointer spre tipul FILE care definete fiierul din care se face citirea.
unsigned fwrite (const void *ptr, unsigned dim, unsigned nrart, FILE *pf);
denumire
TELVIZOR
um
buc
cod
0001
pret
3000000
cantitate
200
#include <stdio.h>
#define MAX 60
typedef struct { char tip[2];
char den[MAX];
int val;
char unit[3];
long cod;
float pret;
float cant;
} ARTICOL;
union {
}buf;
int cit (ARTICOL *str)
// citeste datele de la intrarea standard si le scrie in
{
// structura spre care pointeaza str
int nr,c;
float x,y;
while ((nr = scanf("%1s %60s %3d %2s %ld", str -> tip, str ->den,
&str ->val, str -> unit,&str -> cod)) != 5|| scanf("%f %f", &x,&y) != 2)
{
if(nr = = EOF) return (EOF);
printf ("rind eronat ; se reia\n");
// avans pina la newline
while ((c = getchar ()) != '\n' && c != EOF);
if (c == EOF) return (EOF);
}
// sfarsit while
str -> pret = x;
97
// sfarsit cit
98
99
Exemplu:
Programul urmtor citete un fiier i l rescrie schimbnd literele mari cu litere mici.
Cile spre cele dou fiiere (surs i destinaie) sunt argumente n linia de comand:
argv[1]este un pointer spre fiierul surs;
argv[2]este un pointer spre fiierul destinaie.
#include <stdio.h>
#include <ctype.h>
void main ( int argc, char *argv[ ] )
{ FILE *pf1, *pf2;
int c;
if (argc != 3)
{ printf (linia de comanda eronata\n);
exit(1);
}
if (( pf1 = fopen (argv[1],r) ) = = NULL
{ printf (nu se poate deschide fisierul%s\n, argv[1]);
exit(1);
}
if (( pf2 = fopen (argv[2],w) ) = = NULL
{ printf (nu se poate deschide fisierul%s\n, argv[2]);
exit(1);
}
while (( c = getc (pf1)) != EOF)
if (isascii(c) && isupper (c))
putc (c - A + a, pf2);
// c este codul unei litere mari
else
putc(c, pf2);
// c este codul unei litere mici
fclose (pf1);
fclose (pf2);
// afisarea fisierului destinatie
if (( pf2 = fopen (argv[2], r) = = NULL)
{ printf (nu se poate deschide in citire:%s\n, argv[2]);
exit(1);
}
while ((c = getc(pf2)) != EOF) putchar (c);
fclose (pf2);
}
11.3. Conversii
100
irul de cifre spre care pointeaz ptr este convertit din ntreg zecimal n ntreg
binar de tip int.
Observaie:
1o. Funcia returneaz rezultatul acestei conversii.
Funcia atol are prototipul:
long atol (const char *ptr);
unde:
-
Efectul:
-
ptr este un pointer spre o zon de tip caracter ce conine cifre zecimale care
sunt, eventual, precedate de semnul minus;
irul de cifre spre care pointeaz ptr este convertit din ntreg zecimal n ntreg
binar de tip long.
Observaie:
1o. Funcia returneaz rezultatul acestei conversii.
Funcia atof are prototipul:
double atof (const char *ptr);
unde:
-
ptr este un pointer spre o zon de tip caracter ce conine cifre zecimale care
sunt, eventual, precedate de semnul minus (poate conine marca zecimal);
Efectul:
-
irul de cifre spre care pointeaz ptr este convertit n virgul flotant dubl
precizie.
Observaie:
1o. Funcia returneaz rezultatul acestei conversii.
Funcia itoa are prototipul:
101
valoarea parametrului val se convertete din ntreg binar de tip int n baza de
numeraie definit de parametrul baza i se pstreaz n zona spre care pointeaz
sir.
Observaie:
1o. Funcia returneaz pointerul sir.
Funcia ltoa are prototipul:
char *ltoa (long val, char *sir, int baza)
Efectul:
-
valoarea parametrului val se convertete din ntreg binar de tip long n baza de
numeraie definit de parametrul baza i se pstreaz n zona spre care pointeaz
sir.
Observaie:
1o. Funcia returneaz pointerul sir.
prima funcie copiaz irul de caractere spre care pointeaz sursa n zona spre care
pointeaz dest;
a doua funcie realizeaz acelai lucru, dar copiaz cel mult primii n octei din
surs;
ambele funcii returneaz valoarea pointerului dest.
Funcii de concatenare:
int strcmp (const char *dest, const char *sursa);
char *strncat (const char *dest, const char *sursa, unsigned n);
prima funcie copiaz irul spre care pointeaz sursa la sfritul irului din zona
spre care pointeaz dest;
a doua funcie realizeaz acelai lucru, dar se copiaz cel mult primii n octei din
zona spre care pointeaz sursa;
ambele funcii returneaz valoarea pointerului dest.
Funcii de comparare:
int strcmp (const char *sir1, const char *sir2);
int stricmp (const char *sir1, const char *sir2);
int strncmp (const char *sir1, const char *sir2, unsigned n);
102
int strnicmp (const char *sir1, const char *sir2, unsigned n);
-
aceste funcii compar irurile de caractere din zonele spre care pointeaz pointerii
sir1 i sir2;
Observaie:
1o. Fie irurile s1 i s2 de lungime l1 i l2. Atunci cele dou iruri sunt egale dac:
l1=l2 ( au aceeai lungime);
s1[k] = s2 [k] pentru k=0,1,...,l1
2o. irul s1 este mai mic dect irul s2 dac exist un j, j 0 i j min (l1, l2), astfel nct:
s1[j] < s2[j];
s1[k] = s2[k], pentru k=0,1, . . . , j-1.
3o. irul s1 este mai mare dect irul s2 dac exist un j, j 0 i j min(l1, l2), astfel inct:
s1[j] > s2[j];
s1[k] = s2[k], pentru k=0,1, . . . , j-1.
Funcia lungime:
unsigned strlen (const char *sir);
-
log10
sqrt
ceil
floor
fabs
sinh
cosh
tanh
-lg;
-rdcina ptrat;
-returneaz cel mai mare ntreg mai mare sau egal cu x (partea ntreag);
-returneaz cel mai mare ntreg mai mic sau egal cu x;
-valoarea absolut;
-sinus hiperbolic;
-cosinus hiperbolic;
-tangent hiperbolic;
Alte funcii:
double atan2 (double y, double x); - returneaz arctg(y/x);
double pow (double x, double y);
- returneaz xy;
double cabs (struct complex z);
- returneaz modulul numrului
complex;
double poly (double x, int n, double c[ ] ) - returneaz valoarea polinomului
de grad n n punctul x, coeficienii
sunt c[0], . . . c[n].
Funciile care urmeaz au prototipul n fiierele stdlib.h i math.h:
int abs (int n); - returneaz valoarea absolut din ntregul n;
long labs (long n);
- returneaz valoarea absolut din ntregul long n;
105
Culoare
Constant simbolic
Valoare
negru
albastru
verde
turcoaz
rou
purpuriu
maro
gri deschis
gri nchis
albastru deschis
BLACK
BLUE
GREEN
CYAN
RED
MAGENTA
BROWN
LIGHTGRAY
DARKGRAY
LIGHTBLUE
verde deschis
LIGHTGREEN
turcoaz deschis
LIGHTCYAN
rou dechis
LIGHTRED
purpuriu magenta
galben
alb
LIGHTMAGENTA
YELLOW
WHITE
clipire
BLINK
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
128
Constant simbolic
Valoare
BW40
C40
BW80
C80
MONO
0
1
2
3
7
C4350
64
LASTMODE
-1
Modul MONO se poate seta pe un adaptor monocolor. Celelalte moduri se pot seta pe
adaptoare color.
unde:
-(stanga, sus) = coordonatele colului din stnga al ferestrei;
-(dreapta, jos) = coordonatele colului din dreapta jos.
La un moment dat o singur fereastr este activ i anume aceea definit de ultimul
apel al funciei window. Funciile de gestionare a ecranului n mod text acioneaz
ntotdeauna asupra ferestrei active. Dup setarea modului text cu ajutorul funciei textmode
este activ tot ecranul.
Menionm c funcia window nu are nici un efect dac parametrii de la apel sunt
eronai.
void clrscr(void);
fereastra activ (sau tot ecranul) devine activ; fondul are culoarea definit prin culoarea
de fond curent;
clrscr poziioneaz cursorul n poziia (1,1) a fiecrei ferestre.
Exist cazuri cnd se dorete ascunderea cursorului. Acest lucru se poate realiza printr-o
secven special n care se utilizeaz funcia geninterrupt.
void ascundcursor (void) // face invizibil cursorul
{ _AH = 1;
_CH = 0x20;
geninterrupt (0x10);
}
108
_CL = 7;
geinterrupt (0x10);
}
char
char
char
char
char
char
char
char
char
char
char
winleft;
wintop;
winright;
winbottom;
attribute;
normattr;
currmode;
screenheight;
screenwidth;
curx;
cury;
109
int movetext ( int stanga, int sus, int dreapta, int jos,
int stanga_dest, int dreapta_dest );
- copiaz un text dintr-o poziie n alta;
- returneaz: 1 dac textul s-a copiat cu succes i 0 n caz de eroare.
110
Textele dintr-o zon dreptunghiular a ecranului pot fi salvate sau citite dintr-o zon
de memorie cu ajutorul funciilor puttext i gettext i au prototipurile:
int gettext (int stanga, int sus, int dreapta, int jos, void *destinatie);
unde
- primii patru parametrii definesc fereastra unde se afl textul de salvat;
- destinatie este pointerul spre zona de memorie n care se salveaz textul.
i
int puttext (int stanga, int sus, int dreapta, int jos, void *sursa);
unde
- primii patru parametrii definesc fereastra unde se va scrie pe ecran textul preluat din
memorie;
- sursa este pointerul spre zona de memorie din care se transfer textul.
Ele returneaz:
- 1 la copiere cu succes;
- 0 la eroare.
Observaie:
1o. Fiecare caracter de pe ecran se pstreaz pe doi octei:
- pe un octet codul caracterului;
- pe octetul urmtor atributul caracterului.
Exemple:
1) Programul urmtor seteaz o fereastr i modurile video alb/negru.
#include <conio.h>
void main (void)
{ textmode (BW80);
window (10,4,60,4);
clrscr ();
lowvideo ();
cputs(lowvideo);
normvideo ();
cputs (normvideo);
textmode (LASTMODE);
cprintf (\n\r Acionati o tasta pentru a continua:);
getch ();
}
2) Programul urmtor afieaz toate combinaiile de culori posibile pentru fond i caractere
(adaptor EGA/VGA).
#include <conio.h>
#include <stdio.h>
void main (void)
{ static char *tculoare [ ] = {
BLACK
negru,
111
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BLUE
albastru,
GREEN
verde,
CYAN
turcoaz,
RED
rosu,
MAGENTA
purpuriu,
BROWN
maro,
LIGHTGRAY
gri deschis,
DARKGRAY
gri inchis,
LIGHTBLUE
albastru deschis,
LIGHTGREEN
verde deschis,
LIGHTCYAN
turcoaz deschis,
LIGHTRED
rosu dechis,
LIGHTMAGENTA purpuriu magenta,
YELLOW
galben,
WHITE
alb};
int i,j,k;
struct text_info atribut;
gettextinfo (&atribut);
for (i = 0; i < 8; i++ )
// i alege culoarea fondului
{ window (3,2,60,20);
k=2;
textbackground (i);
clrscr();
for (j=0; j <10; j++, k++)
// j alege culoarea caracterului
{ textcolor (j);
gotoxy (2,k);
if (i = = j) continue;
cputs (tculoare[j]);
}
gotoxy (1,18);
printf (actionati o tasta pentru contiuare\n);
getch();
}
window (atribut.winleft, atribut.wintop, atribut.winright, atribut.winbottom);
textattr (atribut. attribute);
clrscr();
}
112
simbol
1
2
3
4
5
6
7
8
9
10
-
CGA
MCGA
EGA
EGA64
EGAMONO
IBM8514
HERCMONO
ATT400
VGA
PC3270
simbol
0
CGAC0
1
CGAC1
2
CGAC2
3
CGAC3
corespunznd toate pentru o rezoluie de 320*200 de pixeli i permit 4 culori
4
CGAHI
are o rezoluie de 640*200 puncte i lucreaz numai alb/negru.
pentru EGA
valoare
simbol
113
0
1
pentru VGA
valoare
0
1
2
EGALO
EGAHI
VGALO
VGAMED
VGAHI
are 640*200
are 640*350
are 640*480
simbol
Valorile spre care pointez graphdriver definesc nite funcii standard corespunztoare
adaptorului grafic. Aceste funcii se numesc drivere. Ele se afl n subdirectorul BGI.
Funcia detectgraph detecteaz adaptorul grafic prezent la calculator i pstreaz
valoarea corespunztoare acestuia n zona spre care pointeaz graphdriver. Modul grafic se
definete n aa fel nct el s fie cel mai performant pentru adaptorul grafic curent. Cele mai
utilizate adaptoare sunt de tip EGA (calculatoare mai vechi) i VGA i SVGA (calculatoarele
mai noi).
Apelul funciei detectgraph trebuie s fie urmat de apelul funciei initgraph. Aceasta
seteaz modul grafic n conformitate cu parametrii stabilii de apelul prealabil al funciei
detectgraph.
Funcia initgraph are prototipul:
void far initgraph (int far * graphdriver, int far *graphmode, char far *cale);
unde:
-
Exemplu:
Setarea n mod implicit a modului grafic:
int driver, mod_grafic;
...
detectgraph (&driver, &mod_grafic);
initgraph(&driver, &mod_grafic, C:\\BORLANDC\\BGI);
Dup apelul funciei initgraph se pot utiliza celelalte funcii standard de gestiune
grafic a ecranului.
Din modul grafic se poate iei apelnd funcia closegraph care are prototipul:
void closegraph (void);
Funcia initgraph mai poate fi apelat i folosind constanta simbolic DETECT astfel:
114
Exemplu:
Exemplu:
index este un ntreg din {0,. . . , 15} i reprezint indexul n tabloul care definete
paleta pentru culoarea care se modic;
cod este un ntreg din intervalul {0, 1,. . . , 63} i reprezint codul culorii care o
nlocuiete n palet pe cea veche.
setpalette (DARKGRAY, 45);
int
int
int
int
far
far
far
far
getmaxx (void);
getmaxy (void);
getx (void);
gety (void);
Exemplu:
#include <graphics.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
117
118
do { i-- }
119
Obs. Procedurile de mai sus nu sunt necesare ntotdeauna, acest fapt depinznd de complexitatea
problemei. Oricum trebuie reinut cadrul de rezolvare al acestui tip de problem.
Problem rezolvat
Se d o mulime de valori reale X={x1, . . .,xn}. Se cere submulimea YX astfel ca y /yY,
s fie maxim.
Evident c problema este foarte simpl (n Y trebuie introduse elementele strict pozitive
din X; evitm s mai construim procedurile ALEGE, POSIBIL, ADAUG) i vom da rezolvarea
ei n pseudocod:
procedure SUMA (n,X,Y,k)
integer n,X(n),Y(n),k
k:=0;
for i=1,n do
if x(i) > 0 then k:=k+1;
y(k):=x(i)
endif;
endfor;
return;
end procedure
Probleme propuse
14.2.1.
Se dau n iruri S1,S2,...,Sn ordonate cresctor, de lungimi L1,L2, ...,Ln. S se obin un ir S de
lungime L1+L2+...+Ln cu toate elementele celor n iruri ordonate cresctor (problem de
interclasare).
Indicaie: Vom interclasa succesiv cte dou iruri n final obinnd irul ordonat cresctor.
Complexitatea interclasrii a dou iruri A i B de lungimi a i b depinde direct proporional de
a+b (pentru c se fac a+b deplasri de elemente). Se pune problema gsirii ordinii optime n care
trebuie efectuate aceste interclasri astfel ca numrul total de deplasri s fie minim.
Vom da un exemplu pentru a lmuri mai bine lucrurile:
fie 3 iruri de lungimi (90,40,10). Interclasm irul S1 cu S2 apoi irul rezultat cu S3; putem nota
acest lucru formal prin (S1+S2)+S3. Se obin (90+40) + (130+10)=270 deplasri. Dac vom
interclasa irurile n ordinea (S2+S3)+S1 vom obine (40+10)+ (50+90)=190 de deplasri. De aici
concluzia c ntotdeauna vom interclasa irurile de lungimi minime din irurile rmase.
14.2.2.
121
array x(n);
k:=1;
while k>0 do
cod:=0;
while ([mai exist o valoare din Sk netestat] and cod=0)
x(k):=;
if k(x(1),...,x(k)) then cod:=1;
endif;
endwhile;
if cod=0 then
k:=k-1
else
if k=n then write (x(1),...,x(n))
else k:=k+1
endif
endif;
endwhile;
return;
end procedure
Vom face cteva precizri:
1o. Predicatul k(x1, . . . , xk) reprezint condiiile de continuare pentru x1, . . . , xk;
2o. Cod este o valoare ce indic ndeplinirea/nendeplinirea condiiilor de continuare;
3o. Dac predicatul k(x1, . . . , xk) este adevrat k {1,...,n} atunci se vor afla toi vectorii
din S;
4o. Backtracking poate fi uor reprezentat pe un arbore construit astfel:
- nivelul 1 conine rdcina;
- din orice nod de pe nivelul k pleac sk muchii spre nivelul k+1, etichetate cu cele
sk elemente ale lui Sk;
- nivelul n+1 va conine s1*s2*. . .* sn noduri frunz;
- pentru fiecare nod de pe nivelul n+1 etichetele muchiilor coninute n drumul ce
leag rdcina de acest nod reprezint o soluie posibil;
Dac mulimile Sk reprezint progresii aritmetice atunci algoritmul general se modific astfel:
procedure BTA(n,a,b,h)
integer n;
array primul(n),ultimul(n),ratia(n),x(n);
k:=1;
x(1):=primul(1)-ratia(1);
while k>0 do
cod:=0;
while ( x(k)+ratia(k) ultimul(k) and cod=0 )
x(k):=x(k)+ratia(k);
if k(x(1),...,x(k)) then cod:=1 endif;
endwhile;
if cod=0 then
k:=k-1
else
if k=n then write (x(1),...,x(n))
else k:=k+1
123
x(k):=a(k)-h(k)
endif
endif;
endwhile;
return;
end procedure
unde:
- primul(n) reprezint vectorul primilor termeni ai progresiilor aritmetice;
- ultimul(n) reprezint vectorul ultimilor termeni ai progresiilor aritmetice;
- ratia(n) reprezint vectorul raiilor progresiilor aritmetice;
De reinut cele dou avantaje ale acestui procedeu:
evitarea imbricrii unui numr oarecare de cicluri aprioric variabil (n algoritmul propus se
imbric doar dou cicluri pretestate while);
evitarea construirii spaiului tuturor soluiilor posibile S1xS2x . . . xSn.
Problem rezolvat
n cte moduri se pot aranja 8 dame pe tabla de ah astfel nct s nu se "bat" reciproc. S se
foloseasc al doilea algoritm dintre cei menionai anterior.
Prima variant
Acest program respect algoritmul anterior cu unele mici modificri. Facem precizarea c
vectorul x conine n componenta x[i] numrul coloanei de pe tabla de ah pe care se va afla
dama de pe linia i. Tiprirea va reprezenta o permutare (din cele 8! soluii posibile). Se vor afla
92 de soluii. Lsm pe seama cititorului s deduc analogiile i diferenele ntre algoritm i
program.
#include <stdio.h>
#include <math.h>
void main (void)
{ int x[9],cod,k,i,nr;
k=1; x[1]=0;nr=0;
while (k>0)
{ cod=0;
while (((x[k]+1)<=8)&&(cod= =0))
{ x[k]++;
if ((k= =1) && (x[k]= =1)) cod=1;
else { i=1; cod=1;
while ((cod==1)&&(i<k))
{ if ((x[i]==x[k])||(abs(x[i]-x[k])==k-i)) cod=0;
i++; } // sfarsit while
} // sfarsit if
} // sfarsit while
if (cod= =0) k--;
else {if (k= =8)
{ printf("\n%3d. ",++nr);
for (i=1;i<9;printf("%d ",x[i++]));
}}
else x[++k]=0;
}
}
124
A doua variant:
#include <stdio.h>
#include <math.h>
#define n 100
int x[100],cod,k,nc,nsol;
int Verifica(void)
{ int i,cod1;
// cod1=1 conditiile de continuare
cod1=1;
// sunt verificate
if (k>1)
// cod1=0 in caz contrar;
for(i=1; i<= (k-1); i++)
{ if (x[k]= =x[i]) cod1=0;
if (abs(x[k]-x[i]) = = k-i) cod1=0; // (*)
}
return cod1;
}
void ScrieSolutie(void)
{ int i;
printf("\n%3d. ",++nsol);
for (i=1; i<=nc; printf("%3d",x[i++]));
}
void CitesteDate(void)
{ int i;
nsol=0;
clrscr();
nc=n+1;
while ((nc>n) || (nc<0))
{ printf ("Dati n = ");
scanf ("%d",&nc);
};
}
void main (void)
{ CitesteDate();
k=1; x[1]=0;
while (k>0)
{ cod=0;
while (((x[k]+1) <= nc) && (cod= =0))
{x[k]++;
cod=Verifica();
}
if (cod= =0) k--;
else {if (k==nc) ScrieSolutie();
else x[++k]=0;
}
}
}
A doua variant este modular, mai uor de neles i generalizeaz problema damelor
pn la tabla de ah de 100x100. Lsm pe seama cititorului modificarea funciei ScrieSolutie
pentru a afia n mod grafic tabla de ah.
Dac n funcia Verifica se terge instruciunea notat cu (*) atunci se obin toate
permutrile de n obiecte.
Probleme propuse
125
14.3.1
S se rezolve problema turelor de ah dup al doilea algoritm. n cte moduri se pot aranja n
turnuri pe tabla de ah astfel nct s nu se "bat" reciproc.
14.3.2.
S se afieze poziiile succesive ale unui cal pe tabla de ah, pornind dintr-o poziie dat, astfel
nct s fie atinse toate csuele tablei de ah.
14.3.3.
Avnd un fiier cu o mulime de cuvinte din limba romn de aceeai lungime k s se scrie un
program C care afieaz toate careurile rebusiste fr puncte negre. ( Problema e fascinant
implicnd i cunotine gramaticale dar i cunoscnd faptul c nu s-au construit careuri de 10x10
fr puncte negre manual i nici cu ajutorul calculatorului; se poate ncerca apoi i cu k:=11,12, .
. .).
14.3.4.
Un intreprinztor dispune de un capital C i are n variante de investiii. Pentru fiecare investiie i
cunoate fondurile de investiie fi precum i beneficiile bi. Se cere un program care s deduc
toate variantele posibile de investiii al intreprinztorului. Se mai dau i condiiile Cci i {1, .
. . ,n}.
14.3.5.
Avnd un graf neorientat caracterizat prin matricea costurilor s se determine prin bactraking
circuitul de cost minim pornind dintr-un vrf dat.
14.3.6.
Avnd un graf neorientat caracterizat prin matricea de inciden a vrfurilor s se determine prin
bactraking mulimile interior stabile maximale.
14.3.7.
S se determine toate cuvintele binare de lungime 10 care conin exact 5 cifre de 1.
14.3.8.
S se determine toate cuvintele binare de lungime 10 care conin cel mult 5 cifre de 1.
14.3.9.
S se determine toate cuvintele din {a,b,c}* (mulimea tuturor cuvintelor peste alfabetul se
noteaz cu * ) de lungime 10 care conin exact 2 simboluri 'a'; 3 simboluri 'b' i 5 simboluri 'c'.
14.3.10.
S se determine toate cuvintele din {a,b,c}* de lungime n care conin exact na simboluri 'a'; nb
simboluri 'b' i nc simboluri 'c' (cu condiia n=na+nb+nc).
14.3.11.
S se determine toate tripletele (x1,x2,x3) de numere astfel ca:
x1+x2+x3suma
x1*x2*x3produs
cu valorile suma i produs date iar x1S1, x2S2 i x3S3 ; S1, S2 i S3 fiind trei progresii
aritmetice date deasemenea.
126
14.3.12.
S se determine toate variantele de pronosport cu 13 rezultate din {1,x,2} care conin exact n1
simboluri '1'; nx simboluri 'x' i n2 simboluri '2' (cu condiia n1+nx+n2=13).
14.3.13.
S se determine toate variantele de pronosport cu 13 rezultate din {1,x,2} care conin cel mult n1
simboluri '1'; cel mult nx simboluri 'x' i simboluri '2' n rest (cu condiia n1+nx13).
PREL (p,q,)
IMPARTE (p,q,m) ;
DI (p,m,);
DI (m+1,q,);
COMB (,,);
#include <stdio.h>
#include <conio.h>
#define nrelem 100
int x[nrelem];
int n;
void PREL (int p, int q)
{int aux;
if ((p<q) && (x[p] > x[q])) {
aux=x[p];
x[p]=x[q];
x[q]=aux;
}
}
void COMB (int inf, int mijloc, int sup)
{int i,j,k,l;
int y[nrelem];
i=k=inf;
j=mijloc+1;
while (i<=mijloc && j<=sup)
{ if (x[i] <= x[j])
y[k++]=x[i++];
else
y[k++]=x[j++];
}
for(l=i; l<=mijloc; y[k++]=x[l++]);
for(l=j; l<=sup; y[k++]=x[l++]);
for(k=inf; k<=sup; x[k++]=y[k]);
}
void DI (int p, int q)
{ int m;
if ((q-p) <= 1)
PREL (p,q);
else
{m=(p+q)/2;
DI (p,m);
DI (m+1,q);
COMB (p,m,q);
}
return;
}
void main(void)
{int i;
clrscr();
printf ("dati nr de elemente\n");
scanf ("%d",&n);
for (i=1; i<=n; i++)
{printf("x[%d]=",i);
128
scanf("%d",&x[i]);
}
DI (1,n);
printf("\nsirul sortat crescator este:\n");
for (i=1; i<=n; i++) printf("x[%d]=%d\n",i,x[i]);
getch();
}
Rezolvare
Verificm prima variant a principiului optimalitii:
- fie i, i1, i2, . . . , im, j DVM ntre i i j atunci evident c i1, . . . , im, j este DVM ntre i1 i
j;
- notm prin L(i,k) lungimea DVM dintre i i k, kX;
- notm deasemenea dkj valoarea arcului (k,j);
- ecuaiile de recuren sunt:
L(i,j) = min {L(i,k) + dkj)}
(k,j)
#include<stdio.h>
#include<values.h>
#define nn 10
int d[nn+1][nn+1],i,j,n;
int L(int i,int j)
{
int m=MAXINT;
int k,x=0;
if (i= =j) m=0;
else
for (k=1;k<=n;k++)
if (d[k][j] < MAXINT)
{ x=L(i,k)+d[k][j];
if (x<m) m=x;
}
return m;
}
void citestematrice(void)
{int i,j;
printf("\ndati dimensiunea matricii distantelor : ");
scanf("%d",&n);
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{ printf("d[%d,%d]= ",i,j);
scanf ("%d",&d[i][j]);
}
for(i=1;i<=n;i++)
for(j=1;j<=n;j++) if (d[i][j]= = -1) d[i][j]=MAXINT;
}
void citeste(void)
{ printf("\ndati varful initial : ");
scanf("%d",&i);
printf("\ndati varful final : ");
scanf("%d",&j);
}
void afiseaza(int val)
{ printf("\nvdlm intre varful %d si %d este %d",i,j,val);
}
void main(void)
{ int vdlm;
130
clrscr();
citestematrice();
citeste();
vdlm=L(i,j);
afiseaza(vdlm);
getch();
}
Probleme propuse
14.5.1.
Se d o matrice A=(aij), i=1,...,n; j=1,...,m cu elementele din mulimea {0,1,2}. Dou elemente
din A aij i akl se numesc 4-vecine dac i-k+j-l = 1.
Notm cu So, S1 i S2 submulimile formate din elementele matricii egale cu 0, 1 respectiv 2.
Submulimea S1 se mparte n grupuri astfel: aij i akl fac parte din acelai grup dac sunt 4-vecine
sau dac apq S1 : aij i apq sunt 4-vecine iar apk i akl sunt din acelasi grup. Un element akl So
l vom numi spion al grupului G S1 dac aij G a.. akl i aij s fie vecine. Un spion este
perfect dac are toi vecinii din G.
Se cere:
a) cel mai numeros grup (S1) care are un singur spion (dac exist).
b) toate grupurile care au cel puin doi spioni perfeci.
14.5.2.
Se d o matrice cu elemente care au valori din {d,o}, care reprezint un teren cu drumuri {d} i
obstacole {o}. n acest teren se afl un tanc i o int. Acest tanc poate primi comenzi pentru
rotirea evii (cu 90o n ambele sensuri), pentru deplasare (n direcia evii cu o linie sau cu o
coloan) sau pentru tragere (n direcia evii pentru a nimeri inta) n cazul n care ntre tanc i
int nu exist nici un obstacol. Considernd c deplasarea nu se poate efectua prin obstacole, se
cere cel mai scurt drum necesar tancului pentru a putea distruge inta i irul comenzilor efectuate
n cazul n care exist un astfel de drum.
14.5.3.
Se d o matrice cu elemente din {0,1,2,3}, reprezentnd o pdure cu capcane (0) i crri (1). n
aceast pdure se afl o vulpe (2) i mai muli lupi (3). Fiecare lup ncearc s se apropie de
vulpe fr a ti unde se afl capcanele, iar n cazul n care cade ntr-o capcan, moare. Vulpea
ncearc s se ndeprteze de cel mai apropiat lup, avnd ns posibilitatea s descopere i
capcanele i s le ocoleasc. Att vulpea ct i lupii i pot modifica poziia doar cu o linie sau cu
o coloan n fiecare moment. S se spun dac vulpea reuete s scape de lupi.
14.5.4.
Se consider un teren dreptunghiular sub forma unei matrici A de m linii i n coloane.
Elementele aij ale matricii conin cotele (nlimile) diferitelor poriuni astfel obinute. Se
presupune c o bil se gsete pe o poriune (io,jo) avnd cota a(io,jo).
Se cere un program care s precizeze toate traseele (ncepnd cu (io,jo) ) posibile pe care le poate
urma bila spre a iei din teren, tiind c bila se poate deplasa n orice poriune de teren 4nvecinat cu o cot strict inferioar cotei terenului pe care se gsete bila.
14.5.5.
Se cere un program care rezolv problema labirintului (nerecursiv).
131
TEM:
S se scrie programe C care rezolv 5 probleme propuse din capitolul 14.
132
PARTEA II
Limbajul C++
133
Deci toate declaraiile trebuie s fie nainte de prima instruciune. n limbajul C++
declaraiile i instruciunile pot fi n orice ordine n interiorul unei instruciuni compuse.
Nu exist instruciuni de intrare/ieire n limbajul C, i nici n C++. n schimb n
limbajul C exist biblioteci standard de funcii pentru operaii de intrare/ieire. Ele pot fi
folosite i n limbajul C++, dar n afar de aceste biblioteci, mai exist i ierarhii de clase
pentru realizarea operaiilor de intrare/ieire. Ele vor fi tratate pe larg n capitolul 4, dar pentru
a putea utiliza aceste clase, prezentm aici nite exemple simple, ilustrative.
Dispozitivului standard de intrare respectiv ieire s-au ataat aa numitele streamuri
standard: cin pentru stdin i cout pentru stdout. Operaiile de intrare se vor efectua aplicnd
operatorul >> streamului standar cin, iar cele de ieire aplicnd operatorul << streamului
standard cout.
Prezentm n continuare exemple de operaii de intrare/ieire folosind streamurile
standard cin i cout. Pentru fiecare exemplu se va specifica n form de comentariu
echivalentul operaiei, folosind funcii din biblioteca standard a limbajului C.
Ierarhia de clase pentru operaii de intrare/ieire poate fi utilizat numai dac se
include fiierul iostream.h. n continuare prezentm exemple pentru operaii de afiare pe
dispozitivul standard de ieire:
#include <iostream.h>
int main() {
...
int x;
...
cout << x; // printf("%d", x);
...
double y;
...
cout << y; // printf("%lg", y);
...
char z;
134
...
cout << z; // printf("%c", z);
...
char t[] = "exemplu";
...
cout << t; // printf("%s", t);
...
}
Fr a intra acum n detalii menionm c operatorii << respectiv >> pot fi aplicai
nlnuit. Deci de exemplu
int w;
...
cout << "w = " << w + 25 << '\n';
este echivalent cu
int w;
...
printf("w = %d\n", w);
Deci n loc de apelarea funciei printf de mai sus, s-ar fi putut scrie i:
cout << "w = " << w + 25 << endl;
135
cout << 'A' << " cod: " << int('A') << " octeti: "
<< sizeof 'A' << endl;
cout << 'B' << " cod: " << int('B') << " octeti: "
<< sizeof 'B' << endl;
cout << 'AB' << " octeti: " << sizeof 'AB' << endl;
cout << "\'B\' * 256 + \'A\' = " << 'B' * 256 + 'A';
return 0;
}
Observm c ntr-adevr caracterul 'AB' este de tip int i n octetul mai semnificativ
este memorat codul ASCII al caracterului 'B', iar n cel mai puin semnificativ codul ASCII al
caracterului 'A'.
n limbajul C utilizarea operatorului de conversie explicit se face sub forma:
(tip) expresie
Rezult c ntr-adevr s-a realizat conversia variabilei x de la tipul double la tipul int,
i conversia variabilei p de la tipul void* la tipul double*. Menionm c n acest caz,
introducerea tipului p_double a fost obligatorie. Dac s-ar fi folosit conversia explicit
(double*) p, atunci nu ar fi fost necesar definirea tipului p_double.
136
n limbajul C++ putem defini i conversii utilizator ataate unor tipuri definite de
programator. Conversiile utilizator se vor trata n seciunea 16.8.
15.2. Operatori
15.2.1. Operatorul de rezoluie
Dac o dat global se redefinete ntr-o funcie, atunci referirea la data global se
poate face cu ajutorul operatorului de rezoluie. Numele datei globale trebuie precedat de
caracterele ::, deci de operatorul de rezoluie.
Prezentm n continuare un exemplu. Fiierul rezol.cpp:
#include <iostream.h>
#include <conio.h>
int x = 65;
int main() {
clrscr();
char x = 'A';
cout << "Variabila globala: " << ::x << endl;
cout << "Variabila locala: " << x << endl;
return 0;
}
Deci se observ c referirea la variabila global x de tip int s-a fcut cu ajutorul
operatorului de rezoluie (::x ). Menionm c operatorul de rezoluie se va folosi i n cazul
n care se face referire la datele respectiv funciile membru ale unei clase.
se va nelege un tip referin. Cu ajutorul tipului referin putem defini nume alternative
pentru date deja cunoscute. Aceste nume alternative pot fi utilizate n continuare n locul
datelor respective. Deasemenea acest tip de date se va folosi i pentru realizarea apelului prin
referin n limbajul C++.
Dac se folosete ca i parametru formal ntr-o funcie, atunci tipul referin va fi de
forma:
tip & parametru_formal
137
y = 10
y = 50
y = 60
*z = 10
*z = 50
*z = 60
Observm c aceeai zon de memorie este accesat n trei feluri. Primul element al
vectorului x este x[0], y este referin la acest element, deci un nume sinonim pentru x[0], iar
z este un pointer ctre vectorul x, astfel *z reprezentnd acceai zon de memorie ca i x[0].
Rezult c valoarea oricrei dintre aceste trei elemente s-ar schimba, valorile celorlalte se
schimb n mod automat.
Exist posibilitatea definirii unei referine i la o funcie. Acest lucru este prezentat n
exemplul urmtor. Fiierul refer2.cpp:
#include <iostream.h>
#include <math.h>
#include <conio.h>
typedef double TipFunctie(double);
int main() {
clrscr();
const double Pi = 3.141592653;
TipFunctie& sinus = sin;
cout << sinus( Pi / 2 ); // se afiseaza valoarea 1
return 0;
}
138
n exemplul de mai sus s-a declarat o referin la funcia standard de bibliotec sin,
deci numele sinus poate fi folosit n locul funciei sin pentru a calcula sinusul unui numr real.
Tipul referin este cel mai des folosit pentru realizarea apelului prin referin, ceea ce
se trateaz n paragraful urmtor.
139
n exemplul de mai sus s-a realizat modificarea valorii parametrului actual y, dar i n
acest caz s-a folosit de fapt apel prin valoare. S-a transmis valoarea adresei lui y pointerului
x. Deci prin intermediul pointerilor apelul prin valoare s-a transformat n apel prin referin.
Menionm c numele unui vector de elemente este un pointer ctre primul element al
vectorului. n consecin, dac un parametru actual este un vector, atunci valorile elementelor
acestui vector pot fi modificate n interiorul funciei.
Aceast modalitate de realizare a apelului prin referin, prin intermediul pointerilor,
de multe ori ngreuneaz munca programatorului, de aceea n limbajul C++ s-a introdus i
apelul prin referin propriu zis.
Apelul prin referin se realizeaz cu ajutorul tipului referin. n cazul folosirii tipului
referin parametrul formal este un nume alternativ pentru parametrul actual, deci ele
reprezint aceeai zon de memorie i ca atare orice modificare a parametrului formal
conduce la modificarea parametrului actual corespunztor. Iat cum se modific exemplul de
mai sus folosind apelul prin referin. Fiierul refer3.cpp:
#include <iostream.h>
#include <conio.h>
void modifica( int& x ) // parametrul formal x este o
{
// referinta la un intreg
x += 20; // valoarea parametrului formal x
}
// se mareste cu 20
int main () {
int y;
y = 10;
clrscr();
modifica( y );
Putem constata c apelul prin referin este mult mai simplu de utilizat, dect
realizarea ei prin intermediul pointerilor.
new tip
new tip(expresie_1)
new tip[expresie_1]
n toate cele trei cazuri construciile de mai sus formeaz o expresie pe care o vom
nota cu expresie_2. Valoarea expresiei expresie_2 este egal cu adresa de nceput a zonei de
memorie alocate, i este zero n cazul unei erori.
n cazul a) se aloc zon de memorie pentru o variabil de tipul exprimat prin tip. n
cazul b) zona de memorie alocat se iniializeaz cu valoarea expresiei expresie_1, iar n cazul
c) se aloc memorie un numr de expresie_1 de zone de memorie de tipul menionat. Deci n
140
iar dac alocarea s-a fcut prin varianta c) atunci eliberarea zonei de memorie se va face cu
delete [expresie_1] q;
unde expresie_1 este expresia care s-a folosit la alocare. De obicei ea poate s lipseasc.
Nr. crt.
1.
2.
3.
C++
C sau C++
double* d;
d = new double;
int* i;
i = new int(20);
double* p;
p = new double[10];
double *d;
d = (double *) malloc(sizeof(double));
int* i;
i = new int;
*i = 20;
d = (double *) malloc(10 * sizeof(double));
Persoana {
nume[15];
prenume[15];
telefon;
O alt proprietate important este c n limbajul C++ o funcie poate avea structuri ca
i parametrii. Deasemenea i valoarea returnat de o funcie poate fi o structur. n consecin
valoarea returnat de funcie i parametrii unei funcii pot fi structuri, pointeri ctre structuri i
referine la structuri.
Se pot efectua instruciuni de atribuire cu structurile de acelai tip. De exemplu:
Persoana A, Adalbert;
A = Adalbert;
141
n interiorul unei structuri se poate declara o reuniune anonim n limbajul C++ sub
forma:
struct nume {
declaraii
union {
declaraii
};
declaraii
} lis_de_nume;
x[1]=2
x[5]=6
y[0] = 1.5
x[2]=3
x[6]=7
x[3]=4
x[7]=8
y[1] = 2.4
Rezult c primul element (vectorul x) poate fi iniializat la fel cum s-ar fi procedat n
cazul iniializrii elementului n sine (adic a unui vector).
n limbajul C++ variabilelor de tip enumerare se pot atribui numai elementele
componente ale tipului. Numerele asociate cu aceste elemente nu se pot atribui variabilelor de
tip enumerare. Exemplu
enum culori {rosu, galben, verde};
culori Semafor;
...
Semafor = 1;
// corect in C, gresit in C++
Semafor = galben; // corect
15.4. Funcii
15.4.1. Iniializarea parametrilor formali ai funciilor
Parametrii formali ai unei funcii pot fi iniializai n limbajul C++. Vom spune c
parametrii iniializai au o valoare implicit. n lista parametrilor formali elementele
iniializate vor fi declarate n felul urmtor:
tip nume = expresie
Totui exist anumite restricii n acest sens. Dac exist att parametri formali
iniializai ct i neiniializai, atunci cei neiniializai trebuie s fie nainte de cei iniializai n
lista parametrilor formali.
Prezentm un exemplu n continuare. Fiierul init1.cpp:
#include <iostream.h>
#include <conio.h>
void intilniri(char* nume, char* loc,
int ora = 12, int minut = 0)
{
cout << "Nume: " << nume << endl
<< "Loc:
" << loc
<< endl
<< "Ora:
" << ora
<< endl
<< "Minut: " << minut << "\n\n";
}
int main() {
clrscr();
cout << "Intilniri:\n\n";
intilniri("Daniel", "Facultatea de Litere");
intilniri("Eva", "Scoala Generala", 14);
intilniri("bunica", "acasa", 17, 20);
return 0;
}
Daniel
Facultatea de Litere
12
0
Nume:
Loc:
Ora:
Minut:
Eva
Scoala Generala
14
0
Nume:
Loc:
Ora:
Minut:
bunica
acasa
17
20
Observm c paramerii formali ora i minut sunt iniializai cu valorile implicite 12,
respectiv 0. Dac parametrii formali iniializai nu au parametrii actuali corespunztori, atunci
se vor folosi valorile lor implicite, n caz contrar se va ignora valoarea implicit i se vor
folosi parametrii actuali corespunztori.
143
n exemplul de mai sus s-a folosit c n fiierul dos.h este declarat o structur
pentru memorarea datelor sub forma:
struct date {
int da_year; // Anul exprimat printr-un ntreg.
char da_day; // Caracterul se convertete n int
// i astfel se obine ziua.
char da_mon; // Caracterul se convertete n int
// i astfel se obine luna.
};
Funcia getdate are un parametru, care este un pointer ctre structura de mai sus, n
care se va returna data curent.
144
Fiierul se poate compila cu un compilator C++ sub sistemul de operare Dos sau
Windows, i dup execuie se obine un rezultat care poate varia n funcie de data curent. De
exemplu (data curent: 24 august 1998):
ziua:
ziua:
ziua:
ziua:
24
11
18
27
luna:
luna:
luna:
luna:
8
8
4
7
anul:
anul:
anul:
anul:
1998
1998
1998
1991
Din rezultate reiese c n cazul n care nu s-a specificat parametru actual, s-a folosit
valoarea implicit, deci data curent.
Funciile care returneaz tipuri referin pot apare att pe partea dreapt ct i pe
partea stng a operatorilor de atribuire. Se spune c ele pot fi att operanzi lvalue (left value)
ct i rvalue (right value). Un operand lvalue poate s apar pe partea stng, iar unul rvalue
pe partea dreapt a operatorilor de atribuire.
S considerm urmtorul exemplu. ntr-o urn se afl bile albe i negre. Se extrage un
numr de bile din urn la ntmplare. S se scrie un program pentru aflarea numrului de bile
albe, respectiv negre extrase. Fiierul refer4.cpp:
#include
#include
#include
#include
<iostream.h>
<conio.h>
<stdlib.h>
<time.h>
Dup compilarea sub sistemul de operare Dos sau Windows cu Borland C++, i
executarea programului, se obine un rezultat care depinde de numrul total de bile, numr
citit de la intrarea standard. Numrul de bile albe respectiv negre extrase se determin n mod
aleator. Deci rezultatul poate fi:
145
<iostream.h>
<conio.h>
<math.h>
<complex.h>
/*
double atan(double x);
double atan2(double y, double x);
long double atanl(long double (x));
long double atan2l(long double (y), long double (x));
complex atan(complex x);
*/
int main() {
clrscr();
cout.precision(18);
146
n fiierul de mai sus, n form de comentariu s-au specificat cele cinci variante ale
funciei atan. Primele patru sunt declarate n fiierul math.h, ele au numele distincte i se
refer la parametrii formali de tip real. n Borland C++ exist funcia atan, cu acelai nume ca
i prima funcie referitoare la parametrii de tip real. Aceast funcie este declarat n fiierul
complex.h, i se apeleaz n cazul unui parametru actual de tip complex.
Dup executarea programului obinem:
3.141592653589793116
3.141592653589793238
3.141592653589793116
3.141592653589793238
3.141592653589793116
Observm c n cazul n care nu s-au folosit variabile de tip long double (funciile atan
cu parametrii de tip double respectiv complex, i funcia atan2) primele 15 zecimale sunt
corecte, iar pentru funciile atanl i atan2l toate cele 18 zecimale obinute sunt corecte.
Exemplul de mai sus se poate modifica astfel nct toate cele cinci variante ale funciei
atan s aib acelai nume. Pentru a realiza acest lucru vom scrie trei funcii cu numele atan,
care vor apela funciile atan2, atanl respectiv atan2l. Deoarece antetul funciilor va fi diferit,
se poate efectua suprancrcarea lor. Prezentm n continuare aceast variant modificat a
programului. Fiierul sup_fun2.cpp:
#include
#include
#include
#include
<iostream.h>
<conio.h>
<math.h>
<complex.h>
147
atunci deoarece parametrul actual al funciei atan este de tip ntreg, nici unul din cele cinci
funcii nu are un antet corespunztor. n astfel de cazuri se vor lua n considerare urmtoarele
reguli. Etapele determinrii funciei care se va apela:
1. Dac exist o funcie pentru care tipul parametrilor formali coincide cu tipul
parametrilor actuali corespunztoare (deci i numrul parametrilor formali
coincide cu numrul parametrilor actuali), atunci aceast funcie se va apela.
2. Altfel, funcia se va determina folosind conversia implicit pentru tipurile standard
(de exemplu tipul int se va converti n double). n acest caz nu vor fi pierderi de
informaii.
3. Dac nu s-a reuit determinarea unei funcii, atunci se ncearc o conversie pentru
tipuri standard cu eventuale pierderi de informaii (de exemplu conversie din tipul
long n tipul int).
4. Dac nici n acest fel nu s-a reuit determinarea funciei, atunci se ncearc
aplicarea unor conversii i tipurilor definite de programator.
Rezult c n cazul expresiei cout << 4 * atan( 1 ) << endl; se va ncerca aplicarea etapei
a doua a determinrii funciei, dar deoarece s-ar putea aplica att o conversie de la tipul int la
double, ct i la long double, apare o ambiguitate care nu poate fi rezolvat de compilator, i
se va afia un mesaj de eroare la compilare.
Dac ns revenim la exemplul din fiierul sup_fun1.cpp putem constata c la
compilarea expresiei amintite nu au aprut mesaje de eroare. Acest lucru se datoreaz faptului
c funcia atan a fost suprancrcat n aa fel nct au existat numai dou variante ale ei (una
cu parametru formal de tip double, iar alta de tip complex). n acest caz exist o singur
conversie implicit posibil pentru tipuri standard, deci se va aplica conversia de la tipul int la
double, i se va apela funcia atan cu un singur parametru de tip double.
148
Dac parantezele ar lipsi din definirea macroului Val_abs(x), n unele expresii ar putea
s apar erori. ntr-adevr prin expresia
cout << Val_abs_eronata_1(4000L * y + z ) << endl;
i expresia va deveni:
cout << 15 +(-y) > 0 ? (-y) : -(-y);
Operatorul de adunare fiind mai prioritar dect operatorul ?:, se evalueaz mai nti
expresia 15 + (-y), i se va afia valoarea 5. n sfrit, menionm c i n cazul n care
macroul este definit corect pot s apar situaii n care rezultatul nu va fi cel ateptat. Dac
apelarea macroului s-ar face ca i n cazul funciilor, atunci prin evaluarea expresiei
cout << Val_abs( y++ ) << endl;
s-ar afia valoarea 10, iar valoarea parametrului y dup evaluarea expresiei ar fi 11. n cazul
nostru ns s-a afiat valoarea 11, iar dup evaluarea expresiei s-a obinut valoarea y = 12.
Explicaia const i de aceast dat n modul de apelare a macrourilor. n faza de preprocesare
macroul Val_abs se nlocuiete cu urmtorul ir de caractere:
((y++) > 0 ? (y++) : -(y++))
Dup evaluarea expresiei (y++) > 0, se incrementeaz valoarea lui y, deci rezultatul
expresiei condiionale va fi egal cu 11, dup care se incrementeaz nc o dat valoarea
variabilei y. n consecin dup evaluarea expresiei se obine y = 12.
Aceste neajunsuri ale macrourilor pot fi nlturate cu ajutorul funciilor inline.
Apelarea funciilor inline se face n mod asemntor cu apelarea macrourilor. Apelul funciei
se va nlocui cu corpul funciei, deci operaia de salt la adresa corpului funciei i revenirea la
locul apelrii nu sunt necesare. Comparativ cu macrouri, funcia inline are avantajul, c se va
verifica corespondena dintre tipurile parametrilor formali i cei actuali, i se va face o
conversie de tipuri, dac este necesar. Astfel posibilitatea apariiei unor erori este diminuat.
Declararea sau definirea unei funcii inline se face n mod obinuit, dar antetul funciei se va
ncepe cu cuvntul cheie inline.
Exemplul de mai sus se poate transcrie folosind o funcie inline, n modul urmtor.
Fiierul inline1.cpp:
#include <iostream.h>
#include <conio.h>
inline long Val_abs( long x )
{
return x > 0 ? x : -x;
}
int main() {
int y = 10;
long z = -50000;
clrscr();
cout << Val_abs( y ) << endl;
cout << Val_abs( z ) << endl;
cout << Val_abs(4000L * y + z ) << endl;
cout << 15 + Val_abs( -y ) << endl;
cout << Val_abs( y++ ) << endl;
cout << "y = " << y << endl;
return 0;
}
150
Putem constata c, n acest caz, toate rezultatele obinute sunt corecte. Menionm c o
funcie inline nu se poate folosi numai ntr-un singur modul, funcia nu poate s conin
operaii mai sofisticate, de exemplu nici instruciuni de ciclare. n cazul definirii claselor ne
vom ntlni foarte des cu funcii inline.
151
//elementele vectorului
//dimensiunea vectorului
void lapatrat()
{
for(int i = 0; i < d; i++)
e[i] *= e[i];
}
//ridicare la patrat
void afiseaza()
{
for(int i = 0; i < d; i++)
cout << e[i] << ' ';
cout << endl;
}
//afisare
152
Funciile declarate sau definite n interiorul structurii vor fi numite funcii membru iar
datele date membru. Dac o funcie membru este definit n interiorul structurii (ca i funcia
distr din exemplul de mai sus), atunci ea se consider funcie inline. Dac o funcie membru
se definete n afara structurii, atunci numele funciei se va nlocui cu numele tipului abstract
urmat de operatorul de rezoluie (::) i numele funciei membru. Astfel funciile init, lapatrat
i afiseaza vor fi definite n modul urmtor:
void vect::init(int *e1, int d1)
{
d = d1;
e = new int[d];
for(int i = 0; i < d; i++)
153
e[i] = e1[i];
}
void vect::lapatrat()
{
for(int i = 0; i < d; i++)
e[i] *= e[i];
}
void vect::afiseaza()
{
for(int i = 0; i < d; i++)
cout << e[i] << ' ';
cout << endl;
}
Dei prin metoda de mai sus s-a realizat o legtur ntre datele problemei i funciile
referitoare la aceste date, structurile ca tipuri abstracte de date nu ne permit protejarea datelor,
deci ele pot fi accesate de orice funcie utilizator, nu numai de funciile membru. Acest
neajuns se poate nltura cu ajutorul claselor.
Funciile membru ale altor clase pot fi funcii prietene pentru o clas. Dac dorim ca
toate funciile membru ale unei clase Clasa_1 s fie funcii prietene pentru o clas Clasa_2,
atunci clasa Clasa_1 se va declara ca i clas prieten pentru clasa Clasa_2. Acest lucru se
poate realiza n modul urmtor:
class Clasa_1; // Clasa Clasa_1 se definete n mod incomplet
// n prealabil, pentru a se putea referi la ea.
// Ea va fi definit n mod complet ulterior.
class Clasa_2 {
...
friend Clasa_1; // Clasa_1 este clas prieten pentru Clasa_2
...
}
este un destructor.
Tipurile abstracte de date de tip struct pot fi i ele considerate clase cu toate
elementele neprotejate. Observm c constructorul de mai sus este declarat n interiorul clasei,
dar nu este definit, iar destructorul este definit n interiorul clasei. Rezult c destructorul este
o funcie inline. Definirea funciilor membru care sunt declarate, dar nu sunt definite n
interiorul clasei se face ca i la tipuri abstracte de date de tip struct, folosind operatorul de
rezoluie.
155
p->afiseaza();
156
}
void vector::afiseaza()
{
for(int i = 0; i < d; i++)
cout << e[i] << ' ';
cout << endl;
}
16.5. Constructorul
16.5.1. Iniializarea obiectelor prin constructor
Iniializarea obiectelor se face cu o funcie membru specific numit constructor.
Numele constructorului trebuie s coincid cu numele clasei. O clas poate s aib mai muli
constructori. n acest caz aceste funcii membru au numele comun, ceea ce se poate face
datorit posibilitii de suprancrcare a funciilor. Bineneles n acest caz numele i/sau tipul
parametrilor formali trebuie s fie diferit, altfel compilatorul nu poate s aleag constructorul
corespunztor.
Constructorul nu returneaz o valoare. n acest caz nu este permis nici folosirea
cuvntului cheie void.
Prezentm n continuare un exemplu de tip clasa cu mai muli constructori, avnd ca
date membru numele i prenumele unei persoane, i cu o funcie membru pentru afiarea
numelui complet. Fiierul pereche1.cpp (conine definiia clasei persoana):
#include <string.h>
#include <iostream.h>
class persoana {
char* nume;
char* prenume;
public:
persoana();
//constructor implicit
persoana(char* n, char* p); //constructor
persoana(const persoana& p1);
//constructor
//de copiere
~persoana();
//destructor
void afiseaza();
};
persoana::persoana()
{
nume = prenume = 0;
cout << "Apelarea constructorului implicit" << endl;
}
157
persoana::persoana(char* n, char* p)
{
nume = new char[strlen(n)+1];
prenume = new char[strlen(p)+1];
strcpy(nume, n);
strcpy(prenume, p);
}
159
int main() {
persoana A("Pop", "Ion");
persoana B("Popa", "Ioana");
pereche AB(A, B);
AB.afiseaza();
pereche CD("C","C","D","D");
CD.afiseaza();
pereche EF;
EF.afiseaza();
return 0;
}
constructorul de copiere s-ar fi apelat de patru ori. n astfel de situaii se creaz mai nti
obiecte temporale folosind constructorul de copiere (dou apeluri n cazul de fa), dup care
se execut constructorii datelor membru de tip obiect (nc dou apeluri).
16.6. Destructorul
Destructorul este funcia membru care se apeleaz n cazul distrugerii obiectului.
Destructorul obiectelor globale se apeleaz automat la sfritul funciei main ca parte a
funciei exit. Deci nu este indicat folosirea funciei exit ntr-un destructor, pentru c acest
lucru duce la un ciclu infinit. Destructorul obiectelor locale se execut automat la terminarea
blocului n care s-au definit. n cazul obiectelor alocate dinamic, de obicei destructorul se
apeleaz indirect prin operatorul delete (obiectul trebuie s fi fost creat cu operatorul new).
Exist i un mod explicit de apelare a destructorului, n acest caz numele destructorului
trebuie precedat de numele clasei i operatorul de rezoluie.
Numele destructorului ncepe cu caracterul ~ dup care urmeaz numele clasei. Ca i
n cazul constructorului, destructorul nu returneaz o valoare i nu este permis nici folosirea
cuvntului cheie void. Apelarea destructorului n diferite situaii este ilustrat de urmtorul
exemplu. Fiierul destruct.cpp:
#include <iostream.h>
#include <string.h>
class scrie { //scrie pe stdout ce face.
char* nume;
public:
scrie(char* n);
~scrie();
};
scrie::scrie(char* n)
{
nume = new char[strlen(n)+1];
strcpy(nume, n);
cout << "Am creat obiectul: " << nume << '\n';
}
scrie::~scrie()
{
cout << "Am distrus obiectul: " << nume << '\n';
delete nume;
160
}
void functie()
{
cout << "Apelare functie" << '\n';
scrie local("Local");
}
scrie global("Global");
int main() {
scrie* dinamic = new scrie("Dinamic");
functie();
cout << "Se continua programul principal"
<< '\n';
delete dinamic;
return 0;
}
161
//supraincarcarea
//operatorului +
void afiseaza();
};
vector vector::operator +(vector& v1)
{
if (d != v1.d)
{
cerr << "Eroare: dimensiune diferita";
exit(1);
}
int* x = new int[d];
for(int i = 0; i < d; i++)
x[i] = e[i] + v1.e[i];
vector y(x, d);
delete [] x;
return y;
}
Observm c de fapt nu s-a fcut altceva dect s-a nlocuit numele funciei cu
operator +. Operatorul se apeleaz n modul urmtor:
int main() {
int x[] = {1, 2, 3, 4, 5};
vector v(x, 5);
int y[] = {2, 4, 6, 8, 10};
vector t(y, 5);
(v+t).afiseaza();
return 0;
}
162
numarator = numarator1;
numitor = numitor1;
}
inline fractie fractie::operator *(fractie& r)
{
return fractie(numarator*r.numarator,
numitor*r.numitor);
}
inline void fractie::afiseaza()
{
if ( numitor )
cout << numarator << " / " << numitor << endl;
else
cerr << "Fractie incorecta";
}
int main() {
fractie x(3,4);
fractie y(5, 7);
fractie z;
z = x * y;
z.afiseaza();
return 0;
}
Observm c operatorul de atribuire se poate utiliza i rezultatul este cel dorit, deci se
afieaz produsul celor dou fracii. Din pcate ns nu este ntotdeauna aa. Prezentm un alt
exemplu legat de clasa vector pentru ilustrarea acestui lucru. Fiierul vector5.cpp:
#include <iostream.h>
#include <stdlib.h>
class vector {
private:
int* e; //elementele vectorului
int d;
//dimensiunea vectorului
public:
vector(int* e1, int d1);
~vector();
void operator++();
//incrementarea elementelor
//vectorului
void afiseaza(char* text); //afiseaza adresa
//primului element,
//dupa care afiseaza
//elementele
};
vector::vector(int* e1, int d1)
{
d = d1;
e = new int[d];
for(int i = 0; i < d; i++)
e[i] = e1[i];
}
vector::~vector()
{
cout
<< "S-a eliberat zona de memorie de dimensiune "
<< d*sizeof(int)
163
}
void vector::operator++()
{
for(int i = 0; i < d; i++)
e[i]++;
}
void vector::afiseaza(char* text)
{
cout
<< "Vectorul " << text
<< " (adresa primului element si elementele):\n";
cout << e << endl;
for(int i = 0; i < d; i++)
cout << e[i] << ' ';
cout << endl;
}
int main() {
int x[] = {1, 2, 3, 4, 5};
vector v(x, 5);
int y[] = {20, 40, 60, 80, 100};
vector t(y, 5);
v.afiseaza("v");
t.afiseaza("t");
t = v;
cout << "Dupa atribuirea t = v:\n";
t.afiseaza("t");
++v;
cout
<< "Dupa incrementarea elementelor "
<< "vectorului v:" << endl;
v.afiseaza("v");
t.afiseaza("t");
return 0;
}
164
Se observ c nu s-a obinut ceea ce s-a dorit, deoarece prin instruciunea t = v s-a
obinut atribuirea adresei primului element al vectorului v datei membru e, corespunztoare
vectorului t i nu s-au atribuit elementele n sine. De aceea orice modificare a elementelor
vectorului v duce n continuare la modificarea elementelor vectorului t (n cazul nostru prin
incrementarea elementelor vectorului v se incrementeaz i elementele vectorului t).
Un alt neajuns este c nu s-a eliberat zona de memorie alocat iniial elementelor
vectorului t dar s-a eliberat de dou ori cea rezervat pentru elementele vectorului v.
Explicaia rezultatelor de mai sus const n faptul c operatorul de atribuire (=) este
suprancrcat n mod implicit astfel nct s realizeze o copiere bit cu bit a datelor membru. n
exemplul referitor la fracii prin copierea bit cu bit se obin rezultatele dorite dar n al doilea
exemplu ns, nu. n general prin copierea bit cu bit nu se obin rezultate corespunztoare
atunci cnd cel putin una dintre datele membru este un pointer. n acest caz suprancrcarea
operatorului de atribuire se poate face n modul urmtor:
class vector {
private:
int* e; //elementele vectorului
int d;
//dimensiunea vectorului
public:
vector(int* e1, int d1);
vector(const vector& v1);
~vector();
void operator++();
//incrementarea elementelor
//vectorului
vector& operator =(const vector& v1);
//supraincarcarea
//operatorului
//de atribuire
void afiseaza(char* text); //afiseaza adresa
//primului element,
//dupa care afiseaza
//elementele
};
vector& vector::operator =(const vector& v1)
{
if (this != &v1)
{
delete[] e;
d = v1.d;
e = new int[d];
for(int i = 0; i < d; i++)
e[i] = v1.e[i];
}
return *this;
}
Operatorii de atribuire +=, -=, *=, /= nu sunt suprancrcai n mod implicit, deci
trebuie suprancrcai de ctre programator. De exemplu operatorul += pentru clasa vector
poate fi suprancrcat n modul urmtor:
vector& vector::operator +=(vector& v1)
{
return *this = *this + v1;
}
165
//apelarea constructorului
//de copiere
class vector {
private:
int* e; //elementele vectorului
int d;
//dimensiunea vectorului
public:
vector(int* e1, int d1);
vector(vector& v1);
~vector();
vector operator +(vector v1); //supraincarcarea
//adunarii
vector& operator =(const vector& v1);
//supraincarcarea
//operatorului
//de atribuire
void afiseaza(char* text); //afiseaza adresa
//primului element,
//dupa care afiseaza
//elementele
};
vector::vector(int* e1, int d1)
{
d = d1;
e = new int[d];
for(int i = 0; i < d; i++)
e[i] = e1[i];
cout
<< "Apel constructor.\n"
<< "Adresa primului element: " << e << endl;
166
}
vector::vector(vector& v1)
{
d = v1.d;
e = new int[d];
for(int i = 0; i < d; i++)
e[i] = v1.e[i];
cout << "Apel constructor de copiere.\n"
<< "Adresa primului element: " << e << endl;
}
vector::~vector()
{
cout
<< "Apel destructor.\n"
<< "S-a eliberat zona de memorie de dimensiune "
<< d*sizeof(int)
<< " incepind de la adresa: " << e << endl;
delete[] e;
}
vector vector::operator +(vector v1)
{
if (d != v1.d)
{
cerr << "Eroare: dimensiune diferita";
exit(1);
}
int* x = new int[d];
for(int i = 0; i < d; i++)
x[i] = e[i] + v1.e[i];
vector temp(x, d);
temp.afiseaza("temp");
return temp;
//return vector(x, d);
}
vector& vector::operator =(const vector& v1)
{
if (this != &v1)
{
cout
<< "Apel operator de atribuire ( = ).\n"
<< "S-a eliberat zona de memorie "
<< "de dimensiune " << d*sizeof(int)
<< " incepind de la adresa: "
<< e << endl;
delete[] e;
d = v1.d;
e = new int[d];
for(int i = 0; i < d; i++)
e[i] = v1.e[i];
}
return *this;
}
void vector::afiseaza(char* text)
{
cout
<< "Vectorul " << text
<< " (adresa primului element si elementele):\n";
cout << e << endl;
167
Fiierul vector7.cpp:
#include "vector6.cpp"
int main() {
int x[] = {1, 2, 3, 4, 5};
vector vx(x, 5);
int y[] = {20, 40, 60, 80, 100};
vector vy(y, 5);
int z[] = {300, 600, 900, 1200, 1500};
vector vz(z, 5);
vx.afiseaza("vx");
vy.afiseaza("vy");
vz.afiseaza("vz");
vz = vx + vy;
cout << "Dupa instructiunea vz = vx + vy:\n";
vz.afiseaza("vz");
return 0;
}
168
element si elementele):
Se observ c s-a apelat constructorul de copiere de dou ori. Prima dat pentru
crearea parametrului operatorului de adunare prin copierea obiectului vy. Destructorul acestui
obiect s-a apelat automat dup ce s-a prsit funcia membru corespunztoare operatorului de
adunare. Constructorul de copiere s-a apelat a doua oar atunci cnd s-a creat obiectul anonim
vx+vy. Destructorul acestui obiect anonim s-a apelat ns numai la prsirea funciei main. De
aceea dac elementele iniiale a vectorului vz nu se folosesc, ar fi mai convenabil dac
vectorul vz s-ar iniializa printr-un constructor. De exemplu cu funcie main din fiierul
vector8.cpp:
#include "vector6.cpp"
int main() {
int x[] = {1, 2, 3, 4, 5};
vector vx(x, 5);
int y[] = {20, 40, 60, 80, 100};
vector vy(y, 5);
vx.afiseaza("vx");
vy.afiseaza("vy");
vector vz = vx + vy;
cout << "Dupa instructiunea vz = vx + vy:\n";
vz.afiseaza("vz");
return 0;
}
n acest caz constructorul de copiere se apeleaz tot de dou ori, dar nu se mai creaz
obiectul anonim vx+vy.
pentru un obiect v al clasei vector. Dac n locul acestei instruciuni am fi scris v++; atunci
n faza de compilare ar fi aprut un mesaj de avertisment. De exemplu n Borland C++ apare
mesajul: "Overloaded prefix 'operator ++' used as a postfix operator", deci operatorul ++
prefixat s-a folosit ca i operator postfixat. Acelai mesaj de avertisment apare i n urmtorul
exemplu. Fiierul increm1.cpp:
#include <iostream.h>
#include <conio.h>
class Clasa {
169
int x;
public:
Clasa() { x = 0; }
Clasa& operator++();
void scrie_x();
};
Clasa& Clasa::operator++()
{
++x;
cout << "S-a apelat operatorul ++ prefixat\n";
return *this;
}
void Clasa::scrie_x()
{
cout << "x = " << x << endl;
}
int main() {
Clasa cl;
clrscr();
cl.scrie_x();
cl++;
cl.scrie_x();
(++cl)++;
cl.scrie_x();
return 0;
}
n faza de compilare, mesajul de avertisment de mai sus apare de dou ori, pentru cele
dou apeluri ale operatorului de incrementare postfixat. Prin executarea programului se
obine:
x =
S-a
x =
S-a
S-a
x =
0
apelat operatorul ++ prefixat
1
apelat operatorul ++ prefixat
apelat operatorul ++ prefixat
3
Din rezultatul de mai sus nu reiese c s-a apelat operatorul ++ postfixat de dou ori.
Operatorul de incrementare postfixat poate fi suprancrcat cu o funcie membru, care are un
parametru de tip int. La apelarea unui operator postfixat, acest parametru va lua n mod
automat valoarea zero. Deoarece valoarea parametrului nu se va folosi n corpul funciei
membru, numele lui poate fi omis. Exemplul de mai sus se poate modifica n felul urmtor.
Fiierul increm2.cpp:
#include <iostream.h>
#include <conio.h>
class Clasa {
int x;
public:
Clasa() { x = 0; }
Clasa& operator++();
Clasa& operator++( int );
void scrie_x();
};
Clasa& Clasa::operator++()
170
{
++x;
cout << "S-a apelat operatorul ++ prefixat\n";
return *this;
}
Clasa& Clasa::operator++( int )
{
x++;
cout << "S-a apelat operatorul ++ postfixat\n";
return *this;
}
void Clasa::scrie_x()
{
cout << "x = " << x << endl;
}
int main() {
Clasa cl;
clrscr();
cl.scrie_x();
cl++;
cl.scrie_x();
(++cl)++;
cl.scrie_x();
return 0;
}
0
apelat operatorul ++ postfixat
1
apelat operatorul ++ prefixat
apelat operatorul ++ postfixat
3
// simplificare fractie
b)
fractie& fractie::operator~()
{
long d = cmmdc(numarator, numitor);
numarator /= d;
numitor /= d;
return *this;
}
inline fractie fractie::operator *(fractie r)
{
return ~fractie(numarator*r.numarator,
numitor*r.numitor);
}
inline void fractie::afiseaza()
{
if ( numitor )
cout << numarator << " / " << numitor << endl;
else
cerr << "Fractie incorecta";
}
172
compilatorul ar semnala o eroare. Totui ar fi de dorit ca expresiile de forma de mai sus, s fie
corecte. Pentru a realiza acest lucru, trebuie definit o conversie implicit din tipul long n
tipul fractie.
n general, conversiile dintr-un tip standard ntr-un tip abstract se realizeaz cu
ajutorul unui constructor al tipului abstract. Constructorul trebuie s aib un parametru, care
aparine tipului standard, iar dac sunt i ali parametri, ei trebuie s fie iniializai.
n cazul de mai sus constructorul, care realizeaz conversia din tipul long n tipul
fractie, poate fi declarat sub forma:
fractie(long a, long b = 1);
sau
fractie(long a = 0, long b = 1);
sau
fractie(long a);
atunci expresia
z = x * 4;
fractie(4, 1), care se va folosi n locul operandului al doilea. Menionm, c de fapt s-au
fcut dou conversii. Constanta 4 este de tipul int, deci la apelarea constructorului s-a fcut
o conversie de la tipul int la tipul long, dup care s-a fcut o conversie din tipul long n tipul
fractie, prin crearea obiectului anonim.
Totui compilatorul semnalizeaz o eroare la ntlnirea unei expresii de forma
urmtoare:
z = 4 * x;
Dac operatorul de nmulire se definete n modul de mai sus, atunci ambele expresii
x * 4 i 4 * x vor fi corecte. Dezavantajul acestei metode este, c prin utilizarea unei
funcii prieten, gradul de protecie a datelor scade. n continuare prezentm o alt metod de
nlturare a erorii de mai sus, astfel nct s nu fie nevoie de introducerea unei funcii prieten.
Vom defini o funcie membru inmultire, care se va apela de ctre funcia, care suprancarc
operatorul de nmulire.
class fractie {
long numarator;
long numitor;
public:
...
fractie inmultire(fractie r);
...
};
inline fractie fractie::inmultire(fractie r)
{
return ~fractie(numarator*r.numarator,
numitor*r.numitor);
}
fractie operator*(fractie p, fractie q)
{
return p.inmultire( q );
}
Observm c i n acest caz, ambele expresii sunt corecte i nu s-a folosit funcie
prieten.
174
=
=
=
10 lines
12 inches
3 feet
=
=
=
=
2.54 mm
2.54 cm
30.48 cm
91.44 cm
Lungime_inch {
line;
inch;
foot;
yard;
public:
Lungime_inch(int l = 0, int i = 0, int f = 0, int y = 0);
long line_lungime();
void afisare();
};
class Lungime_m {
int mm;
int cm;
int dm;
int m;
double rest;
// restul, care ramane la conversie
public:
Lungime_m( int mm_1 = 0, int cm_1 = 0,
int dm_1 = 0, int m_1 = 0);
Lungime_m(Lungime_inch L);
// conversie
void afisare();
};
Lungime_inch::Lungime_inch( int l, int i, int f, int y)
{
line = l;
inch = i;
foot = f;
yard = y;
}
long Lungime_inch::line_lungime()
{
return line + 10 * inch + 120 * foot + 360 * yard;
}
void Lungime_inch::afisare()
{
if ( line )
cout << line << " line ";
if ( inch )
175
176
Observm, c i n acest caz s-a folosit un constructor pentru realizarea conversiei din
tipul Lungime_inch n tipul Lungime_m. Clasa Lungime_inch s-a declarat nainte de clasa
Lungime_m, deoarece constructorul, care realizeaz conversia, folosete tipul Lungime_inch.
//calculeaza produsul
//a doua fractii,
//nu simplifica
void afiseaza();
};
inline fractie::fractie(long a, long
{
numarator = a;
numitor = b;
}
fractie::operator double()
{
return double(numarator) / numitor;
177
b)
}
inline fractie fractie::operator* (fractie r)
{
return fractie(numarator*r.numarator,
numitor*r.numitor);
}
inline void fractie::afiseaza()
{
if ( numitor )
cout << numarator << " / " << numitor << endl;
else
cerr << "Fractie incorecta";
}
int main() {
clrscr();
fractie x(2,5);
double y;
y = x * 4.5;
cout << y << endl;
y = 4.5 * x;
cout << y << endl;
return 0;
}
178
void afisare();
};
class Lungime_inch {
int line;
int inch;
int foot;
int yard;
public:
Lungime_inch(int l = 0, int i = 0, int f = 0, int y = 0);
operator Lungime_m();
long line_lungime();
void afisare();
};
Lungime_m::Lungime_m( int mm_1, int cm_1,
int dm_1, int m_1, double rest_1)
{
mm = mm_1;
cm = cm_1;
dm = dm_1;
m = m_1;
rest = rest_1;
}
void Lungime_m::afisare()
{
if ( m )
cout << m << " m ";
if ( dm )
cout << dm << " dm ";
if ( cm )
cout << cm << " cm ";
if ( mm + rest )
cout << mm + rest << " mm ";
cout << endl;
}
Lungime_inch::Lungime_inch( int l, int i, int f, int y)
{
line = l;
inch = i;
foot = f;
yard = y;
}
Lungime_inch::operator Lungime_m()
{
double rest_1 = 2.54 * line_lungime();
long mm_1 = rest_1;
rest_1 -= mm_1;
long m_1 = mm_1 / 1000;
mm_1 %= 1000;
long dm_1 = mm_1 / 100;
mm_1 %= 100;
long cm_1 = mm_1 / 10;
mm_1 %= 10;
return Lungime_m(mm_1, cm_1, dm_1, m_1, rest_1);
}
long Lungime_inch::line_lungime()
{
return line + 10 * inch + 120 * foot + 360 * yard;
179
}
void Lungime_inch::afisare()
{
if ( line )
cout << line << " line ";
if ( inch )
cout << inch << " inch ";
if ( foot )
cout << foot << " foot ";
if ( yard )
cout << yard << " yard";
cout << endl;
}
int main() {
clrscr();
Lungime_inch y(5, 2, 1, 3);
y.afisare();
cout << y.line_lungime() << " line" << endl;
cout << 2.54 * y.line_lungime() << " mm" << endl;
Lungime_m x;
x = y;
x.afisare();
return 0;
}
n principiu conversia dintr-un tip abstract ntr-un alt tip abstract se poate realiza n
dou moduri: cu ajutorul unui constructor i prin suprancrcarea operatorul de conversie
explicit. ns n cazul unei aplicaii concrete, numai unul din aceste dou moduri poate fi
folosit. n caz contrar compilatorul va semnala o eroare.
180
membru functia_1"
<< endl;
membru functia_2"
<< endl;
181
void derivata::functia_1()
{
cout << "S-a apelat functia membru functia_1"
<< " a clasei derivate" << endl;
}
int main() {
clrscr();
derivata D;
D.functia_2();
return 0;
}
ns acest lucru nu este rezultatul dorit, deoarece n cadrul funciei main s-a apelat
funcia membru functia_2 motenit de la clasa de baz, dar funcia membru functia_1 apelat
de functia_2 s-a determinat nc n faza de compilare. n consecin, dei funcia membru
functia_1 s-a suprancrcat n clasa derivat nu s-a apelat funcia suprancrcat ci funcia
membru a clasei de baz. De fapt i din rezultatul execuiei programului se obine acelai
lucru.
Acest neajuns se poate nltura cu ajutorul introducerii noiunii de funcie membru
virtual. Dac funcia membru este virtual, atunci la orice apelare a ei, determinarea funciei
membru corespunztoare a ierarhiei de clase nu se va face la compilare ci la execuie, n
funcie de natura obiectului pentru care s-a fcut apelarea. Aceast proprietate se numete
legare dinamic, iar dac determinarea funciei membru se face la compilare, atunci se
vorbete de legare static.
sau
protected clas_de_baz_i
sau
private clas_de_baz_i
neschimbate dac modificatorul de protecie referitor la clasa de baz este public. Din acest
motiv n general datele membru se declar de tip protected i modificatorul de protecie
referitor la clasa de baz este public. Astfel datele membru pot fi accesate, dar rmn protejate
i n clasa derivat.
Accesul la elementele
din clasa de baz
public
protected
private
public
protected
private
public
protected
private
Modificatorii de
protecie referitoare la
clasa de baz
public
public
public
protected
protected
protected
private
private
private
Accesul la elementele
din clasa derivat
public
protected
inaccesibil
protected
protected
inaccesibil
private
private
inaccesibil
pentru numrtor fiind zero iar pentru numitor unu, precum i doi operatori: * pentru
nmulirea a dou fracii i *= pentru nmulirea obiectului curent cu fracia dat ca i
parametru. Deasemenea clasa fractie trebuie s aib i o funcie membru pentru afiarea unui
numr raional. Folosind clasa fractie ca i clas de baz se va defini clasa derivat
fractie_scrie, pentru care se va suprancrca operatorul de nmulire * astfel nct concomitent
cu efectuarea nmulirii s se afieze pe stdout operaia respectiv. Operaia *= nu se va
suprancrca, dar operaia efectuat trebuie s se afieze pe dispozitivul standard de ieire i n
acest caz. n limbajul C++ clasele se definesc n modul urmtor. Fiierul fractie2.cpp:
#include <conio.h>
#include <iostream.h>
class fractie {
protected:
int numarator;
int numitor;
public:
fractie(int numarator1, int numitor1);
fractie operator *( fractie& r); //calculeaza
//produsul
//a doua fractii
//nu simplifica
fractie& operator *=( fractie& r);
void afiseaza();
};
fractie::fractie(int numarator1 = 1,
int numitor1 = 0)
{
numarator = numarator1;
numitor = numitor1;
}
fractie fractie::operator *(fractie& r)
{
return fractie(numarator*r.numarator,
numitor*r.numitor);
}
fractie& fractie::operator *=( fractie& q )
{
*this = *this * q;
return *this;
}
void fractie::afiseaza()
{
if ( numitor )
cout << numarator << " / " << numitor;
else
cerr << "Fractie incorecta";
}
class fractie_scrie: public fractie{
public:
fractie_scrie( int numarator1 = 0, int numitor1 = 1 ):
fractie(numarator1, numitor1) {}
fractie operator *( fractie& r);
};
184
/
/
/
/
/
/
8
8
4) * (5 / 2) = 15 / 8
8
8
8
Observm c rezultatul nu este cel dorit, deoarece operaia de nmulire s-a afiat
numai o singur dat, i anume pentru expresia r1 = p1 * q1. n cazul expresiei r2 = p1 *= q1
ns nu s-a afiat operaia de nmulire. Acest lucru se datoreaz faptului c funcia membru
operator *= nu s-a suprancrcat n clasa derivat. Deci s-a apelat operatorul *= motenit de
la clasa fractie. n interiorul operatorului *= s-a apelat funcia membru operator *, dar
deoarece aceast funcie membru s-a determinat nc n faza de compilare, rezult c s-a
apelat operatorul de nmulire referitor la clasa fractie i nu cel referitor la clasa derivat
fractie_scrie. Deci afiarea operaiei s-a efectuat numai o singur dat.
Soluia este, ca i n exemplul anterior, declararea unei funcii membru virtuale, i
anume operatorul * se va declara virtual. Deci declararea clasei de baz se modific n felul
urmtor:
class fractie {
protected:
int numarator;
int numitor;
185
public:
fractie(int numarator1, int numitor1);
virtual fractie operator *( fractie& r);
fractie& operator *=( fractie& r);
void afiseaza();
};
/
/
/
/
/
/
/
8
8
4) * (5 / 2) = 15 / 8
4) * (5 / 2) = 15 / 8
8
8
8
Deci se observ c afiarea operaiei s-a fcut de dou ori, pentru ambele expresii.
Funciile virtuale, ca i alte funcii membru de fapt, nu trebuie neaprat suprancrcate n
clasele derivate. Dac nu sunt suprancrcate atunci se motenete funcia membru de la un
nivel superior.
Determinarea funciilor membru virtuale corespunztoare se face pe baza unor tabele
construite i gestionate n mod automat. Obiectele claselor care au funcii membru virtuale
conin i un pointer ctre tabela construit. De aceea gestionarea funciilor membru virtuale
necesit mai mult memorie i un timp de execuie mai ndelungat.
186
187
Observm c data membru nume s-a motenit n dou exemplare de ctre clasa cine,
i referirea la aceste date s-a fcut prin mamifer::nume respectiv domestic::nume. Prin
execuie se va afia data membru nume de dou ori, motenit prin cele dou clase.
Deasemenea se va afia greutatea i comportamentul cinelui, i faptul c latr sau nu.
Ar fi de dorit ns ca numele s fie memorat numai ntr-un singur exemplar n
obiectele clasei cine. Acest lucru se poate realiza cu ajutorul claselor virtuale.
Dac nu se dorete ca datele membru a unei clase de baz s fie prezente n mai multe
exemplare ntr-o clas derivat, atunci se folosesc clase virtuale. Clasele de baz devin
virtuale prin motenire, dac se specific acest lucru prin plasarea cuvntului cheie virtual n
faa numelui clasei, n lista claselor de baz. Astfel clasa de baz respectiv va deveni virtual
referitor la clasa derivat.
Exemplul de mai sus se modific n modul urmtor. Fiierul animal2.cpp:
#include <iostream.h>
#include <string.h>
#include <conio.h>
class animal {
protected:
char nume[20];
public:
animal(char* n);
};
class mamifer : virtual public animal {
protected:
int greutate;
public:
mamifer(char* n, int g);
};
//clasa animal va fi
//virtuala referitor
//la clasa mamifer
188
comportament = c;
}
caine::caine(char* n, int g, int c, int l):
animal(n),
//trebuie apelat constructorul clasei animal
mamifer(n, g),//in mod explicit, deoarece ea fiind virtuala
domestic(n, c)//constructorul nu se va apela prin unul din
{ latra = l; } //clasele mamifer sau domestic
void caine::scrie_date()
{
cout << "Numele: " << nume << endl;
cout << "Greutatea: " << greutate << endl;
cout << "Comportmentul: " << comportament << endl;
if ( latra )
cout << "Latra: da" << endl;
else
cout << "Latra: nu" << endl;
}
int main() {
clrscr();
caine B("boxer", 12, 9, 1);
B.scrie_date();
return 0;
}
ntr-o ierarhie complicat de clase unele clase de baz pot fi motenite n mai multe
exemplare ntr-o clas derivat. ntre aceste exemplare pot fi virtuale i nevirtuale. S
considerm urmtorul exemplu referitor la acest lucru :
class A {
public:
A(char* a) {
cout << "Se apeleaza constructorul clasei A"
<< " cu parametrul " << a << endl;
189
}
};
class B: virtual public A {
public:
B(char* b): A(b) {
cout << "Se apeleaza constructorul clasei B"
<< " cu parametrul " << b << endl;
}
};
class C: virtual public A {
public:
C(char* c): A(c) {
cout << "Se apeleaza constructorul clasei C"
<< " cu parametrul " << c << endl;
}
};
class D: public A {
public:
D(char* d): A(d) {
cout << "Se apeleaza constructorul clasei D"
<< " cu parametrul " << d << endl;
}
};
class E {
public:
E(char* e) {
cout << "Se apeleaza constructorul clasei E"
<< " cu parametrul " << e << endl;
}
};
class F {
public:
F(char* f) {
cout << "Se apeleaza constructorul clasei F"
<< " cu parametrul " << f << endl;
}
};
class G: public B, public C, public D, public E, virtual public F {
public:
G(char* g): A(g), B(g), C(g), D(g), E(g), F(g) {
cout << "Se apeleaza constructorul clasei G"
<< " cu parametrul " << g << endl;
}
};
int main() {
clrscr();
G ob("obiect");
return 0;
}
190
Se
Se
Se
Se
Se
apeleaza
apeleaza
apeleaza
apeleaza
apeleaza
constructorul
constructorul
constructorul
constructorul
constructorul
clasei
clasei
clasei
clasei
clasei
C
A
D
E
G
cu
cu
cu
cu
cu
parametrul
parametrul
parametrul
parametrul
parametrul
obiect
obiect
obiect
obiect
obiect
atunci i clasa derivat va fi clas abstract i ca atare nu se pot defini obiecte aparinnd
acelei clase.
S considerm exemplul de mai sus i s scriem un program, care referitor la un
porumbel, urs sau cal determin dac el este gras sau slab, rapid sau ncet, respectiv tiner sau
btrn. Afiarea acestui rezultat se va face de ctre o funcie membru a clasei animal care nu
se suprancarc n clasele derivate. Fiierul abstract.cpp:
#include <conio.h>
#include <iostream.h>
class animal {
protected:
double greutate; // kg
double virsta;
// ani
double viteza;
// km / h
public:
animal( double g, double v1, double v2);
virtual double greutate_medie() = 0;
virtual double durata_de_viata_medie() = 0;
virtual double viteza_medie() = 0;
int gras() { return greutate > greutate_medie(); }
int rapid() { return viteza > viteza_medie(); }
int tiner()
{ return 2 * virsta < durata_de_viata_medie(); }
void afiseaza();
};
animal::animal( double g, double v1, double v2)
{
greutate = g;
virsta = v1;
viteza = v2;
}
void animal::afiseaza()
{
cout << ( gras() ? "gras, " : "slab, " );
cout << ( tiner() ? "tiner, " : "batran, " );
cout << ( rapid() ? "rapid" : "incet" ) << endl;
}
class porumbel : public animal {
public:
porumbel( double g, double v1, double v2):
animal(g, v1, v2) {}
double greutate_medie() { return 0.5; }
double durata_de_viata_medie() { return 6; }
double viteza_medie() { return 90; }
};
class urs: public animal {
public:
urs( double g, double v1, double v2):
animal(g, v1, v2) {}
double greutate_medie() { return 450; }
double durata_de_viata_medie() { return 43; }
double viteza_medie() { return 40; }
};
class cal: public animal {
192
public:
cal( double g, double v1, double v2):
animal(g, v1, v2) {}
double greutate_medie() { return 1000; }
double durata_de_viata_medie() { return 36; }
double viteza_medie() { return 60; }
};
int main() {
clrscr();
porumbel p(0.6, 1, 80);
urs u(500, 40, 46);
cal c(900, 8, 70);
p.afiseaza();
u.afiseaza();
c.afiseaza();
return 0;
}
Observm c dei clasa animal este clas abstract, este util introducerea ei, pentru c
multe funcii membru pot fi definite n clasa de baz i motenite fr modificri n cele trei
clase derivate.
193
Obiect al clasei
istream_withassign
ostream_withassign
ostream_withassign
ostream_withassign
195
Observm c rezultatul afirii irurilor de caractere i datelor de tip int, double i char
este acelai. n cazul afirii unui pointer, valoarea afiat este identic, dar formatul difer
(dac se scrie cu printf se folosesc litere mari i nu se afieaz baza, iar n cazul scrierii cu
cout se folosesc litere mici i se afieaz baza).
Deoarece operatorul << returneaz o referin ctre clasa curent, operatorul se poate
aplica n mod nlnuit. Acest lucru este prezentat n exemplul urmtor. Fiierul
stream2.cpp:
#include <stdio.h>
#include <iostream.h>
#include <conio.h>
int main() {
clrscr();
int x = 10;
cout << "x (dupa incrementare) = " << x
<< "\nx (inainte de incrementare) = " << x++;
x = 10;
cout << "\n";
printf("x (dupa incrementare) = %d\
\nx (inainte de incrementare) = %d", x, x++);
return 0;
}
(dupa incrementare) = 11
(inainte de incrementare) = 10
(dupa incrementare) = 11
(inainte de incrementare) = 10
=
=
=
=
=
=
=
=
=
197
uppercase = 0x0200,
showpos
= 0x0400,
scientific=
fixed
=
unitbuf
=
stdio
=
};
...
};
Data membru x_flags are o valoare implicit pentru fiecare tip standard. Astfel
rezultatul afirii datelor care aparin tipurilor standard, va fi n mod implicit identic cu
rezultatul afirii cu funcia printf, folosind specificatori de format care corespund tipurilor
respective. Prezentm aceast legtur ntre tipuri i specificatori de format, n tabelul 4.
Dac se modific biii corespunztori datei membru x_flags, atunci i rezultatul afirii
se va schimba, conform formatului specificat. Acest lucru se poate realiza cu ajutorul unor
funcii membru.
Tip
int
long
unsigned
long unsigned
float
double
long double
char
ir de caractere
Specificator de format
corespunztor
%d
%ld
%u
%lu
%g
%lg
%Lg
%c
%s
i
long setf(long setbit, long grupa);
Prima variant a funciei membru setf seteaz biii corespunztor parametrului de tip
long: format. Dac un bit din format este egal cu unu, atunci bitul corespunztor din x_flags
va fi unu, iar dac bitul din format este egal cu zero, atunci bitul corepunztor din x_flags
rmne neschimbat.
198
A doua variant a funciei membru setf seteaz un bit din una dintre cele trei grupe
adjustfield, basefield sau floatfield. Cu ajutorul parametrului setbit se determin bitul care se
va seta. n locul unde se afl bitul trebuie s fie unu, iar n rest zero. n parametrul al doilea
trebuie specificat numele grupei. n acest caz se anuleaz biii corespunztori grupei dup care
se seteaz biii din setbit. Ambele variante ale funciei setf returneaz valoarea datei membru
x_flags nainte de modificare.
Referirea la biii datei membru x_flags se face cu numele clasei ios, urmat de
operatorul de rezoluie i numele bitului din tipul enumerare. Referirea la numele unei grupe
se face n mod analog, nlocuid numele din tipul enumerare cu numele grupei. Utilizarea
funciei membru setf este ilustrat de urmtorul exemplu. Fiierul stream4.cpp:
#include <stdio.h>
#include <iostream.h>
#include <conio.h>
void binar_c( long x )
{
printf("x_flags: ");
for( int i = 8*sizeof(long)-1; i >= 0; i--)
{
printf("%d", (x >> i)& 1);
if ( !(i % 8) )
printf(" ");
}
printf("\n");
}
void nume_bit_1( char* s[], long x )
{
for( int i = 0; i < 15; i++)
if ( (x >> i)& 1 )
printf("%-16s", s[i]);
printf("\n");
}
void afisare( char* s[], long x )
{
binar_c( x );
nume_bit_1( s, x);
}
int main() {
char* nume_enum[] = {
"skipws",
"left",
"right",
"internal",
"dec",
"oct",
"hex",
"showbase",
"showpoint",
"uppercase",
"showpos",
"scientific",
"fixed",
"unitbuf",
"stdio"
};
199
clrscr();
afisare( nume_enum, cout.flags() );
cout.setf(ios::oct, ios::basefield);
afisare( nume_enum, cout.flags() );
cout << 36 << '\n';
afisare( nume_enum, cout.flags() );
cout.setf(ios::showbase);
cout.setf(ios::hex, ios::basefield);
afisare( nume_enum, cout.flags() );
cout << 36 << '\n';
afisare( nume_enum, cout.flags() );
return 0;
}
unitbuf
unitbuf
Funcia afisare, din exemplul de mai sus, afieaz mai nti biii datei membru x_flags,
dup care se scriu numele biilor cu valoarea egal cu unu. Mai nti s-a setat bitul oct i s-a
afiat valoarea constantei de tip ntreg 36, folosind o conversie n octal. Astfel s-a obinut
valoarea 44. Dup aceea s-au setat biii hex i showbase, i s-a afiat valoarea constantei nc
o dat, astfel obinnd valoarea 0x24. Observm c folosind a doua variant a funciei
membru setf pentru setarea bitului hex, s-a obinut i anularea bitului oct, aa cum am dorit.
Menionm c numele i valoarea tuturor biilor tipului enumerare s-ar fi putut afia de
exemplu cu urmtoarea funcie:
void nume_bit( char* s[], long x )
{
for( int i = 0; i < 15; i++)
printf("%-11s:%2d\n", s[i], (x >> i)& 1);
}
O alt observaie este urmtoarea. Pentru afiarea biilor datei membru x_flags s-a
folosit funcia printf i nu ierarhia de clase declarat n fiierul iostream.h. Dei n general o
funcie de forma
void binar_stream( long x )
{
for( int i = 8*sizeof(long)-1; i >= 0; i-- )
cout << ((x >> i)& 1) << (i%8 ? "": " ");
cout << '\n';
}
ar afia corect biii datei membru x_flags, vor apare erori n cazul n care este setat
bitul showbase i unul dintre biii oct sau hex. n aceste cazuri se va afia i baza setat, deci
200
numerele ntregi vor fi precedate de zero n cazul conversiei n octal, i de 0x sau 0X n cazul
conversiei n hexazecimal.
i
int width( int lungime );
Prima form a funciei membru width returneaz valoarea datei membru x_width. A
doua variant modific valoarea datei membru x_width la valoarea determinat de lungime, i
returneaz vechea valoare a lui x_width.
Este foarte important de menionat c dup orice operaie de intare/ieire valoarea
datei membru x_width se va reseta la valoarea zero. Deci dac nu se determin o lungime a
cmpului nainte de o operaie de inserare, atunci se va folosi valoarea implicit.
Dac lungimea cmpului, n care se face afiarea, este mai mare dect numrul de
caractere, care vor fi afiate, atunci cadrarea se va face n mod implicit la dreapta, i spaiul
rmas se va completa cu caractere de umplere. n mod implicit caracterele de umplere sunt
spaii, dar ele pot fi modificate cu ajutorul funciei membru fill a clasei ios. Clasa ios are o
dat membru x_fill n care se memoreaz caracterul de umplere. Funcia membru fill are
urmtoarele dou forme:
char fill();
i
char fill( char car );
i
int precision( int p );
numrului . Prezentm n continuare un alt exemplu, n care se vor folosi funciile membru
din acest paragraf. Fiierul stream5.cpp:
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
const double pi = 3.141592653;
void scrie_width_precision_c()
{
printf("x_width:
%d\n", cout.width() );
printf("x_precision: %d\n", cout.precision() );
}
void afisare_pi_c()
{
printf("*");
cout << pi;
printf("*\n");
}
int main() {
clrscr();
scrie_width_precision_c();
afisare_pi_c();
cout.width(7);
cout.precision(2);
scrie_width_precision_c();
afisare_pi_c();
scrie_width_precision_c();
afisare_pi_c();
cout.width(7);
cout.fill('@');
scrie_width_precision_c();
afisare_pi_c();
return 0;
}
0
0
7
2
0
2
7
2
Caracterele '*' s-au afiat pentru a evidenia cmpul n care se face scrierea datelor.
Observm c ntr-adevr valoarea implicit a datelor membru x_width i x_precision este
zero, deci afiarea se va face cu ase zecimale.
Dup modificarea acestor date membru la valorile x_width=7 respectiv x_precision=2,
afiarea se face n cmpul de apte caractere, cu dou zecimale, numrul fiind cadrat la
202
dreapta. Dup operaia de scriere valoarea datei membru x_width devine zero, dar valoarea
datei membru x_precision nu se modific.
Folosind funcia membru width, se atribuie datei membru x_width din nou valoarea
apte. n continuare se folosete funcia membru fill pentru a determina un caracter de
umplere diferit de caracterul blanc.
Menionm c dac n loc de apelarea funciei printf, s-ar fi folosit ierarhia de clase
declarat n fiierul iostream.h, atunci nu s-ar fi obinut ceea ce s-a dorit. De exemplu, dac n
loc de funciile scrie_width_precision_c i afisare_pi_c s-ar fi folosit funciile:
void scrie_width_precision_stream()
{
cout << "x_width:
" << cout.width() << '\n';
cout << "x_precision: " << cout.precision() << '\n';
}
void afisare_pi_stream()
{
cout << "*" << pi << "*\n";
}
0
0
7
2
0
2
7
2
n acest caz, de fiecare dat, valoarea lui se afieaz pe un cmp de lungime egal cu
numrul de caractere afiate. Explicaia este c datei membru x_width dup prima operaie de
inserare s-a atribuit valoarea zero, prima operaie fiind afiarea irului de caractere
"x_width: ", i nu scrierea valorii lui .
Exist o legtur strns ntre funciile membru prezentate n acest paragraf i
formatrile din cadrul funciei printf. Acest lucru este ilustrat de urmtorul exemplu. Fiierul
stream6.cpp:
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
int main() {
clrscr();
const double x = 987.65432;
cout << "*";
cout.width(11);
cout.precision(4);
cout.fill('0');
cout << x << "*\n";
printf("*%011.4lf*\n", x);
return 0;
203
n exemplul de mai sus s-a afiat valoarea constantei de tip real x n dou moduri: cu
ajutorul streamurilor, i folosind funcia printf. Putem s constatm c s-a obinut acelai
rezultat. Dei n acest caz scrierea cu funcia printf este mai compact, afiarea cu streamuri
este mai general, deoarece caracterul de umplere poate fi orice alt caracter, nu numai '0'.
18.2.4. Manipulatori
Biii datei membru x_flags, care corespund conversiei, pot fi setai i ntr-un alt mod,
folosind funcii membru speciale, numite manipulatori. Avantajul manipulatorilor este c ei
returneaz o referin la un stream, deci apelurile acestor funcii membru pot fi nlnuite.
O parte a manipulatorilor este declarat n fiierul iostream.h, iar celelalte n fiierul
iomanip.h. Manipulatorii declarai n fiierul iostream.h sunt:
endl
ends
flush
dec
hex
oct
ws
resetiosflags(long x)
setiosflags(long x)
setfill(int f)
setprecision(int p)
setw(int w)
#include
#include
#include
#include
<iostream.h>
<iomanip.h>
<stdio.h>
<conio.h>
int main() {
clrscr();
const double x = 987.65432;
cout << "*" << setw(11) << setprecision(4)
<< setfill('0') << x << "*" << endl;
printf("*%011.4lf*\n", x);
return 0;
}
Rezultatul obinut este identic cu cel al programului anterior. Manipulatorii s-au apelat
n mod nlnuit, deci programul devine mai simplu. n urmtorul exemplu se va afia
valoarea datei membru x_flags n binar, hexazecimal, octal i zecimal. Fiierul stream8.cpp:
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
void binar_c( long x )
{
printf("x_flags: ");
for( int i = 8*sizeof(long)-1; i >= 0; i--)
{
printf("%d", (x >> i)& 1);
if ( !(i % 8) )
printf(" ");
}
printf("\n");
}
int main() {
clrscr();
binar_c( cout.flags()
cout << "Hexazecimal:
<< "Octal:
<< "Zecimal:
return 0;
}
);
" << hex << cout.flags() << endl
" << oct << cout.flags() << endl
" << dec << cout.flags() << endl;
205
206
207
Rezultatul obinut prin executarea acestui program este identic cu cel al fiierului
dar n acest caz nu s-a folosit funcia prieten.
fractie2.cpp,
208
#include <conio.h>
int main() {
clrscr();
int x;
printf("x (int) = ");
scanf("%d", &x);
double y;
printf("y (double) = ");
scanf("%lf", &y);
char z[20];
printf("z (sir de caractere) = ");
scanf("%s", z);
printf("Datele citite sunt:\
\nx = %d\ny = %lg\nz = %s\n", x, y, z);
return 0;
}
Rezult, c n cazurile de mai sus, nu exist nici o diferen ntre citirea datelor de la
intrarea standard folosind operatorul de extragere, respectiv cu funcia scanf. Totui n
anumite cazuri pot s apar diferene. De exemplu secvena
char c;
...
cin >> c;
nu este identic cu
char c;
...
scanf("%c", &c);
Diferena apare n cazul n care la intrare caracterul curent este un caracter alb, deci
spaiu, tab sau caracter newline (trecere la rnd nou). Prezentm n continuare un exemplu, din
care rezult, c ntr-adevr cele dou secvene de program nu sunt identice.
Vom defini o clas c_alb pentru gestionarea caracterelor, i vom suprancrca
operatorul de inserare pentru aceast clas. Suprancrcarea se va face astfel nct caracterele
albe s fie afiate n mod vizibil (' ' pentru spaii, '\t' pentru taburi i '\n' pentru caractere
newline). Caracterele diferite de cele albe vor fi afiate nemodificate. Fiierul stream11.cpp:
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
class c_alb {
char c;
public:
c_alb( char c_1 ) { c = c_1; }
ostream& afisare( ostream& s);
};
ostream& c_alb::afisare( ostream& s)
{
switch ( c ) {
case '\n': s << "\'\\n\'"; break;
case '\t': s << "\'\\t\'"; break;
case ' ' : s << "\' \'"; break;
default:
s << c;
209
}
return s;
}
ostream& operator<<(ostream& s, c_alb car)
{
return car.afisare( s );
}
int main() {
clrscr();
char v;
cout << "Citire caracter (cu operatorul >>). Intrare = ";
cin >> v;
cout << "Caracterul citit (cu operatorul >>) este: "
<< c_alb(v) << endl;
printf("Citire caracter (cu functia scanf). Intrare = ");
scanf("%c", &v);
cout << "Caracterul citit (cu functia scanf) este: "
<< c_alb(v) << endl;
return 0;
}
Intrare =
este: q
Intrare =
este: '\t'
q
q
La intrare s-a tastat mai nti un caracter tab, dup aceea caracterul 'q', urmat de
trecerea la un rnd nou. Acelai lucru s-a repetat i n cazul citirii cu scanf. La scriere,
caracterul v s-a convertit mai nti ntr-un obiect anonim de tip c_alb, pentru a obine o afiare
corespunztoare a caracterelor albe.
Din cele de mai sus rezult c prin citirea cu operatorul de extragere s-a obinut primul
caracter diferit de caracterele albe. Dac s-a apelat funcia scanf, s-a citit caracterului curent,
indiferent dac el a fost caracter alb sau nu.
Menionm c diferena dintre cele dou modaliti de citire se poate nltura, dac se
anuleaz bitul skipws al datei membru x_flags a clasei ios (valoarea acestui bit n mod implicit
este unu).
n cazul funciei scanf, cmpul din care se face citirea ncepe cu primul caracter diferit
de caracterele albe i se termin, dac urmtorul caracter este alb, sau caracterul respectiv nu
mai corespunde formatului.
n cazul citirii unui ir de caractere, lungimea maxim a cmpului, din care se face
citirea, se determin cu funcia membru width a clasei ios. Este valabil tot ce s-a spus referitor
la funcia membru width n seciunea 18.2.3, dar n acest caz valoarea datei membru x_width
se interpreteaz ca i lungimea maxim a cmpului, din care se face citirea, n loc de lungimea
minim a cmpului n care se face afiarea. Un avantaj important al funciei membru width,
comparativ cu utilizarea funciei scanf, este c parametrul actual al funciei width poate fi
orice expresie, n timp ce aceast valoare n cazul funciei scanf poate fi numai o constant.
Acest lucru se poate folosi pentru nlturarea erorilor, care ar putea s apar din cauza citirii
unui numr mai mare de caractere, dect zona de memorie alocat. Considerm urmtorul
exemplu pentru ilustrare. Fiierul stream12.cpp:
#include <iostream.h>
#include <conio.h>
210
int main() {
clrscr();
char* t;
int max;
cout << "max = ";
cin >> max;
t = new char[max]; // cel mult max-1 caractere si
// caracterul nul
cout << "Citire cel mult " << max-1 << " caractere): ";
cin.width(max);
cin >> t;
cout << "Caracterele citite sunt: " << t;
delete [] t;
return 0;
}
Dup executarea programului obinem urmtorul rezultat (s-au evideniat datele de intrare).
max = 5
Citire cel mult 4 caractere: abcdefghij
Caracterele citite sunt: abcd
Se observ, c dei la intrare s-au tastat mai multe caractere, nu s-a citit dect numrul
de caractere, care se poate memora n spaiul alocat irului.
n cazul operaiilor de extragere se pot folosi i manipulatorii definii n paragraful
18.2.4, cu excepia manipulatorilor endl, ends, flush i setbase. Manipulatorul ws poate fi
folosit numai n operaii de extragere. Se pot folosi i celelalte funcii membru ale clasei ios,
de exemplu funcia membru setf.
{
0x00, // operaie de intrare/ieire corect
0x01, // s-a ajuns la sfrit de fiier
0x02, // ultima operaie de intrare/ieire s-a
// terminat cu insucces
badbit
= 0x04, // operaie invalid
hardfail = 0x80 // eroare irecuperabil
};
...
};
Biii eofbit, failbit, badbit i hardfail se vor numi bii de eroare. Valorile biilor datei
membru state pot fi determinate folosind urmtoarele funcii membru ale clasei ios.
int good();
//
//
int eof();
int fail();
int bad();
//
//
//
Aceste funcii membru returneaz valori diferite de zero, dac condiiile scrise sub
form de comentarii sunt ndeplinite. Funcia membru
int rdstate();
returneaz valoarea datei membru state. Modificarea valorilor biilor datei membru state se
poate efectua cu ajutorul funciei membru clear a clasei ios, funcie declarat n urmtorul
mod:
void clear(int = 0);
Dac se apeleaz funcia membru clear fr parametru, atunci toi biii de eroare se
vor anula, n afar de bitul hardfail, care nu se poate anula. Dac parametrul activ este
prezent, atunci data membru state va lua valoarea parametrului. Pentru a seta un anumit bit se
va folosi numele bitului precedat de numele clasei ios i operatorul de rezoluie. Dac se
folosete o construcie de forma
cin.clear( ios::badbit | cin.rdstate() );
ceilali bii vor rmne nemodificai. n acest caz s-a setat numai bitul badbit, iar ceilali au
rmas nemodificai. n urmtorul exemplu este prezentat modul de utilizare al funciei clear.
Fiierul stare1.cpp:
#include <iostream.h>
#include <conio.h>
void binar( int x )
{
for( int i = 8*sizeof(int)-1; i >= 0; i-- )
cout << ((x >> i)& 1) << (i%8 ? "": " ");
cout << endl;
}
void nume_bit_1( char* s[], int x )
{
int good_setat = 1;
for( int i = 0; i < 5; i++)
if ( (x >> i)& 1 ) {
good_setat = 0;
cout.width(16);
cout.setf(ios::left, ios::adjustfield);
cout << s[i+1];
}
if ( good_setat ) {
cout.width(16);
cout.setf(ios::left, ios::adjustfield);
cout << s[0];
}
cout << endl;
}
void afisare( char* s[], int x )
{
cout << "Data membru state: ";
binar( x );
nume_bit_1( s, x);
}
212
int main() {
char* nume_state[] = {
"goodbit",
"eofbit",
"failbit",
"badbit",
"hardfail"
};
char t[255];
clrscr();
afisare( nume_state, cin.rdstate() );
int x;
cout << "x = ";
cin >> x;
cout << "fail() = " << cin.fail() << endl;
afisare( nume_state, cin.rdstate() );
cout << "
Setarea bitului badbit\n";
cin.clear(ios::badbit | cin.rdstate() );
afisare( nume_state, cin.rdstate() );
cout << "
Anularea bitilor de eroare\n"
<< "
si vidarea zonei tampon la intrare\n";
cin.clear();
// anularea bitilor de eroare
cin.getline(t, 255);
// vidarea zonei tampon la intrare
afisare( nume_state, cin.rdstate() );
int y;
cout << "y = ";
cin >> y;
cout << "fail() = " << cin.fail() << endl;
afisare( nume_state, cin.rdstate() );
return 0;
}
Dac se execut programul, se obine urmtorul rezultat (sunt evideniate caracterele citite).
Data membru state: 00000000 00000000
goodbit
x = a
fail() = 2
Data membru state: 00000000 00000010
failbit
Setarea bitului badbit
Data membru state: 00000000 00000110
failbit
badbit
Anularea bitilor de eroare
si vidarea zonei tampon la intrare
Data membru state: 00000000 00000000
goodbit
y = 3140
fail() = 0
Data membru state: 00000000 00000000
goodbit
Funcia binar afieaz valoarea parametrului actual de tip int, n modul n care este
memorat, iar funcia nume_bit_1 afieaz numele biilor cu valoarea unu. Funcia afisare
apeleaz mai nti funcia binar, iar dup aceea funcia nume_bit_1. Deci un apel de forma
afisare( nume_state, cin.rdstate() );
afieaz mai nti data membru state n forma n care este memorat n calculator, iar dup
aceea numele tuturor biilor setai ai datei membru state.
213
Observm c n cazul citirii variabilei de tip ntreg x, la intrare nu s-a aflat un numr
ntreg (s-a tastat caracterul a). De aceea streamul cin a intrat n stare de eroare, i s-a setat
bitul failbit. Funcia fail a returnat o valoare diferit de zero (valoarea 2 corespunztoare
bitului failbit). Dup aceea s-a setat bitul badbit de ctre programator folosind funcia
membru clear. n continuare, anularea biilor de eroare s-a fcut tot cu funcia membru clear.
nainte de o nou citire trebuie vidat zona tampon corespunztoare intrrii standard.
Acest lucru se poate efectua prin citirea unui ir de caractere. Dac citirea s-ar fi fcut cu
ajutorul operatorului de extragere, atunci irul de caractere citit s-ar fi terminat la primul
caracter alb. De aceea s-a folosit funcia membru getline a clasei istream, funcie care este
declarat n urmtoarele forme:
istream& getline(signed char* z, int n, char c = '\n');
i
istream& getline(unsigned char* z, int n, char c='\n');
Funcia membru getline citete din streamul clasei istream un numr de cel mult n-1
caractere. Citirea se termin la ntlnirea caracterului c, sau dac s-au citit toate cele n-1
caractere. Menionm c nu se iau n considerare formatrile referitoare la streamul din care
se citete.
Deoarece funcia membru getline citete i caracterele albe, ea poate fi folosit pentru
vidarea zonei tampon i n cazul n care la intrare s-au tastat mai multe caractere albe.
Observm c ntr-adevr dup anularea biilor de eroare i vidarea zonei tampon, se poate citi
i valoarea altor date, n acest caz valoarea variabilei y.
Faptul c un stream se afl n stare de eroare sau nu, poate fi verificat i n urmtoarele
dou moduri:
- folosind suprancrcarea operatorului '!';
- folosind conversia streamului ntr-un pointer de tip void*.
Operatorul '!' este suprancrcat cu funcia membru
int operator !();
a clasei ios. Valoarea returnat va fi diferit de zero, dac cel puin unul din biii failbit,
badbit sau hardfail este setat, deci dac funcia membru fail() returneaz o valoare diferit de
zero.
Operatorul '!' se va folosi n urmtorul exemplu. S se defineasc o clas pentru
prelucrarea datelor de tip ntreg. Pentru clasa respectiv se va defini o funcie membru citeste,
care va realiza citirea unei date de tip ntreg. n cazul unei eventuale erori, citirea se va repeta
pn cnd se va citi o dat corect, sau se ajunge la sfrit de fiier. Pentru scrierea datei de tip
ntreg se va suprancrca operatorul de inserare. Fiierul stare2.cpp:
#include <iostream.h>
#include <string.h>
#include <conio.h>
class Intreg {
int x;
214
int main() {
clrscr();
Intreg i(0, "i = ");
i.citeste( cin );
if ( cin.good() )
{
cout << "Valoarea citita este: " << i;
}
return 0;
}
Prin executarea programului obinem urmtorul rezultat (s-au evideniat caracterele citite).
i = abcd
Eroare la citire.
i = efg123
Eroare la citire.
i = 45
Valoarea citita este: 45
215
Clasa Intreg are urmtoarele trei date membru: ntregul propriu zis; textul, care se va
afia nainte de citire i mesajul, care va apare n cazul unei erori nainte de a relua citirea.
Observm c n corpul funciei membru citeste s-a folosit operatorul '!' pentru a testa
dac a aprut o eroare la citire sau nu. n cazul n care nu s-a tastat un ntreg, streamul intr n
stare de eroare, deci biii de eroare trebuie anulai i trebuie vidat zona tampon. Anularea
biilor de eroare s-a fcut cu funcia membru clear.
Vidarea zonei tampon s-a realizat cu ajutorul funciei membru get a clasei istream. Ea
are mai multe forme, din care amintim urmtoarele:
int get();
istream& get(signed char*, int, char = '\n');
istream& get(unsigned char*, int, char = '\n');
Prima form a funciei membru get extrage urmtorul caracter din streamul curent. n
caz de sfrit de fiier returneaz EOF, deci valoarea -1. A doua, respectiv a treia form a
funciei membru get este asemntoare cu cele dou forme a funciei membru getline.
Diferena este c n cazul funciei get caracterul terminal, determinat prin parametrul al
treilea, nu se extrage din streamul curent, iar n cazul funciei getline se extrage i caracterul
terminal.
Vidarea zonei tampon s-ar fi putut efectua ca i n cazul exemplului din fiierul
stare1.cpp, folosind funcia membru getline n modul urmtor:
s.getline(t, 255);
din fiierul stare1.cpp, cu apelarea funciei getline, obinem un rezultat asemntor, dar pot
s apar i diferene, de exemplu n cazul urmtor.
Rezultatul obinut prin executarea programului stare2.cpp, varianta cu funcia
membru getline este
i = abc^Z
Eroare la citire.
i =
abc^Z
Dei ambele rezultate pot fi considerate corecte, credem c n acest caz nu mai este
necesar afiarea mesajului de eroare i a textului pentru reluarea citirii. De aceea varianta cu
funcia membru get este cea preferat.
O alt modalitate de a verifica faptul c streamul este n stare de eroare sau nu, este
conversia spre tipul void*. Rezultatul conversiei este pointerul NUL, deci valoarea zero, dac
cel puin unul din biii failbit, badbit sau hardfail este setat, adic funcia membru fail()
returneaz o valoare diferit de zero. n caz contrar se obine un pointer diferit de zero. De
obicei aceast conversie se va efectua n cazul construciilor de forma urmtoare:
if ( stream >> data )
...
216
Rezultatul expresiei stream >> data este o referin la obiectul stream, care aparine
clasei istream. n cazul de mai sus rezultatul acestei expresii se va converti n mod automat
ntr-un pointer de tip void*. Astfel se poate testa, dac un bit de eroare, diferit de bitul eofbit,
este setat sau nu.
De exemplu funcia membru citeste a clasei Intreg se poate scrie n urmtoarea form:
istream& Intreg::citeste(istream& s)
{
char t[255];
do {
cout << afisare_text;
if ( (s >> x) || (s.eof()) )
return s;
s.clear();
// anularea bitilor de eroare
s.get(t, 255);
// vidarea zonei tampon la intrare
if ( s.get() == EOF ) //citeste '\n' sau EOF
return s;
cout << mesaj_eroare;
} while ( 1 );
}
class Clasa {
...
public:
istream& citire(istream& s);
...
};
istream& Clasa::citire(istream& s)
{
...
return s;
}
istream& operator>>(istream& s, Clasa& c1)
{
return c1.citire(s);
217
operatorul de extragere se va
218
19. Anex
19.1. Un memento al sintaxei limbajului C
Sintaxa limbajului C
a)
b)
c)
d)
e)
A. Atomi lexicali
<Cuvinte_cheie> ::= autobreakcasecharconstcontinuedefaultdodouble| elseenum
externfloatforgotoifint|longregister | returnshortsignedsizeof
static | struct switchtypedefunionunsignedvoidvolatilewhile
<Nume> ::= <Liter>[<Liter><Cifr>] ..
<Liter> ::= abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRS TUVWXYZ_
<Cifr> ::= 0123456789
<Constant> ::= <Constant_ntreag><Constant_real>
<Constant_enumerare><Constant_caracter>
<Constant_ntreag> ::= <Numr_ntreg>[<Sufix_ntreg>
<Numr_ntreg> ::= <ntreg_zecimal><ntreg_hexazecimal><ntreg_octal>
<ntreg_zecimal> ::= <Cifr_nenul>[<Cifr>] ..
<Cifr_nenul> ::= 123456789
<ntreg_hexazecimal> ::= 0x<Cifr_hexa> ..
<Cifr_hexa> ::= <Cifr>abcdefABCDEF
<ntreg_octal> ::= 0[<Cifr_octal>] ..
<Cifr_octal> ::= 01234567
<Sufix_ntreg> ::= <Sufix_unsigned>[<Sufix_long>]<Sufix_long>[<Sufix_unsigned>]
219
<Sufix_unsigned> ::= Uu
<Sufix_long> ::= Ll
<Constant_real> ::= <Constant_fract>[<Exponent>][<Sufix_real>]
<Cifr> .. <Exponent>[<Sufix_real>]
<Constant_fract> ::= [<Cifr>] .. . <Cifr>..<Cifr>.. .
<Exponent> ::= Ee[+-]<Cifr> ..
<Sufix_real> ::= FfLl
<Constant_enumerare> ::= <Nume>
<Constant_caracter> ::= '<Caracter_C> ..'L'<Caracter_C>'
<Caracter_C> ::= <Orice_caracter_tipribil_cu excepia: ' i \>
<Secven_escape>
<Secven_escape> ::= \"\'\?\\\a\b\f\n\r\t\v
\<Cifr_octal>[<Cifr_octal>[<Cifr_octal>]]
\x<Cifr_hexa> ..
<ir_de_caractere> ::= "[<Caracter_S> ..]"
<Caracter_S> ::= <Orice_caracter_tipribil_cu excepia: ' i \><Secvena_escape>
<Operatori_i_semne_de_punctuaie >::=
+-*/%^&~!=->+=-=*=
/=%=^=&=!=<<>><<=>>== =<=
>=<>&&!!++--,()[]{};?:...
B. Declaraii
<Unitate_de_compilare> ::= <O_declaraie> ..
<O_declaraie> ::= <Def_funcie><Declaraie>;
<Declaraie> ::= [<Specificatori>][<List_declaratori>]
<Specificatori> ::= <Specificator> ..
<Specificator> ::= <Clas_memorare><Tip>typedef
<Clas_memorare> ::= autoregisterstaticextern
<Tip> ::= <Tip_simplu><Nume_typedef><Calificator>
<Descriere_tip_enumerare><Descriere_tip_neomogen>
220
externstatic<Tip>
221
C. Expresii
<Expr> ::= <Expresie>[,<Expresie>] ..
<Expresie> ::= <Expr_condiional>|<Expr_unar><Oper_atribuire><Expresie>
<Oper_atribuire> ::= +*=/=%=+=-=<<=>>=&=^=!=
<Expr_condiional> ::= <Expr_SAU_logic>
<Expr_SAU_logic> ? <Expr> : <Expr_condiional>
<Expr_SAU_logic> ::= <Expr_I_logic><Expr_SAU_logic> !! <Expr_I_logic>
<Expr_I_logic> ::= <Expr_SAU><Expr_I_logic> && <Expr_SAU>
<Expr_SAU> ::= <Expr_SAU_exclusiv><Expr_SAU><Expr_SAU_exclusiv>
<Expr_SAU_exclusiv> ::= <Expr_I><Expr_SAU_exclusiv> ^ <Expr_I>
<Expr_I> ::= <Expr_egalitate><Expr_I> & <Expr_egalitate>
<Expr_egalitate> ::= <Expr_relaional><Expr_egalitate>==Expr_relaional>
<Expr_egalitate> != <Expr_relaional>
<Expr_relaional> ::= <Expr_deplasare> <Expr_relaional> < <Expr_deplasare>
<Expr_relaional> > <Expr_deplasare>
<Expr_relaional> <= <Expr_deplasare>
<Expr_relaional> >= <Expr_deplasare>
<Expr_deplasare> ::= <Expr_aditiv> <Expr_deplasare> << <Expr_aditiv>
<Expr_deplasare> >> <Expr_aditiv>
<Expr_aditiv> ::= <Expr_multiplicativ><Expr_aditiv> + <Expr_multiplicativ>
<Expr_aditiv> - <Expr_multiplicativ>
<Expr_multiplicativ>::=
<Expr_multiplicativ> * <Expr_prefixat>
<Expr_multiplicativ> / <Expr_prefixat>
<Expr_multiplicativ> % <Expr_prefixat>
D. Instruciuni
<Instr>::= <Instr_etichetat><Instr_compus> <Instr_expresie>Instr_de_selecie>
<Instr_de_ciclare><Instr_de_salt>
<Instr_etichetat> ::= <Nume>:<Instr>case <Expr_constant { :<Instr>
default : <Instr>
<Instr_compus> ::= <Bloc>
<Bloc> ::= {[<Declaraie>;] .. [<Instr>] ..}
<Instr_expresie> ::= [<Expr>];
223
Nume fiier
Seciune
caracter.cpp
conv1.cpp
rezol.cpp
refer1.cpp
refer2.cpp
valoare1.cpp
valoare2.cpp
refer3.cpp
union1.cpp
init1.cpp
init2.cpp
refer4.cpp
sup_fun1.cpp
sup_fun2.cpp
macro1.cpp
inline1.cpp
vector1.cpp
vector2.cpp
vector3.cpp
vector4.cpp
pereche1.cpp
pereche2.cpp
pereche3.cpp
destruct.cpp
fractie1.cpp
vector5.cpp
vector6.cpp
vector7.cpp
vector8.cpp
15.1.
15.1.
15.2.1.
15.2.2.
15.2.2.
15.2.3.
15.2.3.
15.2.3.
15.3.
15.4.1.
15.4.1.
15.4.2.
15.4.3.
15.4.3.
15.4.4.
15.4.4.
16.1.
16.1.
16.4.
16.4.
16.5.1.
16.5.1.
16.5.2
16.6.
16.7.2.
16.7.2.
16.7.2.
16.7.2.
16.7.2.
Nr.
crt.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
224
Nume fiier
Seciune
increm1.cpp
increm2.cpp
conv2.cpp
lung1.cpp
conv3.cpp
lung2.cpp
virtual1.cpp
fractie2.cpp
animal1.cpp
animal2.cpp
virtual2.cpp
abstract.cpp
stream1.cpp
stream2.cpp
stream3.cpp
stream4.cpp
stream5.cpp
stream6.cpp
stream7.cpp
stream8.cpp
fractie2.cpp
fractie3.cpp
stream9.cpp
stream10.cpp
stream11.cpp
stream12.cpp
stare1.cpp
stare2.cpp
16.7.3.
16.7.3.
16.8.2.
16.8.2.
16.8.3.
16.8.3.
17.1.
17.3.
17.4.
17.4.
17.4.
17.5.
18.2.1.
18.2.1.
18.2.1.
18.2.2.
18.2.3.
18.2.3.
18.2.4.
18.2.4.
18.2.5.
18.2.5.
18.3.1.
18.3.1.
18.3.1.
18.3.1.
18.3.2.
18.3.2.
20. Bibliografie:
[1]
[2]
[3]
[4]
[5]
[6]
Benk T., Poppe A., Benk L.: Bevezets a Borland C++ programozsba,
ComputerBooks, Budapest, 1991.
[7]
[8]
Horowitz E., Sahni S., Mehta D.: Fundamentals od Data Structures in C++,
Computer Science Press, New York, 1995.
[9]
[10]
[11]
[12]
[13]
Stroustrup B.: The C++ Programming Language, Second Edition, AT&T Bell
Telephone Laboratories, 1991.
225