Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
AccessAdobe photoshopAlgoritmiAutocadBaze de dateCC sharp
CalculatoareCorel drawDot netExcelFox proFrontpageHardware
HtmlInternetJavaLinuxMatlabMs dosPascal
PhpPower pointRetele calculatoareSqlTutorialsWebdesignWindows
WordXml


FUNCTII SI STRUCTURA PROGRAMULUI

c



+ Font mai mare | - Font mai mic



FUNCTII SI STRUCTURA PROGRAMULUI

Functiile sparg programele cu calcule mari in mai multe programe

mai mici, si permit oamenilor sa construiasca incepind de la ceea



ce au facut altii deja, in loc de a porni totul de la

capat. Functiile potrivite pot ascunde adesea (parti) detalii

ale operatiilor din parti ale programului pe care nu e nevoie sa

le cunoastem, clarificind astfel intregul, si usurind osteneala

de a face modificari.

Limbajul C a fost proiectat pentru a face functiile eficiente

si usor de folosit; programele C constau, in general mai degraba

din numeroase functii mici decit din citeva functii mari. Un

program poate fi rezident intr-unul sau mai multe fisiere sursa in

orice mod convenabil; fisierele sursa pot fi compilate separat si

incarcate impreuna, impreuna cu alte functii compilate anterior ce

se gasesc in biblioteci. Nu vom intra in intimitatea procesului

aici, deoarece detaliile variaza de la un sistem la altul.

Majoritatea programatorilor sint familiarizati cu functiile

'de biblioteca' pentru intrari si iesiri (getchar, putchar) si

calculele numerice (sin, cos, sqrt). In acest capitol vom prezenta

mai multe despre scrierea de noi functii.

1. Notiuni de baza

Pentru a incepe, haideti sa scriem un program care imprima fiecare

linie care ii este introdusa si care contine un 'model' sau un sir

de caractere. (Acesta este un caz special al programului utiliar

UNIX 'grep'.) De exemplu, sa cautam modelul 'the' in urmatoarele

linii:

Now is the time

for all good

men to come to the aid

of their party.

care va produce urmatoarea iesire:

Now is the time

men to come to the aid

if their party.

Structura de baza a programului se imparte in exact trei parti:

while (mai exista o linie)

if (linia contine modelul)

tipareste-o

Cu toate ca se poate pune codul pentru toate acestea in rutina

principala,o modalitate mai buna este aceea de a folosi structura

naturala si de a face din fiecare parte o functie separata. Este

mai usor sa ne ocupam de trei bucati mai mici decit de o bucata

mare, deoarece detaliile nerelevante pot fi inmormintate in fun-

ctii si sansa de a da interactiuni nedorite este minimalizata. Si

bucatile pot fi utile chiar luate apoi separat.

'While (mai exista o linie) ' este getline, o functie pe

care am scris-o in Capitolul 1 iar 'tipareste-o' este printf

cu care deja am lucrat suficient. Aceasta inseamna ca nu trebuie

sa scriem decit o rutina care decide daca linia contine vreo

aparitie a modelului. Putem rezolva problema furind o proiectare

din PL/1: functia index(s,t) returneaza pozitia sau indexul din

sirul s in care incepe sirul t, sau -1, daca s nu-l contine pe

t. Vom folosi 0 in loc de 1 ca pozitie de inceput pentru s,

deoarece tablourile in C incep din pozitia 0. Cind, mai tirziu vom

avea nevoie de o cautare de model mai sofisticata, nu avem decit

sa inlocuim 'index'; restul codului ramine acelasi.

Data aceasta schita, restul programului este fara ascunzi-

suri. Iata acum programul intreg, asa ca puteti vedea cum se

potrivesc bucatile impreuna. Doar ca acum, modelul care trebuie

cautat este literal sir din argumentele lui index, care nu este

cel mai general dintre mecanisme. Ne vom intoarce pe scurt pentru

a discuta cum sa initializam tablourile de caractere si in

Capitolul 5 vom arata cum se face modelul un parametru care este

setat atunci cind programul este lansat in executie. Dam de

asemenea, o noua versiune getline; gasim ca este instructiv sa o

comparati cu cea din Capitolul 1.

#define MAXLINE 1000

main() /* gasiti toate liniile ce contin un model dat */

getline(s, lim) /* citeste linia in s, returneaza lungimea ei */

char s[];

int lim;

index(s, t) /* returneaza indexul lui t in s, -1 in lipsa */

char s[], t[];

return(-1);

}

Fiecare functie are forma

nume (lista de argumente, daca exista)

declaratii de argumente, daca exista

Asa cum am sugerat, anumite parti pot sa lipseasca; functia minima

este:

dummy()

care nu face nimic. (O functie care nu face nimic este utila

uneori ca loc pastrat pentru dezvoltari ulterioare in program).

Numele functiei poate fi deasemenea precedat de un tip daca

functia returneaza altceva decit o valoare intreaga; acesta este

subiectul urmatorului capitol.

Un program este tocmai un set de definitii de functii indi-

viduale. Comunicarea intre functii este (in acest caz) facuta

prin argumente si valori returnate de functii; ea poate fi

facuta deasemenea, prin variabile externe. Functiile pot apare in

orice ordine in fisierul sursa, si programul sursa poate fi spart

in mai multe fisiere, pe cind o functie nu este sparta.

Instructiunea return este mecanismul de returnare a unei

