You are on page 1of 180

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. Generaliti asupra 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 <stdio.h> 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 <stdio.h> 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, ..., an1. n continuare se parcurge aceast list i se determin produsul valorilor strict pozitive. n final programul afieaz produsul calculat. #include <stdio.h> main() { int n,i; double a[100], p;
__________________________________________________________________________

scanf("%d",&n); for (i=0; i<n; i++) scanf("%lf",&a[i]); p = 1; for (i=0; i<n; i++) if (a[i]>0) p *= a[i]; printf("%lf\n",p); return 0; } n cadrul funciei main se declar dou variabile n i i care vor memora valori ntregi. Variabila n pstreaz numrul de valori reale din lista a. Se declar de asemenea un tablou unidimensional a care va memora 100 de valori de tip real (dubl precizie), i o variabil p care va memora produsul cerut. Se citete de la terminal o valoare n. n continuare se introduc valorile reale ai (i = 0, 1, ..., n Formatul de introducere "%lf" 1). indic faptul c se ateapt introducerea unei valori reale de la terminal, care va fi depus la locaia de memorie asociat variabilei ai. n locul construciei &a[i] se poate folosi forma echivalent a+i. Pentru a introduce toate valorile ai se efectueaz un ciclu for, n cadrul cruia variabila i (care controleaz ciclul) ia toate valorile ntre 0 (inclusiv) i n (exclusiv) cu pasul 1. Trecerea la urmtoarea valoare a variabilei i se face cu ajutorul operatorului ++. n continuare variabila p, care va memora produsul valorilor cerute, se iniializeaz cu 1. Fiecare valoare ai este verificat (instruciunea if) dac este strict pozitiv i n caz afirmativ este nmulit cu valoarea p. Operatorul *= are semnificaia nmulete cu. Al patrulea program este o ilustrare a unor probleme legate de capacitatea reprezentrilor valorilor de tip ntreg i virgul mobil. #include <stdio.h>
__________________________________________________________________________

int main() { short k,i; float a,b,c,u,v,w; i=240; k=i*i; printf("%hd\n",k); a=12345679; b=12345678; c=a*a-b*b; u=a*a; v=b*b; w=u-v; printf("%f %f\n",c,w); if (c==w) return 0; else return 1; } Variabila k, care ar trebui s memoreze valoarea 57600, are tipul ntreg scurt (short), pentru care domeniul de valori este restrns la 32768 32767. Astfel c valoarea 1110000100000000(2) (n zecimal 57600), n reprezentare ntreag cu semn este de fapt 7936. Al doilea set de operaii necesit o analiz mai atent; explicaiile snt valabile pentru programe care ruleaz pe arhitecturi Intel. Variabila c, care ar trebui s memoreze valoarea 2461357 (rezultatul corect), va avea valoarea 2461356, deoarece tipul float are rezervate pentru mantis doar 24 de cifre binare. Rezultatul este foarte apropiat de cel corect deoarece rezultatele intermediare se pstreaz n regitrii coprocesorului matematic cu precizie maxim. Abia la memorare se efectueaz trunchierea, de unde rezult valoarea afiat. Cu totul altfel stau lucrurile n cazul celui de al treilea set de operaii. Aici rezultatele intermediare snt memorate de fiecare dat cu trunchiere n variabile de tip float. n final se calculeaz i diferena dintre cele dou valori trunchiate, de unde rezult valoarea 16777216. nainte de terminare se verific dac valorile c i w snt egale. n caz afirmativ se comunic sistemului de operare un cod 0 (terminare normal). n caz contrar se comunic un cod 1 (terminare anormal). Rulai acest program pe diferite sisteme de calcul i observai care este rezultatul.
__________________________________________________________________________

1.3. Meta-limbajul i setul de caractere


Meta-limbajul care servete la descrierea formal a sintaxei limbajului C este simplu. Categoriile sintactice sau noiunile care trebuie definite snt urmate de simbolul ':'. Definiiile alternative de categorii snt listate pe linii separate. Dac o linie nu este suficient, se trece la linia urmtoare, aliniat la un tab fa de linia precedent. Un simbol opional, terminal sau neterminal este indicat prin adugarea imediat dup el a configuraiei de caractere <opt>. Setul de caractere al limbajului C este un subset al setului de caractere ASCII, format din: 26 litere mici abcdefghijklmnopqrstuvwxyz 26 litere mari ABCDEFGHIJKLMNOPQRSTUVWXYZ 10 cifre 0123456789 30 simboluri speciale Blanc ! " # % & ' ( ) * + , . / : ; < = > ? [ \ ] ^ _ ~ { | } 6 simboluri negrafice \n, \t, \b, \r, \f, \a

__________________________________________________________________________

2. Unitile lexicale ale limbajului C


n limbajul C exist ase tipuri de uniti lexicale: identificatori, cuvinte-cheie, constante, iruri, operatori i separatori.

2.1. Identificatori
Un identificator este o succesiune de litere i cifre dintre care primul caracter este n mod obligatoriu o liter. Se admit i litere mari i litere mici dar ele se consider caractere distincte. Liniua de subliniere _ este considerat ca fiind liter. Deci alfabetul peste care snt definii identificatorii este urmtorul: A = <a,...,z,A,...,Z,0,...,9,_>

2.2. Cuvinte cheie


Cuvintele cheie snt identificatori rezervai limbajului. Ei au o semnificaie bine determinat i nu pot fi utilizai dect aa cum cere sintaxa limbajului. Cuvintele-cheie se scriu obligatoriu cu litere mici. Aceste cuvinte snt (fiecare mediu de programare C poate folosi i alte cuvinte rezervate): int char float double long short unsigned register auto extern static struct union typedef if else for while do continue break const void switch case default return sizeof

__________________________________________________________________________

10

2.3. Constante
n limbajul C exist urmtoarele tipuri de constante: ntreg (zecimal, octal, hexazecimal), ntreg lung explicit, flotant, caracter, simbolic.

Constante ntregi
O constant ntreag const dintr-o succesiune de cifre. O constant octal este o constant ntreag care ncepe cu 0 (cifra zero), i este format cu cifre de la 0 la 7. O constant hexazecimal este o constant ntreag precedat de 0x sau 0X (cifra 0 i litera x). Cifrele hexazecimale includ literele de la A la F i de la a la f cu valori de la 10 la 15. n orice alt caz, constanta ntreag este o constant zecimal. Exemplu: constanta zecimal 31 poate fi scris ca 037 n octal i 0x1f sau 0X1F n hexazecimal. O constant ntreag este generat pe un cuvnt (doi sau patru octei, dac sistemul de calcul este pe 16 sau 32 de bii). O constant zecimal a crei valoare depete pe cel mai mare ntreg cu semn reprezentabil pe un cuvnt scurt (16 bii) se consider de tip long i este generat pe 4 octei. O constant octal sau hexazecimal care depete pe cel mai mare ntreg fr semn reprezentabil pe un cuvnt scurt se consider de asemenea de tip long. O constant ntreag devine negativ dac i se aplic operatorul unar de negativare -.

Constante de tip explicit


O constant ntreag zecimal, octal sau hexazecimal, urmat imediat de litera l sau L este o constant lung. Aceasta va fi generat n calculator pe 4 octei. Exemplu: 123L.

__________________________________________________________________________

11

O constant ntreag zecimal urmat imediat de litera u sau U este o constant de tip ntreg fr semn. Litera u sau U poate fi precedat de litera l sau L. Exemplu: 123lu.

Constante flotante
O constant flotant const dintr-o parte ntreag, un punct zecimal, o parte fracionar, litera e sau E i opional, un exponent care este un ntreg cu semn. Partea ntreag i partea fracionar snt constituite din cte o succesiune de cifre. ntr-o constant flotant, att partea ntreag ct i partea fracionar pot lipsi dar nu ambele; de asemenea poate lipsi punctul zecimal sau litera e i exponentul, dar nu deodat (i punctul i litera e i exponentul). Exemplu: 123.456e sau 0.12e 7 3 Orice constant flotant se consider a fi n precizie extins.

Constante caracter
O constant caracter const dintr-un singur caracter scris ntre apostrofuri, de exemplu 'x'. Valoarea unei constante caracter este valoarea numeric a caracterului, n setul de caractere al calculatorului. De exemplu n setul de caractere ASCII caracterul zero sau '0' are valoarea 48 n zecimal, total diferit de valoarea numeric zero. Constantele caracter particip la operaiile aritmetice ca i oricare alte numere. De exemplu, dac variabila c conine valoarea ASCII a unei cifre, atunci prin instruciunea: c = c - '0' ; aceast valoare se transform n valoarea efectiv a cifrei. Anumite caractere negrafice i caractere grafice ' (apostrof) i \ (backslash) pot fi reprezentate ca i constante caracter cu ajutorul aa numitor secvene de evitare. Secvenele de evitare ofer de altfel i un mecanism general pentru reprezentarea caracterelor mai greu de introdus n calculator i a oricror configuraii de bii. Aceste secvene de evitare snt:
__________________________________________________________________________

12

\n new-line \r carriage return \t tab orizontal \f form feed \b backspace \a semnal sonor \ddd configuraie de bii (ddd)

\\ backslash \' apostrof \" ghilimele

Aceste secvene, dei snt formate din mai multe caractere, ele reprezint n realitate un singur caracter. Secvena '\ddd' unde ddd este un ir de 1 pn la 3 cifre octale, genereaz pe un octet valoarea caracterului dorit sau a configuraiei de bii dorite, date de irul ddd. Exemplu: secvena '\040' va genera caracterul spaiu. Un caz special al acestei construcii este secvena '\0' care indic caracterul NULL, care este caracterul cu valoarea zero. '\0' este scris deseori n locul lui 0 pentru a sublinia natura de caracter a unei anumite expresii. Cnd caracterul care urmeaz dup un backslash nu este unul dintre cele specificate, backslash-ul este ignorat. Atragem atenia c toate caracterele setului ASCII snt pozitive, dar o constant caracter specificat printr-o secven de evitare poate fi i negativ, de exemplu '\377' are valoarea -1.

Constante simbolice
O constant simbolic este un identificator cu valoare de constant. Valoarea constantei poate fi orice ir de caractere introdus prin construcia #define (vezi capitolul 8). Exemplu: #define MAX 1000 Dup ntlnirea acestei construcii compilatorul va nlocui toate apariiile constantei simbolice MAX cu valoarea 1000. Numele constantelor simbolice se scriu de obicei cu litere mari (fr a fi obligatoriu).

__________________________________________________________________________

13

2.4. iruri
Un ir este o succesiune de caractere scrise ntre ghilimele, de exemplu "ABCD". Ghilimelele nu fac parte din ir; ele servesc numai pentru delimitarea irului. Caracterul " (ghilimele) poate aprea ntr-un ir dac se utilizeaz secvena de evitare \". n interiorul unui ir pot fi folosite i alte secvene de evitare pentru constante caracter, de asemenea poate fi folosit caracterul \ (backslash) la sfritul unui rnd pentru a da posibilitatea continurii unui ir pe mai multe linii, situaie n care caracterul \ nsui va fi ignorat. Pentru irul de caractere se mai folosete denumirea constant ir sau constant de tip ir. Cnd un ir apare ntr-un program C, compilatorul creeaz un masiv de caractere care conine caracterele irului i plaseaz automat caracterul NULL ('\0') la sfritul irului, astfel ca programele care opereaz asupra irurilor s poat detecta sfritul acestora. Aceast reprezentare nseamn c, teoretic, nu exist o limit a lungimii unui ir, iar programele trebuie s parcurg irul, analizndu-l pentru a-i determina lungimea. Se admit i iruri de lungime zero. Tehnic, un ir este un masiv ale crui elemente snt caractere. El are tipul masiv de caractere i clasa de memorie static (vezi seciunea 3.1). Un ir este iniializat cu caracterele date (vezi seciunea 5.4). La alocare, memoria fizic cerut este cu un octet mai mare dect numrul de caractere scrise ntre ghilimele, datorit adugrii automate a caracterului null la sfritul fiecrui ir. Exemplu. Funcia strlen(s) returneaz lungimea irului de caractere s, excluznd caracterul terminal null. int strlen(char s[]) { /* returneaz lungimea irului */ int i; i=0; while (s[i]!='\0') ++i;
__________________________________________________________________________

14

return i; } Atragem atenia asupra diferenei dintre o constant caracter i un ir care conine un singur caracter. "x" nu este acelai lucru cu 'x'. 'x' este un singur caracter, folosit pentru a genera pe un octet valoarea numeric a literei x, din setul de caractere al calculatorului. "x" este un ir de caractere, care n calculator se reprezint pe doi octei, dintre care primul conine un caracter (litera x), iar al doilea caracterul NULL care indic sfritul de ir.

2.5. Operatori
Limbajul C prezint un numr mare de operatori care pot fi clasificai dup diverse criterii. Exist operatori unari, binari i ternari, operatori aritmetici, logici, operatori pe bii etc. ntr-un capitol separat vom prezenta clasele de operatori care corespund la diferite nivele de prioritate.

2.6. Separatori
Un separator este un caracter sau un ir de caractere care separ unitile lexicale ntr-un program scris n C. Separatorul cel mai frecvent este aa numitul spaiu alb (blanc) care conine unul sau mai multe spaii, tab-uri, new-line-uri sau comentarii. Aceste construcii snt eliminate n faza de analiza lexical a compilrii. Dm mai jos lista separatorilor admii n limbajul C. ( ) { } [ ] Parantezele mici ncadreaz lista de argumente ale unei funcii sau delimiteaz anumite pri n cadrul expresiilor aritmetice etc Acoladele ncadreaz instruciunile compuse, care constituie corpul unor instruciuni sau corpul funciilor Parantezele mari ncadreaz dimensiunile de masiv sau
15

__________________________________________________________________________

" " ' ' ; /* */

indicii elementelor de masiv Ghilimelele ncadreaz un ir de caractere Apostrofurile ncadreaz un singur caracter sau o secven de evitare Punct i virgula termin o instruciune Slash asterisc nceput de comentariu Asterisc slash sfrit de comentariu

Un comentariu este un ir de caractere care ncepe cu caracterele /* i se termin cu caracterele */. Un comentariu poate s apar oriunde ntr-un program, unde poate aprea un blanc i are rol de separator; el nu influeneaz cu nimic semnificaia programului, scopul lui fiind doar o documentare a programului. Nu se admit comentarii imbricate.

__________________________________________________________________________

16

3. Variabile
O variabil este un obiect de programare cruia i se atribuie un nume. Variabilele ca i constantele snt elemente de baz cu care opereaz un program scris n C. Variabilele se deosebesc dup denumire i pot primi diferite valori. Numele variabilelor snt identificatori. Numele de variabile se scriu de obicei cu litere mici (fr a fi obligatoriu). Variabilele n limbajul C snt caracterizate prin dou atribute: clas de memorie i tip. Aceste dou atribute i snt atribuite unei variabile prin intermediul unei declaraii. Declaraiile listeaz variabilele care urmeaz a fi folosite, stabilesc clasa de memorie, tipul variabilelor i eventual valorile iniiale, dar despre declaraii vom discuta n capitolul 5.

3.1. Clase de memorie


Limbajul C prezint patru clase de memorie: automatic, extern, static, registru.

Variabile automatice
Variabilele automatice snt variabile locale fiecrui bloc (seciunea 6.2) sau funcii (capitolul 7). Ele se declar prin specificatorul de clas de memorie auto sau implicit prin context. O variabil care apare n corpul unei funcii sau al unui bloc pentru care nu s-a fcut nici o declaraie de clas de memorie se consider implicit de clas auto. O variabil auto este actualizat la fiecare intrare n bloc i se distruge n momentul cnd controlul a prsit blocul. Ele nu i rein valorile de la un apel la altul al funciei sau blocului i trebuie iniializate la fiecare intrare. Dac nu snt iniializate, conin valori
__________________________________________________________________________

17

reziduale. Nici o funcie nu are acces la variabilele auto din alt funcie. n funcii diferite pot exista variabile locale cu aceleai nume, fr ca variabilele s aib vreo legtur ntre ele.

Variabile externe
Variabilele externe snt variabile cu caracter global. Ele se definesc n afara oricrei funcii i pot fi apelate prin nume din oricare funcie care intr n alctuirea programului. n declaraia de definiie aceste variabile nu necesit specificarea nici unei clase de memorie. La ntlnirea unei definiii de variabil extern compilatorul aloc i memorie pentru aceast variabil. ntr-un fiier surs domeniul de definiie i aciune al unei variabile externe este de la locul de declaraie pn la sfritul fiierului. Aceste variabile exist i i pstreaz valorile de-a lungul execuiei ntregului program. Pentru ca o funcie s poat utiliza o variabil extern, numele variabilei trebuie fcut cunoscut funciei printr-o declaraie. Declaraia poate fi fcut fie explicit prin utilizarea specificatorului extern, fie implicit prin context. Dac definiia unei variabile externe apare n fiierul surs naintea folosirii ei ntr-o funcie particular, atunci nici o declaraie ulterioar nu este necesar, dar poate fi fcut. Dac o variabil extern este referit ntr-o funcie nainte ca ea s fie definit, sau dac este definit ntr-un fiier surs diferit de fiierul n care este folosit, atunci este obligatorie o declaraie extern pentru a lega apariiile variabilelor respective. Dac o variabil extern este definit ntr-un fiier surs diferit de cel n care ea este referit, atunci o singur declaraie extern dat n afara oricrei funcii este suficient pentru toate funciile care urmeaz declaraiei. Funciile snt considerate n general variabile externe afar de cazul cnd se specific altfel.
__________________________________________________________________________

18

Variabilele externe se folosesc adeseori n locul listelor de argumente pentru a comunica date ntre funcii, chiar dac funciile snt compilate separat.

Variabile statice
Variabilele statice se declar prin specificatorul de clas de memorie static. Aceste variabile snt la rndul lor de dou feluri: interne i externe. Variabilele statice interne snt locale unei funcii i se definesc n interiorul unei funcii, dar spre deosebire de variabilele auto, ele i pstreaz valorile tot timpul execuiei programului. Variabilele statice interne nu snt create i distruse de fiecare dat cnd funcia este activat sau prsit; ele ofer n cadrul unei funcii o memorie particular permanent pentru funcia respectiv. Alte funcii nu au acces la variabilele statice interne proprii unei funcii. Ele pot fi declarate i implicit prin context; de exemplu irurile de caractere care apar n interiorul unei funcii cum ar fi argumentele funciei printf (vezi capitolul 11) snt variabile statice interne. Variabilele statice externe se definesc n afara oricrei funcii i orice funcie are acces la ele. Aceste variabile snt ns globale numai pentru fiierul surs n care ele au fost definite. Nu snt recunoscute n alte fiiere. n concluzie, variabila static este extern dac este definit n afara oricrei funcii i este static intern dac este definit n interiorul unei funcii. n general, funciile snt considerate obiecte externe. Exist ns i posibilitatea s declarm o funcie de clas static. Aceasta face ca numele funciei s nu fie recunoscut n afara fiierului n care a fost declarat.

__________________________________________________________________________

19

Variabile registru
O variabil registru se declar prin specificatorul de clas de memorie register. Ca i variabilele auto ele snt locale unui bloc sau funcii i valorile lor se pierd la ieirea din blocul sau funcia respectiv. Variabilele declarate register indic compilatorului c variabilele respective vor fi folosite foarte des. Dac este posibil, variabilele register vor li plasate de ctre compilator n regitrii rapizi ai calculatorului, ceea ce conduce la programe mai compacte i mai rapide. Variabile register pot fi numai variabilele automatice sau parametrii formali ai unei funcii. Practic exist cteva restricii asupra variabilelor register care reflect realitatea hardware-ului de baz. Astfel: numai cteva variabile din fiecare funcie pot fi pstrate n regitri (de obicei 2 sau 3); declaraia register este ignorat pentru celelalte variabile; numai tipurile de date int, char i pointer snt admise; nu este posibil referirea la adresa unei variabile register.

3.2. Tipuri de variabile


Limbajul C admite numai cteva tipuri fundamentale de variabile: caracter, ntreg, flotant.

Tipul caracter
O variabil de tip caracter se declar prin specificatorul de tip char. Zona de memorie alocat unei variabile de tip char este de un octet. Ea este suficient de mare pentru a putea memora orice caracter al setului de caractere implementate pe calculator. Dac un caracter din setul de caractere este memorat ntr-o variabil de tip char, atunci valoarea sa este egal cu codul ntreg al caracterului respectiv. i alte cantiti pot fi memorate n variabile de tip char, dar implementarea este dependent de sistemul de calcul.
__________________________________________________________________________

20

Ordinul de mrime al variabilelor caracter este ntre -128 i 127. Caracterele setului ASCII snt toate pozitive, dar o constant caracter specificat printr-o secven de evitare poate fi i negativ, de exemplu '\377' are valoarea -1. Acest lucru se ntmpl atunci cnd aceast constant apare ntr-o expresie, moment n care se convertete la tipul int prin extensia bitului cel mai din stnga din octet (datorit modului de funcionare a instruciunilor calculatorului).

