Tipurile enumerare, 'typedef', si operatori pentru biti Tipurile enumerare Pentru declararea tipurilor enumerare se foloseste cuvantul rezervat 'enum'. Acesta va implica denumirea multimii, enumerarea elementelor (numite enumeratori), ca elemente ale multimii. Exemplu: enum zile ;
Aceasta declaratie creeaza tipul utilizator 'enum zile'. Cuvantul rezervat 'enum' este urmat de identificatorul 'zile'. Enumeratorii sunt identificatorii luni, marti, . Acestea sunt constante de tip 'int'. Prin conventie, primul este 0, si apoi restul sunt incrementati. Declararea variabilelor de tip 'enum zile' se face astfel: enum zile zi1, zi2;
Variabilele 'zi1' si 'zi2' pot lua ca valori elemente ale acestui tip. De exemplu, zi1 = miercuri;
va asigna variabilei 'zi1' valoarea miercuri. Instructiunea if (zi1 == zi2)
va testa daca valoarea variabilei 'zi1' este egala cu valoarea variabilei 'zi2'. Enumeratorii pot fi initializati. De asemeni, putem declara variabilele in timpul definirii tipului 'enum'. Exemplu: enum carti a, b, c;
Din moment ce 'trefla' este initializata cu 1, rezulta ca 'caro', 'frunza' si 'inima' sunt initializate cu 2, 3, respectiv 4. Exemplu: enum fructe nr_frct;
Este clar ca 'pere' va fi initializat cu 8, iar 'lamai' cu 4. Numele tipului enumerare poate lipsi, insa atunci nu mai putem declara alte variabile de acel tip. Exemplu: enum copaci;
Singura variabila de tip 'enum ' este copaci (nu se mai poate declara alta). Folosirea lui 'typedef' C pune la dispozitie facilitatea 'typedef' pentru redenumirea tipurilor deja existente. Exemplu: typedef int culoare; culoare rosu, verde, albastru;
Acesta defineste tipul 'culoare' ca fiind un sinonim al lui 'int'. Apoi declaram trei variabile de tipul 'culoare'. Exemplu: Vom ilustra folosirea lui 'typedef' pentru un tip enumerare (creand o functie ce returneaza ziua urmatoare). enum zile ; typedef enum zile zi; zi gaseste_ziua_urmatoare(zi z) return ziua_urmatoare; O alta versiune (mai succinta) folosind 'cast' este: enum zile ; typedef enum zile zi; zi gaseste_urmatoarea_zi(zi z) Expresii si operatori pe biti Operatorii pe biti lucreaza cu expresii integrale reprezentate ca siruri de cifre binare. Acesti operatori sunt dependenti de sistem. Operatorii pe biti sunt: Operatori logici
----- ----- ----------
1. Complement pe bit (unar): ~ 2. Si pe bit : & 3. Sau exclusiv pe bit : ^ 4. Sau inclusiv pe bit : | Operatori de deplasare
----- ----- --------- ----- ----
1. Deplasare stanga : <<
2. Deplasare dreapta : >>
Ca si ceilalti operatori, operatorii pe biti au reguli de precedenta si asociativitate care determina precis cum se evalueaza expresiile ce contin astfel de operatori. Operatorul ~ este unar, restul operatorilor sunt binari si lucreaza cu tipuri integrale. Complement pe bit Operatorul ~ se numeste operator de complement (sau operator de complement pe bit). Acesta inverseaza reprezentarea sirului pe biti, adica 0 devine 1 si 1 devine 0. Exemplu: Fie declaratia int a = 5171;
Reprezentarea binara a lui a este: 00010100 00110011
Expresia ~a este: 11101011 11001100
Aceasta are valoarea intreaga - 5172
Complement fata de doi Reprezentarea complementului fata de doi a unui numar natural este un sir de biti obtinut prin complementarierea scrierii lui n in baza 2. Considerand complementul pe biti al lui n la care adunam 1, obtinem reprezentarea complementului fata de doi a lui -n. O masina care utilizeaza reprezentarea complementului fata de doi ca reprezentare binara in memorie pentru valori integrale se numeste masina complement fata de doi. Reprezentarile complement fata de doi ale lui 0 si -1 sunt speciale. Astfel valoarea 0 are toti bitii 'off', pe cand valoarea -1 are toti bitii 'on'. Numerele negative sunt caracterizate prin aceea ca au bitul cel mai semnificativ 1. Pe masinile care utilizeaza complementul fata de doi, hard-ul permite implementarea scaderii ca o adunare si un complement pe biti. Operatia a - b este aceeasi cu a + (-b), unde -b se obtine considerand complementul pe biti al lui b la care adunam 1. Operatori logici binari pe biti Cei trei operatori & (si), ^ (sau exclusiv) si | (sau inclusiv) sunt binari si au operanzi integrali. Operanzii sunt operati bit cu bit. Tabelul de mai jos contine semantica lor: a b a & b a ^ b a | b
-------- ----- ------ -----------
0 0 0 0 0
1 0 0 1 1
0 1 0 1 1
1 1 1 0 1
Exemplu: Presupunem ca avem declaratiile si initializarile int a = 3333, b = 7777;
Expresie Reprezentare Valoare a 00001101 00000101 3333 b 00011110 01100001 7777 a & b 00001100 00000001 3073
a ^ b 00010011 01100100 4964
a | b 00011111 01100101 8037
~(a | b) 11100000 10011010 -8038 (~a & ~b) 11100000 10011010 -8038 Operatori de deplasare stanga si dreapta Cei doi operanzi ai unui operator de deplasare trebuie sa fie expresii integrale. Tipul returnat de expresie este dat de operandul din stanga. O expresie de forma expresie1 << expresie2
implica reprezentarea pe bit a lui 'expresie1' sa fie deplasata catre stanga cu un numar de pozitii specificat de 'expresie2'. In capatul din dreapta, vor fi adaugate 0-uri. Exemplu: char c = 'Z'; Expresie Reprezentare Actiune c 01011010 nedeplasat
c << 1 10110100 deplasare la stanga cu 1 c << 4 10100000 deplasare la stanga cu 4 c << 15 00000000 deplasare la stanga cu 15 Chiar daca valoarea lui 'c' se memoreaza pe un octet, intr-o expresie aceasta ia tipul 'int'. Deci valoarea expresiilor 'c << 1', 'c << 4' si 'c << 15' se memoreaza pe doi octeti. Operatorul de deplasare la dreapta '>>' nu este chiar simetric cu '<<'. Pentru expresiile integrale fara semn, din stanga se va deplasa 0, iar pentru cele cu semn 1. Exemplu: Presupunem ca avem declaratiile: int a = 1 << 15;
unsigned b = 1 << 15;
Expresie Reprezentare Actiune a 10000000 00000000 nedeplasat
a >> 3 11110000 00000000 deplasare la dreapta cu 3 b 10000000 00000000 nedeplasat
b >> 3 00010000 00000000 deplasare la dreapta cu 3 Ca valoare intreaga, a >> 3 este -4096, iar b >> 3 este 4096, lucru care este in concordanta cu notiunea de numar negativ si pozitiv din matematica. Daca operandul din dreapta a operatorului de deplasare este negativ sau are o valoare care este egala sau depaseste numarul de biti folositi pentru reprezentarea operandului din stanga, atunci comportarea este nedefinita. Tabelul de mai jos ilustreaza regulile de precedenta (+ este mai prioritar) si asociativitate (de la stanga la dreapta) referitoare la operatorii de deplasare. Exemplu: Presupunem ca avem: unsigned a = 1, b = 2; Expresie Expresie echivalenta Reprezentare Valoare a << b >> 1 (a << b) >> 1 00000000 00000010 2 a << 1 + 2 << 3 (a << (1 + 2)) << 3 00000000 01000000 64 a + b << 12 * a >> b ((a + b)<<(12 * a))>>b 00001100 00000000 3072 Masti O 'masca' este o constanta sau variabila folosita pentru extragerea bitilor doriti dintr-o alta variabila sau expresie. Din moment ce constanta 'int' 1 are reprezentarea pe biti: 00000000 00000001
aceasta poate fi folosita pentru determinarea bitului cel mai nesemnificativ dintr-o expresie 'int'. Codul de mai jos foloseste aceasta masca si tipareste o secventa alternativa de 0 si 1: int i, masca = 1;
for (i = 0; i < 10; ++ i)
printf('%d', i & masca);
Daca dorim sa gasim valoarea unui anume bit dintr-o expresie, putem folosi o masca care este 1 in acea pozitie si 0 in rest. Exemplu: Putem folosi expresia 1 << 2 pentru a vedea al treilea bit (numarand de la dreapta). Expresia (v & (1 << 2)) ? 1 : 0 are valoarea 1 sau 0 dupa cum este al treilea bit din 'v'. Alt exemplu de masca este valoarea constanta 255 (adica 2^8 -1). Ea are reprezentarea 00000000 11111111
Deoarece doar octetul din dreapta este plasat pe 'on', atunci expresia v & 255 va intoarce o valoare ce are reprezentare pe biti cu toti bitii din octetul din stanga 'off' si cel din dreapta identic cu octetul din dreapta a lui 'v'. Spunem ca 255 este o masca pentru octetul din dreapta. Un program de impachetare si despachetare a cuvintelor Stim ca un caracter se memoreaza pe un octet, pe cand un intreg pe doi octeti. Folosind operatorii de deplasare, putem comprima doua caractere intr-un intreg. #include #include #include void bit_print(int a) int pack(char a, char b) char unpack(int p, int k) /* k = 0, 1 */ void main() Litere mari -> litere mici Reluam un exemplu dintr-un capitol precedent si anume transformarea literelor mari in mici folosind operatori de deplasare. #include #include #include void main() Recapitulare In cele ce urmeaza, vom preciza cum se calculeaza valoarea unui numar pozitiv, respectiv negativ (in memoria calculatorului). Fie declaratia int a;
Consideram reprezentarea sa in baza 2 b_ b_ . . . . . . . b_9 b_8 b_7 . . . . . . . b_1 Daca b_ = 0 atunci a = suma_^ b_j*2^j altfel consideram reprezentarea in baza 2 a complementului lui a (~a) b'_ b'_ . . . . . b'_9 b'_8 b'_7 . . . . . . b'_1 unde b'_j = 1 - b_j; ~a = suma_^ b'_j*2^j; -a = ~a + 1; a = -(~a + 1); sfarsit. Exercitii propuse spre implementare 1. (Jocul hartie, pumn, farfece) Presupunem ca avem doi jucatori care folosesc mana dreapta pentru reprezentarea a trei obiecte: hartie = palma intinsa pumn = mana stransa sub forma de pumn foarfece = doua degete departate (semnul Victoriei) Ei isi arata simultan mana dreapta in una din aceste configuratii (de mai multe ori). Daca ei arata acelasi lucru, este remiza (nu castiga nimeni). Daca nu se aplica una din urmatoarele trei reguli: a) Hartia acopera pumnul. (deci palma intinsa castiga fata de pumn) b) Pumnul sparge foarfecele. c) Foarfecele taie hartia. Sa se simuleze acest joc, facand un numar arbitrar de evenimente precizand scorul final. Se cere sa se joace persoana-calculator, si varianta a doua calculator-calculator. 2. O ruleta este o masina care selecteaza la intamplare un numar intre 0 si 35 la intamplare. Jucatorul poate paria pe un numar par/impar sau poate paria pe un numar oarecare. Castigul unui pariu par/impar se premiaza cu 2/1 dolari, cu exceptia celor in care ruleta alege 0. Daca jucatorul 'prinde' numarul selectat de ruleta, atunci este platit cu 35 1 dolari. Intrebare: Considerand ca jucam pariuri de 1 dolar, cate parieri trebuie sa facem astfel incat sa pierdem 10 dolari ? 3. Folosind functia 'bit_print' construiti un tabel (cu trei coloane) care sa contina n, reprezentarea binara a lui 2^n, reprezentarea binara a lui 2^n-1, pentru n = 0, 1, 2, , 32. Apoi, afisati un tabel (tot cu trei coloane) care sa contina n, 10^n si 10^n-1 pentru n = 0, 1, 2, , 7 (in baza zece). Ce asemanare observati ? 4. Zilele secolului 20 pot fi notate folosind intregi in forma 'zi/luna/an'. De exemplu, 1/7/93 inseamna 1 iulie 1993. Scrieti o functie care memoreaza ziua, luna si anul compact, astfel: a) pentru zile (maxim 31) sunt suficienti 5 biti; b) pentru luna (12) sunt suficienti 4 biti; c) pentru ani (100) sunt suficienti 7 biti; Functia trebuie sa aiba la intrare ziua, luna si anul ca intregi si trebuie sa returneze data 'impachetata' pe un 'intreg' pe 16 biti. Scrieti inca o functie care face 'despachetarea'. 5. Folosind operatori pe biti, scrieti programe C care: a) testeaza daca un numar de tip 'int' sau 'long' este divizibil cu 8. Generalizare (divizibilitate cu 2^n); b) testeaza daca un numar este pozitiv sau negativ; c) calculeaza pentru un n dat, multiplii de 2, 4, Generalizare; d) calculeaza pentru un n dat, [n/2], [n/4], Generalizare; e) calculeaza m^n, folosind reprezentarea in baza 2 a lui n si metoda 'divide et impera' (vezi exercitiul 2 din capitolul 6). 6. Sa se calculeze suma cifrelor unui numar cu n cifre.