valori din functia apelata in apelant. Orice expresie poate urma

dupa instructiunea return:

return (expresie)

Functia apelanta este libera sa ignore valoarea returnata daca

doreste. Mai mult, nu e necesar sa existe nici o expresie dupa

return; in acest caz, nici o valoare nu este returnata apelantu-

lui. Controlul este deasemenea returnat apelantului fara nici o

valoare atunci cind executia 'se continua dupa sfirsitul' functi-

ei, atingind cea mai din dreapta paranteza. Nu este ilegal ci

probabil un semn de necaz (deranj), daca o functie returneaza o

valoar dintr-un loc si nici o valoare din altul. In orice caz

'valoarea' unei functii care nu returneaza nici una, este sigur un

gunoi. Verificatorul 'lint' cauta si dupa astfel de erori.

Mecanismul prin care se compileaza si se incarca un program

care rezida in mai multe fisiere sursa variaza de la un sistem

la altul . Pe sistemul UNIX, de exemplu, comanda CC, mentionata

in Capitolul 1, face lucrul acesta. Sa presupunem ca cele

trei functii se gasesc in trei fisiere numite main.c, getline.c

si index.c. Atunci comanda:

CC main,c,getline,c,index,c

compileaza cele trei fisiere, plaseaza codul obiect relocabil

rezultat in fisierele main.o, getline.o si index.o si le incarca

pe toate intr-un fisier executabil numit a.out.

Daca exista vreo eroare, sa spunem in main.c, fisierul poate

fi recompilat singur si rezultatul incarcat cu fisierele obiect

anterioare, cu comanda:

CC main.c getline.o index.o

Comanda CC foloseste conventia de notare '.c' spre deosebire de

'.o' pentru a distinge fisierele sursa de fisierul obiect.

Exercitiul 1. Scrieti functia rindex(s, t) care returneaza

pozitia celei mai din dreapta aparitii a lui t in s, si -1 daca

nu e nici una.

2. Functii care returneaza non-intregi

Pina acum, nici unul din programele noastre nu a continut vreo

declaratieasupra tipului unei functii. Aceasta deoarece implicit o

functie este declarata prin aparitia ei intr-o expresie sau in-

structiune, ca in:

while (getline(line, MAXLINE) > 0)

Daca un nume care nu a fost declarat apare intr-o expresie si

este urmat de o paranteza stinga, el este declarat din context

ca fiind un nume de functie. Mai mult, implicit se presupune ca o

functie returneaza un int. Deoarece char se transforma in int

in expresii, nu e nevoie sa declaram functiile care retur-

neaza char. Aceste prezumtii acopera majoritatea cazurilor,

inclusiv toate exemplele noastre de pina acum.

Dar ce se intimpla daca o functie trebuie sa returneze o

valoare de alt tip ? Multe functii numerice, ca sqrt, sin, cos

returneaza double; alte functii specializate returneaza alte

tipuri. Pentru a ilustra modul lor de folosire vom scrie si vom

folosi o functie atof(s) care converteste sirul s in echivalentul

lui in dubla precizie; atof este o extensie a lui atoi , pentru

care am scris in Capitolul 2 si in Capitolul 3; ea minuieste un

semn optional si un punct zecimal, precum si prezenta sau

absenta atit a partii intregi cit si a partii fractionare.(Aceasta

nu este o rutina de conversie de intrari de inalta

calitate; ar lua mult mai mult spatiu decit ne-am propus noi sa

folosim).

In primul rind, atof insasi trebuie sa declare tipul

valoarii pe care ea o returneaza, deoarece el nu este int. Deoare-

ce float este convertit in double in expresii, nu are nici un

rost sa spunem ca atof returneaza un float; putem la fel de bine

sa facem uz de precizie suplimentara, sa declaram ca ea returneaza

double. Numele tipului precede numele functiei, ca in:

double atof(s) /* converteste sirul s in double */

char s[];

return(sign * val / power);

In al doilea rind, si la fel de important, rutina apelanta trebuie

sa specifice ca atof returneaza o valoare non-int. Declaratia

este arata in urmatorul calculator primitiv de birou (adevarat

simplu pentru bilantul de verificare de conturi de carti ?!)

care citeste un numar pe linie, precedat optional de un semn si-l

aduna la toate numerele anterioare, tiparind suma dupa fiecare

intrare.

define MAXLINE 100

main() /* calculator rudimentar de birou */

Declaratia

double sum, atof();

spune ca sum este un double si ca atof este o functie care retur-

neaza o valoare double. Ca mnemonica, ea sugereaza ca sum si

atof() sint amindoua valori flotante in dubla precizie.

In afara faptului cind atof este declarata explicit in

ambele locuri, limbajul C presupune ca ea returneaza un intreg si

raspunsurile primite de dumneavoastra vor fi de neinteles.

Daca atof insasi si apelul ei din main au tipuri inconsistente

in acasi fisier sursa, acest lucru va fi depistat de catre compi-

lator. Dar daca (si asta e mai probabil) atof se compileaza sepa-

rat, nepotrivirea nu va fi detectata si atof va returna un double

pe care main il va trata ca intreg rezultind raspunsuri imprevizi-

bile (lint prinde si aceste erori). Dat atof, putem scrie in

principiu atoi (conversie de sir in intreg) astfel:

atoi(s) /* conversie sir s la intreg */