Tipul ntreg
Variabilele ntregi pozitive sau negative pot fi declarate prin specificatorul de tip int. Zona de memorie alocat unei variabile ntregi poate fi de cel mult trei dimensiuni. Relaii despre dimensiune snt furnizate de calificatorii short, long i unsigned, care pot fi aplicai tipului int. Calificatorul short se refer totdeauna la numrul minim de octei pe care poate fi reprezentat un ntreg, n cazul nostru 2. Calificatorul long se refer la numrul maxim de octei pe care poate fi reprezentat un ntreg, n cazul nostru 4. Tipul int are dimensiunea natural sugerat de sistemul de calcul. Scara numerelor ntregi reprezentabile n main depinde de asemenea de sistemul de calcul: un ntreg poate lua valori ntre -32768 i 32767 (sisteme de calcul pe 16 bii) sau ntre -2147483648 i 2147483647 (sisteme de calcul pe 32 de bii). Calificatorul unsigned alturi de declaraia de tip int determin ca valorile variabilelor astfel declarate s fie considerate ntregi fr semn. Numerele de tipul unsigned respect legile aritmeticii modulo 2n, unde n este numrul de bii din reprezentarea unei variabile de tip int. Numerele de tipul unsigned snt totdeauna pozitive. Declaraiile pentru calificatori snt de forma: short int x; long int y; unsigned int z;
__________________________________________________________________________

21

Cuvntul int poate fi omis n aceste situaii.

Tipul flotant (virgul mobil)


Variabilele flotante pot fi n simpl precizie i atunci se declar prin specificatorul de tip float sau n dubl precizie i atunci se declar prin specificatorul double. Majoritatea sistemelor de calcul admit i reprezentarea n precizie extins; o variabil n precizie extins se declar prin specificatorul long double.

Tipul void (tip neprecizat)


Un pointer poate fi declarat de tip void. n aceast situaie pointerul nu poate fi folosit pentru indirectare (derefereniere) fr un cast explicit, i aceasta deoarece compilatorul nu poate determina mrimea obiectului pe care pointerul l indic. int x; float r; void *p = &x; int main() { *(int *) p = 2; p = &r; *(float *)p = 1.1; }

/* p indic pe x */ /* p indic pe r */

Dac o funcie este declarat de tip void, aceasta nu va returna o valoare: void hello(char *name) { printf("Hello, %s.",name); }

Tipuri derivate
n afar de tipurile aritmetice fundamentale, exist, n principiu, o clas infinit de tipuri derivate, construite din tipurile fundamentale n urmtoarele moduri:
__________________________________________________________________________

22

masive de T pentru masive de obiecte de un tip dat T, unde T este unul dintre tipurile admise; funcii care returneaz T pentru funcii care returneaz obiecte de un tip dat T; pointer la T pentru pointeri la obiecte de un tip dat T; structuri pentru un ir de obiecte de tipuri diferite; reuniuni care pot conine obiecte de tipuri diferite, tratate ntr-o singur zon de memorie. n general aceste metode de construire de noi tipuri de obiecte pot fi aplicate recursiv. Amnunte despre tipurile derivate snt date n seciunea 5.3.

3.3. Obiecte i valori-stnga


Alte dou noiuni folosite n descrierea limbajului C snt obiectul i valoarea-stnga. Un obiect este coninutul unei zone de memorie. O valoare-stnga este o expresie care se refer la un obiect. Un exemplu evident de valoare-stnga este un identificator. Exist operatori care produc valori-stnga: de exemplu, dac E este o expresie de tip pointer, atunci *E este o expresie valoare-stnga care se refer la obiectul pe care-l indic E. Numele valoare-stnga (n limba englez left value) a fost sugerat din faptul c n expresia de atribuire E1 = E2 operandul stng E1 trebuie s fie o expresie valoare-stnga. n paragraful de descriere a operatorilor se va indica dac operanzii snt valori-stnga sau dac rezultatul operaiei este o valoare-stnga.

3.4. Conversii de tip


Un numr mare de operatori pot cauza conversia valorilor unui operand de la un tip la altul. Dac ntr-o expresie apar operanzi de diferite tipuri, ei snt convertii la un tip comun dup un mic numr de reguli. n general se fac automat numai conversiile care an sens, de exemplu: din ntreg n flotant, ntr-o expresie de forma f+i.
__________________________________________________________________________

23

Expresii care nu au sens, ca de exemplu un numr flotant ca indice, nu snt admise.

Caractere i ntregi
Un caracter poate aprea oriunde unde un ntreg este admis. n toate cazurile valoarea caracterului este convertit automat ntr-un ntreg. Deci ntr-o expresie aritmetic tipul char i int pot aprea mpreun. Aceasta permite o flexibilitate considerabil n anumite tipuri de transformri de caractere. Un astfel de exemplu este funcia atoi descris n seciunea 7.5 care convertete un ir de cifre n echivalentul lor numeric. Expresia: s[i] - '0' produce valoarea numeric a caracterului (cifr) memorat n ASCII. Atragem atenia c atunci cnd o variabil de tip char este convertit la tipul int, se poate produce un ntreg negativ, dac bitul cel mai din stnga al octetului conine 1. Caracterele din setul de caractere ASCII nu devin niciodat negative, dar anumite configuraii de bii memorate n variabile de tip caracter pot aprea ca negative prin extensia la tipul int. Conversia tipului int n char se face cu pierderea biilor de ordin superior. ntregii de tip short snt convertii automat la int. Conversia ntregilor se face cu extensie de semn; ntregii snt totdeauna cantiti cu semn. Un ntreg long este convertit la un ntreg short sau char prin trunchiere la stnga; surplusul de bii de ordin superior se pierde.

Conversii flotante
Toate operaiile aritmetice n virgul mobil se execut n precizie extins. Conversia de la float la int se face prin trunchierea prii fracionare. Conversia de la int la float este acceptat.
__________________________________________________________________________

24

ntregi fr semn
ntr-o expresie n care apar doi operanzi, dintre care unul unsigned iar cellalt un ntreg de orice alt tip, ntregul cu semn este convertit n ntreg fr semn i rezultatul este un ntreg fr semn. Cnd un int trece n unsigned, valoarea sa este cel mai mic ntreg fr semn congruent cu ntregul cu semn (modulo 216 sau 232). ntr-o reprezentare la complementul fa de 2 (deci pentru numere negative), conversia este conceptual, nu exist nici o schimbare real a configuraiei de bii. Cnd un ntreg fr semn este convertit la long, valoarea rezultatului este numeric aceeai ca i a ntregului fr semn, astfel conversia nu face altceva dect s adauge zerouri la stnga.

Conversii aritmetice
Dac un operator aritmetic binar are doi operanzi de tipuri diferite, atunci tipul de nivel mai sczut este convertit la tipul de nivel mai nalt nainte de operaie. Rezultatul este de tipul de nivel mai nalt. Ierarhia tipurilor este urmtoarea: char < short < int < long; float < double < long double; tip ntreg cu semn < tip ntreg fr semn; tip ntreg < virgul mobil.

Conversii prin atribuire


Conversiile de tip se pot face prin atribuire; valoarea membrului drept este convertit la tipul membrului stng, care este tipul rezultatului.

Conversii logice
Expresiile relaionale de forma i<j i expresiile logice legate prin operatorii && i || snt definite ca avnd valoarea 1 dac snt adevrate i 0 dac snt false.
__________________________________________________________________________

25

Astfel atribuirea: d = (c>='0') && (c<='9'); l face pe d egal cu 1 dac c este cifr i egal cu 0 n caz contrar.

Conversii explicite
Dac conversiile de pn aici le-am putea considera implicite, exist i conversii explicite de tipuri pentru orice expresie. Aceste conversii se fac prin construcia special numit cast de forma: (nume-tip) expresie n aceast construcie expresie este convertit la tipul specificat dup regulile precizate mai sus. Mai precis aceasta este echivalent cu atribuirea expresiei respective unei variabile de un tip specificat, i aceast nou variabil este apoi folosit n locul ntregii expresii. De exemplu, n expresia: sqrt((double)n) se convertete n la double nainte de a se transmite funciei sqrt. Notm ns c, coninutul real al lui n nu este alterat. Operatorul cast are aceeai preceden ca i oricare operator unar.

Expresia constant
O expresie constant este o expresie care conine numai constante. Aceste expresii snt evaluate n momentul compilrii i nu n timpul execuiei; ele pot fi astfel utilizate n orice loc unde sintaxa cere o constant, ca de exemplu: #define MAXLINE 1000 char line[MAXLINE+1];

__________________________________________________________________________

26

4. Operatori i expresii
Limbajul C prezint un numr mare de operatori, caracterizai prin diferite nivele de prioritate sau preceden. n acest capitol descriem operatorii n ordinea descresctoare a precedenei lor. Operatorii descrii n acelai paragraf au aceeai preceden. Vom specifica de fiecare dat dac asociativitatea este la stnga sau la dreapta. Expresiile combin variabile i constante pentru a produce valori noi i le vom introduce pe msur ce vom prezena operatorii.

4.1. Expresii primare


Expresie-primar: identificator constant ir (expresie) expresie-primar [expresie] expresie-primar (list-expresii<opt>) valoare-stnga . identificator expresie-primar -> identificator List-expresii: expresie list-expresii, expresie Un identificator este o expresie-primar, cu condiia c el s fi fost declarat corespunztor. Tipul su este specificat n declaraia sa. Dac tipul unui identificator este masiv de ..., atunci valoarea expresiei-identificator este un pointer la primul obiect al masivului, iar tipul expresiei este pointer la .... Mai mult, un identificator de masiv nu este o expresie valoare-stnga.
__________________________________________________________________________

27

La fel, un identificator declarat de tip funcie care returneaz ..., care nu apare pe poziie de apel de funcie este convertit la pointer la funcie care returneaz .... O constant este o expresie-primar. Tipul su poate fi int, long sau double. Constantele caracter snt de tip int, constantele flotante snt de tip long double. Un ir este o expresie-primar. Tipul su original este masiv de caractere, dar urmnd aceleai reguli descrise mai sus pentru identificatori, acesta este modificat n pointer la caracter i rezultatul este un pointer la primul caracter al irului. Exist cteva excepii n anumite iniializri (vezi paragraful 5.4). O expresie ntre paranteze rotunde este o expresie-primar, al crei tip i valoare snt identice cu cele ale expresiei din interiorul parantezelor (expresia din paranteze poate fi i o valoare-stnga). O expresie-primar urmat de o expresie ntre paranteze ptrate este o expresie-primar. Sensul intuitiv este de indexare. De obicei expresia-primar are tipul pointer la ..., expresia-indice are tipul int, iar rezultatul are tipul .... O expresie de forma E1[E2] este identic (prin definiie) cu *((E1)+(E2)), unde * este operatorul de indirectare. Un apel de funcie este o expresie-primar. Ea const dintr-o expresie-primar urmat de o pereche de paranteze rotunde, care conin o list-expresii separate prin virgule. Lista-expresii constituie argumentele reale ale funciei; aceast list poate fi i vid. Expresiaprimar trebuie s fie de tipul funcie care returneaz ..., iar rezultatul apelului de funcie va fi de tipul .... naintea apelului, oricare argument de tip float este convertit la tipul double, oricare argument de tip char sau short este convertit la tipul int. Numele de masive snt convertite n pointeri la nceputul masivului. Nici o alt conversie nu se efectueaz automat. Dac este necesar pentru ca tipul unui argument actual s coincid cu cel al argumentului formal, se va folosi un cast (vezi seciunea 3.4). Snt permise apeluri recursive la orice funcie.
__________________________________________________________________________

28

O valoare-stnga urmat de un punct i un identificator este o expresie-primar. Valoarea-stnga denumete o structur sau o reuniune (vezi capitolul 10) iar identificatorul denumete un membru din structur sau reuniune. Rezultatul este o valoare-stnga care se refer la membrul denumit din structur sau reuniune. O expresie-primar urmat de o sgeat (constituit dintr-o liniu i semnul > urmat de un identificator este o expresie-primar. Prima expresie trebuie s fie un pointer la o structur sau reuniune, iar identificatorul trebuie s fie numele unui membru din structura sau reuniunea respectiv. Rezultatul este o valoare-stnga care se refer la membrul denumit din structura sau reuniunea ctre care indic expresia pointer. Expresia E1>E2 este identic din punctul de vedere al rezultatului cu (*E1). E2 Descriem n continuare operatorii limbajului C mpreun cu expresiile care se pot constitui cu aceti operatori.

4.2. Operatori unari


Toi operatorii unari au aceeai preceden, iar expresiile unare se grupeaz de la dreapta la stnga. Expresie-unar: * expresie & valoare-stnga expresie ! expresie ~ expresie ++ valoare-stnga -- valoare-stnga valoare-stnga ++ valoare-stnga -(nume-tip) expresie sizeof (nume-tip)
__________________________________________________________________________

29

Operatorul unar * este operatorul de indirectare. Expresia care-l urmeaz trebuie s fie un pointer, iar rezultatul este o valoare-stnga care se refer la obiectul ctre care indic expresia. Dac tipul expresiei este pointer la ... atunci tipul rezultatului este .... Acest operator trateaz operandul su ca o adres, face acces la ea i i obine coninutul. Exemplu: instruciunea y = *px; atribuie lui y coninutul adresei ctre care indic px. Operatorul unar & este operatorul de obinere a adresei unui obiect sau de obinere a unui pointer la obiectul respectiv. Operandul este o valoare-stnga iar rezultatul este un pointer la obiectul referit de valoarea-stnga. Dac tipul valorii-stnga este ... atunci tipul rezultatului este pointer la .... Exemplu. Fie x o variabil de tip int i px un pointer creat ntrun anumit fel (vezi capitolul 9). Atunci prin instruciunea px = &x; se atribuie variabilei de tip pointer la int px adresa variabilei x; putem spune acum c px indic spre x. Secvena: px = &x; y = *px; este echivalent cu y = x; Operatorul & poate fi aplicat numai la variabile i la elemente de masiv. Construcii de forma &(x+1) i &3 nu snt admise. De asemenea nu se admite ca variabila s fie de clas register. Operatorul unar & ajut la transmiterea argumentelor de tip adres n funcii. Operatorul unar - este operatorul de negativare. Operandul su este o expresie, iar rezultatul este negativarea operandului. n acest caz snt aplicate conversiile aritmetice obinuite. Negativarea unui ntreg de tip unsigned se face scznd valoarea sa din 2n, unde n este numrul de bii rezervai tipului int. Operatorul unar ! este operatorul de negare logic. Operandul su este o expresie, iar rezultatul su este 1 sau 0 dup cum valoarea operandului este 0 sau diferit de zero. Tipul rezultatului este int.
__________________________________________________________________________

30

Acest operator este aplicabil la orice expresie de tip aritmetic sau la pointeri. Operatorul unar ~ (tilda) este operatorul de complementare la unu. El convertete fiecare bit 1 la 0 i invers. El este un operator logic pe bii. Operandul su trebuie s fie de tip ntreg. Se aplic conversiile aritmetice obinuite. Operatorul unar ++ este operatorul de incrementare. Operandul su este o valoare-stnga. Operatorul produce incrementarea operandului cu 1. Acest operator prezint un aspect deosebit deoarece el poate fi folosit ca un operator prefix (naintea variabilei: ++n) sau ca un operator postfix (dup variabil: n++). n ambele cazuri efectul este incrementarea lui n. Dar expresia ++n incrementeaz pe n nainte de folosirea valorii sale, n timp ce n++ incrementeaz pe n dup ce valoarea sa a fost utilizat. Aceasta nseamn c n contextul n care se urmrete numai incrementarea lui n, oricare construcie poate fi folosit, dar ntr-un context n care i valoarea lui n este folosit ++n i n++ furnizeaz dou valori distincte. Exemplu: dac n este 5, atunci x = n++ ; atribuie lui x valoarea 5 x = ++n ; atribuie lui x valoarea 6 n ambele cazuri n devine 6. Rezultatul operaiei nu este o valoare-stnga, dar tipul su este tipul valorii-stnga. Operatorul unar -- este operatorul de decrementare. Acest operator este analog cu operatorul ++ doar c produce decrementarea cu 1 a operandului. Operatorul (nume-tip) este operatorul de conversie de tip. Prin nume-tip nelegem unul dintre tipurile fundamentale admise n C. Operandul acestui operator este o expresie. Operatorul produce conversia valorii expresiei la tipul denumit. Aceast construcie se numete cast. Operatorul sizeof furnizeaz dimensiunea n octei a operandului su. Aplicat unui masiv sau structuri, rezultatul este
__________________________________________________________________________

31

numrul total de octei din masiv sau structur. Dimensiunea se determin n momentul compilrii, din declaraiile obiectelor din expresie. Semantic, aceast expresie este o constant ntreag care se poate folosi n orice loc n care se cere o constant. Cea mai frecvent utilizare o are n comunicarea cu rutinele de alocare a memoriei sau rutinele I/O sistem. Operatorul sizeof poate fi aplicat i unui nume-tip ntre paranteze. n acest caz el furnizeaz dimensiunea n octei a unui obiect de tipul indicat. Construcia sizeof(nume-tip) este luat ca o unitate, astfel c expresia sizeof(nume-tip)-2 este acelai lucru cu (sizeof(nume-tip))-2

4.3. Operatori multiplicativi


Operatorii multiplicativi * / i % snt operatori aritmetici binari i se grupeaz de la stnga la dreapta. Expresie-multiplicativ: expresie * expresie expresie / expresie expresie % expresie Operatorul binar * indic nmulirea. Operatorul este asociativ, dar n expresiile n care apar mai muli operatori de nmulire, ordinea de evaluare nu se specific. Compilatorul rearanjeaz chiar i un calcul cu paranteze. Astfel a*(b*c) poate fi evaluat ca (a*b)*c. Aceasta nu implic diferene, dar dac totui se dorete o anumit ordine, atunci se vor introduce variabile temporare. Operatorul binar / indic mprirea. Cnd se mpart dou numere ntregi pozitive, trunchierea se face spre zero; dac unul dintre operanzi este negativ atunci trunchierea depinde de sistemul de calcul.
__________________________________________________________________________

32

Operatorul binar % furnizeaz restul mpririi primei expresii la cea de a doua. Operanzii nu pot fi de tip float. Restul are totdeauna semnul dempritului. Totdeauna (a/b)*b+a%b este egal cu a (dac b este diferit de 0). Snt executate conversiile aritmetice obinuite.

4.4. Operatori aditivi


Operatorii aditivi + i - snt operatori aritmetici binari i se grupeaz de la stnga la dreapta. Se execut conversiile aritmetice obinuite, Expresie-aditiv: expresie + expresie expresie - expresie Operatorul binar + produce suma operanzilor si. El este asociativ i expresiile care conin mai muli operatori pot fi rearanjate la fel ca n cazul operatorului de nmulire. Operatorul binar - produce diferena operanzilor si.

4.5. Operatori de deplasare


Operatorii de deplasare << i >> snt operatori logici pe bii. Ei se grupeaz de la stnga la dreapta. Expresie-deplasare: expresie << expresie expresie >> expresie Operatorul << produce deplasarea la stnga a operandului din stnga cu un numr de poziii binare dat de operandul din dreapta. 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
__________________________________________________________________________

33

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 E1<<E2 este E1 (interpretat ca i configuraie de bii) deplasat la stnga cu E2 poziii bit; biii eliberai devin zero. Expresia E1>>E2 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<<2 deplaseaz pe x la stnga cu 2 poziii, biii eliberai devin 0; aceasta este echivalent cu multiplicarea lui x cu 4.

4.6. Operatori relaionali


Operatorii relaionali <, >, <=, >= se grupeaz de la stnga la dreapta. Expresie-relaional: expresie < expresie expresie > expresie expresie <= expresie expresie >= expresie Operatorii < (mai mic), > (mai mare), <= (mai mic sau egal) i >= (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<x-1 se consider i<(x-1) aa dup cum ne ateptam.

__________________________________________________________________________

34

4.7. Operatori de egalitate


Expresie-egalitate: expresie == expresie expresie != expresie Operatorii == (egal cu) i != (diferit de) snt analogi cu operatorii relaionali, dar precedena lor este mai mic. Astfel a<b == c<d este 1, dac a<b i c<d au aceeai valoare de adevr.

4.8. Operatorul I pe bii


Expresie-I: expresie & expresie Operatorul & este operatorul I logic pe bii. El este asociativ i expresiile care conin operatorul & pot fi rearanjate. Rezultatul este funcia logic I pe bii aplicat operanzilor si. Operatorul se aplic numai la operanzi de tipuri ntregi. Legea dup care funcioneaz este: & 0 1 0 0 0 1 0 1

Operatorul & este deseori folosit pentru a masca o anumit mulime de bii: de exemplu: c = n & 0177; pune pe zero toi biii afar de ultimii 7 bii de ordin inferior ai lui n, fr a afecta coninutul lui n.

4.9. Operatorul SAU-exclusiv pe bii


Expresie-SAU-exclusiv: expresie ^ expresie Operatorul ^ este operatorul SAU-exclusiv logic pe bii. El este asociativ i expresiile care-l conin pot fi rearanjate. Rezultatul este
__________________________________________________________________________

35

funcia logic SAU-exclusiv pe bii aplicat operanzilor si. Operatorul se aplic numai la operanzi de tipuri ntregi. Legea dup care funcioneaz este: ^ 0 1 0 0 1 1 1 0

4.10. Operatorul SAU-inclusiv pe bii


Expresie-SAU-inclusiv: expresie | expresie Operatorul | este operatorul SAU-inclusiv logic pe bii. El este asociativ i expresiile care-l conin pot fi rearanjate. Rezultatul este funcia logic SAU-inclusiv pe bii aplicat operanzilor si. Operatorul se aplic numai la operanzi de tipuri ntregi. Legea dup care funcioneaz este: | 0 1 0 0 1 1 1 1

Operatorul | este folosit pentru a poziiona bii; de exemplu: x = x | MASK; pune pe 1 toi biii din x care corespund la bii poziionai pe 1 din MASK. Se efectueaz conversiile aritmetice obinuite.

4.11. Operatorul I-logic


Expresie-I-logic: expresie && expresie Operatorul && este operatorul I-logic i el se grupeaz de la stnga la dreapta. Rezultatul este 1 dac ambii operanzi snt diferii de zero i 0 n rest. Spre deosebire de operatorul I pe bii &, operatorul
__________________________________________________________________________

36

I-logic && garanteaz o evaluare de la stnga la dreapta; mai mult, al doilea operand nu este evaluat dac primul operand este 0. Operanzii nu trebuie s aib n mod obligatoriu acelai tip, dar fiecare trebuie s aib unul dintre tipurile fundamentale sau pointer. Rezultatul este totdeauna de tip int.

4.12. Operatorul SAU-logic


Expresie-SAU-logic: expresie || expresie Operatorul || este operatorul SAU-logic i el se grupeaz de la stnga la dreapta. Rezultatul este 1 dac cel puin unul dintre operanzi este diferit de zero i 0 n rest. Spre deosebire de operatorul SAU-inclusiv pe bii |, operatorul SAU-logic || garanteaz o evaluare de la stnga la dreapta; mai mult, al doilea operand nu este evaluat dac valoarea primului operand este diferit de zero. Operanzii nu trebuie s aib n mod obligatoriu acelai tip, dar fiecare trebuie s aib unul dintre tipurile fundamentale sau pointer. Rezultatul este totdeauna de tip int.

4.13. Operatorul condiional


Expresie-condiional: expresie ? expresie : expresie Operatorul condiional ? este un operator ternar. Prima expresie se evalueaz i dac ea este diferit de zero sau adevrat, rezultatul este valoarea celei de-a doua expresii, altfel rezultatul este valoarea expresiei a treia. De exemplu expresia: z = (a>b) ? a : b; calculeaz maximul dintre a i b i l atribuie lui z. Se evalueaz mai nti prima expresie a>b. Dac ea este adevrat se evalueaz a doua expresie i valoarea ei este rezultatul operaiei, aceast valoare se
__________________________________________________________________________

37

atribuie lui z. Dac prima expresie nu este adevrat atunci z ia valoarea lui b. Expresia condiional poate fi folosit peste tot unde sintaxa cere o expresie. Dac este posibil, se execut conversiile aritmetice obinuite pentru a aduce expresia a doua i a treia la un tip comun; dac ambele expresii snt pointeri de acelai tip, rezultatul are i el acelai tip; dac numai o expresie este un pointer, cealalt trebuie sa fie constanta 0, iar rezultatul este de tipul pointerului. ntotdeauna numai una dintre expresiile a doua i a treia este evaluat. Dac f este flotant i n ntreg, atunci expresia (h>0)? f : n este de tip double indiferent dac n este pozitiv sau negativ. Parantezele nu snt necesare deoarece precedena operatorului ?: este mai mic, dar ele pot fi folosite pentru a face expresia condiional mai vizibil.

4.14. Operatori de atribuire


Exist mai muli operatori de atribuire, care se grupeaz toi de la dreapta la stnga. Operandul stng este o valoare-stnga, operandul drept este o expresie. Tipul expresiei de atribuire este tipul operandului stng. Rezultatul este valoarea memorat n operandul stng dup ce atribuirea a avut loc. Cele dou pri ale unui operator de atribuire compus snt uniti lexicale (simboluri) distincte. Expresie-atribuire: valoare-stnga = expresie valoare-stnga op= expresie unde op poate fi unul din operatorii +, -, *, /, %, <<, >>, &, ^, |. ntr-o atribuire simpl cu =, valoarea expresiei nlocuiete pe cea a obiectului referit de valoare-stnga. Dac ambii operanzi au tip aritmetic, atunci operandul drept este convertit la tipul operandului stng nainte de atribuire.
__________________________________________________________________________

38

Expresiile de forma E1 op= E2 se interpreteaz ca fiind echivalente cu expresiile de forma E1 = E1 op E2; totui E1 este evaluat o singur dat. Exemplu: expresia x *= y+1 este echivalent cu x = x * (y+1) i nu cu x = x * y + 1 Pentru operatorii += i -=, operandul stng poate fi i un pointer, n care caz operandul din dreapta este convertit la ntreg (vezi capitolul 9). Toi operanzii din dreapta i toi operanzii din stnga care nu snt pointeri trebuie s fie de tip aritmetic. Atribuirea prescurtat este avantajoas n cazul cnd n membrul stng avem expresii complicate, deoarece ele se evalueaz o singur dat.

4.15. Operatorul virgul


Expresie-virgul: expresie , expresie O pereche de expresii separate prin virgul se evalueaz de la stnga la dreapta i valoarea expresiei din stnga se neglijeaz. Tipul i valoarea rezultatului snt cele ale operandului din dreapta. Aceti operatori se grupeaz de la stnga la dreapta. n contextele n care virgula are un sens special, (de exemplu ntr-o list de argumente reale ale unei funcii i lista de iniializare), operatorul virgul descris aici poate aprea numai n paranteze. De exemplu funcia: f(a,(t=3,t+2),c) are trei argumente, dintre care al doilea are valoarea 5. Expresia acestui argument este o expresie virgul. n calculul valorii lui se evalueaz nti expresia din stnga i se obine valoarea 3 pentru t, apoi cu aceast valoare se evalueaz a doua expresie i se obine t= 5. Prima valoare a lui t se pierde.

__________________________________________________________________________

39

4.16. Precedena i ordinea de evaluare


Tabelul de la sfritul acestei seciuni constituie un rezumat al regulilor de preceden i asociativitate ale tuturor operatorilor. Operatorii din aceeai linie au aceeai preceden; liniile snt scrise n ordinea descresctoare a precedenei, astfel de exemplu operatorii *, / i % au toi aceeai preceden, care este mai mare dect aceea a operatorilor + i -. Dup cum s-a menionat deja, expresiile care conin unul dintre operatorii asociativi sau comutativi (*, +, &, ^, |) pot fi rearanjate de compilator chiar dac conin paranteze. n cele mai multe cazuri aceasta nu produce nici o diferen; n cazurile n care o asemenea diferen ar putea aprea pot fi utilizate variabile temporare explicite, pentru a fora ordinea de evaluare. Limbajul C, ca i multe alte limbaje, nu specific n ce ordine snt evaluai operanzii unui operator. De exemplu ntr-o instruciune de forma: x = f() + g(); f poate fi evaluat nainte sau dup evaluarea lui g; dac f sau g altereaz o variabil extern de care cealalt depinde, x poate depinde de ordinea de evaluare. Din nou rezultate intermediare trebuie memorate n variabile temporare pentru a asigura o secven particular.

__________________________________________________________________________

40

Operator () [] -> . ! ++ -- - (tip) * & sizeof * / % + << >> < <= > >= == != & ^ | && || ?: = op= ,

Asociativitate stnga la dreapta dreapta la stnga stnga la dreapta stnga la dreapta stnga la dreapta stnga la dreapta stnga la dreapta stnga la dreapta stnga la dreapta stnga la dreapta stnga la dreapta stnga la dreapta dreapta la stnga dreapta la stnga stnga la dreapta

__________________________________________________________________________

41

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-tip specificator-declaraie<opt> specificator-clas-memorie specificator-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:
__________________________________________________________________________

42

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 const typedef register

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.
__________________________________________________________________________

43

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 float double void specificator-structur-sau-reuniune typedef-nume unsigned

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
__________________________________________________________________________

44

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 D1 unde 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: *D atunci 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.
__________________________________________________________________________

45

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.
__________________________________________________________________________

46

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;
__________________________________________________________________________

47

*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
__________________________________________________________________________

48

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-tip declarator-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 declaratorabstract, 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*
__________________________________________________________________________

49

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.

__________________________________________________________________________

50

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
__________________________________________________________________________

51

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-1 else 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)
__________________________________________________________________________

52

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.
__________________________________________________________________________

53

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-4 poate 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) instruciune grupate 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:
__________________________________________________________________________

