Alti operatori c



Alti operatori

Pentru a evita obezitatea finala care ameninta cartea, nu au fost tratate doua grupuri de operatori. Primul grup este format din operatorii pe biti, care permit manipularea bitilor individuali intr-o valoare; acesti operatori au fost mosteniti din C. Al doilea consta din cei doi operatori de dereferentiere a membrilor; acestia sunt contributii C++. Aceasta anexa descrie pe scurt cele doua grupe de operatori.

Operatori pe biti

Operatorii pe biti opereaza asupra bitilor unei valori intregi. De exemplu, operatorul de deplasare la stanga muta bitii la stanga, iar operatorul de negare pe biti schimba fiecare unu cu zero si fiecare zero cu unu. Cu totul, in C++ exista sase asemenea operatori: <<, >>, ~, &, ¦ si ^.



Operatorii de deplasare

Operatorul de deplasare la stanga are urmatoarea sintaxa:

valoare << deplasare

Aici, valoare este intregul care trebuie deplasat, iar deplasare este numarul de biti de deplasat. De exemplu:

13 << 3 42193ypj12ppj4r

inseamna deplasarea tuturor bitilor din valoarea 13 cu trei pozitii la stanga. Pozitiile ramase libere se completeaza cu zerouri, iar bitii care ies in afara sunt eliminati (vezi Figura E.1).

Valoarea 13 este stocata ca un int pe doi octeti:

0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1

0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0

Biti eliminati Deplasarea la stanga Biti eliberati

a lui 13 cu 3 biti: 13 << 3 42193ypj12ppj4r completati cu zerouri

Figura E.1 Operatorul de deplasare la stanga.

Deoarece fiecare pozitie a unui bit reprezinta o valoare de doua ori mai mare decat a bitului din dreapta sa (vezi Anexa A), deplasarea cu o pozitie la stanga este echivalenta cu inmultirea valorii cu 2. Analog, deplasarea cu doua pozitii este echivalenta cu inmultirea cu 22, iar deplasarea cu n pozitii este echivalenta cu inmultirea cu 2n.

Operatorul de deplasare la stanga ofera o abilitate intalnita adesea in limbajele de asamblare. Totusi, un operator de deplasare la stanga al unui limbaj de asamblare modifica direct continutul unui registru, in timp ce operatorul din C++ produce o noua valoare fara a modifica valorile existente. Observati urmatorul exemplu:

int x = 20;

int y = x << 3; pp193y2412pppj

Acest cod nu modifica valoarea lui x. Expresia x << 3 foloseste valoarea lui x pentru a calcula o noua valoare, la fel cum x + 3 are ca rezultat o noua valoare fara a-l afecta pe x.

Daca doriti sa modificati valoarea unei variabile folosind operatorul de deplasare la stanga, trebuie sa folositi si o atribuire. Puteti utiliza atribuirea obisnuita sau operatorul <<=, care combina deplasarea cu atribuirea.

x = x << 4; // atribuire normala

y <<= 2; // deplasare si atribuire

Dupa cum poate va asteptati, operatorul de deplasare la dreapta (>>) deplaseaza bitii la dreapta. Are urmatoarea sintaxa:

valoare >> deplasare

Aici, valoare este valoarea intreaga care este deplasata, iar deplasare este numarul de pozitii de deplasat. De exemplu:

17 >> 2

inseamna deplasarea tuturor bitilor in valoarea 17 cu doua locuri la dreapta. Pentru intregii fara semn, locurile eliberate sunt completate cu zerouri, iar bitii deplasati in afara sunt eliminati. Pentru intregii cu semn, locurile eliberate sunt completate cu zerouri sau cu valoarea originala a bitului cel mai din stanga. Alegerea depinde de implementare (vezi Figura E.2 pentru un exemplu in care completarea se face cu zerouri).

Valoarea 13 este stocata ca un int pe doi octeti:

0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1

Biti eliberati Deplasarea la dreapta Biti eliminati

completati cu zerouri a lui 13 cu 3 biti: 13 >> 3

Figura E.2 Operatorul de deplasare la dreapta.

Deplasarea cu o pozitie la dreapta este echivalenta cu o impartire intreaga la 2. In general, deplasarea cu n pozitii la dreapta este echivalenta cu o impartire intreaga la 2n.

C++ defineste de asemenea un operator de deplasare la dreapta si atribuire, pentru cazul in care doriti sa inlocuiti valoarea unei variabile cu valoarea deplasata:

int q = 43;

q >>= 2; // inlocuieste 43 cu 43 >> 2, adica 10