char s[];

Sa remarcam structura declaratiilor si a instructiunii return.

Valoarea expresiei din:

return (expresie)

este intodeauna convertita in tipul functiei inainte ca rezultatul

sa aiba loc. Deci valoarea lui atof, un double este convertita

automat in int, cind apare intr-o instructiune return, deoarece

functia atoi returneaza un int. (Conversia unei valori flotante

intr-un intreg trunchiaza orice parte fractionara, asa cum am

vazut in Capitolul 2).

Exercitiul 2. Extindeti functia atof astfel incit ea sa

minuiasca si notatia stiintifica de forma 123.45e-6 in care un

numar flotant poate fi urmat de e sau E si optional de un

exponent cu semn.

3. Despre argumentele functiilor

In Capitolul 1 am discutat faptul ca argumentele functiilor sint

trimise prin valoarea, adica functia apelata primeste o copie

temporara si privata a fiecarui argument si nu adresele lor.

Aceasta inseamna ca functia nu poate afecta argumentul original

din functia apelanta. Intr-o functie argument este de fapt o

variabila locala initializata cu valoarea cu care functia a fost

apelata.

Cind un nume de tablou apare ca argument al unei functii

locatia de inceput a tabloului este cea trimisa; elementele nu

sint copiate. Functia poate altera elementele tabloului indexind

cu aceasta valoare. Efectul este ca tablourile sint trimise prin

referinta. In capitolul 5 vom discuta folosirea pointerilor pentru

a permite functiilor sa nu altereze tablourile din functiile

apelante.

Fiindca veni vorba, nu este un mod intrutotul satisfacator

acela de a scrie o functie portabila care acepta un numar

variabil de argumente, deoarece nu exista nici o modalitate

portabila pentru functia apelata sa determine cite argumente i-au

fost trimise actual intr-un apel dat. Astfel, nu puteti scrie o

functie portabila intr-adevar de argumente, asa cum sint functi-

ile MAX scrise in FORTRAN sau PL/1.

Este in general sigur sa ne ocupam cu un numar variabil

de argumente daca functia apelata nu foloseste un argument

care nu a fost furnizat efectiv si daca tipurile sint consistente.

printf, cea mai comuna functie in C cu un numar variabil de

argumente, foloseste informatia din primul sau argument pentru a

determina cite alte elemente sint prezente si care sint

tipurile lor. Ea esueaza urit daca apelantul nu furnizeaza

suficiente argumente sau daca tipurile nu sint cele specificate de

primul argument. Ea este deasemenea neportabila si trebuie modifi-

cata pentru diferite calculatoare.

Reciproc, daca argumentele sint de tip cunoscut, este

posibil sa marcam sfirsitul listei de argumente intr-un mod cores-

punzator. De exemplu cu valoare de argument speciala (adresa0)

care specifica sfirsitul listei de argumente.

Variabile externe

Un program C consta dintr-o multime de obiecte externe, care

sint functii sau variabile. Adjectivul 'extern' este folosit

in primul rind in contrast cu 'intern', care descrie argumen-

tele si variabilele automate definite in interiorul functiilor.

Variabilele externe sint definite in afara oricarei functii si

sint astfel disponibile potential pentru mai multe functii.

Functiile insesi sint intodeauna externe, deoarece limbajul

C nu permite definitii de functii in interiorul altor functii.

Implicit variabilele externe sint deasemenea 'globale', astfel

incit toate referintele la o astfel de variabila printr-un

acelasi nume (chiar si pentru functiile compilate separat) sint

referinte la un acelasi lucru. In acest sens, varaiabilele externe

sint analoage cu COMMON din FORTRAN si cu EXTERNAL din PL/1.

Vom vedea mai incolo cum se pot defini variabile si functii

externe care nu sint global disponibile ci sint vizibile ,in in

schimb, doar intr-un singur fisier sursa.

Deoarece variabilele externe sint global accesibile, ele

ofera o alternativa la argumente de functii si valori returnate

pentru comunicari de date intre functii. Orice functie poate

accede o variabila externa prin referirea numelui ei, daca numele

a fost declarat undeva sau cumva.

Daca un numar mare de variabile trebuie sa fie partajat

folosite de mai multe functii, variabilele externe sint mai

convenabile si mai eficiente decit listele lungi de argumente.

Asa cum am precizat in capitolul 1, aceasta modalitate trebuie,

totusi, utilizata cu grija, deoarece ea poate avea efecte negative

asupra structurii programului si poate conduce la programe cu

multe conexiuni de date intre functii.

Un al doilea motiv pentru folosirea variabilelor externe

priveste initializarea. In particular, tablourile externe pot

fi initializate dar tablourile automate nu pot. Vom trata

initializarea aproape de sfirsitul acestui capitol.

Al treilea motiv pentru folosirea varaiabilelor externe este

domeniul si timpul lor de viata. Variabilele autmate sint interne

unei functii; ele capata viata atunci cind rutina este introdusa

si dispar atunci cind rutina se termina. Variabilele externe, pe

de alta parte, sint permanente. Ele nu vin si pleaca, asa ca ele

retin valorile de la un apel de functie la altul. Deci, daca doua

functii trebuie sa-si partajeze niste date, chiar nefolosite de

alte functii niciodata, este adesea mai convenabil daca datele

partajabile sint pastrate in variabile externe decit trimise via

argumente.