54

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.
__________________________________________________________________________

55

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;
__________________________________________________________________________

56

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; i<10; i++) printf(" %d",nc[i]); printf("\nspatii albe: %d, altele: %d\n", nb,na); n acest exemplu se parcurg toate caracterele dintr-un ir, se numr cifrele, spaiile albe i alte caractere i se afieaz aceste numere nsoite de comentarii. Instruciunea while este cea care asigur parcurgerea irului pn la sfrit. Pentru fiecare caracter se execut corpul instruciunii while care const dintr-o singur instruciune switch. Se evalueaz expresia ntreag din paranteze (n cazul nostru caracterul c) i se compar valoarea sa cu toate constantele-case. n
__________________________________________________________________________

57

momentul cnd avem egalitate se ncepe execuia de la case-ul respectiv. Afiarea rezultatelor se face prin intermediul instruciunii for i a funciei printf (vezi capitolul 11).

6.8. Instruciunea break


Format: break; Aceast instruciune determin terminarea celei mai interioare instruciuni while, do, for sau switch care o conine. Controlul trece la instruciunea care urmeaz dup instruciunea astfel terminat.

6.9. Instruciunea continue


Format: continue; Aceast instruciune determin trecerea controlului la poriunea de continuare a ciclului celei mai interioare instruciuni while, do sau for care o conine, adic la sfritul ciclului i reluarea urmtoarei iteraii a ciclului. n while i do se continu cu testul, iar n for se continu cu expresie-3. Mai precis n fiecare dintre instruciunile: while (...) { ... contin:; } for (...) { ... contin:; } do { ... contin:; } while (...);

dac apare o instruciune continue aceasta este echivalent cu un salt la eticheta contin. Dup contin: urmeaz o instruciune vid (vezi seciunea 6.11). Poriunea de program din exemplul urmtor prelucreaz numai elementele pozitive ale unui masiv.
__________________________________________________________________________

58

for (i=0; i<n; i++) { if (a[i]<0) /* sare peste elementele negative */ continue; ... /* prelucreaz elementele pozitive */ }

6.10. Instruciunea return


O instruciune return permite ieirea dintr-o funcie i transmiterea controlului apelantului funciei. O funcie poate returna valori apelantului su, prin intermediul unei instruciuni return. Formate: return; return expresie; n primul caz valoarea returnat nu este definit. n al doilea caz valoarea expresiei este returnat apelantului funciei. Dac se cere, expresia este convertit, ca ntr-o atribuire, la tipul funciei n care ea apare.

6.11. Instruciunea vid


Format: ; Instruciunea vid este util pentru a introduce o etichet naintea unei acolade drepte, ntr-o instruciune compus, sau pentru a introduce un corp nul ntr-o instruciune de ciclare care cere corp al instruciunii, ca de exemplu while sau for. Exemplu: for (nc=0; s[nc]!=0; ++nc) ; Aceast instruciune numr caracterele unui ir. Corpul lui for este vid, deoarece tot lucrul se face n partea de test i actualizare dar sintaxa lui for cere un corp al instruciunii. Instruciunea vid satisface acest lucru.
__________________________________________________________________________

59

7. Funciile i structura unui program


Funciile snt elementele de baz ale unui program scris n C; orice program, de orice dimensiune, const din una sau mai multe funcii, care specific operaiile care trebuie efectuate. O funcie ofer un mod convenabil de ncapsulare a anumitor calcule ntr-o cutie neagr care poate fi utilizat apoi fr a avea grija coninutului ei. Funciile snt ntr-adevr singurul mod de a face fa complexitii programelor mari. Funciile permit desfacerea programelor mari n unele mici i dau utilizatorului posibilitatea de a dezvolta programe, folosind ceea ce alii au fcut deja n loc s o ia de la nceput. Limbajul C a fost conceput s permit definirea de funcii eficiente i uor de mnuit. n general e bine s concepem programe constituite din mai multe funcii mici dect din puine funcii de dimensiuni mari. Un program poate fi mprit n mai multe fiiere surs n mod convenabil, iar fiierele surs pot fi compilate separat. Mai precis, un program C const dintr-o secven de definiii externe. Acestea snt definiii de funcii i de date. Sintaxa definiiilor externe este aceeai ca i a tuturor declaraiilor.

7.1. Definiia funciilor


Sintaxa definiiei unei funcii este urmtoarea: Definiia-funciei: tip-funcie<opt> nume-funcie(lista-parametri) corp-funcie Lista-parametri: declaraie-parametru declaraie-parametru, lista-parametri Corpul-funciei: instruciune-compus Dintre specificatorii de clas de memorie numai extern i static snt admii. Un declarator de funcie este similar cu un
__________________________________________________________________________

60

declarator pentru funcie care returneaz ... cu excepia c el listeaz parametrii formali ai funciei care se definete. Dup cum se observ, diferitele pri pot s lipseasc; o funcie minim este: dummy() {} funcia care nu face nimic. Aceast funcie poate fi util n programe, innd locul unei alte funcii n dezvoltarea ulterioar a programului. Numele funciei poate fi precedat de un tip, dac funcia returneaz altceva dect o valoare ntreag (vezi seciunea 7.2). Numele funciei poate fi de asemenea precedat de clasa de memorie extern sau static. Dac nici un specificator de clas de memorie nu este prezent, atunci funcia este automat declarat extern, deoarece n C nu se admite ca o funcie s fie definit n interiorul altei funcii. Declaraia unei funcii se face implicit la apariia ei. Se interpreteaz ca funcie orice identificator nedeclarat anterior i urmat de o parantez (. Pentru a evita surprize neplcute datorit neconcordanei dintre tipurile de parametri aceast practic nu este recomandat. Dm mai jos un exemplu de funcie complet definit: int max(int a, int b, int c) { int m; m = (a>b) ? a : b; return (m>c) ? m : c; } Aceast funcie determin maximul dintre 3 numere date: int este un specificator de tip care indic faptul c funcia max returneaz o valoare ntreag; max(int a, int b, int c) este declaraia funciei i a parametrilor formali; { . . . } este corpul funciei. S ilustrm mecanismul definirii unei funcii scriind funcia putere power(m,n) care ridic un ntreg m la o putere ntreag pozitiv n. Astfel valoarea lui power(2,5) este 32. Aceast funcie
__________________________________________________________________________

61

desigur nu este complet, deoarece calculeaz numai puteri pozitive de ntregi mici. Prezentm mai jos funcia power i un program principal care o apeleaz, pentru a putea vedea i structura unui program. power(int x, int n) { int i, p; p = 1; for (i=1; i<=n; ++i) p = p * x; return p; } main() { int i; for (i=0; i<10; ++i) printf(" %d %d\n", i, power(2,i), power(-3,i)); } Funcia power este apelat n programul principal de dou ori. Fiecare apel transmite funciei dou argumente. Rezultatul este afiat de funcia de bibliotec printf (vezi capitolul 11). Numele funciei este urmat obligatoriu de paranteze, chiar dac lista parametrilor este vid. n general numele funciei este ales dup dorina utilizatorului. Totui n fiecare program trebuie s existe o funcie cu numele impus main; orice program i ncepe execuia cu funcia main. Celelalte funcii snt apelate din interiorul funciei main. Unele dintre funciile apelate snt definite n acelai program, altele snt coninute ntr-o bibliotec de funcii. Comunicarea ntre funcii se face prin argumente i valori returnate de funcii. Comunicarea ntre funcii poate fi fcut i prin intermediul variabilelor externe. O funcie poate fi declarat i static. n acest caz ea poate fi apelat numai din fiierul unde a fost definit.
__________________________________________________________________________

62

Funciile n C pot fi folosite recursiv, deci o funcie se poate apela pe ea nsi fie direct, fie indirect. n general ns recursivitatea nu face economie de memorie, deoarece trebuie meninut o stiv cu valorile de prelucrat.

7.2. Apelul funciilor


n limbajul C orice funcie este apelat prin numele ei, urmat de lista real a argumentelor, nchis ntre paranteze. Dac ntr-o expresie numele funciei nu este urmat imediat de o parantez (, adic funcia nu apare pe poziia de apel de funcie atunci nu se realizeaz imediat apelul funciei respective ci se genereaz un pointer la funcie (vezi seciunea 9.11).

7.3. Revenirea din funcii


Revenirea dintr-o funcie se face prin intermediul unei instruciuni return. Valoarea pe care o funcie o calculeaz poate fi returnat prin instruciunea return, care dup cum am vzut are dou formate: return; return expresie; Ca argument poate aprea orice expresie admis n C. Funcia returneaz valoarea acestei expresii funciei apelante. O funcie nu returneaz n mod obligatoriu o valoare. O instruciune return, fr expresie ca parametru, cauzeaz numai transferul controlului funciei apelante nu i o valoare util. La rndul su funcia apelant poate ignora valoarea returnat. De obicei o funcie returneaz o valoare de tip ntreg. Dac se dorete ca funcia s returneze un alt tip, atunci numele tipului trebuie s precead numele funciei, iar programul trebuie s conin o declaraie a acestei funcii att n fiierul n care funcia este definit ct i n fiierul unde funcia este apelat.

__________________________________________________________________________

63

Pentru a evita orice confuzie se recomand ca tipul valorii returnate de funcie s fie ntotdeauna precizat, iar dac dorim n mod expres ca funcia s nu returneze o valoare s folosim tipul void. De exemplu, funcia atof(s) din biblioteca asociat compilatorului convertete irul s de cifre n valoarea sa n dubl precizie. Vom declara funcia sub forma: double atof(char s[]); sau mpreun cu alte variabile de tip double: double sum, atof(char s[]); Funciile nu pot returna masive, structuri, reuniuni sau funcii. Dac o funcie returneaz o valoare de tip char, nu este nevoie de nici o declaraie de tip din cauza conversiilor implicite. Totdeauna tipul char este convertit la int n expresii.

7.4. Argumentele funciei i transmiterea parametrilor


O metod de a comunica datele ntre funcii este prin argumente. Parantezele mici care urmeaz dup numele funciei nchid lista argumentelor. n limbajul C argumentele funciilor snt transmise prin valoare. Aceasta nseamn c n C funcia apelat primete valorile argumentelor sale ntr-o copie particular de variabile temporare (n realitate pe stiv). Funcia apelat nu poate altera dect variabilele sale particulare, adic copiile temporare. Apelul prin valoare este o posibilitate, dar nu i o obligativitate. Apelul prin valoare conduce la programe mai compacte cu mai puine variabile externe, deoarece argumentele pot fi tratate ca variabile locale, i care pot fi modificate convenabil n rutina apelat. Ca exemplu, prezentm o versiune a funciei putere care face uz de acest fapt: power(int x, int n) { int p;
__________________________________________________________________________

64

for (p=1; n>0; --n) p = p * x; return p; } Argumentul n este utilizat ca o variabil temporar i este decrementat pn devine zero; astfel nu este nevoie de nc o variabil i. Orice operaii s-ar face asupra lui n n interiorul funciei, ele nu au efect asupra argumentului pentru care funcia a fost apelat. Dac totui se dorete alterarea efectiv a unui argument al funciei apelante, acest lucru se realizeaz cu ajutorul pointerilor sau a variabilelor declarate externe. n cazul pointerilor, funcia apelant trebuie s furnizeze adresa variabilei de modificat (tehnic printr-un pointer la aceast variabil), iar funcia apelat trebuie s declare argumentul corespunztor ca fiind un pointer. Referirea la variabila de modificat se face prin adresare indirect (vezi capitolul 9). Printre argumentele funciei pot aprea i nume de masive. n acest caz valoarea transmis funciei este n realitate adresa de nceput a masivului (elementele masivului nu snt copiate). Prin indexarea acestei valori funcia poate avea acces i poate modifica orice element din masiv.

7.5. Funcii cu numr variabil de parametri


Pentru funciile cu numr variabil de parametri standardul ANSI definete o construcie special ... , numit elips. Acesta este de exemplu cazul funciilor de citire i scriere cu format (familiile ...scanf i ...printf - a se vedea capitolul 11). n acest caz, parametrii fici snt verificai la compilare, iar cei variabili snt transmii ca i cnd nu ar exista prototip de funcie. Pentru simplitatea expunerii am pstrat conveniile definite de standardele mai vechi ale limbajului, pstrate i de standardul ANSI C. O funcie poate fi declarat fr nici un parametru, compilatorul deducnd de aici c funcia respectiv poate avea orici parametri de orice tip. Nu se recomand totui o asemenea practic deoarece este
__________________________________________________________________________

65

posibil s se creeze confuzii care conduc la erori n execuia programelor, erori care se corecteaz foarte greu n cazul programelor mari.

7.6. Exemple de funcii i programe


1. Acest exemplu este un program de cutare i imprimare a tuturor liniilor dintr-un text care conin o anumit configuraie de caractere, de exemplu cuvntul englezesc "the". Structura de baz a acestui program este: while (mai exist linii surs) if (linia conine configuraia de caractere) imprim linia n scopul scrierii acestui program vom scrie trei funcii. Funcia getline citete o linie din textul surs i returneaz lungimea ei. Aceast funcie are dou argumente. Argumentul s indic numele masivului unde se va citi linia, iar argumentul lim reprezint lungimea maxim a unei linii care poate fi citit. Funcia index caut configuraia de caractere dorit n interiorul liniei i furnizeaz poziia sau indexul n irul s, unde ncepe irul t, sau dac linia nu conine irul t. Argumentele funciei snt s care 1 reprezint adresa irului de studiat i t care reprezint configuraia cutat. Funcia care realizeaz afiarea este printf din bibliotec. Programul care realizeaz structura de mai sus este: #define MAXLINE 1000 #define EOF -1 getline(char s[], int lim) { /* citete o linie n s; returneaz lungimea */ int c, i; i = 0; while (--lim>0 && (c=getchar())!= EOF && c!='\n')
__________________________________________________________________________

66

s[i++]=c; if (c=='\n') s[i++]=c; s[i]='\0'; return i; } index(char s[], char t[]) { /* returneaz poziia din irul s unde ncepe irul t, sau */ 1 int i,j,k; for (i=0; s[i]!='\0'; i++) { for (j=i, k=0; t[k]!='\0' && s[j]==t[k]; j++, k++) ; if (t[k]=='\0') return i; } return -1; } main() { /* imprim toate liniile care conin cuvntul the */ char line [MAXLINE]; while (getline(line, MAXLINE)>0) 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++) ; 67 /* sare peste spaii albe */
__________________________________________________________________________

sign = 1; if (s[i]=='+' || s[i]=='-') sign = (s[i++]=='+') ? 1 : -1; for (val=0; s[i]>='0' && s[i]<='9'; i++) val = 10 * val + s[i] - '0'; if (s[i]== '.') /* punct zecimal */ i++; for (power=1; s[i]>='0' && s[i]<='9'; i++) { val = 10 * val +s[i] - '0': power *= 10; } return sign * val / power; } 3. Funcia atoi convertete un ir de cifre n echivalentul lor numeric ntreg. atoi(char s[]) { /* convertete irul s la un ntreg */ int i,n; n = 0; for (i=0; s[i]>='0' && s[i]<='9'; i++) n = 10 * n +s[i] - '0'; return n; } 4. Funcia lower convertete literele mari din setul de caractere ASCII n litere mici. Dac lower primete un caracter care nu este o liter mare atunci l returneaz neschimbat. lower(int c) { if (c>='A' && c<='Z') return c + 'a' - 'A'; else return c; }
__________________________________________________________________________

