Sunteți pe pagina 1din 8

PRELEGERE VII

PROGRAMAREA CALCULATOARELOR ŞI LIMBAJE DE PROGRAMARE

Studiul elementelor de bază din limbajul C++ - continuare

I. Operatori şi expresii
O succesiune nedeterminată de operanzi şi operatori defineşte o expresie. Structura
acesteia depinde de prelucrările pe care urmează să le facă utilizatorul. Evaluarea
expresiei constă în efectuarea tuturor operaţiilor componente după reguli bine stabilite.
Rezultatul obţinut depinde atît de tipul operanzilor, cît şi de operatorii care acţionează
asupra acestora. Valorile operanzilor sînt utilizate fie direct, fie după realizarea unor
conversii impuse de semnificaţia operatorului care se execută asupra operanzilor. De
asemenea, un rol predominant îl au poziţia şi ordinea operatorilor în expresie. Vom
vedea ulterior (ca de exemplu în cazul operatorilor de incrementare şi decrementare),
poziţia operatorului faţă de operand permite ataşarea la numele acestuia unul din
atributele prefixat, infixat sau postfixat. În absenţa parantezelor, ordinea de efectuare a
operatorilor este determinată de apartenenţa operanzilor la diverse clase de precedenţă.
Pentru toate categoriile de operatori descrişi în subsecţiunile următoare, ordinea claselor
de precedenţă este dată în mod descrescător. Pentru priorităţi egale în cadrul unei clase de
precedenţă este precizată o regulă de asociativitate, care arată ordinea aplicării
operatorilor consecutivi. În cele mai multe cazuri, aceasta este de la stînga către dreapta.
O sinteză a acestora este prezentată în tabelul de la sfîrşitul secţiunii.
Spre deosebire de alte limbaje de programare de nivel înalt, limbajul C pune la dispoziţia
utilizatorului o gamă variată de tipuri de operatori: aritmetici, relaţionali, logici de bază şi
la nivel de bit, de deplasare, de incrementare şi decrementare, de asignare, de adresare,
condiţionali, de secvenţiere (operatorul virgulă), de lungime (sizeof), operatorul de
conversie (cast) şi operatorul de rezoluţie (de acces, de domeniu). Caracteristici
programării orientată spre obiect sînt operatorii de alocare dinamică a memoriei new şi
delete. Unele simboluri utilizate în notaţia operatorilor coincid cu cele din matematică,
altele sînt specifice programării calculatoarelor.
O parte dintre operatorii aritmetici şi logici sînt binari, +(adunare), -(scădere),
*(înmulţire), /(împărţire), %(modulo) şi respectiv &&(conjuncţie), ||(disjuncţie),
^(disjuncţie exclusivă), &(conjuncţie la nivel de bit), |(disjuncţie la nivel de bit).
Operatorii de negaţie (!, ~), de incrementare (++), de decrementare (--), sizeof, +, -, cast,
de rezoluţie (::), new şi delete sînt unari, iar operatorul condiţional este ternar.
Operatorul de rezoluţie va face obiectul secţiunii 4.3.2, operatorii new şi delete se vor
prezenta în secţiunea 7.5, iar restul operatorilor se vor analiza în cele ce urmează.
Combinînd operanzi de tipuri diferite cu operatori corespunzători, este posibil ca tratarea
unor situaţii, din contextul de rezolvare al problemei date, să conducă la scrierea unei
singure expresii complexe, care s-ar fi codificat, altfel, printr-o secvenţă de instrucţiuni
de atribuire. Evaluarea unei astfel de expresii poate să fie cu efecte laterale, determinate
prin schimbarea valorii unei variabile sau a valorilor mai multor variabile.

1
Un caz particular de expresie îl constituie aceea care se reduce la o constantă, un nume de
variabilă, un element de tablou, un cîmp sau un apel de funcţie, denumită şi expresie
simplă.

1) Operatori şi expresii de tip aritmetic