Sa examinam aceasta chestiune mai departe cu un exemplu

mai mare. Problema consta in a scrie un alt program calculator,

mai bun decit cel anterior. Aceasta va permite +,-,*,/ si =

(pentru a tipari rezultatul). Deoarece este intrucitva mai usor

de implementat, calculatorul va folosi notatia poloneza inversa

in locul celei 'infix'. (Notatia poloneza este schema folosita, de

exemplu, de calculatoarele de buzunar Hewlett-Packard) In

notatia poloneza inversa, fiecare operator isi urmeaza operanzii;

o expresie 'infix', de tipul:

(1 - 2) * (4 + 5) =

se introduce astfel:

1 2 - 4 5 + * =

Parantezele nu sint necesare.

Implementarea este aproape simpla. Fiecare operand este depus

intr-o stiva. Cind soseste un operator, numarul de operanzi (doi

pentru operatorii liniari) sint scosi din stiva si li se aplica

operatorul iar rezultatul este depus din nou in stiva. In

exemplul de mai sus, 1 si 2 sint depusi in stiva, apoi sint

inlocuiti de diferenta lor, -1 . Apoi 4 si 5 sint depusi in

stiva, apoi sint inlocuiti de suma lor ,9. Produsul lui -1 cu 9,

ii inlocuieste apoi in stiva. Operatorul = tipareste elementul

din virful stivei fara a-l distruge (se pot face astfel verificari

intermediare).

Operatiile de introducere si extragere din stiva sint

triviale dar, daca se adauga detectia de erori de timp si recupe-

rarea lor, codurile sint suficient de lungi pentru a fi mai

bine sa le punem in functii separate decit sa repetam codul de-a

lungul intregului program. La fel, vom considera o functie separa-

ta pentru aducerea urmatorului operand sau operator de la

intrare. Astfel, structura programului este

while (urmatorul operator sau operand nu este sfirsitul de fisier)

if (numar)

pune-l in stiva

else if (operator)

scoate operanzii din stiva

executa operatia

extrage rezultatul

else

eroare

Decizia principala de proiectare care nu a fost inca

discutata este asupra locului stivei, adica ce rutina o poate

accede direct. O posibilitate este aceea de a o tine in main si

sa trecem stiva si pozitia ei curenta rutinelor care o folosesc

pentru introducere si extragere de date. Dar main nu are nevoie sa

stie despre variabilele care controleaza stiva; ea va trebui sa

gindeasca numai in termeni de introducere si extragere in/din

stiva. Asa ca am decis sa facem stiva si informatiile asociate

ei drept variabile externe accesibile functiilor de introducere si

extractie, dar nu si lui main.

Traducerea acestei schite in cod este destul de simpla.

Programul principal este in primul rind un mare comutator

dupa tipul operatorului sau al operandului; aceasta este probabil

cea mai tipica folosire a lui switch pe care am descris-o in

Capitolul 3.

#define MAXOP 20 /* marime maxima operand, operator */

#define NUMBER '0' /* semnul pentru numar gasit */

#define TOOBIG '9' /* semnal pentru sir prea lung */

main() /* calculator de birou cu sirul Polonez invers */

}

#define MAXVAL 100 /* marimea stivei */

int sp = 0; /* pointerul de stiva */

double val[MAXVAL]; /* stiva */

double push(f) /* depune pe f in stiva */

double f ;

}

double pop() /* extrage elementul din virful stivei */

}

clear() /* curata stiva */

Comanda c curata stiva cu ajutorul functiei clear care este folo-

sita deasemenea si de catre functiile pop si push in caz de

eroare. Ne vom intoarce imediat la getop.

Asa cum am aratat in Capitolul 1, o variabila este externa

daca este definita in afara corpului oricarei functii. Astfel

stiva si pointerul de stiva care trebuiesc partajate de catre

push, pop si clear sint definite in afara acestor trei functii.

Dar main insusi nu refera stiva sau pointerul de stiva ( reprezen-

tarea este ascunsa cu grija). Astfel, codul pentru operatorul

= trebuie sa se foloseasca

push(pop()));

pentru a examina virful stivei fara a-l distruge.

Sa notam deasemenea ca deoarece + si * sint operatori

comutativi, orinea in care se combina operanzii scosi din

stiva este irelevanta, dar pentru operatorii - si / trebuie sa

distingem intre operanzii sting si drept.

Exercitiul 3. Dat scheletul de baza, este usor sa extindem

programul calculator. Adaugati procentul % si operatorul unar -.

Adaugati o comanda de stergere, care sterge elementul din virful

stivei. Adaugati comenzi pentru minuirea de variabile (este

usor in cazul variabilelor formate dintr-o singura litera (26)).

5. Reguli de domeniu

Functiile si variabilele externe care compun un program C nu

trebuie sa fie compilate toate in acelasi timp; textul sursa al

programului poate fi pastrat in mai multe fisiere iar rutinele

compilate anterior pot fi incarcate din biblioteci. Cele doua

intrebari care prezinta interes aici sint:

Cum sint scrise declaratiile astfel incit variabilele sa fie

declarate cum se cuvine in timpul compilarii ?

Cum sint fixate declaratiile astfel incit toate piesele sa fie

conectate cum se cuvine atunci cind programul este incarcat ?

Domeniul unui nume este acea parte de program in care numele

este definit. Pentru o variabila automata declarata la inceputul

