|
Variabilelesi constantele sint obiectele - date de baza manipulate intr-un program. Declaratiile listeaza variabilele ce se vor folosi si specifica tipul lor si probabil, valorile lor initiale. Operatorii specifica ce trebuie facut cu ele. Expresiile combina variabile si constante pentru a produce valori noi. Toate acestea constituie subiectul acestui capitol.
Cu toate ca nu am spus-o pina acuma, exista unele restrictii asupra numelor de constante si variabile.Numele sint alcatuite din litere si cifre; primul caracter trebuie sa fie o litera. Liniuta de subliniere '_' este considerata litera; ea este utila in usurarea citirii numelor lungi de variabile. Literele mari si mici sint caractere distincte; practica traditionala in C foloseste literele mici pentru nume de variabile si literele mari pentru constantele simbolice.
Numai primele opt caractere ale unui nume intern sint semnificative, cu toate ca se pot folosi mai multe. Pentru numele externe, de exemplu nume de functii si de variabile externe, numarul de caractere poate sa fie mai mic ca 8, deoarece numele externe sint folosite de diferite asambloare si incarcatoare. In Anexa A se dau detalii. Mai mult, cuvinte cheie ca: if, else, int, etc sint rezervate: nu pot fi folosite ca nume de variabile (trebuie sa fie scrise cu litere mici).
Natural, e intelept sa alegem numele de variabile astfel incit sa insemne ceva,legat de scopul variabilei, si e neplacut sa amestecam litere mari cu mici.
Exista numai citeva tipuri de date de baza in limbajul C:
charunsingur octet, capabil sa pastreze un caracter din setul local de caractere
int unintreg,reflectind tipic marimeaefectivaa intregilorpe calculatorul gazda
float numar flotant in simpla precizie
double numar flotant in dubla precizie.
Inplus,exista un numar de calificatori care pot fi aplicati tipului 'int': short, long si unsigned. short si long se refera la diferite marimi de intregi. Numerele 'unsigned' se supun legilor aritmeticii modulo 2^n unde n este numarul debitidintr-un int;ele sint intodeauna pozitive. Declaratiile pentru calificatori arata astfel:
short int x;
long int y;
unsigned int z;
Cuvintul int poate fi omis in astfel de situatii, ceea ce se si intimpla de obicei.
Preciziaacestor obiecte depinde de calculatorul care le minuieste; tabelul urmator da citeva valori reprezentative:
DEC PDP11 Honeywell 6000 IBM/370 Interdata 8/32
ASCII ASCII EBCDIC ASCII
char 8 biti 9 biti 8 biti 8 biti
int 16 36 32 32
short 16 36 16 16
long 32 36 32 32
float 32 36 32 32
double 64 72 64 64
Intentia e ca short si long sa aiba lungimi diferite de intregi unde e practic; int reflecta normal, cea mai 'naturala' lungime pentru un calculator. Asa cum puteti vedea, fiecare compilator este liber sa interpreteze short si long in functie de hardul pe care se executa. Ceea ce trebuie sa notati este ca short nu este niciodata mai lung decit long.
Constantele int si float au fost deja expuse; notam in plus ca notatia uzuala
123.456e-7
sau notatia stiintifica
0.12E3
pentrunumerele flotante sint ambele legale. Orice constanta flotanta este considerata ca fiind de tipul double, asa ca notatia 'e' serveste atit pentru float cit si pentru double. Constantele lungi sint scrise in stilul 123L. O constanta intreaga normala care este prea lunga pentru un int, este luata deasemenea ca fiind o constanta long.
Existao notatie speciala pentru constantele octale si hexazecimale: un 0 (zero) la inceputul unei constante int inseamna octal; un 0x sau 0X la inceputul unei constante int inseamna hexazecimal. De exemplu, numarul zecimal 31 poatefi scris037 in octal si 0x1f sau 0X1F in hexazecimal. Constantele octale si hexazecimale pot fi urmate un L pentru a le face 'long'.
Oconstanta caracter este un caracter singur scris intre ghilimele simple ca, de exemplu, 'x'. Valoarea unei constante caracter este valoarea numerica a caracterului in setul de caractere al calculatorului. De exemplu, in setul de caractere ASCII, caracterul zero, sau '0', are valoarea 48, iar in EBCDIC, 240, amindoua valorile fiind diferite de valoarea numerica 0. Scriind '0' in loc de o valoare numerica de tipul 48 sau 240, facem programul independent de o valoareparticulara.Constantele caracter participa in operatiile numerice la fel ca oricare alte numere, cu toate ca cel mai adesea ele sint folosite in compararicualte caractere. O sectiune viitoare va trata toate regulile de conversie.
Anumitecaractere negrafice pot fi reprezentate constante caracter cu ajutorul secventelor escape, de exemplu n (linie noua),t (tab), 0 (nul), (backspace), '(ghilimea simpla) etc, care arata ca doua caractere, dar de fapt sint unul singur. Inplus, se poate genera orice model de lungime un octet, scriind:
'ddd'
unde 'ddd' reprezinta 1 - 3 cifre octale, ca in
#define FORMFEED '014' /* ASCII formfeed */
Constantacaracacter '0' reprezinta caracterul ce are valoarea '0' se scrie adesea in locul lui 0 pentru accentua natura caracter a anumitor expresii.
O expresie constanta este o expresie care implica numai constante. Astfel de expresii sint evaluate la compilare si nu la executie si ele pot fi folosite in orice loc in care poate apare o constanta, ca in
#define MAXLINE 1000
char line[MAXLINE+1];
sau
seconds = 60 * 60 * hours;
O constanta-sir este o secventa compusa din zero sau mai multe caractere intre ghilimele duble, ca
'I am a string'
sau
''/* un sir nul */
Ghilimeleleduble nu sint parte a sirului ci servesc doar ca delimitatori. Aceleasi secvente escape folosite pentru constantele caracter se aplica si la siruri; ' reprezinta caracterul dubla ghilimea.
Tehnic, un sir este un tablou ale carui elemente sint caractere. Compilatorul plaseaza automat un caracter nul 0 la sfirsitul oricarui astfel de sir, astfel ca programele pot determina lesne sfirsitul sirului. Aceasta reprezentare spune ca nu exista o limita reala pentru lungimea unui sir, dar programele trebuie sa parcurga tot sirul pentru a-i determina lungimea. Memoria fizica ceruta este cu o locatie mai mult decit numarul de caractere scrise intre ghilimele duble. Functia urmatoare, strlen(s) returneaza lungimea unui sir de carctere s, exclusiv terminatorul 0.
strlen(s) /* returneaza lungimea lui s */
char s[];
Trebuiedistinsintre o constanta caracter si un sir care contine un singur caracter:'x' si 'x' nu sint acelasi lucru. Primul este un caracter, folosit pentru aproducevaloarea numerica a caracterului x din setul de caractere al calculatorului; al doilea este un sir de caractere care contine un singur caracter (litera x) si un 0.
Toatevariabilele trebuie declarate inainte de a fi folosite , cu toate ca anumite declaratii pot fi facute implicit de context. O declaratie specifica un tip si este urmata de o lista de una sau mai multe variabile de acel tip, ca in exemplul de mai jos:
int lower, upper, step;
char c, line[1000];
Variabilele pot apare oricum printre declaratii. Lista de mai sus poate fi scrisa, in mod egal, si astfel:
int lower;
int upper;
int step;
char c;
char line[1000];
Aceastaultima forma ocupa mai mult spatiu dar este mai comoda pentru a adauga cite un comentariu la fiecare declaratie sau pentru modificari ulterioare. Variabilele pot fi, deasemenea, initializate in declaratia lor, cu toate ca exista anumite restrictii.Daca numele este urmat de un semn egal si de o constanta, aceasta serveste la initializare, ca in:
char backslash = '';
int i = 0;
float eps = 1.0e-5;
Daca variabila in chestiune este externa sau statica, initializarea este facuta o singura data,conceptual inainte ca programul sa-si inceapa executia. Variabilele automate initializate explicit sint initializate la fiecare apel al functiei incare sint continute. Variabilele automate pentru care nu exista o initializare explicita au valoare nedefinita (adica gunoi). Variabilele externe si statice se initializeaza implicit cu zero dar este un bun stil de programare acela de a declara initializrea lor
in orice caz.
Vomdiscutainitializarile mai departe pe masura ce se introduc noi tipuri de date.
Operatorii aritmetici binari sint '+', '-', '*', '/' si operatorul modulo '%'. Exista operatorul '-' unar dar nu exista operatorul unar '+'. Impartirea intregilor trunchiaza orice parte fractionara. Expresia
x % y
producerestulcind x se imparte la y si deci este zero cind impartirea este exacta.De exemplu, un an este bisect daca este divizibil cu 4 si daca nu este divizibil cu 100, insa anii divizibili cu 400 sint bisecti. Deci
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
it's a leap year
else
it's not
Operatorul % nu poate fi aplicat la float sau double.
Operatorii + si - au aceeasi pondere,care este mai mica decit ponderea (identica) a lui *,/ si % care la rindul ei este mai mica decit ponderea operatoruluiunar -. Operatorii aritmetici se grupeaza de la stinga la dreapta (Tabela de la sfirsitul capitolului rezuma ponderea si asociativitatea pentru totioperatorii).Ordinea de evaluare nu este specificata pentru operatorii asociativi si comutativi de tipul lui * si +. Compilatorul poate rearanja un calcul cuparantezeimplicind unul din acestia. Astfel, a+(b+c) poaate fi evaluat ca (a+b)+c. Acest lucru produce rar diferente dar daca se cere o ordine particulara, trebuie folosite explicit variabilele temporare.
Actiunile care produc depasiri superioare sau inferioare depind in ultima instanta de calculator.
Operatorii relationali sint > >= < <=. Ei au toti aceasi pondere. Sub ei in tabelul de ponderi se afla operatorii de egalitate == != , care au o aceeasi pondere. Operatorii relationali au ponderea mai mica decit cei aritmetici, asa ca expresii de tipul i < lim-1 se evalueaza ca i < (lim-1), asa cum ar fi de asteptat.
Mai interesanti sint conectorii logici && si ||. Expresiile care-i contin sint evaluate de la stinga la dreapta si evaluarea se opreste in clipa in care se cunoaste adevarul sau falsul rezultatului. Aceste proprietati se dovedesc critice in scrierea programelor. De exemplu iata o bucla luata din functia de intrare getline, pe care am scris-o in Capitolul 1:
for (i=0; i<lim-1 && (c = getchar()) != 'n' && c != EOF;++i)
s[i] = c;
Inmodclar, inainte de a citi un nou caracter trebuie vazut daca mai exista loc pentru a-l depune in tabloul s , asa ca testul i<lim-1 trebuie facut in primul rind. Nu numai atit dar daca testul esueaza, nu trebuie sa mai citim un nou caracter. Similar,ar fi nepotrivit sa testam daca c este EOF inainte de apelul lui getchar; apelul trebuie sa aiba loc inainte ca sa testam caracterul c.
Ponderealui&& este mai mare decit cea a lui || si amindoua sint mai mici decit cele ale operatorilor relationali si de egalitate, asa ca expresii de tipul
i<lim-1 && (c = getchar()) != 'n' && c != EOF
nu mai au nevoie de paranteze suplimentare. Dar, deoarece ponderea lui != este mai mare decit cea a asignarii, este nevoie de paranteze in
(c = getchar()) != 'n'
pentru a obtine rezultatul dorit.
Operatorul unar de negatie '!' converteste un operand nonzero sau adevarat in zero si un operand zero sau fals in 1. O utilizare obisnuita a lui ! este in constructii de tipul
if (!inword)
mai degraba decit
if (inword == 0)
Este mai greu sa generalizam care forma este mai buna. Constructiile de tipul !inword arata mai frumos ('daca nuein cuvint'), dar constructiile mai complicate pot fi greu de inteles.
Exercitiul 1. Scrieti o bucla echivalenta cu bucla for demai sus fara a folosi &&.
Cindintr-o expresiie apar operanzi de mai multe tipuri, ei se convertesc intr-un tip comun, dupa un numar mic de reguli. In general, singurele conversii care se fac automat sint acelea cu sens , de exemplu convertirea unui numar intreg intr-un flotant in expresii de tipul f + i. Expresiile fara sens, de exemplu folosirea unui float ca indice de tablou, nu sint permise.
In primul rind, char-i si int-i pot fi amestecati in expresiile aritmetice: orice char este convertit automat intr-un int. Aceasta permite o flexibilitate remarcabilainanumite tipuri de transformari de caractere. Exemplificam cu functia atoi, care converteste un sir de cifre in echivalentul lui numeric.
atoi(s) /* converteste un sir s in intreg */
char s[];
Asa cum am vazut in Capitolul 1, expresia
s[i]-'0'
reprezinta valoarea numerica a caracterului aflat in s[i] deoarece valorile lui '0','1', etc formeaza un sir crescator pozitiv si contiguu.
Unalt exemplu de conversie intre char si int il constituie functia lower care transforma literele mari din setulde caractere ASCII in litere mici. Daca intrarea nu este o litera mare, functia o returneaza neschimbata:
lower(c) /* conversie ASCII litere mari in litere mici */
int c;
Aceastafunctie este valabila numai pentru ASCII deoarece pe de o parte intre literele mari si literele mici exista o distanta fixata, ca valoare numerica, iar pe de altaparte ambele alfabete sint contigue - intre A si Z se gasesc numailitere. Aceastaultima observatie nu este valabila pentru setul de caractere EBCDIC (IBM 360/370), asa incit functia lower esueaza pentruaceste sisteme, ea va converti mai mult decit literele mari.
Exista o subtilitate in conversia caracterelor in intregi. Limbajul nu specifica daca o variabila de tip char este o cantitate cu semn sau fara semn. Cind un char este convertit intr-un int, poate el produce un intreg negativ ? Din pacate,aceasta variaza de la calculator la calculator,reflectind diferentele arhitecturale. Pe anumite calculatoare (de exemplu PDP-11) un char alcarui cel mai din stinga bit este 1 va fi convertit intr-un intreg negativ ('extensie de semn'.Pealtele,un
charesteconvertit intr-un int prin adaugarea de zerouri in partea stinga si astfel el este intodeauna pozitiv.
Definitia lui C asigura ca orice caracter din setul standard al masinii nu va fi niciodata negativ, asa ca aceste caractere pot fi folosite liber in expresii ca si cantitati pozitive.
Dar modele arbitrare de biti memorate in variabile de tip characterpotapare drept negative pe anumitecalculatoare si drept pozitive pe altele.
Cea mai comuna aparitie a acestei situatii este cind pentru EOF se foloseste -1. Sa consideram codul:
char c;
c = getchar();
if (c == EOF)
Peuncalculator care nu face extensie de semn, c este intodeauna pozitiv deoarece el este un char, dar totusi EOF este negativ. In consecinta testul esueaza intodeauna. Pentru a evita aceasta, trebuie sa avem grija atunci cind folosim int in locdecharpentru orice variabila care primeste o valoare returnata de getchar.
Adevarata ratiune pentru utilizarea lui int in loc de char nu este legata cu nimic de posibila extensie de semn. Pur si simplu, getchar trebuie sa returneze toate caracterele posibile (astfel incit sa poate fi folosita pentru a citi o intrare arbitrara) si in plus, o valoare pentru EOF distincta. Astfel, aceasta valoarenu poate fi reprezentata ca si un char dar, in schimb, trebuie memorata ca si un int.
Oalta forma utila de conversie de tip automata este aceea ca expresiile relationale de tipul i > jsiexpresiile logiceconectate prin && si || se definesc a avea valoarea 1 pentru adevar si 0 pentru fals. Astfel, o asignare:
isdigit = c >= '0' && c <= '9';
pune pe isgit pe 1 daca c este o cifra si pe 0 daca nu. (In partea de test a lui if, while ,for, etc, 'adevarat' inseamna 'nonzero').
Conversiile aritmetice implicite lucreaza in mare masura cum ne asteptam. In general, daca un operator ca + sau * care are doi operanzi (un 'operator binar')are operanzi de tipuri diferite, tipul 'inferior' este promovat la tipul 'superior' inainte de executia operatiei. Rezultatul insusi este de tipul superior.Mai precis, pentru fiecare operator aritmetic, se aplica urmatoarea secventa de reguli de conversie:
char si short se convertesc in int iar float este convertit in double.
Apoi, daca un operand este double , celalalt este convertit in double iar rezultatul este double.
Altfel, daca un operand este long, celalalt este convertit in long iar rezultatul este long.
Altfel, daca un operand este unsigned, celalalt este convertit inunsigned, iar rezultatul este unsigned.
Altfel, operanzii trebuie sa fie int, iar rezultatul este int.
Sa notam ca toti float dintr-o expresie sint convertiti in double; orice calcul flotant in C este facut in dubla precizie.
Conversiilese fac in asignari; valoarea partii drepte este convertita la tipul din stinga,care este tipul rezultatului.Un caracter este convertit intr-un int fie cu extensiedesemn, fie nu, asa cum s-a descris mai sus. Operatia inversa, int in char, se comporta bine, pur si simplu, bitii de ordin superior in exces sint eliminati. Astfel, in:
int i;
char c;
i = c;
c = i;
valoarea lui c ete neschimbata. Acesta este adevarat si cind extensia de semn este implicita si cind nu este implicita.
Daca x este float iar i este int, atunci:
x = i;
si
i = x;
provoaca amindoua conversii; float in int provoaca trunchierea oricarei parti fractionare. double este convertit in float prin rotunjire. Intregii lungi sint convertiti in scurti sau in char prin pierderea bitilor de ordin superior in exces. Deoarece argumentul unei functii este o expresie, conversia de tip are loc deasemenea si cindargumentele sint pasate functiei in particular, char si short devinint,iar floatdevine double. Iata de ce am declarat argumentul functiei ca fiind int si double chiar cind functia este apelata cu char si float.
In final, conversia explicita de tip poate fi fortata in orice expresie cu o constructie numita 'distribuire'(cast). In constructia:
(numedetip) expresie
sus. Semnificatia precisa a unei distribuiri este de fapt ca si daca o expresie ar fi asignata la o variabila de tipul specificat, care este apoi folosita in locul intregii constructii. De exemplu, rutina din biblioteca sqrt are nevoie de un argument double si va produce nonsens daca i se da sa minuiasca altceva. Astfel, daca n este un intreg:
sqrt((double) n)
il converteste pe n in double inainte de a-l pasa lui sqrt. (De notat ca distribuirea produce valoarea n in tipul potrivit; continutul efectiv al lui n nu este alterat ). Operatorul de distribuire are acceasi pondere ca si alti operatori unari,asa cum apare si in tabelul recapitulativ de la sfirsitul capitolului.
Exercitiul Scrieti o functie htoi(s) care converteste un sir de cifre hexazecimale in valoarea sa intreaga echiva lenta. Cifrele sint de la 0 la 9, literele de la a la f si de laA la F.
LimbajulC ofera doi operatori neuzuali pentru incrementarea si decrementarea variabilelor.Operatorul de incrementare ++ aduna 1 la operandul sau; operatorul de decrementare-- scade1.Am folosit frecvent ++ pentru a incrementa variabilele, de exemplu:
if (c == 'n')
++nl;
Aspectulneobisnuit al lui ++ si al lui -- este acela ca ei pot fi folositi atit ca operatori prefix (inaintea variabilei, ca in ++n) cit si ca operatori sufix (dupa variabila, ca in n++). In ambele cazuri, efectul este incrementarea lui n. Dar expresia ++n il incrementeaza pe n inainte de a-i folosi valoarea, in timp ce expresia n++, il incrementeaza pe n dupa ce a fost folosita valoarea lui.Aceasta inseamna ca intr-un context in care valoarea este folosita, si nu numai efectul, ++n si n++ sint diferiti. Daca n este 5, atunci:
x = n++;
il face pe x egal cu 5, dar
x = ++n;
il face pe x egal cu 6.In ambele cazuri, n devine6.
Operatorii de incrementare si decrementare se pot aplica numai variabilelor. O expresie de tipul x = (i+j)++ este ilegala.
Intr-un context in care valoarea nu este folosita, ci numai efectul de incrementare, ca in
if (c == 'n')
nl++;
alegetimodul prefix sau sufix dupa gustul dumneavoastra. Dar exista totusi situatii in care unul sau altul esteapelat din adins. De exemplu, sa consideram functia squeeze(s,c) care elimina toate aparitiile lui c din sirul s:
squeeze (s,c) /* sterge toate aparitiile lui c din s */
char s[];
int c;
De fiecare data cind apare un caracter non-c el este copiat in pozitia j curenta si numai dupa aceea j este incrementat pentru a fi gata pentru urmatorul caracter. Aceasta constructie este echivalenta cu urmatoarea:
if (s[i] != c)
Unaltexemplu de constructie similara este luata din functia getline pe care am scris-o in Capitolul 1, in care putem inlocui
if (c == 'n'
cu mult mai compacta constructie:
if (c == 'n')
s[i++] = c;
Ca un al treilea exemplu functia strcat(s,t) care concateneaza sirul t la sfirsitul sirului s. strcat presupune ca exista
suficient spatiu in s pentru a pastra combinatia.
strcat (s,t)/* concateneaza pe t la sfirsitul lui s */
char s[], t[]; /* s trebuie sa fie suficient de mare */
Cum fiecare caracter este copiat din t in s, se aplica postfixul ++ atit lui i cit si lui j pentru a fi siguri ca sint pe pozitie pentru urmatorul pas din bucla.
Exercitiul 3. Scrieti o alta versiune a lui squeeze(s1, s2) care sterge fiecare caracter din s1 care se potriveste cuvreun caracter din s
Exercitiul4. Scrieti functia any(s1, s2) care returneazaprima locatie din sirul s1 in care apare vreun caracter din sirul s2, sau pe -1 daca s1 nu contine nici un caracter din s
LimbajulC ofera un numar de operatori pentru manipularea bitilor; acestia nu se pot aplica lui float si double.
&SI bit cu bit
|SAU inclusiv bit cu bit
^SAU exclusiv bit cu bit
<<deplasare stinga
>>deplasare dreapta
~complement fata de 1 (unar)
Operatorul SI bit cu bit '&' este folosit adesea pentru a masca anumite multimi de biti; de exemplu
c = n & 0177;
pune pe zero toti biti lui n, mai putin bitul 7 (cel mai tare).
Operatorul SAU bit cu bit '|' este folosit pentru a pune pe 1 biti:
x = x | MASK;
pune pe 1 in x bitii care sint setati pe 1 in MASK.
Trebuie sa distingeti cu grija operatorii pe biti & si | de conectorii logici && si ||, care implica o evaluare de la stinga la dreapta a unei valori de adevar.De exemplu, daca x este 1 si y este 2, atunci x & y este zero dar x && y este 1. (De ce ?)
Operatorii de deplasare << si >> realizeaza deplasari la stinga si la dreapta pentru operandul lor din stinga, cu numarul de pozitii dat de operandul din dreapta lor. Astfel x << 2 deplaseazala stinga pe x cu doua pozitii, umplind locurile libere cu zero; aceasta este echivalent cu inmultirea cu 4. Deplasind la dreaptao cantitate unsigned, bitii vacanti se umplu cu zero. Deplasind la dreapta o cantitate cu semn,bitii vacanti se umplu cu semnul ('deplasarea aritmetica') pe anumite calculatoare, ca de exemplu PDP-11 si cu 0 ('deplasare logica') pe altele.
Operatorul unar ~ da complementul fata de 1 al unui intreg; adica, el converteste fiecare bit de 1 in 0 si vicevesa. Acest operator isi gaseste utilitate in expresii de tipul
x & ~077
care mascheaza ultimii 6 biti ai lui x pe 0. De notat ca x & ~077 este independent de lungimea cuvintului si deci preferabil, de exemplu, luix & 0177700, care presupune ca x este
o cantitate cu o lungime de 16 biti. Forma portabila nu implica un cost mai mare, deoarece ~077 este o expresie constanta si deci evaluata la compilare.
Pentrua ilustra folosirea unora din operatorii de biti, sa consideram functia getbits(x,p,n)care returneaza (cadrat la dreapta) cimpul de lungime n biti al lui x care incepe la pozitia p. Presupunem ca bitul 0 este cel mai din dreapta si ca n si p sintvalori pozitivesensibile. De exemplu, getbits(x,4,3) returneaza 3 biti in pozitiile 4, 3 si 2, cadrati la dreapta.
getbits (x, p, n) /* ia n biti de la pozitia p */
unsigned x, p, n;
x>>(p+1-n) muta cimpul dorit la sfirsitul din dreapta al cuvintului. Declarind argumentul x ca fiind unsigned ne asiguram ca atunci cind el este deplasat la dreapta bitii vacanti vor fi umpluti cu 0 si nu cu bitii de semn, independent de calculatorul pe care este executat programul. ~0 este cuvintul cu toti bitii pe 1; deplasindu-l la stinga cu n pozitii prin ~0 << n cream o masca cu zerouri pe cei mai din dreapta n biti si 1 in rest; complementindu-l cu ~ facem o masca de 1 pe cei mai din dreapta n biti.
Exercitiul 5.Modificati getbits pentru a numara bitii de la stinga la dreapta.
Exercitiul6. Scrieti o functie wordlength() care calculeaza lungimea unui cuvint depecalculatorul gazda, adica numarul de biti dintr-un int. Functia sa fie portabila in sensul ca acelasi cod sursa sa lucreze pe toate calculatoarele.
Exercitiul 7. Scrieti o functie rightrot(n, b) care roteste intregul n la dreapta cu b pozitii.
Exercitiul 8. Scrieti o functie invert(x,p,n) care inverseaza (i.e. schimba pe 1 in 0 si viceversa) cei n biti ai lui x care incep de la pozitia p,lasindu-i pe ceilalti neschimbati.
Expresii de tipul:
i = i + 2
in care membrul sting este repetat in membrul drept pot fi scrise intr-o forma condensata:
i += 2
folosind operatorul de asignare +=.
Majoritatea operatorilor binari (operatori ca+,care au un operand sting si un operand drept) au un operator de asignare corespunzator 'op=', unde op este unul din:
+-*/%<<>>&^|
Daca e1 si e2 sint doua expresii, atunci:
e1 op= e2
este echivalent cu
e1 = (e1) op (e2)
cuexceptia ca e1 este calculat o singura data. Sa remarcam parantezele din jurul lui e2:
x *= y + 1
inseamna de fapt
x = x * (y + 1)
si nu
x = x * y + 1
Dam in continuare,drept exemplu, functia bitcount, care contorizeaza numarul de biti pe 1 dintr-un argument intreg.
bitcount(n) /* contorizeaza bitii 1 din n */
unsigned n;
Lasind la o parte conciziunea, operatorii de asignare au avantajul ca ei corespund cel mai bine modului de gindire al oamenilor. Noi spunem 'adunam 2 la i' sau ' incrementam pe i cu 2' si nu 'ia-l pe i, aduna 2, apoi pune rezultatul inapoi in i'. Deci i += In plus, pentru o expresie complicata, de tipul:
yyval[yypv[p3 + p4] + yypv[p1 + p2]] += 2
operatorul de asignare face codul mai usor de inteles, deoarece cititorul nu trebuie sa verifice sirguincios caceledoua expresii sint intr-adevar o aceeasi sau sa se intrebe de ce nu sint. In plus, un operator de asignare ajuta chiar compilatorul sa produca un cod mai eficient.Am folosit deja faptul ca o instructiune de asignare are o valoare si ca poate sa apara in expresii; exemplul cel mai comun:
while ((c = getchar()) != EOF)
Asignarile folosind alti operatori de asignare (+=, -=, etc) pot deasemenea sa apara in expresii, cu toate ca acestea se intimpla mai rar.Tipul unei expresii de asignare este tipul operandului sau sting.
Exercitiul 9.Intr-un sistem cu numere cu complement fata de 2, x & (x-1) sterge bitul 1 cel mai departe de x. (De ce ?). Folositi aceasta observatie pentru a scrie o versiune mai rapida a lui bitcount.
Instructiunile
if (a < b)
z = a;
else
z = b;
calculeaza desigur in z maximul dintre a si b. Expresia conditionala, scrisa cu operatorul ternar '? :' ofera un mod alternativ pentru a scrie acest lucru precum si constructii similare. In expresia:
e1 ? e2 : e3
expresia e1 se evalueaza prima. Daca ea este nonzero (adevarata) atunci se evalueaza expresia e2 si aceasta este valoarea expresiei conditionale. Altminteri, se evalueaza e3 si aceasta este valoarea. Numai una din expresiile e2 si e3 se evalueaza. Deci, pentru a pune in z maximul dintre a si b:
z = (a > b) ? a : b; /* z = max(a, b) */
Trebuiesa notam ca expresia conditionala este intr-adevar o expresie si ca ea poate fi folosita exact ca oricare alta expresie. Daca e2 si e3 sint expresii de tipuri diferite, tipul reultatului se determina dupa regulile de conversie discutate mai inainte in acest capitol. De exemplu, daca f este un float si n este un int, atunci expresia
(n > 0) ? f : n
este de tipul double, indiferent daca n este pozitiv sau nu.
Parantezele nu sint necesare in jurul primei expresii a unei expresii conditionale, deoarece ponderea lui ? : este foarte mica, chiar deasupra asignarii. Ele sint totusi recomandate, pentru a face partea de conditie a expresiei mai usor de vazut.
Expresiile conditionale conduc adesea la un cod succint.De exemplu, bucla urmatoare tipareste N elemente ale unui tablou, 10 pe linie, cu fiecare coloana separata printr-un blanc si cu
fiecare linie (inclusiv ultima) terminata cu un singur caracter 'linie noua'.
for (i = 0; i << N; i++)
printf('%6d%c', a[i], (i % 10 == 9 || i == N-1) ? 'n' : ' ');
Uncaracter'linienoua' se tipareste tot dupa al zecelea element si dupa al N-lea element. Toate celelalte elemente sint urmate de un blanc. Cu toate ca seamana cu un truc, este instructiv sa incercati sa scrieti lucrul acesta fara a folosi expresia conditionala.
Exercitiul 10.Rescrieti functia lower, care convertesteliterele mari in litere mici, cu o expresie conditionala in locul lui if-else.
Tabelulde mai jos rezuma regulile de pondere si asociativitate pentru toti operatorii,inclusiv pentru aceia pe care nu i-am discutat inca. Operatorii de pe aceeasi linie auaceeasi pondere;liniilesint in ordine de pondere descrescatoare, astfelca,de exemplu, '*', '/' si '%' au o aceeasi pondere, care este mai mare decit a lui '+' '-'.
[] ->
de la stinga la dreapta
! ~ ++ -- - (tip) * & sizeof
de la dreapta la stinga
* / %
de la stinga la dreapta
de la stinga la dreapta
<<>>
de la stinga la dreapta
< <= > >=
de la stinga la dreapta
== !=
de la stinga la dreapta
&
de la stinga la dreapta
de la stinga la dreapta
de la stinga la dreapta
&&
de la stinga la dreapta
de la stinga la dreapta
de la dreapta la stinga
=+=-= etc
de la dreapta la stinga
, (Capitolul 3)
de la stinga la dreapta
Operatorii -> si. sint folositi pentru a accede membrii structurilor; ei vor fi iscutati in Capitolul 6, impreuna cu sizeof (marimea unui obiect). Capitolul 5 discuta * (indirectarea) si & (adresa lui ).
Sa notam ca ponderea operatorilor logicipe biti &, | si ^ este sub== i |=. Aceasta implica faptul ca expresiile care testeaza biti, ca de exemplu
if (( x & MASK) == 0)
trebuiesa fie cuprinse in intregime intre paranteze, pentru a da rezultatele steptate. Asa cum am mentionat mai inainte, expresiile ce implica operatori asociativi si comutativi (+,*,&, ^, |) pot fi rearanjate chiar daca sint cuprinse in paranteze. In marea majoritate a cazurilor,aceasta nu da diferente; in situatia incarear da, se pot folosi variabile temporare explicite pentru a forta o ordine de evaluare particulara.
Limbajul C, ca si majoritatea celorlalte limbaje, nu specifica in ce ordine se evalueaza operanzii unui operator. De exemplu, in instructiuni de tipul
x = f() + g();
fpoatefi evaluat inaintea lui g sauviceversa; deci, daca sau f sau g altereaza o variabila externa de care depinde si cealalta, x poate depinde de ordinea de evaluare. Din nou, rezultateleintermediare pot fi stocate in variabile temporare pentru a fi siguri de o anumita secventa.
In mod similar, ordinea in care sint evaluate argumentele unei functii nu este specficata, asa ca instructiunea
printf('%d %dn', ++n, power(2, n)); /* GRESIT */
poate (si o si face) produce rezultate diferite, pe diferite calculatoare, depinzind de faptul daca n este incrementat sau nu inainte de apelul lui power. Solutia, desigur, este sa scriem:
++n;
printf('%d %dn', n, power(2, n));
Apelurile de functii, instructiunile de asignare imbricate, operatorii de incrementare si decrementare provoaca 'efecte secundare' - o anumita variabila este modificata ca un produsal
unei evaluari de expresie. In orice expresie implicind efecte secundare, pot exista subtile dependente de ordinea in care sint stocate variabilele ce iau parte in expresie. O situatie nefericita este ilustrata de instructiunea:
a[i] = i++;
Chestiuneaconsta in a sti daca indicele este noua valoare a lui i sau daca este vechea.Compilatorul poate faceaceste lucruriinmoduri diferite, depinzind de interpretarea sa. Cind efectele secundare (asignare la variabile efective) au loc, sint lasate la discretia compilatorului, caci cea mai buna ordine depinde puternic de arhitectura calculatorului.
Morala acestei discutii este aceea, ca scrierea de cod ce depinde de ordinea de evaluare,este o proastapractica de programare in orice limbaj. Natural, e necesar sa stim ce lucruritrebuie evitate, dar daca nu stim cum sint ele facute pe diferite calculatoare, aceasta inocenta ne va ajuta sa ne protejam. (Verificatorul lui C, lint, detecteaza majoritatea dependentelor de ordinea de evaluare).