Abordarea expresiilor aritmetice în limbajul C nu diferă de cea cunoscută în matematica
elementară, doar cu excepţia unor convenţii de notaţie pentru o parte dintre operatori.
Astfel, într-o expresia aritmetică pot interveni următorii operatori: adunarea (+), scăderea
(-), înmulţirea (*), împărţirea (/) şi modulo sau restul împărţirii a două constante întregi
(%). Operatorul % aplicat asupra operanzilor a şi b produce 0 dacă b este divizor a lui a,
altfel un rezultat cuprins între 1 şi b-1.
Primii doi operatori pot să apară şi ca operatori unari. Dacă operatorul unar + lipseşte,
atunci numărul se consideră pozitiv.
Ordinea de prioritatea în evaluarea operatorilor aritmetici este prezentată în mod
descrescător, după cum urmează:
operatorii unari + şi - ;
*, / şi %;
+ şi -.
La priorităţi egale operaţiile se execută în ordinea în care apar, adică de la stînga către
dreapta. Ordinea poate fi schimbată, cu ajutorul parantezelor rotunde în cazul
operatorilor asociativi şi comutativi de tipul * şi +. De exemplu, x*y*z poate fi evaluată
sub una din formele, fie (x*y)*z, fie x*(y*z).
Ca operanzi aritmetici se enumeră: expresii simple, expresii aritmetice între paranteze
simple sau (nume_tip) expresie aritmetică.
Un calcul flexibil şi conform cerinţelor programatorului se obţine utilizînd
parantezele rotunde. În cazul perechilor de paranteze imbricate, asocierea lor se
realizează după principiul “ultima paranteză deschisă - cu prima paranteză închisă”. Orice
neconcordanţă în asocierea parantezelor declanşează eroare de complilare.
Cu programul 7.1.1 se află valoarea unei expresii aritmetice, care conţine
operatorii aritmetici +, -, *, şi /. Operanzii sînt constante întregi şi variabile cu tipul float
(raţional simplă precizie). Ordinea de execuţie a operatorilor aritmetici se poate urmări în
desfăşurătorul rezultatelor parţiale.
/* Evaluarea pe etape a unei expresii aritmetice */
#include<stdio.h>
#include<conio.h>
void main(void)
{ float a = 3., b = 5., c = 2.;
int x = 1, y = 4;
clrscr();
printf("\n Valoarea expresiei 5*a*b-c-x/y+a*y este: \
%f", 5*a*b-c-x/y+a*y);
printf("\n Desfasurarea operatiilor:");
printf("\n 5*a=%f", 5*a);
printf("\n 5*a*b=%f", 5*a*b);
printf("\n x/y=%f", x/y);
printf("\n a*y=%f", a*y);

2
printf("\n 5*a*b-c=%f", 5*a*b-c);
printf("\n 5*a*b-c-x/y=%f", 5*a*b-c-x/y);
printf("\n 5*a*b-c-x/y+a*y=%f", 5*a*b-c-x/y+a*y);
getch(); }
Valoarea expresiei 5*a*b-c-x/y+a*y este: 85.000000
Desfasurarea operatiilor:
1. 5*a=15.000000 4. a*y=12.000000
2. 5*a*b=75.000000 5. 5*a*b-c=73.00000
3. x/y=0.000000 6. 5*a*b-c-x/y=73.000000
7. 5*a*b-c-x/y+a*y=85.000000
Programul 7.1.1
Observaţie. Operaţia de ridicare la putere (xy) se codifică, fie prin funcţia pow(x,y), fie
prin expresia exp(y*log(x)). Funcţiile predefinite pow, exp şi log sînt incluse în biblioteca
standard math.h.

2) Operatori şi expresii de tip relaţional


