Sunteți pe pagina 1din 16

Cursul 7

Expresii în C/C++
(II)

5 C. Operatori binari.

Niv Nr Simbol Operator / Operaţie Apelare As. RV LV SE EO


IV 1 * / % op. multiplicativi expr * expr >>> cto - - -
V 2 + - op. aditivi expr + expr >>> cto - - -
VI 3 << >> op. de deplasare pe biţi expr << deplasare >>> int - - -
VII 4 <= < > >= op. de comparaţie expr < expr >>> bool - - -
VIII 5 == != op. de egalitate expr == expr >>> bool - - -
IX 6 & op. şi pe biţi expr & expr >>> int - - -
X 7 ^ op. sau exclusiv pe biţi expr ^ expr >>> int - - -
XI 8 | op. sau pe biţi expr | expr >>> int - - -
XII 9 && op. şi logic expr && expr >>> bool - - >>
XIII 10 || op. sau logic expr || expr >>> bool - - >>

Operatorii binari aritmetici au tipul rezultatului stabilit în funcţie de tipul operanzilor, ca-
re pot fi de tip aritmetic sau de tip pointer. In cazul prezenţei pointerilor se aplică regulile de cal-
cul cu pointeri, iar în cazul ambilor operanzi de tip aritmetic se aplică următoarea convenţie: da-
că operanzii au acelaşi tip rezultatul are tipul comun, în caz contrar operandul cu tipul mai mic
este convertit la tipul celuilalt care va fi şi tipul rezultatului. Ordinea tipurilor aritmetice este ur-
mătoarea:
int < unsigned int < long int <unsigned long < float < double < long double
Orice operand de tip char sau short este promovat din start la int şi apoi, dacă mai este cazul, la
tipul rezultatului.
Operatorii binari logici au rezultatul de tip bool iar operanzii trebuie să fie de tip
aritmetic sau logic. La evaluare operanzii de tip aritmetic sunt mai întâi convertiţi implicit la
tipul bool şi apoi se aplică operaţiile logice corespunzătoare. Conversia implicită la bool se
efectueaza după convenţia: zero trece în false, orice valoare nenulă trece în true.

C1. Operatorii multiplicativi sunt următorii: operatorul de înmulţire *, operatorul de împărţire /


şi operatorul modulo %. Operanzii trebuie să fie aritmetici. In cazul împărţirii, dacă ambii ope-
ranzi sunt întregi rezultatul este câtul împărţirii cu rest, altfel are loc împărţirea cu virgulă. La
împărţirea cu rest câtul se determină prin trunchiere:
cout<<8.0/-3.0<<endl; //-2.66667
cout<<8/-3<<endl; //-2

1
Toţi aceşti trei operatori au acelaşi nivel de prioritate iar asocierea lor este de la stânga la
dreapta:

cout<<2*100/2*100<<endl; //10000

Operatorul modulo se aplică numai operanzilor întregi şi are ca rezultat restul împărţirii
determinat astfel încât să fie respectată „proba împărţirii”:
a== (a / b) * b + a % b
Deoarece câtul a/b se determină prin trunchiere, în cazul existenţei unui operand negativ restul
a%b rezultă negativ. Din acest motiv, pentru a decide de exemplu dacă un număr întreg n este
congruent cu 2 modulo 3 (adică este de forma 3k+2), nu este suficient să testăm dacă n%3 == 2,
deoarece şi acele numere n pentru care n%3 == -1 sunt conguente cu 2 modulo 3. In astfel de
situaţii este indicat să testăm numai congruenţa cu zero:

#include<iostream>
using namespace std;
int main(void){
int tab[10]={12,14,23,-4,54,56,82,72,58,34};
cout<<"In tabel sunt urmatoarele numere de forma 3k+2:"<<endl;
for(int i=0;i<10;i++)
//if(tab[i]%3==2 || tab[i]%3==-1) cout<<tab[i]<<"; ";
if( (tab[i]-2)%3==0) cout<<tab[i]<<"; ";
cout<<endl;
return 0;
}
/*REZULTAT:
In tabel sunt urmatoarele numere de forma 3k+2:
14; 23; -4; 56;
Press any key to continue . . . */