unei functii, domeniul este functia in care numele este declarat

si variabilele cu acelasi nume in functii diferite sint fara

legatura unele cu altele. La fel se intimpla si cu argumentele

functiilor .

Domeniul unei variabile externe dureaza din punctul in care ea

este decalrata intr-un fisier sursa pina la sfirsitul acelui

fisier. De exemplu, daca val,sp,push,pop,clear sint definite

intru-un fisier in ordinea de mai sus, adica:

int sp = 0;

double val[MAXVAL];

double push(f)

double pop()

clear()

atunci variabilele val si sp pot fi folosite in push ,pop si

clear pur si simplu numindu-le; nu sint necesare declaratii supli-

mentare . Pe de alta parte, daca o variabila externa trebuie sa

fie referita inainte de a fi definita sau este definita intr-un

alt fisier sursa decit cel in care este folosita, arunci este

necesara o declaratie'extern'.

Este important sa distingem intre declaratia unei variabile

externe si definitia sa. O declaratie anunta proprietatile unei

variabile (tipul marimea, etc); o definitie provoaca in plus o

alocare de memorie. Daca liniile:

int sp;

double val[MAXVAL];

apar in afara oricarei functii, ele definesc variabilele exter-

ne sp si val, provoaca o alocare de memorie pentru ele si

servesc in plus ,ca declaratie pentru restul fisierului sursa. Pe

de alta parte liniile

extern int sp;

extern double val[];

declara pentru restul fisierului sursa ca sp este un int si ca

val este un tablou double (a carei dimensiune este determinata

altundeva ),dar ele nu creaza variabilele si nici nu aloca memorie

pentru ele .

Trebuie sa existe o singura definitie pentru o variabila

externa in toate fisierele care compun programul sursa; alte

fisiere pot contine declaratii extern pentru a o accede. (Poate

exista o declaratie extern si in fisierul ce contine definitia).

Orice initializare a unei variabile externe se face numai in

definitie. Dimensiunile de tablouri trebuie specificate cu defi-

nitia dar sint optionale cu o declaratie externa.

Cu toate ca nu este o organizare adecvata pentru acest pro-

gram ,val si sp pot fi definite si initializate intr-un fisier

iar functiile push, pop si clear definite intr-altul. Aceste

definitii si declaratii ar trebui legate impreuna astfel:

In fisierul 1:

int sp=0; /* pointerul de stiva */

double val[MAXVAL]; /* valoarea stivei */

In fisierul 2:

extern int sp;

extern double val[];

double push(f)

double pop()

clear ()

Deoarece declaratiile extern din fisierul 2 se gasesc in fata si

in afara celor trei functii, ele se aplica tuturora; un set

de declaratii este suficient pentru tot fisierul 2.

Pentru programe mai mari, facilitatea de includere in fisier

'#include' care va fi discutata mai tirziu in acest capitol,

permite unui utilizator sa pastreze o singura copie a declaratii-

lor 'extern' pentru programul dat si sa o insereze in fiecare

fisier sursa care trebuie compilat.

Ne vom intoarce acum la implementarea lui getop, functia

care aduce urmatorul operator sau operand. Lucrarea de baza este

usoara: se sar blancurile, taburile si liniile noi. Daca urmatorul

caracter nu este o cifra sau punctul zecimal, returneaza-l.Astfel,

colecteaza un sir de cifre (care poate include si punctul zecimal)

si returneaza NUMBER, care semnaleaza faptul ca s-a colectat

un numar.

Rutina este complicata substantial de incercarea de a

minui in mod potrivit situatia in care numarul de intrare

este prea lung getop citeste cifrele (probabil si un punct zeci-

mal) atita timp cit le gaseste dar le memoreaza numai pe acelea

care incap. Daca numarul nu a fost prea lung (nu s-a produs

o depasire ) functia returneaza NUMBER si sirul de cifre. Daca

numarul a fost prea lung totusi getop elimina restul liniei de

intrare asa ca utilizatorul poate retipari simplu linia din

punctul de eroare; functia returneaza TOOBIG drept semnal pentru

depasire:

getop(s, lim) /* obtine urmatorul operand sau operator */

char s[];

int lim;

if (i < lim) else

}

Ce sint getch si ungetch ? Se intimpla adesea cazul ca un program

care citeste date de intrare nu poate determina daca a citit

destul pina cind a ajuns sa citeasca prea mult. Un exemplu este

colectarea de caractere ce alcatuiesc un numar: pina cind nu se

intilneste un caracter necifra, numarul nu este complet. Dar

atunci programul a citit un caracter mult mai necesar, caracter

ce nu a fost pregatit pentru aceasta.

Problema ar putea fi rezolvata daca ar fi fost posibil sa 'nu

citim' caracterul nedorit. Apoi, de fiecare data cind programul

citeste un caracter prea mult, el il poate pune inapoi in

intrare, asa ca restul codului se va comporta ca si cind nu a

fost citit niciodata. Din fericire este usor de simulat necitirea

unui caracter, scriind o pereche de functii de cooperare.

getch descopera urmatorul caracter de intrare ce trebuie consi-

derat; ungetch pune caracterul inapoi in intrare, asa ca urmatorul

apel al lui getch il va returna din nou.

Modul in care lucreaza aceste functii impreuna este

simplu. ungetch pune caracterul intr-un buffer partajabil un