Operatorii relaţionali permit compararea a două valori de diverse tipuri (numeric,
caracter) sau a doi pointeri. Cu alte cuvinte, orice condiţie din contextul rezolvării unei
probleme se descrie prin intermediul unei expresiei relaţionale.
Ca operator de relaţie se poate utiliza, după cum urmează:
< mai mic > mai mare
<= mai mic sau egal >= mai mare sau egal
== egal != diferit.
Deoarece numai expresiile aritmetice pot interveni ca operanzi în expresiile de tip
relaţional, clasa de precedenţă a operatorilor relaţionali se situează după cea a
operatorilor aritmetici. Rezultatul unei expresii relaţionale este 1 dacă aceasta este
adevărată şi 0 în caz contrar.
Pentru expresia relaţională 5*x+yşz<=x*x-y-z s-au considerat două seturi de date
de test. Acestea şi rezultatele corespunzătoare se pot urmări în ultimele două coloane din
tabelul care însoţeşte, în final, programul 7.2.1.
De asemenea, se observă că primul set de valori nu verifică relaţia, ceea ce nu este
cazul cu cel de al doilea set.
/* Evaluarea unei expresii relationale */
#include<stdio.h>
#include<conio.h>
void main(void)
{ int x, y, z;
clrscr();
printf("\n Introduceti x, y si z: ");
scanf("%d%d%d", &x, &y, &z);
printf("\nVal. exp. relat. \
5*x+y/z<=x*x-y-z este : %d", 5*x+y/z<=x*x-y-z);
printf("\n Val. operand 5*x+y/z este : %d", 5*x+y/z);
printf("\n Val. operand x*x-y-z este : %d", x*x-y-z);
getch(); }

3
Introduceţi x, y şi z 3 4 5 34 123 3
Val. exp. relat. 5*x+yşz<=x*x-y-z este : 0 1
Val. operand. 5*x+yşz este : 15 211
Val operand x*x-y-z este : 0 1030
Programul 7.2.1.

3) Operatori şi expresii de tip logic


Declararea explicită a operanzilor logici în limbajul C nu este posibilă. Neexistînd ca în
alte limbaje de programare tipul boolean, recunoaşterea expresiilor logici se face, în mod
implicit, după tipul logic al operatorilor componenţi. În consecinţă, orice tip scalar de
operand se admite conform convenţiei: valoarea nenulă a operandului se intrpretează
adevărat, iar zero ca fals. Aşa cum s- a precizat şi în secţiunea anterioară, cele două
valori logice sînt codificate prin numerele întregi 1 şi respectiv 0. Deci, rezultatul unei
expresii logice este de tip întreg.
Operatorii logici se prezintă în ordinea de prioritate descrescătoare:
! negaţie
&& conjuncţie (şi logic)
|| disjuncţie (sau logic).
Tabelul 7.3.1 sintetizează definiţiile operatorilor logici.
a b a && b a || b !a
==0 0 0 0 1
==0 !=0 0 1
!=0 0 0 1 0
!= !=0 1 1
Tabelul 7.3.1
La priorităţi egale, cu excepţia negaţiei, execuţia operatorilor logici se face de la stînga
către dreapta, iar operatorii relaţionali sînt prioritari acestora.
În următoarele două situaţii se poate renunţa la evaluarea unuia dintre operanzi,
ştiindu-se din valoarea celuilalt care este rezultatul operaţiei logice, şi anume:
- pentru a == 0, rezultatul conjuncţiei este 0;
- pentru a!=0, rezultatul disjuncţiei este 1. Însă, neevaluarea unuia sau a mai
multor operanzi într-o expresie logică poate induce erori în cazul operatorilor cu efecte
laterale.
Observaţie. Prezenţa sau absenţa operatorului de negaţie (!) permite simplificarea unor
construcţii de genul:
- condiţia “a nul” (a==0) este echivalentă cu !a;
- condiţia “a şi b diferiţi de zero” (a!=0 && b!=0) este echivalentă cu a && b.
Rezultatele programului 6.8.3.1 evidenţiază etapele în care se evaluează expresia logică
(x+3)==(y-1) || x*x && 7 && !x && 0 . În componenţa acesteia intervine o expresie
relaţională, variabilele x şi y şi constantele 7 şi 0.
Pentru x=1 şi y=15 expresia este falsă.
/* Evaluarea pe etape a unei expresii logice */
#include<stdio.h>
#include<conio.h>
void main(void)
{ float x = 1, y = 15;

4
clrscr();
printf("\n x="); scanf("%f", &x);
printf(" y="); scanf("%f", &y);
printf("\n Val exp. relat. (x+3)==(y-1) este :%d",
(x+3)==(y-1));
printf("\n Val exp. logice !x este :%d", !x);
printf("\n Val exp. logice x*x && 7 este :%d", x*x && 7);
printf("\n Val exp. logice x*x && 7 && !x este :%d",
x*x && 7 && !x);
printf("\n Val exp. logice x*x && 7 && !x && 0 este :%d",
x*x && 7 && !x && 0 );
printf("\n Val. exp. logice\
((x+3)==(y-1) || x*x && 7 && !x && 0) este : %d",
(x+3)==(y-1 )|| x*x && 7 && !x && 0);
getch(); }
Val. exp. relat. (x+3)==(y-1) este : 0
Val exp. logice !x este : 0
Val exp. logice x*x && 7 este : 1
Val exp. logice x*x && 7 && !x este : 0
Val exp. logice x*x && 7 && !x && 0 este : 0
Val. exp. logice ((x+3)==(y-1) || x*x && 7 && !x && 0) este : 0
Programul 7.3.1.