C2. Operatorii binari aditivi sunt următorii: operatorul de adunare + şi operatorul de scădere – .
Aceşti operatori se aplică la operanzi de tip aritmetic sau de tip pointeri. Dacă ambii operanzi
sunt aritmetici se aplică conversiile aritmetice uzuale, rezultatul fiind suma sau, respectiv, di-
ferenţa operanzilor.
Dacă unul dintre operanzi este de tip pointer se aplică regulile de calcul cu pointeri, care
vor fi studiate mai târziu. Un exemplu:

#include<iostream>
using namespace std;
int main(void){
int i;
int* p=&i;
cout<<p<<endl; //0023F814
cout<<p+1<<endl; //0023F818
return 0;
}

C3. Operatorii de deplasare pe biţi sunt utilizaţi pentru calcule logice rapide în binar. Operanzii
trebuie să fie de tip întreg, cadrul natural de lucru fiind unsigned int (în cazul operanzilor
negativi rezultatul depinde de compilator).

2
In baza 2, înmulţirea lui n cu 2m se execută prin mutarea cifrelor lui n în stânga cu m po-
ziţii şi completerea cu zerouri în dreapta, operaţie efectuată de operatorul de deplasare la stanga
<< prin expresia n<<m. Exemplu:

unsigned n=40;
unsigned n_ori_8=n<<3;
cout<<n_ori_8; //320

Analog, împărţirea lui n cu 2m se execută în baza 2 prin mutarea cifrelor lui n în dreapta
cu m poziţii şi completerea cu zerouri în stânga, operaţie efectuată de operatorul de deplasare la
dreapta >> prin expresia n>>m. Exemplu:

unsigned n=40;
unsigned n_supra_8=n>>3;
cout<<n_supra_8; //5

Următorul program afişează „înainte şi înapoi” primele 32 de puteri ale lui 2:

#include<iostream>
using namespace std;

int main(){
unsigned a,b;
for(int i=0; i<32; i++){
a=1u<<i;
cout<<a<<endl;
}
for(int i=0; i<32; i++){
b=a>>i;
cout<<b<<endl;
}
return 0;
}

Observaţie: în expresia cout<<a operatorul de deplasare la stânga are ca operanzi obiec-


tul cout şi întregul a, şi nu doi întregi aşa cum cere limbajul C. Acest lucru este posibil deoarece
în C++ operatorii predefiniţi pot fi supraîncărcaţi asfel încât să poată fi aplicaţi şi obiectelor de
tip clasă. Expresia dată este tradusă automat de orice compilator de C++ în apelul unei funcţii
membru cu numele „operator<<”, astfel:
cout.operator<<(a);
iar acest apel este acceptat deoarece în clasa ostream a obiectului cout a fost definit modul de
acţiune al acestei funcţii. In clasele de stream-uri operatorii << şi >> au fost deci supraîncărcaţi,
primind noi semnificaţii. Atenţie, supraîncărcarea operatorilor extinde numai domeniul lor de
aplicabilitate dar nu le poate schimba nici nivelul de prioritate şi nici ordinea de asociere.

C4. Operatorii de comparaţie sunt următorii 4, cu sensuri evidente: strict mai mic <, strict mai
mare >, mai mic sau egal <=, mai mare sau egal >=. Operanzii lor trebuie să fie ambii de tip
aritmetic sau ambii de tip pointer. Rezultatul are tipul bool, cu valoarea true (1) dacă relaţia este
respectată şi false (0) în caz contrar.
Iată un exemplu corect de utilizare:

3
#include<iostream>
using namespace std;
int main(){
int a,b;
cout<<"a="; cin>>a;
cout<<"b="; cin>>b;
if(a<b) cout<<"a<b"<<endl;
else cout<<"a>=b"<<endl;
return 0;
}

unul ciudat:

#include<iostream>
using namespace std;
int main(){
char mesaje[2][100]={"a<b","a>=b"};
int a,b;
cout<<"a="; cin>>a;
cout<<"b="; cin>>b;
cout<<mesaje[a>=b]<<endl;
return 0;
}

şi unul greşit:

#include<iostream>
using namespace std;
int main(){
int a=10, b=5, c=3;
if( a > b > c )
cout<<"DA"<<endl;
else
cout<<"NU"<<endl;
return 0;
} //pe monitor: NU

