Documente Academic
Documente Profesional
Documente Cultură
Programarea Calculatoarelor
Programarea Calculatoarelor
PROGRAMAREA CALCULATOARELOR
IN LIMBAJUL C
2. Date si prelucrri
Variabile si constante . . . . . . . . . . . . . . . . . . . . . . . 15
Tipuri de date n limbajul C . . . . . . . . . . . . . . . . . . 15
Constante n limbajul C . . . . . . . . . . . . . . . . . . . . . 17
Operatori si expresii aritmetice n C . . . . . . . . . . . 18
Erori de reprezentare a numerelor . . . . . . . . . . . . . 20
Prelucrri la nivel de bit . . . . . . . . . . . . . . . . . . . . . 22
Ordinea de evaluare a expresiilor . . . . . . . . . . . . . . 23
Instructiuni expresie n C . . . . . . . . . . . . . . . . . . . . 24
Functii standard de intrare-iesire . . . . . . . . . . . . . . 25
3. Prelucrri conditionate
Structuri de control . . . . . . . . . . . . . . . . . . . . . . . . . 27
Bloc de instructiuni . . . . . . . . . . . . . . . . . . . . . . . . 27
Instructiunea "if" . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Operatori de relatie si logici . . . . . . . . . . . . . . . . . . 30
Expresii conditionale . . . . . . . . . . . . . . . . . . . . . . . 33
Instructiunea "switch" . . . . . . . . . . . . . . . . . . . . . . . 34
Macroinstructiunea assert . . . . . . . . . . . . . . . . . . 36
http://elth.srv.ro/
4. Prelucrri repetitive n C
Instructiunea "while" . . . . . . . . . . . . . . . . . . . . . . . 37
Instructiunea "for" . . . . . . . . . . . . . . . . . . . . . . . . . 38
Instructiunea "do" . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Instructiunile "break" si "continue" . . . . . . . . . . . . 40
Vectori n limbajul C . . . . . . . . . . . . . . . . . . . . . . . 42
Matrice n limbajul C . . . . . . . . . . . . . . . . . . . . . . 43
Programare structurat n C . . . . . . . . . . . . . . . . . . 45
5. Programare modular n C
Importanta functiilor n programare . . . . . . . . . . . . 47
Utilizarea functiilor in C . . . . . . . . . . . . . . . . . . . . . 47
Definirea de functii in C . . . . . . . . . . . . . . . . . . . . . 49
Instructiunea return . . . . . . . . . . . . . . . . . . . . . . . 50
Transmiterea de date intre functii . . . . . . . . . . . . . 52
Functii recursive . . . . . . . . . . . . . . . . . . . . . . . . . . . .54
Biblioteci de functii . . . . . . . . . . . . . . . . . . . . . . . . . 56
6. Tipuri pointer n C
Variabile pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Operatii cu pointeri la date . . . . . . . . . . . . . . . . . . . 58
Vectori si pointeri . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Pointeri n functii . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Pointeri la functii . . . . . . . . . . . . . . . . . . . . . . . . . . 64
http://elth.srv.ro/
9. Tipuri structur n C
Definirea de tipuri si variabile structur . . . . . . . . . 85
Utilizarea tipurilor structur . . . . . . . . . . . . . . . . . . 86
Functii cu argumente si rezultat structur . . . . . . . 88
Definirea unor noi tipuri de date . . . . . . . . . . . . . . 89
Structuri cu continut variabil . . . . . . . . . . . . . . . . . 90
Structuri predefinite . . . . . . . . . . . . . . . . . . . . . . . . 92
Structuri legate prin pointeri . . . . . . . . . . . . . . . . . . 93
http://elth.srv.ro/
http://elth.srv.ro/
5
1. Introducere n programare. Limbajul C.
Algoritmi si programe
Un algoritm este o metod de rezolvare a unei probleme printr-o succesiune
de operatii simple. Numrul de operatii este de obicei foarte mare, dar finit.
Spre deosebire de aplicarea unor formule de calcul, un algoritm contine
operatii executate conditionat, numai pentru anumite date, si operatii repetate
de un numr de ori, n functie de datele problemei. Exemplul clasic este
algoritmul lui Euclid pentru determinarea celui mai mare divizor comun a doi
ntregi, care nu poate fi exprimat sub forma unei expresii (formule).
Tipic pentru un algoritm este faptul c anumite operatii se execut
conditionat (n functie de valorile datelor initiale), iar alte operatii se execut
n mod repetat (iar numrul de repetri poate depinde de datele initiale).
Practic nu exist un program fr decizii si cicluri, deci un program n care s
se execute mereu aceleasi operatii, n aceeasi ordine, indiferent de datele
initiale. Altfel spus, anumite operatii dintr-un program pot s nu fie executate
de loc sau s fie executate de un numr de ori, functie de datele initiale.
Algoritmii mai simpli pot fi exprimati direct ntr-un limbaj de programare,
dar pentru un algoritm mai complex se practic descrierea algoritmului fie sub
form grafic (organigrame sau scheme logice), fie folosind un pseudocod,
ca un text intermediar ntre limbajul natural si un limbaj de programare. Un
pseudocod are reguli mai putine si descrie numai operatiile de prelucrare (nu
si variabilele folosite). Nu exist un pseudocod standardizat sau unanim
acceptat. Descrierea unor prelucrri n pseudocod se poate face la diferite
niveluri de detaliere. Exemplu de algoritm pentru afisarea numerelor perfecte
mai mici ca un numr n dat, descris ntr-un pseudocod:
repet pentru fiecare ntreg m ntre 2 si n
calcul sum s a divizorilor lui m
dac m = s atunci
scrie m
http://elth.srv.ro/
6
Prin alinierea spre dreapta s-a pus n evident structura de blocuri, adic ce
operatii fac obiectul unei comenzi de repetare sau de selectie (dac).
Aceast conventie nu este suficient de precis si poate fi nlocuit cu caractere
delimitator pentru operatiile dintr-un bloc ce face obiectul unei repetri sau
unei conditionri. Exemplu:
repet pentru fiecare ntreg m ntre 2 si n
{ s=0
repeta pentru fiecare ntreg d ntre 1 si m
{daca d este divizor al lui m atunci
aduna d la s
}
dac m = s atunci
scrie m
}
http://elth.srv.ro/
7
Desfsurarea n timp a operatiilor de prelucrare este controlat prin
instructiuni de repetere (de ciclare) si de selectie.
Fiecare limbaj de programare are reguli gramaticale precise, a cror
respectare este verificat de programul compilator (compilator = translator
dintr-un limbaj de programare universal n limbajul calculatorului pe care se
va executa programul).
Dezvoltarea de programe
Scrierea unui program ntr-un limbaj de programare este doar primul pas
dintr-un proces care mai cuprinde si alti pasi. Mai corect ar fi s spunem
scrierea unei versiuni initiale a programului, pentru c ntotdeauna aceast
form initial este corectat, modificat sau extins pentru eliminarea unor
erori, pentru satisfacerea unor noi cerinte sau pentru mbunttirea
performantelor n executie.
Un program scris ntr-un limbaj independent de masin (C, Pascal, s.a.)
trebuie mai nti tradus de ctre un program translator sau compilator.
Compilatorul citeste si analizeaz un text surs (de exemplu n limbajul C) si
produce un modul obiect (scris ntr-un fisier), dac nu s-au gsit erori n textul
surs. Pentru programele mari este uzual ca textul surs s fie format din mai
multe fisiere surs, care s poat fi scrise, compilate, verificate si modificate
separat de celelalte fisiere surs.
Mai multe module obiect, rezultate din compilri separate sunt legate
mpreun si cu alte module extrase din biblioteci de functii standard ntr-un
program executabil de ctre un program numit editor de legturi (Linker sau
Builder). Executia unui program poate pune n evident erori de logic sau
chiar erori de programare care au trecut de compilare (mai ales n limbajul C).
Cauzele erorilor la executie sau unor rezultate gresite nu sunt de obicei
evidente din cauz c ele sunt efectul unui numr mare de operatii efectuate de
calculator. Pentru descoperirea cauzelor erorilor se poate folosi un program
depanator (Debugger) sau se pot insera intructiuni de afisare a unor rezultate
intermediare n programul surs, pentru trasarea evolutiei programului.
Fazele de modificare (editare) a textului surs, de compilare, linkeditare si
executie sunt repetate de cte ori este necesar pentru a obtine un program
corect. De fapt, testarea unui program cu diverse date initiale poate arta
prezenta unor erori si nu absenta erorilor, iar efectuarea tuturor testelor
necesare nu este posibil pentru programe mai complexe (pentru. un
compilator sau un editor de texte, de exemplu).
http://elth.srv.ro/
8
Programele compilator si linkeditor pot fi apelate n mod linie de comand
sau prin selectarea unor optiuni din cadrul unui mediu integrat de dezvoltare a
programelor (IDE = Integrated Development Environment).
Alte programe utilizate n procesul de dezvoltare a unor aplicatii mari sunt:
- program bibliotecar pentru crearea si modificarea unor biblioteci de
subprograme pe baza unor module obiect rezultate din compilare.
- program pentru executia unor fisiere de comenzi necesare pentru compilarea
selectiv si re-crearea programului executabil, dup modificarea unor fisiere
surs sau obiect (make).
- program de control al versiunilor succesive de fisiere surs.
Llimbajul de programare C.
Limbajul C s-a impus n principal datorit existentei unui standard care
contine toate facilittile necesare unui limbaj pentru a putea fi folosit ntr-o
mare diversitate de aplicatii, fr a fi necesare abateri sau extinderi fat de
standard (cazul limbajului Pascal). Un exemplu este recunoasterea posibilittii
ca un program s fie format din mai multe fisiere surs si a compilrii lor
separate, inclusiv referiri dintr-un fisier n altul. In plus, exist un numr
relativ mare de functii uzuale care fac parte din standardul limbajului si care
contribuie la portabilitatea programelor C.
Unii programatori apreciaz faptul c limbajul C permite un control total
asupra operatiilor realizate de procesor si asupra functiilor sistemului de
operare gazd, aproape la fel ca si limbajele de asamblare. Astfel se explic de
ce majoritatea programelor de sistem si utilitare sunt scrise de mai multi ani n
limbajul C, pe lng multe programe de aplicatii.
Limbajul C permite scrierea unor programe foarte compacte, ceea ce poate fi
un avantaj dar si un dezavantaj, atunci cnd programele devin criptice si greu
de nteles. Scurtarea programelor C s-a obtinut prin reducerea numrului de
cuvinte cheie, prin existenta unui numr mare de operatori exprimati prin unul
sau prin dou caractere speciale dar si prin posibilitatea de a combina mai
multi operatori si expresii ntr-o singur instructiune (acolo unde alte limbaje
folosesc mai multe instructiuni pentru a obtine acelasi efect).
Din perspectiva timpului se poate spune c instructiunile C sunt o reusit a
limbajului (si au fost preluate fr modificari de multe alte limbaje : C++, Java
s.a.) dar functiile de intrare-iesire (printf,scanf) nu au fost un succes (si au fost
nlocuite n alte limbaje). Un alt neajuns s-a dovedit a fi necesitatea
argumentelor de tip pointer pentru functiile care trebuie s modifice o parte
din argumentele primite si a fost corectat prin argumente de tip referint.
http://elth.srv.ro/
9
Utilizarea direct de pointeri (adrese de memorie) de ctre programatorii C
corespunde lucrului cu adrese de memorie din limbajele de asamblare si
permite operatii imposibile n alte limbaje, dar n timp s-a dovedit si o surs
important de erori la executie, greu de depistat.
Au mai fost preluate n limbajele post-C si anumite conventii, cum ar fi
diferenta dintre litere mici si litere mari, diferenta dintre caractere individuale
si siruri de caractere (si terminarea sirurilor de caractere cu un octet zero),
operatorii, comentariile s.a.
Programarea n C este mai putin sigur ca n alte limbaje ( Pascal, Java) si
necesit mai mult atentie. Limbajul C permite o mare diversitate de
constructii corecte sintactic (care trec de compilare), dar multe din ele trdeaz
intentiile programatorului si produc erori greu de gsit la executie. Poate cel
mai bun exemplu este utilizarea gresit a operatorului de atribuire = n locul
operatorului de comparare la egalitate ==. Exemplu:
if ( a=b) printf (" a = b" \n");
if ( a==b) printf (" a = b" \n");
corect
// gresit
//
http://elth.srv.ro/
10
printf (" main ");
}
declaratii de variabile */
citire date initiale */
instructiune de calcul */
afisare rezultat */
http://elth.srv.ro/
11
Instructiunile si declaratiile limbajului C sunt formate din cuvinte cheie ale
limbajului, din nume simbolice alese de programator, din constante (numerice
si nenumerice) si din operatori formati n general din unul sau dou caractere
speciale.
Vocabularul limbajului contine litere mari si mici ale alfabetului englez,
cifre zecimale si o serie de caractere speciale, care nu sunt nici litere, nici
cifre. Printre caracterele speciale mult folosite sunt semne de punctuatie (',' ';'),
operatori ('=','+','-','*','/'), paranteze ('(',')',[',']','{'}') s.a.
In C se face diferent ntre litere mici si litere mari, iar cuvintele cheie ale
limbajului trebuie scrise cu litere mici. Cuvintele cheie se folosesc n declaratii
si instructiuni si nu pot fi folosite ca nume de variabile sau de functii (sunt
cuvinte rezervate ale limbajului). Exemple de cuvinte cheie:
int, float, char, void, unsigned,
do, while, for, if, switch
struct, typedef, const, sizeof
Numele de functii standard (scanf, printf, sqrt, etc.) nu sunt cuvinte cheie,
dar nu se recomand utilizarea lor n alte scopuri (schimbarea sensului initial,
atribuit n toate versiunile limbajului).
Literele mari se folosesc n numele unor constante simbolice predefinite :
EOF, M_PI, INT_MAX, INT_MIN
http://elth.srv.ro/
12
Respectarea acestei reguli poate fi mai dificil n cazul unor siruri constante
lungi, dar exist posibilitatea prelungirii unui sir constant de pe o linie pe alta
folosind caracterul '\'. Exemple:
puts (" Inceput sir foarte foarte lung\
sfrsit sir");
// spatiile albe se vor afisa
// solutie alternativa
puts ("Inceput sir foarte foarte lung",
"sfrsit sir");
// spatiile albe nu conteaz
Structura programelor C.
Un program C este compus n general din mai multe functii, dintre care
functia "main" nu poate lipsi, deoarece cu ea ncepe executia programului.
Functiile pot face parte dintr-un singur fisier surs sau din mai multe fisiere
surs. Un fisier surs C este un fisier text care contine o succesiune de
declaratii: definitii de functii si, eventual, declaratii de variabile.
Functia main poate fi declarat fr argumente sau cu argumente, prin care
ea primeste date transmise de operator prin linia de comand care lanseaz
programul n executie. Functia main poate fi declarat si de tip int sau fr
tip explicit, dar atunci trebuie folosit instructiunea return pentru a preciza un
cod de terminare (zero pentru terminare normal, negativ pentru terminare cu
eroare). Exemplu:
#include
int main
printf
return
}
<stdio.h>
( ) {
(" main ");
0;
http://elth.srv.ro/
13
for (i=0;i<24;i++)
putchar('\n');
}
void main ( ) {
clear( );
}
// apel functie
Functia clear putea fi scris (definit) si dup functia main, dar atunci
era necesar declararea acestei functii nainte de main. Exemplu:
#include <stdio.h>
void clear();
// declaratie functie
void main ( ) {
clear( );
// apel functie
}
void clear () {
// definitie functie
int i;
for (i=0;i<24;i++)
putchar('\n');
}
Intr-un program cu mai multe functii putem avea dou categorii de variabile:
- variabile definite n interiorul functiilor, numite si "locale".
- variabile definite n afara functiilor, numite si "externe" (globale).
Locul unde este definit o variabil determin domeniul de valabilitate al
variabilei respective: o variabil definit ntr-un bloc poate fi folosit numai n
blocul respectiv. Pot exista variabile cu acelasi nume n blocuri diferite; ele se
memoreaz la adrese diferite si se refer la valori diferite.
In primele versiuni ale limbajului C era obligatoriu ca toate declaratiile de
variabile dintr-o functie (sau dintr-un dintr-un bloc) s fie grupate la nceputul
functiei, nainte de prima instructiune executabil. In C++ si n ultimele
versiuni de C declaratiile pot apare oriunde, intercalate cu instructiuni.
Exemplu (incorect sintactic n C, corect sintactic n C++):
#include <stdio.h>
void main () {
int n;
scanf ("%d", &n);
long nf=1;
for (int k=1;k<=n;k++)
nf=nf*k;
printf ("%ld\n", nf);
}
//
//
//
//
//
//
//
calcul factorial
un ntreg dat
citeste valoare n
variabila rezultat
repeta de n ori
o nmultire
afisare rezultat
Directive preprocesor
http://elth.srv.ro/
14
Un program C contine una sau mai multe linii initiale, care ncep toate cu
caracterul #. Acestea sunt directive pentru preprocesorul C si sunt
interpretate nainte de a se analiza programul propriu-zis (instructiuni si
declaratii). Directivele fac parte din standardul limbajului C.
Cele mai folosite directive sunt #include si #define.
Directiva #include cere includerea n compilare a unor fisiere surs C, care
sunt de obicei fisiere antet (header), ce reunesc declaratii de functii
standard. Fisierele de tip .h nu sunt biblioteci de functii si nu contin definitii
de functii, asa cum se afirm uneori.
Pentru a permite compilatorului s verifice utilizarea corect a unei functii
este necesar ca el s afle declaratia functiei (sau definitia ei) nainte de prima
utilizare. Pentru o functie de bibiotec definitia functiei este deja compilat si
nu se mai transmite programului compilator, deci trebuie comunicate doar
informatiile despre tipul functiei, numrul si tipul argumentelor printr-o
declaratie (prototip al functiei). Fisierele antet contin declaratii de functii.
Absenta declaratiei unei functii utilizate (si datorit absentei unei directive
include) este semnalat ca avertisment n programele C si ca eroare ce nu
permite executia n C++. Pentru anumite functii absenta declaratiei afecteaz
rezultatul functiei (considerat implicit de tip int), dar pentru alte functii (de tip
void sau int) rezultatul nu este afectat de absenta declaratiei.
Orice program trebuie s citeasc anumite date initiale variabile si s scrie
(pe ecran sau la imprimant) rezultatele obtinute. In C nu exist instructiuni de
citire si de scriere, dar exist mai multe functii standard destinate acestor
operatii. Declaratiile functiilor standard de I/E sunt reunite n fisierul antet
stdio.h (Standard Input-Output ), care trebuie inclus n compilare:
#include <stdio.h>
Numele fisierelor antet pot fi scrise cu litere mici sau cu litere mari deoarece
nu sunt nume proprii limbajului C ci sunt nume specifice sistemului de
operare gazd (Windows, Linux etc.). Exemplu:
#include <STDIO.H>
http://elth.srv.ro/
15
In multe din exemplele care urmeaz vom considera implicite directivele de
includere necesare pentru functiile folosite, fr a le mai scrie (dar ele sunt
necesare pentru o compilare fr erori).
2. Date si prelucrri
Variabile si constante
Orice program prelucreaz un numr de date initiale si produce o serie de
rezultate. In plus, pot fi necesare date de lucru, pentru pstrarea unor valori
folosite n prelucrare, care nu sunt nici date initiale nici rezultate finale.
Toate aceste date sunt memorate la anumite adrese, dar programatorul se
refer la ele prin nume simbolice. Cu exceptia unor date constante, valorile
asociate unor nume se modific pe parcursul executiei programului. De aici
denumirea de variabile pentru numele atribuite datelor memorate.
Numele unei variabile ncepe obligatoriu cu o liter si poate fi urmat de
litere si cifre. Caracterul special _ (subliniere) este considerat liter, fiind
folosit n numele unor variabile sau constante predefinite (n fisiere de tip H).
Aplicatiile calculatoarelor sunt diverse, iar limbajele de programare reflect
aceast diversitate, prin existenta mai multor tipuri de date: tipuri numerice
ntregi si nentregi, siruri de caractere de lungime variabil s.a.
Pentru a preciza tipul unei variabile este necesar o definitie ( o declaratie).
Cuvintele definitie si declaratie se folosesc uneori cu acelasi sens,
pentru variabile declarate n main sau n alte functii.
In limbajul C se face diferent ntre notiunile de definitie si declaratie,
iar diferenta apare la variabile definite ntr-un fisier surs si declarate (si
folosite) ntr-un alt fisier surs. O definitie de variabil aloc memorie pentru
acea variabil (n functie de tipul ei) iar o declaratie anunt doar tipul unei
variabile definite n alt parte, pentru a permite compilatorului s verifice
utilizarea corect a variabilelor.
O declaratie trebuie s specifice numele variabilei (ales de programator),
tipul variabilei si, eventual, alte atribute. In C o variabil poate avea mai multe
atribute, care au valori implicite atunci cnd nu sunt specificate explicit (cu
exceptia tipului care trebuie declarat explicit).
O definitie de variabil poate fi nsotit de initializarea ei. Exemplu:
int suma=0;
// declaratie cu initializare
http://elth.srv.ro/
16
// declaratie (prototip)
// square
http://elth.srv.ro/
17
Toate variabilele numerice de un anumit tip se reprezint pe acelasi numr
de octeti, iar acest numr limiteaz domeniul de valori (pentru ntregi si
nentregi) si precizia numerelor nentregi.
De exemplu, n Borland C , domeniul de valori pentru tipul int este cuprins
ntre -32767 si 32767 (ntregi cu semn pe 2 octeti) si de cca. 10 cifre
zecimale pentru tipul long). Depsirile la operatii cu ntregi de orice
lungime nu sunt semnalate desi rezultatele sunt incorecte n caz de depsire.
Exemplu:
short int a=15000, b=20000, c;
c=a+b;
// depasire ! c > 32767
Constante n limbajul C
Tipul constantelor C rezult din forma lor de scriere, dup cum urmeaz:
- Constantele ntregi sunt siruri de cifre zecimale, eventual precedate de un
semn (-, +). Exemple :
0 , 11 , -205 , 12345
http://elth.srv.ro/
18
Constantele caracter se pot scrie si sub forma unei secvente ce ncepe cu \,
urmat de o liter (\n = new line , \t =tab , \b = backspace etc), sau de codul
numeric al caracterului n octal sau n hexazecimal (\012 = \0x0a = 10 este
codul pentru caracterul de trecere la linie nou \n).
- Constantele ntregi n baza 16 trebuie precedate de precedate de sufixul
"0x". Cifrele hexazecimale sunt 0..9,A,B,C,D,E,F sau 0..9,a,b,c,d,e,f. Exemple
0x0A, 0x7FFFF, 0x2c, 0xef
- Constantele formate din unul sau mai multe caractere ntre ghilimele sunt
constante sir de caractere . Exemple:
a , alfa , -1234 ,
####
http://elth.srv.ro/
19
Operatorii aritmetici +,-,*, / se pot folosi cu operanzi numerici ntregi
sau reali.
Operatorul / cu operanzi ntregi are rezultat ntreg (partea ntreag a
ctului) si operatorul % are ca rezultat restul mprtirii ntregi a doi ntregi.
Semnul restului este acelasi cu semnul demprtitului; restul poate fi negativ.
In general, rezultatul unei (sub)expresii cu operanzi ntregi este ntreg.
Dac cei doi operanzi difer ca tip atunci tipul inferior este automat
promovat la tipul superior nainte de efectuarea operatiei. Un tip T1 este
superior unui tip T2 dac toate valorile de tipul T2 pot fi reprezentate n tipul
T1 fr trunchiere sau pierdere de precizie. Ierarhia tipurilor aritmetice din C
este urmtoarea:
char < short < int < long < float < double < long double
Subexpresiile cu operanzi ntregi dintr-o expresie care contine si reali au
rezultat ntreg, deoarece evaluarea subexpresiilor se face n etape. Exemple:
float x = 9.8, y = 1/2*x;
y= x/2; // y=4.9;
// y=0.
i=j=k=0;
d = b*b-4*a*c;
x1=(-b +sqrt(d))/(2*a);
La atribuire, dac tipul prtii stnga difer de tipul prtii dreapta atunci se
face automat conversia de tip (la tipul din stnga), chiar dac ea necesit
trunchiere sau pierdere de precizie. Exemplu:
int a;
a= sqrt(3.);
// a=1
http://elth.srv.ro/
20
// ctul exact
// rotunjire x la intregul apropiat
Conversia prin operatorul (tip) se poate face ntre orice tipuri aritmetice sau
ntre tipuri pointer. Pentru tipurile aritmetice se poate folosi si atribuirea
pentru modificarea tipului (si valorii) unor variabile sau functii. Exemplu:
float x; int k;
x= x+0.5; k=x;
// rotunjire x
In limbajul C exist mai multi operatori care reunesc un calcul sau alt
prelucrare cu o atribuire. Exemple:
+=
-=
*=
/= %=
Efectul unei expresii de forma
v += e
x += 1
++x
x++
// x=0,
corect:
x=(a+b)/2 ;
http://elth.srv.ro/
21
int x = sqrt(2);
// x=1
http://elth.srv.ro/
22
- Erori la adunarea sau scderea a dou numere reale cu valori foarte diferite
prin aducerea lor la acelasi exponent nainte de operatie. Se poate pierde din
precizia numrului mai mic sau chiar ca acesta s fie asimilat cu zero.
- Erori de rotunjire a numerelor reale datorit numrului limitat de cifre
pentru mantis. Mrimea acestor erori depinde de tipul numerelor (float sau
double sau long double), de tipul, numrul si ordinea operatiilor aritmetice.
Pierderea de precizie este mai mare la mprtire si de aceea se recomand ca
aceste operatii s se efectueze ct mai trziu ntr-o secvent de operatii. Deci:
expresia (a*b)/c este preferabil expresiei (a/c)*b.
Erorile de rotunjire se pot cumula pe un numr mare de operatii, astfel c n
anumite metode iterative cresterea numrului de pasi (de iteratii) peste un
anumit prag nu mai reduce erorile de calcul intrinseci metodei, deoarece
erorile de reprezentare nsumate au o influent prea mare asupra rezultatelor.
Un exemplu este calculul valorii unor functii ca sum a unei serii de puteri cu
multi termeni; ridicarea la putere si factorialul au o crestere rapid pentru
numere supraunitare iar numerele subunitare ridicate la putere pot produce
valori nesemnificative.
In general, precizia rezultatelor numerice este determinat de mai multi
factori: precizia datelor initiale (numr de zecimale), numrul si felul
operatiilor, erori intrinseci metodei de calcul (pentru metode de aproximatii
succesive), tipul variabilelor folosite.
// este 0x7FFF
Operatorul pentru produs logic bit cu bit '&' se foloseste pentru fortarea pe
zero a unor biti selectati printr-o masc si pentru extragerea unor grupuri de
http://elth.srv.ro/
23
biti dintr-un sir de biti. Pentru a extrage cei 4 biti din dreapta (mai putini
semnificativi) dintr-un octet memorat n variabila 'c' vom scrie:
c & 0x0F
unde constanta hexa 0x0F reprezint un octet cu primii 4 biti zero si ultimii 4
biti egali cu 1.
Operatorul pentru sum logic bit cu bit '|' se foloseste pentru a forta selectiv
pe 1 anumiti biti si pentru a reuni dou configuratii binare ntr-un singur sir de
biti. Exemplu:
a|0x8000
Operatorul pentru sum modulo 2 ("sau exclusiv") '^' poate fi folosit pentru
inversarea logic sau pentru anularea unei configuratii binare.
Operatorii pentru deplasare stnga '<<' sau dreapta '>>' se folosesc pentru
modificarea unor configuratii binare. Pentru numere fr semn au acelasi efect
cu nmultirea si respectiv mprtirea cu puteri ale lui 2. Exemplu:
a >>10
// echivalent cu a / 1024
Operator
Paranteze si acces la structuri: ( ) [ ] -> .
Operatori unari: ! ~ + - ++ -- & * sizeof (tip)
Inmultire, mprtire, rest : * / %
Adunare si scdere: + Deplasri: << >>
http://elth.srv.ro/
24
6
7
8
9
10
11
12
13
14
15
Instructiuni expresie n C
O expresie urmat de caracterul ; devine o instructiune expresie. Cazurile
uzuale de instructiuni expresie sunt :
- Apelul unei functii (de tip void sau de alt tip) printr-o instructiune :
printf("n="); scanf("%d",&n);
http://elth.srv.ro/
25
- Instructiune de atribuire:
a=1;
b=a;
r=sqrt(a);
c=r/(a+b);
i=j=k=1;
a++;
a<<2;
Prin instructiuni expresie se exprim operatiile de prelucrare si de intrareiesire necesare oricrui program.
Exemplu de program compus numai din instructiuni expresie:
#include <stdio.h>
#include <math.h>
void main () {
float a,b,c,ua,ub,uc;
printf("Lungimi laturi:");
scanf ("%f%f%f",&a,&b,&c);
ua = acos ( (b*b+c*c-a*a)/(2*b*c)
ub = acos ( (a*a+c*c-b*b)/(2*a*c)
uc = acos ( (b*b+a*a-c*c)/(2*a*b)
printf ("%8.6f%8.6f \n",ua+ub+uc,
}
); //
); //
); //
M_PI);
unghi A
unghi B
unghi C
// verificare
http://elth.srv.ro/
26
Celelate argumente sunt variabile (la scanf) sau expresii (la printf) n
care se citesc valori (scanf) sau ale cror valori se scriu (printf). Exemple
de utilizare printf:
printf
printf
printf
linia
printf
printf
("\n");
// trecere la o noua linie
("\n Eroare \n"); // scrie un sir constant
("%d \n",a);
// scrie un intreg si schimba
("a=%d b=%d \n", a, b); // scrie doi intregi
( %2d grade %2d min %2d sec \n, g,m,s);
Desi sunt permise si alte caractere n sirul cu rol de format din scanf se
recomand pentru nceput s nu se foloseasc ntre specificatorii de format
dect blancuri (pentru a usura ntelegerea formatului de citire).
Functia scanf nu poate afisa nimic, iar pentru a precede introducerea de
date de un mesaj trebuie folosit secventa printf, scanf. Exemplu:
http://elth.srv.ro/
27
printf (n= ); scanf (%d, &n);
numere
numere
numere
numere
numere
numere
numere
3. Prelucrri conditionate
Structuri de control
Instructiunile de control dintr-un limbaj permit selectarea si controlul
succesiunii n timp a operatiilor de prelucrare. In limbajele masin si n
primele limbaje de programare controlul succesiunii se realiza prin
instructiuni de salt n program (instructiunea go to mai exist si n prezent n C
si n alte limbaje, desi nu se recomand utilizarea ei).
S-a demonstrat teoretic si practic c orice algoritm (program) poate fi
exprimat prin combinarea a trei structuri de control:
- succesiune fix de operatii (secventa liniar)
- decizie binar (alegere dintre dou alternative posibile)
- ciclul cu conditie initial (repetarea unor operatii n functie de o conditie)
Limbajul C este un limbaj de programare structurat deoarece posed
instructiuni pentru exprimarea direct a acestor trei structuri de control, fr a
se mai folosi instructiuni de salt.
Combinarea celor trei structuri se face prin includere; orice combinatie este
posibil si pe oricte niveluri de adncime (de includere). Deci un ciclu poate
contine o secvent sau o decizie sau un alt ciclu, s.a.m.d.
Limbajul C contine si alte structuri de control, pe lng cele strict necesare:
- selectie multipl (dintre mai multe alternative)
- ciclul cu conditie final (verificat dup executarea operatiilor din ciclu)
- ciclul for (cu conditie initial sau cu numr cunoscut de pasi)
Blocul de instructiuni
http://elth.srv.ro/
28
Instructiunile expresie dintr-un program sunt executate n ordinea aparitiei
lor n program, deci secventa liniar de operatii este realizat natural, prin
ordinea n care sunt scrise instructiunile ntr-un program.
In limbajul C un bloc grupeaz mai multe instructiuni (si declaratii) ntre
acolade. Exemple:
{ t=a; a=b; b=t;}
{ int t; t=a; a=b; b=t;}
Instructiunea "if"
Instructiunea introdus prin cuvntul cheie "if" exprim o decizie binar si
poate avea dou forme: o form fr cuvntul else si o form cu else :
if (e) i
if (e) i1 else i2
http://elth.srv.ro/
29
Expresia din if este de obicei o expresie de relatie sau o expresie logic, dar
poate fi orice expresie cu rezultat numeric.
Exemplu de instructiune if fr alternativ else:
// maxim dintre a si b
max=a;
if ( max < b)
max=b;
printf ("%d \n", max);
//
if (d != 0) return;
http://elth.srv.ro/
30
if ( a==b && b==c)
printf ("echilateral \n");
else
if ( a==b || b==c || a==c)
printf ("isoscel \n");
else
printf ("oarecare \n");
if ( ! e1)
i2
else if (e2)
i1
http://elth.srv.ro/
31
if ( a<b ) {
c=a; a=b; b=c;
}
si-logic
sau-logic
http://elth.srv.ro/
32
if ( x >= a && x <= b)
printf(" x in [a,b] \n");
if ( x < a || x > b)
printf ("x in afara interv. [a,b] \n");
// if ( d==0) return;
// echiv. cu
// echiv. cu
!(a<0 || b<0)
!(x>=a && x<=b)
Intotdeauna putem alege ntre testarea unei conditii sau a negatiei sale, dar
consecintele acestei alegeri pot fi diferite, ca numr de instructiuni, mai ales
atunci cnd instructiunea if se afl ntr-un ciclu. Exemplu:
// determina minim dintr-un vector x
xmin=x[0];
// minim partial
for (i=1;i<n;i++)
if (xmin <= x[i])
;
// nimic daca xmin este minim
else
xmin=x[i];
// modifica minim partial
http://elth.srv.ro/
33
xmin=x[0];
for (i=1;i<n;i++)
if (xmin > x[i])
xmin=x[i];
http://elth.srv.ro/
34
(fals), indiferent de valorile celorlalti operanzi. La fel, evaluarea unei sume
logice se opreste la primul operand nenul, cu rezultat 1 (adevrat).
Pentru a preveni erori cauzate de acest mod de evaluare se vor evita expresii
complicate care includ calcule, atribuiri si verificri de conditii.
Expresii conditionale
Limbajul C contine o expresie ternar (cu trei operanzi), care poate fi privit
ca o expresie concentrat a unei instructiuni if:
exp1 ? exp2 : exp3
Instructiunea
urmtoare:
if (e1) x=e2;
else x=e3;
// secunde
http://elth.srv.ro/
35
x=m1+m2 + x/60; m=x%/60;
// minute
h=h1+h2+x/60;
// ore
Instructiunea "switch"
Selectia multipl, dintre mai multe cazuri posibile, se poate face cu mai
multe instructiuni if incluse unele n altele sau cu instructiunea switch.
Instructiunea switch face o enumerare a cazurilor posibile (fiecare precedat de
cuvntul cheie "case") ntre acolade si foloseste o expresie de selectie, cu
rezultat ntreg. Forma general este:
switch(e) {
case c1: s1;
case c2: s2;
. . .
default: s;
}
//
//
//
//
// e= expresie de selectie
cazul c1
cazul c2
alte cazuri
cazul implicit ( poate lipsi)
unde: c1,c2,.. sunt constante sau expresii constante ntregi (inclusiv char)
s, s1, s2 ... sunt secvente de instructiuni (cu sau fr acolade)
Dac secventele de instructiuni nu se termin cu break, atunci secventa
echivalent cu instructiuni if este urmtoarea:
if (e==c1) { s1}
if (e==c2) {s2}
. . .
else {s}
// daca e difera de c1,c2,...
http://elth.srv.ro/
36
an
Cazul default poate lipsi, dar cnd este prezent atunci este selectat cnd
valoarea expresiei de selectie difer de toate cazurile enumerate explicit.
Macroinstructiunea assert
Macroinstructiunea assert, definit n fisierul <assert.h>, poate nlocui o
instructiune if si este folosit pentru verificarea unor conditii , fr a ncrca
programele cu instructiuni de verificare, care le-ar face mai greu de citit.
O asertiune este o afirmatie presupus a fi adevrat, dar care se poate
dovedi fals.
Utilizarea este similar cu apelul unei functii de tip void, cu un argument al
crei rezultat poate fi adevrat sau fals (nenul sau nul). Parametrul efectiv
este o expresie de relatie sau logic care exprim conditia verificat. Dac
rezultatul expresiei din assert este nenul (adevrat) atunci programul continu
normal, dar dac expresia este nul (fals) atunci se afiseaz un mesaj care
include expresia testat, numele fisierului surs si numrul liniei din fisier,
dup care programul se opreste. Exemple:
assert ( n <= MAX);
assert ( a > 0 && b > 0);
http://elth.srv.ro/
37
mai explicite pentru utilizatorii programului, eventual n alt limb dect
engleza, nsotite de semnale sonore sau de imagini (pentru programe cu
interfat grafic). De asemenea, assert se poate folosi pentru erori foarte
putin probabile dar posibile.
Erorile la operatii de citire de la consol sunt recuperabile, n sensul c se
poate cere operatorului repetarea introducerii, si nu se va folosi assert.
Eliminarea tuturor apelurilor assert dintr-un program se poate face printr-o
directiv de compilare plasat la nceputul programului.
4. Prelucrri repetitive n C
Instructiunea "while"
Instructiunea "while" exprim structura de ciclu cu conditie initial (si cu
numr necunoscut de pasi) si are forma urmtoare:
while (e) i
unde e este o expresie, iar i este o instructiune (instr. expresie, bloc, instr.
de control)
Efectul este acela de executare repetat a instructiunii continute n
instructiunea "while" ct timp expresia din paranteze are o valoare nenul
(este adevarat). Este posibil ca numrul de repetri s fie zero dac expresia
are valoarea zero de la nceput. Exemplu:
// cmmdc prin incercari succesive de posibili divizori
d= min(a,b);
// divizor maxim posibil
while (a%d || b%d)
d=d-1;
// incearca alt numar mai mic
http://elth.srv.ro/
38
Ca si n cazul altor instructiuni de control, este posibil s se repete un bloc
de instructiuni sau o alt instructiune de control. Exemplu:
// determinare cmmdc prin algoritmul lui Euclid
while (a%b > 0) {
r=a%b;
// restul impartirii a prin b
a=b; b=r;
}
// la iesirea din ciclu b este
cmmdc
Instructiunea "for"
Instructiunea "for" din C permite exprimarea compact a ciclurilor cu
conditie initial sau a ciclurilor cu numr cunoscut de pasi si are forma:
for (exp1; exp2; exp3)
instructiune
// operatii de initializare
// cat timp exp2 !=0 repeta
// o instructiune expresie
Oricare din cele 3 expresii pot fi expresii vide, dar nu pot lipsi separatorii de
expresii (caracterul ';'). Dac lipseste "exp2" atunci se consider ca exp2 are
valoarea 1, deci ciclul se va repeta neconditionat. Exemplu de ciclu infinit (sau
din care se va iesi cu break sau return):
// repetare fara sfarsit
for (;;) instructiune
// sau while(1) instructiune
http://elth.srv.ro/
39
Cel mai frecvent instructiunea for se foloseste pentru programarea ciclurilor
cu numr cunoscut de pasi (cu contor). Exemple:
// stergere ecran prin defilare repetata de 24 ori
for (k=1;k<=24;k++)
putchar('\n');
// avans la linie noua
// alta secventa de stergere ecran de 25 de linii
for (k=24;k>0;k--)
putchar('\n');
Cele trei expresii din instructiunea for sunt separate prin ';' deoarece o
expresie poate contine operatorul virgul (','). Este posibil ca prima sau ultima
expresie s reuneasc mai multe expresii separate prin virgule. Exemplu:
// calcul factorial de n
for (nf=1,k=1 ; k<=n ; nf=nf*k,k++)
;
// repeta instr. vida
In general vom prefera programele mai usor de nteles (si de modificat) fat
de programele mai scurte dar mai criptice.
Instructiunea "do"
http://elth.srv.ro/
40
Instructiunea do-while se foloseste pentru exprimarea ciclurilor cu conditie
final, cicluri care se repet cel putin o dat. Forma uzual a instructiunii do
este urmtoarea:
do i while (e);
do { i } while (e);
Acoladele pot lipsi dac se repet o singur instructiune, dar chiar si atunci
se recomand folosirea lor. Exemplu de utilizare a instructiunii "do":
// calcul radical
succesive
r2=x;
//
do {
r1=r2;
//
r2=(r1+x/r1)/2;
//
} while ( abs(r2-r1));
din
prin
aproximatii
aproximatia initiala
r1 este aprox. veche
r2 este aprox. mai noua
// pana cand r2==r1
// citeste un
x
printf ("sqrt(%f)= %lf \n", x,sqrt(x));
} while (x>0);
do i while(e);
http://elth.srv.ro/
41
Un ciclu din care se poate iesi dup un numr cunoscut de pasi sau la
ndeplinirea unei conditii (iesire fortat) este de obicei urmat de o instructiune
if care stabileste cum s-a iesit din ciclu: fie dup numrul maxim de pasi, fie
mai nainte datorit satisfacerii conditiei.
Utilizarea instructiunii break poate simplifica expresiile din while sau for si
poate contribui la urmrirea mai usoar a programelor, desi putem evita
instructiunea break prin complicarea expresiei testate n for sau while.
Secventele urmtoare sunt echivalente:
for (k=0 ; k<n; k++)
if (e) break;
for (k=0 ; k<n && !e ; k++)
;
http://elth.srv.ro/
42
printf ( k==n? prim: neprim);
// verifica daca n este prim
for (prim=1,k=2; k<n && prim;k++)
if (n%k==0)
prim=0;
printf (prim? prim:neprim);
Instructiunea continue este si mai rar folosit fat de break si are ca efect un
salt la prima instructiune din ciclu, pentru reluarea sa. Exemplu :
// numararea comentariilor dintr-un text C
nc=0;
// nc= nr de comentarii
while ((c=getchar() != -1) {
// -1 daca s-a tastat ^Z
if (c !=/)
continue;
// salt peste instruct. urmatoare
c=getchar();
// caracterul imediat urmator
if (c==/ ' || c==*)
++nc;
// este inceput de comentariu
}
Vectori n limbajul C
Prin "vector" se ntelege n programare o colectie liniar de date omogene
(toate de acelasi tip). In limba englez se foloseste si cuvntul "array" pentru
vectori si matrice. Fiecare element din vector este identificat printr-un indice
ntreg, pozitiv care arat pozitia sa n vector. La o prim vedere vectorii sunt
declarati si folositi n limbajul C n mod asemntor cu alte limbaje. Ulterior
vom arta c un nume de vector este similar cu un pointer si c este posibil o
tratare diferit a componentelor unui vector (fat de alte limbaje).
O alt particularitate a vectorilor n C este numerotarea elementelor de la
zero, deci primul element din orice vector are indicele zero, iar ultimul
element dintr-un vector are un indice mai mic cu 1 dect numrul elementelor
din vector. Exemplu:
// suma elementelor 0..n-1 dintr-un vector
for (i=0; i<n; i++)
s = s + a[i];
http://elth.srv.ro/
43
Anumite aplicatii (cu grafuri sau cu matrice de exemplu) folosesc n mod
traditional o numerotare de la 1 ( nu exist un nod zero ntr-un graf). O solutie
simpl este nefolosirea primei pozitii din vector (pozitia zero) si o alocare
suplimentar de memorie, pentru a folosi pozitiile 1..n dintr-un vector cu n+1
elemente. Exemplu:
// suma elementelor 1..n dintr-un vector
for (i=1; i<=n; i++)
s = s + a[i];
azi[3]={01,04,2001}; // zi,luna,an
xmas[]={25,12,2000}; // dimensiune=3
prime[1000]={1,2,3}; // restul elememtelor zero
a[1000]={0};
// toate elementele initial zero
http://elth.srv.ro/
44
De observat c notatiile cu indici din matematic nu se traduc automat n C
pentru c uneori elementele unui sir de numere nu sunt necesare simultan n
memorie si se folosesc succesiv, putnd fi memorate pe rnd ntr-o singur
variabil. Exemplu:
// calcul exp(x) prin dezvoltare in serie de puteri
s=t=1;
// s=t[0]=1;
for (k=1;k<=n;k++){
t=t*x/k; s=s+t;
// t[k] *= x/k; s += t[k];
}
Matrice n limbajul C
O matrice bidimensional este privit n C ca un vector cu componente
vectori, deci un vector de linii. Exemplu de matrice cu dimensiuni constante:
int a[20][10];
http://elth.srv.ro/
45
}
http://elth.srv.ro/
46
Existenta instructiunilor break, continue si return este considerat mpotriva
normelor programrii structurate, pentru c sunt salturi mascate (cu adres
implicit). Totusi instructiunile break si return sunt mult folosite pentru
iesirea fortat din cicluri deoarece reduc lungimea codului surs.
Un ciclu for care contine o instructiune if este n general diferit de un ciclu
while, desi uneori se pot folosi ca solutii alternative. Exemple de secvente
echivalente ca efect:
// verifica daca n este prim cu n-2 impartiri (orice
n)
prim=1;
for (k=2;k<n;k++)
if (n%k==0)
prim=0;
// varianta cu mai putine
divizor)
k=2; prim=1;
while ( n%k && k<n)
k++;
if (k==n) prim=0;
incercari
(pn
la
un
7
10
2
10
3
10
7
10
2
10
3
10
7
10
2
10
7
30
3
20
Dac vectorul de coduri este ordonat se pot utiliza dou cicluri while : un
ciclu (interior) repetat pentru produsele cu acelasi cod, inclus ntr-un ciclu
(exterior) repetat ct timp mai exist elemente n vectori. In locul celor dou
http://elth.srv.ro/
47
cicluri se poate folosi un singur ciclu for, repetat pentru toate elementele
vectorului, care contine un if pentru a verifica trecerea de la un produs la altul
(schimbarea codului la naintarea n vectorul de coduri).
// totalizare cu doua cicluri while
i=0;
while (i < n) {
c=cod[i]; sum=0;
while ( c == cod[i] )
sum=sum+val[i++];
printf ("%6d %6d \n", c,sum);
}
// totalizare cu un singur ciclu
c=cod[0]; sum=val[0];
for (i=1;i<n;i++) {
if ( c == cod[i])
sum=sum+val[i];
else {
printf ("%6d %6d \n",c,sum);
c=cod[i]; sum=val[i];
}
}
printf ("%6d %6d \n",c,sum);
// ultima grupa
Dac vectorii sunt neordonati atunci fiecare cod de produs este cutat n
elementele care i urmeaz n vectorul de coduri si se nsumeaz cantitatile
respective. In acest caz problema este de a marca produsele deja luate n
considerare, pentru a nu aduna de mai multe ori aceeasi cantitate. Se poate
folosi un vector auxiliar, cu cantittile totale pe coduri si o cutare repetat n
acest vector, pentru a afla produsele deja prelucrate.
5. Programare modular n C
Importanta functiilor n programare
Practic nu exist program care s nu apeleze functii din bibliotecile existente
si care s nu contin definitii de functii specifice aplicatiei respective.
Motivele utilizrii de subprograme sunt multiple:
- Un program mare poate fi mai usor de scris, de nteles si de modificat dac
este modular, deci format din module functionale relativ mici.
- Un subprogram poate fi reutilizat n mai multe aplicatii, ceea ce reduce
efortul de programare al unei noi aplicatii.
http://elth.srv.ro/
48
- Un subprogram poate fi scris si verificat separat de restul aplicatiei, ceea ce
reduce timpul de punere la punct a unei aplicatii mari (deoarece erorile pot
apare numai la comunicarea ntre subprograme corecte).
- Intretinerea unei aplicatii este simplificat, deoarece modificrile se fac
numai n anumite subprograme si nu afecteaz alte subprograme (care nici nu
mai trebuie recompilate).
Utilizarea de functii permite dezvoltarea progresiv a unui program mare, fie
de jos n sus (bottom up), fie de sus n jos (top down), fie combinat.
In limbajele anterioare limbajului C subprogramele erau de dou feluri:
- Functii, care au un singur rezultat, asociat cu numele functiei.
- Proceduri (subrutine), care pot avea mai multe rezultate sau nici unul, iar
numele nu are asociat nici o valoare.
In limbajul C exist numai functii, dar pentru functiile fr rezultat direct
(asociat numelui functiei) s-a introdus tipul void. Pentru o functie cu rezultat
direct tipul functiei este tipul rezultatului.
Utilizarea functiilor n C
O functie de tip void se va apela printr-o instructiune expresie. Exemple:
printf (\n n=);
clearerr (stdin);
EOF
http://elth.srv.ro/
49
//
arg.
formal
"double",
arg.efectiv
//
Deci o functie cu argument formal de un tip numeric (de ex. int) poate fi
apelat cu argumente efective de orice tip numeric (inclusiv long, float,
double, long double).
De retinut c nu toate erorile de utilizare a functiilor pot fi semnalate de
compilator si se pot manifesta la executie prin rezultate gresite. Exemplu:
printf(%d,pow(10,3));
// (int) pow(10,3)
Definirea de functii n C
Sintaxa definirii functiilor n C s-a modificat de la prima versiune a
limbajului la versiunea actual (standardizat), pentru a permite verificarea
utilizrii corecte a oricrei functii la compilare. Forma general a unei definitii
de functie, conform standardului, este:
tipf numef (tip1 arg1, tip2 arg2, ...) {
declaratii
instructiuni (blocuri)
}
http://elth.srv.ro/
50
unde:
tipf este tipul functiei (tipul rezultatului sau void)
tip1, tip2,... sunt tipurile argumentelor (parametrilor) functiei
Tipul unei functii C poate fi orice tip numeric, orice tip pointer, orice tip
structur (struct) sau void. Exemplu de functie de tip void:
// sterge ecran prin defilare cu 24 de linii
void erase () {
int i;
for (i=0;i<24;i++)
printf("\n");
}
// declaratie functie
// utilizare functie
// definitie functie
http://elth.srv.ro/
51
In limbajul C se pot defini si functii cu numr variabil de argumente, care
pot fi apelate cu numr diferit de argumente efective. Exemplu de functie
pentru adunarea unui numr oarecare de valori:
#include <stdarg.h>
int va_add(int numberOfArgs, ...) {
va_list ap;
// tip definit in <stdarg.h>
int n = numberOfArgs;
// numar de argumente efective
int sum = 0;
va_start(ap,numberOfArgs); // macro din <stdarg.h>
while (n--)
sum += va_arg(ap,int);
va_end(ap);
// macro din <stdarg.h>
return sum;
}
// exemple de apelare
va_add(3,987,876,567);
// cu 3 arg
va_add(2,456,789);
// cu 2 arg
Instructiunea return
Instructiunea return se foloseste pentru revenirea dintr-o functie apelat la
functia care a fcut apelul si poate contine o expresie ce reprezint rezultatul
functiei. Conversia rezultatului la tipul functiei se face automat, dac e posibil
O functie de un tip diferit de void trebuie s contin cel putin o instructiune
return prin care se transmite rezultatul functiei. Exemplu:
long fact (int n) {
long nf=1L;
while ( n)
nf=nf*n--;
return nf;
}
// factorial de n
// ptr calcul rezultat
// nf=nf*n; n=n-1;
// rezultat functie
Cuvntul else dup o instructiune return poate lipsi, dar de multe ori este
prezent pentru a face codul mai clar. Exemplu fr else:
http://elth.srv.ro/
52
char toupper(char c) {
if (c>='a'&& c<='z')
return c+'A'-'a';
return c;
}
http://elth.srv.ro/
53
ale variabilelor argumente efective si nu poate modifica accidental variabile
din functia apelant.
Compilatorul C genereaz o secvent de atribuiri la argumentele formale
nainte de efectuarea saltului la prima instructiune din functia apelat. Din
acest motiv toate conversiile de tip efectuate automat la atribuire se aplic si la
transmiterea argumentelor.
In functia "fact" se modifica aparent valoarea lui "n" dar de fapt se modific
o variabil local, fr s fie afectat variabila ce contine pe "n" n "main".
Un alt exemplu clasic este o functie care ncearc s schimbe ntre ele
valorile a dou variabile, primite ca argumente:
void swap (int a, int b) {
//
nu
este
corect
!!!
int aux;
aux=a; a=b; b=aux;
}
void main () {
int x=3, y=7;
swap(x,y);
printf ("%d,%d \n",x,y); // scrie 3,7 !
}
// pointeri la
ele
http://elth.srv.ro/
54
variabilelor locale se face tot la executie si de aceea se pot folosi expresii
pentru initializare (nu numai constante). Exemplu:
double arie (double a, double b, double c) {
double p = (a+b+c)/2.;
// initializare cu expresie
return sqrt(p*(p-a)*(p-b)*(p-c));
}
// gresit
http://elth.srv.ro/
55
for (j=0;j<n;j++)
printf("%5d",a[i]);
printf(\n);
// dupa fiecare linie
}
}
Functii recursive
O functie recursiv este o functie care se apeleaz pe ea nssi. Se pot
deosebi dou feluri de functii recursive:
- Functii cu un singur apel recursiv, ca ultim instructiune, care se pot rescrie
usor sub forma nerecursiv (iterativ).
- Functii cu unul sau mai multe apeluri recursive, a cror form iterativ
trebuie s foloseasc o stiv pentru memorarea unor rezultate intermediare.
Recursivitatea este posibil n C datorit faptului c, la fiecare apel al
functiei, adresa de revenire, variabilele locale si parametri formali sunt
memorate ntr-o stiv (gestionat de compilator), iar la iesirea din functie (prin
return) se scot din stiv toate datele puse la intrarea n functie (se "descarc"
stiva).
Exemplu de functie recursiv de tip void :
void binar (int n) {
// se afiseaza n in
binar
if (n>0) {
binar(n/2);
// scrie echiv. binar al lui n/2
printf("%d",n%2); // si restul impartirii n la 2
}
}
Functia de mai sus nu scrie nimic pentru n=0, dar poate fi usor completat
cu o ramur else la instructiunea if.
Orice functie recursiv trebuie s contin (cel putin) o instructiune if (de
obicei chiar la nceput), prin care se verific dac (mai) este necesar un apel
recursiv sau se iese din functie. Reamintim c orice functie void primeste o
instructiune return ca ultim instructiune. Absenta instructiunii if conduce la o
recursivitate infinit ( la un ciclu fr conditie de terminare).
Pentru functiile de tip diferit de void apelul recursiv se face printr-o
instructiune return, prin care fiecare apel preia rezultatul apelului anterior.
Anumite functii recursive corespund unor relatii de recurent. Exemplu:
http://elth.srv.ro/
56
// 0! = 1
// n!=n*(n-1)!
// nerecursiv
Functiile recursive cu mai multe apeluri sau cu un apel care nu este ultima
instructiune pot fi rescrise iterativ numai prin folosirea unei stive. Aceast
stiv poate fi un simplu vector local functiei. Exemplu:
void binar ( int n) {
// afisare in binar
int c[16],i;
// c este stiva de cifre
// pune resturi in stiva c
i=0;
while ( n>0) {
c[i++]=n%2;
http://elth.srv.ro/
57
n=n/2;
}
// descarca stiva: scrie vector in ordine inversa
while (i>0)
printf ("%d",c[--i]);
}
Biblioteci de functii
Standardul limbajului C contine si o serie de functii care trebuie s existe n
toate implementrile limbajului. Declaratiile acestor functii sunt grupate n
fisiere antet cu acelasi nume pentru toate implementrile.
In afara acestor functii standard exist si alte functii specifice sistemului de
operare, precum si functii utile pentru anumite aplicatii (grafic pe calculator,
baze de date, aplicatii de retea s.a.).
Uneori, aceleasi operatii se pot realiza cu functii universale sau cu functii
dependente de sistem: obtinere/modificare timp, operatii cu directoare s.a.
Utilizarea functiilor standard din biblioteci reduce timpul de dezvoltare a
programelor, mreste portabilitatea lor si contribuie la reducerea diversittii
programelor, cu efect asupra usurintei de citire si de ntelegere a lor.
Functiile de bibliotec nestandard utilizate ar trebui marcate prin comentarii.
Informatii complete asupra functiilor de bibliotec pot fi obtinute prin ajutor
(Help) oferit de orice mediu IDE sau prin examinarea fisierelor antet, de tip H.
Aici se vor enumera succint cteva grupuri de functii standard utile:
<stdio.h> Functii standard de intrare-iesire pentru consol si fisiere
<stdlib.h> Functii de alocare memorie, de conversie din caractere n binar
(atoi, atol, atof), de sortare si cutare (qsort, bsearch), functii diverse (exit).
<math.h> Functii standard matematice (cu rezultat si argumente double)
(abs,sqrt,pow,sin,cos,exp,log s.a.)
<string.h> Functii standard pentru operatii cu siruri de caractere
<ctype.h> Functii de verificare tip caractere si de conversie caractere
<time.h> Functii pentru operatii cu timpi si date calendaristice
<stdarg.h> Functii (macrouri) pentru functii cu numr variabil de argumente
<io.h>
Functii standard de intrare-iesire stil Unix
<conio.h> Functii de intrare-iesire cu consola (ecranul si tastatura)
<process.h> Functii pentru executie procese (taskuri)
6. Tipuri pointer n C
Variabile pointer
http://elth.srv.ro/
58
O variabil pointer poate avea ca valori adrese de memorie. Aceste adrese
pot fi:
- Adresa unei valori de un anumit tip (pointer la date)
- Adresa unei functii (pointer la o functie)
- Adresa unei zone cu continut necunoscut (pointer la void).
Cel mai frecvent se folosesc pointeri la date.
Exist o singur constant de tip pointer, cu numele NULL (valoare zero) si
care este compatibil la atribuire si comparare cu orice tip pointer. Totusi, se
poate atribui o constant ntreag convertit la un tip pointer unei variabile
pointer. Exemplu:
char * p = (char*)10000;
// o adresa de memorie
Desi adresele de memorie sunt de multe ori numere ntregi pozitive, tipurile
pointer sunt diferite de tipurile ntregi si au utilizri diferite.
In limbajul C tipurile pointer se folosesc n principal pentru:
- Declararea si utilizarea de vectori, mai ales pentru vectori ce contin siruri de
caractere.
- Argumente de functii prin care se transmit rezultate (adresele unor variabile
din afara functiei).
- Acces la date alocate dinamic si care nu pot fi adresate printr-un nume.
- Argumente de functii prin care se transmite adresa unei alte functii.
Declararea unei variabile (sau argument formal) de un tip pointer include
declararea tipului datelor (sau functiei) la care se refer acel pointer. Sintaxa
declarrii unui pointer la o valoare de tipul tip este
tip * ptr;
Atunci cnd se declar mai multe variabile pointer de acelasi tip, nu trebuie
omis asteriscul care arat ca este un pointer. Exemple:
int *p, m;
int *a, *b ;
http://elth.srv.ro/
59
Tipul unei variabile pointer este important pentru c determin cti octeti
vor fi folositi de la adresa continut n variabila pointer si cum vor fi
interpretati. Un pointer la void nu poate fi utilizat direct, deoarece nu se stie
cti octeti trebuie folositi si cum.
Operatorul unar '&' aplicat unei variabile are ca rezultat adresa variabilei
respective (deci un pointer). Functia "malloc" si alte functii au ca rezultat un
pointer de tip void*.
Unei variabile de tip void* i se poate atribui orice alt tip de pointer fr
conversie de tip explicit si un argument formal de tip void* poate fi nlocuit
cu un argument efectiv de orice tip pointer.
Atribuirea ntre alte tipuri pointer se poate face numai cu conversie de tip
explicit ("cast") si permite interpretarea diferit a unor date din memorie. De
exemplu, putem extrage cei doi octeti dintr-un ntreg scurt astfel:
short int n; char c1, c2;
c1= *(char*)&n;
c2= *(char*)(&n+1);
sau:
char * p = (char*) &n;
http://elth.srv.ro/
60
c1= *p;
c2 = *(p+1);
- Compararea sau scderea a dou variabile pointer de acelasi tip (de obicei
adrese de elemente dintr-un acelasi vector). Exemplu:
// pozitia (indicele) n sirul s1 a sirului s2
// sau un numar negativ daca s1 nu contine pe s2
int pos ( char* s1, char * s2) {
char * p1 =strstr(s1,s2); // adresa lui s2 in s1
return p1-s1-1;
}
// tip * p ;
http://elth.srv.ro/
61
O eroare frecvent este utilizarea unei variabile pointer care nu a primit o
valoare (adic o adres de memorie) prin atribuire sau prin initializare la
declarare. Efectul este accesul la o adres de memorie imprevizibil, chiar n
afara spatiului de memorie ocupat de programul ce contine eroarea. Exemple:
int * a;
// declarata dar neinitializata
while (scanf ("%d",a) > 0)
a++;
Vectori si pointeri
O variabil vector contine adresa de nceput a vectorului (adresa primei
componente din vector) si de aceea este echivalent cu un pointer la tipul
elementelor din vector. Aceasta echivalent este exploatat de obicei n
argumentele de tip vector si n lucrul cu vectori alocati dinamic.
O functie poate avea ca rezultat un pointer dar nu si rezultat vector.
Pentru declararea unei functii care primeste un vector de ntregi si
dimensiunea lui avem cel putin dou posibilitti:
void printVec (int a[], int n);
void printVec (int * a, int n);
// echivalent cu &a[i] si cu
http://elth.srv.ro/
62
&a[0]
&a[1]
&a[k]
a
a+1
a+k
// gresit !
// echivalent cu: int a[]={3,4,5}
// incorect
// corect
http://elth.srv.ro/
63
sizeof (x) / sizeof(x[0])
Pointeri n functii
In definirea functiilor se folosesc pointeri pentru:
- Transmiterea de rezultate prin argumente;
- Transmiterea unei adrese prin rezultatul functiei;
O functie care trebuie s modifice mai multe valori primite prin argumente
sau care trebuie s transmit mai multe rezultate calculate de functie trebuie s
foloseasc argumente de tip pointer.
O functie care primeste un numr si trebuie s modifice acel numr poate
transmite prin rezultatul ei (prin return) valoarea modificat. Exemplu:
// functie care incrementeaza un intreg n modulo m
int incmod (int n, int m ) {
return ++n % m;
}
O functie care primeste dou sau mai multe numere pe care trebuie s le
modifice va avea argumente de tip pointer sau un argument vector care
reuneste toate rezultatele (datele modificate). Exemplu:
// calculeaza urmatorul moment de timp (ora,min,sec)
void inctime (int*h,int*m,int*s) {
*s=incmod(*s,60);
// secunde
if (*s==0) {
*m=incmod(*m,60);
// minute
if (*m==0)
*h=incmod(*h,24);
// ore
}
}
// utilizare functie
void main () {
int h,m,s;
while ( scanf ("%d%d%d",&h,&m,&s) >0) {
inctime (&h,&m,&s);
printf ("%4d%4d%4d \n",h,m,s);
}
}
In exemplul anterior cele trei argumente ntregi pot fi reunite ntr-un vector,
pentru simplificarea functiei:
void inctime (int t[3]) {
t[2]=incmod(t[2],60);
http://elth.srv.ro/
64
if (t[2]==0) {
t[1]=incmod(t[1],60); // minute
if (t[1]==0)
t[0]=incmod(t[0],24); // ore
}
}
http://elth.srv.ro/
65
- Primeste ca argument adresa vectorului (definit si alocat n alt functie) si
depune rezultatele la adresa primit (este solutia recomandat). Exemplu:
void cifre (int n, int c[]) {
int k;
for (k=4;k>=0;k--) {
c[k]=n%10; n=n/10;
}
}
- Aloc dinamic memoria pentru vector (cu "malloc"), iar aceast alocare se
mentine si la iesirea din functie. O solutie oarecum echivalent este utilizarea
unui vector local static, care continu s existe dup terminarea functiei.
Functia are ca rezultat adresa vectorului alocat n cadrul functiei.Problema
este unde si cnd se elibereaz memoria alocat. Exemplu:
int * cifre (int n) {
int k, *c;
// vector local
c = (int*) malloc (5*sizeof(int));
for (k=4;k>=0;k--) {
c[k]=n%10; n=n/10;
}
return c;
// corect
}
Pointeri la functii
Anumite aplicatii numerice necesit scrierea unei functii care s poat apela
o functie cu nume necunoscut, dar cu prototip si efect cunoscut. De exemplu,
o functie care s calculeze integrala definit a oricrei functii cu un singur
argument sau care s determine o rdcin real a oricrei ecuatii (neliniare).
Aici vom lua ca exemplu o functie "listf" care poate afisa (lista) valorile unei
alte functii cu un singur argument, ntr-un interval dat si cu un pas dat.
Exemple de utilizare a functiei "listf" pentru afisarea valorilor unor functii de
bibliotec:
void main () {
listf (sin,0.,2.*M_PI, M_PI/10.);
listf (exp,1.,20.,1.);
}
http://elth.srv.ro/
66
Prin conventie, n limbajul C, numele unei functii nensotit de o list de
argumente (chiar vid) este interpretat ca un pointer ctre functia respectiv
(fr a se folosi operatorul de adresare '&'). Deci "sin" este adresa functiei
"sin(x)" n apelul functiei "listf".
Declararea unui argument formal (sau unei variabile) de tip pointer la o
functie are forma urmtoare:
tip (*pf) (lista_arg_formale)
unde:
pf este numele argumentului (variabilei) pointer la functie
tip este tipul rezultatului functiei
Parantezele sunt importante, deoarece absenta lor modifica interpretarea
declaratiei. Exemplu de declaratie functie cu rezultat pointer:
tip * f (lista_arg_formale)
Pentru a face programele mai explicite se pot defini nume de tipuri pentru
tipuri pointeri la functii, folosind declaratia typedef. Exemplu:
typedef double (* ftype) (double);
void listf(ftype fp,double min,double max, double pas) {
double x,y;
for (x=min; x<=max; x=x+pas) {
y=fp(x);
printf ("\n%20.10lf %20.10lf, x,y);
}
}
http://elth.srv.ro/
67
Un vector de pointeri la functii poate fi folosit n locul unui bloc switch
pentru selectarea unei functii dintr-un grup de mai multe functii, ntr-un
program cu meniu de optiuni prin care operatorul alege una din functiile
realizate de programul respectiv. Exemplu:
// functii ptr. operatii realizate de program
void unu() {
printf ("unu\n");
}
void doi() {
printf ("doi\n");
}
void trei() {
printf ("trei\n");
}
// selectare si apel functie
typedef void (*funPtr) ();
void main () {
funPtr tp[]= {unu,doi,trei};
short option=0;
do {
printf(Optiune (1/2/3):);
scanf ("%hd", &option);
if (option >=1 && option <=3)
tp[option-1]();
// apel functie (unu/doi/trei)
} while (1);
}
http://elth.srv.ro/
68
http://elth.srv.ro/
69
Pentru a determina lungimea unui sir terminat cu zero se poate folosi functia
de bibliotec strlen. Exemplu:
while (scanf (%s,sir) != EOF)
printf (%s %d \n, sir, strlen(sir));
http://elth.srv.ro/
70
scanf (30s, nume);
scanf(%1s, s);
printf (egale\n);
Din aceeasi categorie de erori face parte atribuirea ntre pointeri cu intentia
de copiere a unui sir la o alt adres, desi o parte din aceste erori pot fi
semnalate la compilare. Exemple:
char a[100], b[100], *c ;
// memorie alocata dinamic la adresa c
c = (char*) malloc(100);
a = b;
// eroare la compilare
// corect sintactic dar nu copiaza sir (modifica
c = a;
// copiaza sir de la adresa a la adresa c
c)
http://elth.srv.ro/
71
strcpy (c,a);
// copiaza la adresa a sirul de la adresa b
strcpy (a,b);
http://elth.srv.ro/
72
char * strstr (char *d, char*s);
// eroare la executie
http://elth.srv.ro/
73
char nume[30]="test";
strcat (nume,.cpp);
Reducerea numrului de argumente (prin eliminarea ultimului argument) sar putea face prin alocare dinamic de memorie pentru subsirul extras:
http://elth.srv.ro/
74
Pentru realizarea unor noi operatii cu siruri se pot folosi functiile existente.
Exemple:
// sterge n caractere de la adresa d
char * strdel( char *d, int n) {
if ( n < strlen(d))
strcpy(d,d+n);
return d;
}
// insereaza sirul s la adresa d
void strins (char *d, char *s) {
int ld=strlen(d), ls=strlen(s);
strcpy (d+ld+ls,d);
// deplasare dreapta sir d
strcpy(d,s);
strcpy (d+ls, d+ld+ls);
}
http://elth.srv.ro/
75
Observatiiile urmtoare se refer att la functiile standard pe siruri ct si la
functii definite de utilizatori:
- Argumentele sau rezultatele ce reprezint lungimea unui sir sunt de tip
size_t (echivalent de obicei cu unsigned int) si nu int, pentru a permite siruri
de lungime mai mare.
- Argumentele ce reprezint adrese de siruri care nu sunt modificate de
functie se declar astfel:
const char * str
http://elth.srv.ro/
76
void main ( ) {
char linie[128], * cuv; //
char *sep=.,;\t\n
//
gets(linie);
//
cuv=strtok(linie,sep); //
while ( cuv !=NULL) {
puts (cuv);
//
cuv=strtok(0,sep);
//
}
}
adresa
sir de
citire
primul
cuvant in linie
caractere separator
linie
cuvant din linie
scrie cuvant
urmatorul cuvant din linie
http://elth.srv.ro/
77
Cutarea de cuvinte complete poate folosi functiile strtok si strcmp, iar
cutarea de subsiruri n orice context (ca prti de cuvinte) poate folosi functia
strstr. In secventa urmtoare se caut si se nlocuiesc toate aparitiile sirului
s1 prin sirul s2 ntr-o linie de text, memorat la adresa line:
while(p=strstr(line,s1)) {
// adresa lui s1 n line
strdel (p,strlen(s1)); // sterge caractere de la adr
p
strins(p,s2);
// insertie de caractere la p
// cauta un s1 de la p
http://elth.srv.ro/
78
p=txt;
// adresa de unde se cauta
do {
regexec (r,p);
// completeaza structura de la r
printf ("%s \n",r->startp[0]);
p= r->endp[0];
// de aici continua cautarea
} while ( r->startp[0]); // startp=NULL daca negasit
}
http://elth.srv.ro/
79
char * r;
if (sir) p=sir;
// ignora separatori intre atomi
while (strchr(separ,*p) && *p )
p++;
if (*p=='\0') return NULL;
r=p;
while (strchr(separ,*p)==NULL && *p)
p++;
if (p==r) return NULL;
else {
*p++='\0'; return r;
}
}
http://elth.srv.ro/
80
//aloca memorie pentru 30 de caractere
char * str = (char*) malloc(30);
//aloca memorie ptr. n ntregi
int * a = (int *) malloc( n * sizeof(int));
Realocarea unui vector care creste (sau scade) fat de dimensiunea estimat
anterior se poate face cu functia realloc, care primeste adresa veche si noua
dimensiune si ntoarce noua adres:
// dublare dimensiune curenta a zonei de la adr. a
a = (int *)realloc (a, 2*n* sizeof(int));
http://elth.srv.ro/
81
multe ori programele pot afla (din datele citite) dimensiunile vectorilor cu care
lucreaz si deci pot face o alocare dinamic a memoriei pentru acesti vectori.
Aceasta este o solutie mai flexibil, care foloseste mai bine memoria
disponibil si nu impune limitri arbitrare asupra utilizrii unor programe. In
limbajul C nu exist practic nici o diferent ntre utilizarea unui vector cu
dimensiune fix si utilizarea unui vector alocat dinamic, ceea ce ncurajeaz si
mai mult utilizarea unor vectori cu dimensiune variabil.
Un vector alocat dinamic se declar ca variabil pointer care se initializeaz
cu rezultatul functiei de alocare. Tipul variabilei pointer este determinat de
tipul componentelor vectorului.
De observat c nu orice vector cu dimensiune constant este un vector static;
un vector definit ntr-o functie (alta dect main) nu este static deoarece nu
ocup memorie pe toat durata de executie a programului, desi dimensiunea sa
este stabilit la scrierea programului. Un vector definit ntr-o functie este
alocat pe stiv, la activarea functiei, iar memoria ocupat de vector este
eliberat automat la terminarea functiei.
Exemplul urmtor arat cum se poate defini si utiliza un vector alocat
dinamic:
void main() {
int n,i; int * a;
// adresa vector alocat
dinamic
printf ("n="); scanf ("%d", &n);
//
dimensiune
vector
a=(int *) calloc (n,sizeof(int));
// sau:
a=(int*) malloc (n*sizeof(int));
printf ("componente vector: \n");
for (i=0;i<n;i++)
scanf ("%d", &a[i]);
// sau
scanf (%d, a+i);
for (i=0;i<n;i++)
// afisare vector
printf ("%d ",a[i]);
}
Exist si cazuri n care datele memorate ntr-un vector rezult din anumite
prelucrri, iar numrul lor nu poate fi cunoscut de la nceputul executiei. Un
exemplu poate fi un vector cu toate numerele prime mai mici ca o valoare
dat. In acest caz se poate recurge la o realocare dinamic a memoriei. In
exemplul urmtor se citeste un numr necunoscut de valori ntregi ntr-un
vector extensibil:
#define INCR 100
fiecare realocare
void main() {
int n,i,m ;
float x, * v;
//
cu
cat
creste
vectorul
la
// v = adresa vector
http://elth.srv.ro/
82
n=INCR; i=0;
v = (float *)malloc (n*sizeof(float));
//alocare
initiala
while ( scanf("%f",&x) != EOF) {
if (++i == n) {
// daca este necesar
n= n+ INCR;
//creste dimensiune vector
v=(float *) realloc (vector,n*sizeof(float));
}
v[i]=x;
// memorare in vector
}
for (i=0;i<n;i++)
// afisare vector
printf ("%f ",v[i]);
}
http://elth.srv.ro/
83
for(i=0;i<n;i++)
printf ("%s\n",vp[i]);
}
// utilizare functii
void main ( ) {
// vector de pointeri cu dimensiune fixa
char * list[100]; int n;
n = readstr(list);
printstr (list, n);
}
http://elth.srv.ro/
84
void main ( int argc, char ** argv) {
while (argc --)
printf (%s , *argv++);
}
http://elth.srv.ro/
85
for (j=0;j<nc;j++)
printf (%2d, a[i][j]);
printf(\n);
}
}
// 2 linii si 2 coloane
http://elth.srv.ro/
86
De remarcat c pentru matricea alocat dinamic nu mai putem folosi accesul
direct la elemente, pentru c programul compilator nu are informatiile
necesare s calculeze adresa corect a unui element a[i][j]. Exemplu gresit:
// completare matrice
for (i=0;i<nl;i++)
for (j=0;j<nc;j++)
a[i][j] = nc*i+j+1;
9. Tipuri structur n C
Definirea de tipuri si variabile structur
Un tip structur reuneste cteva componente (cmpuri) avnd fiecare un
nume si un tip. Tipurile cmpurilor unei structuri pot fi si sunt n general
diferite. Definirea unui tip structur are sintaxa urmtoare:
struct tag { tip1 c1, tip2 c2, ... };
unde:
tag este un nume de tip folosit numai precedat de cuvntul cheie struct (n
C, dar n C++ se poate folosi singur ca nume de tip).
tip1,tip2,... este tipul unei componente
c1,c2,... este numele unei componente (cmp)
Ordinea enumerrii cmpurilor unei structuri nu este important, deoarece
ne referim la cmpuri prin numele lor. Se poate folosi o singura declaratie de
tip pentru mai multe cmpuri. Exemple:
// momente de timp
struct time {
int ora,min,sec;
};
// o activitate
struct activ {
char numeact[30];
//
struct time start; //
struct time stop;
//
};
(ora,minut,secunda)
nume activitate
ora de incepere
ora de terminare
http://elth.srv.ro/
87
De remarcat c orice declaratie struct se termin obligatoriu cu caracterul
; chiar dac acest caracter urmeaz dup o acolad; aici acoladele nu
delimiteaz un bloc de instructiuni ci fac parte din declaratia struct.
In structuri diferite pot exista cmpuri cu acelasi nume, dar ntr-o aceeasi
structur numele de cmpuri trebuie s fie diferite.
Declararea unor variabile de un tip structur se poate face fie dup
declararea tipului structur, fie simultan cu declararea tipului structur.
Exemple:
struct time t1,t2, t[100]; // t este vector de structuri
struct complex {float re,im;} c1,c2,c3; // numere
complexe
struct complex cv[200];
//
un
vector
de
numere
complexe
http://elth.srv.ro/
88
operatii aritmetice, operatii de citire-scriere etc. Exemplul urmtor arat cum
se poate ordona un vector de structuri time, tip definit anterior:
// scrie ora
void wrtime ( struct time t) {
printf ("%02d:%02d:%02d \n", t.ora,t.min,t.sec);
}
// compara momente de timp
int cmptime (struct time t1, struct time t2) {
int h,m;
h=t1.ora-t2.ora;
m=t1.min-t2.min;
if (h) return h;
// <0 daca t1<t2 si >0 daca t1>t2
if (m) return m;
// rezultat negativ sau pozitiv
return t1.sec-t2.sec;
// rezultat <0 sau =0 sau > 0
}
void main () {
struct time tab[200], aux; int i,j,n;
. . .
// citire date
// ordonare vector
for (j=1;j<n;j++)
for (i=1;i<n;i++)
if ( cmptime (tab[i-1],tab[i]) > 0) {
aux=tab[i-1]; tab[i-1]=tab[i]; tab[i]=aux;
}
// afisare lista de date
for (i=0;i<n;i++)
wrtime(tab[i]);
}
Dac un cmp este la rndul lui o structur, atunci numele unui cmp poate
contine mai multe puncte ce separ numele variabilei si cmpurilor de care
apartine (n ordine ierarhic). Exemplu:
struct activ a;
printf (%s ncepe la %d: %d si se termina la %d: %d \n,
a.numeact, a.start.ora, a.start.min,
http://elth.srv.ro/
89
a.stop.ora, a.stop.min);
(*pt).ora
http://elth.srv.ro/
90
scanf (%f%f,&c.re, &c.im);
return c;
}
. . . // utilizare
struct complex a[100]; . . .
for (i=0;i<n;i++)
a[i]=readx();
http://elth.srv.ro/
91
De remarcat c o functie nu poate avea ca rezultat direct un vector, dar poate
avea ca rezultat o structur care include un vector.
//int
http://elth.srv.ro/
92
Atunci cnd numele unui tip structur este folosit frecvent, inclusiv n
argumente de functii, este preferabil un nume introdus prin typedef, dar dac
vrem s punem n evident c este vorba de tipuri structur vom folosi numele
precedat de cuvntul cheie struct.
http://elth.srv.ro/
93
};
void write (struct numar n) {
switch (n.tipn) {
case I : printf ("%d ",n.v.ival);break; // I=0
...
}
struct numar read (tnum tip) {
struct numar n;
n.tipn=tip;
switch (tip) {
case I: scanf ("%d", &n.v.ival);break;
. . .
}
return n;
}
void main () {
struct numar a,b,c,d;
a = read(I); write(a);
...
}
Structuri predefinite
Anumite functii de bibliotec folosesc tipuri structur definite n fisiere de
tip H si care ascund detalii ce nu intereseaz pe utilizatori. Un exemplu este
tipul FILE definit n "stdio.h" si a crui definitie depinde de implementare si
de sistemul de operare gazd (o structur opac, invizibil pentru utilizator).
Structura struct tm definit n fisierul <time.h> contine componentele ce
definesc complet un moment de timp:
http://elth.srv.ro/
94
struct tm {
int tm_sec, tm_min, tm_hour;
int tm_mday, tm_mon, tm_year;
int tm_wday, tm_yday;
int tm_isdst;
};
Exemplul urmtor arat cum se poate afisa ora si ziua curent, folosind
numai functii standard:
#include <stdio.h>
#include <time.h>
void main(void) {
time_t t;
// time_t = long
struct tm *area;
t = time(NULL);
// obtine ora curenta
area = localtime(&t); // conv. din time_t in struct tm
printf("Local time is: %s", asctime(area));
}
http://elth.srv.ro/
95
O list nlantuit (linked list) este o colectie de variabile alocate dinamic
(de acelasi tip), dispersate n memorie, dar legate ntre ele prin pointeri, ca
ntr-un lant. Intr-o list liniar simplu nlntuit fiecare element al listei
contine adresa elementului urmtor din list. Ultimul element poate contine ca
adres de legatur fie constanta NULL, fie adresa primului element din list
(lista circular). Adresa primului element din list este memorat ntr-o
variabil cu nume (alocat la compilare) si numit cap de lista (list head).
Pentru o list vid variabila cap de list este NULL.
Structura de list este recomandat atunci cnd colectia de elemente are un
continut foarte variabil (pe parcursul executiei) sau cnd trebuie pstrate mai
multe liste cu continut foarte variabil.
Un element din list (un nod de list) este de un tip structur si are (cel
putin) dou cmpuri: un cmp de date (sau mai multe) si un cmp de legatur.
Exemplu pentru o list de ntregi:
typedef struct snod {
int val ;
struct snod * leg ;
} nod;
// camp de date
// camp de legatura
http://elth.srv.ro/
96
} nod,* pnod, * list;
void printL ( list lst) {
// afisare lista - recursiv
if (lst != NULL) {
printf("%d ",lst->val);
printL (lst->leg);
}
}
list insL( list lst, int x){ // insertie la inceput lista
pnod nou ;
// adresa nod nou
if ((nou=(list)malloc(sizeof(nod))) ==NULL)
return NULL;
nou->val=x; nou->leg=lst;
return nou;
}
void main (){
// creare si afisare lista stiva
list st; int x;
st=NULL;
while (scanf("%d",&x) > 0)
st=insL(st,x);
printL (st);
}
Alte structuri dinamice folosesc cte doi pointeri; ntr-o list dublu nlntuit
fiecare nod contine adresa nodului urmtor si adresa nodului precedent.
10. Fisiere de date n C
Tipuri de fisiere
Un fisier ("File") este o colectie de date memorate pe un suport extern si
care este identificat printr-un nume. Fisierele se folosesc fie pentru date
initiale si pentru rezultate mai numeroase, fie pentru pstrarea de durat a unor
date de interes pentru anumite aplicatii.
Fisierele sunt entitti ale sistemului de operare si ca atare ele au nume care
respect conventiile sistemului, fr legtur cu un limbaj de programare.
Operatiile cu fisiere sunt realizate de ctre sistemul de operare, iar
compilatorul unui limbaj traduce functiile (instructiunile) de acces la fisiere n
apeluri ale functiilor sistem.
Programatorul se refer la un fisier printr-o variabil; tipul acestei variabile
depinde de limbajul folosit si chiar de functiile utilizate (n C). Asocierea
dintre numele extern (un sir de caractere) si variabila din program se face la
deschiderea unui fisier, printr-o functie standard.
De obicei prin "fisier" se subntelege un fisier disc (pe suport magnetic sau
optic), dar notiunea de fisier este mai general si include orice flux de date din
exterior spre memorie sau dinspre memoria intern spre exterior. Dispozitivele
periferice uzuale au nume de fisiere predefinite; de exemplu, n limbajul C sub
MS-DOS si MS-Windows se pot folosi urmtoarele nume :
http://elth.srv.ro/
97
CON = consola sistem (tastaura la citire si monitor la scriere)
PRN (LPT) = imprimanta sistem
Pentru fisierele disc un nume de fisier poate include urmtoarele:
- Numele unittii de disc sau partitiei disc ( ex: A:, C:, D:, E:)
- "Calea" spre fisier, care este o succesiune de nume de fisiere catalog
(director), separate printr-un caracter ('\' n MS-DOS si MS-Windows, sau '/' n
Unix si Linux)
- Numele propriu-zis al fisierului ( max 8 litere si cifre n MS-DOS)
- Extensia numelui, care indic tipul fisierului (continutul su) si care poate
avea ntre 0 si 3 caractere n MS-DOS).
Exemple de nume de fisiere disc:
A:bc.rar , c:\borlandc\bin\bc.exe
c:\work\p1.cpp , c:\work\p1.obj
http://elth.srv.ro/
98
Functia "fopen" are rezultat NULL (0) dac fisierul specificat nu este gsit
dup cutare n directorul curent sau pe calea specificat sau pe cile de
cutare definite n sistem (prin comanda PATH ).
Primul argument al functiei "fopen" este numele extern al fisierului scris cu
respectarea conventiilor limbajului C: pentru separarea numelor de cataloage
dintr-o cale se vor folosi dou caractere "\\", pentru a nu se considera o
secvent de caractere "Escape" a limbajului. Exemple:
FILE * f = fopen ("c:\\work\\t.txt", "r");
// varianta la linia anterioara:
char *numef = "C:\\WORK\\T.TXT";
FILE * f;
if ( (f=fopen(numef,"r")) == NULL){
printf("Eroare la deschidere fisier %s \n", numef);
return;
}
http://elth.srv.ro/
99
Al doilea argument al functiei "fopen" este un sir care poate contine ntre 1
si 3 caractere, dintre urmtoarele caractere posibile:
"r,"w","a" = mod de folosire ("read", "write", "append")
"+" dup "r" sau "a" pentru citire si scriere din acelasi fisier
"t" sau "b" = tip fisier ("text", "binary"), implicit este "t"
Pentru fisierele text sunt folosite modurile "w" pentru crearea unui nou
fisier, "r" pentru citirea dintr-un fisier si "a" pentru adugare la sfrsitul unui
fisier existent. Pentru actualizarea unui fisier text prin modificarea lungimii
unor linii, stergerea sau insertia de linii se va scrie un alt fisier si nu se vor
opera modificrile direct pe fisierul initial.
Pentru fisierele binare se practic actualizarea pe loc a fisierelor, fr
inserarea de date ntre cele existente, deci modurile "r+","a+","w+". (literele 'r'
si 'w' nu pot fi folosite simultan).
Inchiderea unui fisier disc este absolut necesar pentru fisierele n care s-a
scris ceva, dar poate lipsi dac s-au facut doar citiri de date din fisier.
Fisierele standard de intrare-iesire (tastatura si ecranul consolei) au asociate
variabile de tip pointer cu nume predefinit ("stdin" si "stdout"), care pot fi
folosite n diferite functii, dar practic se folosesc numai in functia "fflush" care
goleste zona tampon ("buffer") asociat unui fisier.
Operatiile de stergere a unui fisier existent ("remove") si de schimbare a
numelui unui fisier existent ("rename") nu necesit deschiderea fisierelor.
cu
http://elth.srv.ro/
100
char * gets (char * line);
// scriere linie
int fputs (char * line, FILE *f);
int puts (char * line);
Functia "fgets" adaug sirului citit un octet zero (n memorie), iar functia
"fputs" nu scrie n fisier octetul zero (necesar numai n memorie).
Primul exemplu este un program care citeste un fisier text si afiseaz
continutul su la imprimant:
// listare fisier text la imprimanta
void main () {
char numef[100], linie[132];
FILE * txt, *lst;
puts ("Nume fisier:"); gets (numef);
txt = fopen(numef,"r");
lst = fopen ("PRN","w"); // poat lipsi ptr. stdprn
while (fgets (linie,132,txt))
fputs (linie,lst);
// fputs(linie,stdprn);
fclose(txt);
}
Exemplul urmtor citeste un fisier text si scrie un alt fisier n care toate
literele mari din textul citit sunt transformate n litere mari.
// copiere fisier cu transformare in litere mari
void main (int argc, char * argv[]) {
FILE * f1, * f2; int ch;
f1= fopen (argv[1],"r");
f2= fopen (argv[2],"w");
if ( f1==0 || f2==0) {
puts (" Eroare la deschidere fisiere \n");
return;
}
while ( (ch=fgetc(f1)) != EOF)
// citeste din f1
fputc ( tolower(ch),f2);
// scrie in f2
fclose(f1); fclose(f2);
}
http://elth.srv.ro/
101
int fprintf (FILE * f, char * fmt, ...)
Uneori poate fi util functia fflush (FILE*) care goleste zona tampon
folosit la citire sau scriere si care nu este direct accesibil prin numele su.
Fisiere text cu numere se folosesc pentru fisiere de date initiale cu care se
verific anumite programe, n faza de punere la punct. Rezultatele unui
program pot fi puse ntr-un fisier fie pentru a fi prelucrate de un alt program,
fie pentru arhivare sau pentru imprimare repetat.
De observat ca majoritatea sistemelor de operare permit redirectarea
fisierelor standard de intrare si de iesire, fr a modifica programele. Deci un
program (neinteractiv) care foloseste functiile "scanf" si "printf" sau alte
functii standard (gets, puts, getchar, putchar) poate s-si citeasc datele dintrun fisier sau s scrie rezultatele ntr-un fisier prin specificarea acestor fisiere n
linia de comanda. Exemple de utilizare a unui program de sortare:
date de la tastatura,rezultate afisate pe ecran
sort
date din "input", rezultate in "output"
sort <<input >>output
date de la tastatura, rezultate in "output"
sort >>output
date din "input",rezultate afisate pe ecran
sort <<input
http://elth.srv.ro/
102
Functii de acces secvential la fisiere binare
Un fisier binar este format n general din articole de lungime fix, fr
separatori ntre articole. Un articol poate contine un singur octet sau un numr
binar (pe 2,4 sau 8 octeti) sau o structur cu date de diferite tipuri.
Functiile de acces pentru fisiere binare "fread" si "fwrite" pot citi sau scrie
unul sau mai multe articole, la fiecare apelare. Transferul ntre memorie si
suportul extern se face fr conversie sau editare (adugare de caractere la
scriere sau eliminare de caractere la citire).
Programul urmtor scrie mai multe numere ntregi ntr-un fisier disc si apoi
citeste continutul fisierului si afiseaz pe ecran numerele citite.
void main () {
FILE * f; int x;
// creare fisier
f=fopen ("num.bin","wb");
// din
curent
for (x=1; x<=100; x++)
fwrite (&x,sizeof(float),1,f);
fclose(f);
// citire fisier pentru verificare
printf("\n");
f=fopen ("num.bin","rb");
while (fread (&x,sizeof(float),1,f)==1)
printf ("%4d ",x);
fclose(f);
}
directorul
http://elth.srv.ro/
103
char nume[25];
float medie;
} Elev;
// creare fisier cu nume dat
void creare(char * numef) {
FILE * f; Elev s;
f=fopen(numef,"wb"); assert (f != NULL);
printf (" nume si medie ptr. fiecare student : \n\n");
while (scanf ("%s %f ", s.nume, &s.medie) != EOF)
fwrite(&s,sizeof(s),1,f);
fclose (f);
}
// afisare continut fisier pe ecran
void listare (char* numef) {
FILE * f; Elev e;
f=fopen(numef,"rb"); assert (f != NULL);
while (fread (&e,sizeof(e),1,f)==1)
printf ("%-25s %6.2f \n",e.nume, e.medie);
fclose (f);
}
// adaugare articole la sfarsitul unui fisier existent
void adaugare (char * numef) {
FILE * f; Elev e;
f=fopen(numef,"ab"); assert (f != NULL);
printf (" nume si medie ptr. fiecare student : \n\n");
while (scanf ("%s%f ",e.nume, &e.medie) != EOF)
fwrite(&e,sizeof(e),1,f);
fclose (f);
}
http://elth.srv.ro/
104
int fseek (FILE * f, long bytes, int origin);
unde "bytes" este numrul de octeti fat de punctul de referint "origin", care
poate fi: 0 = nceputul fisierului, 1 = pozitia curent, 2 = sfrsitul fisierului.
Functia "fseek" este util n urmtoarele situatii:
- Pentru repozitionare pe nceput de fisier dup o cutare si nainte de o alt
cutare secvential n fisier (fr a nchide si a redeschide fisierul)
- Pentru pozitionare pe nceputul ultimului articol citit, n vederea scrierii
noului continut (modificat) al acestui articol, deoarece orice operatie de citire
sau scriere avanseaz automat pozitia curent n fisier, pe urmtorul articol.
In exemplul urmtor sunt ilustrate ambele situatii:
// modificare continut articole, dupa cautarea lor
void modificare (char * numef) {
FILE * f; Elev e; char nume[25];
long pos; int eof;
f=fopen(numef,"rb+"); assert (f != NULL);
do {
printf ("Nume cautat: "); eof=scanf ("%s",nume);
if (eof==EOF) break;
// cauta "nume" in fisier
fseek(f,0,0);
// readucere pe inceput de fisier
pos=-1L;
// pozitie nume cautat
while (fread (&e,sizeof(e),1,f)==1)
if (strcmp (e.nume, nume)==0) {
pos= ftell(f)-sizeof(e);
break;
}
if ( pos < 0) break;
printf ("noua medie: "); scanf ("%f", &e.medie);
fseek (f,pos,0);
// pe inceput de articol gasit
fwrite(&e,sizeof(e),1,f);
//rescrie
articol
modificat
} while (eof != EOF);
fclose (f);
}
http://elth.srv.ro/
105
http://elth.srv.ro/
106
c) punct urmat de un ntreg, care arat precizia (numr de cifre dup punctul
zecimal) cu care se scriu numerele nentregi.
d) una din literele h, l sau L care modific lungimea tipului numeric.
Exemplu de utilizare a optiunii 0 pentru a scrie 2 cifre si pentru 0..9 :
#include <stdio.h>
#include <time.h>
void main () { // afisare ora curenta (portabil)
struct tm t; time_t timer; // tipuri definite in time.h
timer =time(NULL);
// ora curent n milisecunde
t= *localtime (&timer); // conversie in ore,min,sec
printf("%02d:%02d:%02d\n",t.tm_hour,t.tm_min,t.tm_sec);
}
http://elth.srv.ro/
107
- Expresii complexe, incluznd prelucrri, atribuiri si comparatii.
- Utilizarea de operatori specifici: atribuiri combinate cu alte operatii, expresii
conditionale s.a.
- Utilizarea instructiunii "break".
- Utilizarea de pointeri n locul unor vectori sau matrice.
- Utilizarea unor declaratii complexe de tipuri, n loc de a defini tipuri
intermediare, mai simple. Exemplu:
// vector de pointeri la functii void f(int,int)
void (*tp[M])(int,int); // greu de citit !
// cu tip intermediar ptr pointer la functie
typedef void (*funPtr) (int,int);
funPtr tp[M];
// vector cu M comp. de tip funPtr
http://elth.srv.ro/
108
Respectarea unor conventii de scriere n majoritatea programelor poate
contribui la reducerea diversittii programelor scrise de diversi autori si deci la
facilitarea ntelegerii si modificrii lor de ctre alti programatori.
O serie de conventii au fost stabilite de autorii limbajului C si ai primului
manual de C. De exemplu, numele de variabile si de functii ncep cu o liter
mic si contin mai mult litere mici (litere mari numai n nume compuse din
mai multe cuvinte alturate, cum sunt nume de functii din MS-Windows).
Literele mari se folosesc n nume pentru constante simbolice. In ceea ce
priveste numele unor noi tipuri de date prerile sunt mprtite.
Una dintre conventii se refer la modul de scriere a acoladelor care
ncadreaz un bloc de instructiuni ce face parte dintr-o functie sau dintr-o
instructiune if, while, for etc. Cele dou stiluri care pot fi ntlnite n diferite
programe si crti sunt ilustrate de exemplele urmtoare:
void main ()
// Afisare numere perfecte , stil Linux
{
int n,m,s,d;
scanf (%d,&n);
for (m=2; m<=n; m++)
{
s=0;
for (d=1; d<m; d++)
{
if ( m % d ==0 )
s= s+ d;
}
if ( m==s)
printf (%6d\n,m);
}
}
// Afisare numere perfecte,
void main () {
int n,m,s,d;
scanf (%d,&n);
for (m=2; m<=n; m++){
s=0;
for (d=1; d<m; d++){
if ( m % d ==0 )
s= s+ d;
}
if ( m==s)
printf (%6d\n,m);
}
}
http://elth.srv.ro/
109
Uneori se recomand utilizare de acolade chiar si pentru o singur
instructiune, anticipnd adugarea altor instructiuni n viitor la blocul
respectiv. Exemplu:
if ( m==s){
printf (%6d\n,m);
}
Pentru alinierea spre dreapta la fiecare bloc inclus ntr-o structur de control
se pot folosi caractere Tab (\t) sau spatii, dar evidentierea structurii de
blocuri incluse este important pentru oamenii acre citesc programe.
In cazul unor structuri de control multiple, suprapuse, se mai poate
simplifica programul prin definirea unor functii auxiliare, care includ o parte
din aceste structuri. De exemplu, n programul anterior se poate defini si folosi
o functie pentru calculul sumei divizorilor unui numr:
void main () {
int n,m;
scanf (%d,&n);
for (m=2; m<=n; m++){
if ( m==sumdiv(m))
printf (%6d\n,m);
}
}
http://elth.srv.ro/
110
*tt++ = digits[ value % radix];
} while ( (value = value / radix) != 0 );
while ( tt != t)
*string++= *(--tt);
*string=0;
return s;
}
Constructii idiomatice
Limbajul C poate fi derutant prin multitudinea posibilittilor de a exprima
un acelasi algoritm sau aceleasi prelucrri. Reducerea diversittii programelor
si a timpului de dezvoltare a programelor se poate face prin utilizarea unor
constructii idiomatice, consacrate de practica programrii n C.
Cuvintele "idiom", "idiomatic" se refer la particularittile unei limbi
(limbaj natural sau limbaj de programare), iar limbajul C exceleaz prin astfel
de particularitti.
Constructiile idiomatice n programare sunt denumite uneori sabloane sau
tipare ("patterns"), pentru c ele revin sub diverse forme n majoritatea
programelor, indiferent de autorii lor. Folosirea unor constructii idiomatice
permite programatorului s se concentreze mai mult asupra algoritmului
problemei si mai putin asupra mijloacelor de exprimare a acestui algoritm.
Specific limbajului C este utilizarea de expresii aritmetice sau de atribuire
drept conditii n instructiuni if, while, for, do n absenta unui tip logic
(boolean). Exemplu:
while (*d++ =*s++);
// copiaza sir de la s la d
In standardul C din 1999 s-a introdus un tip boolean, dar nu s-a modificat
sintaxa instructiunilor astfel c se pot folosi n continuare expresii aritmetice
drept conditii verificate. Limbajul Java a preluat toate instructiunile din C dar
cere ca intructiunile if, do,... s foloseasc expresii logice si nu aritmetice.
Pentru a facilita citirea programelor si trecerea de la C la Java este bine ca
toate conditiile s apar ca expresii de relatie si nu ca expresii aritmetice:
while (*s != 0)
*d++=*s++;
http://elth.srv.ro/
111
exit(-1);
}
Portabilitatea programelor
http://elth.srv.ro/
112
Un program C este portabil atunci cnd poate fi folosit (portat) pe orice
calculator si sub orice sistem de operare, fr modificarea textului surs.
Un program este portabil dac :
- nu foloseste extensii ale standardului limbajului C, specifice unei anumite
implementri a limbajului (unui anumit compilator) si nici elemente de C++.
- nu foloseste functii specifice unui sistem de operare sau unui mediu de
dezvoltare (functii nestandard).
- nu foloseste adrese de memorie sau alte particularitti ale calculatorului.
- nu foloseste particularitti ale mediului de dezvoltare (o anumit lungime
pentru numere ntregi sau pentru pointeri, anumite tipuri de biblioteci etc.).
In general pot fi portabile programele de aplicatii care folosesc numai functii
standard pentru intrri-iesiri (printf, scanf s.a.) si pentru alte servicii ale
sistemului de operare gazd (obtinere or curent, atribute fisiere etc.).
Programele care folosesc ecranul n mod grafic (cu ferestre, butoane,
diverse forme si dimensiuni de caractere etc.) sau care necesit pozitionarea pe
ecran n mod text sunt dependente de sistemul de operare gazd (Windows,
Linux etc.).
Pentru mrirea portabilittii programelor C standardul POSIX (Portable
Operating System) propune noi functii unice n C pentru acces la servicii care
ar trebui asigurate de orice sistem de operare compatibil POSIX.
Aflarea fisierelor dintr-un director si a atributelor acestora este un exemplu
de operatii care depind de sistemul gazd si nu se exprim prin functii
standard n C, desi sunt necesare n multe programe utilitare: listare nume
fisiere, arhivare fisiere, cutarea n mai multe fisiere a unui sir, s.a. Mai exact,
operatiile pot fi exprimate prin una sau dou functii, dar argumentele acestor
functii (structuri sau pointeri la structuri) depind de sistemul gazd.
Programul urmtor este utilizabil numai sub mediul Borland C :
#include <stdio.h>
#include <dir.h>
#include <string.h>
void main(int argc, char * argv[]) {
struct ffblk ffblk;
char mask[10]="*.*";
char *files[1000];
// vector de pointeri la nume
int done,n,i;
// creare vector cu nume fisiere gasite
n=0;
// numar de fisiere gasite
done = findfirst(mask,&ffblk,0xFF);
while (!done) {
files[n++]= strdup(ffblk.ff_name);
done = findnext(&ffblk);
}
for (i=0;i<n;i++) // afisare vector de nume
printf(" %s \n", files[i]);
http://elth.srv.ro/
113
}
http://elth.srv.ro/
114
situatiile n care o variabil poate primi o valoare). In particular, utilizarea de
variabile pointer neinitializate ca adrese de siruri este o eroare uzual.
Indirectarea prin variabile pointer cu valoarea NULL sau neinitializate poate
produce erori de adresare care s afecteze si sistemul de operare gazd.
Erorile la depsirea memoriei alocate pentru vectori (indici prea mari sau
prea mici) nu sunt specifice limbajului C, dar nici nu pot fi detectate la
executie dect prin instructiuni de verificare scrise de programator ( n Pascal
si n Java aceste verificri la indici de vectori se fac automat).
Pentru programele cu intrri-iesiri n mod text, care folosesc functii de I/E
cu conversie de format (din familia scanf, printf) o greseal frecvent este
neconcordanta dintre sirul format (primul argument) si lista de variabile sau
expresii (urmtoarele argumente). Aceste erori se manifest numai prin
rezultatele (gresite) ale programelor. Exemple:
//
// if ( a==b ) ...
http://elth.srv.ro/
115
mprtite ntre mai multe functii, o functie nu trebuie s aib prea multe
argumente, o secvent prea lung de cod surs este mai greu de stpnit.
Programele reale totalizeaz sute si mii de linii surs, deci numrul de
functii din aceste programe va fi mare, iar functiile trebuie s comunice. Pe de
alt parte, transmiterea de rezultate prin argumente pointer n C nu este cea
mai simpl si nici cea mai sigur solutie pentru programatori.
Cea mai dificil situatie este a functiilor care lucreaz cu structuri de date
dinamice, definite prin unul sau mai multi pointeri. Functia primeste un
pointer (de exemplu, adresa de nceput a unei liste nlntuite) si poate
modifica acest pointer. Vom folosi ca exemple functii pentru operatii cu o
stiv list nlntuit de ntregi (list cu acces numai la primul element),
definit astfel:
typedef struct s {
int val;
struct s * leg;
} nod, * Stiva ;
La operatiile cu o stiv pot apare erori de tipul stiv goal (la extragere) si
stiv plin (la introducere), iar aceste situatii trebuie semnalate de functii.
Practica limbajului C este ca rezultatul ntreg al functiilor s indice modul de
terminare: zero cu succes si o valoare nenul pentru terminare anormal.
Functia de scoatere din stiv pop trebuie s transmit ca rezultat valoarea
scoas din vrful stivei, dar si modul de terminare.
Cea mai simpl solutie este utilizarea unei variabile externe, mai ales c cele
mai multe programe folosesc o singur stiv. Exemplu:
Stiva st;
// stiva ca variabila externa
void initSt (){
// initializare stiva
st = NULL;
}
int push (int x){ // pune in stiva un element
nod *p;
p = (nod*)malloc(sizeof(nod));
if(p==NULL)
return -1;
// eroare de alocare
p->val = x; p->leg = st; st = p;
return 1;
// operatie reusita
}
int pop (int * px){
// scoate din stiva un element
nod * p;
if (st==NULL) return -1;
// stiva goala
* px = st->val;
p = st->leg;
free (st) ; st = p;
return 0;
// operatie reusita
http://elth.srv.ro/
116
}
// program de test
void main () {
int x;
initSt ();
while (scanf("%d", &x ) > 0)
push (x);
printf ( " \n continut stiva : \n") ;
while (pop (&x) >= 0)
printf("%d \n", x );
}
http://elth.srv.ro/
117
p =sp->leg;
free (sp);
return p;
}
void main () {
int x ; Stiva s;
s=initSt ();
while ( scanf ("%d",&x) > 0)
s=push (s,x);
while ( ! emptySt(s)) {
s=pop (s,&x);
printf ("%d \n",x);
}
}
Modul de apelare al functiilor push si pop de mai sus este mai putin
obisnuit, iar apelarea acestor functii ca functii de tip void nu este semnalat ca
eroare la compilare si se manifest la executie prin rezultate incorecte.
O alt solutie posibil este transmiterea unui pointer la pointer ca argument al
functiilor, iar rezultatul s fie modul de terminare (ntreg):
void initSt ( Stiva * sp){ // initializare stiva
*sp = NULL;
}
int push (Stiva * sp,int x){ // pune in stiva un element
nod * p;
p = (nod*)malloc(sizeof(nod));
if (p==NULL) return -1;
// stiva plina
p->val = x; p->leg = *sp;
*sp = p;
return 0;
}
int pop (Stiva * sp,int * px){ // scoate din stiva
nod * p;
if (*sp==NULL) return -1;
// stiva goala
* px = (*sp)->val;
p = (*sp)->leg;
free (*sp) ;
*sp = p; return 0;
}
void main (){
// utilizare functii
int x; Stiva s ;
initSt (&s);
while (scanf("%d", &x ) > 0)
http://elth.srv.ro/
118
push (&s,x);
while ( pop (&s,&x) >=0)
printf("%d \n", x ) ;
}
// tipul Stiva
Ultima solutie examinat este si cea mai bun dar nu este proprie limbajului
C deoarece foloseste argumente de tip referint din C++. Unele implementri
de C admit si tipuri referint (exemplu lcc-win32).
void initS ( Stiva & sp){ // initializare stiva
sp = NULL;
}
int push (Stiva & sp, int x){ // pune in stiva un element
nod * p;
p = (nod*)malloc(sizeof(nod));
if (p==NULL) return -1;
// stiva goala
p->val = x; p->leg = sp;
sp = p;
return 0;
}
int pop (Stiva & sp, int & x){ // scoate din stiva
nod * p;
if (sp==NULL) return -1; // stiva goala
x = sp->val;
p = sp->leg;
free (sp) ;
sp = p; return 0;
}
// program de test
void main () {
int x; Stiva s ;
initS (s);
while (scanf("%d", &x ) > 0)
push (s,x);
while ( pop (s,x) >=0)
printf("%d \n", x ) ;
}
http://elth.srv.ro/
119
Exist riscul de a confunda operatorul de adresare & cu caracterul &
folosit n declararea argumentelor de tip referint si care nu este operator.
12. Dezvoltarea programelor mari n C
Particularitti ale programelor mari
Aplicatiile reale conduc la programe mari, cu mai multe sute si chiar mii de
linii surs. Un astfel de program sufer numeroase modificri (cel putin n faza
de punere la punct), pentru adaptarea la cerintele mereu modificate ale
beneficiarilor aplicatiei (pentru mbunttirea aspectului si modului de
utilizare sau pentru extinderea cu noi functii sau pentru corectarea unor erori
aprute n exploatare).
Programarea la scar mare este diferit de scrierea unor programe mici, de
scoal, si pune probleme specifice de utilizare a limbajului, a unor tehnici si
instrumente de dezvoltare a programelor, de comunicare ntre programatori si
chiar de organizare si coordonare a colectivelor de programatori.
Principala metod de stpnire a complexittii programelor mari este
mprtirea lor n module relativ mici, cu functii si interfete bine precizate.
Un program mare este format dintr-un numr oarecare de functii, numr de
ordinul zecilor sau sutelor de functii. Este bine ca aceste functii s fie grupate
n cteva fisiere surs, astfel ca modificri ale programului s se fac prin
editarea si recompilarea unui singur fisier surs (sau a cteva fisiere) si nu a
ntregului program (se evit recompilarea unor functii care nu au suferit
modificri). In plus, este posibil dezvoltarea si testarea n paralel a unor
functii din aplicatie de ctre persoane diferite.
Inainte de a ncepe scrierea de cod este necesar de obicei o etap care
contine de obicei urmtoarele:
- ntelegerea specificatiilor problemei de rezolvat si analiza unor produse
software asemntoare.
- stabilirea functiilor de bibliotec care pot fi folosite si verificarea modului
de utilizare a lor (pe exemple simple).
- determinarea structurii mari a programului: care sunt principalele functii din
componenta programului si care sunt eventualele variabile externe.
Pentru a ilustra o parte din problemele legate de proiectarea si scrierea
programelor mari vom folosi ca exemplu un program care s realizeze efectul
comenzii DIR din MS-DOS (dir si ls din Linux), deci s afiseze numele si
atributele fisierelor dintr-un director dat explicit sau implicit din directorul
curent. O parte din aceste probleme sunt comune mai multor programe
utilitare folosite n mod uzual.
Pentru nceput vom defini specificatiile programului, deci toate datele
initiale (nume de fisiere si optiuni de afisare), eventual dup analiza unor
http://elth.srv.ro/
120
programe existente, cu acelasi rol. Programul va fi folosit n mod linie de
comand si va prelua datele necesare din linia de comand.
O parte din optiunile de afisare au valori implicite; n mod normal se
afiseaz toate fisierele din directorul curent, nu se afiseaz fisierele din
subdirectoare si nu se afiseaz toate atributele fisierelor ci numai cele mai
importante. Exemple de utilizare:
dir
// toate fisierele din directorul curent, cu
atribute
dir c:\work
// toate fisierele din directorul work
dir *.c
// toate fisierele de tip c din directorul
curent
dir a:\pc lab*.txt // fisiere de tip txt din a:\pc
dir /B *.obj // fisiere de tip obj, fara atribute
http://elth.srv.ro/
121
(OBJ) este atasat integral aplicatiei, dar din bibliotec se extrag si se adaug
aplicatiei numai functiile (modulele obiect) apelate de aplicatie.
- Biblioteci cu legare dinamic (numite DLL n sistemul Windows), din care
functiile sunt extrase n faza de executie a programului, ca urmare a apelrii
lor. Astfel de biblioteci, folosite n comun de mai multe aplicatii, nu mresc
lungimea programelor de aplicatie, dar trebuie furnizate mpreun cu aplicatia.
Un alt avantaj este acela c o bibliotec dinamic poate fi actualizat (pentru
efectuarea de corecturi sau din motive de eficient) fr a repeta construirea
aplicatiei care o foloseste (editarea de legturi). In MS-DOS nu se pot folosi
biblioteci cu legare dinamic.
In legtur cu compilarea separat a unor prti din programele mari apar
dou probleme:
- Enumerarea modulelor obiect si bibliotecilor statice componente.
- Descrierea dependentelor dintre diverse fisiere (surse, obiect, executabile)
astfel ca la modificarea unui fisier s se realizeze automat comenzile necesare
pentru actualizarea tuturor fisierelor dependente de cel modificat. Ideea este
de gestiune automat a operatiilor necesare ntretinerii unui program mare, din
care se modific numai anumite prti.
Pentru dezvoltarea de programe C n mod linie de comand solutiile celor
dou probleme sunt:
- Enumerarea fisierelor obiect si bibliotecilor n comanda de linkeditare.
- Utilizarea unui program de tip make si a unor fisiere ce descriu dependente
ntre fisiere si comenzi asociate (makefile).
Atunci cnd se foloseste un mediu integrat pentru dezvoltare (IDE) solutia
comun celor dou probleme o constituie fisierele proiect. Desi au cam
aceleasi functii si suport cam aceleasi operatii, fisierele proiect nu au fost
unificate si au forme diferite pentru medii IDE de la firme diferite sau din
versiuni diferite ale unui IDE de la o aceeasi firm (de ex. Borland C ).
In forma sa cea mai simpl un fisier proiect contine cte o linie pentru
fiecare fisier surs sau obiect sau bibliotec ce trebuie folosit n producerea
unei aplicatii. Exemplu de fisier proiect din Borland C :
input.c
getfiles.c
output.c
dirlist.c
http://elth.srv.ro/
122
- Dac data unui fisier obiect (OBJ) este ulterioar datei unui fisier executabil,
atunci se reface automat operatia de linkeditare, pentru crearea unui nou fisier
executabil.
- Dac data unui fisier surs (C sau CPP) este ulterioar datei unui fisier
obiect, atunci se recompileaz fisierul surs ntr-un nou fisier obiect, ceea ce
va antrena si o nou linkeditare pentru actualizarea programului executabil.
Fisiere antet
Functiile unei aplicatii pot folosi n comun urmtoarele elemente de limbaj:
- tipuri de date definite de utilizatori
- constante simbolice
- variabile externe
Tipurile de date comune se definesc de obicei n fisiere antet (de tip H), care
se includ n compilarea fisierelor surs cu functii (de tip C sau CPP). Tot n
aceste fisiere se definesc constantele simbolice si se declar functiile folosite
n mai multe fisiere din componenta aplicatiei.
Exemplu de fragment dintr-un fisier antet folosit n programul dirlist:
struct file {
char fname[13];
// nume fisier (8+3+.+0)
long fsize;
// dimensiune fisier
char ftime[26] ; // data ultimei modificari
short isdir;
// daca fisier director
};
#define MAXC 256
// dimensiunea unor siruri
#define MAXF 1000 // numar de fisiere estimat
// var comune
http://elth.srv.ro/
123
Domeniul implicit al unei variabile externe este fisierul n care variabila este
definit (mai precis, functiile care urmeaz definitiei). Pentru ca functii din
fisiere surs diferite s se poat referi la o aceeasi variabil, definit ntr-un
singur fisier este necesar declararea variabilei respective cu atributul extern,
n toate fisierele unde se fac referiri la ea. Exemplu :
extern char path[MAXC], mask[MAXC], opt[MAXC];
text
expr
ident
Macrourile pot contine si declaratii, se pot extinde pe mai multe linii si pot
fi utile n reducerea lungimii programelor surs si a efortului de programare.
http://elth.srv.ro/
124
In standardul din 1999 al limbajului C s-a preluat din C++ cuvntul cheie
inline pentru declararea functiilor care vor fi compilate ca macroinstructiuni n
loc de a folosi macrouri definite cu define.
c)- Definirea unor identificatori specifici fiecrui fisier si care vor fi testati cu
directiva ifdef. De exemplu, pentru a evita declaratiile extern n toate fisierele
surs, mai putin fisierul ce contine definitiile variabilelor externe, putem
proceda astfel:
- Se defineste n fisierul surs cu definitiile variabilelor externe un nume
simbolic oarecare:
// fisierul DIRLIST.C
#define MAIN
Directiva include este urmat de obicei de numele unui fisier antet (de tip H
= header), fisier care grupeaz declaratii de tipuri, de constante, de functii si
de variabile, necesare n mai multe fisiere surs (C sau CPP). Fisierele antet nu
ar trebui s contin definitii de variabile sau de functii, pentru c pot apare
erori la includerea multipl a unui fisier antet. Un fisier antet poate include
alte fisiere antet.
Pentru a evita includerea multipl a unui fisier antet (standard sau
nestandard) se recomand ca fiecare fisier antet s nceap cu o secvent de
felul urmtor:
#ifndef HDR
#define HDR
// continut fisier HDR.H ...
#endif
http://elth.srv.ro/
125
Directivele de compilare conditionat de forma if...endif au si ele mai multe
utilizri ce pot fi rezumate la adaptarea codului surs la diferite conditii
specifice, cum ar fi:
- dependenta de modelul de memorie folosit ( n sistemul MS-DOS)
- dependenta de sistemul de operare sub care se foloseste programul (de ex.,
anumite functii sau structuri de date care au forme diferite n sisteme diferite)
- dependenta de fisierul surs n care se afl (de exemplu tcalc.h).
Directivele din grupul if au mai multe forme, iar un bloc if ... endif poate
contine si o directiva elseif.
Proiectul initial
Majoritatea produselor software se preteaz la dezvoltarea lor treptat,
pornind de la o versiune minimal initial, extins treptat cu noi functii. Prima
form, numit si prototip, trebuie s includ partea de interfat cu utilizatorul
final, pentru a putea fi prezentat repede beneficiarilor, care s-si precizeze ct
mai devreme cerintele privind interfata cu operatorii aplicatiei.
Dezvoltarea n etape nseamn ns si definirea progresiv a functiilor din
componenta aplicatiei, fie de sus n jos (top-down), fie de jos n sus
(bottom-up), fie combinat. Abordarea de sus n jos stabileste functiile
importante si programul principal care apeleaz aceste functii. Dup aceea se
defineste fiecare functie, folosind eventual alte functii nc nedefinite, dar care
vor fi scrise ulterior. In varianta initial programul principal arat astfel :
void main(int argc, char * argv[]) {
char *files[MAXF];
// vector cu nume de fisiere
int nf;
// numar de fisiere
getargs (argc,argv);
// preluare date
nf=listFiles(files);
// creare vector de fisiere
printFiles(files,nf); // afisare cu atribute
}
Abordarea de jos n sus porneste cu definirea unor functii mici, care vor fi
apoi apelate n alte functii, s.a.m.d. pn se ajunge la programul principal.
Pentru aflarea fisierelor de un anumit tip dintr-un director dat se pot folosi
functiile nestandard findffirst si findnext, care depind de implementare.
Pentru determinarea atributelor unui fisier cu nume dat se poate folosi functia
stat (file status) sau fstat, declarate n fisierul antet <sys/stat.h> mpreun
cu tipul structur folosit de functie (struct stat). Structura contine
dimensiunea fisierului (st_size), data de creare (st_ctime), data ultimei
modificri si doi octeti cu atributele fisierului (st_mode): fisier normal sau
director, dac poate fi scris (sters) sau nu etc. Anumite atribute depind de
http://elth.srv.ro/
126
sistemul de operare gazd si pot lipsi n alte sisteme, dar functia stat si
structura stat sunt aceleasi pentru diverse implementri. Pentru determinarea
atributelor, fisierul trebuie mai nti deschis. Prototip stat :
int stat (char * filename, struct stat * statptr);
<io.h>
//
<direct.h>
//
<sys/stat.h> // stat
<time.h>
// ctime
<string.h>
http://elth.srv.ro/
127
strcpy(mask,"*.*"); return;
}
p = strrchr(argv[1],'\\'); // ultimul caracter \
if (p==0)
strcpy(mask,argv[1]);
else {
printf("Numai
fisiere
din
acest
director \n");
exit(2);
}
}
Urmtorul modul, si cel mai important, este cel care obtine din sistem
informatiile necesare pentru afisare: lista de fisiere si atributele fiecrui fisier.
Varianta urmtoare este pentru mediul Borland C:
int listFiles ( char* files[]) {
struct ffblk finfo;
int n, err; char full[256];
n=0;
// numar de fisiere gasite
strcpy(full,path); strcat(full,mask);
err= findfirst(full,&finfo,0xff);
while (err >=0 ) {
files[n++]= strdup(finfo.ff_name);
err = findnext(&finfo);
}
return n;
}
http://elth.srv.ro/
128
if ( isdir)
printf("%-12s <DIR>\t\t%s \n", f[i],tim );
else
printf("%-12s %8ld %s \n", f[i],size,tim);
}
printf ("\t%d Files \t %ld bytes \n", nf, tsize);
}
Formatul de afisare este apropiat de cel al comenzii DIR din MS-DOS dar
nu identic, din cauza folosirii functiei ctime si a altor simplificri.
Extinderea programului
Programul nostru poate fi extins treptat, prin adugarea de noi optiuni de
afisare, fr modificri esentiale n versiunile precedente ale programului.
Preluarea optiunilor din linia de comand poate fi relativ simpl dac vom
considera c fiecare optiune este un sir separat, care ncepe cu / (de obicei se
admite gruparea mai multor optiuni ntr-un sir precedat de /). Optiunile pot fi
scrise n orice ordine, nainte si/sau dup numele directorului si masc:
dirlist /B c:\games\*.* /OS
Optiunile comenzii DIR pot avea una sau dou litere, dar numrul de litere
nu conteaz dac fiecare optiune se termin cu spatiu alb.
Rezultatul prelucrrii optiunilor din linia de comand va fi un sir n care
literele ce denumesc fiecare optiune sunt separate ntre ele printr-un caracter /.
void getargs (int argc, char *argv[] ) {
char *p; char f[80];
int i;
opt[0]=0;
if (argc <2){
strcpy(mask,"*.*"); strcpy(path,".\\");
return;
}
for (i=0;i<argc;i++){
strcpy(f,argv[i]);
// numai ptr simplificare cod
if (f[0]=='/') {
// daca optiune
strcat(opt,f); continue;
}
// argument care nu e optiune
p = strrchr(f,'\\');
if (p) {
// daca contine nume director
strncpy(path,f, p-f+1);path[p-f+1]=0;
strcpy(mask,p+1);
}
http://elth.srv.ro/
129
else {
// daca nu contine nume director
strcpy(mask,f); strcpy(path,".\\");
}
}
}
Interpretarea unei optiuni poate fi mai simpl sau mai complicat, functie de
tipul optiunii. Optiunea /B (brief) este cea mai usor de tratat si o vom da ca
exemplu. In ciclul principal din functia printFiles se va insera secventa
urmtoare:
if (strstr(opt,"b")){
// nu se afiseaza numele . si ..
if (strcmp(f[i],".")&& strcmp(f[i],".."))
printf("%-12s \n", f[i]); // doar numele
continue;
// urmatorul fisier din lista
}
http://elth.srv.ro/
130
strcpy(full,path); strcat(full,f);
fp=fopen(full,"r");
stat (full, &fstat);
fat[i].isdir = fstat.st_mode & S_IFDIR;
strcpy(fat[i].ftime, ctime (&fstat.st_ctime));
fat[i].ftime[strlen(fat[i].ftime)-1]=0;
if ( strcmp(f,".")==0 || strcmp(f,"..")==0) {
strcpy(fat[i].fname,f);
continue;
}
fat[i].fsize = fstat.st_size; // dimensiune fisier
strcpy (fat[i].fname, f);
// nume fisier
}
}
http://elth.srv.ro/
131
Imbunttirea programului
Un program corect si complet poate fi perfectionat pentru:
- Reducerea posibilittilor de terminare anormal, fr mesaje explicite.
- Reducerea timpului de executie si a memoriei ocupate.
- Imbunttirea modului de prezentare a rezultatelor.
- Facilitarea unor extinderi sau modificri ulterioare
- Facilitarea reutilizrii unor prti din program n alte aplicatii.
In versiunea final a programului trebuie prevzute toate situatiile n care ar
putea apare erori si mesaje corespunztoare. Nu am verificat dac programul
primeste optiuni care nu au sens pentru el, nu am verificat existenta fisierelor
la deschidere cu fopen sau la apelarea functiei stat. In general, fiecare apel
de functie trebuie urmat imediat de verificarea rezultatului ei. Exemplu:
if ( (fp=fopen(full,"r")) ==NULL){
printf( Eroare la fopen: fisier %s,full);
exit(-1);
}
if (stat (full, &fstat)!= 0)
printf ( Eroare la functia stat: fisier %s,full);
exit (-1);
}
Numrul total de fisiere din directorul curent si din subdirectoare sale poate
fi foarte mare, iar programul trebuie s fac fat oricrui numr.
In program exist si alte limite (la siruri de caractere) iar ncadrarea n aceste
limite trebuie verificat sau se recurge la alocare si realocare dinamic pentru
eliminarea unor limitri arbitrare.
Comparnd cu modul de afisare realizat de comanda DIR programul nostru
necesit mai multe modificri:
- Numrul de octeti ocupat de un fisier si de toate fisierele poate avea multe
cifre iar pentru a fi mai usor de citit trebuie separate grupe de cte 3 cifre prin
virgule. Exemplu: 12,345,678 bytes.
Functia urmtoare transform un numr lung ntr-un astfel de sir:
void format(long x, char * sx) {
http://elth.srv.ro/
132
int r[10],i=0; char aux[4];
*sx=0;
// pregatire strcat(sx,...)
while ( x > 0) {
r[++i]=x%1000; // un numar de max 3 cifre
x=x/1000;
}
while ( i >0){
printf("%d\n",r[i]);
sprintf(aux,"%d",r[i--]);
strcat(sx,aux); strcat(sx,",");
}
sx[strlen(sx)-1]=0; // elimina ultima virgula
}
- Sirul furnizat de functia ctime este greu de citit si contine date inutile (ex.
numele zilei din sptmn), deci mai trebuie prelucrat ntr-o functie.
- In sistemul MS-Windows numele de fisiere nu sunt limitate la 8+3 ca n
MS-DOS si deci va trebui prelucrat pentru reducere la 12 caractere. Programul
NC (Norton Commander) nu retine primele 8 caractere din nume (care pot fi
identice pentru mai multe nume) si formeaz un nume din primele 6 caractere
ale numelui complet, caracterul ~ si o cifr (1,2,3...). Comanda DIR afiseaz
si acest nume prescurtat si numele complet (sau o parte din el).
Functiile findfirst si findnext specifice sistemului MS-DOS fac automat
aceast reducere a numelui, dar alte functii nu o fac si trebuie realizat n
programul de listare.
O parte din functiile programului dirlist pot fi reutilizate si n alte
programe: preluare optiuni si nume fisiere din linia de comand, afisare
numere ntregi foarte mari s.a.
Concluzii
Un program complet pentru comanda DIR este mult mai mare dect schita
de program prezentat anterior, dar este mult mai mic si mai simplu dect alte
programe necesare n practic.
Problemele ridicate de acest program sunt oarecum tipice pentru multe alte
programe reale si permite urmtoarele concluzii:
- Necesitatea stpnirii tuturor aspectelor limbajului folosit : operatii cu siruri
de caractere, cu structuri si vectori de structuri, cu fisiere, alocare dinamic,
transmiterea de date ntre functii, scrierea de functii recursive etc.
- Necesitatea cunoasterii, cel putin la nivel de inventar, a functiilor
disponibile n biblioteci si exersarea lor separat, nainte de a fi folosite ntr-un
program mare.
http://elth.srv.ro/
133
- Dezvoltarea progresiv a programelor, cu teste ct mai complete n fiecare
etap. Este bine s pstrm mereu versiunile corecte anterioare, chiar
incomplete, pentru a putea reveni la ele dac prin extindere se introduc erori
sau se dovedeste c solutia de extindere nu a fost cea mai bun.
- Activitatea de programare necesit mult atentie si concentrare precum si
stpnirea detaliilor, mai ales ntr-un limbaj cum este C. La orice pas trebuie
avute n vedere toate posibilittile existente si tratate.
- Comentarea rolului unor variabile sau instructiuni se va face chiar la
scrierea lor n program si nu ulterior. Numrul acestor comentarii va fi mult
mai mare dect cel din exemplul prezentat, mai ales la fiecare antet de functie.
Aceste comentarii pot facilita adaptarea programului pentru un alt sistem de
operare sau pentru o alt interfat cu utilizatorii programului.
13. Programare generic n C
Structuri de date si algoritmi
Interesul pentru studiul structurilor de date (colectiilor) este determinat de
faptul c la elaborarea unui nou program (la proiectare) se pune att problema
alegerii algoritmilor celor mai performanti ct si problema alegerii structurilor
de date celor mai adecvate. Acest adevr este exprimat si n titlul crtii lui
Niclaus Wirth : Algorithms + Data Structures = Programs.
Evolutia disciplinei Structuri de date si algoritmis-a produs n cteva etape
importante:
- Colectarea si inventarierea structurilor de date folosite n diverse aplicatii.
- Sistematizarea structurilor de date si desprinderea de limbaje si de aplicatii
concrete, prin abstractizare si generalizare.
- Furnizarea de functii (clase) generale, direct utilizabile n aplicatii.
Structura unei colectii si modul de legare a elementelor sunt importante
pentru c determin algoritmii asociati colectiei (modul de realizare a
operatiilor cu o colectie). De exemplu, un algoritm de cutare a unei valori
(sau a unei perechi cu cheie dat) nu depinde de tipul datelor memorate ci de
tipul colectiei: vector, list nlntuit, arbore binar, tabel de dispersie, etc.
Functia de cutare, scris n C, depinde ns si de tipul datelor, care determin
operatia de comparare a datelor.
Procesul de generalizare a structurilor de date si operatiilor asociate a
evoluat n dou directii:
- Colectii de date generice, care pot contine date de orice tip predefinit sau
definit de utilizatori.
- Structuri (tipuri) abstracte de date, care au aceeasi utilizare dar implementri
diferite.
Un exemplu este aplicatia n care se determin frecventa de aparitie a
http://elth.srv.ro/
134
cuvintelor distincte ntr-un text. Crtile mai vechi prezint problema ca o
aplicatie pentru arbori binari de cutare, deoarece sunt necesare cutri
frecvente n lista de cuvinte si mentinerea ei n ordine. Perspectiva modern
asupra acestei probleme este aceea c este necesar un tip abstract de date
numit dictionar (sau asociere), care este o colectie de perechi cheie-valoare
(aici cheia este cuvntul, iar valoarea este numrul de aparitii). Tipul abstract
dictionar (Map) poate fi implementat printr-un tabel de dispersie sau printrun arbore echilibrat de cutare, sau prin alte structuri de date dintre care unele
sunt disponibile sub form de clase predefinite.
Alte tipuri abstracte sunt: multimi, liste generale, stive, cozi, s.a
Colectii de date
O colectie de date (numit si structur de date) grupeaz mai multe
componente, numite si elemente ale colectiei. Componentele unei colectii sunt
fie valori individuale (numere, siruri de caractere, sau alte tipuri de date), fie
perechi cheie-valoare, fie alte colectii sau referinte (pointeri) la date sau la
colectii.
Clasificarea colectiilor de date se face de obicei dup relatiile existente ntre
elemente (dup structura intern a colectiei) si dup operatiile specifice
colectiei, dar nu si dup tipul datelor componente. Astfel avem colectii liniare
(vectori, liste liniare, stive, cozi, tabele de dispersie) si colectii neliniare
(arbori binari, arbori oarecare, grafuri ).
In esent, structurile de date fizice (fundamentale) sunt de dou tipuri mari:
- Structuri de date memorate la adrese consecutive ( Vectori)
- Structuri de date dispersate n memorie, dar legate prin pointeri.
De multe ori se foloseste expresia structuri de date dinamice pentru
colectiile de variabile alocate dinamic si legate prin pointeri : liste cu legturi
si arbori (cu pointeri). De fapt, si un vector alocat dinamic este tot o structur
dinamic, n sensul c alocarea memoriei se face la executie.
Din punct de vedere practic, al programrii, este important si tipul datelor
memorate n fiecare element dintr-un vector sau dintr-o list, sau dintr-un
arbore. Astfel putem avea un vector de numere ntregi, sau un vector de
structuri, sau un vector de pointeri la siruri de caractere sau la tipuri structur.
In principiu exist dou posibilitati pentru implementarea operatiilor cu
colectii de date:
- Utilizatorii s-si scrie singuri operatiile cu liste, arbori, etc. pentru tipurile
de date specifice aplicatiei sale, n general prin adaptarea unor subprograme
existente, publicate n literatura de specialitate sau preluate din alte programe.
- Utilizatorii s foloseasc biblioteci de functii generale pentru operatii cu
http://elth.srv.ro/
135
colectii ce pot contine date de orice tip, cu precizarea tipului la apelarea
functiilor.
http://elth.srv.ro/
136
Pentru exemplificare urmeaz declaratiile pentru trei din aceste functii
(lfind este la fel cu lsearch):
void *bsearch (const void *key, const void *base,
size_t nelem, size_t width,
int (*fcmp)(const void*, const void*));
void *lsearch (const void *key, void *base,
size_t * pnelem, size_t width,
int (*fcmp)(const void *, const void *));
void qsort(void *base, size_t nelem, size_t width,
int (*fcmp)(const void *, const void *));
base este adresa vectorului, key este cheia (valoarea) cutat n vector
(de acelasi tip cu elementele din vector), width este dimensiunea unui
element din vector (ca numr de octeti), nelem este numarul de elemente din
vector, fcmp este adresa functiei de comparare a dou elemente din vector.
Exemplul urmtor arat cum se poate ordona un vector de numere ntregi cu
functia qsort :
// comparare numere intregi
int intcmp (const void * a, const void * b) {
return *(int*)a-*(int*)b;
}
void main () {
int a[]= {5,2,9,7,1,6,3,8,4};
int i, n=9;
//
n=dimensiune
vector
qsort ( a,9, sizeof(int),intcmp);
// ordonare
vector
for (i=0;i<n;i++)
// afisare rezultat
printf("%d ",a[i]);
}
http://elth.srv.ro/
137
int j=0;
while ( j < a.n && x != a.m[j] )
++j;
if ( j==a.n) return 0; // negasit
else return 1;
// gasit
}
int addS ( Set* pa, T x) { // adauga pe x la multimea a
if ( findS (*pa,x) )
return 0;
// nu s-a modificat multimea a
pa->m[pa->n++] = x;
return 1;
//
s-a
modificat
multimea a
}
EQ(a,b) ( strcmp(a,b)==0)
LT(a,b) (strcmp(a,b) < 0)
AT(a,b) ( strcpy(a,b) )
char * T;
// equals
// less than
// assign to
http://elth.srv.ro/
138
b) Utilizarea unor functii de comparatie cu nume predefinite, care vor fi
rescrise n functie de tipul T al elementelor multimii. Exemplu:
typedef char * T;
// comparare la egalitate siruri de caractere
int comp (T a, T b ) {
return strcmp (a,b);
}
int findS ( Set a, T x) { // cauta pe x in multimea a
int j=0;
while ( j < a.n && comp(x,a.m[j]) ==0 )
++j;
if ( j==a.n) return 0;
// negasit
else return 1;
// gasit
}
http://elth.srv.ro/
139
O a doua solutie pentru o colectie generic este o colectie de pointeri la orice
tip (void *), care vor fi nlocuiti cu pointeri la datele folosite n fiecare
aplicatie. Si n acest caz functia de comparare trebuie transmis ca argument
functiilor de inserare sau de cutare n colectie. Avantajul asupra solutiei cu
tip neprecizat T este acela c functiile pentru operatii cu colectii pot fi
compilate si puse ntr-o bibliotec si nu este necesar codul surs. Exemplu de
operatii cu o multime de pointeri:
// Multime ca vector de pointeri
// tipul multime
#define M 100
typedef void* Ptr;
typedef int (*Fcmp) (Ptr,Ptr) ;
typedef struct {
Ptr v[M];
int n;
// nr elem in multime
} * Set;
// afisare date din multime
void printS ( Set a) {
void print ( Ptr); // declara functia apelata
int i;
for(i=0;i<a->n;i++)
print (a->v[i]); // depinde de tipul argumentului
printf ("\n");
}
// cautare in multime
int findS ( Set a, Ptr p, Fcmp comp ) {
int i;
for (i=0;i<a->n;i++)
if (comp(p,a->v[i])==0)
return 1;
return 0;
}
// adaugare la multime
int addS ( Set a, Ptr p, Fcmp comp) {
if ( findS(a,p,comp))
return 0;
// multime nemodificata
a->v[a->n++] = p;
// adaugare la multime
return 1;
// multime modificata
}
// initializare multime
void initS (Set a) {
a->n=0;
}
http://elth.srv.ro/
140
memorie este deci cu mult mai mare dect n cazul unui vector cu date de tip
neprecizat. Exemplu de creare si afisare a unei multimi de ntregi:
// utilizare multime de pointeri
// afisare numar intreg
void print ( Ptr p) {
printf ("%d ", *(int*)p );
}
// comparare de intregi
int intcmp ( void* a, void* b) {
return *(int*)a - *(int*)b;
}
void main () {
// citire numere si creare multime
Set a; int x; int * p;
initS(a);
printf ("Elem. multime: \n");
while ( scanf ("%d", &x) > 0) {
p= (int*) malloc (sizeof(int));
*p=x;
add(a,p,intcmp);
}
printS (a);
}
http://elth.srv.ro/
141
nume dar care difer prin tipul sau numrul argumentelor (dar in C++ este
posibil, prin supradefinirea unor functii).
O solutie C pentru a folosi colectii abstracte este crearea de fisiere antet (de
tip .h) pentru definirea tipurilor folosite (cu declaratii typedef) si crearea de
biblioteci separate pentru diferite implementri.
Functiile urmtoare folosesc o list simplu nlntuit pentru implementarea
unei multimi neordonate de pointeri la date de orice tip.
typedef struct snod { // nnod de lista nlntuita
Ptr pd;
// adresa date
struct snod *next;
} nod,* pnod;
typedef pnod Set;
// Set este un tip pointer !
// cautare in multime
int findS ( Set a, Ptr p, Fcmp comp ) {
while (a)
if (comp(p,a->pd)==0)
return 1;
else
a=a->next;
return 0;
}
// adaugare la multime
int add ( Set *a, Ptr p, Fcmp comp) {
nod * nou;
if ( findS(*a,p,comp))
return 0;
// multime nemodificata
// adaugare la inceput de lista
nou=(nod*) malloc(sizeof(nod));
nou->pd=p; nou->next=*a; // nou devine ultimul nod
*a=nou;
return 1;
// multime modificata
}
void main () {
// citire numere si creare multime
Set a; int x; int * p;
initSet(&a);
printf ("Elem. multime: \n");
while ( scanf ("%d", &x) > 0) {
p= (int*) malloc (sizeof(int));
*p=x;
add(&a,p,intcmp);
printS (a);
}
printS (a);
}
http://elth.srv.ro/
142
In interiorul fiecarei functii se face o conversie de la tipul generic void* la
tipul pointer specific colectiei folosite (vector, list sau altceva). De observat
c o functie care modific un pointer trebuie s primeasc un pointer la pointer
sau nu mai are tipul void. Aceast solutie poate duce ns la functii greu de
scris si de nteles.
Exemplu de operatii cu o stiv vector ce poate contine date de orice tip:
#define MAX 100
typedef struct {
int sp;
void* elem[MAX];
} stiva, * ptrSt;
void initSt (void* ps) {
ptrSt p =(ptrSt)ps;
p->sp=0;
}
int push ( void* ps, void* e) {
ptrSt p =(ptrSt)ps;
if ( p->sp ==MAX) return 0;
// stiva plina
p->elem[p->sp ++] =e;
return 1;
}
// conversie in binar, cu stiva
void binar (int n) {
stiva st; int *pb; void ** pv;
initSt (& st);
while (n > 0) {
pb = (int*) malloc (sizeof(int));
*pb= n % 2 ;
// o cifra binara
push( &st,pb);
// memoreaza rest in stiva
n= n / 2;
}
while (! emptySt(&st)) {
pop( &st, pv);
// scoate din stiva
int * pb = *(int**) pv;
printf ("%d",* pb);
// si afiseaza
}
}
http://elth.srv.ro/
143
http://elth.srv.ro/
144
const int NMAX=1000;
// fr "static" n C++
void main () {
int n, x[NMAX], y[NMAX];
. . .
Diferente la functii
In C++ toate functiile folosite trebuie declarate si nu se mai consider c o
functie nedeclarat este implicit de tipul int. Dar o functie definit fr un tip
explicit este considerat ca fiind de tip int. Asa se explic de ce functia main
este deseori declarat ca fiind de tip void; absenta cuvntului void implic
http://elth.srv.ro/
145
tipul int pentru functia main si compilatorul verific existenta unei instructiuni
return cu expresie de tip ntreg.
In C++ se pot declara valori implicite pentru parametri formali de la
sfrsitul listei de parametri; aceste valori sunt folosite automat n absenta
parametrilor efectivi corespunztori la un apel de functie. O astfel de functie
poate fi apelat deci cu un numr variabil de parametri. Exemplu:
// afisare vector, precedata de un titlu
void printv ( int v[], int n, char * titlu="") {
// afiseaza sirul primit sau sirul nul
printf ("\n %s \n", titlu);
for (int i=0; i<n; i++)
printf ("%d ", v[i]);
}
...
// exemple de apeluri
printv ( x,nx );
// cu 2 parametri
printv (a,na," multimea A este");
// cu 3 parametri
In C++ pot exista mai multe functii cu acelasi nume dar cu parametri diferiti
(ca tip sau ca numr). Se spune c un nume este "suprancrcat" cu
semnificatii ("function overloading").
Compilatorul poate stabili care din functiile cu acelasi nume a fost apelat
ntr-un loc analiznd lista de parametri si tipul functiei. Exemple:
float abs (float f) { return fabs(f); }
long abs (long x) { return labs(x); }
printf ("%6d%12ld
%f \n", abs(-2),abs(-2L),abs(-2.5)
);
http://elth.srv.ro/
146
// vector de n intregi
http://elth.srv.ro/
147
void print();
...
};
// exemple de utilizare in "main"
end=1;
// sau
A::end=0;
f.seekg (0, ios::end);
// din clasa predefinita
"ios"
...
Tipuri referint
In C++ s-au introdus tipuri referint, folosite n primul rnd pentru parametri
modificabili sau de dimensiuni mari. Si functiile care au ca rezultat un obiect
mare pot fi declarate de un tip referint, pentru a obtine un cod mai
performant. Caracterul ampersand (&) folosit dup tipul si naintea numelui
unui parametru formal (sau unei functii) arat compilatorului c pentru acel
parametru se primeste adresa si nu valoarea argumentului efectiv. Exemplu:
// schimba intre ele doua valori
void schimb (int & x, int & y) {
int t = x;
x = y; y = t;
}
// ordonare vector
void sort ( int a[], int n ) {
...
if ( a[i] > a[i+1])
schimb ( a[i], a[i+1]);
...
}
http://elth.srv.ro/
148
Referintele simplific utilizarea unor parametri modificabili de tip pointer,
eliminnd necesitatea unui pointer la pointer. Exemplu:
void initL (nod* & cap) {
cap=new nod;
nod->leg=NULL:
}
// initializare lista
// cap este un pointer
Fluxuri de intrare-iesire
In C++ s-a introdus o alt posibilitate de exprimare a operatiilor de citirescriere, pe lng functiile standard de intrare-iesire din limbajul C. In acest
scop se folosesc cteva clase predefinite pentru "fluxuri de I/E" (declarate n
fisierele antet <iostream.h> si <fstream.h> ).
Un flux de date ("stream") este un obiect care contine datele si metodele
necesare operatiilor cu acel flux. Pentru operatii de I/E la consol sunt definite
variabile de tip flux, numite "cin" (console input), "cout" (console output).
http://elth.srv.ro/
149
Operatiile de citire sau scriere cu un flux pot fi exprimate prin metode ale
claselor flux sau prin doi operatori cu rol de extractor din flux (>>) sau
insertor n flux (<<). Atunci cnd primul operand este de un tip flux,
interpretarea acestor operatori nu mai este cea de deplasare binar ci este
extragerea de date din flux (>>) sau introducerea de date n flux (<<).
Operatorii << si >> implic o conversie automat a datelor ntre forma
intern (binar) si forma extern (sir de caractere). Formatul de conversie
poate fi controlat prin cuvinte cheie cu rol de "modificator". Exemplu de
scriere si citire cu format implicit:
#include <iostream.h>
void main ( ) {
int n; float f; char
cout << " n= "; cin
cout << " f= "; cin
cout << " un sir: ";
}
s[20];
>> n;
>> f;
cin >> s;
Intr-o expresie ce contine operatorul << primul operand trebuie s fie "cout"
(sau o alt variabil de un tip "ostream"), iar al doilea operand poate s fie de
orice tip aritmetic sau de tip "char*" pentru afisarea sirurilor de caractere.
Rezultatul expresiei fiind de tipul primului operand, este posibil o expresie cu
mai multi operanzi (ca la atribuirea multipl). Exemplu:
cout << "x= " << x << "\n";
Tipuri clas
Tipurile clas reprezint o extindere a tipurilor structur si pot include ca
membri variabile si functii. Pentru definirea unei clase se poate folosi unul din
http://elth.srv.ro/
150
cuvintele cheie class, struct sau union, cu urmtoarele efecte asupra atributelor
de accesibilitate ale membrilor clasei:
- O clas definit prin class are implicit toti membri invizibili din afara clasei
(de tip private).
- O clasa definit prin struct sau union are implicit toti membri publici,
vizibili din afara clasei.
In practic avem nevoie ca datele clasei s fie ascunse (locale) si ca functiile
clasei s poat fi apelate de oriunde (publice). Pentru a stabili selectiv nivelul
de acces se folosesc cuvintele cheie public, private si protected, ca etichete de
sectiuni cu aceste atribute, n cadrul unei clase.
In mod uzual, o clas are dou sectiuni: sectiunea de date (private) si
sectiunea de metode (public).
Functiile unei clase, numite si metode ale clasei, pot fi definite complet n
cadrul definitiei clasei sau pot fi numai declarate n clas si definite n afara ei
Exemplul urmtor contine o variant de definire a unei clase pentru un vector
extensibil de numere ntregi:
class intArray {
// clasa vector de intregi
// date clasei (private)
int * arr;
// adresa vector (alocat dinamic)
int d,dmax,inc; // dimensiune curenta si maxima
void extend();
// implicit private, definita
ulterior
public:
intArray (int max=10, int incr=10){ // constructor
dmax=max; inc=incr; d=0;
arr= new int[dmax];
}
~intArray () { delete [] arr;}
// destructor
int get (int i)
{ assert (i >= 0 && i < dmax);
return arr[i];
}
void add (int elem) {
if ( d==dmax)
extend();
arr[d++]=elem;
}
int size() { return d; } // dimensiune curenta vector
};
// extindere vector
void intArray::extend () {
int * oldarr=arr;
dmax+=inc;
arr = new int[dmax];
for (int i=0;i<d;i++)
arr[i]= oldarr[i];
delete [] oldarr;
http://elth.srv.ro/
151
}
Pentru clasele folosite n mai multe aplicatii, cum este clasa intArray, se
recomand ca toate functiile clasei s fie definite n afara clasei, ntr-un fisier
surs separat; eventual se compileaz si se introduc ntr-o bibliotec. Definitia
clasei se pune ntr-un fisier antet separat, care va fi inclus de toate fisierele
surs ce folosesc tipul respectiv. In acest fel este separat descrierea clasei de
implementarea clasei si de utilizrile clasei n diverse aplicatii. Exemplu:
// fisier INTARRAY.H
class intArray {
private:
int * arr;
// adresa vector (alocat dinamic)
int d,dmax,inc; // dimensiune curenta si maxima
void extend();
// implicit private, definita
ulterior
public:
intArray (int max=10, int incr=10); // constructor
~intArray ();
// destructor
int get (int ) ;
// extrage element
void add (int );
// adauga element
int size();
// dimensiune vector
};
// fisier INTARRAY.CPP
#include intArray.h
intArray::intArray (int max=10, int incr=10){
dmax=max; inc=incr; d=0;
arr= new int[dmax];
}
intArray::~intArray () { delete [] arr;}
intArray::int get (int i)
{ assert (i >= 0 && i < dmax);
return arr[i];
}
void intArray::add (int elem) {
if ( d==dmax)
extend();
arr[d++]=elem;
}
int intArray::size() {
return d;
}
// fisier care foloseste clasa intArray : TEST.CPP
#include intArray.h
#include <iostream.h>
void main () {
intArray a(3,1); // iniial 3 elemente, increment 1
for (int i=1;i<=10;i++)
a.add(i);
http://elth.srv.ro/
152
for (i=0;i< a.size();i++)
cout << a.get(i) << ' ';
cout << endl;
}
Orice clas are (cel putin) o functie constructor (public) apelat implicit la
definirea de variabile de tipul clasei; un constructor aloc memorie si
initializeaz variabilele clasei. Functiile constructor au toate numele clasei si
pot diferi prin lista de argumente. O functie constructor nu are tip.
O functie destructor este necesar numai pentru clase cu date alocate
dinamic (n constructor).
Sintaxa pentru apelul unei metode (nestatice) extinde referirea la membri
unei structuri si se interpreteaz ca apel de functie pentru un obiect dat prin
numele su.
Supradefinirea operatorilor
Pentru variabilele de un tip clas (structur) se pot folosi numai doi
operatori, fr a mai fi definiti. Acestia sunt operatorul de atribuire ('=') si
operatorul de obtinere a adresei variabilei ('&'). La atribuirea ntre variabile de
un tip clas se copiaz numai datele clasei .
Alte operatii cu obiecte se definesc prin functii si/sau operatori specifici
clasei respective.
Operatorii limbajului C pot fi supradefiniti, adic pot fi asociati si cu alte
operatii aplicate unor variabile de tip clas. Aceast facilitate este util n
cazul claselor de definesc noi tipuri de date.
Un operator este considerat n C++ ca o functie cu un nume special, dar
supus tuturor regulilor referitoare la functii. Numele unei functii operator
const din cuvntul operator urmat de unul sau dou caractere speciale, prin
care se foloseste operatorul. In exemplul urmtor se defineste o clas pentru
siruri de caractere, cu un operator de concatenare siruri (+).
class string {
char * start; // adresa sir terminat cu zero
public:
string ( char * s); // un constructor
string () { start=new char[80]; *start='\0';}
~string() {delete start; }
string& operator + (string& sir);
void show (void) { cout << start << '\n';}
};
// functii ale clasei 'string'
string::string ( char * s) {
int lung= strlen(s);
http://elth.srv.ro/
153
start=new char[lung+1];
strcpy (start,s);
}
string& string::operator + (string& str) {
int lung=strlen(start)+strlen(str.start);
char * nou=new char[lung+1];
strcpy (nou,start);
strcat (nou,str.start);
delete start; start=nou;
return * this;
}
// teste
main () {
string s1 ("zori "), s2 ("de "), s ;
s= s1+s2; s.show();
// concatenare siruri
string s3 ("zi");
s= s1+s2+s3; s.show();
}
http://elth.srv.ro/