4) Operatori la nivel de bit


Prin introducerea operatorilor la nivel de bit în limbajul C s-a urmărit apropierea acestuia
de limbajele de asamblare. Astfel de încercări se regăsesc şi în alte limbaje de
programare, cum ar fi limbajul Pascal.
Operatorii la nivel de bit acţionează numai asupra operanzilor de tip întreg. La cele patru
operaţii logice menţionate în secţiunea precedentă, se adaugă disjuncţia excusivă şi
operaţiile de deplasare stînga şi dreapta. Nu numai simbolurile prin care se reprezintă
operatorii logici la nivel de bit diferă faţă de cele prezentate anterior, ci şi modul lor de
definire.
În ordinea prorităţii lor descrescătoare se enumeră toşi operatori la nivel de bit:
~ negaţie
& conjuncţie (şi logic)
^ disjuncţie exclusivă (sau exclusiv)
| disjuncţie (sau logic)
<< deplasare stînga
>> deplasare dreapta. Regula de aplicare a operanzilor la priorităţi
egale coincide ce cea de la operatorii logici.
Tabelul de mai jos cuprinde definiţiile tuturor operatorilor logici la nivel de bit.
a b ~a a&b a^b a | b
0 0 1 0 0 0
0 1 1 0 1 1
1 0 0 0 1 1
1 1 0 1 0 1

5
Tabelul 7.4.1
În continuare, cîteva aplicaţii simple se pot verifica, imediat, prin consultarea
tabelului de mai sus: 1010 & 1100 = 1000, 10000 | 110 = 10110 şi ~10 = 01.
Diferenţa dintre cele două categorii de operatori logici se ilustrează prin următorul
exemplu: dacă a şi b au valorile 2 şi respectiv 3, atunci a&&b = 1, iar a&b = 3. Anexa 3
cuprinde codificarea cifrelor octale şi hexazecimale în binar pe 3 şi respectiv 4 poziţii.
În general, operatorii & şi | sînt utilizaţi pentru a masca anumite poziţii într-o
înşiruire de cifre binare. Anularea biţilor care interesează, cu reţinerea celorlalţi şi
respectiv schimbarea unor biţi 0 în 1, cu menţinerea neschimbată a celorlaţi, sînt aşa
numitele operaţii de “mascare”.
Operatorii de deplasare intervin în construcţii cu următoarea sintaxă:
operand << nr_poziţii şi operand >> nr_poziţii.
După cum s-a precizat deja, operandul este, fie o constantă întreagă, fie o variabilă de tip
întreg. Numărul de poziţii (nr_poziţii) precizează cu cîte poziţii binare se deplasează spre
stînga şi respectiv spre dreapta valoarea operandului în codificare binară. În fapt, numai
operandul este prelucrat la nivel de bit.
La o deplasare către stînga, biţii semnificativi se pierd, iar în dreapta se inserează un
număr egal cu nr_poziţii de cifre binare 0. Dacă tipul operandului este unsigned, atunci
deplasarea este echivalentă cu o înmulţire a valorii operandului cu 2nr_poziţii. Altfel, semnul
valorii poate fi afectat. Dacă nr_poziţii > sizeof(operand)*8, atunci rezultatul unei
deplasări către stînga este 0.
La o deplasare spre dreapta, biţii corespunzători din dreapta se elimină, iar în stînga se
completează cu 0 sau 1 după cum tipul operandului este sau nu unsigned. În prima
situaţie, deplasarea este echivalentă cu împărţirea operandului prin 2nr_poziţii, iar în cea de a
doua, cu păstrarea semnului.
De exemplu, dacă a=001100 atunci a<<2=110000, iar a>>2=000011.