tablou de caractere ,getch citeste din buffer pentru a vedea

daca exista vreun caracter si apeleaza pe getchar daca bufferul

este vid. Trebuie deasemenea sa existe o variabila index

care inregistrwaza pozitia caracterului curent din buffer.

Deoarece bufferul si indexul sint partajate de getch si

ungetch si trebuie sa-si retina valorile lor intre apeluri,

ele trebuie sa fie externe ambelor rutine. Deci putem scrie

getch si ungetch precum si variablelelor partajate astfel:

#define BUFSIZE 100

char buf[BUFSIZE]; /* bufferul pentru ungetch */

int bufp = 0 /* urmatoarea pozitie libera din buffer */

getch() /* ia un posibil caracter din buffer */

ungetch(c) /* pune caracterul la loc in intrare */

int c;

Am folosit un tablou pentru buffer si nu un singur caracter deoa-

rece generalitatea programului se va observa mai tirziu.

Exercitiul Scrieti o rutina ungets(s) care va depune

inapoi in intrare un sir intreg de caractere. Cum credeti ca ar

fi mai bine , folosind ungetch sau folosind buf si bufp ?

Exercitiul 5. Presupunem ca in buffer nunva fi niciodata mai

mult de un caracter. Modificati in consecinta getch si ungetch.

Exercitiul 6. Functiile noastre getch si ungetch nu minuiesc

EOF-ul intr-un mod portabil. Decideti ce proprietati ar trebui

sa aibe acestea pentru a minui un EOF apoi implementati-le.

6. Variabile statice

Variabilele statice sint a treia clasa de variabile, pe linga

cele externe si cele automate, pe care le-am intilnit deja.

Variabilele de tip ' static' pot fi atit interne cit si

externe. Variabilele statice sint locale unei functii particulare

la fel ca cele automate dar, spre deosebire de acestea, ele ramin

in existenta (exista) tot timpul si nu apar si dispar de fiecare

data cind functia este activa. Aceasta inseamna ca variabilele

statice interne ofera un mijloc de alocare permanenta si privata

de spatiu intr-o functie. Sirurile de caractere care apar intr-o

fucntie, ca de exemplu argumentele lui printf, s sint statice

interne.

O variabila statica externa este recunoscuta in rstul fisie-

rului sursa in care este declarata, dar nu intr-un alt fisier.

Variabilele externe statice ofera astfel o modalitate de a ascunde

nume ca buf si bufp in combinatia getch-ungetch, care trebuie sa

fie externe ca sa poata fi partajabile si care totusi nu sint

vizibile pentru utiliztorii luigetch si ungetch, asa ca nu exista

nici o posibilitate de conflict. Daca cele doua rutine si cele

doua variabile sint compilate intr-un fisier, cin

static char buf[BUFSIZE]; /* bufer pentru ungetch /

static int bufp = 0; / urmatorea pozitie libera in buf */

getch()

ungetch(c)

atunci nici o alta rutina nu va fi in stare sa acceada buf si

bufp; in fapt, ele nu intra in conflict cu late variabile cu

aceleasi nume din alte fisiere ale aceluiasi program .

Memorarea statica, atit cea interna cit si cea externa se

specifica prefixind declaratia normala cu cuvintul 'static'.

Variabila este externa daca este definita in afara oricarei

functii si este interna daca este definita intr-o functie.

In mod normal, functiile sint obiecte externe; numele

lor sint cunoscute global. Este posibil, totusi, ca o functie sa

fie declarata 'statica '; acest lucru face numele ei sa fie

necunoscut inafara fisierului in care este declarat.

In limbajul C, 'static' conteaza nu numai permanenta dar si

un grad din ceea ce ar putea fi numit 'taina'. Obiectele interne

statice sint cunoscute numai in interiorul unei functii ;obiectele

externe statice (variabile sau functii) sint cunoscute numai in

fisierul sursa in care apar, iar numele lor nu interfereaza cu

variabile sau functii cu acelas si nume care apar in alte fisiere.

Variabilele statice externe si functiile sint o modalitate

de a ascunde obiectele 'date' si orice rutina interna care le

manipuleaza astfel incit orice alta rutina sau data nu poate

intra in conflict cu ele, nici macar din greseala. De exemplu,

getch si ungetch formeaza un 'modul' pentru

introducera si extragerea de caractere; buf si bufp pot fi

statice asa ca sint inaccesibile din afara. In acelasi mod, pish

,pop, clear formeaza un modul pentru lucrul cu stiva; val si sp

pot fi statice externe !

7. Variabile registru

A patra si ultima clasa de stocari este denumita registru. O

declaratie de registru avertizeaza compilatorul ca variabila in

chestiune va fi folosita din greu. Cind este posibil, variabi-

lele registru se plaseaza in registrii calculatorului; cea ce

va genera programe mai scurte si mai rapide.

Declaratia de registru este de forma:

register int x;

register char c;

si asa mai departe; partea 'int' poate fi omisa. Declaratia de

registru poate fi aplicata numai variabilelor automate si parame-

trilor formali ai unei functii. In acest ultim caz, declaratia

este de forma:

f(c,n)

register int c,n;

In practica exista anumite restrictii asupra vriabilelor registru

, reflectind realitatea hardware-ului de suport. Numai citeva

vriabile din fiecare functie pot fi pastrate in registri si numai

anumite tipuri sint permise. Cuvintul 'register' este ignorat