C5. Operatorii de egalitate sunt operatorul egal-egal == şi operatorul not-egal !=. La fel ca la
operatorii de comparaţie ambii operanzi trebuie să fie aritmetici sau ambii de tip pointer, iar
rezultatul este de tip bool.
Expresia a!=b este echivalentă cu !(a==b), dar prima este de preferat deoarece are un
singur operand iar ultima are doi. Expresia a!=b nu este echivalentă cu !a==b deoarece
operatorul de negare leagă mai tare decât comparaţia. Atenţie şi la confuzia des întâlnită între
comparaţie şi atribuire:

#include<iostream>
using namespace std;
int main(){
int a=1, b=2;

cout<<"\nCORECT:"<<endl;

cout<<"a="<<a<<" b="<<b<<endl;
if(a==b)

4
cout<<"DA a==b"<<endl;
else
cout<<"NU a==b"<<endl;
cout<<"a="<<a<<" b="<<b<<endl;

cout<<"\nGRESIT:"<<endl;

cout<<"a="<<a<<" b="<<b<<endl;
if(a=b)
cout<<"DA a=b"<<endl;
else
cout<<"NU a=b"<<endl;
cout<<"a="<<a<<" b="<<b<<endl;
a=0;
b=0;

cout<<"\nGRESIT:"<<endl;

cout<<"a="<<a<<" b="<<b<<endl;
if(a=b)
cout<<"DA a=b"<<endl;
else
cout<<"NU a=b"<<endl;
cout<<"a="<<a<<" b="<<b<<endl;
return 0;
}
/*REZULTAT:

CORECT:
a=1 b=2
NU a==b
a=1 b=2

GRESIT:
a=1 b=2
DA a=b
a=2 b=2

GRESIT:
a=0 b=0
NU a=b
a=0 b=0
Press any key to continue*/

C6. Operatorul „şi” pe biţi & se aplică la doi întregi după ce s-au efectuat conversiile aritmetice uzuale.
Cei doi operanzi sunt parcurşi în acelaşi timp şi se aplică pe rând operatorul boolean „şi” biţilor de acelaşi
rang.

C7. Operatorul „sau exclusiv” pe biţi ^ este analog cu operatorul „şi” pe biţi, operaţia iterată fiind „sau
exclusiv”. Rezultatul lui „sau exclusiv” aplicat unei perchi de biţi este 0 dacă biţii sunt egali şi 1 dacă sunt
diferiţi.

C8. Operatorul „sau” pe biţi | este analog cu operatori pe biţi de mai sus, operaţia iterată fiind acum
„sau”-ul boolean.
Cadrul natural de lucru pentru operatorii logici pe biţi este tipul unsigned. Exemplu de aplicare:

5
#include<iostream>
using namespace std;
int main(void){
unsigned p,q,x,y,z;
p=0xff00aa00; //1111 1111 0000 0000 1010 1010 0000 0000
q=0xaabbccdd; //1010 1010 1011 1011 1100 1100 1101 1101

x=p&q; //1010 1010 0000 0000 1000 1000 0000 0000


y=p^q; //0101 0101 1011 1011 0110 0110 1101 1101
z=p|q; //1111 1111 1011 1011 1110 1110 1101 1101

cout<<hex;
cout<<"x="<<x<<endl;
cout<<"y="<<y<<endl;
cout<<"z="<<z<<endl;
cout<<dec;
return 0;
}
/* REZULTAT:

x=aa008800
y=55bb66dd
z=ffbbeedd
Press any key to continue . . .*/

Operatorii logici pe biţi pot fi folosiţi, de exemplu, pentru implementarea calculului cu


mulţimi. Dacă U = {x0, x1, x2, ... x31} este o mulţime finită cu 32 de elemente, atunci orice
submulţime A a sa este unic determinată de un unsigned a având biţii poziţionaţi după regula:
pentru fiecare k de la 0 la 31, bitul de ordin k este 1 dacă xk aparţine lui A, altfel este 0.
Dacă a şi b determină submulţimile A şi B, atunci a&b, a^b, a|b şi ~a determină inter-
secţia A⋂B, diferenţa simetrică A∆B, reuniunea A⋃B şi, respectiv, complementara lui A faţă de
mulţimea universală U.
Poziţionarea bitului de ordin k poate fi efectuată cu ajutorul funcţiilor următoare:

