EXPRESII IN C. FUNCTII DE INTRARE/IE+IRE UZUALE
PENTRU CONSOLA
Expresiile sunt combinatii valide sintactic de date si operatori. Aici, prin date, intelegem deopotriva constante si variabile. Spre deosebire de constante care sunt valori fixe, variabilele semnifica valori care se pot modifica prin program.
In C, ca si in alte limbaje, datele sunt clasificate in tipuri de date. Exista tipuri de date fundamentale (numite si predefinite, simple sau de baza) si tipuri de date derivate. Tipurile derivate (tablouri, pointeri, structuri, uniuni, enumerari si orice tip definit de programator) se bazeaza pe tipurile fundamentale.
Tipurile fundamentale de date In C
Tipurile de date fundamentale din C se impart in cinci categorii: char, int, float, double si void. Primele patru tipuri se mai numesc si tipuri aritmetice si se refera respectiv la valori caracter, intregi, reale in simpla precizie si reale in dubla precizie. Tipul de date void indica absenta oricarei valori si este utilizat, de exemplu, la descrierea functiilor care nu returneaza nici o valoare.
Dimensiunea zonei de memorie alocate si domeniul de valori asociate tipurilor aritmetice pot sa difere functie de varianta de implementare a limbajului si de tipul de procesor folosit.
Standardul ANSI C nu precizeaza decat domeniul minimal de valori al fiecarui tip de date, nu si dimensiunea sa. In majoritatea implementarilor insa, tipul char ocupa un octet, int ocupa doi octeti, iar float patru octeti. Domeniul de valori poate fi modificat utilizand modificatorii de tip. Acestia sunt: signed, unsigned, short si long. Modificatorii signed, unsigned, short si long se pot aplica tipului int, signed si unsigned, tipului char, iar long, tipului double. Efectul aplicarii modificatorilor signed sau unsigned asupra tipurilor de date intregi consta in interpretarea diferita, din punct de vedere al semnului, a informatiei memorate. Sa consideram o configuratie binara de lungime N, bitii fiind numerotati ca mai jos:
N - 1 |
N - 2 | |||||
Figura 12.1 - Configuratie binara de N biti
Daca aceasta zona o destinam memorarii doar a intregilor pozitivi, printr-un calcul simplu se poate vedea ca plaja de reprezentare este a0, 2N-1i. Daca zona este destinata memorarii atat a intregilor cu semn cat si fara semn, bitul N-1 va fi folosit pentru reprezentarea semnului (0 pentru numere pozitive, 1 pentru numere negative), iar plaja de reprezentare va fi a-2N-1, 2N-1-1i
Avand in vedere aceste consideratii , de exemplu, o variabila de tip signed int va avea un domeniu de valori cuprins intre -32768 si 32767, iar una de tip unsigned int va lua valori intre 0 si 65535.
Observatii:
Pentru tipul intreg de date (char, int, short, long) reprezentarea implicita este signed.
Specificarea unui modificator fara tip inseamna considerarea implicita a tipului int.
In C nu exista tipul de date boolean. Din acest motiv functioneaza urmatoarea conventie: orice expresie diferita de zero are valoarea adevarat, iar daca e egala cu zero, valoarea fals.
Variabile +i tipuri de date
Asocierea dintre numele unei variabile si un anumit tip de date se face folosind declaratiile. Forma generala a declaratiei unei variabile este:
tip lista_de_variabile;
unde:
tip poate fi orice tip de date recunoscut in C, iar lista_de_variabile contine unul sau mai multi identificatori despartiti prin virgula. In exemplele de mai jos vom folosi doar tipurile fundamentale.
Exemple de declaratii de variabile:
float x,y;
int a,b1;
short a_x,b_y;
double z;
Orice variabila folosita in program trebuie mai intai declarata. Daca pe linia de declarare variabila este initializata se spune ca are loc o definire a variabilei.
Exemple de declaratii si definitii de variabile:
float xs38.981,I;
int abs-453;
char chs'A',z;
Orice variabila definita (adica declarata si initializata) pastreaza in continuare atributul de baza al variabilei, adica poate fi modificata. Daca se doreste <inghetarea> asocierii dintre o variabila si o anumita valoare se utilizeaza modificatorul de acces (sau calificatorul) const. Practic, efectul unei declaratii de genul
const tip nume_variabila;
este crearea constantei simbolice nume care poate fi utilizata, dar nu poate fi modificata prin program. Daca tip lipseste se considera implicit ca tipul este int. In exemplul de mai jos se definesc doua constante, constanta pi si constanta de tip int, ore_zi
Exemplu:
const double pis3.1415926536;
const ore_zis24;
Constante +i tipuri de date
In Capitolul 11 (vezi paragraful Vocabularul
limbajului), am prezentat o clasificare a constantelor in: intregi, reale,
caracter, sir. Se pune problema, carui tip de date ii apartine o constanta
numerica? Cand constanta este caracter, raspunsul este simplu: tipului char. De asemenea, constanta in virgula
mobila (in notatie uzuala cu punct sau in notatie stiintifica) va apartine
tipului double. Pentru celelalte
constante numerice compilatorul va considera implicit incadrarea in cel mai mic
tip 525j98f de date compatibil. De exemplu, este de tip int, de tip unsigned, de tip long int. Incadrarea intr-un tip de date se
poate face si explicit adaugand constantei unul din sufixurile L sau U, daca e intreaga sau F sau L, daca e reala. Constanta intreaga
cu sufixul L este de tip long, iar cu sufixul U, de tip unsigned.
Func|ii uzuale de intrare/ie+ire
pentru consolA.
Descriptori de format
Prezentam in continuare functiile folosite frecvent pentru transferul de date de la tastatura in memoria calculatorului (functii de intrare) si din memoria calculatorului pe ecran (functii de iesire); cu aceasta ocazie introducem si descriptorii de format cei mai folositi. Deosebim trei categorii de functii de intrare/iesire pentru consola:
functii generale de intrare/iesire (scanf() si printf());
functii speciale de intrare/iesire:
functii pentru citirea si scrierea caracterelor;
functii pentru citirea si scrierea sirurilor de caractere.
12.4.1. Func|iile printf() +i scanf()
Aceste functii reprezinta echivalentele pentru consola a functiilor de intrare/iesire pentru fisiere, fprintf() si fscanf().
Forma generala a functiei de afisare printf() este:
int printf(sir_format,lista_de_argumente);
unde:
sir_format poate contine: mesaje pentru utilizator, secvente escape si descriptori de format pentru valorile care se afiseaza;
lista_de_argumente reprezinta variabile sau expresii al caror continut se va afisa.
Functia intoarce numarul de caractere scrise efectiv sau o valoare negativa in caz de insucces.
Precizari:
Descriptorii de format servesc la efectuarea conversiilor dintre reprezentarea externa si interna (la citire) si intre reprezentarea interna si externa (la scriere); formatul extern presupune succesiuni de caractere, iar cel intern succesiuni de cifre binare;
Atat la scriere cat si la citire, descriptorii de format sunt pusi in corespondenta de la stanga spre dreapta cu elementele listei de argumente. Argumentul trebuie sa fie compatibil cu tipul anuntat in descriptorul de format corespunzator;
Daca numarul de argumente este mai mic decat numarul descriptorilor de format, datele de iesire sunt nedefinite; daca numarul argumentelor este mai mare decat numarul descriptorilor de format, argumentele in plus sunt ignorate.
In Tabelul 12.1 prezentam lista celor mai utilizati descriptori folositi de functia printf() si semnificatia lor.
Tabelul 12.1 Descriptori de format
Descriptori |
Utilizare |
%u |
numere intregi zecimale fara semn |
%d sau %i |
numere intregi zecimale cu semn |
%c |
caracter |
%f |
numere reale in notatie uzuala |
%e sau % E |
numere reale in notatie stiintifica (e sau E) |
%x sau %X |
hexazecimal fara semn (litere mici sau majuscule) |
%o |
octal fara semn |
%s |
sir de caractere |
%g sau %G |
se alege reprezentarea cu numarul cel mai mic de caractere dintre cea in notatie uzuala si cea in notatie stiintifica (de tip e sau E) |
%p |
valoare pointer |
Descriptorul %x are ca efect afisarea cifrelor hexazecimale A,B,C,D,E,F cu litera mica; daca se foloseste %X se afiseaza cu litere mari.
Daca se foloseste %e litera "e" din notatia stiintifica apare ca e, iar daca se foloseste %E, apare ca majuscula (litera E
Valorile de tip long int se afiseaza
utilizand %ld,%li,%lu,%lo
sau %lx
Valorile de tip short int se afiseaza
utilizand %hd,%hi,%hu,%ho
sau %hx
Pentru a afisa valori double se va alege una din variantele: %lf,%le,%lE,%lg %lG, iar pentru valori long double una din variantele: %Lf,%Le,%LE,%Lg,%LG
Tabelul 3.1 prezinta descriptori de format fara caracteristici de lungime, precizie si aliniere. Folositi astfel, ei aliniaza implicit valorile la stanga si folosesc spatiu de afisare necesar reprezentarii acestor valori dupa cum urmeaza:
%f afiseaza implicit partea intreaga, punctul si 6 cifre la partea subunitara;
%e sau %E afiseaza implicit o cifra la partea intreaga, 6 cifre la partea subunitara, caracterul e sau E si exponentul precedat de sau
%g sau %G alege reprezentarea cu cel mai mic numar de caractere dintre cea uzuala si cea stiintifica.
%d,%i,%c,%o,%x,%X,%s,%p folosesc un numar de coloane egal cu numarul de caractere ce trebuie afisate.
Exemplificam folosirea descriptorilor prezentati si a secventelor de evitare cu ajutorul urmatoarelor programe:
Exemplul 12.1
Afisarea valorilor si sumei a doua numere intregi, sub forma:
xsvaloare ysvaloare
sumasvaloare
# include 'stdio.h'
void main(void)
A
int xs10, ys-43;
printf ('tnttxs%dtttys%dtntt sumas%i', x,y, x+y);
I
Exemplul 12.2
Afisarea unei constante intregi si a valorilor sale in octal si hexazecimal pe cate un rand.
#include 'stdio.h'
void main(void)
A
const xs4529;
printf('tn numarul estes%dtn',x);
printf('tn valoarea in octal estes%o',x);
printf('tn valoarea in hexazecimal estes%x',x);
I
Exemplul 12.3
Afisarea unui caracter si a codului sau ASCII; afisarea se va termina cu un semnal sonor.
#include 'stdio.h'
void main(void)
A
char as'Q';
printf('tn caracterul %c are codul ASCIIs%ita',a,a);
I
Exemplul 12.4
Afisarea unor valori folosind diversi descriptori de format; comentariile arata efectul executiei functiei printf()
#include 'stdio.h'
void main(void)
A
char ch;
short k;
int i;
long int j;
float x;
clrscr();
chs'A';
printf('tn Caracterul %c are codul ASCII s
%i',ch,ch);
/* Caracterul A are codul ASCII s 65 */
ks250;
printf('tn ks%hu',k); /* ks250 */
is4567;
printf('tn is%i',i); /* is4567 */
printf('tn is%u',i); /* is4567 */
printf('tn -is%i',-i); /* -is-4567 */
printf('tn is%i',i); /* is4567 */
printf(' are valoarea hexazecimala %x',i);
/* are valoarea hexazecimala 11d7 */
printf(' sau echivalent, %X',i);
/* sau echivalent, 11D7 */
printf('tn is%i',i); /* is4567 */
printf(' are valoarea octala %o',i);
/* are valoarea octala 10727 */
js123456;
printf('tn js%li',j); /* js123456 */
xs76.5432;
printf('tn xs%f',x); /* xs76.543198 */
printf('tn xs%e',x); /* xs7.65320e+01 */
printf('tn xs%E',x); /* xs7.65320E+01 */
printf('tn xs%g',x); /* xs76.543200 */
printf('tn xs%G',x); /* xs76.543200 */
xs-0.123456789;
printf('tn xs%f',x); /* xs-0.123457 */
printf('tn xs%e',x); /* xs-1.234568e-01 */
printf('tn xs%E',x); /* xs-1.234568E-01 */
printf('tn xs%g',x); /* xs-0.123457 */
printf('tn xs%G',x); /* xs-0.123457 */
printf('tn %s','testare');
I
Dimensiunea campului de afisare, precizia si modul de aliniere pot fi stabilite prin simboluri plasate intre semnul si specificatorul descriptorului de format (i,d,u,f etc.).
Astfel, un intreg pozitiv aflat intre si specificatorul descriptorului de format indica dimensiunea minima a campului de afisare. Daca sirul sau numarul care se afiseaza are mai multe caractere decat dimensiunea minima precizata, atunci afisarea va fi integrala, in caz contrar se completeaza cu spatii pana la realizarea dimensiunii minime. Daca dorim ca aceasta completare sa fie facuta cu cifra in loc de spatii, atunci, inainte de intregul care specifica dimensiunea de afisare, se pune cifra
De exemplu, descriptorul %7f semnifica afisarea unui numar real pe minim coloane si, completarea eventualelor coloane libere cu spatii, iar %07f impune acelasi numar minim de coloane pentru afisare, insa completarea coloanelor libere se va face cu cifra
Utilitatea precizarii dimensiunii minime de afisare apare mai ales la afisarea tablourilor in care alinierea se face pe coloane.
Programul urmator ilustreaza efectul precizarii dimensiunii campului de afisare:
Exemplul 12.5
Afisarea unor valori folosind descriptori de format cu precizarea dimensiunii campului de afisare
#include 'stdio.h'
void main(void)
A
int i;
float x;
is4567;
printf('tn is%4i',i); /* is4567 */
printf('tn is%6i',i); /* is 4567 */
printf('tn is%3i',i); /* is4567 */
printf('tn is%06i',i); /* is004567 */
xs76.123001;
printf('tn xs%10f',x); /* xs 76.123001 */
printf('tn xs%010f',x); /* xs076.123001 */
printf('tn %3s','testare'); /* testare */
printf('tn %10s','testare'); /* testare */
I
Precizia de afisare se specifica printr-un punct urmat de un intreg pozitiv. Specificatorul de precizie astfel obtinut se plaseaza imediat dupa dimensiunea campului de afisare (cand este precizata). Interpretarea lui depinde de tipul de date avut in vedere.
De exemplu, descriptorul %7.3f indica afisarea unui numar real pe minim coloane si cu cifre zecimale dupa virgula. Lucrurile se petrec asemanator daca in loc de %f se foloseste %e sau %E. Daca se foloseste unul din descriptorii %g sau %G specificatorul de precizie arata minimul de cifre semnificative.
Aplicat unui intreg, specificatorul de precizie arata numarul minim de cifre cu care va apare afisat intregul respectiv (daca intregul nu are suficiente cifre atunci se completeaza la inceput cu numarul necesar de cifre
Un descriptor de format prevazut cu specificator de dimensiune si specificator de precizie poate fi aplicat unui sir cu lungimea cuprinsa intre valoarea specificatorului de dimensiune si cea a specificatorului de precizie. Daca se depaseste valoarea specificatorului de precizie, sirul se trunchiaza.
Se poate impune alinierea la stanga a datelor plasand semnul (minus) imediat dupa semnul in cadrul descriptorului.
Programul urmator ilustreaza toate cazurile prezentate:
Exemplul 12.6
Afisarea unor valori folosind diverse facilitati ale descriptorilor prezentati
#include 'stdio.h'
void main(void)
A
int i;
double x;
is4567;
printf('tn is%3.7i',i); /* is0004567 */
printf('tn is%7.3i',i); /* is 4567 */
printf('tn is%-7.3i',i); /* is4567 */
xs76.123401;
printf('tn xs%10.3f',x); /* xs 76.123 */
printf('tn xs%-10.3f',x); /* xs76.123 */
printf('tn xs%3.7f',x); /* xs76.1234010 */
printf('tn xs%10.2e',x); /* xs 7.61e+01 */
printf('tn xs%-10.1E',x); /* xs7.6E+01 */
printf('tn xs%10.3g',x); /* xs 76.1 */
printf('tn xs%-10.4G',x); /* xs76.12 */
printf('tn %.4s','testare'); /* test */
printf('tn %10.4s','testare'); /* test */
printf('tn %-10.4s','testare');/* test */
printf('tn %-1.10s','testare');/* testare */
I
Functia de citire scanf() are forma generala:
int scanf(sir_format,lista_de_argumente);
unde:
sir_format poate contine descriptori de format, caractere de spatiere albe, alte caractere;
lista_de_argumente este de forma: &var1,&var2,,&varn. Prin &v se intelege adresa variabilei v
Functia intoarce numarul de argumente carora li s-a atribuit o valoare sau constanta EOF (egala de obicei cu -1) in caz de insucces.
In marea lor majoritate descriptorii de format folositi la functia scanf() sunt identici cu cei de la functia printf(); practic, din tabelul prezentat anterior obtinem o lista valida pentru scanf(), indepartand %E,%X,%G. Folositi cu scanf(), descriptorii %f,%e,%g sunt echivalenti.
Lista de argumente este citita de la stanga la dreapta si asociata in aceasta ordine cu lista de descriptori. Fiecare descriptor arata functiei scanf() tipul valorii care se va citi: intreg, real, sir, pointer etc.. Sa observam ca aceste valori sunt transferate variabilelor v1,v2,vn prin intermediul adreselor &v1,&v2,&vn
Este de mentionat faptul ca in cazul citirii unui sir, deoarece insusi numele sirului reprezinta o adresa, operatorul de luare a adresei & nu va mai preceda obligatoriu numele sirului. Un exemplu de program care citeste siruri de caractere este prezentat in Capitolul 14 (vezi paragraful Functii pentru prelucrarea sirurilor de caractere)
Ca si in cazul functiei printf() descriptorii de format pot avea si un modificator de lungime maxima a sirului de caractere care va fi citit. De exemplu, apelul:
scanf("%15s",sir);
are drept consecinta citirea a maximum caractere din sirul de intrare si atribuirea sirului format variabilei sir. Daca sirul de intrare are mai mult de caractere, caracterele in plus se ignora; la un nou apel al functiei scanf() explorarea sirului de intrare incepe cu aceste caractere anterior ignorate.
Caracterele albe de spatiere in sirurile de intrare pot fi blankurile (spatiile albe), taburile (spatii tab), sau caracterul linie noua (tasta enter). Aceste caractere albe de spatiere sunt ignorate daca in sirul format avem corespunzator intre descriptori cel putin un spatiu. De asemenea, orice alt caracter poate fi folosit ca separator in fluxul de intrare, cu conditia ca el sa fie plasat corespunzator si intre descriptorii de format din sir_format. Daca aceasta conditie nu e indeplinita, la prima neconcordanta (de la stanga la dreapta) intre separatorii din fluxul de intrare si cei din sir_format executia functiei scanf() se incheie. De exemplu, apelul functiei scanf():
scanf("%d,%f,%s",&x,&y,sir);
realizeaza o atribuire corecta a datelor de intrare daca ele sunt despartite prin virgula. Pentru a atribui variabilei x valoarea , lui y valoarea si variabilei sir valoarea anI, in fluxul de intrare trebuie sa avem 32,10.75,anI
Exemplul 12.7
Citirea numerelor reale x si X de la tastatura, calculul produsului x*X si afisarea lui in format exponential.
#include 'stdio.h'
void main(void)
A
float x,X;
printf('tn Tastati doua numere separate
prin spatiu ');
scanf('%f %f',&x,&X);
XsX*x;
printf('tn Produsul X*x este s %e', X);
I
Observatii:
X si x sunt variabile diferite;
XsX*x; este o expresie de atribuire care se poate scrie mai scurt sub forma X*sx; cu ajutorul operatorului compus *s
Functiile scanf() si printf() sunt functii de intrare/iesire standard cu destinatie generala. Din considerente de eficienta (cod mai mic, viteza de executie sporita, comoditate in programare etc.) limbajul C pune la dispozitia utilizatorului si functii cu destinatie speciala.
12.4.2. Func|ii speciale pentru citirea/scrierea caracterelor la nivelul consolei
Standardul ANSI C prevede doua functii simetrice pentru transferul caracterelor la nivelul consolei: functiile getchar() si putchar(). Ele isi au prototipurile in "stdio.h"
Functia pentru citire getchar() are forma generala
int getchar(void);
si intoarce urmatorul caracter care va fi citit. Daca s-a atins sfarsitul sirului sau se produce o eroare se intoarce EOF.
Desi nu apartin standardului ANSI C, totusi, functiile getch(), getche(), putch() sunt incluse frecvent in biblioteca standard a compilatoarelor compatibile DOS. Prototipurile acestor functii sunt in fisierul header "conio.h"
Functiile getch() si getche() sunt echivalente functional cu functia getchar(). Forma generala este:
int getch(void);
int getche(void);
Spre deosebire de functia getchar() unde caracterul tastat este citit numai daca se apasa in continuare tasta Enter, functiile getch() si getche() preiau caracterul imediat dupa ce a fost tastat (fara a mai apasa Enter De asemenea, functia getch() preia caracterul de la tastatura fara a-l afisa pe ecran, in timp ce getche() afiseaza pe ecran caracterul citit (citire cu ecou).
Functia pentru afisare putchar() are forma generala
int putchar(int ch);
unde ch este caracterul care se afiseaza.
Functia intoarce in caz de succes caracterul scris, iar in caz contrar EOF.
Functia putch() este echivalenta functional cu putchar() si are forma generala
int putch(int ch);
unde ch este caracterul care se afiseaza.
Ea este de asemenea o functie nestandard (nu e definita de standardul ANSI C) frecvent utilizata.
Exemplul 12.8
Citirea si afisarea unui caracter folosind functiile speciale getche() si putch()
#include 'stdio.h'
#include 'conio.h'
void main(void)
A
char x;
printf('tn Tastati o litera! ');
xsgetche();
printf('tn Multumesc! Ati tastat litera ');
putch(x);
getch();
I
12.4.3. Func|ii
speciale pentru citirea/scrierea +irurilor
de caractere la nivelul consolei
Functii speciale pentru citirea/scrierea sirurilor de caractere la nivelul consolei sunt gets() si puts(). Ambele functii isi au prototipurile in fisierul header "stdio.h". Aceste prototipuri sunt prezentate in Capitolul 18 al lucrarii. Deoarece in constructia acestor prototipuri intervine notiunea de pointer, prezentata in detaliu in Capitolul 15, ne limitam aici la a spune ca prin apelul
gets(sir_destinatie);
se citeste un sir de la tastatura in sir_destinatie, iar apelul
puts(sir);
are ca efect afisarea sirului sir pe ecran.
Dam ca exemplu secventa de program:
gets(x);
printf("tn Sirul citit este s");
puts(x);
Un program complet care utilizeaza functiile gets() si puts() este prezentat in Capitolul 14 (vezi paragraful Functii pentru prelucrarea sirurilor de caractere).
12.5. Operatori. Clasificare
Operatorii sunt elemente de baza ale limbajului care arata ce operatii trebuie executate asupra unor operanzi. In C, operanzi pot fi constantele, numele de variabile, numele de functii, expresiile.
Bogata familie de operatori confera limbajului C o trasatura aparte.
Clasificarea operatorilor C se poate face dupa mai multe criterii:
dupa numarul de operanzi prelucrati (unari, binari, ternari);
dupa prioritatea avuta in evaluarea expresiilor (clase de precedenta);
dupa tipul operanzilor (aritmetici, relationali, logici si la nivel de bit).
12.5.1. Operatori unari, binari, ternari
|inand cont de numarul de operanzi prelucrati, in C exista operatori unari, binari si ternari. Clasele rezultate nu sunt disjuncte, in sensul ca, de exemplu, un operator unar poate fi si binar. Astfel, in expresia operatorul (minus) este unar, iar in expresia a-3, este binar.
Singurul operator ternar este operatorul conditional Operanzii sai sunt plasati dupa schema operand1 ? operand2 : operand 3
12.5.2. Clase de preceden|A
Prioritatile operatorilor impun ordinea de evaluare a expresiilor. Ca si in calculele algebrice obisnuite ordinea de evaluare poate fi modificata cu ajutorul parantezelor rotunde.
Operatorii care au prioritati egale, apartin aceleiasi clase de precedenta. Lista operatorilor grupati dupa clase de precedenta este data in Tabelul 12.2.
Tabelul 12.2 Clase de precedenta
Clasa |
Operatori |
|
1 (paranteze, op. de selectie) |
() ai -> . | |
2 (op.unari) |
++ -- ! A - + & * |
sizeof cast |
3 (op. multiplicativi) | ||
4 (op. aditivi) | ||
5 (op. shift) |
<< >> |
|
6 (op. relationali) |
< <s > >s |
|
7 (op. relationali) |
ss !s |
|
8 ("SI" pe bit) |
& |
|
9 ("SAU" exclusiv bit cu bit) | ||
10 ("SAU" bit cu bit) | ||
11 ("SI" logic) |
&& |
|
12 ("SAU" logic) | ||
13 (operator conditional) | ||
14 (atribuire) |
s +s -s *s etc. |
|
15 (secventiere) |
Functioneaza, de asemenea, reguli de asociere de la stanga la dreapta sau de la dreapta la stanga. Singurii operatori care se asociaza de la dreapta la stanga sunt operatorii unari si operatorul , restul se asociaza de la stanga la dreapta.
Operatorul de atribuire s ocupa un loc aparte in familia operatorilor. Cu ajutorul lui putem sa atribuim unei variabile o anumita valoare. Forma sa generala este:
vse;
unde v este un nume de variabila, iar e este o expresie. In C, membrul stang si membrul drept al unei atribuiri se mai numesc valoare stanga (lvalue), respectiv valoare dreapta (rvalue). Spre deosebire de alte limbaje (Fortran, Pascal etc.) in C, operatorul de atribuire poate apare si in interiorul unei expresii, fapt ce permite o compactare a codului sursa. De exemplu, doua atribuiri succesive de genul:
Aspi*r*r;
VsA*h;
pot fi scrise compact sub forma:
Vs(Aspi*r*r)*h;
Practic, ce am scris mai sus este o instructiune expresie. Rezultatul evaluarii expresiei (Aspi*r*r)este pi*r*r; dupa cum se vede, acest rezultat se poate folosi mai departe in calcule. Atribuirea valorii pi*r*r variabilei A apare ca un efect secundar al instructiunii expresie Aspi*r*r;
Compilatorul C permite ca in expresii de genul
vse;
v si e sa aiba tipuri diferite. In aceasta situatie au loc conversii de tip. Regula de conversie este urmatoarea: valoarea membrului drept (valoarea lui e) se converteste la tipul membrului stang (tipul lui v). Deoarece sizeof (int) <s sizeof (float) <s sizeof (double), se spune ca int este mai >slab> decat float, care este la randul sau mai >slab> decat double. Daca membrul stang este de un tip mai >slab> decat tipul membrului drept pot avea loc pierderi de informatie (prin trunchiere) sau depasirea posibilitatilor de reprezentare. De exemplu, in secventa:
int x,y;
float a,b;
xsa;
bsy;
variabila x va primi partea fara fractie a valorii a sau un rezultat imprevizibil daca se depasesc posibilitatile de reprezentare, iar valoarea intreaga y va fi convertita la o valoare reprezentata in virgula mobila.
Consideratiile de mai sus referitore la conversia tipurilor sunt valabile si in situatia in care cel putin unul dintre cei doi operanzi ai operatorului de atribuire sunt variante ale tipului int (char, signed, unsigned, short, long) sau ale tipului double (long double).
In C este posibila atribuirea multipla, ca in exemplul de mai jos:
xsyszsss0;
Efectul este atribuirea valorii variabilelor x,y,z,s
De asemenea, in anumite situatii se pot folosi operatorii de atribuire compusa. Practic, orice atribuire de genul:
variabilasvariabila operator expresie;
unde operator poate fi ales din lista de operatori *, /, %, +, -, >>, <<, , &, ^ se poate scrie simplificat sub forma:
variabila operatorsexpresie;
De exemplu, expresia xsx+2; are acelasi efect cu x+s2;
Daca v e un operand oarecare cazurile uzuale de atribuire vsv+1 si vsv-1 se pot scrie simplificat sub forma v++ si respectiv v--. Operatorul se numeste operator de incrementare, iar operator de decrementare. Functie de pozitia lor fata de operand ei pot fi operatori prefix sau postfix.
De exemplu:
xsx+1 se poate scrie x++ sau ++x,
xsx-1 se poate scrie x-- sau --x.
Daca operandul nu apare in cadrul unei expresii, dupa cum se vede din exemplul de mai sus, nu are importanta daca operatorii sunt prefix sau postfix. Cand operandul apare in cadrul unei expresii, daca este precedat de operatorul sau se executa intai incrementarea sau decrementarea operandului si apoi este folosit in expresie; daca este insa urmat de operatorul sau incrementarea sau decrementarea se va face dupa folosirea sa in expresie.
De exemplu,
expresia ys++x; e echivalenta cu secventa xsx+1; ysx;
iar
expresia ysx++; e echivalenta cu secventa ysx; xsx+1;
Sa observam ca cele doua secvente vor produce aceeasi valoare pentru x si valori diferite pentru y
Folosirea operatorilor de incrementare si decrementare este recomandabila nu doar din ratiuni de simplificare a scrierii programelor ci si datorita faptului ca majoritatea compilatoarelor C genereaza coduri obiect foarte rapide in astfel de cazuri.
Operatorul de secventiere (virgula) permite construirea unei expresii ca o lista de alte expresii. Expresiile din lista se evalueaza de la stanga la dreapta. Valoarea si tipul intregii expresii este dat de ultima expresie din lista. De exemplu, in urma executiei secventei de program:
int xs7,ys3,z,w;
zs(wsx<y,x+y);
z va primi valoarea . Explicatia este urmatoarea: se evalueaza mai intai expresia wsx<y, apoi expresia x+y. Rezultatul evaluarii expresiei x+y este , iar intreaga expresie (wsx<y,x+y) va primi aceasta valoare care se va atribui lui z
Practic, operatorul virgula ofera o modalitate eleganta de a scrie mai multe expresii in secventa, sub forma compacta a unei singure expresii. In exemplul de mai sus, expresia
zs(wsx<y,x+y);
inlocuieste secventa
wsx<y;
zsx+y;
Operatorii ?,a,i,*,&,sizeof, ,-> vor fi prezentati mai pe larg in capitolele unde prezenta lor este necesara: operatorul la instructiuni conditionale, parantezele patrate a si i la tipul de date tablou, *,& si sizeof la pointeri, si -> la tipurile de date struct si union.
12.5.3. Operatori aritmetici
Limbajul C are urmatorii operatori aritmetici:
Semnificatia lor rezulta din Tabelul 11.5 si din prezentarea facuta anterior operatorilor si . In plus vom face urmatoarele observatii:
aplicat unor operanzi intregi operatorul va produce doar partea intreaga a impartirii; de exemplu, secventa:
int x,y;
xs7;
ysx/2;
va produce valoarea pentru y
operatorul aplicat unor operanzi intregi furnizeaza restul impartirii acelor intregi; de exemplu, secventa:
int x,y
xs7;
ysx%2;
produce pentru y valoarea ys1, adica restul impartirii lui la
12.5.4. Operatori rela|ionali +i logici
Operatorii relationali din limbajul C sunt: <, >, <s, >s, ss, !s. Semnificatia lor rezulta din Tabelul 11.5. In urma evaluarii unei expresii in care intervin operatori relationali rezulta valoarea pentru fals si pentru adevarat Programul de mai jos afiseaza valorile anuntate in comentariile alaturate:
Exemplul 12.9
Afisarea valorilor unor expresii in care intervin operatori relationali
#include 'stdio.h'
#include 'conio.h'
void main(void)
A
float xs0, ys2.3;
printf('tn x<y are valoarea %d',x<y) ; /*1*/
printf('tn x<sy are valoarea %d',x<sy); /*1*/
printf('tn x>y are valoarea %d',x>y) ; /*0*/
printf('tn x>sy are valoarea %d',x>sy); /*0*/
printf('tn xssy are valoarea %d',xssy); /*0*/
printf('tn x!sy are valoarea %d',x!sy); /*1*/
getch();
I
Operatorii logici din C sunt:
NU logic
&& SI logic
SAU logic
Modul de actiune al acestor operatori este prezentat in Tabelul 12.3.
Tabelul 12.3 Tabla valorilor de adevar pentru operatori logici
a |
b |
a && b |
a || b |
!a |
| ||||
Observatii:
Din Tabelul 12.3 rezulta ca operatorii logici && si au o prioritate mai mica decat operatorii relationali. Din acest motiv:
expresia a<sb c>d e echivalenta cu (a<sb) (c>d)
expresia a>b&&c<sd e echivalenta cu (a>b)&&(c<sd)
Concluzia este ca in astfel de situatii prezenta parantezelor este optionala. Exista insa cazuri in care parantezele sunt absolut necesare; de exemplu, daca dorim sa negam expresia a>3 vom scrie !(a>3) si nu !a>3. Expresia !(a>3) va returna corect valoarea sau functie de marimea lui a, in timp ce expresia !a>3 are totdeauna valoarea (datorita prioritatii mai mari a operatorului fata de > se evalueaza mai intai !a care poate fi sau ; oricare ar fi valoarea lui a, rezulta in final valoarea
Daca intr-o expresie formata din operanzi legati prin operatorul , iar valoarea primului operand este , valoarea expresiei este (vezi Tabelul 12 ) si ceilalti operanzi nu se mai evalueaza. Daca intr-o expresie formata din operanzi legati prin operatorul &&, primul operand ia valoarea , valoarea expresiei este iar ceilalti operanzi nu se mai evalueaza.
Deoarece prezenta parantezelor nu reduce viteza de executie a expresiilor, ele pot fi folosite, alaturi de spatii, la cresterea gradului de lizibilitate a unui program. Iata doua expresii echivalente:
asc<d d>sc&&a<3;
a s (c<d) (d>sc) && (a<3);
Este evident ca cea de a doua expresie este mai usor de citit.
12.5.5. Operatori la nivel de bit
Operatorii la nivel de bit din C permit programatorului sa manevreze bitii apartinand unor valori de tip char sau int (ei nu se aplica tipurilor de date float, double sau long double). Aceasta este una din acele facilitati care apropie limbajul C de nivelul limbajelor de asamblare.
Operatorii la nivel de bit sunt:
& SI pe bit
SAU pe bit
SAU exclusiv pe bit
A NU pe bit (complement fata de
>> deplasare dreapta (shift dreapta)
<< deplasare stanga (shift stanga)
Modul de actiune al primilor 4 operatori pe bit e rezumat
in
Tabelul 12.4.
Tabelul 12.4 Tabla valorilor de adevar pentru operatorii pe bit &,|,^ si A
a |
b |
a & b |
a b |
a ^ b |
Aa |
Se observa ca a^b ia valoarea numai daca bitii a si b sunt diferiti.
Asemanarea dintre Tabelul 12.3 si Tabelul 12.4 nu trebuie sa ne induca in eroare. Operatorii logici trateaza valorile si ale operanzilor ca pe valori logice, iar operatorii pe bit trateaza operanzii ca succesiuni de biti.
Sa consideram doua valori xs5 si ys36 si reprezentarile lor in baza , adica xs0000 0101 si ys0010 0100. Avem:
0000 0101
0010 0100
x&ys x|ys
0000 0101
x^ys Axs
Observatii:
Operatorul & reprezinta o modalitate de a elimina un bit (de a-l face egal cu ) sau de a retine bitii care ne intereseaza (operatie de mascare).
In operatiile de transmitere a datelor, bitul superior (cel mai din stanga) al unui octet este considerat de cele mai multe ori bit de paritate. Printr-o operatie de setare la a acestui bit sau de anulare a sa, se poate obtine un numar par sau impar de biti in octetul respectiv functie de conventia de paritate acceptata (paritatea poate fi para sau impara). Utilizarea paritatii, permite verificarea corectitudinii octetului transmis. Aratam mai jos cum se poate modifica bitul de paritate:
Exemplu:
Anularea bitului de paritate al unui octet.
1000 0101 &
Setarea la valoarea a bitului de paritate a unui octet.
Observam ca anularea bitului de paritate s-a facut prin "inmultire" cu numarul in baza ), iar setarea la valoarea a aceluiasi bit prin "adunare" cu numarul in baza
Datorita posibilitatii de a modifica valorile bitilor, operatorii pe bit se folosesc mai ales in proiectarea programelor de interfata cu dispozitive periferice.
O alta aplicatie interesanta se refera la codificarea si decodificarea unui fisier. O modalitate simpla este folosirea operatorului A, pornind de la observatia ca A(Ax)sx, pentru x intreg arbitrar. Deci, daca exista un program de codificare, la prima sa rulare toti bitii nuli devin , iar toti bitii de devin nuli ( codificarea). La o noua rulare este evident ca se obtine fisierul initial (decodificarea).
O posibilitate mai puternica de codificare si decodificare este operatorul . In acest caz pentru aceste operatii se foloseste o cheie.
Reluand exemplul cu valorile
xs0000 0101 si ys0010 0100
x^ys
sa presupunem ca y este cheia. Daca intre codificarea obtinuta (x^y) si cheia y efectuam din nou operatia se obtine rezultatul initial x (decodificarea).
(x^y)^ys
Forma generala a operatorilor de deplasare este:
variabila << numar_intreg, pentru deplasare la stanga
si
variabila >> numar intreg, pentru deplasare la dreapta.
Aici, numar_intreg se refera la numarul de pozitii cu care vor fi deplasati spre stanga, respectiv spre dreapta bitii variabilelor.
Deplasarea bitilor spre un capat sau altul poate produce pierderea unui numar de biti de la respectiva extremitate. Cu ce se completeaza bitii ramasi liberi?
Daca deplasarea este spre stanga, bitii liberi din dreapta se completeaza cu
Exemplu. Sa consideram declaratia
unsigned char xs7
Numarul este reprezentat in baza ca . Atunci, x<<1 va produce Daca vom continua cu x<<5 se va obtine ceea ce arata pierderea unui bit.
Daca deplasarea este spre dreapta, bitii liberi din stanga se completeaza automat cu numai daca numarul este fara semn. Daca numarul este negativ, din neccesitatea de a conserva semnul (reprezentat in bitul cel mai semnificativ cu ), bitii liberi din stanga se completeaza cu
Exemplu Sa consideram xs-9. Reprezentarea sa in binar se face folosind codul complementar fata de 2. Acesta se obtine adunand la complementul fata de 1 al numarului valoarea . Deci, valoarea se va obtine ca A9+1. Suita de operatii in binar este urmatoarea:
se reprezinta prin
A9 este
A9+1 este
se reprezinta prin 1111 0111
In consecinta, tinand cont de precizarea facuta mai sus
x>>2 va produce 1111 1101
Observatii. Deplasarea spre stanga cu n pozitii echivaleaza cu o inmultire a variabilei cu 2n, iar deplasarea spre dreapta cu n pozitii
echivaleaza cu impartire a variabilei cu 2n. Aceste deplasari
(operatii shift) sunt mai rapide decat
operatiile corespunzatoare de inmultire sau impartire
cu 2n
Exemplu:
Daca xs7 atunci:
x<<1 va produce adica, 7x21s14
Daca xs-8, atunci x>>2 va produce 1111 1110 adica, -2s-8/22
12.6. Conversii de tip implicite
Limbajul C ofera posibilitatea de a construi expresii cu date de tipuri diferite. Din acest motiv exista un set de reguli de conversie a operanzilor la tipul operandului cel mai >tare>. Aceste reguli sunt cunoscute sub numele de avansare de tip (type promotion). Iata setul de reguli:
Variabile de tip char si short se convertesc la tipul int.
Daca un operand este long double
Atunci al doilea este convertit la long double;
Altfel daca un operand este double
Atunci al doilea e convertit la double;
Altfel daca un operand este float
Atunci al doilea e convertit la float;
Altfel daca un operand este unsigned long
Atunci al doilea e convertit la unsigned long;
Altfel daca un operand este long
Atunci al doilea e convertit la long;
Altfel daca un operand este unsigned
Atunci al doilea este convertit la unsigned.
12.7. Conversii de tip explicite (operatorul cast)
Operatorul de conversie explicita (cast) actioneaza temporar, fortand schimbarea tipului expresiei la care se refera. Forma generala a operatorului este:
(tip)expresie;
unde tip este tipul la care dorim sa se faca conversia expresiei.
De exemplu, rezultatul evaluarii expresiei
(float)i/2;
unde i a fost definit prin
int is3;
este . Daca in aceleasi conditii se evalua expresia i/2; rezultatul ar fi fost trunchiat la (se imparteau doi intregi).
TESTE DE CONTROL
In C exista tipurile fundamentale de date:
a) char, int, float, double, void
b) char, integer, real, double, void
c) char, int, float, double, nul, boolean
d) character, string, real, void
Modificatorii de semn
a) schimba semnul unei expresii
b) schimba semnul numai pentru valori intregi
c) au ca efect interpretarea diferita din punct de vedere al semnului, a informatiei memorate intr-o anumita zona
Declaratiile
char x;
si
signed char x;
a) sunt echivalente
b) sunt gresite
c) sunt corecte
Declaratiile
short int x;
si
int x;
a) sunt echivalente
b) sunt gresite
c) sunt corecte
Declaratia
float x,y;
a) este echivalenta cu float x;float y;
b) este gresita
c) este echivalenta cu x,y:float;
d) este echivalenta cu real x,y;
Linia de program
char chs'A',Z;
are semnificatia:
a) variabila ch ia valori de la A la Z
b) variabila ch este de tip char si este initializata cu valoarea 'A', iar variabila Z este de tip char
c) tipul de date char ia valori de la A la Z
Liniile de program
const ore_zis24;
int ore_zis24;
a) sunt echivalente
b) sunt corecte si compatibile
c) sunt corecte si incompatibile
Secventa de program
int xs10,ys20,zs5,ws7;
printf("tn xs%i ys%i zs%d",x,y);
afiseaza:
a) xs10 ys20 zs5
b) date de iesire nedefinite
c) xs10 ys20
Secventa de program:
int xs10,ys20,zs5,ws7;
printf("tn xs%d ys%i",x,y,z);
a) afiseaza xs10 ys20
b) afiseaza xs10 ys20 zs5
c) este gresita
Instructiunea printf() de mai jos
printf("tnuu!ttaurul tn-are importanta!");
afiseaza
a) tnuu!ttaurultn-are importanta!
b) taurul n-are importanta!
c) uu!aurul -are importanta!
d) uu! aurul
-are importanta!
Secventa de program
int is10,js20;
printf("tn is%i,js%i",j,i);
afiseaza
a) is10, js20
b) is20, js10
c) is10% js20%
Secventa de program
int is10,js20;
printf("tn is%i,js%j",i,j);
a) afiseaza is10,js20
b) este gresita
c) afiseaza is%10,js%20
Secventa de program
char as'q';
printf("tn as%d",a);
a) este gresita deoarece %d este descriptor pentru tipul int, nu pentru tipul char
b) este corecta si afiseaza codul ASCII al caracterului q
c) este corecta si afiseaza codul ASCII al caracterului a
d) este corecta si afiseaza caracterul q
Secventa de program
float xs32.75;
printf("tn xs%e,xs%f",x,x);
a) este gresita deoarece argumentul x se repeta
b) este corecta si va afisa xs32.75,xs32.75
c) este corecta si va afisa xs3.275000e+01,xs32.750000
Secventa de program
int xs439;
printf("tn %o",x);
afiseaza:
a)
b) numarul scris in baza
c) numarul scris in baza
Secventa de program
int xs1011;
printf("tn %x",x);
afiseaza:
a) valoarea lui x in binar
b) valoarea lui x in hexazecimal
c) valoarea lui x in octal
Secventa de program
int xs12;
float ys31.42;
printf("tn xs%f ys%d",x,y);
a) afiseaza xs12 ys31.42
b) afiseaza xs12.0 ys31
c) este gresita
Secventa de program
float xs10.5;
printf("tn xs%-10.5f",x);
a) afiseaza xs-10.5
b) afiseaza xs10.50000
c) este gresita
Secventa de program
float xs10.5;
printf("tn xs%10.5",x);
a) afiseaza xs10.5
b) afiseaza xs 10.50000
c) afiseaza xs10.50000
Despre secventele de program
float x;
scanf("%f",x);
si
float x;
scanf("%f",&x);
se poate afirma ca:
a) sunt corecte si au acelasi efect
b) prima secventa este corecta si a doua incorecta
c) prima secventa este incorecta si a doua corecta
Secventa de program
printf("%.3s","abcde");
a) afiseaza abc
b) afiseaza abcde
c) este gresita
Daca sirul care trebuie citit de la tastatura este abcdef atunci secventa
scanf("%3s",sir);
a) este gresita, deoarece variabila sir nu e precedata de operatorul &
b) este gresita, deoarece sirul de intrare are caractere, iar descriptorul %s prevede doar caractere
c) este corecta, dar se citesc doar caracterele abc
Secventa de program
scanf("%d;%f;%s",&x,&y,sir);
a) este gresita, deoarece descriptorii de format sunt despartiti prin semnul ;
b) este gresita, deoarece variabila sir nu e precedata de operatorul &
c) este corecta si realizeaza corect citirea daca datele din fluxul de intrare sunt despartite prin semnul ;
Operatorii in C pot fi:
a) unari, binari
b) unari, binari, ternari
c) unari, binali, termali
Operatorii si pot fi:
a) numai unari
b) numai binari
c) unari sau binari
Secventa de program:
float x;
int i;
xs34.21;
isx;
a) este gresita deoarece se atribuie valoarea reala din x variabilei intregi i
b) este corecta
c) este corecta, iar i va primi valoarea
Secventa de program
int i;
float x;
is34;
xsi;
a) este gresita, deoarece se atribuie valoarea intreaga din i variabilei reale x
b) este corecta
c) este corecta, iar x va primi valoarea convertita in virgula mobila.
Linia de program
Vs(AsB*b)*h;
a) este eronata deoarece contine operatorul de atribuire s de doua ori
b) este corecta
c) este corecta si este echivalenta cu secventa de program
AsB*b;
VsA*h;
Linia de program
asbscs1;
a) este corecta si e echivalenta cu secventa
as1;
bs1;
cs1;
b) este corecta si e echivalenta cu secventa
1sasbsc;
c) este gresita deoarece operatorul de atribuire apare de mai multe ori
Expresia
x+s1;
a) este gresita
b) este corecta si echivalenta cu xsx+1
c) este corecta si echivalenta cu x++
d) este corecta si echivalenta cu ++x
Expresia
ys--x;
a) este gresita
b) este corecta si echivalenta cu secventa
xsx-1;
ysx;
c) este corecta si echivalenta cu secventa
ysx;
ysx-1;
Expresia
ysx--;
a) este gresita
b) este corecta si echivalenta cu secventa
xsx-1;
ysx;
c) este corecta si echivalenta cu secventa
ysx;
xsx-1;
In urma executiei secventei de program:
int i,j;
is19;
jsi/4;
a) j ia valoarea
b) j ia valoarea
c) j ia valoarea
In urma executiei secventei de program:
int i,j;
is19;
jsi%4;
a) j ia valoarea
b) j ia valoarea
c) j ia valoarea
Daca a,b,c,d sunt variabile numerice atunci expresia (a<b)||(c>d) se poate scrie:
a) a<b||c>d
b) a<(b||c)>d
c) c>d||a<b
In secventa de program
int xs3,ys4,a,b,z;
scanf("%i %i",&a,&b);
zs(y>x)||(a<b);
variabila z va lua
a) o valoare nedefinita
b) valoarea
c) o valoare care depinde de a si b
In secventa de program
int xs3,ys4,a,b,z;
scanf("%i %i",&a,&b);
zs(x>y)&&(a<b);
variabila z va lua
a) o valoare nedefinita
b) valoarea
c) o valoare care depinde de a si b
In secventa de program
int as3,bs4,x,y,z;
zs(xsa+b,ysx);
a) x ia valoarea
b) y ia valoarea
c) z ia valoarea
Operatorii pe bit se aplica
a) valorilor sau pe care le iau anumite variabile de tip int
b) variabilelor de tip char si int
c) variabilelor de tip float si double
Daca doi biti notati a si b au valorile as1 si bs1 atunci:
a) a|b ia valoarea
b) a&b ia valoarea
c) a^b ia valoarea
d) Aa ia valoarea
Daca doi biti notati a si b au valorile as1 si bs0 atunci:
a) a|b ia valoarea
b) a&b ia valoarea
c) a^b ia valoarea
d) Ab ia valoarea
Secventa de program
int xs192;
printf("tn xs%", x<<1);
afiseaza
a)
b)
c)
Complementul fata de al numarului intreg i se obtine ca:
a) Ai
b) 1-i
Complementul fata de al numarului intreg i se obtine ca:
a) Ai+1
b) Ai-1
c) 2-i
Operatia x<<3 echivaleaza cu:
a) o inmultire a lui x cu
b) o impartire a lui x la
c) o inmultire a lui x cu
d) o impartire a lui x cu
Operatia x>>2 echivaleaza cu:
a) o inmultire a lui x cu
b) o impartire a lui x la
c) o inmultire a lui x cu
d) o impartire a lui x cu
Secventa de program
int is7;
float x;
xs(float)i/4;
printf("tn xs%f",x);
a) este gresita
b) este corecta si afiseaza xs1.750000
c) este corecta si afiseaza xs1
RASPUNSURI
12.1-a 12.2-c 12.3-a, c 12.4-c 12.5-a
12.6-b 12.7-c 12.8-b 12.9-a 12.10-d
12.11-b 12.12-a 12.13-b 12.14-c 12.15-b
12.16-b 12.17-c 12.18-b 12.19-b 12.20-c
12.21-a 12.22-c 12.23-c 12.24-b 12.25-c
12.26-b, c 12.27-b, c 12.28-b, c 12.29-a 12.30-b, c, d
12.31-b 12.32-c 12.33-a 12.34-a 12.35-a, c
12.36-b 12.37-b 12.38-a, b, c 12.39-b 12.40-a, b, c, d
12.41-a, b, c, d 12.42-c 12.43-a 12.44-a 12.45-c
12.46-d 12.47b
|