5) Operatori şi expresii de atribuire


Operatorul de atribuire (asignare) poate fi simplu sau combinat. Simbolul prin care se
reprezintă operatorul de atribuire simplu este =. La rîndul lui, operatorul simplu poate fi
combinat cu unul dintre următorii operatori binari: +, -, *, /, %, &, ^, |, << şi >>. Sintaxa
unui operator de atribuire multiplu (compus, combinat) fiind operator_binar=. Există 10
combinaţii posibile pentru operatorii de atribuire combinaţi:
+= -= /= *= %=
&= |= ^=
<<= >>= .
Construcţia generică prin care se descrie o expresie de atribuire este următoarea:
nume_variabila operator_binar = (expresie);.
În mod desfăşurat, expresia de atribuire se poate descrie, astfel: nume_variabila
=nume_variabila operator_binar (expresie);.
Adică, noua valoare a variabilei depinde de ultima valoare a varibilei memorată în
locaţia afectată acesteia, de valoarea expresiei şi de operatorul care acţionează asupra lor.
Dacă variabila este de tip structură, atunci este posibilă o referire, prin calificare, la un
cîmp din cadrul structurii.
Expresia componentă poate fi aritmetică, logică la nivel de bit (care poate să conţină şi
operatori de deplasare, a se vedea programul 2.8.4.1) sau de atribuire. Prezenţa

6
parantezelor nu este obligatorie, însă utilizarea acestora se recomandă în situaţii care pot
genera confuzii. Atribuirea combinată de genul: a*= b-5; se interpretează a = a*(b-5); şi
nu a = a*b-5;.
Pînă la limbajul C, atribuirea, cu sintaxa generală:
nume_variabila=expresie;,
era tratată ca o instrucţiune de bază. Valoarea expresiei din membrul al doilea se memora
la adresa locaţiei afectată variabilei, după ce, în prealabil, se convertise la tipul variabilei
în cazul compatibilităţii lor, prin distrugerea valoarii anterioară a variabilei.
Acum, atribuirea se tratează ca o operaţie cu efecte laterale, concretizate în modificarea
valorii uneia sau a mai multor variabile, dar care furnizează şi un rezultat ce poate fi
utilizat în prelucrările ulterioare. Valoarea (sub)expresiei trebuie să fie compatibilă din
punct de vedere al atribuirii cu tipul variabilei. Aceasta se înscrie în zona de memorie
alocată variabilei, înlocuind valoarea existentă a variabilei respective. În cazul cînd tipul
valorii din membrul al doilea diferă de cel al variabilei, tipul valorii se converteşte la cel
al variabilei. Conversiile automate depind de tipul celor doi operanzi şi pot avea rezultate
diferite de la o implementare la alta. Efecte nedorite apar cînd domeniul de valori al
tipului celui de al doilea operand este mai larg decît cel al tipului variabilei. Acestea apar,
mai ales, în cazul atribuirilor de tip numeric. Ele constau în trunchieri ale cifrelor
semnificative, fără să fi fost semnalate şi deci, precizia rezultatelor este mai mult decît
precară. Conversiile cu efect negativ se numesc degradante. Tabelul 7.5.1 precizează
situaţia conversiilor degradante în cazul atribuirilor de tip aritmetic.
Dacă este necesar, cu sizeof(), se poate afla spaţiul de memorie afectat fiecărui tip
în parte pentru implementări ale limbajului pe diverse tipuri de calculatoare.
Tip variabilă Tip expresie Pierderi de informaţie
(octeţii mai semnificativi)
signed char unsigned char un număr negativ, dacă
valoarea >127
int long int 3
int float cel puţin partea fracţionară
float double se reduce precizia
( cu rotunjire)
double long double se reduce precizia
(prin rotunjire)
unsigned char int 1
unsigned char long int 3
Tabelul 7.5.1
Cu următoarele exemple se aduce un plus de clarificare la afirmaţiile de mai sus.
1. Dacă x este de tip întreg atunci, în urma efectuării expresiei de atribuire x = 5;, x
primeşte valoarea 5, deci rezultatul expresiei este 5.
2. Evaluarea expresiei de atribuire multiple x = y = z = 5; este echivalentă cu evaluarea
expresiilor de atribuire z = 15;, y = z; şi x= y;. Prin utilizarea parantezelor rotunde
expresia de atribuire devine: (x=(y=(z=15)));. În final, dacă variabilele sînt de tip întreg,
atunci în toate locaţiile de memorie corespunzătoare variabililor x, y şi z se înscrie
valoarea 15.
3. Într-o expresie de atribuire se pot introduce şi valorile afectate variabililor
componente, de exemplu: a=(b=2+(c=3));.