cind apare in exces sau in declaratii nepermise. In plus, nu

este posibila aflarea adresei unei variabile registru (o topica ce

va fi acoperita in capitolul 5). Restrictiile specifice

variaza de la un calculator la altul; de exemplu pentru PDP11,

numai primele trei declaratii de registru sint efective intr-o

functie iar tipurile lor pot fi int,char, sau pointer.

8. Structura de bloc

Limbajul C nu este un limbaj structurat pe bloc in sensul lui PL/1

sau ALGOL, adica functiile nu pot fi definite in alte functii.

Pe de alta parte, variabilele pot fi definite intr-o maniera

'structura de bloc'. Declaratiile de variabile (incluzind

initializarile) pot urma dupa paranteza stinga care introduce

orice instructiune compusa si nu numai dupa cea care incepe o

functie. Variabilele declarate in aceasta maniera acopera variabi-

lele numite identic in blocurile mai din afara si ramin in exis-

tenta pina cind intilnesc o paranteza dreapta. De exemplu

if (n > 0)

domeniul variabilei i este intreaga ramura a lui if; acest i

nu are nici o legatura cu oricare alt i din program. Structura de

bloc se aplica deasemenea variabilelor externe.

Date declaratiile:

int x;

f()

atunci, in cadrul functiei f, occurentele lui x se refera la

variabila interna double, in afara lui f, ele se refera la

externul integer. La fel se intimpla lucrurile si cu numele de

parametri formali :

int z;

f(z)

double z;

In cadrul functiei f, z se refera la parametrul formal, si nu la

z-ul extern.

9. Initializare

Initializarea a fost mentionata in trecere de mai multe ori pina

acum, dar intodeauna in trecere si in legatura cu alte subiecte.

Aceasta sectiune rezuma unele din reguli, dat fiind faptul ca pina

acum am discutat mai multe clase de tipuri de memorari.

In absenta initializarii explicite, variabilele externe si statice

se initializeaza pe zero; variabilele automate si de registru sint

nedefinite (i.e. gunoi, ramasita). Variabilele simple ( nu tablo-

urile sau structurile ) pot fi initializate cind se declara,

punind in continuarea numelui lor semnul egal si o expresie con-

stanta:

int x = 1;

char squote = '';

long day = 60 * 24; /* minute intr-o zi */

Pentru variabilele externe si statice, initializarea se face o

data ,la compilare. Pentru variabilele automate si registru, ini-

tializarea se face de fiecare data cind functia sau blocul se

executa .

Pentru variabilele automate si de registru valoarea de ini-

tializare nu trebuie sa fie o constanta: poate fi de fapt orice

expresie valida implicind valori definite anterior, chiar si de

apeluri de functii. De exemplu initializarile din programul de

cautare binara din capitolul 3 pot fi scrise astfel :

binary (x, v, n)

int x, v[], n;

in loc de:

binary (x, v, n)

int x, v[], n;

In fapt, initializarile de variabile automate sint prescurtari

pentru instructiunile de asignare. Care forma este de preferat

este in ultima instanta o chestiune de gust. In general noi am

preferat asignarile explicite, deoarece initializarile in declara-

tii sint mai greu de vazut.

Tablourile automate nu pot fi initializate. Tablourile ex-

terne si statice pot fi initializate punind dupa declaratie o

lista de valori de initializare inclusa intre paranteze si

separate prin virgule. De exemplu programul de contorizare carac-

tere dat in capitolul 1, care incepea

main() /* contorizeaza cifre, blancuri, altele */

poate fi scris si astfel:

int nwhite = 0;

int nother = 0;

int ndigit[10] = ;

main() /* contorizeaza cifre, blancuri, altele */

Aceste initializari sint de fapt necesare deoarece sint toate

zero dar este o buna practica de programare de a le da explicit.

Daca valorile de initializare specificate sint mai putine decit

marimea specificata, restul valorilor vor fi zero. Daca ele sint

mai multe se provoaca eroare . Este regretabil insa faptul ca nu

putem specifica nicicum repetitia unei valori de initializare si

nici sa initializam un element din mijlocul unui tablou fara a

initializa si toate elementele care-l preced.

Tablourile de caractere sint un caz special de initializare.

In locul notatiei cu paranteze si virgule se poate folosi un sir

de caractere:

char pattern[] = 'the'

Aceasta este o prescurtare pentru forma echivalenta dar mai lunga:

char pattern[] = ;

Cind marimea unui tablou de orice tip este omisa, compilatorul

va calcula lungimea contorizind valorile de intializare. In

acest caz specific marimea tabloului este 4 (trei caractere plus

terminatorul 0)

10 Recursivitate

Functiile din C pot fi folosite recursiv. Aceasta inseamna ca

o functie se poate apela pe insasi, fie direct fie indirect. Un

exemplu traditional este cel relativ la tiparirea unui numar ca

si sir de caractere. Asa cum am mentionat mai inainte, cifrele

sint generate intr-o ordine gresita: cele mai putin semnificative

sint dispuse inaintea celor mai semnificative iar tiparirea lor se

face invers.

Exista doua solutii pentru aceasta problema. Una este

de a memora cifrele intr-un tablou asa cum au fost generate,

apoi sa le tiparim in ordine inversa asa cum am facut in cap 3 cu

itoa. Prima versiune a lui printd foloseste acest model.