68

5. Funcia binary realizeaz cutarea valorii x ntr-un masiv sortat v, care are n elemente. binary(int x, int v[], int n) { /* caut x n v0, v1, ..., vn-1 */ int low, high, mid; low = 0; high = n - 1; while (low<=high) { mid = (low + high) / 2; if (x < v[mid]) high = mid - 1; else if (x > v[mid]) low = mid + 1; else /* s-a gsit o intrare */ return(mid); } return -1; } Funcia returneaz poziia lui x (un numr ntre 0 i n dac x 1), apare n v sau altfel. 1 Exemplul ilustreaz o decizie tripl, dac x este mai mic, mai mare sau egal cu elementul din mijlocul irului v[mid].

__________________________________________________________________________

69

8. Liniile de control ale compilatorului


Limbajul C ofer o facilitate de extensie a limbajului cu ajutorul unui preprocesor de macro-operaii simple. Folosirea liniilor de forma: #define #include este cea mai uzual metod pentru extensia limbajului, caracterul # (diez) indicnd faptul c aceste linii vor fi tratate de ctre preprocesor. Liniile precedate de caracterul # au o sintax independent de restul limbajului i pot aprea oriunde n fiierul surs, avnd efect de la punctul de definiie pn la sfritul fiierului.

8.l. nlocuirea simbolurilor; substituii macro


O definiie de forma: #define identificator ir-simboluri determin ca preprocesorul s nlocuiasc toate apariiile ulterioare ale identificatorului cu ir-simboluri dat (ir-simboluri nu se termin cu ; deoarece n urma substituiei identificatorului cu acest ir ar aprea prea multe caractere ;). ir-simboluri sau textul de nlocuire este arbitrar. n mod normal el este tot restul liniei ce urmeaz dup identificator. O definiie ns poate fi continuat pe mai multe linii, introducnd ca ultim caracter n linia de continuat caracterul \ (backslash). Un identificator care este subiectul unei linii #define poate fi redefinit ulterior n program printr-o alt linie #define. n acest caz, prima definiie are efect numai pn la definiia urmtoare. Substituiile nu se realizeaz n cazul n care identificatorii snt ncadrai ntre ghilimele. De exemplu fie definiia: #define ALFA 1 Oriunde va aprea n programul surs identificatorul ALFA el va fi nlocuit cu constanta 1, cu excepia unei situaii de forma:
__________________________________________________________________________

70

printf("ALFA"); n care se va tipri chiar textul ALFA i nu constanta 1, deoarece identificatorul este ncadrat ntre ghilimele. Aceast facilitate este deosebit de valoroas pentru definirea constantelor simbolice ca n: #define TABSIZE 100 int tab[TABSIZE]; deoarece ntr-o eventual modificare a dimensiunii tabelului tab se va modifica doar o singur linie n fiierul surs. O linie de forma: #define identif(identif-1,..., identif-n) ir-simboluri n care nu exist spaiu ntre primul identificator i caracterul ( este o definiie pentru o macro-operaie cu argumente, n care textul de nlocuire (ir-simboluri) depinde de modul n care se apeleaz macroul respectiv. Ca un exemplu s definim o macro-operaie numit max n felul urmtor: #define max(a,b) ((a)>(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 macrooperaia square(x) definit prin: #define square(x) x*x care se apeleaz n programul surs sub forma: z = square(z+1);
__________________________________________________________________________

71

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
__________________________________________________________________________

72

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.
__________________________________________________________________________

73

#include <stdio.h> #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 (n<4) return 1; if ((n&1)==0) return 0; for (i=3; ; i+=2) { r=n%i; if (r==0) return 0; a=n/i; if (a<=i) return 1; } } unsigned cmmdc(unsigned p, unsigned q) { while ((p>0) && (q>0)) if (p>q) p%=q; else q%=p;
__________________________________________________________________________

74

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-compilare unde 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.

__________________________________________________________________________

75

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:
__________________________________________________________________________

76

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) { int temp; temp = x; x = y; y = temp; }
77

/* greit */

__________________________________________________________________________

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.
__________________________________________________________________________

78

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
__________________________________________________________________________

79

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
__________________________________________________________________________

80

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+ALLOCSIZE) { allocp += n; /* dimensiunea satisfcut */ return allocp-n; /* vechea valoare */ } else return NULL; /* nu este spaiu suficient */
__________________________________________________________________________

81

} free(char *p) { /* elibereaz memoria indicat de p */ if (p>=allocbuf && p<allocbuf+ALLOCSIZE) allocp = p; } Testul if (allocp+n<=allocbuf+ALLOCSIZE) verific dac exist spaiu suficient pentru satisfacerea cererii de alocare a n caractere. Dac cererea poate fi satisfcut, alloc revine cu un pointer la zona de n caractere consecutive. Dac nu, alloc trebuie s semnaleze lipsa de spaiu pe care o face returnnd valoarea constantei simbolice NULL. Limbajul C garanteaz c nici un pointer care indic corect o dat nu va conine zero, prin urmare o revenire cu valoarea zero poate fi folosit pentru semnalarea unui eveniment anormal (n cazul nostru, lipsa de spaiu). Atribuirea valorii zero unui pointer este deci un caz special. Observm de asemenea c variabilele allocbuf i allocp snt declarate static cu scopul ca ele s fie locale numai fiierului surs care conine funciile alloc i free. Exemplul de mai sus demonstreaz cteva din facilitile aritmeticii de adrese (pointeri). n primul rnd, pointerii pot fi comparai n anumite situaii. Dac p i q snt pointeri la membri unui acelai masiv, atunci relaiile <, <=, >, >=, ==, != snt valide. Relaia p<q, de exemplu, este adevrat dac p indic un element mai apropiat de nceputul masivului dect elementul indicat de pointerul q. Comparrile ntre pointeri pot duce ns la rezultate imprevizibile, dac ei se refer la elemente aparinnd la masive diferite. Se observ c pointerii i ntregii pot fi adunai sau sczui. Construcia de forma: p+n nseamn adresa celui de-al n-lea element dup cel indicat de p, indiferent de tipul elementului pe care l indic p. Compilatorul C aliniaz valoarea lui n conform dimensiunii elementelor pe care le
__________________________________________________________________________

82

indic p, dimensiunea fiind determinat din declaraia lui p (scara de aliniere este 1 pentru char, 2 pentru int etc). Dac p i q indic elemente ale aceluiai masiv, p-q este numrul elementelor dintre cele pe care le indic p i q. S scriem o alt versiune a funciei strlen folosind aceast ultim observaie: strlen(char *s) { /* returneaz lungimea unui ir */ char *p; p = s; while (*p != '\0') p++; return p-s; } n acest exemplu s rmne constant cu adresa de nceput a irului, n timp ce p avanseaz la urmtorul caracter de fiecare dat. Diferena p-s dintre adresa ultimului element al irului i adresa primului element al irului indic numrul de elemente. n afar de operaiile binare menionate (adunarea sau scderea pointerilor cu ntregi i scderea sau compararea a doi pointeri), celelalte operaii cu pointeri snt ilegale. Nu este permis adunarea, nmulirea, mprirea sau deplasarea pointerilor, dup cum nici adunarea lor cu constante de tip double sau float. Snt admise de asemenea incrementrile i decrementrile precum i alte combinaii ca de exemplu *++p i *--p.

9.5. Pointeri la caracter i funcii


O constant ir, de exemplu: "Buna dimineata" este un masiv de caractere, care n reprezentarea intern este terminat cu caracterul '\0', astfel nct programul poate depista sfritul lui. Lungimea acestui ir n memorie este astfel cu 1 mai mare dect numrul de caractere ce apar efectiv ntre ghilimelele de nceput i sfrit de ir.
__________________________________________________________________________

83

Cea mai frecvent apariie a unei constante ir este ca argument la funcii, caz n care accesul la ea se realizeaz prin intermediul unui pointer. n exemplul: printf("Buna dimineata\n"); funcia printf primete de fapt un pointer la masivul de caractere. n prelucrarea unui ir de caractere snt implicai numai pointeri, limbajul C neoferind nici un operator care s trateze irul de caractere ca o unitate de informaie. Vom prezenta cteva aspecte legate de pointeri i masive analiznd dou exemple. S considerm pentru nceput funcia strcpy(s,t) care copiaz irul t peste irul s. O prim versiune a programului ar fi urmtoarea: strcpy(char s[], char t[]) {/* copiaz t peste s */ int t; i = 0; while ((s[i]=t[i]) != '\0') i++; } O a doua versiune cu ajutorul pointerilor este urmtoarea: strcpy(char *s, char *t) { /* versiune cu pointeri */ while ((*s++=*t++) != '\0') ; } Aceast versiune cu pointeri modific prin incrementare pe s i t n partea de test. Valoarea lui *t++ este caracterul indicat de pointerul t, nainte de incrementare. Notaia postfix ++ asigur c t va fi modificat dup depunerea coninutului indicat de el, la vechea poziie a lui s, dup care i s se incrementeaz. Efectul este c se copiaz caracterele irului t n irul s pn la caracterul terminal '\0' inclusiv.

__________________________________________________________________________

84

Am mai putea face o observaie legat de redundana comparrii cu caracterul '\0', redundan care rezult din structura instruciunii while. i atunci forma cea mai prescurtat a funciei strcpy(s,t) este: strcpy(char *s, char *t) { while (*s++ = *t++) ; } S considerm, ca al doilea exemplu, funcia strcmp(s,t) care compar caracterele irurilor s i t i returneaz o valoare negativ, zero sau pozitiv, dup cum irul s este lexicografic mai mic, egal sau mai mare ca irul t. Valoarea returnat se obine prin scderea caracterelor primei poziii n care s i t difer. O prim versiune a funciei strcmp(s,t) este urmtoarea: strcmp(char s, char t) {/* compar irurile s i t */ int i; i = 0; while (s[i]==t[i]) if (s[i++]=='\0') return 0; return s[i]-t[i]; } Versiunea cu pointeri a aceleiai funcii este: strcmp(char *s, char *t) { for (; *s==*t; s++,t++) if (*s=='\0') return 0; return *s-*t; } n final prezentm funcia strsav care copiaz un ir dat prin argumentul ei ntr-o zon obinut printr-un apel la funcia alloc. Ea returneaz un pointer la irul copiat sau NULL, dac nu mai exist suficient spaiu pentru memorarea irului.
__________________________________________________________________________

85

char *strsav(char *s) { /* copiaz irul s */ char *p; p = alloc(strlen(s)+1); if (p!=NULL) strcpy(p,s); return p; }

9.6. Masive multidimensionale


Limbajul C ofer facilitatea utilizrii masivelor multidimensionale, dei n practic ele snt folosite mai puin dect masivele de pointeri. S considerm problema conversiei datei, de la zi din lun, la zi din an i invers, innd cont de faptul c anul poate s fie bisect sau nu. Definim dou funcii care s realizeze cele dou conversii. Funcia day_of_year convertete ziua i luna n ziua anului i funcia month_day convertete ziua anului n lun i zi. Ambele funcii au nevoie de aceeai informaie i anume un tabel cu numrul zilelor din fiecare lun. Deoarece numrul zilelor din lun difer pentru anii biseci de cele pentru anii nebiseci este mai uor s considerm un tabel bidimensional n care prima linie s corespund numrului de zile ale lunilor pentru anii nebiseci, iar a doua linie s corespund numrului de zile pentru anii biseci. n felul acesta nu trebuie s inem o eviden n timpul calculului a ceea ce se ntmpl cu luna februarie. Atunci masivul bidimensional care conine informaiile pentru cele dou funcii este urmtorul: static int day_tab[2][13] = { {0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31} }; Masivul day_tab trebuie s fie declarat extern pentru a putea fi folosit de ambele funcii.
__________________________________________________________________________

86

n limbajul C, prin definiie, un masiv cu dou dimensiuni este n realitate un masiv cu o dimensiune ale crui elemente snt masive. De aceea indicii se scriu sub forma [i][j] n loc de [i,j], cum se procedeaz n cele mai multe limbaje. Un masiv bidimensional poate fi tratat n acelai fel ca i n celelalte limbaje, n sensul c elementele snt memorate pe linie, adic indicele cel mai din dreapta variaz cel mai rapid. Un masiv se iniializeaz cu ajutorul unei liste de iniializatori nchii ntre acolade; fiecare linie a unui masiv bidimensional se iniializeaz cu ajutorul unei subliste de iniializatori. n cazul exemplului nostru, masivul day_tab ncepe cu o coloan zero, pentru ca numerele lunilor s fie ntre 1 i 12 i nu ntre 0 i 11, aceasta pentru a nu face modificri n calculul indicilor. i atunci funciile care realizeaz conversiile cerute de exemplul nostru snt: day_of_year (int year, int month, int day) { /* ziua anului din lun i zi */ int i, leap; leap = (year%4==0) && (year%100!=0) || (year%400==0); for (i=1; i<month; i++) day += day_tab[leap][i]; return day; } Deoarece variabila leap poate lua ca valori numai zero sau unu dup cum expresia: (year%4==0) && (year%100!=0) || (year%400==0) este fals sau adevrat, ea poate fi folosit ca indice de linie n tabelul day_tab care are doar dou linii n exemplul nostru. month_day(int year, int yearday, int *pmonth, int *pday) { int i,leap; leap = (year%4==0) && (year%100!=0) ||
__________________________________________________________________________

87

(year%400==0); for (i=1; yearday>day_tab[leap][i]; i++) yearday -= day_tab[leap][i]; *pmonth = i; *pday = yearday; } Deoarece aceast ultim funcie returneaz dou valori, argumentele lun i zi vor fi pointeri. Exemplu: month_day(1984,61,&m,&d) va ncrca pe m cu 3, iar pe d cu 1 (adic 1 martie). Dac un masiv bidimensional trebuie transmis unei funcii, declaraia argumentelor funciei trebuie s includ dimensiunea coloanei. Dimensiunea liniei nu este necesar s apar n mod obligatoriu, deoarece ceea ce se transmite de fapt este un pointer la masive de cte 13 ntregi, n cazul exemplului nostru. Astfel, dac masivul day_tab trebuie transmis unei funcii f, atunci declaraia lui f poate fi: f(int (*day_tab)[13]) unde declaraia (*day_tab)[13]) indic faptul c argumentul lui f este un pointer la un masiv de 13 ntregi. n general deci, un masiv d-dimensional a[i][j]...[p] de rangul i*j*...*p este un masiv d - dimensional de rangul 1 j*k*...*p ale crui elemente, fiecare, snt masive d 2 dimensionale de rang k*...*p ale crui elemente, fiecare, snt masive d - dimensionale .a.m.d. Oricare dintre expresiile a[i], 3 a[i][j]..., a[i][j]... [p] pot aprea n expresii. Prima are tipul masiv, ultima are tipul int, de exemplu, dac masivul este de tipul int. Vom mai reveni asupra acestei probleme cu detalii.

9.7. Masive de pointeri i pointeri la pointeri


Deoarece pointerii snt variabile, are sens noiunea de masiv de pointeri. Vom ilustra modul de lucru cu masive de pointeri pe un exemplu.
__________________________________________________________________________

88

S scriem un program care s sorteze lexicografic liniile de lungimi diferite ale unui text, linii care spre deosebire de ntregi nu pot fi comparate sau schimbate printr-o singur operaie. Dac memorm liniile textului una dup alta ntr-un masiv lung de caractere (gestionat de funcia alloc), atunci fiecare linie poate fi accesibil cu ajutorul unui pointer la primul ei caracter. Pointerii tuturor liniilor, la rndul lor, pot fi memorai sub forma unui masiv. Atunci dou linii de text pot fi comparate transmind pointerii lor funciei strcmp. Dac dou linii care nu respect ordinea trebuie s fie schimbate, se schimb doar pointerii lor din masivul de pointeri i nu textul efectiv al liniilor. Procesul de sortare l vom realiza n trei pai: 1) se citesc toate liniile textului de la intrare; 2) se sorteaz liniile n ordine lexicografic; 3) se tipresc liniile sortate n noua ordine. Vom scrie programul prin funciile sale, fiecare funcie realiznd unul din cei trei pai de mai sus. O rutin principal va controla cele trei funcii. Ea are urmtorul cod: #define LINES 100 /* nr maxim de linii de sortat */ main() { /* sorteaz liniile de la intrare */ char *lineptr[LINES]; /* pointeri la linii */ int nlines; /* nr linii intrare citite */ if ((nlines=readlines(lineptr,LINES))>=0) { sort(lineptr,nlines); writelines(lineptr,nlines); } else printf ("Intrarea prea mare pentru sort\n"); } Cele 3 funcii care realizeaz ntregul proces snt: readlines, sort i writelines.

__________________________________________________________________________

89

Rutina de intrare readlines trebuie s memoreze caracterele fiecrei linii i s construiasc un masiv de pointeri la liniile citite. Trebuie, de asemenea, s numere liniile din textul de la intrare, deoarece aceast informaie este necesar n procesul de sortare i de imprimare. ntruct funcia de intrare poate prelucra numai un numr finit de linii de intrare, ea poate returna un numr ilegal, cum ar fi 1, spre a semnala c numrul liniilor de intrare este prea mare pentru capacitatea de care dispune. Atunci funcia readlines care citete liniile textului de la intrare este urmtoarea: #define MAXLEN 1000 #define NULL 0 #define EOF -1 readlines(char *lineptr[], int maxlines) { /* citete liniile */ int len,nlines; char *p,*alloc(),line[MAXLEN]; nlines = 0; while ((len=getline(line,MAXLEN))>0) if (nlines>=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.
__________________________________________________________________________