In unele sisteme de calcul, utilizarea operatorilor de deplasare la stanga si la dreapta este mai rapida decat inmultirea sau impartirea la 2 cu operatorii obisnuiti, dar, o data cu imbunatatirea compilatoarelor, aceste diferente sunt tot mai nesemnificative.

Operatorii logici pe biti

Operatorii logici pe biti sunt analogi cu operatorii logici normali, exceptand faptul ca se aplica unei valori bit cu bit si nu ca unui tot unitar. De exemplu, sa luam operatorul de negare normal (!) si cel de negare pe biti (~). Operatorul ! converteste o valoare true (sau diferita de zero) in false iar o valoare false in una true. Operatorul ~ converteste fiecare bit in parte in opusul sau (1 in 0 si 0 in 1). Sa luam ca exemplu valoarea unsigned char 3:

unsigned char x = 3;

Expresia !x are valoarea 0. Pentru a vedea valoarea lui ~x, il scrieti in notatie binara: 00000011. Apoi convertiti fiecare 0 in 1 si fiecare 1 in 0. Rezulta valoarea 11111100, sau in baza 10, valoarea 252 (vezi Figura E.3 pentru un exemplu pe 16 biti).

Valoarea 13 este stocata ca un int pe doi octeti:

0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1

Valoarea ~13 – fiecare 1 devine 0, iar fiecare 0 devine 1

1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 0

Figura E.3 Operatorul de negare pe biti.

Operatorul SAU pe biti (¦) combina doua valori intregi pentru a obtine o valoare intreaga noua. Fiecare bit din noua valoare este 1 daca unul sau altul, sau amandoi, bitii corespunzatori din valorile initiale este 1. Daca amandoi bitii corespunzatori sunt 0, atunci bitul rezultat este setat tot la 0 (vezi Figura E.4).

a 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 1

b 1 0 1 0 0 1 0 0 0 0 0 0 0 1 1 0

a | b 1 0 1 0 0 1 0 0 1 0 0 0 1 1 1 1

1 deoarece 0 deoarece 1 deoarece 1 deoarece

bitul corespunzator bitii corespunzatori bitul corespunzator bitii corespunzatori

din b este 1 din a si din b sunt 0 din a este 1 din a si din b sunt 1

Figura E.4 Operatorul SAU pe biti.

Tabelul E.1 rezuma modul in care operatorul ¦ combina bitii.

Tabelul E.1 Valoarea b1 ¦ b2.

Valoarea bitilor b1 = 0 b1 = 1

b2 = 0 0 1

b2 = 1 1 1

Operatorul SAU exclusiv pe biti (^) combina doua valori intregi pentru a obtine o valoare intreaga noua. Fiecare bit din noua valoare este 1 daca unul sau altul, dar nu amandoi, bitii corespunzatori din valorile initiale este 1. Daca amandoi bitii corespunzatori sunt 0, sau amandoi sunt 1 atunci bitul rezultat este setat la 0 (vezi Figura E.5).

a 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 1

b 1 0 1 0 0 1 0 0 0 0 0 0 0 1 1 0

a ^ b 1 0 1 0 0 1 0 0 1 0 0 0 1 0 1 1

1 deoarece 0 deoarece 1 deoarece 0 deoarece

bitul corespunzator bitii corespunzatori bitul corespunzator bitii corespunzatori

din b este 1 din a si din b sunt 0 din a este 1 din a si din b sunt 1

Figura E.5 Operatorul SAU exclusiv pe biti.

Tabelul E.2 rezuma modul in care operatorul ^ combina bitii.

Tabelul E.2 Valoarea b1 ^ b2.

Valoarea bitilor b1 = 0 b1 = 1

b2 = 0 0 1

b2 = 1 1 0

Operatorul SI pe biti (&) combina doua valori intregi pentru a obtine o valoare intreaga noua. Fiecare bit din noua valoare este 1 numai daca amandoi bitii corespunzatori din valorile initiale sunt 1. Daca oricare sau amandoi bitii corespunzatori sunt 0, atunci bitul rezultat este setat tot la 0 (vezi Figura E.6).

a 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 1

b 1 0 1 0 0 1 0 0 0 0 0 0 0 1 1 0

a & b 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0

0 deoarece numai unul din 0 deoarece 1 deoarece

bitii corespunzatori bitii corespunzatori bitii corespunzatori

este 1 din a si din b sunt 0 din a si din b sunt 1

Figura E.6 Operatorul SI pe biti.

