Programare C

286
Theory without practice is useless; practice without theory is blindRoger Bacon Limbajul C a fost creat la începutul anilor '70 de către Brian W Kernigham şi Dennis M Ritchie de la Bell Laboratories New Jersey, fiind iniţial destinat scrierii unei părţi din sistemul de operare Unix. Lucrarea „The C Programming Language” a celor doi autori, apărută în mai multe versiuni, a rămas cartea de referinţă în domeniu, impunînd un standard minimal pentru orice implementare. Caracteristicile distinctive ale limbajului au fost clar definite de la început, ele păstrîndu-se în toate dezvoltările ulterioare: – portabilitate maximă; – structurare; – posibilitatea efectuării operaţiilor la nivelul maşinii cu păstrarea caracteristicilor unui limbaj evoluat. Acest manual este structurat pe 12 capitole astfel încît elementele limbajului C să fie prezentate într-o manieră unitară. Primul capitol face o scurtă introducere şi prezintă patru programe C. Următoarele nouă capitole descriu elementele limbajului C. Capitolele unsprezece şi doisprezece trec în revistă funcţiile cele mai des utilizate definite în biblioteca standard, împreună cu cîteva programe demonstrative. Au fost selectate doar funcţiile definite de mai multe standarde (în ________________________________________________________________ __________ 3

Transcript of Programare C

Theory without practice is useless; practice without theory is blind

Roger Bacon

Limbajul C a fost creat la nceputul anilor '70 de ctre Brian W Kernigham i Dennis M Ritchie de la Bell Laboratories New Jersey, fiind iniial destinat scrierii unei pri din sistemul de operare Unix. Lucrarea The C Programming Language a celor doi autori, aprut n mai multe versiuni, a rmas cartea de referin n domeniu, impunnd un standard minimal pentru orice implementare.

Caracteristicile distinctive ale limbajului au fost clar definite de la nceput, ele pstrndu-se n toate dezvoltrile ulterioare:

portabilitate maxim;

structurare;

posibilitatea efecturii operaiilor la nivelul mainii cu pstrarea caracteristicilor unui limbaj evoluat.

Acest manual este structurat pe 12 capitole astfel nct elementele limbajului C s fie prezentate ntr-o manier unitar. Primul capitol face o scurt introducere i prezint patru programe C. Urmtoarele nou capitole descriu elementele limbajului C. Capitolele unsprezece i doisprezece trec n revist funciile cele mai des utilizate definite n biblioteca standard, mpreun cu cteva programe demonstrative. Au fost selectate doar funciile definite de mai multe standarde (n primul rnd ANSI C), pentru a garanta o portabilitate ct mai mare.

Acest manual a fost conceput pentru a servi ca document care s poat fi consultat de programatori n elaborarea proiectelor, i nu pentru a fi memorat. Manualul nu este o introducere n limbajul C; se presupune c cititorul este familiarizat cu:

concepte de baz referitoare la programare: variabile, instruciuni de atribuire, de control al execuiei, apeluri de funcii;

reprezentarea informaiei n calculator a valorilor ntregi, n virgul mobil, a codurilor ASCII;

operaii de intrare / ieire.

Deoarece avem convingerea c cea mai bun explicaie este un program funcional, majoritatea exemplelor din acest manual se regsesc n fiiere surs C care pot fi rulate pe orice mediu de programare C i sub orice sistem de operare.

Ca o ultim observaie amintim recomandarea fcut de nii creatorii limbajului: cea mai bun metod de nvare este practica.

1. limbajului C

1.1. Introducere

Limbajul C este un limbaj de programare universal, caracterizat printr-o exprimare concis, un control modern al fluxului execuiei, structuri de date, i un bogat set de operatori.

Limbajul C nu este un limbaj de nivel foarte nalt i nu este specializat pentru un anumit domeniu de aplicaii. Absena restriciilor i generalitatea sa l fac un limbaj mai convenabil i mai eficient dect multe alte limbaje mai puternice.

Limbajul C permite scrierea de programe bine structurate, datorit construciilor sale de control al fluxului: grupri de instruciuni, luri de decizii (if), cicluri cu testul de terminare naintea ciclului (while, for) sau dup ciclu (do) i selecia unui caz dintr-o mulime de cazuri (switch).

Limbajul C permite lucrul cu pointeri i are o aritmetic de adrese puternic.

Limbajul C nu are operaii care prelucreaz direct obiectele compuse cum snt irurile de caractere, mulimile, listele sau masivele, considerate fiecare ca o entitate. Limbajul C nu prezint faciliti de alocare a memoriei altele dect definiia static sau disciplina de stiv relativ la variabilele locale ale funciilor. n sfrit, limbajul C nu are faciliti de intrare-ieire i nici metode directe de acces la fiiere. Toate aceste mecanisme de nivel nalt snt realizate prin funcii explicite.

Dei limbajul C este, aadar, un limbaj de nivel relativ sczut, el este un limbaj agreabil, expresiv i elastic, care se preteaz la o gam larg de programe. C este un limbaj restrns i se nva relativ uor, iar subtilitile se rein pe msur ce experiena n programare crete.

1.2. Primele programe

n aceast seciune snt prezentate i explicate patru programe cu scopul de a asigura un suport de baz pentru prezentrile din capitolele urmtoare.

Prin tradiie primul program C este un mic exemplu din lucrarea devenit clasic The C programming language, de Brian W Kernigham i Dennis M Ritchie.

#include

main() {

printf("Hello, world\n");

return 0;

}

Acest program afieaz un mesaj de salut.

Prima linie indic faptul c se folosesc funcii de intrare / ieire, i descrierea modului de utilizare (numele, tipul argumentelor, tipul valorii returnate etc) a acestora se afl n fiierul cu numele stdio.h .

A doua linie declar funcia main care va conine instruciunile programului. n acest caz singura instruciune este un apel al funciei printf care afieaz un mesaj la terminal. Mesajul este dat ntre ghilimele i se termin cu un caracter special new-line ('\n').

Instruciunea return pred controlul sistemului de operare la terminarea programului i comunic acestuia codul 0 pentru terminare. Prin convenie aceast valoare semnific terminarea normal a programului - adic nu au aprut erori n prelucrarea datelor.

Corpul funciei main apare ntre acolade.

Al doilea program ateapt de la terminal introducerea unor numere ntregi nenule i determin suma lor. n momentul n care se introduce o valoare zero, programul afieaz suma calculat.

#include

main() {

int s,n;

s = 0;

do {

scanf("%d",&n);

s += n;

} while (n!=0);

printf("%d\n",s);

return 0;

}

n cadrul funciei main se declar dou variabile s i n care vor memora valori ntregi. Variabila s (care va pstra suma numerelor introduse) este iniializat cu valoarea 0.

n continuare se repet o secven de dou instruciuni, prima fiind o operaie de intrare i a doua o adunare.

Primul argument al funciei scanf - formatul de introducere "%d" - indic faptul c se ateapt introducerea unei valori ntregi n format zecimal de la terminal (consol). Al doilea argument indic unde se va depune n memorie valoarea citit; de aceea este necesar s se precizeze adresa variabilei n (cu ajutorul operatorului &).

n a doua instruciune la valoarea variabilei s se adun valoarea variabilei n. Operatorul += are semnificaia adun la.

Aceast secven se repet (do) ct timp (while) valoarea introdus (n) este nenul. Operatorul != are semnificaia diferit de.

n final funcia printf afieaz pe terminal valoarea variabilei s n format zecimal.

Al treilea program ateapt de la terminal introducerea unei valori naturale n, dup care mai ateapt introducerea a n valori reale (dubl precizie): a0, a1, ..., an(1. n continuare se parcurge aceast list i se determin produsul valorilor strict pozitive. n final programul afieaz produsul calculat.

#include

main() {

int n,i;

double a[100], p;

scanf("%d",&n);

for (i=0; i expresie

Operatorul > produce deplasarea la dreapta a operandului din stnga cu un numr de poziii binare dat de operandul din dreapta.

n ambele cazuri se execut conversiile aritmetice obinuite asupra operanzilor, fiecare dintre ei trebuind s fie de tip ntreg. Operandul din dreapta este convertit la int; tipul rezultatului este cel al operandului din stnga. Rezultatul este nedefinit dac operandul din dreapta este negativ sau mai mare sau egal cu lungimea obiectului, n bii. Astfel valoarea expresiei E1E2 este E1 deplasat la dreapta cu E2 poziii binare. Deplasarea la dreapta este logic (biii eliberai devin 0) dac E1 este de tip unsigned; altfel ea este aritmetic (biii eliberai devin copii ale bitului semn).

Exemplu: x (mai mare), = (mai mare sau egal) produc valoarea 0 dac relaia specificat este fals i 1 dac ea este adevrat.

Tipul rezultatului este int. Se execut conversiile aritmetice obinuite. Aceti operatori au precedena mai mic dect operatorii aritmetici, astfel c expresia i=stnga la dreapta

== !=stnga la dreapta

&stnga la dreapta

^stnga la dreapta

|stnga la dreapta

&&stnga la dreapta

||stnga la dreapta

?:dreapta la stnga

= op=dreapta la stnga

,stnga la dreapta

5. Declaraii

Declaraiile se folosesc pentru a specifica interpretarea pe care compilatorul trebuie s o dea fiecrui identificator.

Declaraie:

specificator-declaraie lista-declarator(opt(;

Specificator-declaraie:

specificator-tipspecificator-declaraie(opt(

specificator-clas-memoriespecificator-declaraie(opt(

Specificator-tip:

char

short

int

long

unsigned

float

double

void

specificator-structur-sau-reuniune

typedef-nume

Specificator-clas-memorie:

auto

static

extern

register

const

typedef

Lista-declarator:

declarator-cu-iniializare

declarator-cu-iniializare, lista-declarator

Declarator-cu-iniializare:

declarator iniializator(opt(

Declarator:

identificator

(declarator)

* declarator

declarator ()

declarator [expresie-constant(opt(]

Declaraiile listeaz toate variabilele care urmeaz a fi folosite ntr-un program. O declaraie specific tipul, clasa de memorie i eventual valorile iniiale pentru una sau mai multe variabile de acelai tip. Declaraiile se fac sau n afara oricrei funcii sau la nceputul unei funcii naintea oricrei instruciuni.

Nu orice declaraie rezerv i memorie pentru un anumit identificator, de aceea deosebim:

( declaraia de definiie a unei variabile, care se refer la locul unde este creat variabila i unde i se aloc memorie;

( declaraia de utilizare a unei variabile, care se refer la locul unde numele variabilei este declarat pentru a anuna proprietile variabilei care urmeaz a fi folosit.

5.1. Specificatori de clas de memorie

Specificatorii de clas de memorie snt:

auto

static

extern

register

const

typedef

Specificatorul typedef nu rezerv memorie i este denumit specificator de clas de memorie numai din motive sintactice; el va fi discutat n capitolul 10.

Semnificaia diferitelor clase de memorie a fost discutat deja n paragraful 3.1.

Declaraiile cu specificatorii auto, static i register determin i rezervarea unei zone de memorie corespunztoare. Declaraia cu specificatorul extern presupune o definiie extern pentru identificatorii dai, undeva n afara funciei sau fiierului n care ei snt declarai.

ntr-o declaraie poate s apar cel mult un specificator de clas de memorie. Dac specificatorul de clas lipsete din declaraie, el se consider implicit auto n interiorul unei funcii i definiie extern n afara funciei. Excepie fac funciile care nu snt niciodat automatice. De exemplu liniile:

int sp;

double val[MAXVAL];

care apar ntr-un program n afara oricrei funcii, definesc variabilele externe sp de tip int i val de tip masiv de double. Ele determin alocarea memoriei i servesc de asemenea ca declaraii ale acestor variabile n tot restul fiierului surs. Pe de alt parte liniile:

extern int sp;

extern double val[];

declar pentru restul fiierului surs c variabilele sp i val snt externe, sp este de tip int i val este un masiv de double i c ele au fost definite n alt parte, unde li s-a alocat i memorie. Deci aceste declaraii nu creeaz aceste variabile i nici nu le aloc memorie.

5.2. Specificatori de tip

Specificatorii de tip snt:

char

short

int

long

unsigned

float

doublevoid

specificator-structur-sau-reuniune

typedef-nume

Cuvintele long, short i unsigned pot fi considerate i ca adjective; urmtoarele combinaii snt acceptate:

short int

long int

unsigned int

unsigned long int

long double

ntr-o declaraie se admite cel mult un specificator de tip, cu excepia combinaiilor amintite mai sus. Dac specificatorul de tip lipsete din declaraie, el se consider implicit int.

Specificatorii de structuri i reuniuni snt prezentai n seciunea 10.9, iar declaraiile cu typedef n seciunea 10.10.

5.3. Declaratori

Lista-declarator care apare ntr-o declaraie este o succesiune de declaratori separai prin virgule, fiecare dintre ei putnd avea un iniializator.

Declaratorii din lista-declarator snt identificatorii care trebuie declarai.

Fiecare declarator este considerat ca o afirmaie care, atunci cnd apare o construcie de aceeai form cu declaratorul, produce un obiect de tipul i de clasa de memorie indicat. Fiecare declarator conine un singur identificator. Gruparea declaratorilor este la fel ca i la expresii.

Dac declaratorul este un identificator simplu, atunci el are tipul indicat de specificatorul din declaraie.

Un declarator ntre paranteze este tot un declarator, dar legtura declaratorilor compleci poate fi alterat de paranteze.

S considerm acum o declaraie de forma:

T D1unde T este un specificator de tip (ca de exemplu int) i D1 un declarator. S presupunem c aceast declaraie face ca identificatorul s aib tipul ...T unde ... este vid dac D1 este un identificator simplu (aa cum tipul lui x n int x este int). Dac D1 are forma:

*Datunci tipul identificatorului pe care-l conine acest declarator este pointer la T.

Dac D1 are forma:

D()atunci identificatorul pe care-l conine are tipul funcie care returneaz T.

Dac D1 are forma:

D[expresie-constant] sau D[]atunci identificatorul pe care-l conine are tipul masiv de T.

n primul caz expresia constant este o expresie a crei valoare este determinabil la compilare i al crei tip este int. Cnd mai muli identificatori masiv de T snt adiaceni, se creeaz un masiv multidimensional; expresiile constante care specific marginile masivelor pot lipsi numai pentru primul membru din secven. Aceast omisiune este util cnd masivul este extern i definiia real care aloc memoria este n alt parte (vezi seciunea 5.1). Prima expresie constant poate lipsi de asemenea cnd declaratorul este urmat de iniializare. n acest caz dimensiunea este calculat la compilare din numrul elementelor iniiale furnizate.

Un masiv poate fi construit din obiecte de unul dintre tipurile de baz, din pointeri, din reuniuni sau structuri, sau din alte masive (pentru a genera un masiv multidimensional).

Nu toate posibilitile admise de sintaxa de mai sus snt permise. Restriciile snt urmtoarele: funciile nu pot returna masive, structuri, reuniuni sau funcii, dei ele pot returna pointeri la astfel de obiecte; nu exist masive de funcii, dar pot fi masive de pointeri la funcii. De asemenea, o structur sau reuniune nu poate conine o funcie, dar ea poate conine un pointer la funcie. De exemplu, declaraia

int i, *ip, f(), *fip(), (*pfi)();

declar un ntreg i, un pointer ip la un ntreg, o funcie f care returneaz un ntreg, o funcie fip care returneaz un pointer la un ntreg, un pointer pfi la o funcie care returneaz un ntreg. Prezint interes compararea ultimilor doi declaratori.

Construcia *fip() este *(fip()), astfel c declaraia sugereaz apelul funciei fip i apoi utiliznd indirectarea prin intermediul pointerului se obine un ntreg.

n declaratorul (*pfi)(), parantezele externe snt necesare pentru arta c indirectarea printr-un pointer la o funcie furnizeaz o funcie, care este apoi apelat; ea returneaz un ntreg.

Declaraiile de variabile pot fi explicite sau implicite prin context. De exemplu declaraiile:

int a,b,c;

char d, m[100];

specific un tip i o list de variabile. Aici clasa de memorie nu este declarat explicit, ea se deduce din context. Dac declaraia este fcut n afara oricrei funcii atunci clasa de memorie este extern; dac declaraia este fcut n interiorul unei funcii atunci implicit clasa de memorie este auto.

Variabilele pot fi distribuite n declaraii n orice mod; astfel listele le mai sus pot fi scrise i sub forma:

int a;

int b;

int c;

char d;

char m[100];

Aceasta ultim form ocup mai mult spaiu dar este mai convenabil pentru adugarea unui comentariu pentru fiecare declaraie sau pentru modificri ulterioare.

5.4. Modificatorul const

Valoarea unei variabile declarate cu acest modificator nu poate fi modificat.

Sintaxa:

const nume-variabil = valoare ;

nume-funcie (..., const tip *nume-variabil, ...)

n prima variant, modificatorul atribuie o valoare iniial unei variabile care nu mai poate fi ulterior modificat de program. De exemplu,

const int virsta = 39;

Orice atribuire pentru variabila virsta va genera o eroare de compilare.

Atenie! O variabil declarat cu const poate fi indirect modificat prin intermediul unui pointer:

int *p = &virsta;

*p = 35;

n a doua variant modificatorul const este folosit mpreun cu un parametru pointer ntr-o list de parametri ai unei funcii. Funcia nu poate modifica variabila pe care o indic pointerul:

int printf (const char *format, ...);

5.5. Iniializare

Un declarator poate specifica o valoare iniial pentru identificatorul care se declar. Iniializatorul este precedat de semnul = i const dintr-o expresie sau o list de valori incluse n acolade.

Iniializator:

expresie

{list-iniializare}

List-iniializare:

expresie

list-iniializare, list-iniializare

{list-iniializare}

Toate expresiile dintr-un iniializator pentru variabile statice sau externe trebuie s fie expresii constante (vezi seciunea 3.4) sau expresii care se reduc la adresa unei variabile declarate anterior, posibil offset-ul unei expresii constante. Variabilele de clas auto sau register pot fi iniializate cu expresii oarecare, nu neaprat expresii constante, care implic constante sau variabile declarate anterior sau chiar funcii.

n absena iniializrii explicite, variabilele statice i externe snt iniializate implicit cu valoarea 0. Variabilele auto i register au valori iniiale nedefinite (reziduale).

Pentru variabilele statice i externe, iniializarea se face o singur dat, n principiu nainte ca programul s nceap s se execute.

Pentru variabilele auto i register, iniializarea este fcut la fiecare intrare n funcie sau bloc.

Dac un iniializator se aplic unui scalar (un pointer sau un obiect de tip aritmetic) el const dintr-o singur expresie, eventual n acolade. Valoarea iniial a obiectului este luat din expresie; se efectueaz aceleai operaii ca n cazul atribuirii.

Pentru iniializarea masivelor i masivelor de pointeri vezi seciunea 9.8. Pentru iniializarea structurilor vezi seciunea 10.3.

Dac masivul sau structura conine sub-masive sau sub-structuri regula de iniializare se aplic recursiv la membrii masivului sau structuri.

5.6. Nume-tip

n cele expuse mai sus furnizarea unui nume-tip a fost necesar n dou contexte:

( pentru a specifica conversii explicite de tip prin intermediul unui cast (vezi seciunea 3.4);

( ca argument al lui sizeof (vezi seciunea 4.2).

Un nume-tip este n esen o declaraie pentru un obiect de acest tip, dar care omite numele obiectului.

Nume-tip:

specificator-tipdeclarator-abstract

Declarator-abstract:

vid

(declarator-abstract)

*declarator-abstract

declarator-abstract()

declarator-abstract[expresie-constant(opt(]

Pentru a evita ambiguitatea, n construcia:

(declarator-abstract)declaratorul abstract se presupune a nu fi vid. Cu aceast restricie, este posibil s identificm n mod unic locul ntr-un declarator-abstract, unde ar putea aprea un identificator, dac aceast construcie a fost un declarator ntr-o declaraie. Atunci tipul denumit este acelai ca i tipul identificatorului ipotetic. De exemplu:

int

int*

int *[3]

int(*)[3]

int *( )

int(*)()

denumete respectiv tipurile int, pointer la ntreg, masiv de 3 pointeri la ntregi, pointer la un masiv de 3 ntregi, funcie care returneaz pointer la ntreg i pointer la o funcie care returneaz ntreg.

6. Instruciuni

ntr-un program scris n limbajul C instruciunile se execut secvenial, n afar de cazul n care se indic altfel.

Instruciunile pot fi scrise cte una pe o linie pentru o lizibilitate mai bun, dar nu este obligatoriu.

6.1. Instruciunea expresie

Cele mai multe instruciuni snt instruciuni expresie. O expresie devine instruciune dac ea este urmat de punct i virgul.

Format:

expresie;

De obicei instruciunile expresie snt atribuiri sau apeluri de funcie; de exemplu:

x = 0;

printf(...);

n limbajul C punct i virgula este un terminator de instruciune i este obligatoriu.

6.2. Instruciunea compus sau blocul

Instruciunea compus este o grupare de declaraii i instruciuni nchise ntre acolade. Ele au fost introduse cu scopul de a folosi mai multe instruciuni acolo unde sintaxa cere o instruciune. Instruciunea compus sau blocul snt echivalente sintactic cu o singur instruciune.

Format:

Instruciune-compus:

{ list-declaratori(opt( list-instruciuni(opt( }

List-declaratori:

declaraie

declaraie list-declaratori

List-instruciuni:

instruciune

instruciune list-instruciuni

Dac anumii identificatori din lista-declaratori au fost declarai anterior, atunci declaraia exterioar este salvat pe durata blocului, dup care i reia sensul su.

Orice iniializare pentru variabile auto i register se efectueaz la fiecare intrare n bloc. Iniializrile pentru variabilele static se execut numai o singur dat cnd programul ncepe s se execute.

Un bloc se termin cu o acolad dreapt care nu este urmat niciodat de punct i virgul.

6.3. Instruciunea condiional

Sintaxa instruciunii condiionale admite dou formate:

if (expresie)

instruciune-1

if (expresie)

instruciune-1else instruciune-2

Instruciunea condiional se folosete pentru a lua decizii. n ambele cazuri se evalueaz expresia i dac ea este adevrat (deci diferit de zero) se execut instruciune-1. Dac expresia este fals (are valoarea zero) i instruciunea if are i parte de else atunci se execut instruciune-2.

Una i numai una dintre cele dou instruciuni se execut. Deoarece un if testeaz pur i simplu valoarea numeric a unei expresii, se admite o prescurtare i anume:

if (expresie)n loc de:

if (expresie != 0)

Deoarece partea else a unei instruciuni if este opional, exist o ambiguitate cnd un else este omis dintr-o secven de if imbricat. Aceasta se rezolv asociind else cu ultimul if care nu are else.

Exemplu:

if (n>0)

if (a>b)

z = a;

else

z = b;

Partea else aparine if-ului din interior. Dac nu dorim acest lucru atunci folosim acoladele pentru a fora asocierea:

if (n>0) {

if (a>b)

z = a;

}

else

z = b;

Instruciunea condiional admite i construcia else-if de forma:

if (expresie-1)

instruciune-1

else if (expresie-2)

instruciune-2

else if (expresie-3)

instruciune-3

else

instruciune-4

Aceast secven de if se folosete frecvent n programe, ca mod de a exprima o decizie multipl.

Expresiile se evalueaz n ordinea n care apar; dac se ntlnete o expresie adevrat, atunci se execut instruciunea asociat cu ea i astfel se termin ntregul lan.

Oricare instruciune poate fi o instruciune simpl sau un grup de instruciuni ntre acolade.

`Instruciunea dup ultimul else se execut n cazul n care nici o expresie nu a fost adevrat.

Dac n acest caz nu exist nici o aciune explicit de fcut, atunci partea

else instruciune-4poate s lipseasc.

Funcia binary din seciunea 7.5 este un exemplu de decizie multipl de ordinul 3.

Pot exista un numr arbitrar de construcii:

else if (expresie)

instruciunegrupate ntre un if iniial i un else final.

ntotdeauna un else se leag cu ultimul if ntlnit.

6.4. Instruciunea while

Format:

while (expresie)

instruciune

Instruciunea se execut repetat atta timp ct valoarea expresiei este diferit de zero. Testul are loc naintea fiecrei execuii a instruciunii. Prin urmare ciclul este urmtorul: se testeaz condiia din paranteze dac ea este adevrat, deci expresia din paranteze are o valoare diferit de zero, se execut corpul instruciunii while, se verific din nou condiia, dac ea este adevrat se execut din nou corpul instruciunii. Cnd condiia devine fals, adic valoarea expresiei din paranteze este zero, se face un salt la instruciunea de dup corpul instruciunii while, deci instruciunea while se termin.

6.5. Instruciunea do

Format:

do instruciune while

(expresie);

Instruciunea se execut repetat pn cnd valoarea expresiei devine zero. Testul are loc dup fiecare execuie a instruciunii.

6.6. Instruciunea for

Format:

for (expresie-1(opt(; expresie-2(opt(; expresie-3(opt()

instruciune

Aceast instruciune este echivalent cu:

expresie-1;

while (expresie-2) {

instruciune;

expresie-3;

}

Expresie-1 constituie iniializarea ciclului i se execut o singur dat naintea ciclului. Expresie-2 specific testul care controleaz ciclul. El se execut naintea fiecrei iteraii. Dac condiia din test este adevrat atunci se execut corpul ciclului, dup care se execut expresie-3, care const de cele mai multe ori n modificarea valorii variabilei de control al ciclului. Se revine apoi la reevaluarea condiiei. Ciclul se termin cnd condiia devine fals.

Oricare dintre expresiile instruciunii for sau chiar toate pot lipsi.

Dac lipsete expresie-2, aceasta implic faptul c clauza while este echivalent cu while (1), ceea ce nseamn o condiie totdeauna adevrat. Alte omisiuni de expresii snt pur i simplu eliminate din expandarea de mai sus.

Instruciunile while i for permit un lucru demn de observat i anume, ele execut testul de control la nceputul ciclului i naintea intrrii n corpul instruciunii.

Dac nu este nimic de fcut, nu se face nimic, cu riscul de a nu intra niciodat n corpul instruciunii.

6.7. Instruciunea switch

Instruciunea switch este o decizie multipl special i determin transferul controlului unei instruciuni sau unui bloc de instruciuni dintr-un ir de instruciuni n funcie de valoarea unei expresii.

Format:

switch (expresie) instruciune

Expresia este supus la conversiile aritmetice obinuite dar rezultatul evalurii trebuie s fie de tip int.

Fiecare instruciune din corpul instruciunii switch poate fi etichetat cu una sau mai multe prefixe case astfel:

case expresie-constant:unde expresie-constant trebuie s fie de tip int.

Poate exista de asemenea cel mult o instruciune etichetat cu

default:

Cnd o instruciune switch se execut, se evalueaz expresia din paranteze i valoarea ei se compar cu fiecare constant din fiecare case.

Dac se gsete o constant case egal cu valoarea expresiei, atunci se execut instruciunea care urmeaz dup case-ul respectiv.

Dac nici o constant case nu este egal cu valoarea expresiei i dac exist un prefix default, atunci se execut instruciunea de dup el, altfel nici o instruciune din switch nu se execut.

Prefixele case i default nu altereaz fluxul de control, care continu printre astfel de prefixe.

Pentru ieirea din switch se folosete instruciunea break (vezi seciunea 6.8) sau return (vezi seciunea 6.10).

De obicei instruciunea care constituie corpul unui switch este o instruciune compus. La nceputul acestei instruciuni pot aprea i declaraii, dar iniializarea variabilelor automatice i registru este inefectiv.

na = nb = nc = 0;

while (c=s[i++])

switch (c) {

case '0':

case '1':

case '2':

case '3':

case '4':

case '5':

case '6':

case 'T':

case '8':

case '9':

nc[c-'0']++;

break;

case ' ':

case '\r':

case '\t':

nb++;

break;

default:

na++;

break;

}

printf("cifre: ");

for (i=0; i0)

if (index(line,"the")>=0)

printf("%s",line);

}

2. Funcia atof convertete un ir de cifre din formatul ASCII ntr-un numr flotant n precizie dubl.

double atof(char s[]) {

/* convertete irul s n dubl precizie */

double val, power;

int i, sign;

for (i=0; s[i]==' ' || s[i]=='\n' ||

s[i]=='\t'; i++) ;/* sare peste spaii albe */

sign = 1;

if (s[i]=='+' || s[i]=='-')

sign = (s[i++]=='+') ? 1 : -1;

for (val=0; s[i]>='0' && s[i]='0' && s[i]='0' && s[i]='A' && c(b) ? (a) : (b))

atunci linia dintr-un program surs:

x = max(p+q,r+s);

va fi nlocuit cu:

x = ((p+q)>(r+s) ? (p+q) : (r+s));

Aceast macro-definiie furnizeaz o funcie maximum care se expandeaz n cod, n loc s se realizeze un apel de funcie. Acest macro va servi pentru orice tip de date, nefiind nevoie de diferite tipuri de funcii maximum pentru diferite tipuri de date, aa cum este necesar n cazul funciilor propriu-zise.

Dac se examineaz atent expandarea lui max se pot observa anumite probleme ce pot genera erori, i anume: expresiile fiind evaluate de dou ori, n cazul n care ele conin operaii ce genereaz efecte colaterale (apelurile de funcii, operatorii de incrementare) se pot obine rezultate total eronate.

De asemenea, trebuie avut mare grij la folosirea parantezelor pentru a face sigur ordinea evalurii dorite. De exemplu macro-operaia square(x) definit prin:

#define square(x) x*x

care se apeleaz n programul surs sub forma:

z = square(z+1);

va produce un rezultat, altul dect cel scontat, datorit prioritii mai mari a operatorului * fa de cea a operatorului +.

8.2. Includerea fiierelor

O linie de forma:

#include "nume-fiier"realizeaz nlocuirea liniei respective cu ntregul coninut al fiierului nume-fiier. Fiierul denumit este cutat n primul rnd n directorul fiierului surs curent i apoi ntr-o succesiune de locuri standard, cum ar fi biblioteca I/O standard asociat compilatorului. Alternativ, o linie de forma:

#include (nume-fiier(caut fiierul nume-fiier numai n biblioteca standard i nu n directorul fiierului surs.

Deseori, o linie sau mai multe linii, de una sau ambele forme apar la nceputul fiecrui fiier surs pentru a include definiii comune (prin declaraii #define i declaraii externe pentru variabilele globale).

Facilitatea de includere a unor fiiere ntr-un text surs este deosebit de util pentru gruparea declaraiilor unui program mare. Ea va asigura faptul c toate fiierele surs vor primi aceleai definiii i declaraii de variabile, n felul acesta eliminndu-se un tip particular de erori. Dac se modific un fiier inclus printr-o linie #include, toate fiierele care depind de el trebuie recompilate.

8.3. Compilarea condiionat

O linie de control a compilatorului de forma:

#if expresie-constant

verific dac expresia constant este evaluat la o valoare diferit de zero.

O linie de control de forma:

#ifdef identificator

verific dac identificatorul a fost subiectul unei linii de control de forma #define.

O linie de control de forma:

#ifndef identificator

verific dac identificatorul este nedefinit n preprocesor.

Toate cele trei forme de linii de control precedente pot fi urmate de un numr arbitrar de linii care, eventual, pot s conin o linie de control forma:

#else

i apoi de o linie de control de forma:

#endif

Dac condiia supus verificrii este adevrat, atunci orice linie ntre #else i #endif este ignorat. Dac condiia este fals atunci toate liniile ntre testul de verificare i un #else sau n lipsa unui #else pn la #endif snt ignorate.

Toate aceste construcii pot fi imbricate.

8.4. Utilizarea dirctivelor de compilare

Prezentm n continuare un exemplu didactic de utilizare a directivelor de compilare n dezvoltarea unor proiecte.

Se citete de la tastatur o pereche de numere naturale p i q. S se determine dac fiecare din cele dou numere este prim sau nu, i s se calculeze cel mai mare divizor comun.

S rezolvm aceast problem folosind dou fiiere surs. Primul conine funcia main i apeleaz dou funcii: eprim i cmmdc. Al doilea fiier implementeaz cele dou funcii.

Prezentm mai nti un fiier header (numere.h) care conine definiiile celor dou funcii.

#ifndef _Numere_H

#define _Numere_H

unsigned eprim(unsigned n);

unsigned cmmdc(unsigned p, unsigned q);

#endif

Fiierul surs princ.c este foarte scurt.

#include

#include "numere.h"

static void citire(unsigned *n) {

scanf("%u",n);

if (eprim(*n))

printf("%u e prim\n",*n);

else

printf("%u nu e prim\n",*n);}

int main() {

unsigned p,q,k;

citire(&p);

citire(&q);

k=cmmdc(p,q);

printf("Cmmdc: %u\n",k);

return 0;}

Fiierul surs numere.c este prezentat n continuare.

#include "numere.h"

unsigned eprim(unsigned n) {

unsigned i,a,r;

if (n==0) return 0;

if (n0))

if (p>q) p%=q;

else q%=p;

if (p==0) return q;

else return p;

}

Pentru a obine un program executabil din aceste dou fiiere se poate utiliza o singur comand de compilare:

Cc princ.c numere.c opiuni-de-compilareunde Cc este numele compilatorului folosit (exemplu: bcc, gcc). Opiunile de compilare (atunci cnd snt necesare) snt specifice mediului de programare folosit.

Proiectele programe de dimensiuni mari snt compuse de cele mai multe ori din module care se elaboreaz i se pun la punct separat. Uneori pot fi identificate funcii care prezint un interes general mai mare, i care pot fi utilizate n elaborarea unor tipuri de aplicaii foarte diverse. Acestea snt dezvoltate separat i, dup ce au fost puse la punct n totalitate, se depun ntr-o bibliotec de funcii n format obiect. n momentul n care acestea snt incluse n proiecte nu se mai pierde timp cu compilarea modulelor surs. n schimb modulele care le apeleaz au nevoie de modul cum snt descrise aceste funcii, i acesta se pstreaz n fiiere header.

9. Pointeri i masive

Un pointer este o variabil care conine adresa unei alte variabile. Pointerii snt foarte mult utilizai n programe scrise n C, pe de o parte pentru c uneori snt unicul mijloc de a exprima un calcul, iar pe de alt parte pentru c ofer posibilitatea scrierii unui program mai compact i mai eficient dect ar putea fi obinut prin alte ci.

9.1. Pointeri i adrese

Deoarece un pointer conine adresa unui obiect, cu ajutorul lui putem avea acces, n mod indirect, la acea variabil (obiect).

S presupunem c x este o variabil de tip ntreg i px un pointer la aceast variabil. Atunci aplicnd operatorul unar & lui x, instruciunea:

px = &x;

atribuie variabilei px adresa variabilei x; n acest fel spunem c px indic (pointeaz) spre x.

Invers, dac px conine adresa variabilei x, atunci instruciunea:

y = *px;

atribuie variabilei y coninutul locaiei pe care o indic px.

Evident toate variabilele care snt implicate n aceste instruciuni trebuie declarate. Aceste declaraii snt:

int x,y;

int *px;

Declaraiile variabilelor x i y snt deja cunoscute. Declaraia pointerului px este o noutate. A doua declaraie indic faptul c o combinaie de forma *px este un ntreg, iar variabila px care apare n contextul *px este echivalent cu un pointer la o variabil de tip ntreg. n locul tipului ntreg poate aprea oricare dintre tipurile admise n limbaj i se refer la obiectele pe care le indic px.

Pointerii pot aprea i n expresii, ca de exemplu n expresia urmtoare:

y = *px + 1;

unde variabilei y i se atribuie coninutul variabilei x plus 1.

Instruciunea:

d = sqrt((double)*px);

are ca efect convertirea coninutului variabilei x pe care o indic px n tip double i apoi depunerea rdcinii ptrate a valorii astfel convertite n variabila d.

Referiri la pointeri pot aprea de asemenea i n partea stng a atribuirilor. Dac, de exemplu, px indic spre x, atunci:

*px = 0;

atribuie variabilei x valoarea zero, iar:

*px += 1;

incrementeaz coninutul variabilei x cu 1, ca i n expresia:

(*px)++;

n acest ultim exemplu parantezele snt obligatorii deoarece, n lipsa lor, expresia ar incrementa pe px n loc de coninutul variabilei pe care o indic (operatorii unari *, ++ au aceeai preceden i snt evaluai de la dreapta spre stnga).

9.2 Pointeri i argumente de funcii

Deoarece n limbajul C transmiterea argumentelor la funcii se face prin valoare (i nu prin referin), funcia apelat nu are posibilitatea de a altera o variabil din funcia apelant. Problema care se pune este cum procedm dac totui dorim s schimbm un argument?

De exemplu, o rutin de sortare poate schimba ntre ele dou elemente care nu respect ordinea dorit, cu ajutorul unei funcii swap. Fie funcia swap definit astfel:

swap(int x, int y) {

/* greit */

int temp;

temp = x;

x = y;

y = temp;

}

Funcia swap apelat prin swap(a,b) nu va realiza aciunea dorit deoarece ea nu poate afecta argumentele a i b din rutina apelant.

Exist ns o posibilitate de a obine efectul dorit, dac funcia apelant transmite ca argumente pointeri la valorile ce se doresc interschimbate. Atunci n funcia apelant apelul va fi:

swap(&a,&b);

iar forma corect a lui swap este:

swap(int *px, int *py) {/* interschimb *px i *py */

int temp;

temp = *px;

*px = *py;

*py = temp;

}

9.3. Pointeri i masive

n limbajul C exist o strns legtur ntre pointeri i masive. Orice operaie care poate fi realizat prin indicarea masivului poate fi de asemenea fcut prin pointeri, care, n plus, conduce i la o accelerare a operaiei. Declaraia:

int a[10];

definete un masiv de dimensiune 10, care reprezint un bloc de 10 obiecte consecutive numite a[0], ... a[9]. Notaia a[i] reprezint al i-lea element al masivului sau elementul din poziia i(1, ncepnd cu primul element. Dac pa este un pointer la un ntreg declarat sub forma:

int *pa;

atunci atribuirea:

pa = &a[0];

ncarc variabila pa cu adresa primului element al masivului a.

Atribuirea:

x = *pa;

copiaz coninutul lui a[0] n x.

Dac pa indic un element particular al unui masiv a, atunci prin definiie pa+i indic un element cu i poziii dup elementul pe care l indic pa, dup cum pa-i indic un element cu i poziii nainte de cel pe care indic pa. Astfel, dac variabila pa indic pe a[0] atunci *(pa+i) se refer la coninutul lui a[i].

Aceste observaii snt adevrate indiferent de tipul variabilelor din masivul a.

ntreaga aritmetic cu pointeri are n vedere faptul c expresia pa+i nseamn de fapt nmulirea lui i cu lungimea elementului pe care l indic pa i adunarea apoi la pa, obinndu-se astfel adresa elementului de indice i al masivului.

Corespondena dintre indexarea ntr-un masiv i aritmetica de pointeri este foarte strns. De fapt, o referire la un masiv este convertit de compilator ntr-un pointer la nceputul masivului. Efectul este c un nume de masiv este o expresie pointer, deoarece numele unui masiv este identic cu numele elementului de indice zero din masiv.

Atribuirea:

pa = &a[0];

este identic cu:

pa = a;

De asemenea, expresiile a[i] i *(a+i) snt identice. Aplicnd operatorul & la ambele pri obinem &a[i] identic cu a+i. Pe de alt parte, dac pa este un pointer, expresiile pot folosi acest pointer ca un indice: pa[i] este identic cu *(pa+i). Pe scurt orice expresie de masiv i indice poate fi scris ca un pointer i un deplasament i invers, chiar n aceeai instruciune.

Exist ns o singur diferen ntre un nume de masiv i un pointer la nceputul masivului. Un pointer este o variabil, deci pa = a i pa++ snt instruciuni corecte. Dar un nume de masiv este o constant i deci construcii de forma a = pa, a++ sau p = &a snt ilegale.

Cnd se transmite unei funcii un nume de masiv, ceea ce se transmite de fapt este adresa primului element al masivului. Aadar, un nume de masiv, argument al unei funcii, este n realitate un pointer, adic o variabil care conine o adres. Fie de exemplu funcia strlen care calculeaz lungimea irului s:

strlen(char *s) {

/* returneaz lungimea irului */

int n;

for (n=0; *s!='\0'; s++)

n++;

return n;

}

Incrementarea lui s este legal deoarece s este o variabil pointer. s++ nu afecteaz irul de caractere din funcia care apeleaz pe strlen, ci numai copia adresei irului din funcia strlen.

Este posibil s se transmit unei funcii, ca argument, numai o parte a unui masiv, printr-un pointer la nceputul sub-masivului respectiv. De exemplu, dac a este un masiv, atunci:

f(&a[2])

f(a+2)

transmit funciei f adresa elementului a[2], deoarece &a[2] i a+2 snt expresii pointer care, ambele, se refer la al treilea element al masivului a. n cadrul funciei f argumentul se poate declara astfel:

f(int arr[]) { }

sau

f(int *arr) { }

Declaraiile int arr[] i int *arr snt echivalente, opiunea pentru una din aceste forme depinznd de modul n care vor fi scrise expresiile n interiorul funciei.

9.4. Aritmetica de adrese

Dac p este un pointer, atunci p += i incrementeaz pe p pentru a indica cu i elemente dup elementul pe care l indic n prealabil p. Aceast construcie i altele similare snt cele mai simple i comune formule ale aritmeticii de adrese, care constituie o caracteristic puternic a limbajului C. S ilustrm cteva din proprietile aritmeticii de adrese scriind un alocator rudimentar de memorie. Fie rutina alloc(n) care returneaz un pointer p la o zon de n caractere consecutive care vor fi folosite de rutina apelant pentru memorarea unui ir de caractere. Fie rutina free(p) care elibereaz o zon ncepnd cu adresa indicat de pointerul p pentru a putea fi refolosit mai trziu. Zona de memorie folosit de rutinele alloc i free este o stiv funcionnd pe principiul ultimul intrat ( primul ieit, iar apelul la free trebuie fcut n ordine invers cu apelul la alloc. S considerm c funcia alloc va gestiona stiva ca pe elementele unui masiv pe care l vom numi allocbuf. Vom mai folosi un pointer la urmtorul element liber din masiv, pe care-l vom numi allocp. Cnd se apeleaz rutina alloc pentru n caractere, se verific dac exist suficient spaiu liber n masivul allocbuf. Dac da, alloc va returna valoarea curent a lui allocp, adic adresa de nceput a blocului cerut, dup care va incrementa pe allocp cu n pentru a indica urmtoarea zon liber. free(p) actualizeaz allocp cu valoarea p, dac p indic n interiorul lui allocbuf.

#define NULL 0

/* valoarea pointerului pentru semnalizarea erorii */

#define ALLOCSIZE 1000

/* dimensiunea spaiului disponibil */

static char allocbuf [ALLOCSIZE];

/* memoria pentru alloc */

static char *allocp = allocbuf;

/* urmtoarea poziie liber */

char *alloc(int n) {

/* returneaz pointer la n caractere */

if (allocp+n=allocbuf && p=maxlines)

return -1;

else if ((p=alloc(len))==NULL)

return -1;

else {

line[len-1] = '\0';

strcpy(p,line);

lineptr[nlines++] = p;

}

return nlines;

}

Instruciunea line[len-1] = '\0'; terge caracterul (LF( de la sfritul fiecrei linii ca s nu afecteze ordinea n care snt sortate liniile i depune n locul lui caracterul '\0' ca marc de sfrit de ir.

Rutina care tiprete liniile n noua lor ordine este writelines i are urmtorul cod:

writelines(char *lineptr[], int nlines) {

/* scrie liniile sortate */

int i;

for (i=0; i=0)

printf("%s\n",*lineptr++);

}

n funcia printf, lineptr indic iniial prima linie de imprimat; fiecare incrementare avanseaz pe *lineptr la urmtoarea linie de imprimat, n timp ce nlines se micoreaz dup fiecare tiprire a unei linii cu 1.

Funcia care realizeaz sortarea efectiv a liniilor se bazeaz pe algoritmul de njumtire i are urmtorul cod:

#define NULL 0

#define LINES 100

/* nr maxim de linii de sortat */

sort(char *v[], int n) {

/* sorteaz irurile v0, v1, ... vn-1 n ordine cresctoare */

int gap,i,j;

char *temp;

for (gap=n/2; gap>0; gap/=2)

for (i=gap; i=0; j-=gap) {

if (strcmp(v[j],v[j+gap])0)

printf((argc>1)? "%s ":"%s\n",*++argv);

}

Aceast versiune arat c argumentul funciei printf poate fi o expresie ca oricare alta, cu toate c acest mod de utilizare nu este foarte frecvent.

Ca un al doilea exemplu, s reconsiderm programul din seciunea 7.5, care imprim fiecare linie a unui text care conine un ir specificat de caractere (schem).

Dorim acum ca aceast schem s poat fi modificat dinamic, de la execuie la execuie. Pentru aceasta o specificm printr-un argument n linia de comand.

i atunci programul care caut schema dat de primul argument al liniei de comand este:

#define MAXLINE 1000

main(int argc, char *argv[ ]) {

/* gsete schema din primul argument */

char line[MAXLINE];

if (argc!=2)

printf("Linia de comanda eronata\n");

else

while (getline(line,MAXLINE)>0)

if (index(line,argv[1])>=0)

printf("%s",line);

}

unde linia de comand este de exemplu: "find limbaj" n care "find" este numele programului, iar "limbaj" este schema cutat. Rezultatul va fi imprimarea tuturor liniilor textului de intrare care conin cuvntul "limbaj".

S elaborm acum modelul de baz, legat de linia de comand i argumentele ei.

S presupunem c dorim s introducem n linia de comand dou argumente opionale: unul care s tipreasc toate liniile cu excepia acelora care conin schema, i al doilea care s precead fiecare linie tiprit cu numrul ei de linie.

O convenie pentru programele scrise n limbajul C este ca argumentele dintr-o linie de comand care ncep cu un semn '-' s introduc un parametru opional. Dac alegem, de exemplu, -x pentru a indica cu excepia i -n pentru a cere numrarea liniilor, atunci comanda:

find -x -n la

avnd intrarea:

la miezul stinselor lumini

s-ajung victorios,

la temelii, la rdcini,

la mduv, la os.

va produce tiprirea liniei a doua, precedat de numrul ei, deoarece aceast linie nu conine schema "la".

Argumentele opionale snt permise n orice ordine n linia de comand. Analizarea i prelucrarea argumentelor unei linii de comand trebuie efectuat n funcia principal main, iniializnd n mod corespunztor anumite variabile. Celelalte funcii ale programului nu vor mai ine evidena acestor argumente.

Este mai comod pentru utilizator dac argumentele opionale snt concatenate, ca n comanda:

find -xn la

Caracterele 'x' respectiv 'n' indic doar absena sau prezena acestor opiuni (switch) i nu snt tratate din punct de vedere al valorii lor.

Fie programul care caut schema "la" n liniile de la intrare i le tiprete pe acelea, care nu conin schema, precedate de numrul lor de linie. Programul trateaz corect, att prima form a liniei de comand ct i a doua.

#define MAXLINE 1000

main(int argc, char *argv[]) {

/* caut schema */

char line[MAXLINE], *s;

long line0;

int except, number;

line0 = 0;

number = 0;

while (--argc>0 && (*++argv)[0]=='-')

for (s=argv[0]+1; *s!='\0'; s++)

switch(*s) {

case 'x': except = 1; break;

case 'n': number = 1; break;

default:

printf

("find: optiune ilegala %c\n",

*s);

argc = 0;

break;

}

if (argc!=1)

printf

("Nu exista argumente sau schema\n");

else

while (getline(line,MAXLINE)>0) {

line0++;

if ((index(line,*argv)>=0)!=except)

{

if (number)

printf("%d:",line0);

printf("%s",line);

}

}

}

Dac nu exist erori n linia de comand, atunci la sfritul primului ciclu while argc trebuie s fie 1, iar *argv conine adresa schemei. *++argv este un pointer la un ir argument, iar (*++argv)[0] este primul caracter al irului. n aceast ultim expresie parantezele snt necesare deoarece fr ele expresia nseamn *++(argv[0]) ceea ce este cu totul altceva (i greit): al doilea caracter din numele programului. O alternativ corect pentru (*++argv[0]) este **++argv.

9.11. Pointeri la funcii

n limbajul C o funcie nu este o variabil, dar putem defini un pointer la o funcie, care apoi poate fi prelucrat, transmis unor alte funcii, introdus ntr-un masiv i aa mai departe. Relativ la o funcie se pot face doar dou operaii: apelul ei i considerarea adresei ei. Dac numele unei funcii apare ntr-o expresie, fr a fi urmat imediat de o parantez stng, deci nu pe poziia unui apel la ea, atunci se genereaz un pointer la aceast funcie. Pentru a transmite o funcie unei alte funcii, ca argument, se poate proceda n felul urmtor:

int f();

g(f);

unde funcia f este un argument pentru funcia g. Definiia funciei g va fi:

g(int(*funcpt) ()) {

(*funcpt)();

}

Funcia f trebuie declarat explicit n rutina apelant (int f();), deoarece apariia ei n g(f) nu a fost urmat de parantez stng (. n expresia g(f) f nu apare pe poziia de apel de funcie. n acest caz, pentru argumentul funciei g se genereaz un pointer la funcia f. Deci g apeleaz funcia f printr-un pointer la ea.

Declaraiile din funcia g trebuie studiate cu grij.

int (*funcpt)();

spune c funcpt este un pointer la o funcie care returneaz un ntreg. Primul set de paranteze este necesar, deoarece fr el

int *funcpt();

nseamn c funcpt este o funcie care returneaz un pointer la un ntreg, ceea ce este cu totul diferit fa de sensul primei expresii. Folosirea lui funcpt n expresia:

(*funcpt)();

indic faptul c funcpt este un pointer la o funcie, *funcpt este funcia, iar (*funcpt)() este apelul funciei.

O form echivalent simplificat de apel este urmtoarea:

funcpt();

Ca un exemplu, s considerm procedura de sortare a liniilor de la intrare, descris n seciunea 9.7, dar modificat n sensul ca dac argumentul opional -n apare n linia de comand, atunci liniile se vor sorta nu lexicografic ci numeric, liniile coninnd grupe de numere.

O sortare const adesea din trei pri: o comparare care determin ordinea oricrei perechi de elemente, un schimb care inverseaz ordinea elementelor implicate i un algoritm de sortare care face comparrile i inversrile pn cnd elementele snt aduse n ordinea cerut. Algoritmul de sortare este independent de operaiile de comparare i inversare, astfel nct transmind diferite funcii de comparare i inversare funciei de sortare, elementele de intrare se pot aranja dup diferite criterii.

Compararea lexicografic a dou linii se realizeaz prin funciile strcmp i swap. Mai avem nevoie de o rutin numcmp care s compare dou linii pe baza valorilor numerice i care s returneze aceiai indicatori ca i rutina strcmp.

Declarm aceste trei funcii n funcia principal main, iar pointerii la aceste funcii i transmitem ca argumente funciei sort, care la rndul ei va apela aceste funcii prin intermediul pointerilor respectivi.

Funcia principal main va avea atunci urmtorul cod:

#define LINES 100

/* nr maxim de linii de sortat */

main (int argc, char *argv[]) {

char *lineptr[LINES];

/* pointeri la linii text */

int nlines;

/* numr de linii citite */

int strcmp(), numcmp();/* funcii de comparare */

int swap ();

/* funcia de inversare */

int numeric;

numeric = 0;

/* 1 dac sort numeric */

if (argc>1 && argv[1][0]=='-' &&

argv[1][1]=='n')

numeric = 1;

if ((nlines=readlines(lineptr,LINES))>=0)

{

if (numeric)

sort(lineptr,nlines,numcmp,swap);

else

sort(lineptr,nlines,strcmp,swap);

writelines (lineptr,nlines);

}

else

printf

("Nr de linii de intrare prea mare\n");

}

n apelul funciei sort, argumentele strcmp, numcmp i swap snt adresele funciilor respective. Deoarece ele au fost declarate funcii care returneaz un ntreg, operatorul & nu este necesar s precead numele funciilor, compilatorul fiind cel care gestioneaz transmiterea adreselor funciilor.

Funcia sort care aranjeaz liniile n ordinea cresctoare se va modifica astfel:

sort(char *v[], int n, int (*comp)(),

int (*exch)()) {/* sorteaz v0, v1, ... , vn(1 */

int gap,i,j;

for (gap=n/2; gap>0; gap/=2)

for (i=gap; i=0; j-=gap) {

if (comp(v[j],v[j+gap])year%4==0) &&

(pd->year%100!==0) ||

(pd->year%400==0);

for (i=1; imonth; i++)

day += day_tab[leap][i];

return day;

}

Declaraia:

struct date * pd;

indic faptul c pd este un pointer la o structur de ablonul lui date. Notaia:

pd->year

indic faptul c se refer membrul "year" al acestei structuri. n general, dac p este un pointer la o structur p->membru-structur se refer la un membru particular (operatorul -> se formeaz din semnul minus urmat de semnul mai mare).

Deoarece pd este pointer la o structur, membrul year poate fi de asemenea referit prin:

(*pd).year

Notaia "->" se impune ca un mod convenabil de prescurtare. n notaia (*pd).year, parantezele snt necesare deoarece precedena operatorului membru de structur . este mai mare dect cea a operatorului *.

Ambii operatori . i -> snt asociativi de la stnga la dreapta, astfel nct:

p->q->membru

emp.birthdate.month

snt de fapt:

(p->q)->membru

(emp.birthdate).month

Operatorii -> i . ai structurilor, mpreun cu () pentru listele de argumente i [] pentru indexare se gsesc n vrful listei de preceden (vezi seciunea 4.16), fiind din acest punct de vedere foarte apropiai. Astfel, fiind dat declaraia:

struct {

int x;

int *y;} *p;

unde p este un pointer la o structur, atunci expresia:

++p->x

incrementeaz pe x, nu pointerul p, deoarece operatorul -> are o preceden mai mare dect ++. Parantezele pot fi folosite pentru a modifica ordinea operatorilor dat de precedena. Astfel:

(++p)->x

incrementeaz mai nti pe p i apoi acceseaz elementul x, din structura nou pointat.

n expresia (p++)->x se acceseaz mai nti x, apoi se incrementeaz pointerul p.

n mod analog, *p->y indic coninutul adresei pe care o indic y. Expresia *p->y++ acceseaz mai nti ceea ce indic y i apoi incrementeaz pe y. Expresia (*p->y)++ incrementeaz ceea ce indic y. Expresia *p++->y acceseaz ceea ce indic y i apoi incrementeaz pointerul p.

10.3. Masive de structuri

Structurile snt n mod special utile pentru tratarea masivelor de variabile legate prin context. Pentru exemplificare vom considera un program care numr intrrile fiecrui cuvnt cheie dintr-un text. Pentru aceasta avem nevoie de un masiv de iruri de caractere, pentru pstrarea numelor cuvintelor cheie i un masiv de ntregi pentru a memora numrul apariiilor. O posibilitate este de a folosi dou masive paralele keyword i keycount declarate prin:

char *keyword[NKEYS];

int keycount[NKEYS];

respectiv unul de pointeri la iruri de caractere i cellalt de ntregi. Fiecrui cuvnt cheie i corespunde perechea:

char *keyword;

int keycount;

astfel nct putem considera cele dou masive ca fiind un masiv de perechi. Atunci, declaraia de structur:

struct key {

char *keyword;

int keycount;

} keytab[NKEYS];

definete un masiv keytab de structuri de acest tip i aloc memorie pentru ele. Fiecare element al masivului keytab este o structur de acelai ablon ca i structura key.

Definiia masivului keytab poate fi scris i sub forma:

struct key {

char *keyword;

int keycount;

};

struct key keytab[NKEYS];

Deoarece masivul de structuri keytab conine, n cazul nostru, o mulime constant de cuvinte cheie, este mai uor de iniializat o dat pentru totdeauna chiar n locul unde este definit. Iniializarea structurilor este o operaie analoag cu iniializarea unui masiv n sensul c definiia este urmat de o list de iniializatori nchii n acolade.

Atunci iniializarea masivului de structuri keytab va fi urmtoarea:

struct key {

char * keyword;

int keycount;

} keytab[] = {

"break",0,

"case",0,

"char",0,

/* ... */

"while",0};

Iniializatorii snt perechi care corespund la membrii structurii. Iniializarea ar putea fi fcut i incluznd iniializatorii fiecrei structuri din masiv n acolade ca n:

{"break",0},{"case",0}....

dar parantezele interioare nu snt necesare dac iniializatorii snt variabile simple sau iruri de caractere i dac toi iniializatorii snt prezeni.

Compilatorul va calcula, pe baza iniializatorilor, dimensiunea masivului de structuri keytab motiv pentru care, la iniializare, nu este necesar indicarea dimensiunii masivului.

Programul de numrare a cuvintelor cheie ncepe cu definirea masivului de structuri keytab. Rutina principal main citete textul de la intrare prin apel repetat la o funcie getword, care extrage din intrare cte un cuvnt la un apel. Fiecare cuvnt este apoi cutat n tabelul keytab cu ajutorul unei funcii de cutare binary, descris n seciunea 7.5. Lista cuvintelor cheie trebuie s fie n ordine cresctoare pentru ca funcia binary s lucreze corect. Dac cuvntul cercetat este un cuvnt cheie atunci funcia binary returneaz numrul de ordine al cuvntului n tabelul cuvintelor cheie, altfel returneaz (1.

#define MAXWORD 20

binary(char *word, struct key tab[],

int n) {

int low,high,mid,cond;

low = 0;

high = n - 1;

while (low=0)

keytab[n].keycount++;

for (n=0; n0)

printf("%4d %s\n",

keytab[n].keycount,

keytab[n].keyword);

}

nainte de a scrie funcia getword este suficient s spunem c ea returneaz constanta simbolic LETTER de fiecare dat cnd gsete un cuvnt n textul de intrare i copiaz cuvntul n primul ei argument.

Cantitatea NKEYS este numrul cuvintelor cheie din keytab (dimensiunea masivului de structuri). Dei putem calcula acest numr manual, este mai simplu i mai sigur s-o facem cu calculatorul, mai ales dac lista cuvintelor cheie este supus modificrilor. O posibilitate de a calcula NKEYS cu calculatorul este de a termina lista iniializatorilor cu un pointer NULL i apoi prin ciclare pe keytab s detectm sfritul lui. Acest lucru este mai mult dect necesar deoarece dimensiunea masivului de structuri este perfect determinat n momentul compilrii. Numrul de intrri se determin mprind dimensiunea masivului la dimensiunea structurii key.

Operatorul sizeof descris n seciunea 4.2 furnizeaz dimensiunea n octei a argumentului su.

n cazul nostru, numrul cuvintelor cheie este dimensiunea masivului keytab mprit la dimensiunea unui element de masiv. Acest calcul este fcut ntr-o linie #define pentru a da o valoare identificatorului NKEYS:

#define NKEYS (sizeof(keytab) /

sizeof(struct key))

S revenim acum la funcia getword. Programul pe care-l vom da pentru aceast funcie este mai general dect este necesar n aplicaia noastr, dar nu este mult mai complicat.

Funcia getword citete cuvntul urmtor din textul de intrare, printr-un cuvnt nelegndu-se fie un ir de litere i cifre, cu primul caracter liter, fie un singur caracter. Funcia returneaz constanta simbolic LETTER dac a gsit un cuvnt, EOF dac a detectat sfritul fiierului sau caracterul nsui, dac el nu a fost alfabetic.

getword(char *w, int lim) {/* citete un cuvnt */

int t;

while (--lim>0) {

t = type(*w++=getchar());

if (t==EOF)

return EOF;

if (t!=LETTER && t!=DIGIT)

break;

}

*(w-1) = '\0';

return LETTER;

}

Funcia getword apeleaz funcia type pentru identificarea tipului fiecrui caracter citit la intrare cu ajutorul funciei getchar. Versiunea funciei type pentru caractere ASCII este:

type(int c) {

/* returneaz tipul caracterului */

if (c>='a' && c='A' && c='0' && ckeycount++;

for (p=keytab; pkeycount>0)

printf("%4d %s\n",p->keycount,

p->keyword);

}

S observm cteva lucruri importante n acest exemplu. n primul rnd, declaraia funciei binary trebuie s indice c ea returneaz un pointer la o structur de acelai ablon cu structura key, n loc de un ntreg. Acest lucru este declarat att n funcia principal main ct i n funcia binary. Dac binary gsete un cuvnt n structura key, atunci returneaz un pointer la el; dac nu-1 gsete, returneaz NULL. n funcie de aceste dou valori returnate, funcia main semnaleaz gsirea cuvntului prin incrementarea cmpului keycount corespunztor cuvntului sau citete urmtorul cuvnt.

n al doilea rnd, toate operaiile de acces la elementele masivului de structuri keytab se fac prin intermediul pointerilor. Acest lucru determin o modificare semnificativ n funcia binary. Calculul elementului mijlociu nu se mai poate face simplu prin:

mid = (low + high) / 2

deoarece adunarea a doi pointeri este o operaie ilegal, nedefinit. Aceast instruciune trebuie modificat n:

mid = low + (high-low) / 2

care face ca mid s pointeze elementul de la jumtatea distanei dintre low i high.

S mai observm iniializarea pointerilor low i high, care este perfect legal, deoarece este posibil iniializarea unui pointer cu o adres a unui element deja definit.

n funcia main avem urmtorul ciclu:

for(p=keytab; pword = strsav(w);

p->count = 1;

p->left = p->right = NULL;

}

else

if ((cond=strcmp(w,p->word))==0)

p->count++;

else

if (condleft = tree(p->left,w);

else

/* noul cuvnt mai mare */

p->right = tree(p->right,w);

return p;

}

Memoria pentru noul nod se aloc de ctre rutina talloc, care este o adaptare a rutinei alloc, pe care am vzut-o deja. Ea returneaz un pointer la un spaiu liber, n care se poate nscrie noul nod al arborelui. Vom discuta rutina talloc mai trziu. Noul cuvnt se copiaz n acest spaiu cu ajutorul rutinei strsav, care returneaz un pointer la nceputul cuvntului, contorul de apariii se iniializeaz la 1 i pointerii ctre cei doi descendeni se fac NULL. Aceast parte de cod se execut numai cnd se adaug un nou nod.

Rutina treeprint tiprete arborele astfel nct pentru fiecare nod se imprim sub-arborele lui stng, adic toate cuvintele mai mici dect cuvntul curent, apoi cuvntul curent i la sfrit sub-arborele drept, adic toate cuvintele mai mari dect cuvntul curent. Rutina treeprint este una din cele mai tipice rutine recursive.

treeprint(struct tnode *p) {

/* tiprete arborele p recursiv */

if (p!=NULL) {

treeprint(p->left);

printf("%5d %s\n",p->count,p->word);

treeprint(p->right);

}

}

Este important de reinut faptul c n algoritmul de cutare n arbore, pentru a ajunge la un anumit nod, se parcurg toate nodurile precedente, pe ramura respectiv (stng sau dreapt), ncepnd ntotdeauna cu nodul rdcin. Dup fiecare ieire din rutina tree, din cauza recursivitii, se parcurge acelai drum, de data aceasta de la nodul gsit spre rdcina arborelui, refcndu-se toi pointerii drumului parcurs.

Dac considerai ca nu ai neles suficient de bine recursivitatea, desenai-v un arbore i imprimai-l cu ajutorul rutinei treeprint, avnd grij s memorai fiecare ieire din tree i treeprint.

O observaie legat de acest exemplu: dac arborele este nebalansat, adic cuvintele nu sosesc n ordine aleatoare din punct de vedere lexicografic, atunci timpul de execuie al programului poate deveni foarte mare. Cazul limit n acest sens este acela n care cuvintele de la intrare snt deja n ordine, (cresctoare sau descresctoare), caz n care programul nostru simuleaz o cutare liniar ntr-un mod destul de costisitor.

S ne oprim puin asupra alocrii de memorie. Cu toate c se aloc diferite tipuri de obiecte, este de preferat s existe un singur alocator de memorie ntr-un program. Relativ la acest alocator de memorie se pun doua probleme: n primul rnd cum poate satisface el condiiile de aliniere ale obiectelor de un anumit tip (de exemplu ntregii trebuie alocai la adrese pare); n al doilea rnd cum se poate declara c alocatorul returneaz pointeri la tipuri diferite de obiecte.

Cerinele de aliniere pot fi n general rezolvate cu uurin pe seama unui spaiu care se pierde, dar care este nesemnificativ ca dimensiune. De exemplu, alocatorul alloc returneaz totdeauna un pointer la o adres par. n cazul n care cererea de alocare poate fi satisfcut i de o adres impar (pentru iruri de caractere, de exemplu) se pierde un caracter.

n ceea ce privete declararea tipului alocatorului alloc (adic a tipului de obiect pe care l indic pointerul returnat de alloc), un foarte bun procedeu n limbajul C este de a declara c funcia alloc returneaz un pointer la char i apoi s convertim explicit acest pointer la tipul dorit printr-un cast. Astfel dac p este declarat n forma:

char *p;

atunci:

(struct tnode *)p;

convertete pe p dintr-un pointer la char ntr-un pointer la o structur de ablon tnode, dac el apare ntr-o expresie. i atunci, o versiune a alocatorului talloc poate fi urmtoarea:

struct tnode *talloc() {

char *alloc();

return (struct tnode *)alloc

(sizeof(struct tnode));

}

10.6. Cutare n tabele

O alt problem legat de definirea i utilizarea structurilor este cutarea n tabele. Cnd se ntlnete de exemplu, o linie de forma:

#define YES 1

simbolul YES i textul de substituie 1 se memoreaz ntr-o tabel. Mai trziu, ori de cte ori textul YES va aprea n instruciuni, el se va nlocui cu constanta 1.

Crearea i gestionarea tabelelor de simboluri este o problem de baz n procesul de compilare. Exist dou rutine principale care gestioneaz simbolurile i textele lor de substituie. Prima, install(s,t) nregistreaz simbolul s i textul de substituie t ntr-o tabel, s i t fiind iruri de caractere. A doua, lookup(s) caut irul s n tabel i returneaz fie un pointer la locul unde a fost gsit, fie NULL dac irul s nu figureaz n tabel.

Algoritmul folosit pentru crearea i gestionarea tabelei de simboluri este o cutare pe baz de hashing. Fiecrui simbol i se calculeaz un cod hash astfel: se adun codurile ASCII ale caracterelor simbolului i se ia restul provenit din mprirea numrului obinut din adunare i dimensiunea tabelului. Astfel, fiecrui simbol i se asociaz un cod hash H care verific relaia:

0name)==0)

return np;

/* s-a gsit s */

return NULL;

/* nu s-a gsit s */

}

Rutina install folosete funcia lookup pentru a determina dac simbolul nou care trebuie introdus n lan este deja prezent sau nu. Dac mai exist o definiie anterioar pentru acest simbol, ea trebuie nlocuit cu definiia nou. Altfel, se creeaz o intrare nou pentru acest simbol, care se introduce la nceputul lanului. Funcia install returneaz NULL, dac din anumite motive nu exist suficient spaiu pentru crearea unui bloc unu.

struct nlist *install(char *name, char

*def) {

/* scrie (nume, def) n htab */

struct nlist *np, *lookup();

char *strsav(), *alloc();

int hashval;

if ((np=lookup(name))==NULL) {/* nu s-a gsit */

np = (struct nlist*)alloc(sizeof(*np));

if (np==NULL)

return NULL;

/* nu exist spaiu */

if ((np->name=strsav(name))==NULL)

return NULL;

hashval = hash(np->name);

np->next = hashtab[hashval];

hashtab[hashval] = np;

}

else

/* nodul exist deja */

free(np->def);

/* elibereaz definiia veche */

if ((np->def=strsav(def))==NULL)

return NULL;

return np;

}

Deoarece apelurile la funciile alloc i free pot aprea n orice ordine i deoarece alinierea conteaz, versiunea simpl a funciei alloc, prezentat n capitolul 9 nu este adecvat aici. n biblioteca standard exist funcii de alocare fr restricii, care se apeleaz implicit sau explicit de ctre utilizator dintr-un program scris n C pentru a obine spaiul de memorie necesar. Deoarece i alte aciuni dintr-un program pot cere spaiu de memorie ntr-o manier asincron, spaiul de memorie gestionat de funcia alloc poate s fie necontiguu. Astfel, spaiul liber de memorie este pstrat sub forma unui lan de blocuri libere, fiecare bloc coninnd o dimensiune, un pointer la urmtorul bloc i spaiul liber propriu-zis. Blocurile snt pstrate n ordinea cresctoare a adreselor iar, ultimul bloc, de adresa cea mai mare, indic primul bloc, prin pointerul lui la blocul urmtor din lan, astfel nct lanul este circular.

Cnd se lanseaz o cerere, se examineaz lista spaiului liber, pn se gsete un bloc suficient de mare pentru cererea respectiv. Dac blocul are exact dimensiunea cerut, el se elibereaz din lanul blocurilor libere i este returnat utilizatorului. Dac blocul este mai mare se descompune, astfel nct partea cerut se transmite utilizatorului, iar partea rmas se introduce napoi n lista de spaiu liber. Dac nu se gsete un bloc suficient de mare pentru cererea lansat se caut un alt bloc de memorie.

Eliberarea unei zone de memorie prin intermediul rutinei free cauzeaz, de asemenea, o cutare n lista de spaiu liber, pentru a gsi locul corespunztor de inserare a blocului de memorie eliberat. Dac blocul de memorie eliberat este adiacent cu un bloc din lista de spaiu liber la orice parte a sa, el este alipit la acel bloc, crendu-se un bloc mai mare, astfel ca memoria s nu devin prea fragmentat. Determinarea adiacenei este uurat de faptul c lista de spaiu liber se pstreaz n ordinea cresctoare a adreselor de memorie.

Exemplul de utilizare a acestor funcii iniializeaz elementele masivului hashtab cu NULL. n continuare se ateapt de la tastatur introducerea unui nume i a unei definiii pentru acest nume. Dac numele introdus nu exist n lista hashtab atunci se afieaz un mesaj corespunztor, altfel se afieaz vechea definiie care este apoi nlocuit de noua definiie introdus.

main() {

char num[30],def[30];

int i;

struct nlist *np;

for (i=0; idef);

install(num,def);

} while (1);

}

10.7. Cmpuri

Un cmp se definete ca fiind o mulime de bii consecutivi dintr-un cuvnt sau ntreg. Adic din motive de economie a spaiului de memorie, este util mpachetarea unor obiecte ntr-un singur cuvnt main. Un caz frecvent de acest tip este utilizarea unui set de flaguri, fiecare pe un bit, pentru tabela de simboluri a unui compilator.

Fiecare simbol dintr-un program are anumite informaii asociate lui, cum snt de exemplu, clasa de memorie, tipul, dac este sau nu cuvnt cheie .a.m.d. Cel mai compact mod de a codifica aceste informaii este folosirea unui set de flaguri, de cte un bit, ntr-un singur ntreg sau caracter.

Modul cel mai uzual pentru a face acest lucru este de a defini un set de mti, fiecare masc fiind corespunztoare poziiei bitului m interiorul caracterului sau cuvntului. De exemplu:

#define KEYWORD 01

#define EXTERNAL 02

#define STATIC 04

definesc mtile KEYWORD, EXTERNAL i STATIC care se refer la biii 0, 1 i respectiv 2 din caracter sau cuvnt. Atunci accesarea acestor bii se realizeaz cu ajutorul operaiilor de deplasare, mascare i complementare, descrii ntr-un capitol anterior. Numerele trebuie s fie puteri ale lui 2.

Expresii de forma:

flags | = EXTERNAL | STATIC;

apar frecvent i ele seteaz biii 1 i 2 din caracterul sau ntregul flags (n exemplul nostru)

n timp ce expresia:

flags &= (EXTERNAL | STATIC);

selecteaz biii 1 i 2 din flags.

Expresia:

if (flags & (EXTERNAL | STATIC)) ...

este adevrat cnd cel puin unul din biii 1 sau 2 din flags este unu.

Expresia:

if (!(flags & (EXTERNAL | STATIC))) ...

este adevrat cnd biii 1 i 2 din flags snt ambii zero.

Limbajul C ofer aceste expresii, ca o alternativ, pentru posibilitatea de a defini i de a accesa biii dintr-un cuvnt, n mod direct, folosind operatorii logici pe bii.

Sintaxa definiiei cmpului i a accesului la el se bazeaz pe structuri. De exemplu construciile #define din exemplul de mai sus pot fi nlocuite prin definirea a trei cmpuri:

struct {

unsigned is_keyword: 1;

unsigned is_external:1;

unsigned is_static: 1;

} flags;

Aceast construcie definete variabila flags care conine 3 cmpuri, fiecare de cte un bit. Numrul care urmeaz dup : reprezint lungimea cmpului n bii. Cmpurile snt declarate unsigned pentru a sublinia c ele snt cantiti fr semn. Pentru a ne referi la un cmp individual din variabila flags folosim o notaie similar cu notaia folosit pentru membrii structurilor.

flags.is_keyword

flags.is_static

Cmpurile se comport ca nite ntregi mici fr semn i pot participa n expresii aritmetice ca orice ali ntregi. Astfel, expresiile anterioare pot fi scrise mai natural sub forma urmtoare:

flags.is_extern = flags.is_static = 1;

pentru setarea biilor 1 i 2 din variabila flags,

flags.is_extern = flags.is_static = 0;

pentru tergerea biilor, iar:

if (flags.is_extern==0 &&

flags.is_static==0)

pentru testarea lor.

Un cmp nu trebuie s depeasc limitele unui cuvnt. n caz contrar, cmpul se aliniaz la limita urmtorului cuvnt. Cmpurile nu necesit s fie denumite. Un cmp fr nume, descris numai prin caracterul : i lungimea lui n bii este folosit pentru a rezerva spaiu n vederea alinierii urmtorului cmp. Lungimea zero a unui cmp poate fi folosit pentru forarea alinierii urmtorului cmp la limita unui nou cuvnt, el fiind presupus a conine tot cmpuri i nu un membru obinuit al structuri, deoarece n acest ultim caz, alinierea se face n mod automat. Nici un cmp nu poate fi mai lung dect un cuvnt. Cmpurile se atribuie de la dreapta la stnga.

Cmpurile nu pot constitui masive, nu au adrese, astfel nct operatorul '&' nu se poate aplica asupra lor.

10.8. Reuniuni

O reuniune este o variabil care poate conine, la momente diferite, obiecte de diferite tipuri i dimensiuni; compilatorul este cel care ine evidena dimensiunilor i aliniamentului.

Reuniunile ofer posibilitatea ca mai multe tipuri diferite de date s fie tratate ntr-o singur zon de memorie, fr a folosi n program vreo informaie dependent de main.

S relum exemplul tabelei de simboluri a unui compilator, presupunnd c constantele pot fi de tip int, float sau iruri de caractere.

Valoarea unei constante particulare trebuie memorat ntr-o variabil de tip corespunztor, cu toate c este mai convenabil, pentru gestiunea tabelei de simboluri, ca valoarea s fie memorat n aceeai zon de memorie, indiferent de tipul ei i s ocupe aceeai cantitate de memorie. Acesta este scopul unei reuniuni: de a furniza o singur variabil care s poat conine oricare dintre valorile unor tipuri de date. Ca i n cazul cmpurilor, sintaxa definiiei i accesului la o reuniune se bazeaz pe structuri. Fie definiia:

union u_tag. { int ival;

float fval;

char *pval;

} uval;

Variabila uval va fi suficient de mare ca s poat pstra pe cea mai mare dintre cele trei tipuri de componente. Oricare dintre tipurile de mai sus poate fi atribuit variabilei uval i apoi folosit n expresii n mod corespunztor, adic tipul n uval este tipul ultim atribuit. Utilizatorul este cel care ine evidena tipului curent memorat ntr-o reuniune.

Sintactic, membrii unei reuniuni snt accesibili printr-o construcie de forma:

nume-reuniune. membrusau

pointer-la-reuniune->membru

Dac variabila utype este utilizat pentru a ine evidena tipului curent memorat n uval, atunci fie urmtorul cod:

if (utype==INT)

printf ("%d\n",uval.ival);

else if (utype== FLOAT)

printf("%f\n",uval.fval);

else if (utype==STRING)

printf("%s\n",uval.pval);

else

printf("tip incorect %d in utype\n",

utype);

Reuniunile pot aprea n structuri i masive i invers. Sintaxa pentru accesarea unui membru al unei reuniuni, dintr-o structur, sau invers este identic cu cea pentru structurile imbricate. Pe exemplu, n masivul de structuri symtab[NSYM] definit de:

struct {

char * name;

int flags;

int utype;

union {

int ival;

float fval;

char *pval;

} uval;

} symtab[NSYM];

variabila ival se refer prin:

symtab[i].uval.ival

iar primul caracter al irului pointat de pval prin:

*symtab[i].uval.pval

De fapt, o reuniune este o structur n care toi membrii au deplasamentul zero, structura fiind suficient de mare pentru a putea pstra pe cel mai mare membru. Alinierea este corespunztoare pentru toate tipurile reuniunii. Ca i la structuri, singurele operaii permise cu reuniuni snt accesul la un membru al reuniunii i considerarea adresei ei.

Reuniunile nu pot fi atribuite, transmise la funcii sau returnate de ctre acestea. Pointerii la reuniuni pot fi folosii n mod similar cu pointerii la structuri.

10.9. Declaraii de structuri, reuniuni i cmpuri

Deoarece specificatorii de structuri, reuniuni i cmpuri au aceeai form vom prezenta sintaxa lor general n acest paragraf.

Specificator-structur-sau-reuniune:

struct-sau-union { lista-declaraiilor }

struct-sau-union identificator { lista-declaraiilor }

struct-sau-union identificator

Struct-sau-union:

struct

union

Lista-declaraiilor este o secven de declaraii pentru membrii structurii sau reuniunii.

Lista-declaraiilor:

declaraie-structur

declaraie-structur, lista-declaraiilor

Declaraie-structur:

specificator-tip, lista-declarator;

Lista-declarator:

declarator-structur

declarator-structur, lista-declarator

n mod obinuit, un declarator-structur este chiar un declarator pentru un membru al structurii sau reuniunii. Un membru al structurii poate fi constituit dintr-un numr specificat de bii, caz n care avem de-a face cu un cmp. Lungimea lui se separ de nume prin caracterul : Atunci:

Declarator-structur:

declarator

declarator : expresie-constant

: expresie-constant

ntr-o structur fiecare membru care nu este un cmp ncepe la o adres corespunztoare tipului su. Astfel ntr-o structur pot exista zone fr nume neutilizate, rezultate din motive de aliniere.

Limbajul C nu introduce restricii privind tipurile obiectelor care pot fi declarate cmpuri.

Un specificator-structur-sau-reuniune de forma a doua declar un identificator ca fiind eticheta (marcajul) structurii sau reuniunii. Atunci o declaraie ulterioar poate folosi forma a treia a unui specificator-structur-sau-reuniune.

Etichetele de structuri permit definirea structurilor auto-referite; de asemenea permit ca partea de declaraie a corpului structurii s fie dat o singur dat i folosit de mai multe ori. Este interzis declararea recursiv a unei structuri sau reuniuni, dar o structur sau o reuniune poate conine un pointer la ea.

Dou structuri pot partaja o secven iniial comun de membri; adic acelai membru poate aprea n dou structuri diferite dac el are acelai tip n ambele structuri i dac toi membri precedeni lui snt identici n cele dou structuri.

10.10. Typedef

Limbajul C ofer o facilitate numit typedef pentru a crea noi nume de tipuri de date. Specificatorul de tip typedef-nume are sintaxa:

typedef-nume:

declarator

ntr-o declaraie implicnd typedef fiecare identificator care apare ca parte a unui declarator devine sintactic echivalent cu cuvntul cheie rezervat pentru tipul asociat cu identificatorul. De exemplu, declaraia:

typedef int LENGTH;

l face pe LENGTH sinonim cu int. Tipul LENGTH poate fi folosit ulterior n declaraii n acelai mod ca i tipul int.

LENGTH len, maxlen;

LENGTH *length[];

n mod similar, declaraia:

typedef char *STRING;

l face pe STRING sinonim cu char*, adic pointer la caracter, care apoi poate fi utilizat n declaraii de tipul:

STRING p, lineptr[LINES], alloc();

Se observ c tipul care se declar prin typedef apare pe poziia numelui de variabil nu imediat dup cuvntul rezervat typedef. Sintactic typedef este sinonim cu clasele de memorie extern, static etc, dar nu rezerv memorie pentru variabilele respective.

Ca un exemplu mai complicat s relum declaraia unui nod al unui arbore, de data aceasta folosind typedef pentru a crea un nou nume pentru un tip structur (vezi seciunea 10.5).

typedef struct tnode {

char *word;

/* pointer la text */

int count;

/* numr apariii */

struct tnode *left;/* descendent stng */

struct tnode *right;/* descendent drept */

} TREENODE, *TREEPTR;

Aceast declaraie creeaz dou nume noi de tipuri, numite TREENODE, care este o structur i TREEPTR, care este un pointer la o structur. Atunci rutina talloc poate fi scris sub forma:

TREEPTR talloc() {

char *alloc();

return (TREEPTR)alloc(sizeof(TREENODE)));

}

Trebuie subliniat faptul c declaraia typedef nu creeaz noi tipuri n nici un caz; ea adaug doar sinonime pentru anumite tipuri de date, deja existente. Variabilele declarate n acest fel au exact aceleai proprieti ca i cele declarate explicit. De fapt, typedef se aseamn cu #define, cu excepia faptului c n timp ce #define este tratat de preprocesor, typedef este tratat de ctre compilator. De exemplu:

typedef int(*PFI)();

creeaz numele PFI pentru pointer la o funcie care returneaz un ntreg, tip care poate fi folosit ulterior ntr-un context de tipul:

PFI strcmp, numcmp, swap;

n programul de sortare din capitolul 9.

Exist dou motive principale care impun folosirea declaraiilor typedef. Primul este legat de problemele de portabilitate. Cnd se folosesc declaraii typedef pentru tipuri de date care snt dependente de main, atunci pentru o compilare pe un alt sistem de calcul este necesar modificarea doar a acestor declaraii nu i a datelor din program.

Al doilea const n faptul c prin crearea de noi nume de tipuri se ofer posibilitatea folosirii unor nume mai sugestive n program, deci o mai rapid nelegere a programului.

11. Intrri / ieiri

ntruct limbajul C nu a fost dezvoltat pentru un sistem particular de operare i datorit faptului c s-a dorit realizarea unei portabiliti ct mai mari, att a unui compilator C, ct i a programelor scrise n acest limbaj, el nu posed faciliti de intrare / ieire.

Exist totui un sistem de intrare / ieire (sistemul I/O) constituit dintr-un numr de subprograme care realizeaz funcii de intrare / ieire pentru programe scrise n C, dar care nu fac parte din limbajul C. Aceste subprograme se gsesc n biblioteca C.

Scopul acestui capitol este de a descrie cele mai utilizate subprograme de intrare / ieire i interfaa lor cu programele scrise n limbajul C.

11.1. Intrri i ieiri standard; fiiere

Sistemul I/O ofer utilizatorului trei "fiiere" standard de lucru. Cuvntul fiier a fost pus ntre ghilimele, deoarece limbajul nu definete acest tip de dat i pentru c fiierele reprezint mai degrab nite fluxuri de intrare / ieire standard puse la dispoziia utilizatorului. Aceste fiiere snt:

fiierul standard de intrare (stdin);

fiierul standard de ieire (stdout);

fiierul standard de afiare a mesajelor (stderr).

Toate aceste trei fiiere snt secveniale i n momentul execuiei unui program C snt implicit definite i deschise.

stdin i stdout snt asociate n mod normal terminalului de la care a fost lansat programul n execuie. Sistemul I/O permite redirectarea acestor fiiere pe alte periferice sau nchiderea lor dup lansarea programului. Redirectarea fiierului stdin se specific prin construcia:

specificator-fiiern linia de comand prin care a fost lansat programul.

Redirectarea fiierului stdout pe un alt periferic, n scopul efecturii unei operaii de adugare (append) se specific prin construcia :

>>specificator-fiier

stderr este ntotdeauna asociat terminalului de la care a fost lansat programul n execuie i nu poate fi redirectat.

Pentru a se putea face o referire la aceste fiiere orice program C trebuie s conin fiierul stdio.h, care se include printr-o linie de forma:

#include

dac acest fiier se afl n biblioteca standard.

Pentru claritatea i lizibilitatea programelor scrise n C, ct i pentru crearea unei imagini sugestive asupra lucrului cu fiiere, n fiierul de definiii standard stdio.h s-a definit un nou nume de tip de dat i anume FILE care este o structur. Pentru a referi un fiier, este necesar o declaraie de forma:

FILE *fp;

unde fp va fi numele de dat cu care se va referi fiierul n orice operaie de intrare / ieire asociat. Iat cteva informaii pstrate de structura FILE:

un identificator de fiier pe care sistemul de operare l asociaz fluxului pe durata prelucrrii; acesta poate fi aflat cu ajutorul funciei fileno;

adresele zonelor tampon asociate; poziia curent n aceste zone;

indicatorii de sfrit de fiier i de eroare;

alte informaii.

11.2. Accesul la fiiere; deschidere i nchidere

Nume

fopen - deschide un flux

Declaraie

FILE *fopen(const char *path,

const char *mode);

Descriere

Funcia fopen deschide fiierul al crui nume este un ir indicat de path i i asociaz un flux.

Argumentul mode indic un ir care ncepe cu una din secvenele urmtoare:

rdeschide un fiier pentru citire;

r+deschide pentru citire i scriere;

wtrunchiaz fiierul la lungime zero sau creeaz un fiier pentru scriere;

w+deschide pentru adugare la sfrit, n citire i scriere; fiierul este creat dac nu exist, altfel este trunchiat;

adeschide pentru adugare la sfrit, n scriere; fiierul este creat dac nu exist;

a+deschide pentru adugare la sfrit, n citire i scriere; fiierul este creat dac nu exist;

Dup deschidere, n primele patru cazuri indicatorul poziiei n flux este la nceputul fiierului, n ultimele dou la sfritul acestuia.

irul mode include de asemenea litera b (deschide un fiier binar) sau t (deschide un fiier text) fie pe ultima poziie fie pe cea din mijloc.

Operaiile de citire i scriere pot alterna n cazul fluxurilor read / write n orice ordine. S reinem c standardul ANSI C cere s existe o funcie de poziionare ntre o operaie de intrare i una de ieire, sau ntre o operaie de ieire i una de intrare, cu excepia cazului cnd o operaie de citire detecteaz sfritul de fiier. Aceast operaie poate fi inefectiv - cum ar fi fseek(flux, 0L, SEEK_CUR) apelat cu scop de sincronizare.

Valori returnate

n caz de succes se returneaz un pointer de tip FILE. n caz de eroare se returneaz NULL i variabila global errno indic codul erorii.

Nume

fclose - nchide un flux

Declaraie

int fclose( FILE *flux);

Descriere

Funcia fclose nchide fiierul asociat fluxului flux. Dac flux a fost deschis pentru ieire, orice date aflate n zone tampon snt scrise n fiier n prealabil cu un apel fflush.

Valori returnate

n caz de succes se returneaz 0. n caz de eroare se returneaz EOF i variabila global errno indic codul erorii.

Nume

tmpfile - creeaz un fiier temporar

Declaraie

FILE *tmpfile();

Descriere

Funcia tmpfile genereaz un nume unic de fiier temporar. Acesta este deschis n mod binar pentru scriere / citire ("wb+"). Fiierul va fi ters automat la nchidere sau la terminarea programului.

Valoare returnat

Funcia returneaz un descriptor de flux n caz de succes, sau NULL dac nu poate fi generat un nume unic de fiier sau dac fiierul nu poate fi deschis. n caz de eroare variabila global errno indic codul erorii.

Nume

fflush - foreaz scrierea n flux

Declaraie

int fflush(FILE *flux);

Descriere

Funcia fflush foreaz o scriere a tuturor datelor aflate n zone tampon ale fluxului flux. Fluxul rmne deschis.

Valori returnate

n caz de succes se returneaz 0. n caz de eroare se returneaz EOF i variabila global errno indic codul erorii.

Nume

fseek, ftell, rewind - repoziioneaz un flux

Declaraie

int fseek(FILE *flux, long offset,

int reper);

long ftell(FILE *flux);

void rewind(FILE *flux);

Descriere

Funcia fseek seteaz indicatorul de poziie pentru fiierul asociat fluxului flux. Noua poziie, dat n octei, se obine adunnd offset octei la poziia specificat de reper. Dac reper este SEEK_SET, SEEK_CUR, sau SEEK_END, offset este relativ la nceputul fiierului, poziia curent a indicatorului, respectiv sfritul fiierului. Funcia fseek