unsigned set_bit(unsigned a, int k){ //a_k=1


if(k<0||k>31) return a;
return a|(1u<<k);
}

unsigned clear_bit(unsigned a, int k) //a_k=0


if(k<0||k>31) return a;
return a&~(1u<<k);
}

unsigned toggle_bit(unsigned a, int k){ //a_k=~a_k


if(k<0||k>31) return a;
return a^(1u<<k);
}
Aici utilizăm „masca de biţi” 1u<<k în care numai bitul de ordin k este 1, restul fiind 0.
Masca protejează biţii celuilalt operand de acţiunea operatorului. Deoarece pentru orice bit x
expresia „x sau 0” are valoarea x, se spune că 0 ascunde (maschează) acţiunea lui „sau”. Biţii 0 ai
unei măşti ascund acţiunea lui „sau” şi a lui „sau exclusiv” iar biţii 1 ascund acţiunea lui „şi”.

6
C9. Operatorul „şi” logic && se aplică la doi operanzi logici şi are ca rezultat un bool cu
valoarea true dacă ambii operanzi sunt adevăraţi, altfel rezultatul este false. In cazul operanzilor
numerici (aritmetici sau pointeri) are loc conversia implicită la tipul bool înainte de evaluare.
Exemplu:
if(a==b && b==c) cout<<"a,b si c sunt egale"<<endl;
In cazul operatorului && este precizată ordinea de evaluare de la stânga la dreapta a
operanzilor, mai mult, dacă evaluarea primului operand decide rezultatul final (adică dacă primul
operand e fals), atunci al doilea operand nu mai este evaluat, şi în consecinţă eventualele efecte
secundare ale acestuia nu se mai produc. Se spune că operatorul && este „leneş” sau că este
evaluat în scurt-circuit.
Iată un exemplu de utilizare a acestei proprietăţi:

#include<iostream>
using namespace std;
int main (void){
int tab[10]={1,2,0,3,4,0,5,6,0,7};
int i,contor;
contor=0;
cout<<"In sirul"<<endl;
for(i=0;i<10;i++){
cout<<tab[i]<<" ";
tab[i] && contor++;
}
cout<<"\nsunt "<<contor<<" elemente nenule"<<endl;
return 0;
}
/*REZULTAT:
In sirul
1 2 0 3 4 0 5 6 0 7
sunt 7 elemente nenule
Press any key to continue . . .*/

Utilizarea evaluării în scurt-circuit a şi-ului logic este esenţială în multe situaţii. In pro-
gramul următor, de exemplu, parcurgerea tabloului tab se termină cu o eroare la rulare deorece
la evaluarea espresiei
0!=tab[i]++ && i<dimMax
noi modificăm elementul de indice i înainte de a testa dacă avem voie. Expresia corectă este
i<dimMax && 0!=tab[i]++
deoarece acum incrementarea lui tab[i] nu mai are loc dacă am ieşit din tablou.

include<iostream>
using namespace std;

const int dimMax=8;

void mareste(int tab[dimMax]){


//incrementeaza elementele lui tab pana la primul zero
//for(int i=0; i<dimMax && 0!=tab[i]++; i++)//corect
for(int i=0; 0!=tab[i]++ && i<dimMax; i++)
cout<<tab[i]<<endl;
return;
}

7
int main(){
int a[dimMax]={10,20,30, 40,50,60,70,80};
mareste(a);
return 0;
}

C10. Operatorul „sau” logic || acţionează întru totul similar „şi”-ului logic, având prescrisă şi el
ordinea de evaluare a operanzilor, tot de la sânga la dreapta în scurt-circuit.
Exemplu de utilizare:

#include<iostream>
using namespace std;

//Urmatoarea functie decide daca


//i si j apartin multimii {0,1}

bool suntUndeTrebuie(int i, int j){


return (i == 0 || i == 1) && (j == 0 || j == 1);
}