Tabelul E.3 rezuma modul in care operatorul & combina bitii.

Tabelul E.3 Valoarea b1 & b2.

Valoarea bitilor b1 = 0 b1 = 1

b2 = 0 0 0

b2 = 1 0 1

Cateva tehnici de baza pentru manipularea bitilor

Adesea, controlul hardware presupune activarea si dezactivarea unor biti, precum si verificarea starii acestora. Operatorii pe biti ofera posibilitatea realizarii unor astfel de actiuni. Vom trece rapid in revista aceste metode.

In exemplele urmatoare multibiti reprezinta o valoare generala, iar bit reprezinta valoarea corespunzatoare unui anumit bit. Bitii sunt numerotati de la dreapta la stanga, incepand cu bitul 0, deci valoarea corespunzatoare bitului din pozitia n este 2n. De exemplu, un intreg in care singurul bit 1 este bitul numarul 3, are valoarea 23 sau 8. In general, fiecare bit individual corespunde unei puteri a lui 2, asa cum am descris in Anexa A. Deci vom folosi denumirea de bit pentru a reprezenta o putere a lui 2; adica un anumit bit este setat la valoarea 1 iar ceilalti la 0.

Setarea unui bit la valoarea 1

Urmatoarele operatii realizeaza setarea bitului din multibiti care este corespunzator valorii bit, la valoarea 1:

multibiti = multibiti ¦ bit;

multibiti ¦= bit;

Fiecare operatie seteaza valoarea bitului corespunzator la valoarea 1 indiferent de valoarea sa initiala. Acest lucru se intampla deoarece 1 ¦ 0 este 1, la fel cum 1 ¦ 1 este tot 1. Ceilalti biti din multibiti raman nemodificati. Acest lucru se intampla deoarece 0 ¦ 0 este 0, iar 0 ¦ 1 este 1.

Comutarea unui bit

Urmatoarele operatii realizeaza comutarea bitului din multibiti care este corespunzator valorii bit. Adica, il pune pe 1 daca era 0 si il pune pe 0 daca era 1:

multibiti = multibiti ^ bit;

multibiti ^= bit;

1 ^ 0 este 1, activand un bit inactiv, iar 1 ^ 1 este 0, dezactivand un bit activ. Ceilalti biti din multibiti raman nemodificati. Acest lucru se intampla deoarece 0 ^ 0 este 0, iar 0 ^ 1 este 1.

Setarea unui bit la valoarea 0

Urmatoarea operatie seteaza bitul din multibiti care corespunde valorii bit la valoarea 0:

multibiti = multibiti & ~bit;

Aceasta instructiune pune bitul respectiv pe valoarea 0, indiferent de valoarea sa initiala. Mai intai, operatia ~bit are ca rezultat un intreg in care toti bitii sunt 1 in afara celui care era initial 1; acesta devine in schimb 0. 0 & 0, ca si 0 & 1 are rezultatul 0. Deci, bitul respectiv este dezactivat. Ceilalti biti din multibiti raman nemodificati. Acest lucru se intampla deoarece 1 & orice bit are ca rezultat valoarea initiala a bitului respectiv.

Iata o modalitate mai rapida pentru a realiza acelasi lucru:

multibiti &= ~bit;

Testarea valorii unui bit

Sa presupunem ca doriti sa aflati daca bitul din multibiti care corespunde lui bit este 1. Urmatorul test nu este corect:

if (multibiti == bit) // nu e buna

Chiar daca bitul corespunzator este 1, mai pot exista si alti biti cu valoarea 1. Egalitatea de mai sus este adevarata doar daca numai bitul corespunzator este 1. Remediul este efectuarea mai intai a operatiei multibiti & bit. Astfel, rezulta o valoare care are bitii 0 pe toate celelalte pozitii, deoarece 0 & orice valoare este 0. Ramane nemodificat doar bitul care corespunde valorii bit, deoarece 1 & orice valoare nu modifica acea valoare. Prin urmare testul corect este:

if (multibiti & bit == bit) // testarea unui bit

Operatori pentru dereferentierea membrilor

Inainte de a trata operatorii pentru dereferentierea membrilor, trebuie sa discutam putin bazele acestor operatori. C++ va permite sa definiti pointeri spre membrii unei clase, dar acest proces nu e deloc simplu. Pentru a vedea ce presupune, sa studiem un exemplu de clasa care pune unele probleme:

class exemplu

{

private:

int picioare;

int toli;

public:

exemplu();

exemplu(int p);

~exemplu();

void arata_toli(); // afiseaza membrul toli

exemplu operator+(exemplu &ex);

}