7
4. Aria bazei şi volumul unui con, cu raza r şi înălţimea h, pot fi calculate folosind o
expresie de atribuire de genul
volum=h * (aria_bazei=pi * r*r)/3; care este echivalentă cu secvenţa de instrucţiuni din
programarea tradiţională:
aria_bazei=pi*r * r;
volum=h * aria_bazei/3;.
5. Folosirea unei expresii de atribuire poate restrînge descrierea unor instrucţiuni de
decizie sau de repetare, cum ar fi :
while((c=getchar())!=’\0’) j-=2;. Echivalent, se poate scrie:
c=getchar();
while(c!=’\0’) j-=2;. Caracterul introdus de la tastatură prin funcţia getchar() se
atribuie lui c. Dacă acesta este diferit de ‘\0’, atunci j=j-2;.
Ultimele trei exemple reliefează utilitatea scrierii compacte şi imbricate a unor secvenţe
de prelucrări prin expresii de atribuire, care pentru începători poate, pe de o parte,
îngreuna lizibilitatea programului, iar pe de alta, facilita introducerea de erori.
Există totuşi situaţii în care nu trebuie să ne temem de aceste dezavantaje:
folosirea expresiilor de asignare în codificarea formulelor de recurenţă cu membrul stîng
conţinînd elemente de tablou cu indici expresii complexe sau forme complicate de
referiri de cîmpuri. De exemplu, tablou[i*j+5*k,i*j-5*k]+=10; este scrierea succintă
pentru expresia următoare: tablou[i*j+5*k,i*j-5*k]= tablou[i*j+5*k,i*j-5*k]+10;.
Programul 7.5.1 are ca scop simularea unor conversii degradante (inoportune) în
cazul expresiilor de atribuire de tip întreg. Limitele superioare sau inferioare ale
diferitelor domenii de valori întregi sînt exprimate în sistemul de numeraţie hexazecimal.
Operatorii multipli utilizaţi sînt += şi -=.
/* Operatorii de atribuire -
in conversii degradante de tip intreg */
#include <stdio.h>
#include <conio.h>
void main(void)
{ int a=0x7FFF;
short int d=0x0;
clrscr();
printf("\n a= %0x a+1000= %d",a,a+=1000);
printf("\n d= %0x d-1000= %d",d,d-=1000);
getch(); }
a=0x7FFF a+1000=-31769
d=0x0 d-1000=-1000
Programul 7.5.1

S-ar putea să vă placă și