90

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<nlines; i++) printf("%s\n",lineptr[i]); } Declaraia nou care apare n aceste programe este: char *lineptr[LINES]; care indic faptul c lineptr este un masiv de LINES elemente, fiecare element al masivului fiind un pointer la un caracter. Astfel lineptr[i] este un pointer la un caracter, iar *lineptr[i] permite accesul la caracterul respectiv. Deoarece lineptr este el nsui un masiv, care se transmite ca argument funciei writelines, el va fi tratat ca un pointer (vezi seciunea 9.3) i atunci funcia writelines mai poate fi scris i astfel: writelines(char *lineptr[], int nlines) { while (--nlines>=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;
__________________________________________________________________________

91

char *temp; for (gap=n/2; gap>0; gap/=2) for (i=gap; i<n; i++) for (j=i-gap; j>=0; j-=gap) { if (strcmp(v[j],v[j+gap])<=0) break; temp = v[j]; v[j] = v[j+gap]; v[j+gap] = temp; } } Deoarece fiecare element al masivului v (care este de fapt masivul lineptr) este un pointer la primul caracter al unei linii, variabila temp va fi i ea un pointer la un caracter, deci operaiile de atribuire din ciclu dup variabila j snt admise i ele realizeaz reinversarea pointerilor la linii dac ele nu snt n ordinea cerut. S reinem deci urmtoarele lucruri legate de masive i pointeri. De cte ori apare ntr-o expresie un identificator de tip masiv el este convertit ntr-un pointer la primul element al masivului. Prin definiie, operatorul de indexare [] este interpretat astfel nct E1[E2] este identic cu *((E1)+(E2)). Dac E1 este un masiv, iar E2 un ntreg, atunci E1[E2] se refer la elementul de indice E2 al masivului E1. O regul corespunztoare se aplic i masivelor multidimensionale. Dac E1 este un masiv d-dimensional, de rangul i*j*...*k, atunci ori de cte ori e1 apare ntr-o expresie, e1 va fi convertit ntr-un pointer la un masiv d - dimensional de rangul 1 j*...*k, ale crui elemente snt masive. Dac operatorul * se aplic acestui pointer, rezultatul este masivul d - dimensional, care 1 se va converti imediat ntr-un pointer la un masiv d - dimensional 2 .a.m.d. Raionamentul se poate aplica n mod inductiv pn cnd, n final, ca urmare a aplicrii operatorului * se obine ca rezultat un ntreg, de exemplu, dac masivul a fost declarat de tipul int. S considerm, de exemplu, masivul:
__________________________________________________________________________

92

int x[3][5]; x este un masiv de ntregi, de rangul 3*5. Cnd x apare ntr-o expresie, el este convertit ntr-un pointer la (primul din cele trei) masive de 5 ntregi.

n expresia x[i], care este echivalent cu expresia *(x+i), x este convertit ntr-un pointer la un masiv, ale crui elemente snt la rndul lor masive de 5 elemente; apoi i se convertete la tipul x, adic indicele i se nmulete cu lungimea elementului pe care l indic x (adic 5 ntregi) i apoi rezultatele se adun. Se aplic operatorul * pentru obinerea masivului i (de 5 ntregi) care la rndul lui este convertit ntr-un pointer la primul ntreg din cei cinci. Se observ deci c primul indice din declaraia unui masiv nu joac rol n calculul adresei.

9.8. Iniializarea masivelor i masivelor de pointeri


Iniializatorul unei variabile declarate masiv const dintr-o list de iniializatori separai prin virgul i nchii ntre acolade, corespunztori tuturor elementelor masivului. Ei snt scrii n ordinea cresctoare a indicilor masivului. Dac masivul conine sub-masive atunci regula se aplic recursiv membrilor masivului. Dac n lista de iniializare exist mai puini iniializatori dect elementele masivului, restul elementelor neiniializate se iniializeaz cu zero. Nu se admite iniializarea unui masiv de clas cu automatic. Acoladele { i } se pot omite n urmtoarele situaii: dac iniializatorul ncepe cu o acolad stng ({), atunci lista de iniializatori, separai prin virgul, va iniializa elementele masivului; nu se accept s existe mai muli iniializatori dect numrul elementelor masivului;
__________________________________________________________________________

93

dac ns iniializatorul nu ncepe cu acolad stng ({), atunci se iau din lista de iniializatori atia iniializatori ci corespund numrului de elemente ale masivului, restul iniializatorilor vor iniializa urmtorul membru al masivului, care are ca parte (submasiv) masivul deja iniializat. Un masiv de caractere poate fi iniializat cu un ir, caz n care caracterele succesive ale irului iniializeaz elementele masivului. Exemple: 1) int x[] = {1,3,5}; Aceast declaraie definete i iniializeaz pe x ca un masiv unidimensional cu trei elemente, n ciuda faptului c nu s-a specificat dimensiunea masivului. Prezena iniializatorilor nchii ntre acolade determin dimensiunea masivului. 2) Declaraia int y[4][3]={ {1,3,5}, {2,4,6}, {3,5,7}, }; este o iniializare complet nchis ntre acolade. Valorile 1,3,5 iniializeaz prima linie a masivului y[0] i anume pe y[0][0], y[0][1], y[0][2]. n mod analog urmtoarele dou linii iniializeaz pe y[1] i y[2]. Deoarece iniializatorii snt mai putini dect numrul elementelor masivului, linia y[3] se va iniializa cu zero, respectiv elementele y[3][0], y[3][1], y[3][2] vor avea valorile zero. 3) Acelai efect se poate obine din declaraia: int y[4][3] = {1,3,5,2,4,6,3,5,7}; unde iniializatorul masivului y ncepe cu acolada stng n timp ce iniializatorul pentru masivul y[0] nu, fapt pentru care primii trei iniializatori snt folosii pentru iniializarea lui y[0], restul iniializatorilor fiind folosii pentru iniializarea masivelor y[1] i respectiv y[2].
__________________________________________________________________________

94

4) Declaraia: int y[4][3] = { {1},{2,},{3,},{4} }; iniializeaz masivul y[0] cu (1,0,0), masivul y[1] cu (2,0,0), masivul y[2] cu (3,0,0) i masivul y[4] cu (4,0,0). 5) Declaraia: static char msg[] = "Eroare de sintaxa"; iniializeaz elementele masivului de caractere msg cu caracterele succesive ale irului dat. n ceea ce privete iniializarea unui masiv de pointeri s considerm urmtorul exemplu. Fie funcia month_name care returneaz un pointer la un ir de caractere care indic numele unei luni a anului. Funcia dat conine un masiv de iruri de caractere i returneaz un pointer la un astfel de ir, cnd ea este apelat. Codul funciei este urmtorul: char *month_name(int n) { /* returneaz numele lunii a n-a */ static char *name[] = { "luna ilegala", "ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august",
"septembrie", "octombrie", "noiembrie", "decembrie"

} return ((n<1) || (n>12)) ? name[0] : name[n] ; } n acest exemplu, name este un masiv de pointeri la caracter, al crui iniializator este o list de iruri de caractere. Compilatorul aloc o zon de memorie pentru memorarea acestor iruri i genereaz cte un pointer la fiecare din ele pe care apoi i introduce n masivul name. Deci name[i] va conine un pointer la irul de
__________________________________________________________________________

95

caractere avnd indice i al iniializatorului. Dimensiunea masivului name nu este necesar a fi specificat deoarece compilatorul o calculeaz numrnd iniializatorii furnizai i o completeaz n declaraia masivului.

9.9. Masive de pointeri i masive multidimensionale


Adesea se creeaz confuzii n ceea ce privete diferena dintre un masiv bidimensional i un masiv de pointeri. Fie date declaraiile: int a[10][10]; int *b[10]; n aceast declaraie a este un masiv de ntregi cruia i se aloc spaiu pentru toate cele 100 de elemente, iar calculul indicilor se face n mod obinuit pentru a avea acces la oricare element al masivului. Pentru masivul b, declaraia aloc spaiu numai pentru zece pointeri, fiecare trebuind s fie ncrcat cu adresa unui masiv de ntregi. Presupunnd c fiecare pointer indic un masiv de zece elemente nseamn c ar trebui alocate nc o sut de locaii de memorie pentru elementele masivelor. n aceast accepiune, folosirea masivelor a i b poate fi similar n sensul c a[5][5] i b[5][5], de exemplu, se refer ambele la unul i acelai ntreg (dac fiecare element b[i] este iniializat cu adresa masivului a[i]). Astfel, masivul de pointeri utilizeaz mai mult spaiu de memorie dect masivele bidimensionale i pot cere un pas de iniializare explicit. Dar masivele de pointeri prezint dou avantaje, i anume: accesul la un element se face cu adresare indirect, prin intermediul unui pointer, n loc de procedura obinuit folosind nmulirea i apoi adunarea, iar al doilea avantaj const n aceea c dimensiunea masivelor pointate poate fi variabil. Acest lucru nseamn c un element al masivului de pointeri b poate indica un masiv de zece
__________________________________________________________________________

96

elemente, altul un masiv de dou elemente i altul de exemplu poate s nu indice nici un masiv. Cu toate c problema prezentat n acest paragraf am descris-o n termenii ntregilor, ea este cel mai frecvent utilizat n memorarea irurilor de caractere de lungimi diferite (ca n funcia month_name prezentat mai sus).

9.10. Argumentele unei linii de comand


n sistemul de calcul care admite limbajul C trebuie s existe posibilitatea ca n momentul execuiei unui program scris n acest limbaj s i se transmit acestuia argumente sau parametri prin linia de comand. Cnd un program este lansat n execuie i funcia main este apelat, apelul va conine dou argumente. Primul argument (numit convenional argc) reprezint numrul de argumente din linia de comand care a lansat programul. Al doilea argument (argv) este un pointer la un masiv de pointeri la iruri de caractere care conin argumentele, cte unul pe ir. S ilustrm acest mod dinamic de comunicare ntre utilizator i programul su printr-un exemplu. Fie programul numit pri care dorim s imprime la terminal argumentele lui luate din linia de comand, imprimarea fcndu-se pe o linie, iar argumentele imprimate s fie separate prin spaii. Comanda: pri succes colegi va avea ca rezultat imprimarea la terminal a textului succes colegi Prin convenie, argv[0] este un pointer la numele pri al programului apelat, astfel c argc, care specific numrul de argumente din linia de comand este cel puin 1. n exemplul nostru, argc este 3, iar argv[0], argv[1] i argv[2] snt pointeri la "pri", "succes" i respectiv "colegi". Primul argument real este argv[1] iar ultimul este argv[argc-1]. Dac argc este 1, nseamn c linia de comand nu are nici un argument dup numele programului.
__________________________________________________________________________

97

Atunci programul pri are urmtorul cod: main(int argc, char *argv[]) { /* tiprete argumentele */ int i; for (i=1; i<argc; i++) printf("%s%%c",argv[i],(i<argc-1)? ' ':'\n'); } Deoarece argv este un pointer la un masiv de pointeri, exist mai multe posibiliti de a scrie acest program. S mai scriem dou versiuni ale acestui program. main(int argc, char *argv[]) { /* versiunea a doua */ while (--argc>0) printf("%s%c",*++argv,(argv>1)? ' ':'\n'); } Deoarece argv este un pointer la un masiv de pointeri, incrementndu-l, (++argv), el va pointa la argv[1] n loc de argv[0]. Fiecare incrementare succesiv poziioneaz pe argv la urmtorul argument, iar *argv este pointerul la argumentul irului respectiv. n acelai timp argc este decrementat pn devine zero, moment n care nu mai snt argumente de imprimat. Alternativ: main(int argc, char *argv[ ]) { /* versiunea a treia */ while (--argc>0) printf((argc>1)? "%s ":"%s\n",*++argv); }

__________________________________________________________________________

98

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
__________________________________________________________________________

99

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;
__________________________________________________________________________

100

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.
__________________________________________________________________________

101

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();
__________________________________________________________________________

102

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)
__________________________________________________________________________

103

{ 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, ... , vn1 */ int gap,i,j; for (gap=n/2; gap>0; gap/=2) for (i=gap; i<n; i++) for (j=i-gap; j>=0; j-=gap) { if (comp(v[j],v[j+gap])<=0) break; exch(v+j,v+j+gap); } } S studiem declaraiile din aceast funcie. int(*comp)(), (*exch)(); indic faptul c comp i exch snt pointeri la funcii care returneaz un ntreg (primul set de paranteze este necesar). if (comp(v[j],v[j+gap])<=0)
__________________________________________________________________________

104

nseamn apelul funciei comp (adic strcmp sau numcmp), deoarece comp este un pointer la funcie, *comp este funcia, iar comp(v[j],v[j+gap]) este apelul funciei. exch(v+j,v+j+gap) este apelul funciei swap, de inversare a dou linii, inversare care realizeaz interschimbarea adreselor liniilor implicate (vezi seciunea 9.2). Funcia numcmp este urmtoarea: numcmp(char *s1, char *s2) { /* compar s1 i s2 numeric */ double atof(),v1,v2; v1 = atof(s1); v2 = atof(s2); if (v1<v2) return -1; else if (v1>v2) return 1; else return 0; } Pentru ca programul nostru s fie complet s mai prezentm i codul funciei swap, care schimb ntre ei pointerii a dou linii. swap(char *px[], char *py[]) { char *temp; temp = *px; *px = *py; *py = temp; }

__________________________________________________________________________

105

10. Structuri i reuniuni


O structur este o colecie de una sau mai multe variabile, de acelai tip sau de tipuri diferite, grupate mpreun sub un singur nume pentru a putea fi tratate mpreun (n alte limbaje, structurile se numesc articole). Structurile ajut la organizarea datelor complicate, n special n programele mari, deoarece permit unui grup de variabile legate s fie tratate ca o singur entitate. Vom ilustra n acest capitol modul de utilizare a structurilor.

10.1. Elemente de baz


S revenim asupra rutinei de conversie a datei prezentat n capitolul 9. O dat const din zi, lun i an, eventual numrul zilei din an i numele lunii. Aceste cinci variabile pot fi grupate ntr-o singur structur astfel: struct date { int day; int month; int year; int yearday; char mon_name[4]; }; Cuvntul cheie struct introduce o declaraie de structur care este o list de declaraii nchise n acolade. Cuvntul struct poate fi urmat opional de un nume, numit marcaj de structur sau etichet de structur, cum este n exemplul nostru numele date. Acest marcaj denumete acest tip de structur i poate fi folosit n continuare ca o prescurtare pentru declaraia de structur detaliat creia i este asociat.
__________________________________________________________________________

106

Elementele sau variabilele menionate ntr-o structur se numesc membri ai structurii. Un membru al structurii sau o etichet i o variabil oarecare, nemembru, pot avea acelai nume fr a genera conflicte, deoarece ele vor fi ntotdeauna deosebite una de alta din context. Acolada dreapt care ncheie o list de membri ai unei structuri poate fi urmat de o list de variabile, la fel ca i n cazul tipurilor de baz. De exemplu: struct {. . .} x,y,z; este din punct de vedere sintactic analog cu: int x,y,z; n sensul c fiecare declaraie declar pe x, y i z ca variabile de tipul numit (structur n primul caz i ntreg n al doilea) i cauzeaz alocarea de spaiu pentru ele. O declaraie de structur care nu este urmat de o list de variabile nu aloc memorie; ea descrie numai un ablon, o form de structur. Dac structura este marcat sau etichetat, atunci marcajul ei poate fi folosit mai trziu pentru definirea unor alte variabile de tip structur, cu acelai ablon ca structura marcat. De exemplu, fiind dat declaraia: struct date d; ea definete variabila d, ca o structur de acelai fel (ablon) ca structura date. O structur extern sau static poate fi iniializat, atand dup definiia ei o list de iniializatori pentru componente, de exemplu: struct date d = {4,7,1984,185,"iulie"}; Un membru al unei structuri este referit printr-o expresie de forma: nume-structur.membru n care operatorul membru de structur . leag numele membrului de numele structurii. Ca exemplu fie atribuirea: leap = (d.year%4==0) && (d.year%100!=0) || (d.year%400==0); sau verificarea numelui lunii: if (strcmp(d.mon_name,"august")==0) ...
__________________________________________________________________________

107

Structurile pot fi imbricate; o nregistrare de stat de plat, de exemplu, poate fi de urmtoarea form: struct person { char name[NAMESIZE]; char address[ADRSIZE]; long zipcode; long ss_number; double salary; struct date birthdate; struct date hiredate; }; Structura person conine dou structuri de ablon date. Declaraia: struct person emp; definete i aloc o structur cu numele emp de acelai ablon ca i person. Atunci: emp.birthdate.month se refer la luna de natere. Operatorul de membru de structur . este asociativ de la stnga la dreapta.

10.2. Structuri i funcii


Exist un numr de restricii asupra structurilor n limbajul C. Singurele operaii care se pot aplica unei structuri snt accesul la un membru al structurii i considerarea adresei ei cu ajutorul operatorului &. Acest lucru implic faptul c structurile nu pot fi atribuite sau copiate ca entiti i c ele nu pot fi transmise ca argumente la funcii i nici returnate din funcii. Structurile de clas automatic, ca i masivele de aceeai clas, nu pot fi iniializate; pot fi iniializate numai structurile externe i statice, regulile de iniializare fiind aceleai ca pentru masive. Pointerii la structuri nu se supun ns acestor restricii, motiv pentru care structurile i funciile pot coexista i conlucra prin intermediul pointerilor.
__________________________________________________________________________

108

Ca un exemplu, s rescriem programul de conversie a datei, care calculeaz ziua anului, din lun i zi. day_of_year(struct date *pd) { /* calculul zilei anului */ int i, day, leap; day = pd->day; leap = (pd->year%4==0) && (pd->year%100!==0) || (pd->year%400==0); for (i=1; i<pd->month; 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:
__________________________________________________________________________

109

(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];
__________________________________________________________________________

110

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,
__________________________________________________________________________

111

"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<=high) { mid =(low + high) / 2; if ( (cond=strcmp(word,tab[mid].keyword)) <0)
__________________________________________________________________________

112

high = mid - 1; else if (cond>0) low = mid + 1; else return mid; } return -1; } main() { /* numr cuvintele cheie */ int n,t; char word[MAXWORD]; while ((t=getword(word,MAXWORD))!=EOF) if (t==LETTER) if ( (n=binary(word,keytab,NKEYS)) >=0) keytab[n].keycount++; for (n=0; n<NKEYS; n++) if (keytab[n].keycount>0) 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
__________________________________________________________________________

113

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 */
114
__________________________________________________________________________

if (c>='a' && c<='z' || c>='A' && c<='Z') return LETTER; if (c>='0' && c<='9') return DIGIT; return c; } Constantele simbolice LETTER i DIGIT pot avea orice valoare care nu vine n contradicie cu caracterele nealfabetice i EOF. Valori posibile pot fi: #define LETTER 'a' #define DIGIT '0'

10.4. Pointeri la structuri


Pentru a ilustra modul de corelare dintre pointeri i masivele de structuri, s rescriem programul de numrare a cuvintelor cheie dintrun text, de data aceasta folosind pointeri, n loc de indici de masiv. Declaraia extern a masivului de structuri keytab nu necesit modificri, n timp ce funciile main i binary da. Prezentm, n continuare, aceste funcii modificate. #define MAXWORD 20 struct key *binary(char *word, struct key tab[], int n) { /* caut cuvnt */ int cond; struct key * low; struct key *high; struct key *mid; low = &tab[0]; high = &tab[n-1]; while (low<=high) { mid = low + (high-low) / 2; if ((cond=strcmp(word,mid->keyword))<0) high = mid - 1; else if (cond>0)
__________________________________________________________________________

115