printd(n) /* print n in decimal */

int n;

i = 0;

do while ((n /= 10) > 0); /* discard it */

while (--i >= 0)

putchar(s[i]);

}

Alternativa este o solutie recursiva, in care fiecare apelare

a lui printd intii se autoapeleaza pentru a trata cifrele din

fata, apoi tipareste cifra din coada.

printd(n) /* print n in decimal(recursive) */

int n;

if ((i = n/10) != 0)

printd(i)

putchar(n % 10 + '0');

}

Cind o functie se autoapeleaza fiecare invocare genereaza un set

proaspat de variabile automate absolut independent de setul prece-

dent. Astfel in printd(123) primul printd are n=123. Acesta trece

12 celui de-al doilea printd, apoi tipareste 3 cind acesta din

urma revine. In axcelasi fel, urmatorul printd trece 1 la al

treilea apoi tipareste 2.

Recursivitatea nu duce in general la economie de memorie atita

timp cit trebuie mentinuta o stiva cu valorile ce urmeaza a fi

procesate . Codul recursiv este mai compact si adesea mai

usor de scris si inteles. Recursivitatea este convenabila in

special pt structuri de date recursive precum arborii; vom vedea

un exemplu dragut in capitolul 6.

Exercitiul 4-7 Adaptati ideile de la printd pt a scrie o

versiune recursiva a lui itoa; adica de a converti un intreg

intr-un sir printr-o rutina recursiva.

Exercitiul 4-8 Scrieti o versiune recursiva a functiei

reverse(s) care inverseaza sirul s.

11 Preprocesorul C

C admite unele extensii de limbaj cu ajutorul unui simplu

macropreprocesor. Posibilitatile lui #define sint cele mai

obisnuite exemple despre aceste extensii; alta este posibilitatea

de a include continutul altor fisiere in timpul compilarii.

Includerea fisierelor

Pentru a usura manipularea colectii de #define si declaratii

(printre altele) C admite includerea fisierelor. Orice linie de

tipul

#include 'filename'

este inlocuita prin continutul fisierului 'filename'. Adesea o

linie sau doua de aceasta forma apar la inceputul fiecarui

fisier sursa pentru a include declaratiile #define comune si

declaratiile extern pentru variabilele globale. #include-urile pot

fi grupate.

#include este calea preferata pentru a uni declaratiile

impreuna pt un program mai mare. Aceasta garanteaza ca toate

fisierele sursa vor fi alimentate cu aceleasi definitii si decla-

rari de varaibile. Desigur atunci cind un fisier inclus este

schimbat toate fisierele dependente trebuiesc recompilate.

Macro substituirea

O definitie de forma

#define YES 1

apeleaza o macrosubstituire de cea mai simpla forma -inlocuirea

unui nume cu un sir de caractere. Numele din #define au aceasi

forma ca si identificatorii din 'C'; textul de inlocuire este

restul liniei, o definitie lunga se poate continua prin plasarea

unui la sfirsitul liniei de continuat. Domeniul unui nume

definit prin #define este de la locul definirii pina la sfirsi-

tul fisierului sursa. Numele pot fi redefinite si o definire poate

folosi definirii precedente. Substitutiile nu se pun intre

ghilimele, astfel daca YES este un nume definit, nu va avea loc

nici o substituire in printf ('YES').

Deoarece implementarea lui #define nu este o parte a compila-

torului propriuzis, sint foarte putine restrictii asupra a ce

poate fi definit. De exemplu adeptii Algolului pot spune:

#define then

#define begin

si apoi sa scrie:

if (i > 0) then

begin

a = 1;

b = 2

end

Este de asemenea posibil de definit macrouri cu argumente, astfel

ca textul de inlocuire depinde defelul in care macroul este

apelat. De exemplu sa definim un macro numit max astfel:

#define max(A, B) ((A) > (B) ? (A) : (B))

Acum linia

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

va fi inlocuita de linia:

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

Aceasta admite o functie maxima care se expadeaza intr-un cod 'in-

line' si nu intr-o apelare de functie. Atita vreme cit argumen-

tele sint tratat adecvat, acest macro va servi pt orice tip de

date; nu exista diferite tipuri de max pt diferite tipuri de

date, asa cum se intimpla cu functiile.

Desigur, daca examinati expansiunea lui max de mai sus

veti observa citeva capcane. Expresiile sint evaluate de doua

ori; aceasta este rau daca sint impicate efecte colaterale ca

apelari de functii si operatori de incrementare. Masuri de preve-

dere trebuie luate cu parantezele pentru a fi siguri ca ordinea de

evaluare este respectata. (Considerati macro-ul

#define square(x) x * x

cind se apeleaza ca square(z+1).)

Exista chiar probleme lexicale; nu pot exista spatii intre numele

macroului si paranteza stinga care introduce lista de argumente.

Insa fara indoiala, macro-urile sint suficient de valoroase. Un

exemplu practic este biblioteca standard I/O care va fi

deschisa in capitolul 7 ,in care getchar si putchar sint

definite ca mcrouri, pt a evita apelarea unei functii pentru

fiecare caracter procesat .

Alte posibilitati ale macropocesorului sint descrise in

Apendix A.

Exercitiul 9 Definiti un macro swap(x,y) care schimba intre

ele toate cele 2 argmente int.



Politica de confidentialitate | Termeni si conditii de utilizare



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 1113
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved