Professional Documents
Culture Documents
Theory without practice is useless; practice without theory is blind Roger Bacon Acest manual este structurat astfel nct elementele limbajului C s fie prezentate ntro manier unitar. Primul capitol face o scurt introducere i prezint ase programe C. Urmtoarele cinci capitole descriu elementele primare ale limbajului C: tipurile fundamentale de date, definiii de constante i variabile, instruciuni, funcii. Capitolul apte descrie strategii de organizare a activitii de programare pentru elaborarea programelor mari, compuse din mai multe module. Tot n acest capitol snt prezentate i directivele de compilare cele mai folosite. Capitolele opt i nou descriu elemente avansate ale limbajului C: pointeri i structuri. Capitolele zece i unsprezece trec n revist funciile cele mai des utilizate definite n biblioteca standard, mpreun cu cteva programe demonstrative. Am selectat 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 prezentare exhaustiv a limbajului C; am selectat strictul necesar care s permit unui programator s elaboreze programe eficiente i uor de ntreinut. Deoarece avem convingerea c cea mai bun explicaie este un program funcional, exemplele din acest manual 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. Dac constructorii ar fi construit cldiri aa cum scriu programatorii programe, atunci prima ciocnitoare care ar fi venit ar fi distrus civilizaia (din Legile lui Murphy)
____________________________________________
C este un limbaj restrns i se nva relativ uor, iar subtilitile se rein pe msur ce crete experiena n programare.
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> int 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 definete 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. Orice program C trebuie s aib o funcie main. 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> int 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 definesc 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 (do) 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 afieaz media aritmetic a acestor valori, calculat n cadrul unei funcii care se definete n acest scop.
#include <stdio.h> double media(double v[], int n) { double s; int i; for (s=i=0; i<n; i++) s += v[i]; return s/n; } int main() { int n,i; double a[100]; scanf("%d",&n); for (i=0; i<n; i++) scanf("%lf",&a[i]); printf("%.3lf\n", media(a,n)); return 0; }
n cadrul funciei main se definesc dou variabile n i i care vor memora valori ntregi. Variabila n pstreaz numrul actual de valori din masivul a. Se definesc de asemenea un masiv unidimensional a care va putea memora 100 de valori de tip real (dubl precizie). Se citete de la terminal o valoare n. n continuare se introduc valorile reale ai (i= 0,1,...,n1); se observ modul de referire la fiecare element din
____________________________________________
masiv: a[i]. Formatul de introducere "%lf" indic faptul c se ateapt introducerea unei valori n dubl precizie 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 ++. Media aritmetic a acestor valori, calculat cu ajutorul funciei media, este afiat cu o precizie de trei cifre dup punctul zecimal, conform formatului de afiare ".3lf". S analizm acum definiia funciei media. Aceasta calculeaz o valoare de tip double, opernd asupra unui masiv v care are elemente de tip double. Cte elemente are masivul, aflm din al doilea parametru al funciei: n. Declaraia double v[] nu definete un masiv: n momentul apelului din main, argumentul v va indica spre masivul a cu care este pus n coresponden. Declaraia int n definete o variabil local n, care n momentul apelului din main va primi valoarea argumentului n. Trebuie s subliniem faptul c cele dou variabile, dei au acelai nume, snt distincte: fiecare este local funciei n care este definit. Aceeai observaie este valabil i pentru variabilele i. Ciclul for iniializeaz variabilele s i i cu zero. Variabila s (de asemenea local) va memora suma valorilor din masivul v. Cu ajutorul variabilei i se parcurge masivul v, i valoarea fiecrui element este cumulat n s. n final instruciunea return comunic funciei principale valoarea s/n. Acelai program poate fi scris i altfel, definind o parte din variabile n afara celor dou funcii.
#include <stdio.h> int n; double a[100]; double media() { double s; int i; for (s=i=0; i<n; i++) s += a[i]; return s/n; } int main() { int i;
____________________________________________
n acest caz ambele funcii se refer la acelai masiv a i la aceeai variabil n; spunem c variabilele a i n snt globale. Funcia media definete dou variabile locale: s i i, funcia main definete o variabil local: i. Exist o diferen important ntre cele dou funcii media din cele dou programe. Prima definiie este mai general, deoarece poate opera pe orice masiv de elemente de tip double, avnd oricte elemente. Dac, de exemplu, n funcia principal am avea nc un masiv b cu m elemente, apelul ar fi: media(b,m). A doua definiie nu permite acest lucru. Funcia principal ar trebui s copieze mai nti toate elementele masivului b n masivul a, i valoarea m n variabila n. Abia dup aceea ar putea fi apelat funcia media. Dar astfel am distruge vechile valori din a i n. Al doilea stil de programare trebuie evitat, deoarece utilizarea funciilor este foarte greoaie pentru argumente distincte la apeluri distincte ale acestora. Exist o singur justificare pentru acest stil: cnd tim cu certitudine c o funcie pe care o definim va opera ntotdeauna cu argumente memorate tot timpul n aceleai variabile. Al cincilea 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 = 60.1; k = a * 100; 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. n continuare variabila a primete valoarea 60.1, care nu poate fi reprezentat exact n virgul mobil. De aceea variabila k primete valoarea 6009 datorit erorilor de aproximare. Al treilea 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 24691357 (rezultatul corect), va avea valoarea 24691356, 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 patrulea 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. Ultimul program citete cte o linie de la intrarea standard i o afieaz la ieirea standard.
#include <stdio.h> char lin[80]; int main() { while (gets(lin)) puts(lin); return 0; }
Linia citit cu ajutorul funciei de bibliotec gets este memorat n masivul lin, ultimul caracter valid al liniei fiind urmat de o valoare zero, care marcheaz astfel sfritul irului. Citirea se ncheie atunci cnd intrarea standard nu mai furnizeaz nimic. Funcia gets returneaz o valoare care are semnificaia True dac intrarea standard a furnizat o linie, sau False dac nu s-a mai furnizat nimic. Dac linia se termin cu new-line, acest caracter este preluat de la intrarea standard, dar nu este memorat n irul lin. Am presupus c fiecare linie introdus are mai puin de 80 de caractere, altfel programul nu va funciona corect. De altfel funcia gets este folosit n aceast lucrare doar n scop didactic, pentru simplificarea expunerilor.
____________________________________________
Fiecare linie citit de la intrarea standard este afiat cu ajutorul funciei puts. Aceasta afieaz automat la sfrit caracterul new-line. Dac programul este rulat n mod obinuit nu se ntmpl nimic spectaculos: fiecare linie introdus de la terminal va fi reafiat. Programul poate fi totui folosit pentru a afia coninutul unui fiier text, pentru a crea un fiier text, sau pentru a copia un fiier text. n acest scop programul este rulat n mod linie de comand astfel: Prog < Fiier-intrare >Fiier-ieire Prog este numele programului executabil, obinut n urma compilrii programului surs. Fiier-intrare este numele fiierului al crui coninut dorim s-l afim sau s-l copiem. Fiier-ieire este numele fiierului pe care dorim s-l crem. Oricare din cele dou argumente poate lipsi. Dac primul argument este prezent, programul va citi datele din fiierul de intrare specificat, altfel va atepta introducerea datelor de la terminal. n al doilea caz trebuie marcat ntr-un mod special terminarea introducerii, i acest mod depinde de sistemul de operare sub care se lucreaz. Dac al doilea argument este prezent, programul va scrie datele n fiierul de ieire specificat, care va fi creat n acest scop. Dac ieirea nu este specificat, datele vor fi afiate la terminal. Observaie general. n cazul fiecrui program, fiecare variabil sau funcie este definit nainte de utilizare, pentru ca n momentul utilizrii s existe toate informaiile despre contextul n care poate fi utilizat identificatorul respectiv.
____________________________________________
2.2. Constante
n limbajul C exist urmtoarele tipuri de constante: ntreg (zecimal, octal, hexazecimal), ntreg lung explicit, flotant, caracter, simbolic.
____________________________________________
10
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 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.456e7 sau 0.12e3 Orice constant flotant se consider a fi n precizie extins.
____________________________________________
11
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 unor secvene de evitare. Acestea ofer de altfel i un mecanism general pentru reprezentarea caracterelor mai dificil de introdus n calculator i a oricror configuraii de bii. Aceste secvene de evitare snt: \n new-line \r carriage return \\ backslash \t tab orizontal \f form feed \' apostrof \b backspace \a semnal sonor \" ghilimele \ooo configuraie de bii precizat n baza 8 \xhh configuraie de bii precizat n baza 16 Fiecare din aceste secvene, dei e format din mai multe caractere, reprezint n realitate un singur caracter. Exemplu: secvena '\040' va genera caracterul spaiu.
Constante simbolice
O constant simbolic este un identificator cu valoare de constant. Valoarea constantei poate fi orice ir de caractere introdus prin construcia #define (capitolul apte). 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).
____________________________________________
12
2.3. 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 (capitolul trei). Un ir este iniializat cu caracterele date. 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. Secvena urmtoare determin lungimea irului de caractere s, excluznd caracterul terminal null.
for (n=0; s[i]; n++) ;
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.4. 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. Capitolul patru este rezervat n exclusivitate descrierii operatorilor definii n limbajul C.
____________________________________________
13
2.5. 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 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 acestuia. Nu se admit comentarii imbricate.
____________________________________________
14
3. Variabile
Ca i constantele, variabilele snt elemente de baz cu care opereaz un program scris n C. O variabil este un obiect de programare cruia i se atribuie un nume i i se asociaz o zon de memorie. Variabilele se deosebesc dup nume i pot primi diferite valori. Numele variabilelor snt identificatori. Numele de variabile se scriu de obicei cu litere mici (fr a fi obligatoriu). n limbajul C, variabilele snt caracterizate prin dou atribute: clas de memorie i tip. Acestea i snt atribuite unei variabile prin intermediul unei declaraii. Declaraiile enumer variabilele care urmeaz a fi folosite, stabilesc clasa de memorie, tipul variabilelor i eventual valorile iniiale. Sintaxa unei declaraii este: clas tip list-variabile; Lista de variabile poate avea un element sau mai multe, n al doilea caz ele fiind separate prin virgul. Clasa de memorie precizeaz care funcii pot vedea variabilele declarate n cadrul unui modul: doar funcia / blocul unde acestea snt definite: variabile automatice i variabile statice interne; toate funciile din cadrul modulului care urmeaz declaraiei: variabile globale statice; toate funciile din toate modulele care cunosc declaraia: variabile globale externe. Clasa de memorie precizeaz n plus i durata de via a variabilelor declarate. Variabilele automatice exist doar pe durata execuiei funciei sau blocului unde au fost definite. Variabilele statice i externe exist pe toat durata execuiei programului. Tipul unei variabile precizeaz domeniul de valori pe care le poate lua i operaiile care se pot efectua cu aceste valori. Declaraiile se folosesc pentru a specifica interpretarea pe care compilatorul trebuie s o dea fiecrui identificator, i pot aprea 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 creeaz variabila i i se aloc memorie; declaraia de utilizare a unei variabile, care doar anun proprietile variabilei care urmeaz a fi folosit.
____________________________________________
15
Variabile automatice
Variabilele automatice snt variabile locale fiecrui bloc sau funcii. 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 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. Exemple. n fiecare program din primul capitol, variabilele locale snt automatice.
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 dou sau trei); declaraia register este ignorat pentru celelalte variabile; numai tipurile de date ntregi i pointer snt admise; nu este posibil referirea la adresa unei variabile register.
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; spre deosebire de variabilele auto, ele i pstreaz valorile tot
____________________________________________
16
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 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. n seciunea rezervat problemei iniializrilor de la sfritul acestui capitol vom da un exemplu care ilustreaz comportamentul variabilelor automatice i statice.
Variabile externe
Variabilele externe snt variabile cu caracter global. Ele se definesc n afara oricrei funcii i pot fi apelate prin nume din oricare modul (fiier surs) 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. Detalii despre utilizarea variabilelor externe vor fi date n capitolul apte.
17
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. Domeniul valorilor 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). Domeniul valorilor variabilelor caracter fr semn (unsigned char) este ntre 0 i 255. Descriptori de format: %c pentru o variabil sau o valoare de tip char; %s pentru o variabil sau o expresie de tip ir de caractere.
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 se reprezint un ntreg, de obicei 2. Calificatorul long se refer la numrul maxim de octei pe care poate fi reprezentat un ntreg, de obicei 4. Tipul int are dimensiunea natural sugerat de sistemul de calcul. Domeniul numerelor ntregi reprezentabile n main depinde 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. Un ntreg fr semn poate lua valori ntre 0 i 65535 (sisteme de calcul pe 16 bii) sau ntre 0 i 4294967295 (sisteme de calcul pe 32 de bii).
____________________________________________
18
Valorile de tip unsigned respect legile aritmeticii modulo 2w, unde w 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; unsigned long int u; Cuvntul int poate fi omis n aceste situaii. Descriptori de format: %d pentru o variabil sau o valoare de tip int; %u pentru o variabil sau o valoare de tip unsigned; %ld pentru o variabil sau o valoare de tip long; %lu pentru o variabil sau o valoare de tip unsigned long; %hd pentru o variabil sau o valoare de tip short; %hu pentru o variabil sau o valoare de tip unsigned short.
Tipuri derivate
n afar de tipurile aritmetice fundamentale, exist, n principiu, o clas infinit de tipuri derivate, construite din tipurile fundamentale n urmtoarele moduri: 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.
____________________________________________
19
n general aceste metode de construire de noi tipuri de obiecte pot fi aplicate recursiv. Tipurile derivate vor fi detaliate n capitolele opt i nou.
n partea dreapt a operatorului de atribuire, a reprezint valoarea variabilei respective. Operaia s = V; semnific: valoarea V se atribuie variabilei s.
*p = *q;
n partea dreapt a operatorului de atribuire, *q reprezint valoarea aflat la adresa indicat de q. Operaia *p = V; semnific: valoarea V se memoreaz la adresa indicat de p.
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 capitolul ase care convertete un ir de cifre n echivalentul lor numeric.
____________________________________________
20
s[i] - '0'
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 virgul mobil la ntreg se face prin trunchierea prii fracionare. Conversia de la ntreg la virgul mobil este acceptat.
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, 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 < tip virgul mobil.
____________________________________________
21
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. 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. Variabila n nu-i modific valoarea.
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 NMAX 1000 char lin[NMAX+1];
3.5. Masive
n limbajul C se pot defini masive unidimensionale, bidimensionale, tridimensionale etc. Un masiv se compune din mai multe elemente de acelai tip; un element se identific prin indice (poziia relativ n masiv), sau prin indici (dac masivul este multidimensional). Exemple:
char s[100]; int a[10][15];
____________________________________________
22
Prima linie declar un ir de caractere: s[0], s[1], ..., s[99]. A doua linie declar o matrice de ntregi: a[0][0], ..., a[0][14], a[1][0], ..., a[1] [14], ..., a[9][0], ..., a[9][14]. Acest subiect va fi detaliat n capitolul opt.
3.6. Iniializri
ntr-o declaraie se poate specifica o valoare iniial pentru identificatorul care se declar. Iniializatorul este precedat de semnul = i const dintr-o expresie (variabile simple) sau o list de valori incluse n acolade (masive sau structuri). Toate expresiile dintr-un iniializator pentru variabile statice sau externe trebuie s fie expresii constante sau expresii care se reduc la adresa unei variabile declarate anterior. 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. Problema iniializrii masivelor i masivelor de pointeri este detaliat n capitolul opt. Problema iniializrii structurilor este detaliat n capitolul nou.
#include <stdio.h> int e = 1; int f() { int a = 2; a++; e++; return a + e; } int g() { static int s = 2; s++; e++; return s + e; } int main() { int v1,v2,v3,v4; v1 = f(); v2 = g(); v3 = f(); v4 = g(); printf("%d %d %d %d %d\n",v1,v2,v3,v4,e); return 0;
____________________________________________
23
S executm pas cu pas acest program. v1 = f(): a = 3; e = 2; v1 = 5; v2 = g(): s = 3; e = 3; v2 = 6; v3 = f(): a = 3; e = 4; v3 = 7; variabila a este reiniializat la intrarea n funcia f; v4 = g(): s = 4; e = 5; v1 = 9; variabila s i pstreaz valoarea la intrarea n funcia g; Se vor afia valorile: 5 6 7 9 5
____________________________________________
24
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. Vom preciza 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.
identificator
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 T, atunci valoarea expresieiidentificator este un pointer la primul obiect al masivului, iar tipul expresiei este pointer la T. Mai mult, un identificator de masiv nu este o expresie valoare-stnga (detalii n capitolul opt). La fel, un identificator declarat de tip funcie care returneaz T, care nu apare pe poziie de apel de funcie este convertit la pointer la funcie care returneaz T (detalii n capitolul opt).
ir
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.
(expresie)
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).
expresie-primar[expresie-indice]
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 T, expresia-indice are tipul int, iar rezultatul are tipul T. O
____________________________________________
25
expresie de forma E1[E2] este identic (prin definiie) cu *((E1)+(E2)), unde * este operatorul de indirectare (detalii n capitolul opt).
expresie-primar(list-expresii)
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. Expresia-primar trebuie s fie de tipul funcie care returneaz T, iar rezultatul apelului de funcie va fi de tipul T (detalii n capitolul ase). 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 ca tipul unui argument actual s coincid cu cel al argumentului formal, se va folosi un cast. Snt permise apeluri recursive la orice funcie. Despre definirea i apelul funciilor, detalii n capitolul ase.
valoare-stnga . identificator
O valoare-stnga urmat de un punct i un identificator este o expresieprimar. Valoarea-stnga denumete o structur sau o reuniune (capitolul nou) iar identificatorul denumete un membru din structur sau reuniune. Rezultatul este o valoare-stnga care se refer la membrul denumit din structur sau reuniune.
List-expresii
O list de expresii este considerat de asemenea expresie, dac acestea snt separate prin virgul.
____________________________________________
26
* expresie
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 T atunci tipul rezultatului este T. Acest operator trateaz operandul su ca o adres, face acces la ea i i obine coninutul (detalii n capitolul opt).
& valoare-stnga
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 valoriistnga este T atunci tipul rezultatului este pointer la T (detalii n capitolul opt). 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 2w, unde w este numrul de bii rezervai tipului int.
expresie
! expresie
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. Acest operator este aplicabil la orice expresie de tip aritmetic sau la pointeri.
~ expresie
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.
++ valoare-stnga valoare-stnga ++
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
____________________________________________
27
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 valoriistnga.
-- valoare-stnga valoare-stnga -Operatorul unar -- este operatorul de decrementare. Acest operator este analog cu operatorul ++ doar c produce decrementarea cu 1 a operandului.
(nume-tip) expresie
Operatorul (nume-tip) este operatorul de conversie de tip. Prin nume-tip nelegem unul dintre tipurile fundamentale admise n C inclusiv tipul pointer. Operandul acestui operator este o expresie. Operatorul produce conversia valorii expresiei la tipul denumit. Aceast construcie se numete cast.
sizeof(operand)
Operatorul sizeof furnizeaz dimensiunea n octei a operandului su. Aplicat unui masiv sau structuri, rezultatul este 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 este n comunicarea cu rutinele de alocare a memoriei sau cele de intrare / ieire. Operatorul sizeof poate fi aplicat i unui nume-tip. n acest caz el furnizeaz dimensiunea n octei a unui obiect de tipul indicat.
28
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. Operatorul binar % furnizeaz restul mpririi primei expresii la cea de a doua. Operanzii trebuie s fie de tip ntreg. 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.
29
Operatorii == (egal cu) i != (diferit de) snt analogi cu operatorii relaionali, dar precedena lor este mai mic. Astfel c a<b == c<d este 1 dac a<b i c<d au aceeai valoare de adevr.
Operatorul & este deseori folosit pentru a masca o anumit mulime de bii: de exemplu:
____________________________________________
30
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.
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.
31
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.
32
33
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.
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
34
stnga la dreapta
____________________________________________
35
5. 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.
n limbajul C punct i virgula este un terminator de instruciune i este obligatoriu. O instruciune de atribuire poate aprea pe post de expresie ntr-o alt instruciune. Funcia strcpy copiaz irul s peste irul t.
void strcpy(char t[], const char s[]) { int i = 0; while (t[i]=s[i]) i++; }
Expresia t[i]=s[i] este o atribuire i are valoarea True sau False n funcie de valoarea s[i].
36
Orice iniializare pentru variabilele 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 caracterul punct i virgul.
5.3. Instruciunea if
Sintaxa instruciunii condiionale if admite dou formate: if (expresie) if (expresie) instruciune-1 instruciune-1 else instruciune-2 Instruciunea if 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. n al doilea caz una i numai una dintre cele dou instruciuni se execut. Deoarece un if testeaz pur i simplu valoarea numeric a unei expresii, se admite o prescurtare i anume: if (expresie) n loc de: if (expresie != 0) Deoarece partea else a unei instruciuni if este opional, exist o ambiguitate cnd un else este omis dintr-o secven de if imbricat. Aceasta se rezolv asociind else cu ultimul if care nu are else.
if (n>0) if (a>b) z = a; else z = b; if (n>0) { if (a>b) z = a; } else z = b;
n exemplul de mai sus, n primul caz partea else se asociaz if-ului din interior. Dac nu dorim acest lucru atunci folosim acoladele pentru a fora asocierea, ca n al doilea caz. Instruciunea condiional admite i construcia else-if:
if (expresie-1) instruciune-1
else
if (expresie-2) instruciune-2
else
if (expresie-3) instruciune-3 else instruciune-4
____________________________________________
37
Aceast cascad de if-uri se folosete frecvent n programe, ca mod de a exprima o decizie multipl. Expresiile se evalueaz n ordinea n care apar; dac se ntlnete o expresie adevrat, atunci se execut instruciunea asociat cu ea i astfel se termin ntregul lan. Oricare instruciune poate fi o instruciune simpl sau un grup de instruciuni ntre acolade. Instruciunea dup ultimul else se execut n cazul n care nici o expresie nu a fost adevrat. Dac n acest caz nu exist nici o aciune explicit de fcut, atunci ultima parte poate s lipseasc:
else instruciune-4
grupate ntre un if iniial i un else final. ntotdeauna un else se asociaz cu ultimul if ntlnit.
Secvena din stnga afieaz numrul de cifre zecimale ale valorii lui n numai dac n> 0, altfel afieaz zero. Secvena din dreapta afieaz numrul de cifre zecimale ale valorii lui n n orice condiii.
39
2) Funcia strlen(s) returneaz lungimea irului de caractere s, excluznd caracterul terminal null.
int strlen(const char s[]) { int n; for (n=0; s[i]; ++n) ; return n; }
3) Instruciunea de iniializare (expresie-1) trebuie s fie una singur, de aceea, dac avem mai multe operaii de fcut, ele trebuie separate prin virgul i nu prin punct i virgul. Aceeai observaie este valabil i pentru expresie-3. Funcia strrev inverseaz un ir de caractere:
void strrev(char s[]) { int i,j; for (i=0,j=strlen(s)-1; i<j; i++,j--) { char c = s[i]; s[i] = s[j]; s[j] = c; } }
4) Relum problema din seciunea precedent: dndu-se o valoare natural n, s se determine numrul de cifre zecimale.
unsigned n,c; scanf("%u",&n); for (c=!n; n; c++) n /= 10; printf("%u",c); void strcpy(char t[], const char s[]) { int i; for (i=0; t[i]=s[i]; i++) ; }
40
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 ntreg. 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 secvena 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 secvena 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 sau return. Dac o secven de instruciuni este urmat de un nou case fr s fie ncheiat cu break sau return, nu se iese din switch. n concluzie, ieirea din switch trebuie s fie explicit. Exemplu. Un meniu poate fi implementat astfel:
char op[80];
do {
gets(op); switch (op[0]) { case 'i': case 'I': Initializare(. . .); break; case 'a': case 'A': Adaugare(. . .); break; case 'm': case 'M': Modificare(. . .); break; case 's': case 'S': Stergere(. . .); break; case 'c': case 'C': Consultare(. . .); break; case 'l': case 'L': Listare(. . .); break; default: Eroare(. . .); break; } } while (op!='t' && op!='T');
____________________________________________
41
n funcie de opiunea dorit (primul caracter din irul op) se selecteaz aciunea asociat valorii respective. Dac opiunea nu este recunoscut, se selecteaz aciunea care semnaleaz o eroare. Toate aceste aciuni se execut n cadrul unui ciclu do. Acesta se ncheie n momentul n care se introduce opiunea 't' sau 'T'.
Formatul instruciunii continue: 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. Secvena urmtoare prelucreaz numai elementele pozitive ale unui masiv.
for (i=0; i<n; i++) { if (a[i]<0) /* sare peste elementele negative */ continue; ... /* prelucreaz elementele pozitive */ }
Un exemplu de utilizare a instruciunii goto Pentru a efectua un salt explicit n interiorul unui program C se poate folosi i instruciunea goto, dei aceasta conduce la programe greu de verificat.
____________________________________________
42
Fie dou masive, a cu n elemente i b cu m elemente. S se determine dac exist cel puin o valoare comun. O secven care utilizeaz instruciunea goto se poate scrie astfel:
for (i=0; i<n; i++) for (j=0; j<m; j++) if (a[i]==b[j]) goto egale; /* Nu s-a gsit nici un element; prelucrri necesare acestui caz */ goto contin; egale: /* Un element comun pe poziiile i i j; prelucrri necesare acestui caz */ contin: /* Continu execuia dup finalizarea prelucrrilor pentru cazul depistat */
Dac scriem o funcie n acest scop, nu avem nevoie s folosim instruciunea goto. Funcia va memora poziiile i i j n variabile ale cror adrese snt comunicate de o funcie apelant. Dac nu exist o valoare comun atunci aceste variabile vor conine valori negative.
void Comun(double a[], int n, double b[], int m, int *pi, int *pj) { int i,j; *pi = *pj = -1; for (i=0; i<n; i++) for (j=0; j<m; j++) if (a[i]==b[j]) { *pi = i; *pj = j; return; } }
43
funciei apelante. Dac este nevoie, expresia este convertit, ca ntr-o atribuire, la tipul funciei n care ea apare. Detalii despre instruciunea return vor fi prezentate n capitolul urmtor.
n aceast instruciune, care numr caracterele unui ir, corpul lui for este vid, deoarece toat activitatea se face n partea de test i actualizare dar sintaxa lui for cere un corp al instruciunii. Instruciunea vid satisface acest lucru. Am precizat anterior c o instruciune compus nu este urmat niciodat de caracterul punct i virgul.
if (...) { . . . }; else . . .
Aceast secven este greit, deoarece dup instruciunea compus urmeaz o instruciune vid, i clauza else apare izolat fr a avea nici o instruciune if asociat.
____________________________________________
44
6. Funcii
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, permit desfacerea programelor mari n module mai 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. Un program C const dintr-o secven de definiii externe de funcii i de date. 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.
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 o valoare de alt tip dect int, sau dac tipul acesteia este void. O funcie de tip void nu returneaz o valoare n mod explicit, execut doar o secven de instruciuni.
____________________________________________
45
Numele funciei poate fi de asemenea precedat de clasa de memorie extern sau static, ali specificatori de clas nu snt admii. 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. Dac o funcie este declarat static, ea poate fi apelat numai din fiierul unde a fost definit. Comunicarea ntre funcii se face prin argumente i valori returnate de funcii. Comunicarea ntre funcii poate fi fcut i prin intermediul variabilelor globale. Se recomand ca definiia unei funcii sau cel puin declararea ei s se fac nainte de orice apelare. Astfel compilatorul poate depista imediat neconcordane ntre tipurile de parametri. S ilustrm mecanismul definirii unei funcii scriind funcia factorial fact(n). Aceast funcie este folosit ntr-un program care calculeaz valoarea n! k expresiei Cn = k!( n k )!
#include <stdio.h> int fact(int n) { int i,p; for (p=i=1; i<=n; i++) p *= i; return p; } int main() { int n,k,i; puts("Valori n si k: "); scanf("%d %d",&n,&k); printf("Combinari:%d\n",fact(n)/fact(k)/fact(n-k)); return 0; }
Funcia fact este apelat n programul principal de trei ori, fiecare apel transmite funciei un argument. O secven mai eficient din punctul de vedere al volumului de calcul i al corectitudinii acestora se bazeaz pe urmtoarea formul: n n 1 n k +1 Cnk = ... 1 2 k Funcia fact produce rezultate eronate pentru valori mari ale lui n (n 8, sisteme de calcul pe 16 bii; n 12, sisteme de calcul pe 32 de bii). Funcia comb, bazat pe relaia de mai sus, permite operarea cu valori mai mari ale parametrilor n i k.
____________________________________________
46
int comb(int n, int k) { int i,p; for (p=i=1; i<=k; i++) p = p * (n-i+1) / i; return p; }
deoarece se va evalua mai nti expresia din dreapta i rezultatul va fi nmulit cu valoarea lui p. Astfel se vor obine rezultate eronate pentru orice valoare n par.
2) Funcia rfibo implementeaz procedura recursiv de calcul a termenului de rang n din irul lui Fibonacci: n, n 1 Fn = Fn 1 + Fn 2 , n > 1
int rfibo(int n) { if (n<=1) return 1; else return fibo(n-1) + fibo(n-2); }
Simplitatea cu care snt definite aceste funcii ar putea cuceri imediat pe un programator neexperimentat. Trebuie s atragem atenia asupra faptului c cele dou definiii au o complexitate exponenial: pentru o valoare oarecare a lui n,
____________________________________________
47
numrul de operaii elementare efectuate (aritmetice i logice) este aproximativ proporional cu 2n. De aceea recursivitatea este justificat numai n situaia n care tim cu certitudine c se obine o definiie avnd complexitate acceptabil. Un exemplu n acest sens va fi dat n capitolul nou: gestionarea unei structuri de date de tip arbore. Am vzut n seciunea precedent o definiie simpl i eficient a unei funcii k care calculeaz valoarea Cn . n continuare prezentm o definiie simpl i eficient pentru determinarea unui termen din irul lui Fibonacci:
int int a = for fibo(int n) { a,b,c,i; 0; b = 1; c = n; (i=2; i<=n; i++) { c = a + b; a = b; b = c; } return p; }
Funcia folosete variabilele locale a i b pentru a memora termenii Fk2 i Fk 1. La un anumit pas se calculeaz Fk= Fk2+Fk1 n variabila c, i apoi variabilele a i b memoreaz termenii Fk1 i Fk.
____________________________________________
48
De exemplu, funcia atof(s) din biblioteca asociat limbajului C convertete irul s de cifre n valoarea sa n dubl precizie. Putem declara funcia sub forma:
double atof(char s[]);
Dac o funcie returneaz o valoare de tip char, nu este nevoie de nici o declaraie de tip datorit conversiilor implicite. Totdeauna tipul char este convertit la int n expresii.
Argumentul n este utilizat ca o variabil automatic 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 al variabilelor declarate externe. n cazul pointerilor, funcia apelant trebuie s furnizeze adresa variabilei care trebuie modificat (tehnic printr-un pointer la aceast variabil), iar funcia apelat trebuie s declare argumentul corespunztor ca fiind un pointer. Referirea la variabila care trebuie modificat se face prin adresare indirect (capitolul opt). 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
____________________________________________
49
avea acces i poate modifica orice element din masiv. Dac a fost precizat calificatorul const, modificarea nu este permis. Pentru funciile cu numr variabil de parametri standardul ANSI definete o construcie special ... (trei puncte) numit elips. Acesta este de exemplu cazul funciilor de citire i scriere cu format (familiile ...scanf i ...printf capitolul zece). n acest caz, parametrii fici snt verificai la compilare, iar cei variabili snt transmii fr nici o verificare.
50
} int main() { /* afieaz toate liniile care conin cuvntul "the" */ char lin[80]; while (gets(lin)>0) if (index(lin,"the")>=0) puts(lin); }
2. Funcia atof convertete un ir de cifre din formatul ASCII ntr-un numr flotant n precizie dubl. Aceast funcie accept un ir care conine reprezentarea unui numr zecimal (eventual cu semn), compus din parte ntreag i parte fracionar (oricare putnd lipsi), separate prin punct zecimal.
double atof(char s[]) { double val,fr; int i,sn; sn = 1; i = 0; if (s[0]=='+' || s[0]=='-') sn = (s[i++]=='+') ? 1 : -1; for (val=0; s[i]>='0' && s[i]<='9'; i++) val = 10 * val + s[i] - '0'; if (s[i]== '.') i++; for (fr=1; s[i]>='0' && s[i]<='9'; i++) { val = 10 * val +s[i] - '0': fr *= 10; } return sn * val / fr; }
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.
____________________________________________
51
int lower(int c) { if (c>='A' && c<='Z') return c + 'a' - 'A'; else return c; }
5. Funcia binary realizeaz cutarea valorii x ntr-un masiv sortat v, care are n elemente.
int binary(int x, int v[], int n) { int l,r,m; l = 0; r = n - 1; while (l<=r) { m = (l + r) / 2; if (v[m]==x) return m; if (v[m]<x) l = m + 1; else r = m - 1; } return -1; }
Funcia returneaz poziia lui x (un numr ntre 0 i n1), dac x apare n v sau 1 altfel.
____________________________________________
52
Aceast linie 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 directoare standard, cum ar fi biblioteca I/O standard asociat compilatorului. Linia urmtoare caut fiierul nume-fiier numai n biblioteca standard i nu n directorul fiierului surs:
#include < nume-fiier>
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.
____________________________________________
53
determin ca preprocesorul s nlocuiasc toate apariiile ulterioare ale identificatorului cu ir-simboluri dat (ir-simboluri nu se termin cu ; (punct i virgul) deoarece n urma substituiei identificatorului cu acest ir ar aprea prea multe caractere ; unele situaii conduc chiar la erori de sintax. 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:
printf("ALFA");
n care se va afia 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 NMAX 100 int tab[NMAX];
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 ( (parantez stnga) este o definiie pentru o macro-operaie cu argumente, n care textul de nlocuire (ir-simboluri) depinde de modul n care se apeleaz macro-ul respectiv. Ca un exemplu s definim o macro-operaie numit max n felul urmtor:
#define Max(a,b) ((a)>(b) ? (a) : (b))
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
____________________________________________
54
tip de date, nefiind nevoie de diferite tipuri de funcii maximum pentru diferite tipuri de date, aa cum este necesar n cazul funciilor propriu-zise. Dac se examineaz atent expandarea lui Max se pot observa anumite probleme ce pot genera erori, i anume: expresiile fiind evaluate de dou ori, n cazul n care ele conin operaii ce genereaz efecte colaterale (apelurile de funcii, operatorii de incrementare) se pot obine rezultate total eronate. De asemenea, trebuie avut mare grij la folosirea parantezelor pentru a face sigur ordinea evalurii dorite. De exemplu macro-operaia Square(x) definit prin:
#define Square(x) x*x
va produce un rezultat, altul dect cel scontat, datorit prioritii mai mari a operatorului * fa de cea a operatorului +.
O linie de control de forma urmtoare verific dac identificatorul a fost deja definit:
#ifdef identificator
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
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.
7.4. Exemple
1) 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.
____________________________________________
55
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 declaraiile celor dou funcii.
#ifndef _Numere_H #define _Numere_H unsigned eprim(unsigned n); unsigned cmmdc(unsigned p, unsigned q); #endif
Fiierul surs princn.c conine funciile main i citire. S observm c funcia citire este declarat static, de aceea nu poate fi apelat de alte module.
#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 proiectat pentru a putea fi folosit n mai multe aplicaii care au nevoie de astfel de funcii. Prin includerea fiierului numere.h se garanteaz c definiia funciilor este conform cu ateptrile modulului principal, n care este inclus acelai fiier header.
#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;
____________________________________________
56
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; 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. 2) Al doilea proiect gestioneaz o list de valori numerice asupra creia se efectueaz urmtoarele operaii: iniializare, operaie realizat automat la lansarea n execuie a programului; adugarea unei valori n list, dac aceasta nu exist deja; interogare: o anumit valoare se afl n list? tergerea unei valori din list; meninerea n permanen a unei relaii de ordine cresctoare. Masivul Lis[] memoreaz lista de valori, care are un numr de ne elemente, iniial zero. Prezentm mai nti un fiier header (lista.h) care conine declaraiile celor trei funcii, precum i a variabilelor externe Lis i ne.
#ifndef _Lista_H #define _Lista_H extern double Lis[]; extern int ne; int Adaugare(double v); int Stergere(double v); int Interogare(double v); #endif
Fiierul surs princl.c conine funcia main. Acesta ateapt de la terminal opiunea utilizatorului, urmat de un spaiu i de valoarea care se dorete a fi prelucrat. n funcie de opiune se apeleaz funcia corespunztoare sau se afieaz un mesaj de eroare. Dup fiecare operaie se afieaz lista valorilor.
____________________________________________
57
#include <stdio.h> #include "lista.h" int main() { double atof(char s[]); double v; int i; char o[80]; do { gets(o); v = atof(op+2); switch (o[0]) { case 'a': i = Adaugare(v); break; case 's': if (Stergere(v)) puts("S-a sters"); else puts("Nu exista"); break; case 'i': i = Interogare(v); if (i>=0) printf("Pozitia: %d\n",i); else puts("Nu exista"); break; default: puts("Optiune eronata"); break; } puts("Lista:"); for (i=0; i<ne; i++) printf(" %.3lf",Lis[i]); puts(""); } while (o!='t'); return 0; }
Fiierul surs lista.c definete variabilele Lis[] i ne, i funciile Adaugare, Interogare, Stergere. Funcia Adaugare adaug o valoare v n list dac aceasta nu exist. Valorile mai mari snt deplasate spre dreapta cu o poziie pentru a face loc noii valori. Returneaz poziia valorii n list dup (o eventual) adugare. Funcia Stergere terge o valoare v din list dac aceasta exist. Valorile mai mari snt deplasate spre stnga cu o poziie pentru a pstra lista compact. Returneaz True dac operaia a reuit i False n caz contrar.
____________________________________________
58
Funcia Interogare returneaz poziia pe care se afl valoarea v n list, dac aceasta exist, sau 1 n caz contrar. Deoarece lista este ordonat se face o cutare binar. Aceast funcie este apelat i de funcia Adaugare.
#include "lista.h" double Lis[100]; int ne; int Adaugare(double v) { int k; k = Interogare(v); if (k<0) { for (k=ne-1; k>=0; k--) if (Lis[k]>v) Lis[k+1] = Lis[k]; else break; Lis[k+1] = v; ne++; } return k; } int Stergere(double v) { int k; k = Interogare(v); if (k<0) return 0; for (; k<ne; k++) Lis[k] = Lis[k+1]; ne--; return 1; } int Interogare(double v) { int l,r,m; double d; l = 0; r = ne 1; while (l<=r) { m = (l + r) / 2; d = Lis[m] v; if (d==0) return m; if (d<0) l = m + 1; else r = m 1; } return 1; }
Proiectele programe de dimensiuni mari snt compuse de cele mai multe ori din module care se elaboreaz i se pun la punct n mod independent. 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 dezvol____________________________________________
59
tate 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 definite aceste funcii, i acesta se pstreaz ca i declaraii n fiiere header. Fiierele header pstreaz i alte informaii comune mai multor module, cum snt de exemplu tipurile definite de programator, cel mai adesea tipuri structurate (detalii n capitolul nou).
____________________________________________
60
8. 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.
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. Variabila x are asociat o zon de memorie, i aceast zon are (presupunem) adresa 8A54. Dup atribuirea px = &x, variabila px va avea valoarea 8A54. Evident toate variabilele care snt implicate n aceste instruciuni trebuie declarate. Aceste declaraii snt:
int x,y,*px;
Declaraiile variabilelor x i y snt deja cunoscute. Declaraia pointerului px este o noutate. Aceasta 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; n expresia urmtoare variabilei y i se atribuie valoare variabilei x plus 1:
y = *px + 1;
Instruciunea urmtoare are ca efect convertirea valorii variabilei x pe care o indic px n tip double i apoi depunerea rdcinii ptrate a valorii astfel convertite n variabila d:
d = sqrt((double)*px);
Referiri la pointeri pot aprea de asemenea i n partea stng a atribuirilor. Dac, de exemplu, px indic spre x, atunci urmtoarea instruciune atribuie variabilei x valoarea zero:
____________________________________________
61
*px = 0;
n acest ultim exemplu parantezele snt obligatorii deoarece, n lipsa lor, expresia ar incrementa pe px n loc de valoarea variabilei pe care o indic (operatorii unari *, ++ au aceeai preceden i snt evaluai de la dreapta spre stnga).
Pointerii generici snt foarte utilizai de unele funcii de bibliotec, atunci cnd este nevoie s se opereze cu zone de memorie care pot conine informaii de orice natur: operaii de intrare / ieire fr format (capitolul zece), alocri de memorie, sortare i cutare (capitolul unsprezece). Aceste funcii nu cunosc tipul datelor cu care opereaz, pentru aceste funcii zonele de memorie nu au un tip precizat. Acest fapt este evideniat prin pointeri de tip void.
____________________________________________
62
x = y; y = tmp; }
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);
Modificarea nu este permis dac parametrul de tip pointer este precedat de declaratorul const.
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, i a este un masiv de ntregi, atunci instruciunea de atribuire ncarc variabila pa cu adresa primului element al masivului a.
int *pa,a[10]; pa = &a[0]; x = *pa;
Atribuirea urmtoare copiaz coninutul lui a[0] n x: Dac pa indic un element particular al unui masiv a, atunci prin definiie pa+i indic un element cu i poziii dup elementul pe care l indic pa, dup cum pa-i indic un element cu i poziii nainte de cel pe care indic pa. Astfel, dac variabila pa indic pe a[0] atunci *(pa+i) se refer la coninutul lui a[i]. Aceste observaii snt adevrate indiferent de tipul variabilelor din masivul a.
____________________________________________
63
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. Cele dou instruciuni de atribuire snt identice:
pa = &a[1]; pa = a + 1;
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 urmtoarele instruciuni snt corecte:
pa = a; pa++;
Dar un nume de masiv este o constant i deci urmtoarele construcii nu snt permise:
a = pa; a++; p = &a;
Cnd se transmite unei funcii un nume de masiv, ceea ce se transmite de fapt este adresa primului element al masivului. Aadar, un nume de masiv, argument al unei funcii, este n realitate un pointer, adic o variabil care conine o adres. Fie de exemplu funcia strlen care determin lungimea irului s:
int strlen(const char *s) { int n; for (n=0; *s; 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:
____________________________________________
64
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[]) {. . .} f(int *arr) {. . .}
Aceast construcie i altele similare snt cele mai simple i comune formule ale aritmeticii de adrese, care constituie o caracteristic puternic a limbajului C. Pentru exemplificare relum inversarea caracterelor unui ir:
void strrev(char s[]) { int i,j; char c; for (i=0,j=strlen(s)-1; i<j; i++,j--) { c = s[i]; s[i] = s[j]; s[j] = c; } }
Exemplul de mai sus demonstreaz cteva din facilitile aritmeticii de adrese (pointeri). n primul rnd, pointerii pot fi comparai n anumite situaii. Relaiile <, <=, >, >=, ==, != snt valide numai dac p i q snt pointeri la elementele aceluiai masiv. 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.
____________________________________________
65
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 indic p, dimensiunea fiind determinat din declaraia lui p (scara de aliniere este 1 pentru char, 2 pentru short 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 alte versiuni ale funciei strlen folosind aceast ultim observaie:
int strlen(char *s) { char *p; p = s; while (*p) p++; return p-s; } int strlen(char *s) { char *p; for (p=s; *p; 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. Snt admise de asemenea incrementrile i decrementrile precum i alte combinaii ca de exemplu *++p i *--p. 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.
funcia puts 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.
____________________________________________
66
Vom prezenta cteva aspecte legate de pointeri i masive analiznd dou exemple. S considerm pentru nceput funcia strcpy(t,s) care copiaz irul s (source) peste irul t (target). O prim versiune a programului ar fi urmtoarea:
void strcpy(char t[], const char s[]) { int i; i = 0; while (t[i]=s[i]) i++; }
Aceast versiune cu pointeri modific prin incrementare pe t i s n partea de test. Valoarea lui *s++ este caracterul indicat de pointerul s, nainte de incrementare. Notaia postfix ++ asigur c s va fi modificat dup preluarea coninutului indicat de el, de la vechea poziie a lui s, dup care s se incrementeaz. La fel, t va fi modificat dup memorarea caracterului la vechea poziie a lui t, dup care i t se incrementeaz. Efectul este c se copiaz caracterele irului s n irul t pn la caracterul terminal 0 inclusiv. Am preferat s evitm redundana comparrii cu caracterul 0, facilitate care rezult din structura instruciunii while. Am fi putut scrie i astfel:
while ((*t++=*s++)!=0) ;
S considerm, ca al doilea exemplu, funcia strcmp(t,s) care compar caracterele irurilor t i s i returneaz o valoare negativ, zero sau pozitiv, dup cum irul t este lexicografic mai mic, egal sau mai mare ca irul s. Valoarea returnat se obine prin scderea caracterelor primei poziii n care t i s difer. O prim versiune a funciei strcmp(t,s) este urmtoarea:
int strcmp(const char t, const char s) { int i; i = 0; while (t[i]==s[i]) if (t[i++]==0) return 0; return t[i]-s[i]; }
67
Masivul Zile trebuie s fie declarat global pentru a putea fi folosit de ambele funcii. 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 multe alte limbaje. ntr-un masiv bidimensional 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 Zile 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:
int NrZile (int an, int luna, int zi) {
____________________________________________
68
int i,bis; bis = !(an%4) && (an%100) || !(an%400); for (i=1; i<luna; i++) zi += Zile[bis][i]; return zi; }
Deoarece variabila bis poate lua ca valori numai zero sau unu, dup cum expresia (scris de data aceasta explicit):
(an%4==0) && (an%100!=0) || (an%400==0)
este fals sau adevrat, ea poate fi folosit ca indice de linie n tabelul Zile care are doar dou linii n exemplul nostru.
void LunaZi(int an, int zian, int *plun, int *pzi) { int i,bis; bis = !(an%4) && (an%100) || !(an%400); for (i=1; zian>Zile[bis][i]; i++) zian -= Zile[bis][i]; *plun = i; *pzi = zian; }
Deoarece aceast ultim funcie returneaz dou valori, argumentele care corespund lunii i zilei snt pointeri. Exemplu. LunaZi(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 Zile trebuie transmis unei funcii f, atunci declaraia lui f poate fi:
f(int (*Zile)[13]); f(int Zile[][13]);
unde declaraia (*Zile)[13]) indic faptul c argumentul lui f este un pointer la un masiv de 13 ntregi. n general, un masiv d-dimensional a[i][j]...[l] de rangul i*j*...*l este un masiv (d 1)-dimensional de rangul j*k*...*l ale crui elemente, fiecare, snt masive (d 2)-dimensionale de rang k*...*l ale crui elemente, fiecare, snt masive (d 3)-dimensionale .a.m.d. Oricare dintre expresiile urmtoare: a[i], a[i][j]..., a[i][j]...[l] 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.
____________________________________________
69
Adresele liniilor snt memorate ntr-un masiv de pointeri la caracter. Astfel 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. n primul pas al procesului de sortare se citesc toate liniile textului de la intrare. n al doilea pas se sorteaz liniile n ordine lexicografic. n ultimul pas se afieaz 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:
int main() { /* program care sorteaz liniile de la intrare */ #define NLIN 100 /* nr maxim de linii de sortat */ char *linptr[NLIN]; /* pointeri la linii */ int nrl; /* nr linii intrare citite */ nrl = ReadLines(linptr,NLIN); if (nrl>=0) { Sort(linptr,nrl); WriteLines(linptr,nrl); } else puts("Intrare prea mare pentru sort"); return 0; }
Cele 3 funcii care realizeaz ntregul proces snt: ReadLines, Sort i WriteLines.
____________________________________________
70
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 afiare. ntruct funcia de intrare poate prelucra numai un numr finit de linii de intrare, ea poate returna un numr ilegal, cum ar fi spre a 1, 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:
int ReadLines(char *linptr[], int nmax) { int nrl = 0; char *p,lin[80]; while (gets(lin)) { if (nrl>=nmax) return -1; p = strdup(lin); if (!p) return -1; linptr[nrl++] = p; } return nrl; }
Rutina care afieaz liniile n noua lor ordine este WriteLines i are urmtorul cod:
void WriteLines(char *linptr[], int nrl) { int i; for (i=0; i<nrl; i++) puts(linptr[i]); }
care indic faptul c linptr este un masiv de NLIN elemente, fiecare element al masivului fiind un pointer la un caracter. Astfel linptr[i] este un pointer la un caracter, iar *linptr[i] permite accesul la caracterul respectiv. Deoarece linptr este el nsui un masiv, care se transmite ca argument funciei WriteLines, el va fi tratat ca un pointer i atunci funcia WriteLines mai poate fi scris i astfel:
void WriteLines(char *linptr[], int nrl) { while (--nrl>=0) puts(*linptr++); }
____________________________________________
71
n funcia puts, linptr indic iniial prima linie de afiat; fiecare incrementare avanseaz pe *linptr la urmtoarea linie de afiat, n timp ce nrl se micoreaz dup fiecare afiare a unei linii cu 1. Funcia care realizeaz sortarea efectiv a liniilor se bazeaz pe algoritmul de njumtire i are urmtorul cod:
void Sort(char *v[], int n) { int gap,i,j; char *tmp; 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; tmp = v[j]; v[j] = v[j+gap]; v[j+gap] = tmp; } }
Deoarece fiecare element al masivului v (care este de fapt masivul linptr din funcia main) este un pointer la primul caracter al unei linii, variabila tmp 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. Structura programului este urmtoarea:
#include <stdlib.h> #include <string.h> #include <stdio.h> int ReadLines(...) { ... } void WriteLines(...) { ... } void Sort(...) { ... } int main(...) { ... }
S reinem 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 multi-dimensionale. 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 1)-dimensional de
____________________________________________
72
rangul j*...*k, ale crui elemente snt masive. Dac operatorul * se aplic acestui pointer, rezultatul este masivul (d 1)-dimensional, care se va converti imediat ntrun pointer la un masiv (d 2)-dimensional .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: 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.
____________________________________________ |______________|______________|______________| |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__| X[0] X[1] X[2]
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.
73
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, chiar dac 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]. 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.
____________________________________________
74
Fie funcia NumeLuna 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 *NumeLuna(int n) { /* returneaz numele lunii a n-a */ static char *NL[] = { "luna ilegala", "ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie" } return (n<1) || (n>12) ? NL[0] : NL[n] ; }
n acest exemplu, NL 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 NL[i] va conine un pointer la irul de caractere avnd indice i al iniializatorului. Nu este necesar s se specifice dimensiunea masivului NL deoarece compilatorul o determin numrnd iniializatorii furnizai i o completeaz n declaraia masivului.
n acest caz 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
____________________________________________
75
dimensiunea masivelor de pointeri poate fi variabil. Acest lucru nseamn c un element al masivului de pointeri b poate indica un masiv de zece elemente, altul un masiv de dou elemente i altul poate s nu indice nici un masiv. Cu toate c problema prezentat n acest paragraf a fost descris n termenii ntregilor, ea este cel mai frecvent utilizat n memorarea irurilor de caractere de lungimi diferite (ca n funcia NumeLuna prezentat mai sus). Prezentm n continuare un model de rezolvare a alocrii de memorie pentru o matrice de dimensiuni care snt cunoscute abia n momentul execuiei. n biblioteca limbajului C exist funcii care ne ofer posibilitatea de a aloca memorie necesar pentru memorarea unor date. Dup ce datele au fost prelucrate, memoria ocupat nu mai este necesar i poate fi eliberat. Declaraiile acestor funcii se obin cu:
#include <stdlib.h>
Funcia aloc o zon de memorie de n octei i returneaz adresa acesteia. O zon de memorie avnd adresa p (p fiind un pointer) poate fi eliberat dup ce nu mai este necesar:
void free(void *p);
Presupunem c trebuie s alocm memorie pentru o matrice de elemente ntregi avnd n linii i m coloane.
#include <stdlib.h> int **IntMatr(int n, int m) { int i,**p; p = (int **)malloc(n*sizeof(int *)); p[0] = (int *)malloc(nm*sizeof(int)); for (i=1; i<n; i++) p[i] = p[i-1] + m; return p; }
Iniial se aloc memorie unde se vor depune adresele celor n linii, de aceea primul apel malloc cere o zon de memorie pentru n pointeri. n continuare trebuie alocat memorie i pentru matricea propriu-zis. Pentru simplificare am preferat s alocm o zon continu de n m ntregi (al doilea apel malloc). n acest moment putem determina adresa fiecrei linii a matricei. Prima linie are determinat adresa din momentul alocrii. Pentru o linie oarecare i (i> 0) adresa acesteia se obine din adresa liniei precedente la care se adaug m (numrul de elemente ale liniei); a se vedea i figura de mai jos.
___ ________________________________
|__||__|__|__|__|__|__|__|__|__|
____________________________________________
76
|__|| | |__||
Este foarte important s se respecte aceti pai n alocarea memoriei pentru matrice. n caz contrar variabilele pointer conin valori necunoscute i nu exist garanii c indic spre zone de memorie sigure: este foarte probabil c programul nu are acces la zonele respective. Funcia principal va fi scris astfel:
int main() { int **Mat,n,m; /* alte definiii de variabile */ /* Citete valorile n i m */ Mat = IntMatr(n,m); /* Completeaz matricea cu valori Mat[i][j] ... */ /* Prelucreaz matricea */ free(Mat[0]); free(Mat); /* Elibereaz memoria */ }
Este foarte important s se respecte i paii precizai pentru eliberarea memoriei ocupate: mai nti zona ocupat de matrice i apoi i zona ocupat de pointeri. Dac se elibereaz mai nti zona ocupat de pointeri, programul nu mai are acces la zona respectiv, i un apel free(Mat[0]) va fi respins de sistemul de operare. S relum problema alocrii de memorie pentru o matrice n cazul general, unde cunoatem: n: numrul liniilor matricei; M[]: masiv care indic numrul elementelor din fiecare linie; s: mrimea n octei a unui element al matricei (se obine cu operatorul sizeof); toate elementele matricei snt de acelai tip.
void **Genmatr(int n, int M[], int s) { int i; void **p; p = (void **)malloc(n*sizeof(void *)); for (i=0; i<n; i++) p[i] = malloc(M[i]*s); return p; }
Spre deosebire de exemplul precedent, unde toat memoria a fost alocat ntro singur operaie (al doilea apel malloc), aici am alocat separat memorie pentru fiecare linie. Apelul acestei funcii necesit anumite pregtiri:
____________________________________________
77
double **Dm; /* pentru o matrice cu elemente de tip double */ int n,*M; /* alte definiii de variabile */ /* Citete valoarea n */ M = (int *)malloc(n*sizeof(int)); /* precizeaz pentru fiecare linie numrul de elemente */ Dm = (double **)Genmatr(n,M,sizeof(double)); /* Operaii cu elementele matricei */
Este posibil s alocm o zon compact pentru memorarea matricei, trebuie mai nti s determinm numrul total de elemente. A doua variant a funciei GenMatr este:
void **Genmatr(int n, int M[], int s) { int i,l; char **p; p = (char **)malloc(n*sizeof(void *)); for (l=i=0; i<n; i++) l += M[i]; p[0] = (char *)malloc(l*s); for (i=1; i<n; i++) p[i] = p[i-1] + M[i] * s; return p; }
Memoria ocupat de matrice se elibereaz cu dou apeluri free, aa cum am artat mai sus n cazul matricei de ntregi. Considerm c acest stil de programare, bazat pe alocarea compact, este mai avantajos deoarece se evit o fragmentare excesiv a memoriei. Sistemul de operare memoreaz informaii suplimentare (ascunse programatorului) care i permit ulterior s elibereze o zon de memorie la cerere.
Pentru a obine descrieri compacte ale programelor am preferat s folosim numele ac i av pentru argumentele funciei main. Primul argument reprezint numrul de argumente din linia de comand care a lansat programul. Al doilea argument 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 afieze la terminal argumentele lui luate din linia de comand, afiarea fcndu-se pe o linie, iar argumentele afiate s fie separate prin spaii. Comanda:
pri succes colegi
va avea ca rezultat afiarea la terminal a textului succes colegi Prin convenie, av[0] este un pointer la numele pri al programului apelat, astfel c ac, care specific numrul de argumente din linia de comand, este cel puin 1. n exemplul nostru, ac este 3, iar av[0], av[1] i av[2] snt pointeri la pri, succes i respectiv colegi. Primul argument efectiv este av[1] iar ultimul este av[ac-1]. Dac ac este 1, nseamn c linia de comand nu are nici un argument dup numele programului.
int main(int ac, char *av[]) { /* afieaz argumentele */ int i; for (i=1; i<ac; i++) printf("%s%c",av[i],(i<ac-1)?' ':'\n'); return 0; }
Deoarece av este un pointer la un masiv de pointeri, exist mai multe posibiliti de a scrie acest program. S mai scriem o versiune a acestui program.
int main(int ac, char *av[]) { /* versiunea a doua */ while (--ac>0) printf("%s%c",*++av,(av>1) ? ' ' : '\n'); return 0; }
Deoarece av este un pointer la un masiv de pointeri, prin incrementare *+ +av va indica spre av[1] n loc de av[0]. Fiecare incrementare succesiv poziioneaz pe av la urmtorul argument, iar *av este pointerul la argumentul irului respectiv. n acelai timp ac este decrementat pn devine zero, moment n care nu mai snt argumente de afiat.
____________________________________________
79
Aceast versiune arat c argumentul funciei printf poate fi o expresie ca oricare alta. Ca un al doilea exemplu, s reconsiderm un program anterior care afieaz fiecare linie a unui text care conine un ir specificat de caractere (ablon). Dorim acum ca acest ablon s poat fi precizat printr-un argument n linia de comand la momentul execuiei. i atunci funcia principal a programului care caut ablonul dat de primul argument al liniei de comand este:
int main(int ac, char *av[ ]) { char lin[80]; if (ac!=2) puts("Linia de comanda eronata"); else while (gets(lin)) if (index(lin,av[1])>=0) puts(lin); return 0; }
unde linia de comand este de exemplu: find limbaj n care find este numele programului, iar limbaj este ablonul cutat. Rezultatul va fi afiarea 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 afieze toate liniile cu excepia acelora care conin ablonul, i al doilea care s precead fiecare linie afiat cu numrul ei de linie. O convenie pentru programele scrise n limbajul C este ca argumentele dintro linie de comand care ncep cu un caracter - s introduc un parametru opional. Dac alegem, de exemplu, -x pentru a indica cu excepia i -n pentru a cere numrarea liniilor, atunci comanda:
find -x -n la
avnd intrarea: la miezul stinselor lumini s-ajung victorios, la temelii, la rdcini, la mduv, la os. va produce afiarea liniei a doua, precedat de numrul ei, deoarece aceast linie nu conine ablonul la. Argumentele opionale snt permise n orice ordine n linia de comand. Analizarea i prelucrarea argumentelor unei linii de comand trebuie efectuat n
____________________________________________
80
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 ablonul la n liniile de la intrare i le afieaz pe acelea, care nu conin ablonul, precedate de numrul lor de linie. Programul trateaz corect att prima form a liniei de comand ct i a doua. n continuare este prezentat doar funcia principal a acestui program.
int main(int ac, char *av[]) { /* caut un ablon */ char lin[80],*s; long nl; int exc,num,tst; nl = num = 0; while (--ac>0 && (*++av)[0]=='-') for (s=av[0]+1; *s!=0; s++) switch(*s) { case 'x': exc = 1; break; case 'n': num = 1; break; default: printf("find: optiune ilegala %c\n",*s); ac = 0; break; } if (ac!=1) puts("Nu exista argumente sau sablon"); else while (gets(lin) { nl++; tst = index(lin,*av)>=0; if (tst!=except) { if (num) printf("%4d:",nl); puts(lin); } } }
Dac nu exist erori n linia de comand, atunci la sfritul primului ciclu while ac trebuie s fie 1, iar *av conine adresa ablonului. *++av este un pointer la un ir argument, iar (*++av)[0] este primul caracter al irului. n aceast ultim expresie parantezele snt necesare deoarece fr ele expresia
____________________________________________
81
nseamn *++(av[0]) ceea ce este cu totul altceva (i greit): al doilea caracter din numele programului. O alternativ corect pentru (*++av)[0] este **+ +av.
n expresia g(f), f nu este un 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();
ar nsemna 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 apelul funciei a crei adres este dat de pointerul funcpt. Ca un exemplu, s considerm procedura de sortare a liniilor de la intrare, 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 de regul din trei componente: o comparare care determin ordinea oricrei perechi de elemente, un schimb pentru a inversa 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 astfel nct, transmind diferite funcii
____________________________________________
82
de comparare funciei de sortare, elementele de intrare se pot aranja dup diferite criterii. Compararea lexicografic a dou linii se realizeaz prin funcia strcmp. 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 NLIN 100 /* numr maxim de linii de sortat */ int main (int ac, char *av[]) { char *linptr[NLIN]; /* pointeri la linii text */ int nrl; /* numr de linii citite */ int num = 0; /* 1 dac sort numeric */ if (ac>1 && av[1][0]=='-' && av[1][1]=='n') num = 1; nrl = ReadLines(linptr,NLIN); if (nrl>=0) { if (num) Sort(linptr,nrl,numcmp); else Sort(linptr,nrl,strcmp); WriteLines(linptr,nrl); } else puts("Intrare prea mare pentru Sort"); }
n apelul funciei Sort, argumentele strcmp i numcmp snt adresele funciilor respective. Deoarece ele snt declarate funcii care returneaz un ntreg (a se vedea mai jos structura programului), operatorul & nu trebuie s precead numele funciilor, compilatorul fiind cel care gestioneaz transmiterea adreselor funciilor. S sintetizm ntr-o declaraie elementele comune ale celor dou funcii:
typedef int (*P_func)(const char *, const char *);
Aceast declaraie definete identificatorul P_func ca fiind un tip pointer la o funcie care returneaz un ntreg, i are ca argumente doi pointeri la caracter. Elementele celor dou masive nu pot fi modificate. Tipul P_func poate fi folosit n continuare pentru a construi noi declaraii. Funcia Sort care aranjeaz liniile n ordinea cresctoare se va modifica astfel:
void Sort(char *v[], int n, P_func comp) { int gap,i,j; char *tmp;
____________________________________________
83
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; tmp = v[j]; v[j] = v[j+gap]; v[j+gap] = tmp; } }
Funcia numcmp este definit astfel: int numcmp(const char *s1, const char *s2) { double atof(char *s),v1,v2; v1 = atof(s1); v2 = atof(s2); if (v1<v2) return -1; if (v1>v2) return 1; return 0; } Structura programului este urmtoarea:
#include <stdlib.h> #include <string.h> #include <stdio.h> int ReadLines(...) {...} void WriteLines(...) {...} int numcmp(...) {...} typedef int (*P_func)(const char *, const char *); void Sort(...) {...} int main(...) {...}
n funcia main, n locul variabilei num de tip ntreg putem folosi un pointer de tip P_func astfel:
P_func Fcomp = strcmp; if (ac>1 && av[1][0]=='-' && av[1][1]=='n') Fcomp = numcmp; nrl = ReadLines(linptr,NLIN); if (nrl>=0) { Sort(linptr,nrl,Fcomp); WriteLines(linptr,nrl); }
Dac programul este compus din mai multe module, se utilizeaz un fiier header care conine declaraiile tuturor funciilor utilizate. Acest fiier trebuie inclus n fiecare modul.
____________________________________________
84
9. 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. Structurile ajut la organizarea datelor complexe, n special n programele mari, deoarece permit unui grup de variabile legate s fie tratate ca o singur entitate. n acest capitol vom ilustra modul de utilizare a structurilor.
Cuvntul cheie struct introduce o declaraie de structur care este o list de declaraii nchise ntre acolade. Acest cuvnt poate fi urmat opional de un nume, numit marcaj de structur sau etichet de structur, cum este n exemplul nostru numele S_data. Marcajul denumete acest tip de structur i poate fi folosit n continuare ca o prescurtare pentru declaraia de structur detaliat creia i este asociat. 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.
____________________________________________
85
Declaraia urmtoare definete variabila d ca o structur avnd acelai ablon ca structura S_data:
struct S_data d;
O structur extern sau static poate fi iniializat, atand dup definiia ei o list de iniializatori pentru componente, de exemplu:
struct S_data d = {4,7,1984};
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:
bis = !(d.an%4) && (d.an%100) || !(d.an%400);
Structurile pot fi imbricate: o nregistrare de stat de plat, de exemplu, poate fi de urmtoarea form:
struct S_pers { long nrl; char num[LNUM],adr[LADR]; long sal; struct S_data dnast,dang; } ;
Tipul structur S_pers conine dou structuri de ablon S_data. Declaraia urmtoare definete o variabil de tip structur cu numele ang, avnd acelai ablon ca structura S_pers: struct S_pers ang; n acest caz: ang.dnast.luna se refer la luna de natere ang.dang.an se refer la anul angajrii Singurele operaii pe care le vom aplica unei structuri vor fi accesul la un membru al structurii i considerarea adresei ei cu ajutorul operatorului &. 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. Structurile i funciile conlucreaz foarte eficient prin intermediul pointerilor. Ca un exemplu, s rescriem funcia de conversie a datei, care determin numrul zilei din an.
int NrZile(const struct S_data *pd) { int i,zi,bis; zi = pd->zi; bis = !(pd->an%4) && (pd->an%100) || !(pd->an%400); for (i=1; i<pd->luna; i++) zi += Zile[bis][i];
____________________________________________
86
return zi; }
Declaraia din lista de argumente indic faptul c pd este un pointer la o structur de ablonul S_data. Notaia pd->an indic faptul c se refer la membrul an 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 an poate fi de asemenea referit prin: (*pd).an, dar notaia -> se impune ca un mod convenabil de prescurtare. n notaia (*pd).an, parantezele snt necesare deoarece precedena operatorului . (membru de structur) este mai mare dect cea a operatorului * (indirectare). Operatorii -> i . ai structurilor, mpreun cu () (modificare prioriti, liste de argumente) i [] (indexare) se gsesc n vrful listei de preceden, fiind din acest punct de vedere foarte apropiai. Astfel, fie declaraia urmtoare care definete un pointer p la o structur:
struct { int x,*y; } *p;
Semnificaiile urmtoarelor expresii snt: ++p->x Incrementeaz membrul x, nu pointerul p, deoarece operatorul -> are o preceden mai mare dect ++. Parantezele pot fi folosite pentru a modifica ordinea operatorilor dat de precedena. (++p)->x Incrementeaz mai nti pointerul p, apoi acceseaz membrul x, din structura indicat de noua valoare a lui p. (p++)->x Acceseaz mai nti membrul x, apoi incrementeaz pointerul p. *p->y Indic coninutul de la adresa memorat la y. *p->y++ Acceseaz mai nti ceea ce indic y, apoi incrementeaz pe y. (*p->y)++ Incrementeaz ceea ce indic y. *p++->y
____________________________________________
87
9.2. typedef
Stilul de utilizare a tipurilor de date structurate prezentat mai sus este foarte frecvent, i se ntlnete mai ales n programe care apeleaz funcii ale sistemelor de operare Linux. Considerm totui c a scrie declaraii de funcii care au ca i parametri date structurate, sau a defini variabile de tip structur, este destul de neplcut datorit cerinei de a preciza numele tipului precedat de cuvntul rezervat struct. De aceea, pe parcursul lucrrii vom folosi o facilitate (typedef) de a defini sinonime pentru tipuri de date existente, care pot fi fundamentale (predefinite) sau definite de programator. ntr-o declaraie 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 urmtoare l face pe LENGTH sinonim cu int:
typedef int LENGTH;
Tipul LENGTH poate fi folosit ulterior n declaraii n acelai mod ca i tipul int:
LENGTH len,maxlen,*lgm[];
n mod similar, declaraia urmtoare l face pe STRING sinonim cu char*, adic pointer la caracter:
typedef char *STRING;
Observm c tipul care se declar prin typedef apare pe poziia numelui de variabil, nu imediat dup cuvntul rezervat typedef.
typedef int (*P_func)(const char *, const char *);
Aceast declaraie definete identificatorul P_func ca fiind un tip pointer la o funcie care returneaz un ntreg, i are ca argumente doi pointeri la caracter. Elementele celor dou masive nu pot fi modificate. Tipul P_func poate fi folosit n continuare pentru a construi noi declaraii. Rescriem definiiile de tipuri din seciunea precedent:
typedef struct { int zi,luna,an; } T_data;
____________________________________________
88
O structur extern sau static poate fi iniializat, atand dup definiia ei o list de iniializatori pentru componente, de exemplu:
T_data d = {4,7,1984};
Structurile pot fi imbricate: un tip de nregistrare de stat de plat, de exemplu, poate fi definit astfel:
typedef struct { long nrl; char num[LNUM],adr[LADR]; long sal; T_data dnast,dang; } T_pers;
Exist dou motive principale care impun folosirea declaraiilor typedef. Primul este legat de problemele de portabilitate. Cnd se folosesc declaraii typedef pentru tipuri de date care snt dependente de main, atunci pentru o compilare pe un alt sistem de calcul este necesar modificarea doar a acestor declaraii nu i a datelor din program. La sfritul acestui capitol vom da un exemplu n acest sens relund definirea tipului structurat dat calendaristic. 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. n continuare pe parcursul lucrrii vom folosi facilitatea typedef pentru a defini tipuri complexe de date i n special structuri.
respectiv unul de pointeri la iruri de caractere i cellalt de ntregi. Fiecrui cuvnt cheie i corespunde perechea (keyw,keyc) astfel nct putem considera cele dou masive ca fiind un masiv de perechi. Urmtoarea declaraie definete un tip structur T_key:
typedef struct {
____________________________________________
89
Folosim acest tip structur pentru a defini un masiv keym, unde NKEYS este o constant definit cu ajutorul directivei #define:
T_key keym[NKEYS];
Fiecare element al masivului este o structur de acelai ablon ca i structura T_key. Deoarece masivul de structuri keym 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 analog cu iniializarea unui masiv n sensul c definiia este urmat de o list de iniializatori nchii n acolade. Atunci masivul de structuri keym va fi iniializat astfel:
T_key keym[] = { "break",0, "case",0, "char",0, /* ... */ "while",0 };
Iniializatorii snt perechi care corespund la membrii structurii. Iniializarea ar putea fi fcut i incluznd iniializatorii fiecrei structuri din masiv n acolade ca n:
T_key keym[] = { {"break",0},{"case",0}, . . . }
dar parantezele interioare nu mai snt necesare dac iniializatorii snt variabile simple sau iruri de caractere i dac toi iniializatorii snt prezeni. Dac masivul keym este global, eventualii membri pentru care nu se precizeaz o valoare iniial vor primi valoarea zero, dar n acest caz parantezele interioare snt necesare:
T_key keym[] = { {"break"},{"case"}, . . . }
Compilatorul va determina, pe baza iniializatorilor, dimensiunea masivului de structuri keym, motiv pentru care nu este necesar indicarea dimensiunii masivului la iniializare. Programul de numrare a cuvintelor cheie ncepe cu definirea masivului de structuri keym. Funcia principal main citete cte un cuvnt (ir de caractere) din textul de la intrarea standard prin apelul repetat al funciei scanf. Fiecare ir este apoi cutat n tabelul keym cu ajutorul unei funcii de cutare binary. Lista cuvintelor cheie trebuie s fie n ordine cresctoare pentru ca funcia binary s lucreze corect. Dac irul cercetat este un cuvnt cheie atunci funcia binary
____________________________________________
90
Important! Funcia scanf cu descriptorul de format "%s" furnizeaz n masivul word un ir de caractere care nu conine spaii. Dac irul are cel puin 36 de caractere, comportamentul programului este imprevizibil. Dac dorim s prelum cel mult 35 de caractere folosim formatul "%35s". Citirea i scrierea cu format vor fi detaliate n capitolul 10. Valoarea NKEYS este numrul cuvintelor cheie din keym (dimensiunea masivului de structuri). Dei putem calcula acest numr manual, este mai simplu i mai sigur s-o facem prin program, mai ales dac lista cuvintelor cheie este supus modificrilor. O posibilitate de a calcula NKEYS prin program este de a termina lista iniializatorilor cu un pointer NULL i apoi prin ciclare pe keym s detectm sfritul lui. Acest lucru nu este necesar deoarece dimensiunea masivului de structuri este perfect determinat n momentul compilrii. Numrul de intrri se determin mprind dimensiunea masivului la dimensiunea structurii key. Operatorul sizeof furnizeaz dimensiunea n octei a argumentului su.
____________________________________________
91
n cazul nostru, numrul cuvintelor cheie este dimensiunea masivului keym 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(keym)/sizeof(T_key))
92
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 T_key, n loc de un ntreg. Dac binary gsete un cuvnt n structura T_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 keyc corespunztor cuvntului sau trece direct la citirea urmtorului cuvnt. n al doilea rnd, toate operaiile de acces la elementele masivului de structuri keym se fac prin intermediul pointerilor. Acest lucru determin o modificare semnificativ n funcia binary. Calculul elementului mijlociu nu se mai poate face simplu prin:
m = (l + r) / 2
deoarece adunarea a doi pointeri este o operaie ilegal, nedefinit. Aceast instruciune trebuie modificat n:
m = l + (r - l) / 2
care face ca adresa memorat la m s indice elementul de la jumtatea distanei dintre l i r. S mai observm iniializarea pointerilor l i r, 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=keym; p<keym+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 structurii. Nu ntotdeauna dimensiunea structurii este egal cu suma dimensiunilor membrilor ei, deoarece din cerine de aliniere a unor membri pot aprea goluri n interiorul acesteia. De aceea, dac dorim s economisim ct mai mult memorie n cazul masivelor de structuri, recomandm gruparea membrilor astfel nct s eliminm pe ct posibil apariia unor astfel de goluri. n principiu, fiecare tip de date are anumite cerine de aliniere: short la multiplu de 2; long, float, double, long double la multiplu de 4. Dimensiunea unei structuri este rotunjit la un multiplu ntreg corespunztor acestor cerine.
____________________________________________
93
94
} T_list, *P_list;
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 de mai sus definete un tip structurat cu numele T_list, sinonim cu tipul struct S_list. Tipul P_list este sinonim cu T_list * i cu struct S_list *. Masivul de pointeri care indic nceputurile lanului de blocuri ce descriu simboluri de acelai cod hash este: #define NHASH 0x100 static T_list *hasht[NHASH]; Algoritmul de hashing pe care-l prezentm nu este cel mai bun posibil, dar are meritul de a fi extrem de simplu:
int hashf(char *s) { /* formeaz valoarea hash pentru irul s */ int hv; for (hv=0; *s;) hv += *s++; return hv % NHASH; }
Acesta produce un indice n masivul de pointeri hasht. n procesul de cutare a unui simbol, dac acesta exist trebuie s se afle n lanul de blocuri care ncepe la adresa coninut de elementul din hasht cu indicele respectiv. Cutarea n tabela de simboluri hasht se realizeaz cu funcia Lookup. Dac simbolul cutat este prezent undeva n lan, funcia returneaz un pointer la el, altfel returneaz NULL.
T_list *Lookup(char *s) { /* caut irul s n hasht */ T_list *np; for (np=hasht[hashf(s)]; np; np=np->nxt) if (strcmp(s,np->num)==0) return np; /* s-a gsit s */ return NULL; /* nu s-a gsit s */ }
Dac dorim s eliminm un nod din list, avem nevoie s tim unde este memorat adresa acestuia. Dac nodul nedorit este primul din list, adresa acestuia este memorat ntr-un element din masivul hast, altfel aceast adres este memorat n membrul nxt al nodului precedent din list. Variabila pointer pp va reine unde se afl memorat adresa care ne intereseaz. n parcurgerea listei, valoarea variabilei pp este tot timpul cu un pas n urma lui np, care indic nodul curent.
P_list *pp; T_list *Lookup(char *s) { /* caut irul s n hasht */ 95
____________________________________________
T_list *np; pp = hasht + hashf(s); while (np=*pp) { if (strcmp(s,np->num)==0) return np; pp = &np->nxt; } return pp = NULL; }
Fiecare nod din list are tipul T_list i conine, printre altele, i adresa urmtorului nod. Dar adresa primului nod e memorat ntr-un element care are alt tip dect T_list. De aceea avem nevoie s memorm adresa zonei unde este memorat adresa care ne intereseaz, de unde necesitatea definiiei globale
P_list *pp;
Dac i elementele masivului ar avea tipul T_list, nu am avea nevoie de dou nivele de indirectare, ar fi suficient s memorm adresa nodului precedent ntr-o variabil pointer la T_list. 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 nou.
T_list *Install(char *num, char *def) { /* scrie (num, def) n htab */ T_list *np; int hv; np = Lookup(num); if (np==NULL) { /* nu s-a gsit */ np = (T_list*)calloc(1,sizeof(*np)); if (np==NULL) return NULL; /* nu exist spaiu */ np->num = strdup(num); if (np==NULL) return NULL; hv = hashf(np->num); np->nxt = hasht[hv]; hasht[hv] = np; } else /* nodul exist deja */ free(np->def); /* elibereaz definiia veche */ np->def = strdup(def);
____________________________________________
96
Cum eliberm toate zonele ocupate de o list? Trebuie s fim foarte ateni deoarece, nainte de a elibera memoria ocupat de nodul curent, trebuie eliberat memoria ocupat de nodul care urmeaz celui curent. Prezentm mai nti varianta recursiv:
void FreeList(T_list *np) { if (np) { FreeList(np->nxt); free(np); } }
Definiia recursiv a funciei FreeList consum destul de mult memorie din stiv, de aceea redm n continuare i varianta nerecursiv, mai eficient.
void FreeList(T_list *np) { T_list *nx; while (np) { nx = np->nxt; free(np); np = nx; } }
Funcia main, care utilizeaz aceste rutine, nu are nevoie s iniializeze elementele masivului hasht cu NULL, deoarece acesta este declarat global i este automat iniializat corespunztor. De la intrarea standard se ateapt introducerea unor comenzi: + nume valoare nume ? nume Comanda + insereaz n list simbolul nume cu valoarea dat. Dac numele introdus nu exist n listele hasht atunci se afieaz un mesaj corespunztor, altfel se afieaz vechea valoare care este apoi nlocuit de noua valoare.
____________________________________________
97
Comanda ? afieaz valoarea simbolului nume, dac acesta exist n listele hasht. Comanda elimin simbolul nume, dac acesta exist n liste. Dup epuizarea datelor introduse memoria ocupat de cele NHASH liste este eliberat.
int main() { char num[36],def[36],op; T_list *np; int i; while (scanf(" %c",&op)!=EOF) switch (op) { case '+': scanf("%s %s",num,def); np = Lookup(num); if (!np) puts("New name\n"); else printf("Old value: %s\n",np->def); Install(num,def); break; case '-': case '?': scanf("%s",num); np = Lookup(num); if (!np) puts("Not in list"); else switch (op) { case '-': Remove(); puts("Removed"); break; case '?': printf("Value: %s\n",np->def); break; } } for (i=0; i<NHASH; i++) FreeList(hasht[i]); return 0; }
98
folosind algoritmul prezentat la sfritul capitolului precedent, altfel contorizarea ar fi fost foarte simpl pentru o list deja ordonat. Nu putem utiliza nici liste nlnuite pentru a vedea dac un cuvnt citit exist sau nu n liste, pentru c timpul de execuie al programului 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: | car | | bac | | cer |
/ / \ _____
| lac |
Fiecare nod al arborelui va reprezenta un cuvnt distinct din intrare i va conine urmtoarea informaie: un pointer la un ir de caractere; un contor pentru numrul de apariii; un pointer la descendentul stng al nodului; un pointer la descendentul drept al nodului; 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, sub-arborele stng al su conine numai cuvintele care snt mai mici dect cuvntul din nod, iar sub-arborele drept conine numai cuvintele 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. Prin urmare se impune de la sine ca rutinele de inserare n arbore i de afiare s fie recursive.
____________________________________________
99
Declaraia de mai sus definete un tip structurat cu numele T_node, sinonim cu tipul struct S_node. Tipul P_node este sinonim cu T_node * i cu struct S_node *. Funcia principal main citete cte un cuvnt din textul de la intrarea standard, i l plaseaz n arbore prin rutina TreeInsert.
int main() { /* contorizare apariii cuvinte */ char word[36]; P_node root; root = NULL; while (scanf("%s",word)!=EOF) TreeInsert(&root,word); TreePrint(root); TreeFree(root); }
Funcia TreeInsert 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 TreeInsert. 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 TreeInsert l leag nodul al crui descendent este noul nod, n cmpul l sau r, dup cum noul cuvnt este mai mic sau mai mare fa de cuvntul origine. Tocmai din acest motiv rutina TreeInsert primete adresa variabilei pointer care va indica spre viitorul nod fiu.
void TreeInsert(P_node *p, char *w) { int c; if (*p) { c = strcmp(w,(*p)->w); if (c==0) (*p)->c++; else if (c<0) /* noul cuvnt mai mic, mergem spre stnga */
____________________________________________
100
TreeInsert(&(*p)->l,w); else /* noul cuvnt mai mare, mergem spre dreapta */ TreeInsert(&(*p)->r,w); } else { *p = (P_node)calloc(1,sizeof(T_node)); (*p)->w = strdup(w); (*p)->c = 1; }
Am considerat c este mai comod s solicitm alocare de memorie pentru un nod apelnd funcia calloc:
void calloc(unsigned ne, unsigned sz);
Argumentele acestei funcii snt ne: numrul de elemente pe care le va avea zona, i sz: mrimea n octei a unui element, care se poate obine cu operatorul sizeof. Coninutul zonei de memorie este pus la zero, spre deosebire de funcia malloc care pstreaz neschimbat acest coninut (valori reziduale). La adugarea unui nod nou, funcia calloc iniializeaz pointerii spre cei doi descendeni cu NULL. Rmne s crem o copie a irului dat cu funcia strdup, deja cunoscut, i s iniializm contorul cu 1. Rutina TreePrint afieaz arborele astfel nct pentru fiecare nod se afieaz 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. Funcia TreePrint este una din cele mai tipice rutine recursive.
void TreePrint(P_node p) { if (p) { Treeprint(p->l); printf("%5d %s\n",p->c,p->w); TreePrint(p->r); } }
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 TreeInsert, datorit recursivitii se parcurge acelai drum, de data aceasta de la nodul gsit spre rdcina arborelui, refcndu-se toi pointerii drumului parcurs. O observaie legat de acest exemplu: dac arborele este nebalansat, adic cuvintele nu sosesc n ordine aleatoare din punct de vedere lexicografic, atunci timpul de execuie al programului poate deveni foarte mare. Cazul limit n acest sens este acela n care cuvintele de la intrare snt deja n ordine (cresctoare sau
____________________________________________
101
descresctoare), caz n care programul nostru simuleaz o cutare liniar ntr-un mod destul de costisitor. Rutina TreeFree elibereaz memoria ocupat de nodurile arborelui. Ordinea n care se face eliberarea este urmtoarea: mai nti sub-arborele stng, apoi sub-arborele drept, i n final nodul curent. i aici eliberarea memoriei ocupate de nod trebuie s respecte o ordine: mai nti memoria ocupat de irul de caractere, apoi memoria ocupat de nodul propriu-zis.
void TreeFree(P_node p) { if (p) { TreeFree(p->l); TreeFree(p->r); free(p->w); free(p); } }
9.7. Cmpuri
Un cmp se definete ca fiind o mulime de bii consecutivi dintr-un cuvnt sau ntreg. Concret, 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 n 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 urmtoare apar frecvent i ele seteaz biii 1 i 2 din caracterul sau ntregul flags (n exemplul nostru):
flags | = EXTERNAL | STATIC; flags &= EXTERNAL | STATIC;
____________________________________________
Expresia urmtoare este adevrat cnd cel puin unul din biii 1 sau 2 din flags este unu:
if (flags & (EXTERNAL | STATIC)) ...
Expresia urmtoare este adevrat cnd biii 1 i 2 din flags snt ambii zero:
if (!(flags & (EXTERNAL | STATIC))) ...
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 : (dou puncte) reprezint lungimea cmpului n bii. Cmpurile snt declarate unsigned pentru a sublinia c ele snt cantiti fr semn. Pentru a ne referi la un cmp individual din variabila flags folosim o notaie similar cu notaia folosit pentru membrii structurilor.
flags.is_keyword flags.is_static
Cmpurile se comport ca nite ntregi mici fr semn i pot participa n expresii aritmetice ca orice ali ntregi. Astfel, expresiile anterioare pot fi scrise mai natural sub forma urmtoare:
flags.is_extern = flags.is_static = 1;
pentru testarea lor. Un cmp nu trebuie s depeasc limitele unui cuvnt (16 sau 32 de bii, n funcie de sistemul de calcul). 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
____________________________________________
103
poate fi mai lung dect un cuvnt. Cmpurile se atribuie de la dreapta la stnga, de la poziiile cele mai puin semnificative la cele mai semnificative. Cmpurile nu pot constitui masive, nu au adrese, astfel nct operatorul & nu se poate aplica asupra lor.
9.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. S relum exemplul tabelei de simboluri a unui compilator, presupunnd c se gestioneaz constante de tip int, float sau iruri de caractere. Valoarea unei constante particulare ar putea fi memorat ntr-o variabil de tip corespunztor, dar este mai convenabil, pentru gestiunea tabelei de simboluri, ca valoarea s fie memorat n aceeai zon de memorie, indiferent de tipul ei. 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 pointer-la-reuniune->membru Dac variabila utype este utilizat pentru a ine evidena tipului curent memorat n uval, atunci putem avea urmtorul cod:
switch (utype) { case INT: printf ("%d\n",uval.ival); break; case FLOAT: printf("%f\n",uval.fval); break; case STRING:
____________________________________________
104
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. De exemplu, n masivul de structuri symtab[NSYM] definit de:
struct { char *name; int flags,utype; union { int ival; float fval; char *pval; } uval; } symtab[NSYM];
symtab[i].uval.ival *symtab[i].uval.pval
iar primul caracter al irului indicat de pval prin: Tehnic, 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. Pointerii la reuniuni pot fi folosii n mod similar cu pointerii la structuri. Urmtorul exemplu trebuie studiat cu atenie, deoarece implementarea este dependent de arhitectura sistemului de calcul. De aceea vom prezenta dou declaraii de tip, corespunztoare celor dou moduri de reprezentare a valorilor ntregi: pentru arhitecturi Big Endian:
typedef struct { short an; char luna, zi; } T_struc;
105
} T_struc;
n continuare putem interpreta coninutul variabilelor dc1 i dc2 fie ca structuri fie ca ntregi lungi. O variabil de acest tip se iniializeaz astfel:
dc1.ds.an = . . .; dc1.ds.luna = . . .; dc1.ds.zi = . . .;
____________________________________________
106
107
Prog < intrare-standard >> ieire-standard ali parametri Fiecare din parametri poate lipsi; n lipsa unui specificator de intrare sau ieire standard se folosete terminalul curent. Asocierea unui fiier cu intrarea sau ieirea standard este fcut de sistemul de operare, programul primind doar informaii despre ceilali parametri din linia de comand. Pentru a se putea face o referire la fiiere orice program C trebuie s conin fiierul stdio.h, care se include printr-o linie de forma:
#include <stdio.h>
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.
Descriere Funcia fopen deschide fiierul al crui nume este un ir indicat de num i i asociaz un flux. Argumentul mod 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;
____________________________________________
108
Dup deschidere, n primele patru cazuri indicatorul poziiei n flux este la nceputul fiierului, n ultimele dou la sfritul acestuia. irul mod include de asemenea litera b (deschide un fiier binar) sau t (deschide un fiier text) fie pe ultima poziie fie pe cea din mijloc. Operaiile de citire i scriere pot alterna n cazul fluxurilor read / write n orice ordine. S reinem c standardul ANSI C cere s existe o funcie de poziionare ntre o operaie de intrare i una de ieire, sau ntre o operaie de ieire i una de intrare, cu excepia cazului cnd o operaie de citire detecteaz sfritul de fiier. Aceast operaie poate fi inefectiv cum ar fi fseek(flux, 0L, SEEK_CUR) apelat cu scop de sincronizare. Valori returnate n caz de succes se returneaz un pointer de tip FILE. n caz de eroare se returneaz NULL i variabila global errno indic codul erorii.
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.
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
____________________________________________
109
Funcia returneaz un descriptor de flux n caz de succes, sau NULL dac nu poate fi generat un nume unic de fiier sau dac fiierul nu poate fi deschis. n caz de eroare variabila global errno indic codul erorii.
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.
Descriere Funcia fseek seteaz indicatorul de poziie pentru fiierul asociat fluxului flux. Noua poziie, dat n octei, se obine adunnd ofs octei (offset) la poziia specificat de reper. Dac reper este SEEK_SET, SEEK_CUR, sau SEEK_END, ofs 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. Funcia rewind poziioneaz indicatorul de poziie pentru fiierul asociat fluxului flux la nceputul fiierului. Este echivalent cu:
(void)fseek(flux, 0L, SEEK_SET)
____________________________________________
110
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.
Descriere Funcia fileno examineaz argumentul flux i returneaz descriptorul asociat de sistemul de operare acestui flux.
Descriere Funcia fgets citete cel mult size-1 caractere din flux i le memoreaz n zona indicat de s. Citirea se oprete la detectarea sfritului de fiier sau newline. 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.
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.
____________________________________________
111
Valori returnate Funcia returneaz o valoare non-negativ n caz de succes, sau EOF n caz de eroare.
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 ntlnete sfritul de fiier, valoarea returnat este mai mic dect nel (posibil zero).
Descriere Funciile din familia ...scanf scaneaz intrarea n concordan cu irul de caractere fmt 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
____________________________________________
112
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). 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 d, i, o, u, x sau n i argumentul asociat este un pointer la short (n loc de int). Conversia este de tip d, i, o, u, x sau n i argumentul asociat este un pointer la long (n loc de int), sau conversia este de tip e, f, g i argumentul asociat este un pointer la double (n loc de float). Conversia este de tip e, f, g 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 baza 16 dac ncepe cu 0x sau
____________________________________________
d i
113
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 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
____________________________________________
e,g Echivalent cu f. s
114
unui caracter care nu se afl (sau, dac se precizeaz ^, care se afl) n set sau dac se atinge mrimea maxim specificat. n 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 o 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, i se poziioneaz bitul corespunztor din structura FILE, care poate fi testat.
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 fmt 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
____________________________________________
115
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 ca tip 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. Caractere indicatori Caracterul % este urmat de zero, unul sau mai muli indicatori: 0 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.
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 corespondent, 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 corespondent, care trebuie s fie de tip int.
____________________________________________
116
Dac precizia este dat doar de caracterul . 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. Dac se folosete * pentru lime sau precizie (sau ambele), argumentele se iau n ordine: lime, precizie, valoare de scris. 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 care urmeaz corespunde unui argument de tip: long sau unsigned long, dac este o conversie de tip d, i, o, u, x, X sau n; double, dac este o conversie de tip f. Urmtoarea conversie de tip e, E, f, F, g, 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.
____________________________________________
117
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 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 4 mai mare sau 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, 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
____________________________________________
118
Funciile returneaz numrul de caractere generate (nu se include caracterul terminator null pentru sprintf).
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 newline. 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.
119
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 nonzero 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 terminrii. 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 poziionat indicatorul de sfrit de fiier, deci funcia fgets nc 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 irului 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.
120
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. Cteva erori posibile EACCES Acces interzis ENOTDIR nume nu este un director
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 sa depistat sfritul de director sau dac a aprut o eroare.
121
Descriere Funcia closedir nchide fluxul dir. Valoare returnat Funcia returneaz 0 n caz de succes sau EOF n caz de eroare.
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.
122
Funciile fread i fwrite se folosesc pentru fluxuri deschise n mod binar. Cum se utilizeaz pentru copierea unui fiier binar?
#include <stdio.h> #define LZON 4096 char zon[LZON]; FILE *fi, *fo; unsigned 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;
____________________________________________
123
124
dir = opendir(av[1]); if (!dir) { perror("Eroare open dir"); return 1; } while (ent=readdir(dir)) printf("%s\n",ent->d_name); return 0; }
____________________________________________
125
Descriere Funcia calloc aloc memorie pentru un masiv 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 modific 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 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, aliniat n mod corespunztor pentru orice tip de variabile, sau NULL dac nu exist suficient memorie continu.
____________________________________________
126
Funcia free nu returneaz nimic. Funcia realloc returneaz un pointer la noua zon de memorie alocat, aliniat n mod corespunztor pentru orice tip de variabile. Valoarea acestuia poate fi diferit de ptr, 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.
Descriere Funcia qsort sorteaz un masiv de nel elemente, fiecare de mrime size. Argumentul base indic spre nceputul masivului. Elementele masivului 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 masivul sortat este nedefinit. Funcia bsearch caut ntr-un masiv de nel elemente, fiecare de mrime size, un membru care coincide cu obiectul indicat de key. Argumentul base indic spre nceputul masivului. Coninutul masivului 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 se definete ca n cazul qsort. Primul argument este adresa cheii, al doilea este adresa unui element din masiv. Valoare returnat Funcia bsearch returneaz un pointer la un membru al masivului care coincide cu obiectul indicat de key, sau NULL dac nu se gsete nici un
____________________________________________
127
membru. Dac exist mai multe elemente care coincid cu key, poate fi returnat oricare element cu aceast proprietate.
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 (liter sau cifr). isalpha Verific dac c este alfabetic (liter mare sau mic). isascii Verific dac c este o valoare pe 7 bii din setul de caractere ASCII. iscntrl
____________________________________________
128
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. isprint Verific dac c este un caracter afiabil inclusiv spaiu. ispunct Verific dac c este un caracter diferit de spaiu i non-alfanumeric. 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: 0123456789 abcdefABCDEF 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.
Nume
____________________________________________
129
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.
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.
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.
130
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.
Descriere Funcia strlen calculeaz lungimea irului s, fr a include caracterul terminator null. Valoare returnat Funcia returneaz numrul de caractere din s.
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.
____________________________________________
131
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.
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. Valoare returnat Funcia returneaz un pointer la irul duplicat, sau NULL dac nu exist memorie suficient disponibil.
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.
____________________________________________
132
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.
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.
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.
____________________________________________
133
Descriere Funcia strspn determin lungimea segmentului iniial din s format n ntregime numai cu caractere din acc. Funcia strcspn determin 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.
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:
#include <time.h> srand(time(NULL));
134
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.
int abs(int i); long labs(long i); int atoi(char *s); long atol(char *s); double atof(char *s);
Tot n fiierul <stdlib.h> snt descrise i urmtoarele funcii: valoare absolut valoare absolut conversie din ASCII n ntreg conversie din ASCII n ntreg lung conversie din ASCII n dubl precizie Urmtoarele funcii de conversie necesit o discuie detaliat:
conversie din ASCII n ntreg lung fr semn irul de caractere str este convertit n reprezentare binar, ca i n cazul funciilor atoi, atol, atof. Pentru conversii de tip ntreg se precizeaz i baza de numeraie folosit, care poate fi ntre 2 i 36. Pentru baze mai mari dect zece se folosesc litere mici sau mari. n plus, dac parametrul end indic o adres valid (nu este null), la ieire primete adresa primului caracter invalid din str, care nu respect regulile de scriere a unei valori numerice de tipul ateptat. 2) Funciile din a doua categorie snt declarate n <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)
____________________________________________
135
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 double double double double double double double exp(double x); ex log(double x); ln(x) pow(double x, double y); sinh(double x); sinh(x) cosh(double x); cosh(x) tanh(double x); tgh(x) ldexp(double x, int e); fmod(double x, double y);
xy
x 2e 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.
Funcia time
Data i ora calendaristic se reprezint n format compact printr-o valoare de tip time_t. Majoritatea mediilor de programare definesc acest tip ca un sinonim pentru unsigned long. Aceast valoare se obine prin apelul funciei time. time_t time(time_t *tims);
____________________________________________
136
Funcia returneaz data i ora curent n coordonate universale (UTC), care reprezint numrul de secunde de la nceputul erei Unix (1 ianuarie 1970, ora 0:00:00). Valoarea returnat este memorat la adresa indicat de argumentul tims, dac acesta nu este NULL. Dac argumentul tims este NULL, trebuie s memorm valoarea returnat.
struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; };
/* secunde, ntre 0 i 59 */ /* minute, ntre 0 i 59 */ /* ore, ntre 0 i 23 */ /* ziua din lun, ntre 1 i 31 */ /* luna, ntre 0 i 11 */ /* anul curent minus 1900 */ /* ziua din sptmn, duminica: 0 */ /* ziua din an, ntre 0 i 365 */ /* opiunea daylight saving time */
Observaie. Alegerea numelui tm pentru aceast structur a fost ct se poate de neinspirat. Programatorii trebuie s fie deosebit de ateni ca, atunci cnd folosesc funcii din aceast categorie, s nu foloseasc n nici un caz numele tm pentru definiii proprii. Funcia gmtime efectueaz aceast defalcare. Aceast funcie returneaz adresa unei zone unde se afl data calendaristic defalcat. Informaiile din aceast zon trebuie salvate n cazul n care se efectueaz prelucrri ulterioare. Un apel ulterior al funciei gmtime le va suprascrie, zona fiind alocat static n interiorul acesteia.
137
limitelor permise, acestea snt automat normalizate. De exemplu, ora 25:00:00 este echivalat cu ora 1:00:00 a zilei urmtoare. Funcia corecteaz (dac e nevoie) toate valorile structurii. Dac data calendaristic nu poate fi reprezentat ca o valoare de tip time_t, funcia returneaz valoarea (time_t)(1).
Funcia clock
clock_t clock(void); Funcia clock returneaz o aproximare a timpului pe care procesorul l-a folosit pentru program pn la momentul apelului. Tipul clock_t este definit de majoritatea mediilor de programare ca un sinonim pentru unsigned long. Numrul de secunde de la lansarea programului se obine astfel: (double)clock()/CLOCKS_PER_SECOND Constanta CLOCKS_PER_SECOND este specific mediului de programare folosit. De exemplu, biblioteca GNU C Compiler pentru sistemul de operare Linux definete valoarea 1000000. O consecin a acestui fapt este c pe un sistem de calcul pe 32 de bii funcia clock va returna aceeai valoare la aproximativ fiecare 72 de minute. Observaie. Standardul C nu impune ca aceast cronometrare s nceap de la zero la lansarea programului. De aceea, pentru a garanta un maxim de portabilitate, cronometrarea se face astfel:
main() { double stm; stm = clock(); ... printf("Time:%.3lf\n",(clock()-stm)/ CLOCKS_PER_SECOND); }
Unele sisteme de operare, de exemplu Linux, permit cronometrarea exact a timpului de rulare a unui program (evident, cu limitarea la 72 de minute), incluznd aici i timpul ocupat de sistemul de operare pentru efectuarea unor
____________________________________________
138
operaii cerute de program. Nu este cronometrat timpul ct procesorul este ocupat cu rularea altui program.
Descriere Funcia system execut comanda com prin lansarea interpretorului de comenzi cu acest parametru. Se recomand folosirea cu atenie a acestei funcii sub unele sisteme de operare (de exemplu Linux), dac interpretorul de comenzi permite precizarea mai multor comenzi ntr-o singur linie.
139
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); } 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 Mbr 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 masivul 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[11], ar;
____________________________________________
140
float na, nb, nc, md; } StEl; #define Mbr(P,f) ((StEl *)P)->f int comp(const void *A, const void *B) { float w; int d; if (d=Mbr(A,ar)-Mbr(B,ar)) return d; if (Mbr(A,ar)=='A') { w=Mbr(B,md)-Mbr(A,md); if (w>0) return 1; if (w<0) return -1; } return strcmp(Mbr(A,nm),Mbr(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 %f %f %f",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; 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++)
____________________________________________
141
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): 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]; 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);
____________________________________________
142
____________________________________________
143
____________________________________________
144
Cuprins
1. Generaliti asupra limbajului C . . . . . . . . . . . . . . . . . . . .
1.1. Introducere. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Reprezentarea valorilor numerice . . . . . . . . . . . . . . . . . . . . . . 1.3. Primele programe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
4 5 6
14
14 15 17 18 18
3. Variabile. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1. Clase de memorie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2. Tipuri fundamentale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3. Obiecte i valori-stnga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4. Conversii de tip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5. Masive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6. Iniializri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.7. Calificatorul const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
21 23 26 27 30 30 32
4. Operatori i expresii . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1. Expresii primare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2. Operatori unari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3. Operatori aritmetici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4. Operatori de comparare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5. Operatori logici pe bii. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6. Operatori pentru expresii logice . . . . . . . . . . . . . . . . . . . . . . . 4.7. Operatorul condiional. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.8. Operatori de atribuire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.9. Operatorul virgul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.10. Precedena i ordinea de evaluare . . . . . . . . . . . . . . . . . . . . .
33
33 35 37 38 39 41 42 43 44 44
5. Instruciuni. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1. Instruciunea expresie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2. Instruciunea compus sau blocul . . . . . . . . . . . . . . . . . . . . . . 5.3. Instruciunea if. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.4. Instruciunile while i do . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5. Instruciunea for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.6. Instruciunea switch. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.7. Instruciunile break i continue. . . . . . . . . . . . . . . . . . . . 5.8. Instruciunea return . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.9. Instruciunea vid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
____________________________________________
46
46 46 47 49 49 51 53 55 55
145
6. Funcii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.1. Definiia funciilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2. Apelul funciilor; funcii recursive . . . . . . .. . . . . . . . . . . . . . . 6.3. Revenirea din funcii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4. Argumentele funciei i transmiterea parametrilor . . . . . . . . . 6.5. Exemple de funcii i programe. . . . . . . . . . . . . . . . . . . . . . . .
57
57 59 61 62 63
67
67 68 70 70
8. Pointeri i masive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.1. Pointeri i adrese . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2 Pointeri i argumente de funcii . . . . . . . . . . . . . . . . . . . . . . . . 8.3. Pointeri i masive. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.4. Aritmetica de adrese . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.5. Pointeri la caracter i funcii . . . . . . . . . . . . . . . . . . . . . . . . . . 8.6. Masive multidimensionale . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.7. Masive de pointeri i pointeri la pointeri . . . . . . . . . . . . . . . . 8.8. Iniializarea masivelor i masivelor de pointeri . . . . . . . . . . . 8.9. Masive de pointeri i masive multidimensionale . . . . . . . . . . 8.10. Argumentele unei linii de comand . . . . . . . . . . . . . . . . . . . 8.11. Pointeri la funcii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.1. Elemente de baz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2. typedef. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.3. Masive de structuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.4. Pointeri la structuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5. Structuri auto-referite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.6. Arbori binari de cutare. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.7. Cmpuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.8. Reuniuni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.1. Intrri i ieiri standard; fiiere . . . . . . . . . . . . . . . . . . . . . . . 10.2. Accesul la fiiere; deschidere i nchidere. . . . . . . . . . . . . . . 10.3. Citire i scriere fr format . . . . . . . . . . . . . . . . . . . . . . . . . . 10.4. Citire cu format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.5. Scriere cu format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.6. Tratarea erorilor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.7. Operaii cu directoare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.8. Programe demonstrative . . . . . . . . . . . . . . . . . . . . . . . . . . . .
____________________________________________
76
76 78 79 81 83 84 87 91 94 98 102 106 109 111 115 117 123 127 129 133 135 138 140 143 148 150 152
146
Bibliografie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
____________________________________________
147