low = mid + 1; else return mid; } return NULL; } main() {/* numr cuvintele cheie, versiune cu pointeri */ int t; char word[MAXWORD]; struct key *binary(), *p; while ((t=getword(word.MAXWORD))!=EOF) if (t==LETTER) if ((p=binary(word,keytab,NKEYS)) != NULL) p->keycount++; for (p=keytab; p<keytab+NKEYS; p++) if (p->keycount>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
__________________________________________________________________________

116

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; p<keytab+NKEYS; p++)... Dac p este un pointer la un masiv de structuri, orice operaie asupra lui p ine cont de dimensiunea unei structuri, astfel nct p++ incrementeaz pointerul p la urmtoarea structur din masiv, adunnd la p dimensiunea corespunztoare a unei structuri. Acest lucru nu nseamn c dimensiunea structurii este egal cu suma dimensiunilor membrilor ei deoarece din cerine de aliniere a unor membri se pot genera goluri ntr-o structur. n sfrit, cnd o funcie returneaz un tip complicat i are o list complicat de argumente, ca n: struct key *binary(char *word, struct key tab, int n) funcia poate fi mai greu vizibil i detectabil cu un editor de texte. Din acest motiv, se poate opta i pentru urmtoarea form: struct key *binary(word,tab,n) char *word; struct key tab; int n; unde nainte de acolada de deschidere se precizeaz tipul fiecrui parametru. Alegei forma care v convine i care vi se pare mai sugestiv.

10.5. Structuri auto-referite


S considerm problema mai general, a numrului apariiilor tuturor cuvintelor dintr-un text de intrare. Deoarece lista cuvintelor nu este cunoscut dinainte, n-o putem sorta folosind algoritmul de cutare binar, ca n paragraful precedent. Nu putem utiliza nici o
__________________________________________________________________________

117

cutare liniar pentru fiecare cuvnt, pe msura apariiei lui pentru a vedea dac a mai fost prezent sau nu, pentru c timpul de execuie al programelor ar crete ptratic cu numrul cuvintelor de la intrare. Un mod de a organiza datele pentru a lucra eficient cu o list de cuvinte arbitrare este de a pstra mulimea de cuvinte, tot timpul sortat, plasnd fiecare nou cuvnt din intrare pe o poziie corespunztoare, relativ la intrrile anterioare. Dac am realiza acest lucru prin deplasarea cuvintelor ntr-un masiv liniar, programul ar dura, de asemenea, foarte mult. De aceea, pentru rezolvarea eficient a acestei probleme vom folosi o structur de date numit arbore binar. Fiecare nod al arborelui va reprezenta un cuvnt distinct din intrare i va conine urmtoarea informaie: - un pointer la cuvnt; - un contor pentru numrul de apariii; - un pointer la descendentul stng al cuvntului; - un pointer la descendentul drept al cuvntului. Nici un nod al arborelui nu va avea mai mult dect doi descendeni dar poate avea un descendent sau chiar nici unul. Arborele se construiete astfel nct pentru orice nod, subarborele stng al su conine numai cuvintele care snt mai mici dect cuvntul din nod, iar sub-arborele drept conine numai cuvinte, care snt mai mari dect cuvntul din nod, compararea fcndu-se din punct de vedere lexicografic. Pentru a ti dac un cuvnt nou din intrare exist deja n arbore se pornete de la nodul rdcin i se compar noul cuvnt cu cuvntul memorat n nodul rdcin. Dac ele coincid se incrementeaz contorul de numrare a apariiilor pentru nodul rdcin i se va citi un nou cuvnt din intrare. Dac noul cuvnt din intrare este mai mic dect cuvntul memorat n nodul rdcin, cutarea continu cu descendentul stng, altfel se investigheaz descendentul drept. Dac nu exist nici un descendent pe direcia cerut, noul cuvnt nu exist n arbore i va fi inserat pe poziia descendentului corespunztor. Se observ c acest proces de cutare este recursiv, deoarece cutarea din fiecare nod utilizeaz o cutare ntr-unul dintre descendenii si.
__________________________________________________________________________

118

Prin urmare se impune de la sine ca rutinele de inserare n arbore i de imprimare s fie recursive. Revenind la descrierea unui nod, el apare ca fiind o structur cu patru componente: struct tnode { /* nodul de baz */ char *word; /* pointer la cuvnt */ int count; /* numrtor de apariii */ struct tnode *left; /* descendent stng */ struct tnode *right; /* descendent drept */ }; Aceast declaraie recursiv a unui nod este perfect legal, deoarece o structur nu poate conine ca i component o intrare a ei nsi dar poate conine un pointer la o structur de acelai ablon cu ea. Declaraia: struct tnode *left; declar pe left ca fiind un pointer la structur (nod) i nu o structur nsi. n program vom folosi rutinele getword, pentru citirea unui cuvnt din intrare, alloc pentru rezervarea de spaiu necesar memorrii unui cuvnt i alte cteva rutine pe care le cunoatem deja. Rutina principal main citete prin intermediul rutinei getword un cuvnt, i l plaseaz n arbore prin rutina tree. #define MAXWORD 20 main() { /* contorizare apariii cuvinte */ struct tnode *root, *tree(); char word[MAXWORD]; int t; root = NULL; while ((t=getword(word,MAXWORD))!=EOF) if (t==LETTER) root = tree(root,word); treeprint(root); }
__________________________________________________________________________

119

Rutina main gestioneaz fiecare cuvnt din intrare ncepnd cu cel mai nalt nivel al arborelui (rdcina). La fiecare pas, cuvntul din intrare este comparat cu cuvntul asociat rdcinii i este apoi transmis n jos, fie descendentului stng, fie celui drept, printr-un apel recursiv la rutina tree. n acest proces, cuvntul fie exist deja, undeva n arbore, caz n care contorul lui de numrare a apariiilor se incrementeaz, fie cutarea continu pn la ntlnirea unui pointer NULL, caz n care nodul trebuie creat i adugat arborelui. Cnd se creeaz un nod nou, rutina tree returneaz un pointer la el, care apoi este introdus n nodul de origine (adic n nodul al crui descendent este noul nod) n cmpul left sau right dup cum noul cuvnt este mai mic sau mai mare fa de cuvntul origine. Rutina tree, care returneaz un pointer la o structur de ablon tnode are urmtorul cod: struct tnode *tree(struct tnode *p, char *w) { /* introduce cuvntul w n nodul p */ struct tnode *talloc(int n); char *strsav(char *s); int cond; if (p==NULL) { /* a sosit un nou cuvnt */ p = talloc(); /* creeaz un nod nou */ p->word = strsav(w); p->count = 1; p->left = p->right = NULL; } else if ((cond=strcmp(w,p->word))==0) p->count++; else if (cond<0) /* noul cuvnt mai mic */ p->left = tree(p->left,w); else /* noul cuvnt mai mare */ p->right = tree(p->right,w); return p;
__________________________________________________________________________

120

} 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
__________________________________________________________________________

121

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)); }
__________________________________________________________________________

122

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: 0<=H<0x100 (n hexazecimal) Codul hash astfel obinut va fi folosit apoi ca un indice ntr-o tabel de pointeri. Un element al acestei tabele (masiv) indic nceputul unui lan de blocuri care descriu simboluri cu acelai cod hash. Dac un element al tabelei este NULL nseamn c nici un simbol nu are valoarea respectiv de hashing. Un bloc dintr-un lan indicat de un element al tabelei este o structur care conine un pointer la simbol, un pointer la textul de substituie i un pointer la urmtorul bloc din lan. Un pointer NULL la urmtorul bloc din lan indic sfritul lanului. ablonul unei structuri (nod) este urmtorul: struct nlist { char *name; char *def;
__________________________________________________________________________

123

struct nlist *next;/ * urmtoarea intrare n lan */ }; Tabelul de pointeri care indic nceputurile lanului de blocuri ce descriu simboluri de acelai cod hash este: #define HASHSIZE 0x100 static struct nlist *hashtab[HASHSIZE]; Algoritmul de hashing pe care-l prezentm nu este cel mai bun posibil, dar are meritul de a fi extrem de simplu: hash(char *s) { /* formeaz valoarea hash pentru irul s */ int hashval; for (hashval=0; *s!='\0';) hashval += *s++; return hashval % HASHSIZE; } Algoritmul de hashing produce un indice n masivul de pointeri hashtab. n procesul de cutare a unui simbol, dac el exist, el trebuie s fie n lanul de blocuri care ncepe la adresa coninut de elementul din hashtab cu indicele respectiv. Cutarea n tabela de simboluri hashtab se realizeaz cu funcia lookup. Dac simbolul cutat este prezent undeva n lan, funcia returneaz un pointer la el; altfel returneaz NULL. struct nlist *lookup(char *s) { /* caut irul s n hashtab */ struct nlist *np; for (np=hashtab[hash(s)]; np!=NULL; np=np->next) if (strcmp(s,np->name)==0) return np; /* s-a gsit s */ return NULL; /* nu s-a gsit s */ }

__________________________________________________________________________

124

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
__________________________________________________________________________

125

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; i<HASHSIZE; i++) hashtab[i] = NULL;
__________________________________________________________________________

126

do { getword(num); getword(def); if ((np=lookup(num))==NULL) printf("New name\n"); else printf("Old definition: %s\n", np->def); install(num,def); } while (1); }

10.7. Cmpuri
Un cmp se definete ca fiind o mulime de bii consecutivi dintrun 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:
__________________________________________________________________________

127

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

__________________________________________________________________________

128

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.
__________________________________________________________________________

129

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. membru sau 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);
__________________________________________________________________________

130

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 }
__________________________________________________________________________

131

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.
__________________________________________________________________________

132

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
__________________________________________________________________________

133

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; int count; struct tnode *left; struct tnode *right; } TREENODE, *TREEPTR; /* pointer la text */ /* numr apariii */ /* descendent stng */ /* descendent drept */

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
__________________________________________________________________________

134

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.

__________________________________________________________________________

135

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-fiier n linia de comand prin care a fost lansat programul.
__________________________________________________________________________

136

Redirectarea fiierului stdout se specific prin construcia: >specificator-fiier n 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 <stdio.h> 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.

__________________________________________________________________________

137

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: r deschide un fiier pentru citire; r+ deschide pentru citire i scriere; w trunchiaz 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; a deschide 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
__________________________________________________________________________

138

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
__________________________________________________________________________

139

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 terge indicatorul de sfrit de fiier. Funcia ftell obine valoarea curent a indicatorului de poziie pentru fiierul asociat fluxului flux.

__________________________________________________________________________

140

Funcia rewind poziioneaz indicatorul de poziie pentru fiierul asociat fluxului flux la nceputul fiierului. Este echivalent cu: (void)fseek(flux, 0L, SEEK_SET) cu completarea c funcia rewind terge i indicatorul de eroare al fluxului. Valori returnate Funcia rewind nu returneaz nici o valoare. n caz de succes, fseek returneaz 0, i ftell returneaz offset-ul curent. n caz de eroare se returneaz EOF i variabila global errno indic codul erorii.

11.3. Citire i scriere fr format


Nume fgets - citete un ir de caractere dintr-un flux text
Declaraie char *fgets(char *s, int size, FILE *flux); Descriere Funcia fgets cel mult size-1 caractere din flux i le memoreaz n zona indicat de s. Citirea se oprete la detectarea sfritului de fiier sau new-line. Dac se citete caracterul new-line acesta este memorat n s. Dup ultimul caracter se memoreaz null. Apeluri ale acestei funcii pot fi combinate cu orice apeluri ale altor funcii de intrare din bibliotec (fscanf, de exemplu) pentru un acelai flux de intrare. Valori returnate Funcia returneaz adresa s n caz de succes, sau NULL n caz de eroare sau la ntlnirea sfritului de fiier dac nu s-a citit nici un caracter.

__________________________________________________________________________

141

Nume fputs - scrie un ir de caractere ntr-un flux text


Declaraie int fputs(const char *s, FILE *flux); Descriere Funcia fputs scrie irul s n flux fr caracterul terminator null. Apeluri ale acestei funcii pot fi combinate cu orice apeluri ale altor funcii de ieire din bibliotec (fprintf, de exemplu) pentru un acelai flux de ieire. Valori returnate Funcia returneaz o valoare non-negativ n caz de succes, sau EOF n caz de eroare.

Nume fread, fwrite - intrri / ieiri pentru fluxuri binare


Declaraie unsigned fread(void *ptr, unsigned size, unsigned nel, FILE *flux); unsigned fwrite(const void *ptr, unsigned size, unsigned nel, FILE *flux); Descriere Funcia fread citete nel elemente, fiecare avnd mrimea size octei, din fluxul indicat de flux, i le memoreaz n zona indicat de ptr. Funcia fwrite scrie nel elemente, fiecare avnd mrimea size octei, din fluxul indicat de flux, pe care le ia din zona indicat de ptr. Valori returnate Funciile returneaz numrul de elemente citite sau scrise cu succes (i nu numrul de caractere). Dac apare o eroare sau se
__________________________________________________________________________

142

ntlnete sfritul de fiier, valoarea returnat este mai mic dect nel (posibil zero).

11.4. Citire cu format


Nume scanf, fscanf, sscanf - citire cu format
Declaraie int scanf(const char *format, ...); int fscanf(FILE *flux, const char *format, ...); int sscanf(const char *str, const char *format, ...); Descriere Familia de funcii scanf scaneaz intrarea n concordan cu irul de caractere format dup cum se descrie mai jos. Acest format poate conine specificatori de conversie; rezultatele unor astfel de conversii (dac se efectueaz) se memoreaz prin intermediul argumentelor pointer. Funcia scanf citete irul de intrare din fluxul standard stdin, fscanf din flux, i sscanf din irul indicat de str. Fiecare argument pointer trebuie s corespund n ordine ca tip cu fiecare specificator de conversie (dar a se vedea suprimarea mai jos). Dac argumentele nu snt suficiente comportamentul programului este imprevizibil. Toate conversiile snt introduse de caracterul %. irul format poate conine i alte caractere. Spaii albe (blanc, tab, sau new-line) din irul format se potrivesc cu orice spaiu alb n orice numr (inclusiv nici unul) din irul de intrare. Orice alte caractere trebuie s se potriveasc exact. Scanarea se oprete atunci cnd un caracter din irul de intrare nu se potrivete cu cel din format. Scanarea se oprete de asemenea atunci cnd o conversie nu se mai poate efectua (a se vedea mai jos).

__________________________________________________________________________

143

Conversii Dup caracterul % care introduce o conversie poate urma un numr de caractere indicatori, dup cum urmeaz: * Suprim atribuirea. Conversia care urmeaz se face n mod obinuit, dar nu se folosete nici un argument pointer; rezultatul conversiei este pur i simplu abandonat. Conversia este de tip dioux sau n i argumentul asociat este un pointer la short (n loc de int). Conversia este de tip dioux sau n i argumentul asociat este un pointer la long (n loc de int), sau conversia este de tip efg i argumentul asociat este un pointer la double (n loc de float). Conversia este de tip efg i argumentul asociat este un pointer la long double.

h l

n completare la aceti indicatori poate exista o mrime w maxim opional pentru cmp, exprimat ca un ntreg zecimal, ntre caracterul % i cel de conversie, i naintea indicatorului. Dac nu este dat o mrime maxim se folosete mrimea implicit infinit (cu o excepie la conversia de tip c); n caz contrar se scaneaz cel mult un numr de w caractere n timpul conversiei. nainte de a ncepe o conversie, majoritatea conversiilor ignor spaiile albe; acestea nu snt contorizate n mrimea cmpului. Snt disponibile urmtoarele conversii: % Potrivire cu un caracter %. Cu alte cuvinte, %% n irul format trebuie s se potriveasc cu un caracter %. Nu se efectueaz nici o conversie i nici o atribuire. Potrivire cu un ntreg zecimal (eventual cu semn); argumentul asociat trebuie s fie un pointer la int. Potrivire cu un ntreg (eventual cu semn); argumentul asociat trebuie s fie un pointer la int. Valoarea ntreag este citit n
__________________________________________________________________________

d i

144

baza 16 dac ncepe cu 0x sau 0X, n baza 8 dac ncepe cu 0, i n baza 10 n caz contrar. Snt folosite numai caracterele care corespund bazei respective. o u x f Potrivire cu un ntreg octal fr semn; argumentul asociat trebuie s fie un pointer la unsigned. Potrivire cu un ntreg zecimal fr semn; argumentul asociat trebuie s fie un pointer la unsigned. Potrivire cu un ntreg hexazecimal fr semn; argumentul asociat trebuie s fie un pointer la unsigned. Potrivire cu un numr n virgul mobil (eventual cu semn); argumentul asociat trebuie s fie un pointer la float. Potrivire cu o secven de caractere diferite de spaiu alb; argumentul asociat trebuie s fie un pointer la char, i zona trebuie s fie suficient de mare pentru a putea primi toat secvena i caracterul terminator null. irul de intrare se termin la un spaiu alb sau la atingerea mrimii maxime a cmpului (prima condiie ntlnit). Potrivire cu o secven de caractere de mrime w (dac aceasta este specificat; prin lips se ia w= 1); argumentul asociat trebuie s fie un pointer la char, i zona trebuie s fie suficient de mare pentru a putea primi toat secvena (nu se adaug terminator null). Nu se ignor ca de obicei spaiile albe din fa. Pentru a ignora mai nti spaiile albe se indic un spaiu explicit n format. Potrivire cu o secven nevid de caractere din setul specificat de caractere acceptate; argumentul asociat trebuie s fie un pointer la char, i zona trebuie s fie suficient de mare pentru a putea primi toat secvena i caracterul terminator null. Nu se ignor ca de obicei spaiile albe din fa. irul de intrare va fi format din caractere aflate n (sau care nu se afl n) setul specificat n format; setul este definit de caracterele aflate ntre [ i ]. Setul
__________________________________________________________________________

e,g Echivalent cu f. s

145

exclude acele caractere dac primul caracter dup [ este ^. Pentru a include caracterul ] n set, acesta trebuie s fie primul caracter dup [ sau ^; caracterul ] aflat n orice alt poziie nchide setul. Caracterul - are i el un rol special: plasat ntre dou alte caractere adaug toate celelalte caractere aflate n intervalul respectiv la set. Pentru a include caracterul - acesta trebuie s fie ultimul caracter nainte de ]. De exemplu, "% [^]0-9-]" semnific setul orice caracter cu excepia ], 0 pn la 9, i -. irul se termin la apariia unui caracter care nu se afl (sau, dac se precizeaz ^, care se afl) n set sau dac se atinge mrimea maxim specificat. p n Potrivire cu o valoare pointer (aa cum se afieaz cu %p n printf); argumentul asociat trebuie s fie un pointer la pointer. Nu se prelucreaz nimic din irul de intrare; n schimb, numrul de caractere consumate pn la acest punct din irul de intrare este memorat la argumentul asociat, care trebuie s fie un pointer la int.

Valori returnate Funciile returneaz numrul de valori atribuite, care poate fi mai mic dect numrul de argumente pointer, sau chiar zero, n cazul n care apar nepotriviri ntre format i irul de intrare. Zero indic faptul c, chiar dac avem un ir de intrare disponibil, nu s-a efectuat nici o conversie (i atribuire); aceast situaie apare atunci cnd un caracter din irul de intrare este invalid, cum ar fi un caracter alfabetic pentru o conversie %d. Valoarea EOF este returnat dac apare un eroare nainte de prima conversie, cum ar fi detectarea sfritului de fiier. Dac o eroare sau un sfrit de fiier apare dup ce o conversie a nceput, se returneaz numrul de conversii efectuate cu succes.

__________________________________________________________________________

146

11.5. Scriere cu format


Nume printf, fprintf, sprintf - scriere cu format
Declaraie int printf(const char *format, ...); int fprintf(FILE *flux, const char *format, ...); int sprintf(char *str, const char *format, ...); Descriere Funciile din familia printf genereaz o ieire n concordan cu format dup cum se descrie mai jos. Funcia printf afieaz ieirea la fluxul standard stdout; fprintf scrie ieirea la flux; sprintf scrie ieirea n irul de caractere str. Aceste funcii genereaz ieirea sub controlul irului format care specific cum se convertesc argumentele pentru ieire. irul de formatare irul format este un ir de caractere, printre care se pot afla zero sau mai multe directive: caractere obinuite (diferite de %) care snt copiate aa cum snt n fluxul de ieire, i specificaii de conversie, fiecare dintre ele rezultnd din ncrcarea a zero sau mai multe argumente. Fiecare specificaie de conversie este introdus de caracterul % i se termin cu un specificator de conversie. ntre acestea pot fi (n aceast ordine) zero sau mai muli indicatori, o mrime minim a cmpului opional, o precizie opional i un modificator opional de lungime. Argumentele trebuie s corespund n ordine cu specificatorii de conversie. Acestea snt folosite n ordinea dat, unde fiecare caracter * i fiecare specificator de conversie solicit urmtorul argument. Dac argumentele nu snt suficiente comportamentul programului este imprevizibil.
__________________________________________________________________________

147

Caractere indicatori Caracterul % este urmat de zero, unul sau mai muli indicatori: # Valoarea numeric se convertete n format alternativ. Pentru conversii de tip o, primul caracter al irului de ieire este zero (prin prefixare cu 0 dac valoarea nu este zero). Pentru conversii de tip x i X, o valoare nenul este prefixat cu 0x (sau 0X pentru conversii de tip X). Pentru conversii de tip e, E, f, F, g i G, rezultatul va conine ntotdeauna punctul zecimal, chiar dac nu apare partea fracionar (n mod normal punctul zecimal apare n aceste conversii numai dac exist i partea fracionar). Pentru conversii de tip g i G zerourile finale nu snt eliminate aa cum se procedeaz n mod normal. Pentru alte conversii rezultatul este nedefinit. Valoarea numeric este convertit cu zerouri la stnga. Pentru conversii de tip d, i, o, u, x, X, e, E, f, F, g i G, valoarea convertit este completat cu zerouri la stnga n loc de blanc. Dac apar indicatorii 0 i - mpreun, indicatorul 0 este ignorat. Dac pentru o conversie numeric (d, i, o, u, x, X) este dat o precizie, indicatorul 0 este ignorat. Pentru alte conversii rezultatul este nedefinit. Valoarea convertit este aliniat la stnga (implicit alinierea se face la dreapta). Cu excepia conversiilor de tip n, valoarea convertit este completat la dreapta cu blanc, n loc s fie completat la stnga cu blanc sau zero. Dac apar indicatorii 0 i - mpreun, indicatorul 0 este ignorat.

Sp (spaiu) n cazul unui rezultat al unei conversii cu semn, naintea unui numr pozitiv sau ir vid se pune un blanc. + Semnul (+ sau -) este plasat naintea numrului generat de o conversie cu semn. Implicit semnul este folosit numai pentru numere negative. Dac apar indicatorii + i Sp mpreun, indicatorul Sp este ignorat.
__________________________________________________________________________