int main(){
int i = 1, j = 1;
if (suntUndeTrebuie(i, j))
cout << "DA" << endl; // DA
else
cout << "NU" << endl;
return 0;
}
Programul următor numără din câte încercări reuşeşte utilizatorul să introducă de la tasta-
tură un număr dintr-un interval specificat:
#include<iostream>
using namespace std;
int main()
{
double x, xmin=0, xmax=100;
cout<<"Dati un numar strict intre "<<xmin<<" si "<<xmax<<endl;
int i, imax=5;
for(i=0, x=xmin; i<=imax && (x<=xmin || xmax<=x); i++ ){
cout<<"x=";
cin>>x;
}

8
if(i==1) cout<<"Ok, ati reusit de la prima incercare"<<endl;
else if(i<=imax) cout<<"Ati reusit din "<<i<<" incercari"<<endl;
else cout<<"Data viitoare poate veti fi mai atent!"<<endl;
return 0;
}

/*REZULTAT:

Dati un numar strict intre 0 si 100


x=100000
x=10000
x=1000
x=100
x=10
Ati reusit din 5 incercari
Press any key to continue . . .*/

5 D. Operatori ternari.

Niv Nr Simbol Operator / Operaţie Apelare As. RV LV SE EO


XIV 1 ? : operatorul condiţional cond ? Da : Nu --- cto Lv - >>

Limbajul C are un singur operator ternar (operator cu trei operanzi), operatorul condiţi-
onal ? :, numit şi operatorul if aritmetic.
Evaluarea if-ului aritmetic decurge astfel: se începe cu evaluarea primului operand – care
trebuie să fie de tip numeric – şi, înainte de a trece mai departe, se completează toate eventualele
efecte secundare ale acestuia. Dacă rezultatul are valoarea logică „adevărat” atunci se trece la
evaluarea celui de al doilea operand, în caz contrar se trece la evaluarea celui de al treilea. In
orice situaţie este evaluat numai unul dintre operanzi, iar rezultatul obţinut este rezultatul final al
întregii expresii.
Exemplu

double x, a=1.0;
x=(a++<20 ? a : a-100);
cout<<"x="<<x<<endl; //x=2

Operanzii de pe locurile doi şi trei trebuie să aibe acelaşi tip sau să existe conversii impli-
cite la un tip comun. Tipul rezultatului este tipul comun minimal al acestor doi operanzi.

#include<iostream>
using namespace std;
struct Complex{double x, y;};
int main(void){
Complex u={1,2},v={10,20};
cout<<(u.x >= v.x ? u : v ).x<<endl; //10
return 0;
}

9
Rezultatul if-ului aritmetic are l-valoare numai în cazul în care ambii operanzi de pe locu-
rile doi şi trei au l-valoare şi sunt de acelaşi tip.

#include<iostream>
using namespace std;

double f(double x){


return x>0 ? x*x+1.0 : (x==0 ? 0 : x*x-1);
}

int main(void){
double a=1.0, b=2.0;
(a < b ? a : b) = f(-10.0);
cout<<"a="<<a<<" b="<<b<<endl;
return 0;
}
/*
REZULTAT:
a=99 b=2
Press any key to continue . . .*/

5 E. Operatorii de atribuire.

In programare, atribuirea unei valori unei variabile constă în determinarea acelei valori şi
înscrierea ei în locaţia de memorie alocată acelei variabile. Orice atribuire presupune mutarea
unor date între regiştrii microprocesorului şi diverse locaţii de memorie prin utilizarea de instruc-
ţiuni mov în limbaj de asamblare.
Specific limbajului C este efectuarea atribuirilor cu ajutorul operatorilor de atribuire, ca
Niv Nr Simbol Operator / Operaţie Apelare As. RV LV SE EO
XV 1 = atribuire simplă l-val = expresie <<< cto L* SE -
2 += -= atribuire compusă l-val += termen <<< cto L* SE -
*= /= %= aritmetică l-val *= factor
3 <<= >>= atribuire compusă l-val<<=depl <<< cto L* SE -
&= ^= |= pe biţi l-val &= masca
efect secundar al evaluării expresiilor de atribuire, şi nu prin executarea unei instrucţiuni de atr-
ibuire. Acestă atribuire „din mers”, în cadrul evaluării unei expresii, permite declanşarea de atri-
buiri în locuri în care limbajul nu permite utilizarea unei instrucţiuni, de exemplu în timpul testă-
rii condiţiei de continuare a unei instrucţiuni de ciclare. Exemplu:

#include<iostream>
using namespace std;

const int dim=100;

