Documente Academic
Documente Profesional
Documente Cultură
Manual de Programare C
Manual de Programare C
Roger Bacon
__________________________________________________________________________
3
1. Generaliti asupra limbajului C
1.1. Introducere
Limbajul C este un limbaj de programare universal, caracterizat
printr-o exprimare concis, un control modern al fluxului execuiei,
structuri de date, i un bogat set de operatori.
Limbajul C nu este un limbaj de nivel foarte nalt i nu este
specializat pentru un anumit domeniu de aplicaii. Absena
restriciilor i generalitatea sa l fac un limbaj mai convenabil i mai
eficient dect multe alte limbaje mai puternice.
Limbajul C permite scrierea de programe bine structurate,
datorit construciilor sale de control al fluxului: grupri de
instruciuni, luri de decizii (if), cicluri cu testul de terminare
naintea ciclului (while, for) sau dup ciclu (do) i selecia unui
caz dintr-o mulime de cazuri (switch).
Limbajul C permite lucrul cu pointeri i are o aritmetic de
adrese puternic.
Limbajul C nu are operaii care prelucreaz direct obiectele
compuse cum snt irurile de caractere, mulimile, listele sau
masivele, considerate fiecare ca o entitate. Limbajul C nu prezint
faciliti de alocare a memoriei altele dect definiia static sau
disciplina de stiv relativ la variabilele locale ale funciilor. n
sfrit, limbajul C nu are faciliti de intrare-ieire i nici metode
directe de acces la fiiere. Toate aceste mecanisme de nivel nalt snt
realizate prin funcii explicite.
Dei limbajul C este, aadar, un limbaj de nivel relativ sczut, el
este un limbaj agreabil, expresiv i elastic, care se preteaz la o gam
larg de programe. C este un limbaj restrns i se nva relativ uor,
iar subtilitile se rein pe msur ce experiena n programare crete.
__________________________________________________________________________
4
1.2. Primele programe
n aceast seciune snt prezentate i explicate patru programe cu
scopul de a asigura un suport de baz pentru prezentrile din
capitolele urmtoare.
Prin tradiie primul program C este un mic exemplu din lucrarea
devenit clasic The C programming language, de Brian W
Kernigham i Dennis M Ritchie.
#include <stdio.h>
main() {
printf("Hello, world\n");
return 0;
}
__________________________________________________________________________
9
2. Unitile lexicale ale limbajului C
2.1. Identificatori
Un identificator este o succesiune de litere i cifre dintre care
primul caracter este n mod obligatoriu o liter. Se admit i litere
mari i litere mici dar ele se consider caractere distincte. Liniua de
subliniere _ este considerat ca fiind liter. Deci alfabetul peste care
snt definii identificatorii este urmtorul:
A a,...,z,A,...,Z,0,...,9,_
__________________________________________________________________________
10
2.3. Constante
n limbajul C exist urmtoarele tipuri de constante: ntreg
(zecimal, octal, hexazecimal), ntreg lung explicit, flotant, caracter,
simbolic.
Constante ntregi
O constant ntreag const dintr-o succesiune de cifre.
O constant octal este o constant ntreag care ncepe cu 0
(cifra zero), i este format cu cifre de la 0 la 7.
O constant hexazecimal este o constant ntreag precedat de
0x sau 0X (cifra 0 i litera x). Cifrele hexazecimale includ literele de
la A la F i de la a la f cu valori de la 10 la 15.
n orice alt caz, constanta ntreag este o constant zecimal.
Exemplu: constanta zecimal 31 poate fi scris ca 037 n octal i
0x1f sau 0X1F n hexazecimal.
O constant ntreag este generat pe un cuvnt (doi sau patru
octei, dac sistemul de calcul este pe 16 sau 32 de bii).
O constant zecimal a crei valoare depete pe cel mai mare
ntreg cu semn reprezentabil pe un cuvnt scurt (16 bii) se consider
de tip long i este generat pe 4 octei.
O constant octal sau hexazecimal care depete pe cel mai
mare ntreg fr semn reprezentabil pe un cuvnt scurt se consider
de asemenea de tip long.
O constant ntreag devine negativ dac i se aplic operatorul
unar de negativare -.
__________________________________________________________________________
11
O constant ntreag zecimal urmat imediat de litera u sau U
este o constant de tip ntreg fr semn. Litera u sau U poate fi
precedat de litera l sau L.
Exemplu: 123lu.
Constante flotante
O constant flotant const dintr-o parte ntreag, un punct
zecimal, o parte fracionar, litera e sau E i opional, un exponent
care este un ntreg cu semn. Partea ntreag i partea fracionar snt
constituite din cte o succesiune de cifre. ntr-o constant flotant, att
partea ntreag ct i partea fracionar pot lipsi dar nu ambele; de
asemenea poate lipsi punctul zecimal sau litera e i exponentul, dar
nu deodat (i punctul i litera e i exponentul).
Exemplu: 123.456e7 sau 0.12e3
Orice constant flotant se consider a fi n precizie extins.
Constante caracter
O constant caracter const dintr-un singur caracter scris ntre
apostrofuri, de exemplu 'x'. Valoarea unei constante caracter este
valoarea numeric a caracterului, n setul de caractere al
calculatorului. De exemplu n setul de caractere ASCII caracterul
zero sau '0' are valoarea 48 n zecimal, total diferit de valoarea
numeric zero.
Constantele caracter particip la operaiile aritmetice ca i oricare
alte numere. De exemplu, dac variabila c conine valoarea ASCII a
unei cifre, atunci prin instruciunea:
c = c - '0' ;
aceast valoare se transform n valoarea efectiv a cifrei.
Anumite caractere negrafice i caractere grafice ' (apostrof) i \
(backslash) pot fi reprezentate ca i constante caracter cu ajutorul aa
numitor secvene de evitare. Secvenele de evitare ofer de altfel i
un mecanism general pentru reprezentarea caracterelor mai greu de
introdus n calculator i a oricror configuraii de bii. Aceste
secvene de evitare snt:
__________________________________________________________________________
12
\n new-line \r carriage return \\ backslash
\t tab orizontal \f form feed \' apostrof
\b backspace \a semnal sonor \" ghilimele
\ddd configuraie de bii (ddd)
Aceste secvene, dei snt formate din mai multe caractere, ele
reprezint n realitate un singur caracter. Secvena '\ddd' unde ddd
este un ir de 1 pn la 3 cifre octale, genereaz pe un octet valoarea
caracterului dorit sau a configuraiei de bii dorite, date de irul ddd.
Exemplu: secvena '\040' va genera caracterul spaiu.
Un caz special al acestei construcii este secvena '\0' care
indic caracterul NULL, care este caracterul cu valoarea zero. '\0'
este scris deseori n locul lui 0 pentru a sublinia natura de caracter a
unei anumite expresii.
Cnd caracterul care urmeaz dup un backslash nu este unul
dintre cele specificate, backslash-ul este ignorat. Atragem atenia c
toate caracterele setului ASCII snt pozitive, dar o constant caracter
specificat printr-o secven de evitare poate fi i negativ, de
exemplu '\377' are valoarea -1.
Constante simbolice
O constant simbolic este un identificator cu valoare de
constant. Valoarea constantei poate fi orice ir de caractere introdus
prin construcia #define (vezi capitolul 8).
Exemplu: #define MAX 1000
Dup ntlnirea acestei construcii compilatorul va nlocui toate
apariiile constantei simbolice MAX cu valoarea 1000.
Numele constantelor simbolice se scriu de obicei cu litere mari
(fr a fi obligatoriu).
__________________________________________________________________________
13
2.4. iruri
Un ir este o succesiune de caractere scrise ntre ghilimele, de
exemplu "ABCD".
Ghilimelele nu fac parte din ir; ele servesc numai pentru
delimitarea irului. Caracterul " (ghilimele) poate aprea ntr-un ir
dac se utilizeaz secvena de evitare \". n interiorul unui ir pot fi
folosite i alte secvene de evitare pentru constante caracter, de
asemenea poate fi folosit caracterul \ (backslash) la sfritul unui
rnd pentru a da posibilitatea continurii unui ir pe mai multe linii,
situaie n care caracterul \ nsui va fi ignorat.
Pentru irul de caractere se mai folosete denumirea constant ir
sau constant de tip ir.
Cnd un ir apare ntr-un program C, compilatorul creeaz un
masiv de caractere care conine caracterele irului i plaseaz automat
caracterul NULL ('\0') la sfritul irului, astfel ca programele care
opereaz asupra irurilor s poat detecta sfritul acestora. Aceast
reprezentare nseamn c, teoretic, nu exist o limit a lungimii unui
ir, iar programele trebuie s parcurg irul, analizndu-l pentru a-i
determina lungimea. Se admit i iruri de lungime zero.
Tehnic, un ir este un masiv ale crui elemente snt caractere. El
are tipul masiv de caractere i clasa de memorie static (vezi
seciunea 3.1). Un ir este iniializat cu caracterele date (vezi
seciunea 5.4).
La alocare, memoria fizic cerut este cu un octet mai mare dect
numrul de caractere scrise ntre ghilimele, datorit adugrii
automate a caracterului null la sfritul fiecrui ir.
Exemplu. Funcia strlen(s) returneaz lungimea irului de
caractere s, excluznd caracterul terminal null.
int strlen(char s[]) {
/* returneaz lungimea irului */
int i;
i=0;
while (s[i]!='\0')
++i;
__________________________________________________________________________
14
return i;
}
Atragem atenia asupra diferenei dintre o constant caracter i un
ir care conine un singur caracter. "x" nu este acelai lucru cu 'x'.
'x' este un singur caracter, folosit pentru a genera pe un octet
valoarea numeric a literei x, din setul de caractere al calculatorului.
"x" este un ir de caractere, care n calculator se reprezint pe doi
octei, dintre care primul conine un caracter (litera x), iar al doilea
caracterul NULL care indic sfritul de ir.
2.5. Operatori
Limbajul C prezint un numr mare de operatori care pot fi
clasificai dup diverse criterii. Exist operatori unari, binari i
ternari, operatori aritmetici, logici, operatori pe bii etc.
ntr-un capitol separat vom prezenta clasele de operatori care
corespund la diferite nivele de prioritate.
2.6. Separatori
Un separator este un caracter sau un ir de caractere care separ
unitile lexicale ntr-un program scris n C.
Separatorul cel mai frecvent este aa numitul spaiu alb (blanc)
care conine unul sau mai multe spaii, tab-uri, new-line-uri sau
comentarii.
Aceste construcii snt eliminate n faza de analiza lexical a
compilrii.
Dm mai jos lista separatorilor admii n limbajul C.
( ) Parantezele mici ncadreaz lista de argumente ale unei
funcii sau delimiteaz anumite pri n cadrul expresiilor
aritmetice etc
{ } Acoladele ncadreaz instruciunile compuse, care
constituie corpul unor instruciuni sau corpul funciilor
[ ] Parantezele mari ncadreaz dimensiunile de masiv sau
__________________________________________________________________________
15
indicii elementelor de masiv
" " Ghilimelele ncadreaz un ir de caractere
' ' Apostrofurile ncadreaz un singur caracter sau o secven
de evitare
; Punct i virgula termin o instruciune
/* Slash asterisc nceput de comentariu
*/ Asterisc slash sfrit de comentariu
Un comentariu este un ir de caractere care ncepe cu caracterele
/* i se termin cu caracterele */.
Un comentariu poate s apar oriunde ntr-un program, unde
poate aprea un blanc i are rol de separator; el nu influeneaz cu
nimic semnificaia programului, scopul lui fiind doar o documentare
a programului.
Nu se admit comentarii imbricate.
__________________________________________________________________________
16
3. Variabile
Variabile automatice
Variabilele automatice snt variabile locale fiecrui bloc
(seciunea 6.2) sau funcii (capitolul 7). Ele se declar prin
specificatorul de clas de memorie auto sau implicit prin context. O
variabil care apare n corpul unei funcii sau al unui bloc pentru care
nu s-a fcut nici o declaraie de clas de memorie se consider
implicit de clas auto.
O variabil auto este actualizat la fiecare intrare n bloc i se
distruge n momentul cnd controlul a prsit blocul. Ele nu i rein
valorile de la un apel la altul al funciei sau blocului i trebuie
iniializate la fiecare intrare. Dac nu snt iniializate, conin valori
__________________________________________________________________________
17
reziduale. Nici o funcie nu are acces la variabilele auto din alt
funcie. n funcii diferite pot exista variabile locale cu aceleai nume,
fr ca variabilele s aib vreo legtur ntre ele.
Variabile externe
Variabilele externe snt variabile cu caracter global. Ele se
definesc n afara oricrei funcii i pot fi apelate prin nume din
oricare funcie care intr n alctuirea programului.
n declaraia de definiie aceste variabile nu necesit specificarea
nici unei clase de memorie.
La ntlnirea unei definiii de variabil extern compilatorul aloc
i memorie pentru aceast variabil.
ntr-un fiier surs domeniul de definiie i aciune al unei
variabile externe este de la locul de declaraie pn la sfritul
fiierului.
Aceste variabile exist i i pstreaz valorile de-a lungul
execuiei ntregului program.
Pentru ca o funcie s poat utiliza o variabil extern, numele
variabilei trebuie fcut cunoscut funciei printr-o declaraie.
Declaraia poate fi fcut fie explicit prin utilizarea specificatorului
extern, fie implicit prin context.
Dac definiia unei variabile externe apare n fiierul surs
naintea folosirii ei ntr-o funcie particular, atunci nici o declaraie
ulterioar nu este necesar, dar poate fi fcut.
Dac o variabil extern este referit ntr-o funcie nainte ca ea
s fie definit, sau dac este definit ntr-un fiier surs diferit de
fiierul n care este folosit, atunci este obligatorie o declaraie extern
pentru a lega apariiile variabilelor respective.
Dac o variabil extern este definit ntr-un fiier surs diferit de
cel n care ea este referit, atunci o singur declaraie extern dat n
afara oricrei funcii este suficient pentru toate funciile care
urmeaz declaraiei.
Funciile snt considerate n general variabile externe afar de
cazul cnd se specific altfel.
__________________________________________________________________________
18
Variabilele externe se folosesc adeseori n locul listelor de
argumente pentru a comunica date ntre funcii, chiar dac funciile
snt compilate separat.
Variabile statice
Variabilele statice se declar prin specificatorul de clas de
memorie static. Aceste variabile snt la rndul lor de dou feluri:
interne i externe.
Variabilele statice interne snt locale unei funcii i se definesc n
interiorul unei funcii, dar spre deosebire de variabilele auto, ele i
pstreaz valorile tot timpul execuiei programului. Variabilele statice
interne nu snt create i distruse de fiecare dat cnd funcia este
activat sau prsit; ele ofer n cadrul unei funcii o memorie
particular permanent pentru funcia respectiv.
Alte funcii nu au acces la variabilele statice interne proprii unei
funcii.
Ele pot fi declarate i implicit prin context; de exemplu irurile
de caractere care apar n interiorul unei funcii cum ar fi argumentele
funciei printf (vezi capitolul 11) snt variabile statice interne.
Variabilele statice externe se definesc n afara oricrei funcii i
orice funcie are acces la ele. Aceste variabile snt ns globale numai
pentru fiierul surs n care ele au fost definite. Nu snt recunoscute
n alte fiiere.
n concluzie, variabila static este extern dac este definit n
afara oricrei funcii i este static intern dac este definit n
interiorul unei funcii.
n general, funciile snt considerate obiecte externe. Exist ns
i posibilitatea s declarm o funcie de clas static. Aceasta face
ca numele funciei s nu fie recunoscut n afara fiierului n care a
fost declarat.
__________________________________________________________________________
19
Variabile registru
O variabil registru se declar prin specificatorul de clas de
memorie register. Ca i variabilele auto ele snt locale unui
bloc sau funcii i valorile lor se pierd la ieirea din blocul sau funcia
respectiv. Variabilele declarate register indic compilatorului c
variabilele respective vor fi folosite foarte des. Dac este posibil,
variabilele register vor li plasate de ctre compilator n regitrii
rapizi ai calculatorului, ceea ce conduce la programe mai compacte i
mai rapide.
Variabile register pot fi numai variabilele automatice sau
parametrii formali ai unei funcii. Practic exist cteva restricii
asupra variabilelor register care reflect realitatea hardware-ului
de baz. Astfel:
numai cteva variabile din fiecare funcie pot fi pstrate n regitri
(de obicei 2 sau 3); declaraia register este ignorat pentru celelalte
variabile;
numai tipurile de date int, char i pointer snt admise;
nu este posibil referirea la adresa unei variabile register.
Tipul caracter
O variabil de tip caracter se declar prin specificatorul de tip
char. Zona de memorie alocat unei variabile de tip char este de
un octet. Ea este suficient de mare pentru a putea memora orice
caracter al setului de caractere implementate pe calculator.
Dac un caracter din setul de caractere este memorat ntr-o
variabil de tip char, atunci valoarea sa este egal cu codul ntreg al
caracterului respectiv. i alte cantiti pot fi memorate n variabile de
tip char, dar implementarea este dependent de sistemul de calcul.
__________________________________________________________________________
20
Ordinul de mrime al variabilelor caracter este ntre -128 i
127. Caracterele setului ASCII snt toate pozitive, dar o constant
caracter specificat printr-o secven de evitare poate fi i negativ,
de exemplu '\377' are valoarea -1. Acest lucru se ntmpl atunci
cnd aceast constant apare ntr-o expresie, moment n care se
convertete la tipul int prin extensia bitului cel mai din stnga din
octet (datorit modului de funcionare a instruciunilor
calculatorului).
Tipul ntreg
Variabilele ntregi pozitive sau negative pot fi declarate prin
specificatorul de tip int. Zona de memorie alocat unei variabile
ntregi poate fi de cel mult trei dimensiuni.
Relaii despre dimensiune snt furnizate de calificatorii short,
long i unsigned, care pot fi aplicai tipului int.
Calificatorul short se refer totdeauna la numrul minim de
octei pe care poate fi reprezentat un ntreg, n cazul nostru 2.
Calificatorul long se refer la numrul maxim de octei pe care
poate fi reprezentat un ntreg, n cazul nostru 4.
Tipul int are dimensiunea natural sugerat de sistemul de
calcul. Scara numerelor ntregi reprezentabile n main depinde de
asemenea de sistemul de calcul: un ntreg poate lua valori ntre
-32768 i 32767 (sisteme de calcul pe 16 bii) sau ntre
-2147483648 i 2147483647 (sisteme de calcul pe 32 de bii).
Calificatorul unsigned alturi de declaraia de tip int
determin ca valorile variabilelor astfel declarate s fie considerate
ntregi fr semn.
Numerele de tipul unsigned respect legile aritmeticii modulo 2 n,
unde n este numrul de bii din reprezentarea unei variabile de tip
int. Numerele de tipul unsigned snt totdeauna pozitive.
Declaraiile pentru calificatori snt de forma:
short int x;
long int y;
unsigned int z;
__________________________________________________________________________
21
Cuvntul int poate fi omis n aceste situaii.
Tipuri derivate
n afar de tipurile aritmetice fundamentale, exist, n principiu, o
clas infinit de tipuri derivate, construite din tipurile fundamentale
n urmtoarele moduri:
__________________________________________________________________________
22
masive de T pentru masive de obiecte de un tip dat T, unde T este
unul dintre tipurile admise;
funcii care returneaz T pentru funcii care returneaz obiecte de
un tip dat T;
pointer la T pentru pointeri la obiecte de un tip dat T;
structuri pentru un ir de obiecte de tipuri diferite;
reuniuni care pot conine obiecte de tipuri diferite, tratate ntr-o
singur zon de memorie.
n general aceste metode de construire de noi tipuri de obiecte
pot fi aplicate recursiv. Amnunte despre tipurile derivate snt date n
seciunea 5.3.
__________________________________________________________________________
23
Expresii care nu au sens, ca de exemplu un numr flotant ca indice,
nu snt admise.
Caractere i ntregi
Un caracter poate aprea oriunde unde un ntreg este admis. n
toate cazurile valoarea caracterului este convertit automat ntr-un
ntreg. Deci ntr-o expresie aritmetic tipul char i int pot aprea
mpreun. Aceasta permite o flexibilitate considerabil n anumite
tipuri de transformri de caractere. Un astfel de exemplu este funcia
atoi descris n seciunea 7.5 care convertete un ir de cifre n
echivalentul lor numeric.
Expresia:
s[i] - '0'
produce valoarea numeric a caracterului (cifr) memorat n ASCII.
Atragem atenia c atunci cnd o variabil de tip char este
convertit la tipul int, se poate produce un ntreg negativ, dac bitul
cel mai din stnga al octetului conine 1. Caracterele din setul de
caractere ASCII nu devin niciodat negative, dar anumite configuraii
de bii memorate n variabile de tip caracter pot aprea ca negative
prin extensia la tipul int.
Conversia tipului int n char se face cu pierderea biilor de
ordin superior.
ntregii de tip short snt convertii automat la int. Conversia
ntregilor se face cu extensie de semn; ntregii snt totdeauna cantiti
cu semn.
Un ntreg long este convertit la un ntreg short sau char prin
trunchiere la stnga; surplusul de bii de ordin superior se pierde.
Conversii flotante
Toate operaiile aritmetice n virgul mobil se execut n
precizie extins. Conversia de la float la int se face prin
trunchierea prii fracionare. Conversia de la int la float este
acceptat.
__________________________________________________________________________
24
ntregi fr semn
ntr-o expresie n care apar doi operanzi, dintre care unul
unsigned iar cellalt un ntreg de orice alt tip, ntregul cu semn
este convertit n ntreg fr semn i rezultatul este un ntreg fr
semn.
Cnd un int trece n unsigned, valoarea sa este cel mai mic
ntreg fr semn congruent cu ntregul cu semn (modulo 2 16 sau 232).
ntr-o reprezentare la complementul fa de 2 (deci pentru numere
negative), conversia este conceptual, nu exist nici o schimbare
real a configuraiei de bii.
Cnd un ntreg fr semn este convertit la long, valoarea
rezultatului este numeric aceeai ca i a ntregului fr semn, astfel
conversia nu face altceva dect s adauge zerouri la stnga.
Conversii aritmetice
Dac un operator aritmetic binar are doi operanzi de tipuri
diferite, atunci tipul de nivel mai sczut este convertit la tipul de
nivel mai nalt nainte de operaie. Rezultatul este de tipul de nivel
mai nalt. Ierarhia tipurilor este urmtoarea:
char short int long;
float double long double;
tip ntreg cu semn tip ntreg fr semn;
tip ntreg virgul mobil.
Conversii logice
Expresiile relaionale de forma i<j i expresiile logice legate
prin operatorii && i || snt definite ca avnd valoarea 1 dac snt
adevrate i 0 dac snt false.
__________________________________________________________________________
25
Astfel atribuirea:
d = (c>='0') && (c<='9');
l face pe d egal cu 1 dac c este cifr i egal cu 0 n caz contrar.
Conversii explicite
Dac conversiile de pn aici le-am putea considera implicite,
exist i conversii explicite de tipuri pentru orice expresie. Aceste
conversii se fac prin construcia special numit cast de forma:
(nume-tip) expresie
n aceast construcie expresie este convertit la tipul specificat
dup regulile precizate mai sus. Mai precis aceasta este echivalent
cu atribuirea expresiei respective unei variabile de un tip specificat, i
aceast nou variabil este apoi folosit n locul ntregii expresii. De
exemplu, n expresia:
sqrt((double)n)
se convertete n la double nainte de a se transmite funciei sqrt.
Notm ns c, coninutul real al lui n nu este alterat. Operatorul
cast are aceeai preceden ca i oricare operator unar.
Expresia constant
O expresie constant este o expresie care conine numai
constante. Aceste expresii snt evaluate n momentul compilrii i nu
n timpul execuiei; ele pot fi astfel utilizate n orice loc unde sintaxa
cere o constant, ca de exemplu:
#define MAXLINE 1000
char line[MAXLINE+1];
__________________________________________________________________________
26
4. Operatori i expresii
__________________________________________________________________________
27
La fel, un identificator declarat de tip funcie care
returneaz ..., care nu apare pe poziie de apel de funcie este
convertit la pointer la funcie care returneaz ....
O constant este o expresie-primar. Tipul su poate fi int,
long sau double. Constantele caracter snt de tip int, constantele
flotante snt de tip long double.
Un ir este o expresie-primar. Tipul su original este masiv de
caractere, dar urmnd aceleai reguli descrise mai sus pentru
identificatori, acesta este modificat n pointer la caracter i
rezultatul este un pointer la primul caracter al irului. Exist cteva
excepii n anumite iniializri (vezi paragraful 5.4).
O expresie ntre paranteze rotunde este o expresie-primar, al
crei tip i valoare snt identice cu cele ale expresiei din interiorul
parantezelor (expresia din paranteze poate fi i o valoare-stnga).
O expresie-primar urmat de o expresie ntre paranteze ptrate
este o expresie-primar. Sensul intuitiv este de indexare. De obicei
expresia-primar are tipul pointer la ..., expresia-indice are tipul
int, iar rezultatul are tipul .... O expresie de forma E1[E2] este
identic (prin definiie) cu *((E1)+(E2)), unde * este operatorul
de indirectare.
Un apel de funcie este o expresie-primar. Ea const dintr-o
expresie-primar urmat de o pereche de paranteze rotunde, care
conin o list-expresii separate prin virgule. Lista-expresii constituie
argumentele reale ale funciei; aceast list poate fi i vid. Expresia-
primar trebuie s fie de tipul funcie care returneaz ..., iar
rezultatul apelului de funcie va fi de tipul ....
naintea apelului, oricare argument de tip float este convertit la
tipul double, oricare argument de tip char sau short este
convertit la tipul int. Numele de masive snt convertite n pointeri la
nceputul masivului. Nici o alt conversie nu se efectueaz automat.
Dac este necesar pentru ca tipul unui argument actual s
coincid cu cel al argumentului formal, se va folosi un cast (vezi
seciunea 3.4).
Snt permise apeluri recursive la orice funcie.
__________________________________________________________________________
28
O valoare-stnga urmat de un punct i un identificator este o
expresie-primar. Valoarea-stnga denumete o structur sau o
reuniune (vezi capitolul 10) iar identificatorul denumete un membru
din structur sau reuniune. Rezultatul este o valoare-stnga care se
refer la membrul denumit din structur sau reuniune.
O expresie-primar urmat de o sgeat (constituit dintr-o
liniu i semnul urmat de un identificator este o expresie-
primar. Prima expresie trebuie s fie un pointer la o structur sau
reuniune, iar identificatorul trebuie s fie numele unui membru din
structura sau reuniunea respectiv. Rezultatul este o valoare-stnga
care se refer la membrul denumit din structura sau reuniunea ctre
care indic expresia pointer.
Expresia E1E2 este identic din punctul de vedere al
rezultatului cu (*E1). E2
Descriem n continuare operatorii limbajului C mpreun cu
expresiile care se pot constitui cu aceti operatori.
__________________________________________________________________________
29
Operatorul unar * este operatorul de indirectare. Expresia care-l
urmeaz trebuie s fie un pointer, iar rezultatul este o valoare-stnga
care se refer la obiectul ctre care indic expresia. Dac tipul
expresiei este pointer la ... atunci tipul rezultatului este .... Acest
operator trateaz operandul su ca o adres, face acces la ea i i
obine coninutul.
Exemplu: instruciunea y = *px; atribuie lui y coninutul
adresei ctre care indic px.
Operatorul unar & este operatorul de obinere a adresei unui
obiect sau de obinere a unui pointer la obiectul respectiv. Operandul
este o valoare-stnga iar rezultatul este un pointer la obiectul referit
de valoarea-stnga. Dac tipul valorii-stnga este ... atunci tipul
rezultatului este pointer la ....
Exemplu. Fie x o variabil de tip int i px un pointer creat ntr-
un anumit fel (vezi capitolul 9). Atunci prin instruciunea
px = &x;
se atribuie variabilei de tip pointer la int px adresa variabilei x;
putem spune acum c px indic spre x. Secvena:
px = &x; y = *px;
este echivalent cu
y = x;
Operatorul & poate fi aplicat numai la variabile i la elemente de
masiv. Construcii de forma &(x+1) i &3 nu snt admise. De
asemenea nu se admite ca variabila s fie de clas register.
Operatorul unar & ajut la transmiterea argumentelor de tip
adres n funcii.
Operatorul unar - este operatorul de negativare. Operandul su
este o expresie, iar rezultatul este negativarea operandului. n acest
caz snt aplicate conversiile aritmetice obinuite. Negativarea unui
ntreg de tip unsigned se face scznd valoarea sa din 2 n, unde n este
numrul de bii rezervai tipului int.
Operatorul unar ! este operatorul de negare logic. Operandul
su este o expresie, iar rezultatul su este 1 sau 0 dup cum valoarea
operandului este 0 sau diferit de zero. Tipul rezultatului este int.
__________________________________________________________________________
30
Acest operator este aplicabil la orice expresie de tip aritmetic sau la
pointeri.
Operatorul unar ~ (tilda) este operatorul de complementare la
unu. El convertete fiecare bit 1 la 0 i invers. El este un operator
logic pe bii.
Operandul su trebuie s fie de tip ntreg. Se aplic conversiile
aritmetice obinuite.
Operatorul unar ++ este operatorul de incrementare. Operandul
su este o valoare-stnga. Operatorul produce incrementarea
operandului cu 1. Acest operator prezint un aspect deosebit deoarece
el poate fi folosit ca un operator prefix (naintea variabilei: ++n) sau
ca un operator postfix (dup variabil: n++). n ambele cazuri efectul
este incrementarea lui n. Dar expresia ++n incrementeaz pe n
nainte de folosirea valorii sale, n timp ce n++ incrementeaz pe n
dup ce valoarea sa a fost utilizat. Aceasta nseamn c n contextul
n care se urmrete numai incrementarea lui n, oricare construcie
poate fi folosit, dar ntr-un context n care i valoarea lui n este
folosit ++n i n++ furnizeaz dou valori distincte.
Exemplu: dac n este 5, atunci
x = n++ ; atribuie lui x valoarea 5
x = ++n ; atribuie lui x valoarea 6
n ambele cazuri n devine 6.
Rezultatul operaiei nu este o valoare-stnga, dar tipul su este
tipul valorii-stnga.
Operatorul unar -- este operatorul de decrementare. Acest
operator este analog cu operatorul ++ doar c produce decrementarea
cu 1 a operandului.
Operatorul (nume-tip) este operatorul de conversie de tip. Prin
nume-tip nelegem unul dintre tipurile fundamentale admise n C.
Operandul acestui operator este o expresie. Operatorul produce
conversia valorii expresiei la tipul denumit. Aceast construcie se
numete cast.
Operatorul sizeof furnizeaz dimensiunea n octei a
operandului su. Aplicat unui masiv sau structuri, rezultatul este
__________________________________________________________________________
31
numrul total de octei din masiv sau structur. Dimensiunea se
determin n momentul compilrii, din declaraiile obiectelor din
expresie. Semantic, aceast expresie este o constant ntreag care se
poate folosi n orice loc n care se cere o constant. Cea mai frecvent
utilizare o are n comunicarea cu rutinele de alocare a memoriei sau
rutinele I/O sistem.
Operatorul sizeof poate fi aplicat i unui nume-tip ntre
paranteze. n acest caz el furnizeaz dimensiunea n octei a unui
obiect de tipul indicat.
Construcia sizeof(nume-tip) este luat ca o unitate, astfel c
expresia
sizeof(nume-tip)-2
este acelai lucru cu
(sizeof(nume-tip))-2
__________________________________________________________________________
32
Operatorul binar % furnizeaz restul mpririi primei expresii la
cea de a doua. Operanzii nu pot fi de tip float. Restul are totdeauna
semnul dempritului. Totdeauna (a/b)*b+a%b este egal cu a
(dac b este diferit de 0). Snt executate conversiile aritmetice
obinuite.
__________________________________________________________________________
34
4.7. Operatori de egalitate
Expresie-egalitate:
expresie == expresie
expresie != expresie
Operatorii == (egal cu) i != (diferit de) snt analogi cu operatorii
relaionali, dar precedena lor este mai mic. Astfel a<b == c<d
este 1, dac a<b i c<d au aceeai valoare de adevr.
& 0 1
0 0 0
1 0 1
Operatorul & este deseori folosit pentru a masca o anumit
mulime de bii: de exemplu:
c = n & 0177;
pune pe zero toi biii afar de ultimii 7 bii de ordin inferior ai lui n,
fr a afecta coninutul lui n.
^ 0 1
0 0 1
1 1 0
| 0 1
0 0 1
1 1 1
Operatorul | este folosit pentru a poziiona bii; de exemplu:
x = x | MASK;
pune pe 1 toi biii din x care corespund la bii poziionai pe 1 din
MASK. Se efectueaz conversiile aritmetice obinuite.
__________________________________________________________________________
36
I-logic && garanteaz o evaluare de la stnga la dreapta; mai mult, al
doilea operand nu este evaluat dac primul operand este 0.
Operanzii nu trebuie s aib n mod obligatoriu acelai tip, dar
fiecare trebuie s aib unul dintre tipurile fundamentale sau pointer.
Rezultatul este totdeauna de tip int.
__________________________________________________________________________
37
atribuie lui z. Dac prima expresie nu este adevrat atunci z ia
valoarea lui b.
Expresia condiional poate fi folosit peste tot unde sintaxa cere
o expresie.
Dac este posibil, se execut conversiile aritmetice obinuite
pentru a aduce expresia a doua i a treia la un tip comun; dac ambele
expresii snt pointeri de acelai tip, rezultatul are i el acelai tip;
dac numai o expresie este un pointer, cealalt trebuie sa fie
constanta 0, iar rezultatul este de tipul pointerului. ntotdeauna numai
una dintre expresiile a doua i a treia este evaluat.
Dac f este flotant i n ntreg, atunci expresia
(h>0)? f : n
este de tip double indiferent dac n este pozitiv sau negativ.
Parantezele nu snt necesare deoarece precedena operatorului ?:
este mai mic, dar ele pot fi folosite pentru a face expresia
condiional mai vizibil.
__________________________________________________________________________
39
4.16. Precedena i ordinea de evaluare
Tabelul de la sfritul acestei seciuni constituie un rezumat al
regulilor de preceden i asociativitate ale tuturor operatorilor.
Operatorii din aceeai linie au aceeai preceden; liniile snt
scrise n ordinea descresctoare a precedenei, astfel de exemplu
operatorii *, / i % au toi aceeai preceden, care este mai mare
dect aceea a operatorilor + i -.
Dup cum s-a menionat deja, expresiile care conin unul dintre
operatorii asociativi sau comutativi (*, +, &, ^, |) pot fi rearanjate de
compilator chiar dac conin paranteze. n cele mai multe cazuri
aceasta nu produce nici o diferen; n cazurile n care o asemenea
diferen ar putea aprea pot fi utilizate variabile temporare explicite,
pentru a fora ordinea de evaluare.
Limbajul C, ca i multe alte limbaje, nu specific n ce ordine snt
evaluai operanzii unui operator. De exemplu ntr-o instruciune de
forma:
x = f() + g();
f poate fi evaluat nainte sau dup evaluarea lui g; dac f sau g
altereaz o variabil extern de care cealalt depinde, x poate
depinde de ordinea de evaluare. Din nou rezultate intermediare
trebuie memorate n variabile temporare pentru a asigura o secven
particular.
__________________________________________________________________________
40
Operator Asociativitate
() [] -> . stnga la dreapta
! ++ -- - (tip) * & sizeof 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
= op= dreapta la stnga
, stnga la dreapta
__________________________________________________________________________
41
5. Declaraii
__________________________________________________________________________
43
ntr-o declaraie poate s apar cel mult un specificator de clas
de memorie. Dac specificatorul de clas lipsete din declaraie, el se
consider implicit auto n interiorul unei funcii i definiie extern n
afara funciei. Excepie fac funciile care nu snt niciodat
automatice. De exemplu liniile:
int sp;
double val[MAXVAL];
care apar ntr-un program n afara oricrei funcii, definesc
variabilele externe sp de tip int i val de tip masiv de double.
Ele determin alocarea memoriei i servesc de asemenea ca declaraii
ale acestor variabile n tot restul fiierului surs. Pe de alt parte
liniile:
extern int sp;
extern double val[];
declar pentru restul fiierului surs c variabilele sp i val snt
externe, sp este de tip int i val este un masiv de double i c
ele au fost definite n alt parte, unde li s-a alocat i memorie. Deci
aceste declaraii nu creeaz aceste variabile i nici nu le aloc
memorie.
__________________________________________________________________________
44
ntr-o declaraie se admite cel mult un specificator de tip, cu
excepia combinaiilor amintite mai sus. Dac specificatorul de tip
lipsete din declaraie, el se consider implicit int.
Specificatorii de structuri i reuniuni snt prezentai n
seciunea 10.9, iar declaraiile cu typedef n seciunea
10.10.
5.3. Declaratori
Lista-declarator care apare ntr-o declaraie este o succesiune de
declaratori separai prin virgule, fiecare dintre ei putnd avea un
iniializator.
Declaratorii din lista-declarator snt identificatorii care trebuie
declarai.
Fiecare declarator este considerat ca o afirmaie care, atunci cnd
apare o construcie de aceeai form cu declaratorul, produce un
obiect de tipul i de clasa de memorie indicat. Fiecare declarator
conine un singur identificator. Gruparea declaratorilor este la fel ca
i la expresii.
Dac declaratorul este un identificator simplu, atunci el are tipul
indicat de specificatorul din declaraie.
Un declarator ntre paranteze este tot un declarator, dar legtura
declaratorilor compleci poate fi alterat de paranteze.
S considerm acum o declaraie de forma:
T D1
unde T este un specificator de tip (ca de exemplu int) i D1 un
declarator. S presupunem c aceast declaraie face ca identificatorul
s aib tipul ...T unde ... este vid dac D1 este un identificator
simplu (aa cum tipul lui x n int x este int). Dac D1 are forma:
*D
atunci tipul identificatorului pe care-l conine acest declarator este
pointer la T.
Dac D1 are forma:
D()
atunci identificatorul pe care-l conine are tipul funcie care
returneaz T.
__________________________________________________________________________
45
Dac D1 are forma:
D[expresie-constant] sau D[]
atunci identificatorul pe care-l conine are tipul masiv de T.
n primul caz expresia constant este o expresie a crei valoare
este determinabil la compilare i al crei tip este int. Cnd mai
muli identificatori masiv de T snt adiaceni, se creeaz un masiv
multidimensional; expresiile constante care specific marginile
masivelor pot lipsi numai pentru primul membru din secven.
Aceast omisiune este util cnd masivul este extern i definiia real
care aloc memoria este n alt parte (vezi seciunea 5.1). Prima
expresie constant poate lipsi de asemenea cnd declaratorul este
urmat de iniializare. n acest caz dimensiunea este calculat la
compilare din numrul elementelor iniiale furnizate.
Un masiv poate fi construit din obiecte de unul dintre tipurile de
baz, din pointeri, din reuniuni sau structuri, sau din alte masive
(pentru a genera un masiv multidimensional).
Nu toate posibilitile admise de sintaxa de mai sus snt permise.
Restriciile snt urmtoarele: funciile nu pot returna masive,
structuri, reuniuni sau funcii, dei ele pot returna pointeri la astfel de
obiecte; nu exist masive de funcii, dar pot fi masive de pointeri la
funcii. De asemenea, o structur sau reuniune nu poate conine o
funcie, dar ea poate conine un pointer la funcie. De exemplu,
declaraia
int i, *ip, f(), *fip(), (*pfi)();
declar un ntreg i, un pointer ip la un ntreg, o funcie f care
returneaz un ntreg, o funcie fip care returneaz un pointer la un
ntreg, un pointer pfi la o funcie care returneaz un ntreg. Prezint
interes compararea ultimilor doi declaratori.
Construcia *fip() este *(fip()), astfel c declaraia
sugereaz apelul funciei fip i apoi utiliznd indirectarea prin
intermediul pointerului se obine un ntreg.
n declaratorul (*pfi)(), parantezele externe snt necesare
pentru arta c indirectarea printr-un pointer la o funcie furnizeaz o
funcie, care este apoi apelat; ea returneaz un ntreg.
__________________________________________________________________________
46
Declaraiile de variabile pot fi explicite sau implicite prin
context. De exemplu declaraiile:
int a,b,c;
char d, m[100];
specific un tip i o list de variabile. Aici clasa de memorie nu este
declarat explicit, ea se deduce din context. Dac declaraia este
fcut n afara oricrei funcii atunci clasa de memorie este extern;
dac declaraia este fcut n interiorul unei funcii atunci implicit
clasa de memorie este auto.
Variabilele pot fi distribuite n declaraii n orice mod; astfel
listele le mai sus pot fi scrise i sub forma:
int a;
int b;
int c;
char d;
char m[100];
Aceasta ultim form ocup mai mult spaiu dar este mai
convenabil pentru adugarea unui comentariu pentru fiecare
declaraie sau pentru modificri ulterioare.
5.5. Iniializare
Un declarator poate specifica o valoare iniial pentru
identificatorul care se declar. Iniializatorul este precedat de semnul
= i const dintr-o expresie sau o list de valori incluse n acolade.
Iniializator:
expresie
{list-iniializare}
List-iniializare:
expresie
list-iniializare, list-iniializare
{list-iniializare}
Toate expresiile dintr-un iniializator pentru variabile statice sau
externe trebuie s fie expresii constante (vezi seciunea 3.4) sau
expresii care se reduc la adresa unei variabile declarate anterior,
posibil offset-ul unei expresii constante. Variabilele de clas auto sau
register pot fi iniializate cu expresii oarecare, nu neaprat expresii
constante, care implic constante sau variabile declarate anterior sau
chiar funcii.
n absena iniializrii explicite, variabilele statice i externe snt
iniializate implicit cu valoarea 0. Variabilele auto i register au valori
iniiale nedefinite (reziduale).
Pentru variabilele statice i externe, iniializarea se face o singur
dat, n principiu nainte ca programul s nceap s se execute.
Pentru variabilele auto i register, iniializarea este fcut
la fiecare intrare n funcie sau bloc.
Dac un iniializator se aplic unui scalar (un pointer sau un
obiect de tip aritmetic) el const dintr-o singur expresie, eventual n
__________________________________________________________________________
48
acolade. Valoarea iniial a obiectului este luat din expresie; se
efectueaz aceleai operaii ca n cazul atribuirii.
Pentru iniializarea masivelor i masivelor de pointeri vezi
seciunea 9.8. Pentru iniializarea structurilor vezi seciunea 10.3.
Dac masivul sau structura conine sub-masive sau sub-structuri
regula de iniializare se aplic recursiv la membrii masivului sau
structuri.
5.6. Nume-tip
n cele expuse mai sus furnizarea unui nume-tip a fost necesar n
dou contexte:
pentru a specifica conversii explicite de tip prin intermediul unui
cast (vezi seciunea 3.4);
ca argument al lui sizeof (vezi seciunea 4.2).
Un nume-tip este n esen o declaraie pentru un obiect de acest
tip, dar care omite numele obiectului.
Nume-tip:
specificator-tip declarator-abstract
Declarator-abstract:
vid
(declarator-abstract)
*declarator-abstract
declarator-abstract()
declarator-abstract[expresie-constantopt]
Pentru a evita ambiguitatea, n construcia:
(declarator-abstract)
declaratorul abstract se presupune a nu fi vid. Cu aceast restricie,
este posibil s identificm n mod unic locul ntr-un declarator-
abstract, unde ar putea aprea un identificator, dac aceast
construcie a fost un declarator ntr-o declaraie. Atunci tipul denumit
este acelai ca i tipul identificatorului ipotetic. De exemplu:
int
int*
__________________________________________________________________________
49
int *[3]
int(*)[3]
int *( )
int(*)()
denumete respectiv tipurile int, pointer la ntreg, masiv de 3
pointeri la ntregi, pointer la un masiv de 3 ntregi, funcie care
returneaz pointer la ntreg i pointer la o funcie care returneaz
ntreg.
__________________________________________________________________________
50
6. Instruciuni
__________________________________________________________________________
51
declaraie list-declaratori
List-instruciuni:
instruciune
instruciune list-instruciuni
Dac anumii identificatori din lista-declaratori au fost declarai
anterior, atunci declaraia exterioar este salvat pe durata blocului,
dup care i reia sensul su.
Orice iniializare pentru variabile auto i register se
efectueaz la fiecare intrare n bloc. Iniializrile pentru variabilele
static se execut numai o singur dat cnd programul ncepe s
se execute.
Un bloc se termin cu o acolad dreapt care nu este urmat
niciodat de punct i virgul.
__________________________________________________________________________
52
Deoarece partea else a unei instruciuni if este opional,
exist o ambiguitate cnd un else este omis dintr-o secven de if
imbricat. Aceasta se rezolv asociind else cu ultimul if care nu
are else.
Exemplu:
if (n>0)
if (a>b)
z = a;
else
z = b;
Partea else aparine if-ului din interior. Dac nu dorim acest
lucru atunci folosim acoladele pentru a fora asocierea:
if (n>0) {
if (a>b)
z = a;
}
else
z = b;
Instruciunea condiional admite i construcia else-if de forma:
if (expresie-1)
instruciune-1
else if (expresie-2)
instruciune-2
else if (expresie-3)
instruciune-3
else
instruciune-4
Aceast secven de if se folosete frecvent n programe, ca
mod de a exprima o decizie multipl.
Expresiile se evalueaz n ordinea n care apar; dac se ntlnete
o expresie adevrat, atunci se execut instruciunea asociat cu ea i
astfel se termin ntregul lan.
__________________________________________________________________________
53
Oricare instruciune poate fi o instruciune simpl sau un grup de
instruciuni ntre acolade.
` Instruciunea dup ultimul else se execut n cazul n care nici
o expresie nu a fost adevrat.
Dac n acest caz nu exist nici o aciune explicit de fcut,
atunci partea
else instruciune-4
poate s lipseasc.
Funcia binary din seciunea 7.5 este un exemplu de decizie
multipl de ordinul 3.
Pot exista un numr arbitrar de construcii:
else if (expresie)
instruciune
grupate ntre un if iniial i un else final.
ntotdeauna un else se leag cu ultimul if ntlnit.
6.5. Instruciunea do
Format:
__________________________________________________________________________
54
do instruciune while
(expresie);
Instruciunea se execut repetat pn cnd valoarea expresiei
devine zero. Testul are loc dup fiecare execuie a instruciunii.
__________________________________________________________________________
55
6.7. Instruciunea switch
Instruciunea switch este o decizie multipl special i
determin transferul controlului unei instruciuni sau unui bloc de
instruciuni dintr-un ir de instruciuni n funcie de valoarea unei
expresii.
Format:
switch (expresie) instruciune
Expresia este supus la conversiile aritmetice obinuite dar
rezultatul evalurii trebuie s fie de tip int.
Fiecare instruciune din corpul instruciunii switch poate fi
etichetat cu una sau mai multe prefixe case astfel:
case expresie-constant:
unde expresie-constant trebuie s fie de tip int.
Poate exista de asemenea cel mult o instruciune etichetat cu
default:
Cnd o instruciune switch se execut, se evalueaz expresia
din paranteze i valoarea ei se compar cu fiecare constant din
fiecare case.
Dac se gsete o constant case egal cu valoarea expresiei,
atunci se execut instruciunea care urmeaz dup case-ul respectiv.
Dac nici o constant case nu este egal cu valoarea expresiei i
dac exist un prefix default, atunci se execut instruciunea de
dup el, altfel nici o instruciune din switch nu se execut.
Prefixele case i default nu altereaz fluxul de control, care
continu printre astfel de prefixe.
Pentru ieirea din switch se folosete instruciunea break
(vezi seciunea 6.8) sau return (vezi seciunea 6.10).
De obicei instruciunea care constituie corpul unui switch este
o instruciune compus. La nceputul acestei instruciuni pot aprea i
declaraii, dar iniializarea variabilelor automatice i registru este
inefectiv.
na = nb = nc = 0;
__________________________________________________________________________
56
while (c=s[i++])
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case 'T':
case '8':
case '9':
nc[c-'0']++;
break;
case ' ':
case '\r':
case '\t':
nb++;
break;
default:
na++;
break;
}
printf("cifre: ");
for (i=0; i<10; i++)
printf(" %d",nc[i]);
printf("\nspatii albe: %d, altele: %d\n",
nb,na);
n acest exemplu se parcurg toate caracterele dintr-un ir, se
numr cifrele, spaiile albe i alte caractere i se afieaz aceste
numere nsoite de comentarii.
Instruciunea while este cea care asigur parcurgerea irului
pn la sfrit. Pentru fiecare caracter se execut corpul instruciunii
while care const dintr-o singur instruciune switch.
Se evalueaz expresia ntreag din paranteze (n cazul nostru
caracterul c) i se compar valoarea sa cu toate constantele-case. n
__________________________________________________________________________
57
momentul cnd avem egalitate se ncepe execuia de la case-ul
respectiv.
Afiarea rezultatelor se face prin intermediul instruciunii
for i a funciei printf (vezi capitolul 11).
__________________________________________________________________________
63
Pentru a evita orice confuzie se recomand ca tipul valorii
returnate de funcie s fie ntotdeauna precizat, iar dac dorim n mod
expres ca funcia s nu returneze o valoare s folosim tipul void.
De exemplu, funcia atof(s) din biblioteca asociat
compilatorului convertete irul s de cifre n valoarea sa n dubl
precizie. Vom declara funcia sub forma:
double atof(char s[]);
sau mpreun cu alte variabile de tip double:
double sum, atof(char s[]);
Funciile nu pot returna masive, structuri, reuniuni sau funcii.
Dac o funcie returneaz o valoare de tip char, nu este nevoie
de nici o declaraie de tip din cauza conversiilor implicite. Totdeauna
tipul char este convertit la int n expresii.
__________________________________________________________________________
66
s[i++]=c;
if (c=='\n') s[i++]=c;
s[i]='\0';
return i;
}
index(char s[], char t[]) {
/* returneaz poziia din irul s unde ncepe irul t, sau 1 */
int i,j,k;
for (i=0; s[i]!='\0'; i++) {
for (j=i, k=0; t[k]!='\0' &&
s[j]==t[k]; j++, k++)
;
if (t[k]=='\0')
return i;
}
return -1;
}
main() {
/* imprim toate liniile care conin cuvntul the */
char line [MAXLINE];
while (getline(line, MAXLINE)>0)
if (index(line,"the")>=0)
printf("%s",line);
}
lower(int c) {
if (c>='A' && c<='Z')
return c + 'a' - 'A';
else
return c;
}
__________________________________________________________________________
68
5. Funcia binary realizeaz cutarea valorii x ntr-un masiv
sortat v, care are n elemente.
binary(int x, int v[], int n) {
/* caut x n v0, v1, ..., vn-1 */
int low, high, mid;
low = 0;
high = n - 1;
while (low<=high) {
mid = (low + high) / 2;
if (x < v[mid])
high = mid - 1;
else if (x > v[mid])
low = mid + 1;
else /* s-a gsit o intrare */
return(mid);
}
return -1;
}
Funcia returneaz poziia lui x (un numr ntre 0 i n1), dac x
apare n v sau 1 altfel.
Exemplul ilustreaz o decizie tripl, dac x este mai mic, mai
mare sau egal cu elementul din mijlocul irului v[mid].
__________________________________________________________________________
69
8. Liniile de control ale compilatorului
__________________________________________________________________________
72
verific dac identificatorul a fost subiectul unei linii de control de
forma #define.
O linie de control de forma:
#ifndef identificator
verific dac identificatorul este nedefinit n preprocesor.
Toate cele trei forme de linii de control precedente pot fi urmate
de un numr arbitrar de linii care, eventual, pot s conin o linie de
control forma:
#else
i apoi de o linie de control de forma:
#endif
Dac condiia supus verificrii este adevrat, atunci orice linie
ntre #else i #endif este ignorat. Dac condiia este fals atunci
toate liniile ntre testul de verificare i un #else sau n lipsa unui
#else pn la #endif snt ignorate.
Toate aceste construcii pot fi imbricate.
__________________________________________________________________________
75
9. Pointeri i masive
__________________________________________________________________________
76
Pointerii pot aprea i n expresii, ca de exemplu n expresia
urmtoare:
y = *px + 1;
unde variabilei y i se atribuie coninutul variabilei x plus 1.
Instruciunea:
d = sqrt((double)*px);
are ca efect convertirea coninutului variabilei x pe care o indic px
n tip double i apoi depunerea rdcinii ptrate a valorii astfel
convertite n variabila d.
Referiri la pointeri pot aprea de asemenea i n partea stng a
atribuirilor. Dac, de exemplu, px indic spre x, atunci:
*px = 0;
atribuie variabilei x valoarea zero, iar:
*px += 1;
incrementeaz coninutul variabilei x cu 1, ca i n expresia:
(*px)++;
n acest ultim exemplu parantezele snt obligatorii deoarece, n
lipsa lor, expresia ar incrementa pe px n loc de coninutul variabilei
pe care o indic (operatorii unari *, ++ au aceeai preceden i snt
evaluai de la dreapta spre stnga).
Testul if (allocp+n<=allocbuf+ALLOCSIZE)
verific dac exist spaiu suficient pentru satisfacerea cererii de
alocare a n caractere. Dac cererea poate fi satisfcut, alloc revine
cu un pointer la zona de n caractere consecutive. Dac nu, alloc
trebuie s semnaleze lipsa de spaiu pe care o face returnnd valoarea
constantei simbolice NULL. Limbajul C garanteaz c nici un pointer
care indic corect o dat nu va conine zero, prin urmare o revenire
cu valoarea zero poate fi folosit pentru semnalarea unui eveniment
anormal (n cazul nostru, lipsa de spaiu). Atribuirea valorii zero unui
pointer este deci un caz special.
Observm de asemenea c variabilele allocbuf i allocp
snt declarate static cu scopul ca ele s fie locale numai fiierului
surs care conine funciile alloc i free.
Exemplul de mai sus demonstreaz cteva din facilitile
aritmeticii de adrese (pointeri). n primul rnd, pointerii pot fi
comparai n anumite situaii. Dac p i q snt pointeri la membri
unui acelai masiv, atunci relaiile <, <=, >, >=, ==, != snt
valide. Relaia p<q, de exemplu, este adevrat dac p indic un
element mai apropiat de nceputul masivului dect elementul indicat
de pointerul q. Comparrile ntre pointeri pot duce ns la rezultate
imprevizibile, dac ei se refer la elemente aparinnd la masive
diferite.
Se observ c pointerii i ntregii pot fi adunai sau sczui.
Construcia de forma:
p+n
nseamn adresa celui de-al n-lea element dup cel indicat de p,
indiferent de tipul elementului pe care l indic p. Compilatorul C
aliniaz valoarea lui n conform dimensiunii elementelor pe care le
__________________________________________________________________________
82
indic p, dimensiunea fiind determinat din declaraia lui p (scara de
aliniere este 1 pentru char, 2 pentru int etc).
Dac p i q indic elemente ale aceluiai masiv, p-q este
numrul elementelor dintre cele pe care le indic p i q. S scriem o
alt versiune a funciei strlen folosind aceast ultim observaie:
strlen(char *s) { /* returneaz lungimea unui ir */
char *p;
p = s;
while (*p != '\0')
p++;
return p-s;
}
n acest exemplu s rmne constant cu adresa de nceput a
irului, n timp ce p avanseaz la urmtorul caracter de fiecare dat.
Diferena p-s dintre adresa ultimului element al irului i adresa
primului element al irului indic numrul de elemente.
n afar de operaiile binare menionate (adunarea sau scderea
pointerilor cu ntregi i scderea sau compararea a doi pointeri),
celelalte operaii cu pointeri snt ilegale. Nu este permis adunarea,
nmulirea, mprirea sau deplasarea pointerilor, dup cum nici
adunarea lor cu constante de tip double sau float.
Snt admise de asemenea incrementrile i decrementrile
precum i alte combinaii ca de exemplu *++p i *--p.
__________________________________________________________________________
83
Cea mai frecvent apariie a unei constante ir este ca argument
la funcii, caz n care accesul la ea se realizeaz prin intermediul unui
pointer.
n exemplul:
printf("Buna dimineata\n");
funcia printf primete de fapt un pointer la masivul de caractere.
n prelucrarea unui ir de caractere snt implicai numai pointeri,
limbajul C neoferind nici un operator care s trateze irul de caractere
ca o unitate de informaie.
Vom prezenta cteva aspecte legate de pointeri i masive
analiznd dou exemple. S considerm pentru nceput funcia
strcpy(s,t) care copiaz irul t peste irul s. O prim versiune
a programului ar fi urmtoarea:
strcpy(char s[], char t[]) {/* copiaz t peste s */
int t;
i = 0;
while ((s[i]=t[i]) != '\0')
i++;
}
O a doua versiune cu ajutorul pointerilor este urmtoarea:
strcpy(char *s, char *t) {
/* versiune cu pointeri */
while ((*s++=*t++) != '\0') ;
}
Aceast versiune cu pointeri modific prin incrementare pe s i t
n partea de test. Valoarea lui *t++ este caracterul indicat de
pointerul t, nainte de incrementare. Notaia postfix ++ asigur c t
va fi modificat dup depunerea coninutului indicat de el, la vechea
poziie a lui s, dup care i s se incrementeaz. Efectul este c se
copiaz caracterele irului t n irul s pn la caracterul terminal
'\0' inclusiv.
Am mai putea face o observaie legat de redundana comparrii
cu caracterul '\0', redundan care rezult din structura
instruciunii while.
__________________________________________________________________________
84
i atunci forma cea mai prescurtat a funciei strcpy(s,t)
este:
strcpy(char *s, char *t) {
while (*s++ = *t++) ;
}
S considerm, ca al doilea exemplu, funcia strcmp(s,t)
care compar caracterele irurilor s i t i returneaz o valoare
negativ, zero sau pozitiv, dup cum irul s este lexicografic mai
mic, egal sau mai mare ca irul t. Valoarea returnat se obine prin
scderea caracterelor primei poziii n care s i t difer.
O prim versiune a funciei strcmp(s,t) este
urmtoarea:
strcmp(char s, char t) {/* compar irurile s i t */
int i;
i = 0;
while (s[i]==t[i])
if (s[i++]=='\0')
return 0;
return s[i]-t[i];
}
Versiunea cu pointeri a aceleiai funcii este:
strcmp(char *s, char *t) {
for (; *s==*t; s++,t++)
if (*s=='\0')
return 0;
return *s-*t;
}
n final prezentm funcia strsav care copiaz un ir dat prin
argumentul ei ntr-o zon obinut printr-un apel la funcia alloc.
Ea returneaz un pointer la irul copiat sau NULL, dac nu mai exist
suficient spaiu pentru memorarea irului.
char *strsav(char *s) { /* copiaz irul s */
__________________________________________________________________________
85
char *p;
p = alloc(strlen(s)+1);
if (p!=NULL)
strcpy(p,s);
return p;
}
__________________________________________________________________________
92
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.
__________________________________________________________________________
93
dac ns iniializatorul nu ncepe cu acolad stng ({), atunci se
iau din lista de iniializatori atia iniializatori ci corespund
numrului de elemente ale masivului, restul iniializatorilor vor
iniializa urmtorul membru al masivului, care are ca parte (sub-
masiv) masivul deja iniializat.
Un masiv de caractere poate fi iniializat cu un ir, caz n care
caracterele succesive ale irului iniializeaz elementele masivului.
Exemple:
1) int x[] = {1,3,5};
Aceast declaraie definete i iniializeaz pe x ca un masiv
unidimensional cu trei elemente, n ciuda faptului c nu s-a specificat
dimensiunea masivului. Prezena iniializatorilor nchii ntre acolade
determin dimensiunea masivului.
2) Declaraia
int y[4][3]={
{1,3,5},
{2,4,6},
{3,5,7},
};
este o iniializare complet nchis ntre acolade. Valorile 1,3,5
iniializeaz prima linie a masivului y[0] i anume pe y[0][0],
y[0][1], y[0][2]. n mod analog urmtoarele dou linii
iniializeaz pe y[1] i y[2]. Deoarece iniializatorii snt mai
putini dect numrul elementelor masivului, linia y[3] se va
iniializa cu zero, respectiv elementele y[3][0], y[3][1], y[3]
[2] vor avea valorile zero.
3) Acelai efect se poate obine din declaraia:
int y[4][3] = {1,3,5,2,4,6,3,5,7};
unde iniializatorul masivului y ncepe cu acolada stng n timp ce
iniializatorul pentru masivul y[0] nu, fapt pentru care primii trei
iniializatori snt folosii pentru iniializarea lui y[0], restul
iniializatorilor fiind folosii pentru iniializarea masivelor y[1] i
respectiv y[2].
__________________________________________________________________________
94
4) Declaraia:
int y[4][3] = {
{1},{2,},{3,},{4}
};
iniializeaz masivul y[0] cu (1,0,0), masivul y[1] cu (2,0,0),
masivul y[2] cu (3,0,0) i masivul y[4] cu (4,0,0).
5) Declaraia:
static char msg[] = "Eroare de sintaxa";
iniializeaz elementele masivului de caractere msg cu caracterele
succesive ale irului dat.
n ceea ce privete iniializarea unui masiv de pointeri s
considerm urmtorul exemplu.
Fie funcia month_name care returneaz un pointer la un ir de
caractere care indic numele unei luni a anului. Funcia dat conine
un masiv de iruri de caractere i returneaz un pointer la un astfel de
ir, cnd ea este apelat.
Codul funciei este urmtorul:
char *month_name(int n) {
/* returneaz numele lunii a n-a */
static char *name[] = {
"luna ilegala", "ianuarie",
"februarie", "martie", "aprilie",
"mai", "iunie", "iulie", "august",
"septembrie", "octombrie", "noiembrie",
"decembrie"
}
return ((n<1) || (n>12)) ? name[0] :
name[n] ;
}
n acest exemplu, name este un masiv de pointeri la caracter, al
crui iniializator este o list de iruri de caractere. Compilatorul
aloc o zon de memorie pentru memorarea acestor iruri i
genereaz cte un pointer la fiecare din ele pe care apoi i introduce n
masivul name. Deci name[i] va conine un pointer la irul de
__________________________________________________________________________
95
caractere avnd indice i al iniializatorului. Dimensiunea masivului
name nu este necesar a fi specificat deoarece compilatorul o
calculeaz numrnd iniializatorii furnizai i o completeaz n
declaraia masivului.
__________________________________________________________________________
96
elemente, altul un masiv de dou elemente i altul de exemplu poate
s nu indice nici un masiv.
Cu toate c problema prezentat n acest paragraf am descris-o n
termenii ntregilor, ea este cel mai frecvent utilizat n memorarea
irurilor de caractere de lungimi diferite (ca n funcia month_name
prezentat mai sus).
__________________________________________________________________________
98
Aceast versiune arat c argumentul funciei printf poate fi o
expresie ca oricare alta, cu toate c acest mod de utilizare nu este
foarte frecvent.
Ca un al doilea exemplu, s reconsiderm programul din
seciunea 7.5, care imprim fiecare linie a unui text care conine un
ir specificat de caractere (schem).
Dorim acum ca aceast schem s poat fi modificat dinamic,
de la execuie la execuie. Pentru aceasta o specificm printr-un
argument n linia de comand.
i atunci programul care caut schema dat de primul argument
al liniei de comand este:
#define MAXLINE 1000
main(int argc, char *argv[ ]) {
/* gsete schema din primul argument */
char line[MAXLINE];
if (argc!=2)
printf("Linia de comanda eronata\n");
else
while (getline(line,MAXLINE)>0)
if (index(line,argv[1])>=0)
printf("%s",line);
}
unde linia de comand este de exemplu: "find limbaj" n care
"find" este numele programului, iar "limbaj" este schema
cutat. Rezultatul va fi imprimarea tuturor liniilor textului de intrare
care conin cuvntul "limbaj".
S elaborm acum modelul de baz, legat de linia de comand i
argumentele ei.
S presupunem c dorim s introducem n linia de comand dou
argumente opionale: unul care s tipreasc toate liniile cu excepia
acelora care conin schema, i al doilea care s precead fiecare linie
tiprit cu numrul ei de linie.
O convenie pentru programele scrise n limbajul C este ca
argumentele dintr-o linie de comand care ncep cu un semn '-' s
introduc un parametru opional. Dac alegem, de exemplu, -x
__________________________________________________________________________
99
pentru a indica cu excepia i -n pentru a cere numrarea
liniilor, atunci comanda:
find -x -n la
avnd intrarea:
la miezul stinselor lumini
s-ajung victorios,
la temelii, la rdcini,
la mduv, la os.
va produce tiprirea liniei a doua, precedat de numrul ei, deoarece
aceast linie nu conine schema "la".
Argumentele opionale snt permise n orice ordine n linia de
comand. Analizarea i prelucrarea argumentelor unei linii de
comand trebuie efectuat n funcia principal main, iniializnd n
mod corespunztor anumite variabile. Celelalte funcii ale
programului nu vor mai ine evidena acestor argumente.
Este mai comod pentru utilizator dac argumentele opionale snt
concatenate, ca n comanda:
find -xn la
Caracterele 'x' respectiv 'n' indic doar absena sau prezena
acestor opiuni (switch) i nu snt tratate din punct de vedere al
valorii lor.
Fie programul care caut schema "la" n liniile de la intrare i
le tiprete pe acelea, care nu conin schema, precedate de numrul
lor de linie. Programul trateaz corect, att prima form a liniei de
comand ct i a doua.
__________________________________________________________________________
101
9.11. Pointeri la funcii
n limbajul C o funcie nu este o variabil, dar putem defini un
pointer la o funcie, care apoi poate fi prelucrat, transmis unor alte
funcii, introdus ntr-un masiv i aa mai departe. Relativ la o funcie
se pot face doar dou operaii: apelul ei i considerarea adresei ei.
Dac numele unei funcii apare ntr-o expresie, fr a fi urmat
imediat de o parantez stng, deci nu pe poziia unui apel la ea,
atunci se genereaz un pointer la aceast funcie. Pentru a transmite o
funcie unei alte funcii, ca argument, se poate proceda n felul
urmtor:
int f();
g(f);
unde funcia f este un argument pentru funcia g. Definiia funciei g
va fi:
g(int(*funcpt) ()) {
(*funcpt)();
}
Funcia f trebuie declarat explicit n rutina apelant (int
f();), deoarece apariia ei n g(f) nu a fost urmat de parantez
stng (. n expresia g(f) f nu apare pe poziia de apel de funcie.
n acest caz, pentru argumentul funciei g se genereaz un pointer la
funcia f. Deci g apeleaz funcia f printr-un pointer la ea.
Declaraiile din funcia g trebuie studiate cu grij.
int (*funcpt)();
spune c funcpt este un pointer la o funcie care returneaz un
ntreg. Primul set de paranteze este necesar, deoarece fr el
int *funcpt();
nseamn c funcpt este o funcie care returneaz un pointer la un
ntreg, ceea ce este cu totul diferit fa de sensul primei expresii.
Folosirea lui funcpt n expresia:
(*funcpt)();
indic faptul c funcpt este un pointer la o funcie, *funcpt este
funcia, iar (*funcpt)() este apelul funciei.
O form echivalent simplificat de apel este urmtoarea:
funcpt();
__________________________________________________________________________
102
Ca un exemplu, s considerm procedura de sortare a liniilor de
la intrare, descris n seciunea 9.7, dar modificat n sensul ca dac
argumentul opional -n apare n linia de comand, atunci liniile se
vor sorta nu lexicografic ci numeric, liniile coninnd grupe de
numere.
O sortare const adesea din trei pri: o comparare care determin
ordinea oricrei perechi de elemente, un schimb care inverseaz
ordinea elementelor implicate i un algoritm de sortare care face
comparrile i inversrile pn cnd elementele snt aduse n ordinea
cerut. Algoritmul de sortare este independent de operaiile de
comparare i inversare, astfel nct transmind diferite funcii de
comparare i inversare funciei de sortare, elementele de intrare se
pot aranja dup diferite criterii.
Compararea lexicografic a dou linii se realizeaz prin funciile
strcmp i swap. Mai avem nevoie de o rutin numcmp care s
compare dou linii pe baza valorilor numerice i care s returneze
aceiai indicatori ca i rutina strcmp.
Declarm aceste trei funcii n funcia principal main, iar
pointerii la aceste funcii i transmitem ca argumente funciei sort,
care la rndul ei va apela aceste funcii prin intermediul pointerilor
respectivi.
Funcia principal main va avea atunci urmtorul cod:
#define LINES 100 /* nr maxim de linii de sortat */
main (int argc, char *argv[]) {
char *lineptr[LINES]; /* pointeri la linii text */
int nlines; /* numr de linii citite */
int strcmp(), numcmp(); /* funcii de comparare */
int swap (); /* funcia de inversare */
int numeric;
numeric = 0; /* 1 dac sort numeric */
if (argc>1 && argv[1][0]=='-' &&
argv[1][1]=='n')
numeric = 1;
if ((nlines=readlines(lineptr,LINES))>=0)
__________________________________________________________________________
103
{
if (numeric)
sort(lineptr,nlines,numcmp,swap);
else
sort(lineptr,nlines,strcmp,swap);
writelines (lineptr,nlines);
}
else
printf
("Nr de linii de intrare prea mare\n");
}
n apelul funciei sort, argumentele strcmp, numcmp i
swap snt adresele funciilor respective. Deoarece ele au fost
declarate funcii care returneaz un ntreg, operatorul & nu este
necesar s precead numele funciilor, compilatorul fiind cel care
gestioneaz transmiterea adreselor funciilor.
Funcia sort care aranjeaz liniile n ordinea cresctoare
se va modifica astfel:
sort(char *v[], int n, int (*comp)(),
int (*exch)()) { /* sorteaz v0, v1, ... , vn1 */
int gap,i,j;
for (gap=n/2; gap>0; gap/=2)
for (i=gap; i<n; i++)
for (j=i-gap; j>=0; j-=gap) {
if (comp(v[j],v[j+gap])<=0)
break;
exch(v+j,v+j+gap);
}
}
S studiem declaraiile din aceast funcie.
int(*comp)(), (*exch)();
indic faptul c comp i exch snt pointeri la funcii care returneaz
un ntreg (primul set de paranteze este necesar).
if (comp(v[j],v[j+gap])<=0)
__________________________________________________________________________
104
nseamn apelul funciei comp (adic strcmp sau numcmp),
deoarece comp este un pointer la funcie, *comp este funcia, iar
comp(v[j],v[j+gap])
este apelul funciei.
exch(v+j,v+j+gap)
este apelul funciei swap, de inversare a dou linii, inversare care
realizeaz interschimbarea adreselor liniilor implicate (vezi seciunea
9.2). Funcia numcmp este urmtoarea:
numcmp(char *s1, char *s2) {
/* compar s1 i s2 numeric */
double atof(),v1,v2;
v1 = atof(s1);
v2 = atof(s2);
if (v1<v2)
return -1;
else
if (v1>v2)
return 1;
else
return 0;
}
Pentru ca programul nostru s fie complet s mai prezentm i
codul funciei swap, care schimb ntre ei pointerii a dou linii.
swap(char *px[], char *py[]) {
char *temp;
temp = *px;
*px = *py;
*py = temp;
}
__________________________________________________________________________
105
10. Structuri i reuniuni
__________________________________________________________________________
106
Elementele sau variabilele menionate ntr-o structur se numesc
membri ai structurii. Un membru al structurii sau o etichet i o
variabil oarecare, nemembru, pot avea acelai nume fr a genera
conflicte, deoarece ele vor fi ntotdeauna deosebite una de alta din
context.
Acolada dreapt care ncheie o list de membri ai unei structuri
poate fi urmat de o list de variabile, la fel ca i n cazul tipurilor de
baz. De exemplu:
struct {. . .} x,y,z;
este din punct de vedere sintactic analog cu:
int x,y,z;
n sensul c fiecare declaraie declar pe x, y i z ca variabile de
tipul numit (structur n primul caz i ntreg n al doilea) i cauzeaz
alocarea de spaiu pentru ele.
O declaraie de structur care nu este urmat de o list de
variabile nu aloc memorie; ea descrie numai un ablon, o form de
structur. Dac structura este marcat sau etichetat, atunci marcajul
ei poate fi folosit mai trziu pentru definirea unor alte variabile de tip
structur, cu acelai ablon ca structura marcat. De exemplu, fiind
dat declaraia:
struct date d;
ea definete variabila d, ca o structur de acelai fel (ablon) ca
structura date.
O structur extern sau static poate fi iniializat, atand dup
definiia ei o list de iniializatori pentru componente, de exemplu:
struct date d = {4,7,1984,185,"iulie"};
Un membru al unei structuri este referit printr-o expresie de
forma:
nume-structur.membru
n care operatorul membru de structur . leag numele membrului
de numele structurii. Ca exemplu fie atribuirea:
leap = (d.year%4==0) && (d.year%100!=0)
|| (d.year%400==0);
sau verificarea numelui lunii:
if (strcmp(d.mon_name,"august")==0) ...
__________________________________________________________________________
107
Structurile pot fi imbricate; o nregistrare de stat de plat, de
exemplu, poate fi de urmtoarea form:
struct person {
char name[NAMESIZE];
char address[ADRSIZE];
long zipcode;
long ss_number;
double salary;
struct date birthdate;
struct date hiredate;
};
Structura person conine dou structuri de ablon date.
Declaraia:
struct person emp;
definete i aloc o structur cu numele emp de acelai ablon ca i
person. Atunci:
emp.birthdate.month
se refer la luna de natere. Operatorul de membru de structur .
este asociativ de la stnga la dreapta.
__________________________________________________________________________
108
Ca un exemplu, s rescriem programul de conversie a datei, care
calculeaz ziua anului, din lun i zi.
day_of_year(struct date *pd) {
/* calculul zilei anului */
int i, day, leap;
day = pd->day;
leap = (pd->year%4==0) &&
(pd->year%100!==0) ||
(pd->year%400==0);
for (i=1; i<pd->month; i++)
day += day_tab[leap][i];
return day;
}
Declaraia:
struct date * pd;
indic faptul c pd este un pointer la o structur de ablonul lui date.
Notaia:
pd->year
indic faptul c se refer membrul "year" al acestei structuri. n
general, dac p este un pointer la o structur p->membru-structur
se refer la un membru particular (operatorul -> se formeaz din
semnul minus urmat de semnul mai mare).
Deoarece pd este pointer la o structur, membrul year poate fi de
asemenea referit prin:
(*pd).year
Notaia "->" se impune ca un mod convenabil de prescurtare. n
notaia (*pd).year, parantezele snt necesare deoarece precedena
operatorului membru de structur . este mai mare dect cea a
operatorului *.
Ambii operatori . i -> snt asociativi de la stnga la dreapta,
astfel nct:
p->q->membru
emp.birthdate.month
snt de fapt:
__________________________________________________________________________
109
(p->q)->membru
(emp.birthdate).month
Operatorii -> i . ai structurilor, mpreun cu () pentru
listele de argumente i [] pentru indexare se gsesc n vrful listei de
preceden (vezi seciunea 4.16), fiind din acest punct de vedere
foarte apropiai. Astfel, fiind dat declaraia:
struct {
int x;
int *y;} *p;
unde p este un pointer la o structur, atunci expresia:
++p->x
incrementeaz pe x, nu pointerul p, deoarece operatorul -> are o
preceden mai mare dect ++. Parantezele pot fi folosite pentru a
modifica ordinea operatorilor dat de precedena. Astfel:
(++p)->x
incrementeaz mai nti pe p i apoi acceseaz elementul x, din
structura nou pointat.
n expresia (p++)->x se acceseaz mai nti x, apoi se
incrementeaz pointerul p.
n mod analog, *p->y indic coninutul adresei pe care o indic
y. Expresia *p->y++ acceseaz mai nti ceea ce indic y i apoi
incrementeaz pe y. Expresia (*p->y)++ incrementeaz ceea ce
indic y. Expresia *p++->y acceseaz ceea ce indic y i apoi
incrementeaz pointerul p.
__________________________________________________________________________
116
deoarece adunarea a doi pointeri este o operaie ilegal, nedefinit.
Aceast instruciune trebuie modificat n:
mid = low + (high-low) / 2
care face ca mid s pointeze elementul de la jumtatea distanei
dintre low i high.
S mai observm iniializarea pointerilor low i high, care este
perfect legal, deoarece este posibil iniializarea unui pointer cu o
adres a unui element deja definit.
n funcia main avem urmtorul ciclu:
for(p=keytab; p<keytab+NKEYS; p++)...
Dac p este un pointer la un masiv de structuri, orice operaie
asupra lui p ine cont de dimensiunea unei structuri, astfel nct p++
incrementeaz pointerul p la urmtoarea structur din masiv, adunnd
la p dimensiunea corespunztoare a unei structuri. Acest lucru nu
nseamn c dimensiunea structurii este egal cu suma dimensiunilor
membrilor ei deoarece din cerine de aliniere a unor membri se pot
genera goluri ntr-o structur.
n sfrit, cnd o funcie returneaz un tip complicat i are o list
complicat de argumente, ca n:
struct key *binary(char *word, struct key
tab, int n)
funcia poate fi mai greu vizibil i detectabil cu un editor de texte.
Din acest motiv, se poate opta i pentru urmtoarea form:
struct key *binary(word,tab,n)
char *word; struct key tab; int n;
unde nainte de acolada de deschidere se precizeaz tipul fiecrui
parametru.
Alegei forma care v convine i care vi se pare mai sugestiv.
__________________________________________________________________________
117
cutare liniar pentru fiecare cuvnt, pe msura apariiei lui pentru a
vedea dac a mai fost prezent sau nu, pentru c timpul de execuie al
programelor ar crete ptratic cu numrul cuvintelor de la intrare.
Un mod de a organiza datele pentru a lucra eficient cu o list de
cuvinte arbitrare este de a pstra mulimea de cuvinte, tot timpul
sortat, plasnd fiecare nou cuvnt din intrare pe o poziie
corespunztoare, relativ la intrrile anterioare. Dac am realiza acest
lucru prin deplasarea cuvintelor ntr-un masiv liniar, programul ar
dura, de asemenea, foarte mult. De aceea, pentru rezolvarea eficient
a acestei probleme vom folosi o structur de date numit arbore binar.
Fiecare nod al arborelui va reprezenta un cuvnt distinct din
intrare i va conine urmtoarea informaie:
- un pointer la cuvnt;
- un contor pentru numrul de apariii;
- un pointer la descendentul stng al cuvntului;
- un pointer la descendentul drept al cuvntului. Nici un nod al
arborelui nu va avea mai mult dect doi descendeni dar poate avea un
descendent sau chiar nici unul.
Arborele se construiete astfel nct pentru orice nod, sub-
arborele stng al su conine numai cuvintele care snt mai mici dect
cuvntul din nod, iar sub-arborele drept conine numai cuvinte, care
snt mai mari dect cuvntul din nod, compararea fcndu-se din punct
de vedere lexicografic.
Pentru a ti dac un cuvnt nou din intrare exist deja n arbore se
pornete de la nodul rdcin i se compar noul cuvnt cu cuvntul
memorat n nodul rdcin. Dac ele coincid se incrementeaz
contorul de numrare a apariiilor pentru nodul rdcin i se va citi
un nou cuvnt din intrare.
Dac noul cuvnt din intrare este mai mic dect cuvntul memorat
n nodul rdcin, cutarea continu cu descendentul stng, altfel se
investigheaz descendentul drept. Dac nu exist nici un descendent
pe direcia cerut, noul cuvnt nu exist n arbore i va fi inserat pe
poziia descendentului corespunztor. Se observ c acest proces de
cutare este recursiv, deoarece cutarea din fiecare nod utilizeaz o
cutare ntr-unul dintre descendenii si.
__________________________________________________________________________
118
Prin urmare se impune de la sine ca rutinele de inserare n arbore
i de imprimare s fie recursive.
Revenind la descrierea unui nod, el apare ca fiind o structur cu
patru componente:
struct tnode { /* nodul de baz */
char *word; /* pointer la cuvnt */
int count; /* numrtor de apariii */
struct tnode *left; /* descendent stng */
struct tnode *right; /* descendent drept */
};
Aceast declaraie recursiv a unui nod este perfect legal,
deoarece o structur nu poate conine ca i component o intrare a ei
nsi dar poate conine un pointer la o structur de acelai ablon cu
ea.
Declaraia:
struct tnode *left;
declar pe left ca fiind un pointer la structur (nod) i nu o
structur nsi.
n program vom folosi rutinele getword, pentru citirea unui
cuvnt din intrare, alloc pentru rezervarea de spaiu necesar
memorrii unui cuvnt i alte cteva rutine pe care le cunoatem deja.
Rutina principal main citete prin intermediul rutinei getword un
cuvnt, i l plaseaz n arbore prin rutina tree.
#define MAXWORD 20
main() { /* contorizare apariii cuvinte */
struct tnode *root, *tree();
char word[MAXWORD];
int t;
root = NULL;
while ((t=getword(word,MAXWORD))!=EOF)
if (t==LETTER)
root = tree(root,word);
treeprint(root);
}
__________________________________________________________________________
119
Rutina main gestioneaz fiecare cuvnt din intrare ncepnd cu
cel mai nalt nivel al arborelui (rdcina). La fiecare pas, cuvntul din
intrare este comparat cu cuvntul asociat rdcinii i este apoi
transmis n jos, fie descendentului stng, fie celui drept, printr-un apel
recursiv la rutina tree. n acest proces, cuvntul fie exist deja,
undeva n arbore, caz n care contorul lui de numrare a apariiilor se
incrementeaz, fie cutarea continu pn la ntlnirea unui pointer
NULL, caz n care nodul trebuie creat i adugat arborelui. Cnd se
creeaz un nod nou, rutina tree returneaz un pointer la el, care
apoi este introdus n nodul de origine (adic n nodul al crui
descendent este noul nod) n cmpul left sau right dup cum
noul cuvnt este mai mic sau mai mare fa de cuvntul origine.
Rutina tree, care returneaz un pointer la o structur de ablon
tnode are urmtorul cod:
struct tnode *tree(struct tnode *p,
char *w) { /* introduce cuvntul w n nodul p */
struct tnode *talloc(int n);
char *strsav(char *s);
int cond;
if (p==NULL) { /* a sosit un nou cuvnt */
p = talloc(); /* creeaz un nod nou */
p->word = strsav(w);
p->count = 1;
p->left = p->right = NULL;
}
else
if ((cond=strcmp(w,p->word))==0)
p->count++;
else
if (cond<0) /* noul cuvnt mai mic */
p->left = tree(p->left,w);
else /* noul cuvnt mai mare */
p->right = tree(p->right,w);
return p;
__________________________________________________________________________
120
}
Memoria pentru noul nod se aloc de ctre rutina talloc, care
este o adaptare a rutinei alloc, pe care am vzut-o deja. Ea
returneaz un pointer la un spaiu liber, n care se poate nscrie noul
nod al arborelui. Vom discuta rutina talloc mai trziu. Noul cuvnt
se copiaz n acest spaiu cu ajutorul rutinei strsav, care returneaz
un pointer la nceputul cuvntului, contorul de apariii se iniializeaz
la 1 i pointerii ctre cei doi descendeni se fac NULL. Aceast parte
de cod se execut numai cnd se adaug un nou nod.
Rutina treeprint tiprete arborele astfel nct pentru fiecare
nod se imprim sub-arborele lui stng, adic toate cuvintele mai mici
dect cuvntul curent, apoi cuvntul curent i la sfrit sub-arborele
drept, adic toate cuvintele mai mari dect cuvntul curent. Rutina
treeprint este una din cele mai tipice rutine recursive.
treeprint(struct tnode *p) {
/* tiprete arborele p recursiv */
if (p!=NULL) {
treeprint(p->left);
printf("%5d %s\n",p->count,p->word);
treeprint(p->right);
}
}
Este important de reinut faptul c n algoritmul de cutare n
arbore, pentru a ajunge la un anumit nod, se parcurg toate nodurile
precedente, pe ramura respectiv (stng sau dreapt), ncepnd
ntotdeauna cu nodul rdcin. Dup fiecare ieire din rutina tree,
din cauza recursivitii, se parcurge acelai drum, de data aceasta de
la nodul gsit spre rdcina arborelui, refcndu-se toi pointerii
drumului parcurs.
Dac considerai ca nu ai neles suficient de bine recursivitatea,
desenai-v un arbore i imprimai-l cu ajutorul rutinei treeprint,
avnd grij s memorai fiecare ieire din tree i treeprint.
O observaie legat de acest exemplu: dac arborele este
nebalansat, adic cuvintele nu sosesc n ordine aleatoare din punct
__________________________________________________________________________
121
de vedere lexicografic, atunci timpul de execuie al programului
poate deveni foarte mare. Cazul limit n acest sens este acela n care
cuvintele de la intrare snt deja n ordine, (cresctoare sau
descresctoare), caz n care programul nostru simuleaz o cutare
liniar ntr-un mod destul de costisitor.
S ne oprim puin asupra alocrii de memorie. Cu toate c se
aloc diferite tipuri de obiecte, este de preferat s existe un singur
alocator de memorie ntr-un program. Relativ la acest alocator de
memorie se pun doua probleme: n primul rnd cum poate satisface el
condiiile de aliniere ale obiectelor de un anumit tip (de exemplu
ntregii trebuie alocai la adrese pare); n al doilea rnd cum se poate
declara c alocatorul returneaz pointeri la tipuri diferite de obiecte.
Cerinele de aliniere pot fi n general rezolvate cu uurin pe
seama unui spaiu care se pierde, dar care este nesemnificativ ca
dimensiune. De exemplu, alocatorul alloc returneaz totdeauna un
pointer la o adres par. n cazul n care cererea de alocare poate fi
satisfcut i de o adres impar (pentru iruri de caractere, de
exemplu) se pierde un caracter.
n ceea ce privete declararea tipului alocatorului alloc (adic a
tipului de obiect pe care l indic pointerul returnat de alloc), un
foarte bun procedeu n limbajul C este de a declara c funcia alloc
returneaz un pointer la char i apoi s convertim explicit acest
pointer la tipul dorit printr-un cast. Astfel dac p este declarat n
forma:
char *p;
atunci:
(struct tnode *)p;
convertete pe p dintr-un pointer la char ntr-un pointer la o
structur de ablon tnode, dac el apare ntr-o expresie. i atunci, o
versiune a alocatorului talloc poate fi urmtoarea:
struct tnode *talloc() {
char *alloc();
return (struct tnode *) alloc
(sizeof(struct tnode));
}
__________________________________________________________________________
122
10.6. Cutare n tabele
O alt problem legat de definirea i utilizarea structurilor este
cutarea n tabele. Cnd se ntlnete de exemplu, o linie de forma:
#define YES 1
simbolul YES i textul de substituie 1 se memoreaz ntr-o tabel.
Mai trziu, ori de cte ori textul YES va aprea n instruciuni, el se va
nlocui cu constanta 1.
Crearea i gestionarea tabelelor de simboluri este o problem de
baz n procesul de compilare. Exist dou rutine principale care
gestioneaz simbolurile i textele lor de substituie. Prima,
install(s,t) nregistreaz simbolul s i textul de substituie t
ntr-o tabel, s i t fiind iruri de caractere. A doua, lookup(s)
caut irul s n tabel i returneaz fie un pointer la locul unde a fost
gsit, fie NULL dac irul s nu figureaz n tabel.
Algoritmul folosit pentru crearea i gestionarea tabelei de
simboluri este o cutare pe baz de hashing. Fiecrui simbol i se
calculeaz un cod hash astfel: se adun codurile ASCII ale
caracterelor simbolului i se ia restul provenit din mprirea
numrului obinut din adunare i dimensiunea tabelului. Astfel,
fiecrui simbol i se asociaz un cod hash H care verific relaia:
0<=H<0x100 (n hexazecimal)
Codul hash astfel obinut va fi folosit apoi ca un indice ntr-o
tabel de pointeri. Un element al acestei tabele (masiv) indic
nceputul unui lan de blocuri care descriu simboluri cu acelai cod
hash. Dac un element al tabelei este NULL nseamn c nici un
simbol nu are valoarea respectiv de hashing.
Un bloc dintr-un lan indicat de un element al tabelei este o
structur care conine un pointer la simbol, un pointer la textul de
substituie i un pointer la urmtorul bloc din lan. Un pointer NULL
la urmtorul bloc din lan indic sfritul lanului.
ablonul unei structuri (nod) este urmtorul:
struct nlist {
char *name;
char *def;
__________________________________________________________________________
123
struct nlist *next;/ * urmtoarea intrare n lan */
};
Tabelul de pointeri care indic nceputurile lanului de blocuri ce
descriu simboluri de acelai cod hash este:
#define HASHSIZE 0x100
static struct nlist *hashtab[HASHSIZE];
Algoritmul de hashing pe care-l prezentm nu este cel mai bun
posibil, dar are meritul de a fi extrem de simplu:
hash(char *s) {
/* formeaz valoarea hash pentru irul s */
int hashval;
for (hashval=0; *s!='\0';)
hashval += *s++;
return hashval % HASHSIZE;
}
Algoritmul de hashing produce un indice n masivul de pointeri
hashtab. n procesul de cutare a unui simbol, dac el exist, el
trebuie s fie n lanul de blocuri care ncepe la adresa coninut de
elementul din hashtab cu indicele respectiv.
Cutarea n tabela de simboluri hashtab se realizeaz cu
funcia lookup. Dac simbolul cutat este prezent undeva n lan,
funcia returneaz un pointer la el; altfel returneaz NULL.
struct nlist *lookup(char *s) {
/* caut irul s n hashtab */
struct nlist *np;
for (np=hashtab[hash(s)]; np!=NULL;
np=np->next)
if (strcmp(s,np->name)==0)
return np; /* s-a gsit s */
return NULL; /* nu s-a gsit s */
}
__________________________________________________________________________
124
Rutina install folosete funcia lookup pentru a determina
dac simbolul nou care trebuie introdus n lan este deja prezent sau
nu. Dac mai exist o definiie anterioar pentru acest simbol, ea
trebuie nlocuit cu definiia nou. Altfel, se creeaz o intrare nou
pentru acest simbol, care se introduce la nceputul lanului. Funcia
install returneaz NULL, dac din anumite motive nu exist
suficient spaiu pentru crearea unui bloc unu.
struct nlist *install(char *name, char
*def) { /* scrie (nume, def) n htab */
struct nlist *np, *lookup();
char *strsav(), *alloc();
int hashval;
if ((np=lookup(name))==NULL) { /* nu s-a gsit */
np = (struct nlist*)alloc(sizeof(*np));
if (np==NULL)
return NULL; /* nu exist spaiu */
if ((np->name=strsav(name))==NULL)
return NULL;
hashval = hash(np->name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
}
else /* nodul exist deja */
free(np->def); /* elibereaz definiia veche */
if ((np->def=strsav(def))==NULL)
return NULL;
return np;
}
Deoarece apelurile la funciile alloc i free pot aprea n
orice ordine i deoarece alinierea conteaz, versiunea simpl a
funciei alloc, prezentat n capitolul 9 nu este adecvat aici. n
biblioteca standard exist funcii de alocare fr restricii, care se
apeleaz implicit sau explicit de ctre utilizator dintr-un program
scris n C pentru a obine spaiul de memorie necesar. Deoarece i
alte aciuni dintr-un program pot cere spaiu de memorie ntr-o
__________________________________________________________________________
125
manier asincron, spaiul de memorie gestionat de funcia alloc
poate s fie necontiguu. Astfel, spaiul liber de memorie este pstrat
sub forma unui lan de blocuri libere, fiecare bloc coninnd o
dimensiune, un pointer la urmtorul bloc i spaiul liber propriu-zis.
Blocurile snt pstrate n ordinea cresctoare a adreselor iar, ultimul
bloc, de adresa cea mai mare, indic primul bloc, prin pointerul lui la
blocul urmtor din lan, astfel nct lanul este circular.
Cnd se lanseaz o cerere, se examineaz lista spaiului liber, pn
se gsete un bloc suficient de mare pentru cererea respectiv. Dac
blocul are exact dimensiunea cerut, el se elibereaz din lanul
blocurilor libere i este returnat utilizatorului. Dac blocul este mai
mare se descompune, astfel nct partea cerut se transmite
utilizatorului, iar partea rmas se introduce napoi n lista de spaiu
liber. Dac nu se gsete un bloc suficient de mare pentru cererea
lansat se caut un alt bloc de memorie.
Eliberarea unei zone de memorie prin intermediul rutinei free
cauzeaz, de asemenea, o cutare n lista de spaiu liber, pentru a gsi
locul corespunztor de inserare a blocului de memorie eliberat. Dac
blocul de memorie eliberat este adiacent cu un bloc din lista de spaiu
liber la orice parte a sa, el este alipit la acel bloc, crendu-se un bloc
mai mare, astfel ca memoria s nu devin prea fragmentat.
Determinarea adiacenei este uurat de faptul c lista de spaiu liber
se pstreaz n ordinea cresctoare a adreselor de memorie.
Exemplul de utilizare a acestor funcii iniializeaz elementele
masivului hashtab cu NULL. n continuare se ateapt de la
tastatur introducerea unui nume i a unei definiii pentru acest nume.
Dac numele introdus nu exist n lista hashtab atunci se afieaz
un mesaj corespunztor, altfel se afieaz vechea definiie care este
apoi nlocuit de noua definiie introdus.
main() {
char num[30],def[30];
int i;
struct nlist *np;
for (i=0; i<HASHSIZE; i++)
hashtab[i] = NULL;
__________________________________________________________________________
126
do {
getword(num); getword(def);
if ((np=lookup(num))==NULL)
printf("New name\n");
else
printf("Old definition: %s\n",
np->def);
install(num,def);
} while (1);
}
10.7. Cmpuri
Un cmp se definete ca fiind o mulime de bii consecutivi dintr-
un cuvnt sau ntreg. Adic din motive de economie a spaiului de
memorie, este util mpachetarea unor obiecte ntr-un singur cuvnt
main. Un caz frecvent de acest tip este utilizarea unui set de flaguri,
fiecare pe un bit, pentru tabela de simboluri a unui compilator.
Fiecare simbol dintr-un program are anumite informaii asociate
lui, cum snt de exemplu, clasa de memorie, tipul, dac este sau nu
cuvnt cheie .a.m.d. Cel mai compact mod de a codifica aceste
informaii este folosirea unui set de flaguri, de cte un bit, ntr-un
singur ntreg sau caracter.
Modul cel mai uzual pentru a face acest lucru este de a defini un
set de mti, fiecare masc fiind corespunztoare poziiei bitului m
interiorul caracterului sau cuvntului. De exemplu:
#define KEYWORD 01
#define EXTERNAL 02
#define STATIC 04
definesc mtile KEYWORD, EXTERNAL i STATIC care se refer la
biii 0, 1 i respectiv 2 din caracter sau cuvnt. Atunci accesarea
acestor bii se realizeaz cu ajutorul operaiilor de deplasare, mascare
i complementare, descrii ntr-un capitol anterior. Numerele trebuie
s fie puteri ale lui 2.
Expresii de forma:
__________________________________________________________________________
127
flags | = EXTERNAL | STATIC;
apar frecvent i ele seteaz biii 1 i 2 din caracterul sau ntregul
flags (n exemplul nostru)
n timp ce expresia:
flags &= (EXTERNAL | STATIC);
selecteaz biii 1 i 2 din flags.
Expresia:
if (flags & (EXTERNAL | STATIC)) ...
este adevrat cnd cel puin unul din biii 1 sau 2 din flags este unu.
Expresia:
if (!(flags & (EXTERNAL | STATIC))) ...
este adevrat cnd biii 1 i 2 din flags snt ambii zero.
Limbajul C ofer aceste expresii, ca o alternativ, pentru
posibilitatea de a defini i de a accesa biii dintr-un cuvnt, n mod
direct, folosind operatorii logici pe bii.
Sintaxa definiiei cmpului i a accesului la el se bazeaz pe
structuri. De exemplu construciile #define din exemplul de mai
sus pot fi nlocuite prin definirea a trei cmpuri:
struct {
unsigned is_keyword: 1;
unsigned is_external:1;
unsigned is_static: 1;
} flags;
Aceast construcie definete variabila flags care conine 3
cmpuri, fiecare de cte un bit. Numrul care urmeaz dup :
reprezint lungimea cmpului n bii. Cmpurile snt declarate
unsigned pentru a sublinia c ele snt cantiti fr semn. Pentru a
ne referi la un cmp individual din variabila flags folosim o notaie
similar cu notaia folosit pentru membrii structurilor.
flags.is_keyword
flags.is_static
__________________________________________________________________________
128
Cmpurile se comport ca nite ntregi mici fr semn i pot
participa n expresii aritmetice ca orice ali ntregi. Astfel, expresiile
anterioare pot fi scrise mai natural sub forma urmtoare:
flags.is_extern = flags.is_static = 1;
pentru setarea biilor 1 i 2 din variabila flags,
flags.is_extern = flags.is_static = 0;
pentru tergerea biilor, iar:
if (flags.is_extern==0 &&
flags.is_static==0)
pentru testarea lor.
Un cmp nu trebuie s depeasc limitele unui cuvnt. n caz
contrar, cmpul se aliniaz la limita urmtorului cuvnt. Cmpurile nu
necesit s fie denumite. Un cmp fr nume, descris numai prin
caracterul : i lungimea lui n bii este folosit pentru a rezerva
spaiu n vederea alinierii urmtorului cmp. Lungimea zero a unui
cmp poate fi folosit pentru forarea alinierii urmtorului cmp la
limita unui nou cuvnt, el fiind presupus a conine tot cmpuri i nu
un membru obinuit al structuri, deoarece n acest ultim caz, alinierea
se face n mod automat. Nici un cmp nu poate fi mai lung dect un
cuvnt. Cmpurile se atribuie de la dreapta la stnga.
Cmpurile nu pot constitui masive, nu au adrese, astfel nct
operatorul '&' nu se poate aplica asupra lor.
10.8. Reuniuni
O reuniune este o variabil care poate conine, la momente
diferite, obiecte de diferite tipuri i dimensiuni; compilatorul este cel
care ine evidena dimensiunilor i aliniamentului.
Reuniunile ofer posibilitatea ca mai multe tipuri diferite de date
s fie tratate ntr-o singur zon de memorie, fr a folosi n program
vreo informaie dependent de main.
S relum exemplul tabelei de simboluri a unui compilator,
presupunnd c constantele pot fi de tip int, float sau iruri de
caractere.
__________________________________________________________________________
129
Valoarea unei constante particulare trebuie memorat ntr-o
variabil de tip corespunztor, cu toate c este mai convenabil, pentru
gestiunea tabelei de simboluri, ca valoarea s fie memorat n aceeai
zon de memorie, indiferent de tipul ei i s ocupe aceeai cantitate
de memorie. Acesta este scopul unei reuniuni: de a furniza o singur
variabil care s poat conine oricare dintre valorile unor tipuri de
date. Ca i n cazul cmpurilor, sintaxa definiiei i accesului la o
reuniune se bazeaz pe structuri. Fie definiia:
union u_tag. { int ival;
float fval;
char *pval;
} uval;
Variabila uval va fi suficient de mare ca s poat pstra pe cea
mai mare dintre cele trei tipuri de componente. Oricare dintre tipurile
de mai sus poate fi atribuit variabilei uval i apoi folosit n expresii
n mod corespunztor, adic tipul n uval este tipul ultim atribuit.
Utilizatorul este cel care ine evidena tipului curent memorat ntr-o
reuniune.
Sintactic, membrii unei reuniuni snt accesibili printr-o
construcie de forma:
nume-reuniune. membru
sau
pointer-la-reuniune->membru
Dac variabila utype este utilizat pentru a ine evidena tipului
curent memorat n uval, atunci fie urmtorul cod:
if (utype==INT)
printf ("%d\n",uval.ival);
else if (utype== FLOAT)
printf("%f\n",uval.fval);
else if (utype==STRING)
printf("%s\n",uval.pval);
else
printf("tip incorect %d in utype\n",
utype);
__________________________________________________________________________
130
Reuniunile pot aprea n structuri i masive i invers. Sintaxa
pentru accesarea unui membru al unei reuniuni, dintr-o structur, sau
invers este identic cu cea pentru structurile imbricate. Pe exemplu,
n masivul de structuri symtab[NSYM] definit de:
struct {
char * name;
int flags;
int utype;
union {
int ival;
float fval;
char *pval;
} uval;
} symtab[NSYM];
variabila ival se refer prin:
symtab[i].uval.ival
iar primul caracter al irului pointat de pval prin:
*symtab[i].uval.pval
De fapt, o reuniune este o structur n care toi membrii au
deplasamentul zero, structura fiind suficient de mare pentru a putea
pstra pe cel mai mare membru. Alinierea este corespunztoare
pentru toate tipurile reuniunii. Ca i la structuri, singurele operaii
permise cu reuniuni snt accesul la un membru al reuniunii i
considerarea adresei ei.
Reuniunile nu pot fi atribuite, transmise la funcii sau returnate
de ctre acestea. Pointerii la reuniuni pot fi folosii n mod similar cu
pointerii la structuri.
__________________________________________________________________________
131
struct-sau-union identificator { lista-declaraiilor }
struct-sau-union identificator
Struct-sau-union:
struct
union
Lista-declaraiilor este o secven de declaraii pentru membrii
structurii sau reuniunii.
Lista-declaraiilor:
declaraie-structur
declaraie-structur, lista-declaraiilor
Declaraie-structur:
specificator-tip, lista-declarator;
Lista-declarator:
declarator-structur
declarator-structur, lista-declarator
n mod obinuit, un declarator-structur este chiar un declarator
pentru un membru al structurii sau reuniunii. Un membru al structurii
poate fi constituit dintr-un numr specificat de bii, caz n care avem
de-a face cu un cmp. Lungimea lui se separ de nume prin caracterul
: Atunci:
Declarator-structur:
declarator
declarator : expresie-constant
: expresie-constant
ntr-o structur fiecare membru care nu este un cmp ncepe la o
adres corespunztoare tipului su. Astfel ntr-o structur pot exista
zone fr nume neutilizate, rezultate din motive de aliniere.
Limbajul C nu introduce restricii privind tipurile obiectelor care
pot fi declarate cmpuri.
Un specificator-structur-sau-reuniune de forma a doua declar
un identificator ca fiind eticheta (marcajul) structurii sau reuniunii.
Atunci o declaraie ulterioar poate folosi forma a treia a unui
specificator-structur-sau-reuniune.
__________________________________________________________________________
132
Etichetele de structuri permit definirea structurilor auto-referite;
de asemenea permit ca partea de declaraie a corpului structurii s fie
dat o singur dat i folosit de mai multe ori. Este interzis
declararea recursiv a unei structuri sau reuniuni, dar o structur sau
o reuniune poate conine un pointer la ea.
Dou structuri pot partaja o secven iniial comun de membri;
adic acelai membru poate aprea n dou structuri diferite dac el
are acelai tip n ambele structuri i dac toi membri precedeni lui
snt identici n cele dou structuri.
10.10. Typedef
Limbajul C ofer o facilitate numit typedef pentru a crea noi
nume de tipuri de date. Specificatorul de tip typedef-nume are
sintaxa:
typedef-nume:
declarator
ntr-o declaraie implicnd typedef fiecare identificator care
apare ca parte a unui declarator devine sintactic echivalent cu
cuvntul cheie rezervat pentru tipul asociat cu identificatorul. De
exemplu, declaraia:
typedef int LENGTH;
l face pe LENGTH sinonim cu int. Tipul LENGTH poate fi folosit
ulterior n declaraii n acelai mod ca i tipul int.
LENGTH len, maxlen;
LENGTH *length[];
n mod similar, declaraia:
typedef char *STRING;
l face pe STRING sinonim cu char*, adic pointer la caracter, care
apoi poate fi utilizat n declaraii de tipul:
STRING p, lineptr[LINES], alloc();
Se observ c tipul care se declar prin typedef apare pe
poziia numelui de variabil nu imediat dup cuvntul rezervat
typedef. Sintactic typedef este sinonim cu clasele de memorie
__________________________________________________________________________
133
extern, static etc, dar nu rezerv memorie pentru variabilele
respective.
Ca un exemplu mai complicat s relum declaraia unui nod al
unui arbore, de data aceasta folosind typedef pentru a crea un nou
nume pentru un tip structur (vezi seciunea 10.5).
typedef struct tnode {
char *word; /* pointer la text */
int count; /* numr apariii */
struct tnode *left; /* descendent stng */
struct tnode *right; /* descendent drept */
} TREENODE, *TREEPTR;
Aceast declaraie creeaz dou nume noi de tipuri, numite
TREENODE, care este o structur i TREEPTR, care este un pointer la
o structur. Atunci rutina talloc poate fi scris sub forma:
TREEPTR talloc() {
char *alloc();
return (TREEPTR)alloc(sizeof(TREENODE)));
}
Trebuie subliniat faptul c declaraia typedef nu creeaz noi
tipuri n nici un caz; ea adaug doar sinonime pentru anumite tipuri
de date, deja existente. Variabilele declarate n acest fel au exact
aceleai proprieti ca i cele declarate explicit. De fapt, typedef se
aseamn cu #define, cu excepia faptului c n timp ce #define
este tratat de preprocesor, typedef este tratat de ctre compilator.
De exemplu:
typedef int(*PFI)();
creeaz numele PFI pentru pointer la o funcie care returneaz un
ntreg, tip care poate fi folosit ulterior ntr-un context de tipul:
PFI strcmp, numcmp, swap;
n programul de sortare din capitolul 9.
Exist dou motive principale care impun folosirea declaraiilor
typedef. Primul este legat de problemele de portabilitate. Cnd se
folosesc declaraii typedef pentru tipuri de date care snt
__________________________________________________________________________
134
dependente de main, atunci pentru o compilare pe un alt sistem de
calcul este necesar modificarea doar a acestor declaraii nu i a
datelor din program.
Al doilea const n faptul c prin crearea de noi nume de tipuri se
ofer posibilitatea folosirii unor nume mai sugestive n program, deci
o mai rapid nelegere a programului.
__________________________________________________________________________
135
11. Intrri / ieiri
__________________________________________________________________________
137
11.2. Accesul la fiiere; deschidere i nchidere
Nume
fopen - deschide un flux
Declaraie
FILE *fopen(const char *path,
const char *mode);
Descriere
Funcia fopen deschide fiierul al crui nume este un ir indicat
de path i i asociaz un flux.
Argumentul mode indic un ir care ncepe cu una din secvenele
urmtoare:
r deschide un fiier pentru citire;
r+ deschide pentru citire i scriere;
w trunchiaz fiierul la lungime zero sau creeaz un fiier pentru
scriere;
w+ deschide pentru adugare la sfrit, n citire i scriere; fiierul este
creat dac nu exist, altfel este trunchiat;
a deschide pentru adugare la sfrit, n scriere; fiierul este creat
dac nu exist;
a+ deschide pentru adugare la sfrit, n citire i scriere; fiierul este
creat dac nu exist;
Dup deschidere, n primele patru cazuri indicatorul poziiei n
flux este la nceputul fiierului, n ultimele dou la sfritul acestuia.
irul mode include de asemenea litera b (deschide un fiier
binar) sau t (deschide un fiier text) fie pe ultima poziie fie pe cea
din mijloc.
Operaiile de citire i scriere pot alterna n cazul fluxurilor read /
write n orice ordine. S reinem c standardul ANSI C cere s existe
o funcie de poziionare ntre o operaie de intrare i una de ieire, sau
ntre o operaie de ieire i una de intrare, cu excepia cazului cnd o
operaie de citire detecteaz sfritul de fiier. Aceast operaie poate
__________________________________________________________________________
138
fi inefectiv - cum ar fi fseek(flux, 0L, SEEK_CUR) apelat
cu scop de sincronizare.
Valori returnate
n caz de succes se returneaz un pointer de tip FILE. n caz de
eroare se returneaz NULL i variabila global errno indic codul
erorii.
Nume
fclose - nchide un flux
Declaraie
int fclose( FILE *flux);
Descriere
Funcia fclose nchide fiierul asociat fluxului flux. Dac
flux a fost deschis pentru ieire, orice date aflate n zone tampon
snt scrise n fiier n prealabil cu un apel fflush.
Valori returnate
n caz de succes se returneaz 0. n caz de eroare se returneaz
EOF i variabila global errno indic codul erorii.
Nume
tmpfile - creeaz un fiier temporar
Declaraie
FILE *tmpfile();
Descriere
Funcia tmpfile genereaz un nume unic de fiier temporar.
Acesta este deschis n mod binar pentru scriere / citire ("wb+").
Fiierul va fi ters automat la nchidere sau la terminarea
programului.
Valoare returnat
Funcia returneaz un descriptor de flux n caz de succes, sau
NULL dac nu poate fi generat un nume unic de fiier sau dac
__________________________________________________________________________
139
fiierul nu poate fi deschis. n caz de eroare variabila global errno
indic codul erorii.
Nume
fflush - foreaz scrierea n flux
Declaraie
int fflush(FILE *flux);
Descriere
Funcia fflush foreaz o scriere a tuturor datelor aflate n zone
tampon ale fluxului flux. Fluxul rmne deschis.
Valori returnate
n caz de succes se returneaz 0. n caz de eroare se returneaz
EOF i variabila global errno indic codul erorii.
Nume
fseek, ftell, rewind - repoziioneaz un flux
Declaraie
int fseek(FILE *flux, long offset,
int reper);
long ftell(FILE *flux);
void rewind(FILE *flux);
Descriere
Funcia fseek seteaz indicatorul de poziie pentru fiierul
asociat fluxului flux. Noua poziie, dat n octei, se obine adunnd
offset octei la poziia specificat de reper. Dac reper este
SEEK_SET, SEEK_CUR, sau SEEK_END, offset este relativ la
nceputul fiierului, poziia curent a indicatorului, respectiv sfritul
fiierului. Funcia fseek terge indicatorul de sfrit de fiier.
Funcia ftell obine valoarea curent a indicatorului de poziie
pentru fiierul asociat fluxului flux.
__________________________________________________________________________
140
Funcia rewind poziioneaz indicatorul de poziie pentru
fiierul asociat fluxului flux la nceputul fiierului. Este echivalent
cu:
(void)fseek(flux, 0L, SEEK_SET)
cu completarea c funcia rewind terge i indicatorul de eroare al
fluxului.
Valori returnate
Funcia rewind nu returneaz nici o valoare. n caz de succes,
fseek returneaz 0, i ftell returneaz offset-ul curent. n caz de
eroare se returneaz EOF i variabila global errno indic codul
erorii.
Nume
fgets - citete un ir de caractere dintr-un flux text
Declaraie
char *fgets(char *s, int size, FILE *flux);
Descriere
Funcia fgets cel mult size-1 caractere din flux i le
memoreaz n zona indicat de s. Citirea se oprete la detectarea
sfritului de fiier sau new-line. Dac se citete caracterul new-line
acesta este memorat n s. Dup ultimul caracter se memoreaz null.
Apeluri ale acestei funcii pot fi combinate cu orice apeluri ale
altor funcii de intrare din bibliotec (fscanf, de exemplu) pentru
un acelai flux de intrare.
Valori returnate
Funcia returneaz adresa s n caz de succes, sau NULL n caz de
eroare sau la ntlnirea sfritului de fiier dac nu s-a citit nici un
caracter.
__________________________________________________________________________
141
Nume
fputs - scrie un ir de caractere ntr-un flux text
Declaraie
int fputs(const char *s, FILE *flux);
Descriere
Funcia fputs scrie irul s n flux fr caracterul terminator
null.
Apeluri ale acestei funcii pot fi combinate cu orice apeluri ale
altor funcii de ieire din bibliotec (fprintf, de exemplu) pentru
un acelai flux de ieire.
Valori returnate
Funcia returneaz o valoare non-negativ n caz de succes, sau
EOF n caz de eroare.
Nume
fread, fwrite - intrri / ieiri pentru fluxuri binare
Declaraie
unsigned fread(void *ptr, unsigned size,
unsigned nel, FILE *flux);
unsigned fwrite(const void *ptr, unsigned
size, unsigned nel, FILE *flux);
Descriere
Funcia fread citete nel elemente, fiecare avnd mrimea
size octei, din fluxul indicat de flux, i le memoreaz n zona
indicat de ptr.
Funcia fwrite scrie nel elemente, fiecare avnd mrimea
size octei, din fluxul indicat de flux, pe care le ia din zona
indicat de ptr.
Valori returnate
Funciile returneaz numrul de elemente citite sau scrise cu
succes (i nu numrul de caractere). Dac apare o eroare sau se
__________________________________________________________________________
142
ntlnete sfritul de fiier, valoarea returnat este mai mic dect
nel (posibil zero).
Nume
scanf, fscanf, sscanf - citire cu format
Declaraie
int scanf(const char *format, ...);
int fscanf(FILE *flux, const char *format,
...);
int sscanf(const char *str, const char
*format, ...);
Descriere
Familia de funcii scanf scaneaz intrarea n concordan cu irul
de caractere format dup cum se descrie mai jos. Acest format
poate conine specificatori de conversie; rezultatele unor astfel de
conversii (dac se efectueaz) se memoreaz prin intermediul
argumentelor pointer. Funcia scanf citete irul de intrare din
fluxul standard stdin, fscanf din flux, i sscanf din irul
indicat de str.
Fiecare argument pointer trebuie s corespund n ordine ca tip
cu fiecare specificator de conversie (dar a se vedea suprimarea mai
jos). Dac argumentele nu snt suficiente comportamentul
programului este imprevizibil. Toate conversiile snt introduse de
caracterul %. irul format poate conine i alte caractere. Spaii albe
(blanc, tab, sau new-line) din irul format se potrivesc cu orice spaiu
alb n orice numr (inclusiv nici unul) din irul de intrare. Orice alte
caractere trebuie s se potriveasc exact. Scanarea se oprete atunci
cnd un caracter din irul de intrare nu se potrivete cu cel din format.
Scanarea se oprete de asemenea atunci cnd o conversie nu se mai
poate efectua (a se vedea mai jos).
__________________________________________________________________________
143
Conversii
Dup caracterul % care introduce o conversie poate urma un
numr de caractere indicatori, dup cum urmeaz:
* Suprim atribuirea. Conversia care urmeaz se face n mod
obinuit, dar nu se folosete nici un argument pointer; rezultatul
conversiei este pur i simplu abandonat.
h Conversia este de tip dioux sau n i argumentul asociat este un
pointer la short (n loc de int).
l Conversia este de tip dioux sau n i argumentul asociat este un
pointer la long (n loc de int), sau conversia este de tip efg i
argumentul asociat este un pointer la double (n loc de
float).
L Conversia este de tip efg i argumentul asociat este un pointer la
long double.
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.
d Potrivire cu un ntreg zecimal (eventual cu semn); argumentul
asociat trebuie s fie un pointer la int.
i Potrivire cu un ntreg (eventual cu semn); argumentul asociat
trebuie s fie un pointer la int. Valoarea ntreag este citit n
__________________________________________________________________________
144
baza 16 dac ncepe cu 0x sau 0X, n baza 8 dac ncepe cu 0, i
n baza 10 n caz contrar. Snt folosite numai caracterele care
corespund bazei respective.
o Potrivire cu un ntreg octal fr semn; argumentul asociat trebuie
s fie un pointer la unsigned.
u Potrivire cu un ntreg zecimal fr semn; argumentul asociat
trebuie s fie un pointer la unsigned.
x Potrivire cu un ntreg hexazecimal fr semn; argumentul asociat
trebuie s fie un pointer la unsigned.
f Potrivire cu un numr n virgul mobil (eventual cu semn);
argumentul asociat trebuie s fie un pointer la float.
e,g Echivalent cu f.
s 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).
c Potrivire cu o secven de caractere de mrime w (dac aceasta
este specificat; prin lips se ia w1); 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
__________________________________________________________________________
145
exclude acele caractere dac primul caracter dup [ este ^.
Pentru a include caracterul ] n set, acesta trebuie s fie primul
caracter dup [ sau ^; caracterul ] aflat n orice alt poziie
nchide setul. Caracterul - are i el un rol special: plasat ntre
dou alte caractere adaug toate celelalte caractere aflate n
intervalul respectiv la set. Pentru a include caracterul - acesta
trebuie s fie ultimul caracter nainte de ]. De exemplu, "%
[^]0-9-]" semnific setul orice caracter cu excepia ], 0
pn la 9, i -. irul se termin la apariia unui caracter care nu
se afl (sau, dac se precizeaz ^, care se afl) n set sau dac se
atinge mrimea maxim specificat.
p Potrivire cu o valoare pointer (aa cum se afieaz cu %p n
printf); argumentul asociat trebuie s fie un pointer la pointer.
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 un eroare
nainte de prima conversie, cum ar fi detectarea sfritului de fiier.
Dac o eroare sau un sfrit de fiier apare dup ce o conversie a
nceput, se returneaz numrul de conversii efectuate cu succes.
__________________________________________________________________________
146
11.5. Scriere cu format
Nume
printf, fprintf, sprintf - scriere cu format
Declaraie
int printf(const char *format, ...);
int fprintf(FILE *flux, const char
*format, ...);
int sprintf(char *str, const char *format,
...);
Descriere
Funciile din familia printf genereaz o ieire n concordan cu
format dup cum se descrie mai jos. Funcia printf afieaz ieirea
la fluxul standard stdout; fprintf scrie ieirea la flux;
sprintf scrie ieirea n irul de caractere str.
Aceste funcii genereaz ieirea sub controlul irului format care
specific cum se convertesc argumentele pentru ieire.
irul de formatare
irul format este un ir de caractere, printre care se pot afla zero
sau mai multe directive: caractere obinuite (diferite de %) care snt
copiate aa cum snt n fluxul de ieire, i specificaii de conversie,
fiecare dintre ele rezultnd din ncrcarea a zero sau mai multe
argumente. Fiecare specificaie de conversie este introdus de
caracterul % i se termin cu un specificator de conversie. ntre
acestea pot fi (n aceast ordine) zero sau mai muli indicatori, o
mrime minim a cmpului opional, o precizie opional i un
modificator opional de lungime.
Argumentele trebuie s corespund n ordine cu specificatorii de
conversie. Acestea snt folosite n ordinea dat, unde fiecare caracter
* i fiecare specificator de conversie solicit urmtorul argument.
Dac argumentele nu snt suficiente comportamentul programului
este imprevizibil.
__________________________________________________________________________
147
Caractere indicatori
Caracterul % este urmat de zero, unul sau mai muli indicatori:
# Valoarea numeric se convertete n format alternativ. Pentru
conversii de tip o, primul caracter al irului de ieire este zero
(prin prefixare cu 0 dac valoarea nu este zero). Pentru conversii
de tip x i X, o valoare nenul este prefixat cu 0x (sau 0X
pentru conversii de tip X). Pentru conversii de tip e, E, f, F, g i
G, rezultatul va conine ntotdeauna punctul zecimal, chiar dac
nu apare partea fracionar (n mod normal punctul zecimal apare
n aceste conversii numai dac exist i partea fracionar).
Pentru conversii de tip g i G zerourile finale nu snt eliminate
aa cum se procedeaz n mod normal. Pentru alte conversii
rezultatul este nedefinit.
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.
__________________________________________________________________________
148
Limea cmpului
Un ir de cifre zecimale (cu prima cifr nenul) specific o lime
minim pentru cmp. Dac valoarea convertit are mai puine
caractere dect limea specificat, va fi completat cu spaii la stnga
(sau dreapta, dac s-a specificat aliniere la stnga). n locul unui
numr zecimal se poate folosi * pentru a specifica faptul c limea
cmpului este dat de argumentul urmtor, care trebuie s fie de tip
int. O valoare negativ pentru lime este considerat un indicator -
urmat de o valoare pozitiv pentru lime. n nici un caz nu se va
trunchia cmpul; dac rezultatul conversiei este mai mare dect
limea cmpului, cmpul este expandat pentru a conine rezultatul
conversiei.
Precizia
Precizia (opional) este dat de caracterul . urmat de un ir de
cifre zecimale. n locul irului de cifre zecimale se poate scrie *
pentru a specifica faptul c precizia este dat de argumentul urmtor,
care trebuie s fie de tip int. Dac precizia este dat doar de ., sau
dac precizia este negativ, atunci aceasta se consider zero. Precizia
d numrul minim de cifre care apar pentru conversii de tip d, i, o,
u, x, X, numrul de cifre care apar dup punctul zecimal pentru
conversii de tip e, E, f, F, numrul maxim de cifre semnificative
pentru conversii de tip g i G, sau numrul maxim de caractere
generate pentru conversii de tip s.
Modificator de lungime
n acest caz prin conversie ntreag nelegem conversie de tip d,
i, o, u, x, X.
h Conversia ntreag care urmeaz corespunde unui argument
short sau unsigned short, sau urmtoarea conversie de tip
n corespunde unui argument de tip pointer la short.
l Conversia ntreag care urmeaz corespunde unui argument
long sau unsigned long, sau urmtoarea conversie de tip n
corespunde unui argument de tip pointer la long.
__________________________________________________________________________
149
L Urmtoarea conversie de tip e, E, f, g sau G corespunde unui
argument long double.
Specificator de conversie
Un caracter care specific tipul conversiei care se va face.
Specificatorii de conversie i semnificaia lor snt:
d,i
Argumentul de tip int este convertit la notaia zecimal cu
semn. Precizia, dac este dat, d numrul minim de cifre care
trebuie s apar; dac valoarea convertit necesit mai puine
cifre, aceasta este completat la stnga cu zerouri. Precizia
implicit este 1. Dac valoarea 0 este afiat cu precizie explicit
0, ieirea este vid.
o,u,x,X
Argumentul de tip unsigned este convertit la notaie octal
fr semn (o), zecimal fr semn (u), sau hexazecimal fr
semn (x i X). Literele abcdef se folosesc pentru conversii de
tip x; literele ABCDEF pentru conversii de tip X. Precizia, dac
este dat, d numrul minim de cifre care trebuie s apar; dac
valoarea convertit necesit mai puine cifre, aceasta este
completat la stnga cu zerouri. Precizia implicit este 1. Dac
valoarea 0 este afiat cu precizie explicit 0, ieirea este vid.
e,E
Argumentul de tip flotant este rotunjit i convertit n stil
[-]d.dddedd unde avem o cifr nainte de punctul zecimal i
numrul de cifre dup acesta este egal cu precizia; dac aceasta
lipsete se consider 6; dac precizia este zero, punctul zecimal
nu apare. O conversie de tip E folosete litera E (n loc de e)
pentru a introduce exponentul. Exponentul are ntotdeauna cel
puin dou cifre; dac valoarea este zero, exponentul este 00.
f,F
Argumentul de tip flotant este rotunjit i convertit n notaie
zecimal n stil [-]ddd.ddd, unde numrul de cifre dup punctul
zecimal este egal cu precizia specificat. Dac precizia lipsete se
__________________________________________________________________________
150
consider 6; dac precizia este explicit zero, punctul zecimal nu
apare. Dac punctul zecimal apare, cel puin o cifr apare
naintea acestuia.
g,G
Argumentul de tip flotant este convertit n stil f sau e (sau E
pentru conversii de tip G). Precizia specific numrul de cifre
semnificative. Dac precizia lipsete se consider 6; dac precizia
este zero se consider 1. Stilul e este folosit dac exponentul
rezultat n urma conversiei este mai mic dect 4 ori 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 Argumentul de tip int este convertit la unsigned char i se
scrie caracterul rezultat.
s Argumentul de tip const char * este un pointer la un ir de
caractere. Caracterele din ir snt scrise pn la (fr a include)
caracterul terminator null; dac precizia este specificat, nu se
scrie un numr mai mare dect cel specificat. Dac precizia este
dat, nu e nevoie de caracterul null; dac precizia nu este
specificat, sau dac este mai mare dect mrimea irului, irul
trebuie s conin un caracter terminator null.
p Argumentul de tip pointer este scris n hexazecimal; formatul este
specific sistemului de calcul.
n 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 %%.
Valoare returnat
Funciile returneaz numrul de caractere generate (nu se include
caracterul terminator null pentru sprintf).
__________________________________________________________________________
151
11.6. Tratarea erorilor
Nume
perror - afieaz un mesaj de eroare sistem
Declaraie
void perror(const char *s);
#include <errno.h>
const char *sys_errlist[];
int sys_nerr;
Descriere
Rutina perror afieaz un mesaj la ieirea standard de eroare,
care descrie ultima eroare ntlnit la ultimul apel sistem sau funcie
de bibliotec. Mai nti se afieaz argumentul s, apoi virgula i
blanc, i n final mesajul de eroare i new-line. Se recomand (mai
ales pentru depanare) ca argumentul s s includ numele funciei n
care a aprut eroarea. Codul erorii se ia din variabila extern errno.
Lista global de erori sys_errlist[] indexat cu errno
poate fi folosit pentru a obine mesajul de eroare fr new-line.
Ultimul indice de mesaj din list este sys_nerr-1. Se recomand
o atenie deosebit n cazul accesului direct la list deoarece unele
coduri noi de eroare pot lipsi din sys_errlist[].
Dac un apel sistem eueaz variabila errno indic codul erorii.
Aceste valori pot fi gsite n <errno.h>. Funcia perror servete
la afiarea acestui cod de eroare ntr-o form lizibil. Dac un apel
terminat cu eroare nu este imediat urmat de un apel perror,
valoarea variabilei errno se poate pierde dac nu e salvat.
Nume
clearerr, feof, ferror - verific i reseteaz starea
fluxului
Declaraie
void clearerr(FILE *flux);
__________________________________________________________________________
152
int feof(FILE *flux);
int ferror(FILE *flux);
int fileno( FILE *flux);
Descriere
Funcia clearerr terge indicatorii de sfrit de fiier i eroare
ai fluxului.
Funcia feof testeaz indicatorul de sfrit de fiier al fluxului,
i returneaz non-zero dac este setat. Acesta este setat dac o
operaie de citire a detectat sfritul de fiier.
Funcia ferror testeaz indicatorul de eroare al fluxului, i
returneaz non-zero dac este setat. Acesta este setat dac o operaie
de citire sau scriere a detectat o eroare (datorat de exemplu
hardware-ului).
Funciile de citire (cu sau fr format) nu fac distincie ntre
sfrit de fiier i eroare, astfel c trebuie apelate funciile feof i
ferror pentru a determina cauza.
Funcia fileno examineaz argumentul flux i returneaz
descriptorul asociat de sistemul de operare acestui flux.
Atenie! Este foarte frecvent folosirea incorect a funciei feof
pentru a testa dac s-a ajuns la sfritul fiierului. Nu se recomand n
nici un caz acest stil de programare:
#define LSIR 80
char lin[LSIR];
FILE *fi,*fo;
fi=fopen(nume-fiier-intrare,"rt");
fo=fopen(nume-fiier-ieire,"wt");
while (!feof(fi)) { /* greit! */
fgets(lin,LSIR,fi);
fputs(lin,fo);
}
fclose(fi); fclose(fo);
n aceast secven, dac i ultima linie a fiierului text de intrare
este terminat cu new-line, aceasta va fi scris de dou ori n fiierul
de ieire. De ce? Dup ce se citete ultima linie nc nu este
__________________________________________________________________________
153
poziionat indicatorul de sfrit de fiier, deci funcia fgets
returneaz succes. La reluarea ciclului se ncearc un nou fgets i
abia acum se depisteaz sfritul de fiier, fapt marcat n zona
rezervat fluxului fi. Astfel coninutul tabloului lin rmne
nemodificat i este scris a doua oar n fiierul de ieire. Abia la o
nou reluare a ciclului funcia feof ne spune c s-a depistat sfritul
de fiier.
n acest manual snt prezentate mai multe programe care
efectueaz diferite prelucrri asupra unor fiiere text. Pentru
simplitate toate programele presupun c nu apar erori la citire sau la
scriere.
Nume
opendir - deschide un director
Declaraie
DIR *opendir(const char *nume);
Descriere
Funcia opendir deschide un flux pentru directorul cu numele
nume, i returneaz un pointer la fluxul deschis. Fluxul este
poziionat pe prima intrare din director.
Valoare returnat
Funcia returneaz un pointer la flux n caz de succes, sau NULL
n caz de eroare i variabila global errno indic codul erorii.
__________________________________________________________________________
154
Cteva erori posibile
EACCES Acces interzis
ENOTDIR nume nu este un director
Nume
readdir - citete un director
Declaraie
struct dirent *readdir(DIR *dir);
Descriere
Funcia readdir returneaz un pointer la o structur de tip
dirent care reprezint urmtoarea intrare din directorul indicat de
fluxul dir. Returneaz NULL dac s-a depistat sfritul de director
sau dac a aprut o eroare.
Structura de tip dirent conine un cmp char d_name[].
Utilizarea altor cmpuri din structur reduce portabilitatea
programelor.
Valoare returnat
Funcia returneaz un pointer la o structur de tip dirent, sau
NULL dac s-a depistat sfritul de director sau dac a aprut o
eroare.
Nume
closedir - nchide un director
Declaraie
int closedir(DIR *dir);
Descriere
Funcia closedir nchide fluxul dir.
Valoare returnat
Funcia returneaz 0 n caz de succes sau EOF n caz de eroare.
__________________________________________________________________________
155
Nume
rename - redenumete un fiier
remove - terge un fiier
Declaraie
int rename(const char *old, const char
*new);
int remove(const char *name);
Descriere
Funcia rename schimb numele unui fiier din old n new.
Dac a fost precizat un periferic n new, acesta trebuie s coincid cu
cel din old. Directoarele din old i new pot s fie diferite, astfel c
rename poate fi folosit pentru a muta un fiier dintr-un director n
altul. Nu se permit specificatori generici (wildcards).
Funcia remove terge fiierul specificat prin name.
Valoare returnat
n caz de succes se returneaz 0. n caz de eroare se returneaz
EOF i variabila global errno indic codul erorii.
__________________________________________________________________________
159
12. Alte rutine din biblioteca standard
__________________________________________________________________________
161
Funcia qsort sorteaz un tablou de nel elemente, fiecare de
mrime size. Argumentul base indic spre nceputul tabloului.
Elementele tabloului snt sortate n ordine cresctoare n
concordan cu funcia de comparare referit de comp, apelat cu
dou argumente care indic spre obiectele ce se compar. Funcia de
comparare trebuie s returneze un ntreg mai mic dect, egal cu, sau
mai mare dect zero dac primul argument este considerat a fi mai
mic dect, egal cu, respectiv mai mare dect al doilea. Dac cele dou
elemente comparate snt egale, ordinea n tabloul sortat este
nedefinit.
Funcia bsearch caut ntr-un tablou de nel elemente, fiecare
de mrime size, un membru care coincide cu obiectul indicat de
key. Argumentul base indic spre nceputul tabloului.
Coninutul tabloului trebuie s fie sortat cresctor n concordan
cu funcia de comparare referit de comp, apelat cu dou argumente
care indic spre obiectele ce se compar. Funcia de comparare
trebuie s returneze un ntreg mai mic dect, egal cu, sau mai mare
dect zero dac primul argument este considerat a fi mai mic dect,
egal cu, respectiv mai mare dect al doilea.
Valoare returnat
Funcia bsearch returneaz un pointer la un membru al
tabloului care coincide cu obiectul indicat de key, sau NULL dac nu
se gsete nici un membru. Dac exist mai multe elemente care
coincid cu key, poate fi returnat oricare element cu aceast
proprietate.
__________________________________________________________________________
163
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 0 1 2 3 4 5 6 7
8 9 a b c d e f A B C D E F.
tolower
Convertete caracterul c, dac este o liter, la litera mic
corespunztoare.
toupper
Convertete caracterul c, dac este o liter, la litera mare
corespunztoare.
Valoare returnat
Valoarea returnat de funciile is... este nenul dac caracterul c
se afl n clasa testat, i zero n caz contrar.
Valoarea returnat de funciile to... este litera convertit dac
caracterul c este o liter, i nedefinit n caz contrar.
__________________________________________________________________________
164
Nume
memcpy - copiaz o zon de memorie
Declaraie
void *memcpy(void *dest, const void *src,
unsigned n);
void *memmove(void *dest, const void *src,
unsigned n);
Descriere
Funcia memcpy copiaz n octei din zona de memorie src n
zona de memorie dest. Zonele de memorie nu trebuie s se
suprapun. Dac exist acest risc se utilizeaz memmove.
Valoare returnat
Funciile returneaz un pointer la dest.
Nume
memcmp - compar dou zone de memorie
Declaraie
int memcmp(const void *s1, const void *s2,
unsigned n);
Descriere
Funcia memcmp compar primii n octei ai zonelor de memorie
s1 i s2.
Valoare returnat
Returneaz un ntreg mai mic dect, egal cu, sau mai mare dect
zero dac s1 este mai mic dect, coincide, respectiv este mai mare
dect s2.
Nume
memset - umple o zon de memorie cu o constant pe un
octet
Declaraie
__________________________________________________________________________
165
void *memset(void *s, int c, unsigned n);
Descriere
Funcia memset umple primii n octei ai zonei de memorie
indicat de s cu constanta c pe un octet.
Valoare returnat
Funcia returneaz un pointer la zona de memorie s.
Nume
memchr - caut n memorie un caracter
Declaraie
void *memchr(const void *s, int c,
unsigned n);
Descriere
Funcia memchr caut caracterul c n primii n octei de memorie
indicai de s. Cutarea se oprete la primul octet care are valoarea c
(interpretat ca unsigned char).
Valoare returnat
Funcia returneaz un pointer la octetul gsit sau NULL dac
valoarea nu exist n zona de memorie.
Nume
strlen - calculeaz lungimea unui ir
Declaraie
unsigned strlen(const char *s);
Descriere
__________________________________________________________________________
166
Funcia strlen calculeaz lungimea irului s, fr a include
caracterul terminator null.
Valoare returnat
Funcia returneaz numrul de caractere din s.
Nume
strcpy, strncpy - copiaz un ir de caractere
Declaraie
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src,
unsigned n);
Descriere
Funcia strcpy copiaz irul indicat de src (inclusiv
caracterul terminator null) n zona indicat de dest. irurile nu
trebuie s se suprapun, i n plus zona dest trebuie s fie suficient
de mare pentru a primi copia.
Funcia strncpy este similar, cu excepia faptului c nu se
copiaz mai mult de n octei din src. Astfel, dac caracterul
terminator null nu se afl n primii n octei din src, rezultatul nu va
fi terminat cu null. n cazul n care lungimea lui src este mai mic
dect n, restul octeilor din dest primesc valoarea null.
Valoare returnat
Funciile returneaz un pointer la irul dest.
Nume
strdup - duplic un ir
Declaraie
char *strdup(const char *s);
Descriere
Funcia strdup returneaz un pointer la un nou ir care este un
duplicat al irului s. Memoria pentru noul ir se obine cu malloc,
i poate fi eliberat cu free.
__________________________________________________________________________
167
Valoare returnat
Funcia returneaz un pointer la irul duplicat, sau NULL dac nu
exist memorie suficient disponibil.
Nume
strcat, strncat - concateneaz dou iruri
Declaraie
char *strcat(char *dest, const char *src);
char *strncat(char *dest, const char *src,
unsigned n);
Descriere
Funcia strcat adaug irul src la irul dest suprascriind
caracterul null de la sfritul lui dest, i la sfrit adaug un caracter
terminator null. irurile nu trebuie s se suprapun, i n plus irul
dest trebuie s aib suficient spaiu pentru a pstra rezultatul.
Funcia strncat este similar, cu excepia faptului c numai
primele n caractere din src se adaug la dest.
Valoare returnat
Funciile returneaz un pointer la irul rezultat dest.
Nume
strcmp - compar dou iruri de caractere
Declaraie
int strcmp(const char *s1, const char
*s2);
Descriere
Funcia strcmp compar cele dou iruri s1 i s2.
Valoare returnat
Funcia returneaz un ntreg mai mic dect, egal cu, sau mai mare
dect zero dac s1 este mai mic dect, coincide, respectiv este mai
mare dect s2.
__________________________________________________________________________
168
Nume
strchr, strrchr - localizeaz un caracter
Declaraie
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
Descriere
Funcia strchr returneaz un pointer la prima apariie a
caracterului c n irul s.
Funcia strrchr returneaz un pointer la ultima apariie a
caracterului c n irul s.
Valoare returnat
Funciile returneaz un pointer la caracterul gsit sau NULL dac
valoarea nu a fost gsit.
Nume
strstr - localizeaz un subir
Declaraie
char *strstr(const char *sir, const char
*subs);
Descriere
Funcia strstr gsete prima apariie a subirului subs n
irul sir. Caracterul terminator null nu este luat n considerare.
Valoare returnat
Funcia returneaz un pointer la nceputul subirului, sau NULL
dac subirul nu este gsit.
Nume
strspn, strcspn - caut un set de caractere ntr-un ir
Declaraie
unsigned strspn(const char *s, const char
__________________________________________________________________________
169
*acc);
unsigned strcspn(const char *s, const char
*rej);
Descriere
Funcia strspn calculeaz lungimea segmentului iniial din s
format n ntregime numai cu caractere din acc.
Funcia strcspn calculeaz lungimea segmentului iniial din s
format n ntregime numai cu caractere care nu se gsesc n rej.
Valori returnate
Funcia strspn returneaz poziia primului caracter din s care
nu se afl n acc.
Funcia strcspn returneaz poziia primului caracter din s care
se afl n rej.
__________________________________________________________________________
172
12.7. Programe demonstrative
1) Programul prezentat n continuare genereaz un ir de n valori
ntregi aleatoare n intervalul [0,M1] pe care le depune n tabloul X
(alocat dinamic), i apoi le sorteaz cresctor. n continuare se
genereaz k valori ntregi aleatoare pe care le caut n tabloul X.
Pentru fiecare cutare cu succes se afieaz pe terminal valoarea
cutat i poziia n tablou.
Valorile n, k i M se iau n aceast ordine din linia de comand.
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int cmp(const void *A, const void *B) {
return *(int *)A-*(int *)B;
}
int main(int ac, int **av) {
int *X,*p,M,n,k,i,v;
if (ac!=4) {
fputs("Trei argumente!\n",stderr);
return 1;
}
n=atoi(av[1]); k=atoi(av[2]);
M=atoi(av[3]);
X=(int *)malloc(n*sizeof(int));
if (!X) return 1;
srand(time(NULL));
for (i=0; i<n; i++)
X[i]=rand()%M;
qsort(X,n,sizeof(int),cmp);
for (i=0; i<k; i++) {
v=rand()%M;
p=(int *)bsearch(&v,X,n,sizeof(int),
cmp);
if (p)
printf("Val: %d Pos: %d\n",v,p-X);
}
__________________________________________________________________________
173
free(X);
return 0;
}
__________________________________________________________________________
177
Bibliografie
Brian W Kernigham, Dennis M Ritchie - The C Programming Language
Prentice-Hall Software Series, 1978
- Limbajul C; manual de programare
Institutul de tehnic de calcul, Cluj-Napoca 1984
Herbert Schildt - Manual C complet
Editura Teora, 1998
Manuale electronice
http://www.programmingtutorials.com/c.html
Marshall Brain - Introduction to C Programming
http://devcentral.iftech.com/learning/tutorials/c-cpp/c/
Steve Summit - Introductory C Programming Class Notes
http://www.eskimo.com/~scs/cclass/cclass.html
Steve Summit - Intermediate C Programming Class Notes
http://www.eskimo.com/~scs/cclass/cclass.html
Brian Brown - C Programming
http://www.cit.ac.nz/smac/cprogram/onlinet.htm
Brian Brown - An Introduction to C Programming
http://www.cit.ac.nz/smac/cprogram/default.htm
__________________________________________________________________________
178
Cuprins
__________________________________________________________________________
182