148

Limea cmpului Un ir de cifre zecimale (cu prima cifr nenul) specific o lime minim pentru cmp. Dac valoarea convertit are mai puine caractere dect limea specificat, va fi completat cu spaii la stnga (sau dreapta, dac s-a specificat aliniere la stnga). n locul unui numr zecimal se poate folosi * pentru a specifica faptul c limea cmpului este dat de argumentul urmtor, care trebuie s fie de tip int. O valoare negativ pentru lime este considerat un indicator urmat de o valoare pozitiv pentru lime. n nici un caz nu se va trunchia cmpul; dac rezultatul conversiei este mai mare dect limea cmpului, cmpul este expandat pentru a conine rezultatul conversiei. Precizia Precizia (opional) este dat de caracterul . urmat de un ir de cifre zecimale. n locul irului de cifre zecimale se poate scrie * pentru a specifica faptul c precizia este dat de argumentul urmtor, care trebuie s fie de tip int. Dac precizia este dat doar de ., sau dac precizia este negativ, atunci aceasta se consider zero. Precizia d numrul minim de cifre care apar pentru conversii de tip d, i, o, u, x, X, numrul de cifre care apar dup punctul zecimal pentru conversii de tip e, E, f, F, numrul maxim de cifre semnificative pentru conversii de tip g i G, sau numrul maxim de caractere generate pentru conversii de tip s. Modificator de lungime n acest caz prin conversie ntreag nelegem conversie de tip d, i, o, u, x, X. h Conversia ntreag care urmeaz corespunde unui argument short sau unsigned short, sau urmtoarea conversie de tip n corespunde unui argument de tip pointer la short. Conversia ntreag care urmeaz corespunde unui argument long sau unsigned long, sau urmtoarea conversie de tip n corespunde unui argument de tip pointer la long.
__________________________________________________________________________

149

Urmtoarea conversie de tip e, E, f, g sau G corespunde unui argument long double.

Specificator de conversie Un caracter care specific tipul conversiei care se va face. Specificatorii de conversie i semnificaia lor snt: d,i Argumentul de tip int este convertit la notaia zecimal cu semn. Precizia, dac este dat, d numrul minim de cifre care trebuie s apar; dac valoarea convertit necesit mai puine cifre, aceasta este completat la stnga cu zerouri. Precizia implicit este 1. Dac valoarea 0 este afiat cu precizie explicit 0, ieirea este vid. o,u,x,X Argumentul de tip unsigned este convertit la notaie octal fr semn (o), zecimal fr semn (u), sau hexazecimal fr semn (x i X). Literele abcdef se folosesc pentru conversii de tip x; literele ABCDEF pentru conversii de tip X. Precizia, dac este dat, d numrul minim de cifre care trebuie s apar; dac valoarea convertit necesit mai puine cifre, aceasta este completat la stnga cu zerouri. Precizia implicit este 1. Dac valoarea 0 este afiat cu precizie explicit 0, ieirea este vid. e,E Argumentul de tip flotant este rotunjit i convertit n stil [-]d.dddedd unde avem o cifr nainte de punctul zecimal i numrul de cifre dup acesta este egal cu precizia; dac aceasta lipsete se consider 6; dac precizia este zero, punctul zecimal nu apare. O conversie de tip E folosete litera E (n loc de e) pentru a introduce exponentul. Exponentul are ntotdeauna cel puin dou cifre; dac valoarea este zero, exponentul este 00. f,F Argumentul de tip flotant este rotunjit i convertit n notaie zecimal n stil [-]ddd.ddd, unde numrul de cifre dup punctul zecimal este egal cu precizia specificat. Dac precizia lipsete se
__________________________________________________________________________

150

consider 6; dac precizia este explicit zero, punctul zecimal nu apare. Dac punctul zecimal apare, cel puin o cifr apare naintea acestuia. g,G Argumentul de tip flotant este convertit n stil f sau e (sau E pentru conversii de tip G). Precizia specific numrul de cifre semnificative. Dac precizia lipsete se consider 6; dac precizia este zero se consider 1. Stilul e este folosit dac exponentul rezultat n urma conversiei este mai mic dect ori mai mare sau 4 egal cu precizia. Zerourile finale snt eliminate din partea fracionar a rezultatului; punctul zecimal apare numai dac este urmat de cel puin o cifr. c s Argumentul de tip int este convertit la unsigned char i se scrie caracterul rezultat. Argumentul de tip const char * este un pointer la un ir de caractere. Caracterele din ir snt scrise pn la (fr a include) caracterul terminator null; dac precizia este specificat, nu se scrie un numr mai mare dect cel specificat. Dac precizia este dat, nu e nevoie de caracterul null; dac precizia nu este specificat, sau dac este mai mare dect mrimea irului, irul trebuie s conin un caracter terminator null. Argumentul de tip pointer este scris n hexazecimal; formatul este specific sistemului de calcul. Numrul de caractere scrise pn n acest moment este memorat la argumentul de tip int *. Nu se face nici o conversie. Se scrie un caracter %. Nu se face nici o conversie. Specificaia complet este %%.

p n %

Valoare returnat Funciile returneaz numrul de caractere generate (nu se include caracterul terminator null pentru sprintf).

__________________________________________________________________________

151

11.6. Tratarea erorilor


Nume perror - afieaz un mesaj de eroare sistem
Declaraie void perror(const char *s); #include <errno.h> const char *sys_errlist[]; int sys_nerr; Descriere Rutina perror afieaz un mesaj la ieirea standard de eroare, care descrie ultima eroare ntlnit la ultimul apel sistem sau funcie de bibliotec. Mai nti se afieaz argumentul s, apoi virgula i blanc, i n final mesajul de eroare i new-line. Se recomand (mai ales pentru depanare) ca argumentul s s includ numele funciei n care a aprut eroarea. Codul erorii se ia din variabila extern errno. Lista global de erori sys_errlist[] indexat cu errno poate fi folosit pentru a obine mesajul de eroare fr new-line. Ultimul indice de mesaj din list este sys_nerr-1. Se recomand o atenie deosebit n cazul accesului direct la list deoarece unele coduri noi de eroare pot lipsi din sys_errlist[]. Dac un apel sistem eueaz variabila errno indic codul erorii. Aceste valori pot fi gsite n <errno.h>. Funcia perror servete la afiarea acestui cod de eroare ntr-o form lizibil. Dac un apel terminat cu eroare nu este imediat urmat de un apel perror, valoarea variabilei errno se poate pierde dac nu e salvat.

Nume clearerr, feof, ferror - verific i reseteaz starea fluxului


Declaraie void clearerr(FILE *flux);
__________________________________________________________________________

152

int feof(FILE *flux); int ferror(FILE *flux); int fileno( FILE *flux); Descriere Funcia clearerr terge indicatorii de sfrit de fiier i eroare ai fluxului. Funcia feof testeaz indicatorul de sfrit de fiier al fluxului, i returneaz non-zero dac este setat. Acesta este setat dac o operaie de citire a detectat sfritul de fiier. Funcia ferror testeaz indicatorul de eroare al fluxului, i returneaz non-zero dac este setat. Acesta este setat dac o operaie de citire sau scriere a detectat o eroare (datorat de exemplu hardware-ului). Funciile de citire (cu sau fr format) nu fac distincie ntre sfrit de fiier i eroare, astfel c trebuie apelate funciile feof i ferror pentru a determina cauza. Funcia fileno examineaz argumentul flux i returneaz descriptorul asociat de sistemul de operare acestui flux. Atenie! Este foarte frecvent folosirea incorect a funciei feof pentru a testa dac s-a ajuns la sfritul fiierului. Nu se recomand n nici un caz acest stil de programare: #define LSIR 80 char lin[LSIR]; FILE *fi,*fo; fi=fopen(nume-fiier-intrare,"rt"); fo=fopen(nume-fiier-ieire,"wt"); while (!feof(fi)) { /* greit! */ fgets(lin,LSIR,fi); fputs(lin,fo); } fclose(fi); fclose(fo); n aceast secven, dac i ultima linie a fiierului text de intrare este terminat cu new-line, aceasta va fi scris de dou ori n fiierul de ieire. De ce? Dup ce se citete ultima linie nc nu este
__________________________________________________________________________

153

poziionat indicatorul de sfrit de fiier, deci funcia fgets returneaz succes. La reluarea ciclului se ncearc un nou fgets i abia acum se depisteaz sfritul de fiier, fapt marcat n zona rezervat fluxului fi. Astfel coninutul tabloului lin rmne nemodificat i este scris a doua oar n fiierul de ieire. Abia la o nou reluare a ciclului funcia feof ne spune c s-a depistat sfritul de fiier. n acest manual snt prezentate mai multe programe care efectueaz diferite prelucrri asupra unor fiiere text. Pentru simplitate toate programele presupun c nu apar erori la citire sau la scriere.

11.7. Operaii cu directoare


Funciile de parcurgere a cataloagelor de fiiere descrise n aceast seciune (opendir, readdir, closedir) snt definite de mai multe medii de programare C (Borland, Watcom, Visual C, GNU Linux), precum i de standardul POSIX. Aceste funcii snt descrise n <dirent.h>. Funciile de redenumire i tergere a unor fiiere snt descrise n <stdio.h>.

Nume opendir - deschide un director


Declaraie DIR *opendir(const char *nume); Descriere Funcia opendir deschide un flux pentru directorul cu numele nume, i returneaz un pointer la fluxul deschis. Fluxul este poziionat pe prima intrare din director. Valoare returnat Funcia returneaz un pointer la flux n caz de succes, sau NULL n caz de eroare i variabila global errno indic codul erorii.
__________________________________________________________________________

154

Cteva erori posibile EACCES Acces interzis ENOTDIR nume nu este un director

Nume readdir - citete un director


Declaraie struct dirent *readdir(DIR *dir); Descriere Funcia readdir returneaz un pointer la o structur de tip dirent care reprezint urmtoarea intrare din directorul indicat de fluxul dir. Returneaz NULL dac s-a depistat sfritul de director sau dac a aprut o eroare. Structura de tip dirent conine un cmp char d_name[]. Utilizarea altor cmpuri din structur reduce portabilitatea programelor. Valoare returnat Funcia returneaz un pointer la o structur de tip dirent, sau NULL dac s-a depistat sfritul de director sau dac a aprut o eroare.

Nume closedir - nchide un director


Declaraie int closedir(DIR *dir); Descriere Funcia closedir nchide fluxul dir. Valoare returnat Funcia returneaz 0 n caz de succes sau EOF n caz de eroare.

__________________________________________________________________________

155

Nume rename - redenumete un fiier remove - terge un fiier


Declaraie int rename(const char *old, const char *new); int remove(const char *name); Descriere Funcia rename schimb numele unui fiier din old n new. Dac a fost precizat un periferic n new, acesta trebuie s coincid cu cel din old. Directoarele din old i new pot s fie diferite, astfel c rename poate fi folosit pentru a muta un fiier dintr-un director n altul. Nu se permit specificatori generici (wildcards). Funcia remove terge fiierul specificat prin name. Valoare returnat n caz de succes se returneaz 0. n caz de eroare se returneaz EOF i variabila global errno indic codul erorii.

11.8. Programe demonstrative


Primele trei programe primesc ca parametri n linia de comand numele fiierelor pe care le vor prelucra. Ultimul program primete ca parametru n linia de comand numele directorului al crui coninut va fi afiat. 1) Determinarea mrimii unui fiier #include <stdio.h> FILE *f; int main(int ac, char **av) { if (ac!=2) { fputs("Un argument!\n",stderr); return 1; }
__________________________________________________________________________

156

f = fopen(av[1],"rb"); if (!f) { perror("Eroare la deschidere"); return 1; } fseek(f,0,SEEK_END); fprintf(stderr,"File %s, size %ld\n", ftell(f)); fclose(f); return 0; } 2) Copierea unui fiier Funciile fgets i fputs se folosesc pentru fluxuri deschise n mod text. Cum se utilizeaz pentru copierea unui fiier text? #include <stdio.h> #define LSIR 80 char lin[LSIR]; FILE *fi, *fo; int main(int ac, char **av) { if (ac!=3) { fputs("Doua argumente!\n",stderr); } fi=fopen(av[1],"rt"); fo=fopen(av[2],"wt"); if (!fi || !fo) { perror("Eroare la deschidere"); return 1; } while (fgets(lin,LSIR,fi)) fputs(lin,fo); fclose(fi); fclose(fo); return 0; } Funciile fread i fwrite se folosesc pentru fluxuri deschise n mod binar. Cum se utilizeaz pentru copierea unui fiier binar?
__________________________________________________________________________

157

#include <stdio.h> #define LZON 80 char zon[LZON]; FILE *fi, *fo; int k; int main(int ac, char **av) { if (ac!=3) { fputs("Doua argumente!\n",stderr); return 1; } fi=fopen(av[1],"rb"); fo=fopen(av[2],"wb"); if (!fi || !fo) { perror("Eroare la deschidere"); return 1; } while (k=fread(zon,1,LZON,fi)) fwrite(zon,1,k,fo); fclose(fi); fclose(fo); return 0; } 3) Prelucrarea unui fiier text Programul prezentat n continuare citete un fiier text care conine pe fiecare linie un ir de caractere (fr spaii) i trei valori ntregi, i afieaz pe terminal numele pe 12 poziii aliniat la stnga i media aritmetic a celor trei valori ntregi. #include <stdio.h> FILE *fi; char num[10]; int a,b,c; double m; int main(int ac, char **av) { if (ac!=2) { fputs("Un argument!\n",stderr);
__________________________________________________________________________

158

return 1; } fi=fopen(av[1],"rt"); if (!fi) { perror("Eroare la deschidere"); return 1; } while (fscanf(fi,"%s %d %d %d", num,&a,&b,&c)!=EOF) { m=(a+b+c)/3.0; printf("%-12s%6.2lf\n",num,m); } fclose(fi); return 0; } 4) Afiarea coninutului unui director #include <dirent.h> #include <stdio.h> DIR *dir; struct dirent *ent; int main(int ac, char **av) { if (ac!=2) { printf("Un parametru\n"); return 1; } dir = opendir(av[1]); if (!dir) { perror("Eroare open dir"); return 1; } while (ent=readdir(dir)) printf("%s\n",ent->d_name); return 0; }
__________________________________________________________________________

159

12. Alte rutine din biblioteca standard


n acest capitol snt descrise funcii care rezolv probleme legate de alocarea dinamic a memoriei, sortare i cutare, clasificare, operaii cu blocuri de memorie i iruri de caractere, funcii matematice.

12.1. Alocarea dinamic a memoriei


Nume calloc, malloc, realloc - aloc memoria n mod dinamic free - elibereaz memoria alocat n mod dinamic
Declaraie #include <stdlib.h> void *calloc(unsigned nel, unsigned size); void *malloc(unsigned size); void *realloc(void *ptr, unsigned size); void free(void *ptr); Descriere Funcia calloc aloc memorie pentru un tablou de nel elemente, fiecare de mrime size octei i returneaz un pointer la memoria alocat. Coninutul memoriei este pus la zero. Funcia malloc aloc size octei i returneaz un pointer la memoria alocat. Coninutul memoriei nu este ters. Funcia free elibereaz spaiul de memorie indicat de ptr, care trebuie s fi fost returnat de un apel anterior malloc, calloc sau realloc. n caz contrar, sau dac a existat deja un apel anterior free(ptr), comportamentul programului este imprevizibil. Funcia realloc schimb mrimea blocului de memorie indicat de ptr la size octei. Coninutul rmne neschimbat la mrimea minim dintre mrimea veche i cea nou; noul spaiu de memorie
__________________________________________________________________________

160

care este eventual alocat este neiniializat. Dac ptr este NULL apelul este echivalent cu malloc(size); dac size este egal cu zero apelul este echivalent cu free(ptr). Cu excepia cazului cnd ptr este NULL, acesta trebuie s fi fost returnat de un apel precedent malloc, calloc sau realloc. Valori returnate Pentru calloc i malloc valoarea returnat este un pointer la memoria alocat, care este aliniat n mod corespunztor pentru orice tip de variabile, sau NULL dac nu exist suficient memorie continu. Funcia free nu returneaz nimic. Funcia realloc returneaz un pointer la noua zon de memorie alocat, care este aliniat n mod corespunztor pentru orice tip de variabile, i poate fi diferit de ptr, sau poate fi NULL dac nu exist suficient memorie continu sau dac valoarea size este egal cu 0. Dac realloc eueaz, blocul original rmne neatins nu este nici eliberat nici mutat.

12.2. Sortare i cutare


Nume qsort - sorteaz un tablou bsearch - cutare binar ntr-un tablou sortat
Declaraie #include <stdlib.h> void qsort(void *base, unsigned nel, unsigned size, int (*comp) (const void *, const void *)); void *bsearch(const void *key, const void *base, unsigned nel, unsigned size, int (*comp)(const void *, const void *)); Descriere
__________________________________________________________________________

161

Funcia qsort sorteaz un tablou de nel elemente, fiecare de mrime size. Argumentul base indic spre nceputul tabloului. Elementele tabloului snt sortate n ordine cresctoare n concordan cu funcia de comparare referit de comp, apelat cu dou argumente care indic spre obiectele ce se compar. Funcia de comparare trebuie s returneze un ntreg mai mic dect, egal cu, sau mai mare dect zero dac primul argument este considerat a fi mai mic dect, egal cu, respectiv mai mare dect al doilea. Dac cele dou elemente comparate snt egale, ordinea n tabloul sortat este nedefinit. Funcia bsearch caut ntr-un tablou de nel elemente, fiecare de mrime size, un membru care coincide cu obiectul indicat de key. Argumentul base indic spre nceputul tabloului. Coninutul tabloului trebuie s fie sortat cresctor n concordan cu funcia de comparare referit de comp, apelat cu dou argumente care indic spre obiectele ce se compar. Funcia de comparare trebuie s returneze un ntreg mai mic dect, egal cu, sau mai mare dect zero dac primul argument este considerat a fi mai mic dect, egal cu, respectiv mai mare dect al doilea. Valoare returnat Funcia bsearch returneaz un pointer la un membru al tabloului care coincide cu obiectul indicat de key, sau NULL dac nu se gsete nici un membru. Dac exist mai multe elemente care coincid cu key, poate fi returnat oricare element cu aceast proprietate.

12.3. Rutine de clasificare


Nume isalnum, isalpha, isascii, iscntrl, isdigit, isgraph, islower, isprint, ispunct, isspace, isupper, isxdigit - rutine de clasificare tolower - conversie n liter mic toupper - conversie n liter mare
__________________________________________________________________________

162

Declaraie #include <ctype.h> int isalnum(int c); int isalpha(int c); int isascii(int c); int iscntrl(int c); int isdigit(int c); int isgraph(int c); int tolower(int c);

int int int int int int

islower(int c); isprint(int c); ispunct(int c); isspace(int c); isupper(int c); isxdigit(int c);

int toupper(int c);

Descriere Primele 12 funcii verific dac c, care trebuie s fie o valoare de tip unsigned char sau EOF, se afl n una din clasele de caractere enumerate mai sus. isalnum Verific dac c este alfanumeric; (isalpha(c) || isdigit(c)). este echivalent cu

isalpha Verific dac c este alfabetic; este echivalent cu (isupper(c) || islower(c)). isascii Verific dac c este o valoare pe 7 bii din setul de caractere ASCII. iscntrl Verific dac c este un caracter de control. isdigit Verific dac c este o cifr (ntre 0 i 9). isgraph Verific dac c este un caracter afiabil cu excepia spaiului. islower Verific dac c este o liter mic.
__________________________________________________________________________

163

isprint Verific dac c este un caracter afiabil inclusiv spaiu. ispunct Verific dac c este un caracter diferit de spaiu i nonalfanumeric. isspace Verific dac c este un spaiu alb. isupper Verific dac c este o liter mare. isxdigit Verific dac c este o cifr hexazecimal din setul 0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F. tolower Convertete caracterul c, dac este o liter, la litera mic corespunztoare. toupper Convertete caracterul c, dac este o liter, la litera mare corespunztoare. Valoare returnat Valoarea returnat de funciile is... este nenul dac caracterul c se afl n clasa testat, i zero n caz contrar. Valoarea returnat de funciile to... este litera convertit dac caracterul c este o liter, i nedefinit n caz contrar.

12.4. Operaii cu blocuri de memorie


Pentru majoritatea funciilor din aceast categorie compilatorul expandeaz codul acestora folosind instruciuni pe iruri de caractere. Declaraiile acestor funcii se obin cu #include <string.h>

__________________________________________________________________________

164

Nume memcpy - copiaz o zon de memorie


Declaraie void *memcpy(void *dest, const void *src, unsigned n); void *memmove(void *dest, const void *src, unsigned n); Descriere Funcia memcpy copiaz n octei din zona de memorie src n zona de memorie dest. Zonele de memorie nu trebuie s se suprapun. Dac exist acest risc se utilizeaz memmove. Valoare returnat Funciile returneaz un pointer la dest.

Nume memcmp - compar dou zone de memorie