int main(void){
char text[dim]="tabloul text este accesat o singura data";
char ch;
for(int i=0; i<dim && (ch=text[i])!='\0'; i++)
cout<<ch<<" "<<(int)ch<<endl;
return 0;
}

10
Mai mult, deoarece orice expresie de atribuire are o valoare rezultat, este posibil şi un
calcul aritmetic cu atribuiri, util în unele situaţii. De exemplu, în secvenţa de program
int tab[dim]={1,2,3};
int tab_rezerva[dim];
int s=0;
for(int i=0; i<dim; i++)
s+=(tab_rezerva[i]=tab[i]);
cout<<s<<endl;
tabloul tab este sumat şi copiat în acelaşi timp.
Limbajul C dispune de un operator de atribuire simplă, care provoacă transferul obişnuit
al datelor, şi de 10 operatori de atribuire compusă, care declanşează aplicarea „pe loc”, în loca-
ţia desemnată de operandul stâng, a unor operatori binari.
Operandul stâng al oricărui operator de atribuire trebuie să desemneze locaţia unei date
modificabile, altfel spus trebuie să aibe l-valoare. In cazul atribuirii simple operandul stâng este
evaluat numai pentru determinarea l-valorii sale.
Operanzii oricărei atribuiri trebuie să aibe acelaşi tip sau, dacă nu, trebuie să existe o con-
versie implicită a tipului din dreapta la tipul operandului din stânga. Rezultatul atribuirii este în-
todeauna valoarea desemnată de operandul din stânga, după efectuarea modificărilor în memorie.
Limbajul C nu cere ca rezultatul expresiei de atribuire sa aibe şi l-valoare.
Evaluarea unei atribuiri începe cu evaluarea expresiei din dreapta, valoarea obţinută este
apoi transferată sau, după caz, operată cu cea aflată în locaţia desemnată de expresia din stânga.

E1. Operatorul de atribuire simplă = poate avea operanzi de tip numeric (aritmetic sau pointer)
sau obiecte de tip structură sau clasă. Nu se pot atribui tablouri sau funcţii.
Atribuirile pot fi concatenate fără utilizarea parantezelor deoarece ordinea lor de asociere
este de la dreapa la stânga. Expresia
a=b=c=10.0;
este citită de compilator sub forma
a=(b=(c=10.0))
şi are ca rezultat valoarea actualizată a variabilei a. In timpul evaluării au fost actualizate şi vari-
abilele b şi c.
Expresia
a=1+b=1+c=10.0;
nu trece de compilare deoarece este citită ca
a=((1+b)=((1+c)=10.0))
şi conţine expresii fără l-valoare poziţionate în stânga operatorului de atribuire.
Expresia
a=1+(b=1+(c=10.0))
este corectă şi are ca rezultat numărul 12.0. După evaluare variabilele a, b şi c au valorile 12, 11
şi, respectiv, 10.
Expresia
a=(b=1)+(b=2)
este ambiguă deoarece depinde de ordinea şi momentul exact al completării efectelor secundare.
Pe compilatorul Ms Visual Studio 2015 are ca rezultat numărul 4 deoarece adunarea a fost
programată după executarea atribuirilor b=1 şi b=2 în ordinea de la stânga la dreapta:

a = (b = 1) + (b = 2);
002A5E9E mov dword ptr [b],1
002A5EA5 mov dword ptr [b],2
002A5EAC mov eax,dword ptr [b]

11
002A5EAF add eax,dword ptr [b]
002A5EB2 mov dword ptr [a],eax

Dacă evaluarea operanzilor adunării ar fi fost efectuată de la dreapta la stânga, rezultatul


ar fi fost altul.

E2. Operatorii aritmetici de atribuire compusă au forma „operator=” cu „operator” unul dintre
operatorii aritmetici *, %, /, + sau -. Operanzii trebuie să fie numerici, în cazul pointerilor se
aplică regulile de calcul cu pointeri. Evaluarea expresiei de atribuire compusă începe cu determi-
narea r-valorilor celor doi operanzi, acestea sunt apoi operate între ele iar rezultatul este depus în
locaţia desemnată de operandul stâng.
Regula practică este următoarea: punem operandul drept între paranteze rotunde, ştergem
semnul =, evaluăm expresia rămasă şi atribuim rezultatul obţinut operandului stâng. Exemplu:
int a=2,b=10;
a*=b+1;
//calculam a*(b+1)si
//rezultatul il atribuim lui a
cout<<"a="<<a<<endl;//a=22