Sa presupunem ca doriti sa definiti un pointer la membrul toli al clasei. Nu veti avea succes daca incercati astfel:

int * ptoli = &toli; // nu e cod C++ valid

Veti esua deoarece toli nu este de tip int. Deoarece toli este declarata in clasa, are o vizibilitate de clasa. Din acest motiv tipul membrului toli trebuie sa specifice si clasa de care apartine acesta. Pentru ca declaratia sa fie valida, trebuie sa utilizati operatorul de vizibilitate, pentru a identifica clasa pointerului si a membrului:

int exemplu::* ptoli = &exemplu::toli // cod C++ valid

In aceasta declaratie expresia int exemplu::* inseamna tipul „pointer la un int membru al clasei exemplu”. Expresia &exemplu::toli inseamna „adresa membrului toli al clasei exemplu”.

Puteti folosi acest tip de declaratie in functiile membre si in cele prietene. Pointerul ptoli se comporta ca un membru al clasei, adica trebuie apelat cu un obiect al clasei. Aici intervin operatorii pentru dereferentierea membrilor. De exemplu, sa presupunem ca ex este un obiect exemplu declarat intr-o functie membra. Pentru a accesa membrul toli al obiectului ex, puteti folosi notatia standard ex.toli. Dar puteti folosi de asemenea si operatorul .* cu pointerul ptoli:

cout << ex.toli; // afiseaza membrul toli

cout << ex.*ptoli; // idem

Deci, operatorul . acceseaza un membru folosind numele acestuia, iar .* acceseaza un membru folosind un pointer spre acesta.

In mod analog, daca pex este un pointer la un obiect exemplu, atunci folositi operatorul -> pentru a accesa membrul toli folosindu-i numele, sau utilizati operatorul de dereferentiere ->* pentru a accesa membrul toli prin intermediul unui pointer la un membru:

pex = &ex; // pex pointer la un obiect exemplu

cout << pex->toli; // afiseaza membrul toli

cout << pex->*ptoli; // idem

Observati ca pex este un pointer la un obiect, in timp ce ptoli este un pointer la un membru al clasei.

Pentru a vedea modul in care functioneaza acesti operatori in practica, ii vom folosi intr-o implementare cam intortocheata a functiei operator+(). Functia aduna doua obiecte. Unul este argumentul functiei. Deoarece este un obiect, nu un pointer, puteti folosi operatorul .* pentru accesul la membrul toli. Celalalt obiect este obiectul apelant, care, va amintiti, este reprezentat de pointerul this. Prin urmare, veti folosi pentru acesta operatorul ->, dupa cum puteti vedea in urmatorul segment de cod:

exemplu exemplu::operator+(exemplu &ex)

{

exemplu sum;

int exemplu::*ptoli = &exemplu::toli;

// indica un membru toli al clasei exemplu

sum.toli = ex.*ptoli + this->*ptoli;

sum.picioare = 12 * sum.toli;

return sum;

}

Aici ex.*ptoli reprezinta membrul toli al lui ex, iar this->*ptoli reprezinta membrul toli al obiectului pe care il indica pointerul this. Observati ca ptoli este folosit ca un nume de membru.

Listingul E.1 va prezinta restul definitiilor de metode si o functie main() care utilizeaza clasa.

Listingul E.1 memb_ptoli.cpp.

// memb_ptoli.cpp - - dereferentierea pointerilor spre membrii

// clasei

#include <iostream>

using namespace std;

class exemplu

{

private:

int picioare;

int toli;

public:

exemplu();

exemplu(int p);

~exemplu();

void arata_toli();

exemplu operator+(exemplu &ex);

};

exemplu::exemplu()

{

picioare = 0;

toli = 0;

}

exemplu::exemplu(int p)

{

picioare = p;

toli = 12 * picioare;

}

exemplu::~exemplu()

{

}

void exemplu::arata_toli()

{

cout << toli << " toli\n";

}

exemplu exemplu::operator+(exemplu &ex)

{

exemplu sum;

int exemplu::*ptoli = &exemplu::toli;

// indica un membru toli al clasei exemplu

sum.toli = ex.*ptoli + this->*ptoli;

sum.picioare = 12 * sum.toli;

return sum;

}

int main()

{

exemplu masina(15);

exemplu duba(20);

exemplu garaj;

garaj = masina + duba;

masina.arata_toli();

duba.arata_toli();

garaj.arata_toli();

return 0;

}

Iata iesirea programului:

180 toli

240 toli

420 toli