Declaraie int memcmp(const void *s1, const void *s2, unsigned n); Descriere Funcia memcmp compar primii n octei ai zonelor de memorie s1 i s2. Valoare returnat Returneaz un ntreg mai mic dect, egal cu, sau mai mare dect zero dac s1 este mai mic dect, coincide, respectiv este mai mare dect s2.

Nume memset - umple o zon de memorie cu o constant pe un octet


Declaraie
__________________________________________________________________________

165

void *memset(void *s, int c, unsigned n); Descriere Funcia memset umple primii n octei ai zonei de memorie indicat de s cu constanta c pe un octet. Valoare returnat Funcia returneaz un pointer la zona de memorie s.

Nume memchr - caut n memorie un caracter


Declaraie void *memchr(const void *s, int c, unsigned n); Descriere Funcia memchr caut caracterul c n primii n octei de memorie indicai de s. Cutarea se oprete la primul octet care are valoarea c (interpretat ca unsigned char). Valoare returnat Funcia returneaz un pointer la octetul gsit sau NULL dac valoarea nu exist n zona de memorie.

12.5. Operaii cu iruri de caractere


Pentru majoritatea funciilor din aceast categorie compilatorul expandeaz codul acestora folosind instruciuni pe iruri de caractere. Declaraiile acestor funcii se obin cu #include <string.h>

Nume strlen - calculeaz lungimea unui ir


Declaraie unsigned strlen(const char *s); Descriere
__________________________________________________________________________

166

Funcia strlen calculeaz lungimea irului s, fr a include caracterul terminator null. Valoare returnat Funcia returneaz numrul de caractere din s.

Nume strcpy, strncpy - copiaz un ir de caractere


Declaraie char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, unsigned n); Descriere Funcia strcpy copiaz irul indicat de src (inclusiv caracterul terminator null) n zona indicat de dest. irurile nu trebuie s se suprapun, i n plus zona dest trebuie s fie suficient de mare pentru a primi copia. Funcia strncpy este similar, cu excepia faptului c nu se copiaz mai mult de n octei din src. Astfel, dac caracterul terminator null nu se afl n primii n octei din src, rezultatul nu va fi terminat cu null. n cazul n care lungimea lui src este mai mic dect n, restul octeilor din dest primesc valoarea null. Valoare returnat Funciile returneaz un pointer la irul dest.

Nume strdup - duplic un ir


Declaraie char *strdup(const char *s); Descriere Funcia strdup returneaz un pointer la un nou ir care este un duplicat al irului s. Memoria pentru noul ir se obine cu malloc, i poate fi eliberat cu free.
__________________________________________________________________________

167

Valoare returnat Funcia returneaz un pointer la irul duplicat, sau NULL dac nu exist memorie suficient disponibil.

Nume strcat, strncat - concateneaz dou iruri


Declaraie char *strcat(char *dest, const char *src); char *strncat(char *dest, const char *src, unsigned n); Descriere Funcia strcat adaug irul src la irul dest suprascriind caracterul null de la sfritul lui dest, i la sfrit adaug un caracter terminator null. irurile nu trebuie s se suprapun, i n plus irul dest trebuie s aib suficient spaiu pentru a pstra rezultatul. Funcia strncat este similar, cu excepia faptului c numai primele n caractere din src se adaug la dest. Valoare returnat Funciile returneaz un pointer la irul rezultat dest.

Nume strcmp - compar dou iruri de caractere


Declaraie int strcmp(const char *s1, const char *s2); Descriere Funcia strcmp compar cele dou iruri s1 i s2. Valoare returnat Funcia returneaz un ntreg mai mic dect, egal cu, sau mai mare dect zero dac s1 este mai mic dect, coincide, respectiv este mai mare dect s2.
__________________________________________________________________________

168

Nume strchr, strrchr - localizeaz un caracter


Declaraie char *strchr(const char *s, int c); char *strrchr(const char *s, int c); Descriere Funcia strchr returneaz un pointer la prima apariie a caracterului c n irul s. Funcia strrchr returneaz un pointer la ultima apariie a caracterului c n irul s. Valoare returnat Funciile returneaz un pointer la caracterul gsit sau NULL dac valoarea nu a fost gsit.

Nume strstr - localizeaz un subir


Declaraie char *strstr(const char *sir, const char *subs); Descriere Funcia strstr gsete prima apariie a subirului subs n irul sir. Caracterul terminator null nu este luat n considerare. Valoare returnat Funcia returneaz un pointer la nceputul subirului, sau NULL dac subirul nu este gsit.

Nume strspn, strcspn - caut un set de caractere ntr-un ir


Declaraie unsigned strspn(const char *s, const char
__________________________________________________________________________

169

*acc); unsigned strcspn(const char *s, const char *rej); Descriere Funcia strspn calculeaz lungimea segmentului iniial din s format n ntregime numai cu caractere din acc. Funcia strcspn calculeaz lungimea segmentului iniial din s format n ntregime numai cu caractere care nu se gsesc n rej. Valori returnate Funcia strspn returneaz poziia primului caracter din s care nu se afl n acc. Funcia strcspn returneaz poziia primului caracter din s care se afl n rej.

12.6. Biblioteca matematic


1) Funciile din prima categorie snt descrise n <stdlib.h>.

Nume rand, srand - generarea numerelor pseudo-aleatoare


Declaraie int rand(void); void srand(unsigned int seed); Descriere Funcia rand returneaz un ntreg pseudo-aleator ntre 0 i RAND_MAX (pentru majoritatea mediilor de programare C aceast constant este egal cu valoarea maxim cu semn reprezentabil pe un cuvnt al sistemului de calcul). Funcia srand iniializeaz generatorul cu valoarea seed pentru o nou secven de valori ntregi pseudo-aleatoare care vor fi returnate de rand. Aceste secvene se repet dac srand se apeleaz cu aceeai valoare seed. Se obinuiete ca generatorul s fie iniializat cu o valoare dat de ceasul sistemului de calcul, ca n exemplul de mai jos:
__________________________________________________________________________

170

#include <time.h> srand(time(NULL)); Valoare returnat Funcia rand returneaz o valoare ntre 0 i RAND_MAX. Observaie n lucrarea Numerical Recipes in C: The Art of Scientific Computing - William H Press, Brian P Flannery, Saul A Teukolsky, William T Vetterling / New York: Cambridge University Press, 1990 (1st ed, p 207), se face urmtorul comentariu: "Dac dorii s generai o valoare aleatoare ntreag ntre 1 i 10, se recomand s folosii secvena j=1+(int)(10.0*rand()/(RAND_MAX+1.0)); i nu o secven de tipul j=1+(int)(1000000.0*rand())%10; care folosete biii de rang inferior." Tot n fiierul <stdlib.h> snt descrise i urmtoarele funcii: int abs(int i); valoare absolut long labs(long i); valoare absolut int atoi(char *s); conversie din ASCII n ntreg long atol(char *s); conversie din ASCII n ntreg lung double atof(char *s); conversie din ASCII n dubl precizie 2) Funciile din a doua categorie snt descrise n fiierul <math.h>. double double double double double double double double fabs(double x); valoare absolut floor(double x); parte ntreag inferioar ceil(double x); parte ntreag superioar sqrt(double x); x sin(double x); sin(x) cos(double x); cos(x) tan(double x); tg(x) asin(double x); arcsin(x)
171

__________________________________________________________________________

double acos(double x); arccos(x) double atan(double x); arctg(x) n [-/2,/2] double atan2(double y, double x); arctg(y/x) n [,] double exp(double x); ex double log(double x); ln(x) double pow(double x, double y); xy double sinh(double x); sinh(x) double cosh(double x); cosh(x) double tanh(double x); tgh(x) double ldexp(double x, int e); x 2e double fmod(double x, double y); x modulo y Funcia fmod returneaz o valoare f definit astfel: x = a y + f a este o valoare ntreag (dat de x/y) i 0 |f | < y; f are semnul lui x. Urmtoarele dou funcii returneaz dou valori: una este valoarea returnat de funcie (de tip double), i cealalt returnat prin intermediul unui argument de tip pointer la int respectiv double. double frexp(double x, int *e); Funcia frexp desparte valoarea x n dou pri: o parte fracionar normalizat (f [0.5,1)) i un exponent e. Dac x este 0 atunci f= 0 i e= 0. Valoarea returnat este f. double modf(double x, double *n); Funcia modf desparte valoarea x n dou pri: o parte fracionar subunitar f i o parte ntreag n. Valorile f i n au acelai semn ca i x. Valoarea returnat este f.

__________________________________________________________________________

172

12.7. Programe demonstrative


1) Programul prezentat n continuare genereaz un ir de n valori ntregi aleatoare n intervalul [0,M pe care le depune n tabloul X 1] (alocat dinamic), i apoi le sorteaz cresctor. n continuare se genereaz k valori ntregi aleatoare pe care le caut n tabloul X. Pentru fiecare cutare cu succes se afieaz pe terminal valoarea cutat i poziia n tablou. Valorile n, k i M se iau n aceast ordine din linia de comand. #include <stdlib.h> #include <stdio.h> #include <time.h> int cmp(const void *A, const void *B) { return *(int *)A-*(int *)B; } int main(int ac, int **av) { int *X,*p,M,n,k,i,v; if (ac!=4) { fputs("Trei argumente!\n",stderr); return 1; } n=atoi(av[1]); k=atoi(av[2]); M=atoi(av[3]); X=(int *)malloc(n*sizeof(int)); if (!X) return 1; srand(time(NULL)); for (i=0; i<n; i++) X[i]=rand()%M; qsort(X,n,sizeof(int),cmp); for (i=0; i<k; i++) { v=rand()%M; p=(int *)bsearch(&v,X,n,sizeof(int), cmp); if (p) printf("Val: %d Pos: %d\n",v,p-X); }
__________________________________________________________________________

173

free(X); return 0; } 2) S relum al treilea exemplu din capitolul precedent. Se citete un fiier text care conine pe fiecare linie un nume (ir de caractere fr spaiu) i trei valori reale (note). Pentru fiecare linie se calculeaz media aritmetic a celor trei valori i se determin dac elementul este admis (fiecare not este minimum 5) sau respins (cel puin o not este sub 5). n final se afieaz liniile n ordinea urmtoare: mai nti elementele admise n ordinea descresctoare a mediei, i apoi elementele respinse n ordine alfabetic dup nume. Se afieaz doar numele, situaia (A/R) i media. n acest exemplu punem n eviden o modalitate comod de selectare a membrilor unei structuri cu ajutorul macrourilor. Macroul Fld selecteaz din zona referit de pointerul P membrul f. Deoarece pointerul P (care poate fi argumentul A sau B al funciei comp) refer o zon de tip void, este necesar mai nti un cast pentru a preciza tipul concret al acesteia. Membrul f poate fi: nm, ar, md, definii n cadrul structurii de tip StEl. Deoarece nu tim de la nceput cte linii are fiierul de intrare, sntem nevoii s folosim urmtoarea strategie. La nceput alocm pentru tabloul El o zon care s memoreze NA elemente. Pe msur ce aceast zon se completeaz, la un moment dat numrul de linii citite coincide cu NA. n acest moment se aloc o zon nou care s poat memora un numr mai mare de elemente. Desigur, de fiecare dat se va actualiza mrimea spaiului alocat. #include <stdlib.h> #include <string.h> #include <stdio.h> #define NA 32 typedef struct { char nm[10], ar; float na, nb, nc, md; } StEl;
__________________________________________________________________________

174

#define Fld(P,f) ((StEl *)P)->f int comp(const void *A, const void *B) { float w; int d; if (d=Fld(A,ar)-Fld(B,ar)) return d; if (Fld(A,ar)=='A') { w=Fld(B,md)-Fld(A,md); if (w>0) return 1; if (w<0) return -1; } return strcmp(Fld(A,nm),Fld(B,nm)); } int main(int ac, char **av) { int na,ne,i; StEl *El; FILE *fi; if (ac!=2) { fputs("Un argument!\n",stderr); return 1; } fi=fopen(av[1],"rt"); if (!fi) { perror("Eroare la deschidere"); return 1; } na=NA; ne=0; El=(StEl *)malloc(na*sizeof(StEl)); while (fscanf(fi,"%s %d %d %d", El[ne].nm,&El[ne].na,&El[ne].nb, &El[ne].nc)!=EOF) { if ((El[ne].na>=5) && (El[ne].nb>=5) && (El[ne].nc>=5)) El[ne].ar='A'; else El[ne].ar='R'; El[ne].md=(El[ne].na+El[ne].nb+ El[ne].nc)/3.0;
__________________________________________________________________________

175

ne++; if (ne==na) { na+=NA; El=(StEl *)realloc(El,na* sizeof(StEl)); } } fclose(fi); qsort(El,ne,sizeof(StEl),comp); for (i=0; i<ne; i++) printf("%-12s %c%6.2lf\n", El[i].nm,El[i].ar,El[i].md); free(El); return 0; } 3) Se citete dintr-un fiier text o valoare natural n. Urmtoarele linii conin n cuvinte, fiecare cuvnt avnd acelai numr de litere (cel mult 10). S se afieze cuvintele din fiier ordonate alfabetic. Pentru a memora lista de cuvinte folosim urmtoarea strategie. n loc s alocm pentru fiecare cuvnt (ir de caractere) citit o zon nou de memorie, alocm de la nceput o zon n care s putem memora toate cuvintele din list. Aceast zon va avea mrimea de (l+1)*n octei, unde l este lungimea fiecrui cuvnt (numrul de litere). De ce (l+1)? pentru c trebuie s memorm i caracterul terminator null. Avantaje: memoria este utilizat mai eficient dac se aloc de la nceput o zon contigu de dimensiune mai mare, i se reduce foarte mult fragmentarea memoriei. #include <stdlib.h> #include <string.h> #include <stdio.h> int comp(const void *A, const void *B) { return strcmp((char *)A,(char *)B); } int main(int ac, char **av) { char *C,s[11];
__________________________________________________________________________

176

int n,l,i; FILE *fi; if (ac!=2) { fputs("Un argument!\n",stderr); return 1; } fi=fopen(av[1],"rt"); if (!fi) { perror("Eroare la deschidere"); return 1; } fscanf(fi,"%d %s",n,s); l=strlen(s); C=(char *)malloc((l+1)*n); Strcpy(C,s); for (i=1; i<n; i++) fscanf(fi,"%s",C+(l+1)*i); fclose(fi); qsort(C,n,l+1,comp); for (i=0; i<n; i++) printf("%s\n",C+(l+1)*i); free(C); return 0; }

__________________________________________________________________________

177

Bibliografie
Brian W Kernigham, Dennis M Ritchie - The C Programming Language Prentice-Hall Software Series, 1978 - Limbajul C; manual de programare Institutul de tehnic de calcul, Cluj-Napoca 1984 Herbert Schildt - Manual C complet Editura Teora, 1998

Manuale electronice
http://www.programmingtutorials.com/c.html Marshall Brain - Introduction to C Programming http://devcentral.iftech.com/learning/tutorials/c-cpp/c/ Steve Summit - Introductory C Programming Class Notes http://www.eskimo.com/~scs/cclass/cclass.html Steve Summit - Intermediate C Programming Class Notes http://www.eskimo.com/~scs/cclass/cclass.html Brian Brown - C Programming http://www.cit.ac.nz/smac/cprogram/onlinet.htm Brian Brown - An Introduction to C Programming http://www.cit.ac.nz/smac/cprogram/default.htm

__________________________________________________________________________

178

Cuprins
1. Generaliti asupra limbajului C . . . . . . . . . . . . . . . . . . . 4
1.1. Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2. Primele programe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.3. Meta-limbajul i setul de caractere . . . . . . . . . . . . . . . . . . . . 9

2. Unitile lexicale ale limbajului C . . . . . . . . . . . . . . . . 10


2.1. Identificatori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.2. Cuvinte cheie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.3. Constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.4. iruri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.5. Operatori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.6. Separatori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

3. Variabile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.1. Clase de memorie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2. Tipuri de variabile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.3. Obiecte i valori-stnga . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.4. Conversii de tip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4. Operatori i expresii . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.1. Expresii primare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.2. Operatori unari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.3. Operatori multiplicativi . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.4. Operatori aditivi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.5. Operatori de deplasare . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.6. Operatori relaionali . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 4.7. Operatori de egalitate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.8. Operatorul I pe bii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.9. Operatorul SAU-exclusiv pe bii . . . . . . . . . . . . . . . . . . . . 35 4.10. Operatorul SAU-inclusiv pe bii . . . . . . . . . . . . . . . . . . . . 36 4.11. Operatorul I-logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 4.12. Operatorul SAU-logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 4.13. Operatorul condiional . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 4.14. Operatori de atribuire . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
__________________________________________________________________________

179

4.15. Operatorul virgul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 4.16. Precedena i ordinea de evaluare . . . . . . . . . . . . . . . . . . . 40

5. Declaraii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5.1. Specificatori de clas de memorie . . . . . . . . . . . . . . . . . . . 43 5.2. Specificatori de tip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 5.3. Declaratori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 5.4. Modificatorul const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 5.5. Iniializare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 5.6. Nume-tip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

6. Instruciuni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
6.1. Instruciunea expresie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 6.2. Instruciunea compus sau blocul . . . . . . . . . . . . . . . . . . . . 51 6.3. Instruciunea condiional . . . . . . . . . . . . . . . . . . . . . . . . . . 52 6.4. Instruciunea while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 6.5. Instruciunea do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 6.6. Instruciunea for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 6.7. Instruciunea switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 6.8. Instruciunea break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 6.9. Instruciunea continue . . . . . . . . . . . . . . . . . . . . . . . . . . 58 6.10. Instruciunea return . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 6.11. Instruciunea vid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

7. Funciile i structura unui program . . . . . . . . . . . . . . . . 60


7.1. Definiia funciilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 7.2. Apelul funciilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 7.3. Revenirea din funcii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .63 7.4. Argumentele funciei i transmiterea parametrilor . . . . . . . 64 7.5. Funcii cu numr variabil de parametri . . . . . . . . . . . . . . . . 65 7.6. Exemple de funcii i programe . . . . . . . . . . . . . . . . . . . . . 66

8. Liniile de control ale compilatorului . . . . . . . . . . . . . . .70


8.l. nlocuirea simbolurilor, substituii macro . . . . . . . . . . . . . . 70 8.2. Includerea fiierelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 8.3. Compilarea condiionat . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 8.4. Utilizarea directivelor de compilare . . . . . . . . . . . . . . . . . . 73
__________________________________________________________________________

180

9. Pointeri i masive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
9.1. Pointeri i adrese . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 9.2 Pointeri i argumente de funcii . . . . . . . . . . . . . . . . . . . . . . 77 9.3. Pointeri i masive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 9.4. Aritmetica de adrese . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 9.5. Pointeri la caracter i funcii . . . . . . . . . . . . . . . . . . . . . . . . 83 9.6. Masive multidimensionale . . . . . . . . . . . . . . . . . . . . . . . . . 86 9.7. Masive de pointeri i pointeri la pointeri . . . . . . . . . . . . . . 88 9.8. Iniializarea masivelor i masivelor de pointeri . . . . . . . . . 93 9.9. Masive de pointeri i masive multidimensionale . . . . . . . . 96 9.10. Argumentele unei linii de comand . . . . . . . . . . . . . . . . . 97 9.11. Pointeri la funcii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

10. Structuri i reuniuni . . . . . . . . . . . . . . . . . . . . . . . . . 106


10.1. Elemente de baz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 10.2. Structuri i funcii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 10.3. Masive de structuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 10.4. Pointeri la structuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 10.5. Structuri auto-referite . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 10.6. Cutare n tabele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 10.7. Cmpuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 10.8. Reuniuni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 10.9. Declaraii de structuri, reuniuni i cmpuri . . . . . . . . . . 131 10.10. Typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

11. Intrri / ieiri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136


11.1. Intrri i ieiri standard; fiiere . . . . . . . . . . . . . . . . . . . . 136 11.2. Accesul la fiiere; deschidere i nchidere . . . . . . . . . . . 138 11.3. Citire i scriere fr format . . . . . . . . . . . . . . . . . . . . . . . 141 11.4. Citire cu format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 11.5. Scriere cu format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 11.6. Tratarea erorilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 11.7. Operaii cu directoare . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 11.8. Programe demonstrative . . . . . . . . . . . . . . . . . . . . . . . . 156

12. Alte rutine din biblioteca standard . . . . . . . . . . . . . . 160


12.1. Alocarea dinamic a memoriei . . . . . . . . . . . . . . . . . . . . 160
__________________________________________________________________________

181

12.2. Sortare i cutare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 12.3. Rutine de clasificare . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 12.4. Operaii cu blocuri de memorie . . . . . . . . . . . . . . . . . . . 164 12.5. Operaii cu iruri de caractere . . . . . . . . . . . . . . . . . . . . 166 12.6. Biblioteca matematic . . . . . . . . . . . . . . . . . . . . . . . . . . 170 12.7. Programe demonstrative . . . . . . . . . . . . . . . . . . . . . . . . . 173 Bibliografie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178

__________________________________________________________________________

182

You might also like