Posibilitatea calculului „pe loc” introdusă de atribuirea compusă este o facilitate impor-
tantă a limbajului C care trebuie însuşită şi folosită din plin de către programatori. Atribuirea
compusă trebuie gândită ca o acţiune asupra operandului stâng: expresia a+=10 îl măreşte pe a
cu 10 unităţi, a-=10 îl micşorează, iar a*=10 îl amplifică pe a cu 10.
Sumele se calculează prin acumularea valorilor sumate într-o variabilă iniţializată cu 0

double suma=0;
for(int i=0; i<dim; i++)
suma+=tab[i];
cout<<suma<<endl;

iar produsele prin amplificarea unei variabile iniţializate cu 1

double prod=1;
for(int i=0; i<dim; i++)
prod*=tab[i];
cout<<prod<<endl;

La evaluarea expresiei de acumulare a+=t operandul a este evaluat o singură dată, iar la
evaluarea expresiei a=a+t operandul a este evaluat de două ori, prin urmare în cazul prezenţei
efectelor secundare cele două expresii nu sunt echivalente:

#include<iostream>
using namespace std;
const int dim=10;
int main(void){
int i, a[dim];

for(int k=0;k<dim; k++) a[k]=10*k;


i=1;
a[++i]+=1111;

12
for(i=0;i<dim;i++)
cout<<a[i]<<" ";
cout<<endl;

for(int k=0;k<dim; k++) a[k]=10*k;


i=1;
a[++i]=a[++i]+1111;
for(i=0;i<dim;i++)
cout<<a[i]<<" ";
cout<<endl;
return 0;
}
/*REZULTAT:
0 10 1131 30 40 50 60 70 80 90
0 10 20 1141 40 50 60 70 80 90
Press any key to continue . . .*/

E3. Operatorii pe biţi de atribuire compusă <<=, >>=, &=, ^=, |= execută pe loc operaţiile binare
pe biţi. Operanzii trebuie să fie de tip întreg, cadrul natural de lucru fiind tipul unsigned.
De exemplu, considerând masca de biţi
unsigned m3=1u<<3;
expresia a|=m3 seteză bitul de ordin 3 al lui a (îi dă valoarea 1), a&=~m3 îl şterge, iar a^=m3 îl
basculează:

#include<iostream>
using namespace std;
void afiseaza(unsigned a){
//afiseaza bitii lui a
for(int k=31;k>=0;k--)
cout<<!!(a&(1u<<k));
cout<<endl;
}
int main(){
unsigned m3=1u<<3;
unsigned a=0xff77u;
afiseaza(a);
a|=m3;
afiseaza(a);
a&=~m3;
afiseaza(a);
a^=m3;
afiseaza(a);
return 0;
}
/*REZULTAT:
00000000000000001111111101110111
00000000000000001111111101111111
00000000000000001111111101110111
00000000000000001111111101111111
Press any key to continue . . .*/

13
5F. Operatorul de serializare.
Niv Nr Simbol Operator / Operaţie Apelare As. RV LV SE EO
XVI 1 , operatorul virgulă expr , expr >>> cto L* - >>

Limbajul C dispune de un singur operator pentru serializarea evaluărilor unui şir de ex-
presii, şi anume operatorul virgulă, care este un operator binar aplicat unor operanzi de orice tip.
Expresia operand1 , operand2 este evaluată strict în ordinea de la stânga la dreapta, întâi
este evaluată expresia operand1 şi sunt completate efectele sale secundare, apoi este evaluată ex-
presia operand2 şi rezultatul obţinut este rezultatul final al întregii expresii. Primul operand este
evaluat numai pentru generarea de efecte secundare, rezultatul său se pierde. Exemplu:
double x,y;
y=(x=4,x*x);
cout<<y<<endl;//16
Ordinea de asociere a operatorului virgulă este de la stânga la dreapta, astfel încât conca-
tenarea sa se execută firesc în acestă ordine:
cout<<(x=13,y=100,x*y)<<endl;//1300
Serializarea expresiilor este utilă în cazul în care dorim să fie evaluate mai multe expresii
într-un loc din program unde este permisă scrierea numai uneia singure. Un caz tipic este efectu-
area mai multor acţiuni la iniţializare sau la reluarea ciclării într-un for:

#include<iostream>
using namespace std;

int main(){
int tab[10]={0,11,22,33,44,55,66,77,88,99};
int i,j,aux;
for(i=0,j=9; i<j; i++,j--)
aux=tab[i], tab[i]=tab[j], tab[j]=aux;
for(i=0;i<10;i++)
cout<<tab[i]<<" ";
cout<<endl;
return 0;
}
/*REZULTAT:
99 88 77 66 55 44 33 22 11 0
Press any key to continue . . .*/

Virgula operatorului de serializare nu trebuie confundată cu virgula care separă itemii


listelor la declararea mai multor variabile, sau care separă parametrii unei funcţii.

#include<iostream>
using namespace std;
void afiseaza(char a, char b){
cout<<a<<b<<endl;
}
int main(){
afiseaza('X',('Y','Z'));
//XZ
afiseaza('X','Y','Z');
//error C2660: 'afiseaza' : function does not take 3 arguments
return 0;
}

14
6. Claritatea codului
Limbajul C/C++ are, după cum am văzut, numeroase posibilităţi de a scrie expresii de
calcul numeric şi calcul logic. Mai mult, utilizând if-ul aritmetic, operatorul de serializare şi
calculul cu atribuiri, se pot forma expresii complexe a căror evaluare este echivalentă cu par-
curgerea unei întregi secvenţe de instrucţiuni. De exemplu, secvenţa de cod
int a=1,b,x;
cin>>b;
if(a<b) {
cout<<"a<b"<<endl;
x=b;
}
else {
cout<<"a>=b"<<endl;
x=a;
}
cout<<x<<endl;

poate fi scrisă compact sub forma

int a=1,b,x;
cout<<(x=a<(cin>>b,b)?(cout<<"a<b"<<endl,b):(cout<<"a>=b"<<endl,a))<<endl;

O astfel de compactificare nu este cu nimic justificată, prezenţa în acest caz a operaţiilor


de intrare/ieşire făcând inutilă compararea vitezelor de execuţie. In general, dacă nu aduc o sim-
plificare evidentă a implementării algoritmului de calcul, astfel de compactificări nu fac decât să
reducă claritatea codului, îngreunând depanarea şi actualizarea programelor.
In final, un ultim contra-exemplu: programul următor trasează la consolă mulţimea lui
Mandelbrot prin metoda timpului de scăpare (escape-time algorithm), utilizând un singur for în
loc de trei for-uri imbricate (după b, a şi ch):
#include<iostream>
using namespace std;

int main(){
double x=0, y=0, xx=0, yy=0, a=-2, b=-2;
for(char ch=0;
b+= a>2 ? -0.04*(a=-2) : 0 , b<2;
ch++<125 & (xx=x*x)+(yy=y*y)<4 ?
(y=2*x*y+b, x=xx-yy+a):(cout<<ch, ch=30, x=y=0, a+=.05)
);
return 0;
}

15
Aici ch reprezintă “culoarea” numărului complex u=a+ib, cu -2<a<2 şi -2<b<2, dată de
numărul de iteraţii necesar şirului recurent z0=0, zn+1=zn2+u să iasă din discul de rază 2 centrat în
originea planului numerelor complexe. Termenii şirului (zn) sunt calculaţi pe loc în variabila
z=x+iy.
Iată şi varianta “în clar” a programului precedent:
#include<iostream>
using namespace std;
int main(){
double x=0, y=0, xx=0, yy=0, a, b;
char ch=0;
for(b=-2; b<2; b+=0.08)
for(a=-2; a<2; a+=0.05){
//determinam ch-ul lui u=a+ib
for( ch=30, x=y=xx=yy=0; ch<126 && xx+yy<4; ch++){
xx=x*x;
yy=y*y;
//calculam pe componente
y=2*x*y+b; //z=z*z+u
x=xx-yy+a; //cu z=x+iy
}
cout<<ch;
}
return 0;
}

Programul presupune că lungimea liniei consolei de ieşire este de 80 de caractere.

16