Documente online.
Zona de administrare documente. Fisierele tale
Am uitat parola x Creaza cont nou
 HomeExploreaza
upload
Upload




Sumar de operatori

c


Sumar de operatori



Operatorii C++ sint descrisi sistematic si complet in &r7. Aici, este un sumar al lor si niste exemple. Fiecare operator este urmat de unul sau mai multe nume utilizate in comun pentru el si de un exemplu de utilizare a lui. In aceste exemple class_name este numele unei clase, member este un nume al unui membru, un object este o expresie care produce un obiect, un pointer este o expresie care produce un pointer, o expr este o expresie, iar o lvalue este o expresie ce noteaza un obiect neconstant. Un type poate fi un nume de tip general complet (cu *, (), etc.) numai cind el apare in paranteze. Altfel exista restrictii.

Operatorii unari si operatorii de atribuire se asociaza de la dreapta; toti ceilalti se asociaza de la stinga.

Adica

a = b = c inseamna a = (b = c),

a + b + c inseamna (a + b) + c,

iar

*p++ inseamna *(p++), nu (*p)++.

  SUMAR DE OPERATORI

::   domeniu de existenta   class_name::member

::   global   ::name

-> selectare de membru pointer->member

[]   indexare   pointer[expr]

()   apel de functie   expr(expr_list)

()   constructie de valoare   type(expr_list)

sizeof dimensiunea unui obiect   sizeof expr

sizeof dimensiunea unui tip sizeof(type)

++ increment postfixat   lvalue++

++ increment prefixat   ++lvalue

-- decrement postfixat   lvalue--

-- decrement prefixat   --lvalue

~ complement   ~expr

! negare   !expr

- minus unar   -expr

+ plus unar   +expr

& adresa   &lvalue

* indirectare   *expr

new creaza(aloca)   new type

delete distruge(dealoca)   delete pointer

delete[] distruge un vector delete[expr]pointer

(type) conversie de tip (type)expr

* inmultire   expr * expr

/ impartire   expr / expr

% modulo(rest)   expr % expr

+ adunare(plus)   expr + expr

-   scadere(minus)   expr - expr

<< deplasare stinga expr << expr

>> deplasare dreapta expr >> expr

< mai mic   expr < expr

<= mai mic sau egal expr <= expr

> mai mare   expr > expr

>= mai mare sau egal expr >= expr

= = egal   expr == expr

! = diferit expr != expr

& si pe biti   expr & expr

^ sau exclusiv pe biti   expr ^ expr

| sau pe biti   expr | expr

&& si logic expr && expr

||   sau logic   expr || expr

? : if aritmetic   expr ? expr : expr

= asignare simpla   lvalue = expr

*= inmultire si asignare   lvalue *= expr

/= impartire si asignare   lvalue /= expr

%= modulo si asignare   lvalue %= expr

+= adunare si asignare   lvalue += expr

-= scadere si asignare   lvalue -= expr

<<= deplasare stinga si asignare lvalue <<= expr

>>= deplasare dreapta si asignare lvalue >>= expr

&= si pe biti si asignare lvalue &= expr

|= sau pe biti si asignare lvalue |= expr

^= sau exclusiv pe biti si asignare lvalue ^= expr

, virgula(succesiune)   expr, expr

Fiecare dreptunghi contine operatori cu aceeasi prioritate. Un operator are o prioritate mai mare decit operatorii aflati in dreptunghiuri inferioare. De exemplu:

a + b * c

inseamna

a + (b * c)

deoarece * are prioritate mai mare decit +, iar a + b - c inseam­na (a + b) - c deoarece + si - au aceeasi prioritate, dar opera­torii + si - sint asociati de la stinga spre dreapta.

3.2.1. Paranteze rotunde

Parantezele rotunde sint suprasolicitate in sintaxa lui C++. Ele au un numar mare de utilizari: includ argumentele in apelu­rile de functii, includ tipul intr-o conversie de tip, includ nume de tipuri pentru a nota functii si, de asemenea, pentru a rezolva conflictul prioritatilor intr-o expresie. Din fericire, ultimul caz nu este necesar foarte frecvent deoarece regulile cu nivelele de prioritate si de asociativitate sint astfel definite ca expresiile sa "functioneze" asa cum ne asteptam (adica sa re flecte utilizarile cele mai frecvente). De exemplu:

if(i <= 0 || max < i)

are intelesul obisnuit. Cu toate acestea, parantezele ar trebui utilizate ori de cite ori un programator este in dubiu despre acele reguli:

if((i <= 0)||(max < i))

Utilizarea parantezelor este mai frecventa cind subexpre­siile sint mai complicate; dar subexpresiile complicate sint o sursa de erori, asa ca daca simtim nevoia de a folosii paranteze am putea sa descompunem expresiile utilizind variabile auxiliare. Exista, de asemenea, cazuri cind prioritatea operatorilor nu conduce la o interpretare "evidenta". De exemplu:

if(i & mask == 0)

nu aplica o masca la i si apoi testeaza daca rezultatul este zero. Intrucit == are o prioritate mai mare decit &, expresia este interpretata ca: i & (mask == 0). In acest caz parantezele sint importante:

if((i & mask) == 0)

De asemenea, poate fi util sa observam ca secventa de mai jos nu functioneaza in modul in care s-ar astepta un utilizator naiv:

if(0 <= a <= 99)

este legal, dar se interpreteaza ca:

(0 <= a) <= 99

si rezultatul primei comparatii este 0 sau 1 si nu a (daca a diferit de 1). Pentru a testa daca a este in domeniul 0..99 se poate folosi:

if(0 <= a && a <= 99)

3.2.2. Ordinea de evaluare

Ordinea de evaluare a subexpresiilor intr-o expresie este nedefinita. De exemplu:

int i = 1;

v[i] = i++

poate fi evaluata sau ca v[1] = 1, sau ca v[2] = 1. Un cod mai bun se poate genera in absenta restrictiilor asupra ordinii de evaluare a expresiilor. Ar fi mai bine daca compilatorul ne-ar avertiza despre astfel de ambiguitati;majoritatea compilatoarelor nu fac acest lucru.

Operatorii && si || garanteaza faptul ca operandul lor sting se evalueaza inaintea celui drept. De exemplu, b = (a = 2, a + 1) atribuie lui b valoarea 3. Exemple de utilizare a lui && si || se dau in paragraful &3.3.1. Sa observam ca operatorul virgula este logic diferit de virgula folosita pentru a separa argumente intr-un apel de functie. Sa consideram:

f1(v[i], i++); //doua argumente

f2((v[i], i++)); //un argument

Apelul lui f1 are doua argumente, v[i] si i++, iar ordinea de evaluare a expresiilor argument este nedefinita. Ordinea de evaluare a expresiilor argument este neportabila si nu este precizata. Apelul lui f2 are un singur argument si anume expresia (v[i], i++).

Parantezele nu pot fi utilizate pentru a forta ordinea de evaluare; a*(b/c) poate fi evaluata ca (a*b)/c deoarece * si / au aceeasi precedenta. Cind ordinea de evaluare este importanta, se pot introduce variabile temporare. De exemplu:

(t = b / c, a * t)

3.2.3. Incrementare si Decrementare

Operatorul ++ se utilizeaza pentru a exprima o incrementare directa in schimbul exprimarii ei folosind o combinatie intre adunare si atribuire. Prin definitie, ++lvalue inseamna: lvalue += 1 care din nou inseamna lvalue = lvalue + 1 cu conditia ca lvalue sa nu aiba efecte secundare. Expresia care noteaza obiectul de incrementat se evalueaza o singura data. Decrementa­rea este exprimata similar prin operatorul --. Operatorii ++ si -- pot fi utilizati ambii atit prefix cit si postfix. Valoarea lui ++x este noua valoare a lui x (adica cea incrementata). De exemplu y = ++x este echivalent cu y = (x += 1). Valoarea lui x++ este valoarea veche a lui x. De exemplu y=x++ este echivalent cu y = (t=x, x+=1, t), unde t este o variabila de acelasi tip cu x.

Operatorii de incrementare sint utili mai ales pentru a incrementa si decrementa variabile in cicluri. De exemplu se poate copia un sir terminat cu zero astfel:

inline void cpy(char* p, const char* q)

Sa ne amintim ca incrementind si decrementind pointeri, ca si adunarea sau scaderea dintr-un pointer, opereaza in termenii elementelor vectorului spre care pointeaza pointerul in cauza; p++ face ca p sa pointeze spre elementul urmator. Pentru un pointer de tip T*, are loc prin definitie:

long(p + 1) == long(p) + sizeof(T);

3.2.4. Operatori logici pe biti

Operatorii logici pe biti &, |, ^, ~, >> si << se aplica la intregi; adica obiecte de tip char, short, int, long si corespun­zatoarele lor fara semn (unsigned), iar rezultatele lor sint de asemenea intregi.

O utilizare tipica a operatorilor logici pe biti este de a implementa seturi mici (vectori de biti). In acest caz fiecare bit al unui intreg fara semn reprezinta numai un membru al setu­lui, iar numarul de biti limiteaza numarul de membri. Operatorul binar & este interpretat ca intersectie, | ca reuniune si ^ ca diferenta. O enumerare poate fi utilizata pentru a numi membri unui astfel de set. Iata un mic exemplu imprumutat din implemen­tarea (nu interfata utilizator) lui <stream.h>:

enum state_value;

Definirea lui _good nu este necesara. Eu numai am dorit sa existe un nume adecvat pentru starea in care nu sint probleme. Starea unui sir poate fi resetata astfel:

cout.state = _good;

Se poate testa daca un sir a fost deformat sau o operatie a esuat, ca mai jos:

if(cout.state & (_bad | _fail)) //nu este bine

Parantezele sint necesare deoarece & are o precedenta mai mare decit |. O functie care intilneste sfirsitul intrarii poate sa indice acest lucru astfel: cin.state |= _eof. Se utilizeaza operatorul |= deoarece sirul ar putea fi deformat deja (adica state == _bad) asa ca: cin.state = _eof ar fi sters conditia respectiva. Se poate gasi modul in care difera doua stari astfel:

state_value diff = cin.state ^ cout.state;

Pentru tipul stream_state o astfel de diferenta nu este foarte folositoare, dar pentru alte tipuri similare ea este mai utila. De exemplu, sa consideram compararea unui vector de biti care reprezinta setul de intreruperi de prelucrat cu un altul care reprezinta setul de intreruperi ce asteapta sa fie prelu­crat.

Sa observam ca utilizind cimpurile (&2.5.1) se obtine o prescurtare convenabila pentru a deplasa masca si a extrage cimpuri de biti dintr-un cuvint. Aceasta se poate face, evident, utilizind operatorii logici pe biti. De exemplu, se pot extrage 16 biti din mijlocul unui int de 32 de biti astfel:

unsigned short middle(int a)

Sa nu se faca confuzie intre operatorii logici pe biti cu cei logici &&, || si !. Acestia din urma returneaza sau 0 sau 1 si ei sint in primul rind utili pentru a scrie teste in if, while sau for (&3.3.1). De exemplu !0 (negatia lui 0) are valoarea 1, in timp ce ~0 (complementul lui zero) reprezinta valoarea -1 (toti biti sint unu).

3.2.5. Conversia tipului

Uneori este necesar sa se converteasca o valoare de un tip spre o valoare de un alt tip. O conversie de tip explicit produce o valoare de un tip dat pentru o valoare a unui alt tip. De exemplu:

float r = float(1);

converteste valoarea 1 spre valoarea flotanta 1.0 inainte de a face atribuirea. Rezultatul unei conversii de un tip nu este o lvalue deci nu i se poate face o asignare (numai daca tipul este un tip referinta).

Exista doua notatii pentru conversia explicita a tipului: notatia traditionala din C (de exemplu (double)) si notatia functionala (double(a)). Notatia functionala nu poate fi folosita pentru tipuri care nu au un nume simplu. De exemplu, pentru a converti o valoare spre un pointer se poate folosi notatia din C:

char* p = (char*)0777;

sau sa se defineasca un nume de tip nou:

typedef char* pchar;

char* p = pchar(077

Dupa parerea mea, notatia functionala este preferabila pentru exemple netriviale. Sa consideram aceste doua exemple echivalente:

pname n2 = pbase(n1->tp)->b_name;

pname n3 = ((pbase)n2->tp)->b_name

Intrucit operatorul -> are prioritate mai mare decit (tip), ultima expresie se interpreteaza astfel:

((pbase)(n2->tp))->b_name

Utilizind explicit conversia de tip asupra tipurilor pointer este posibil sa avem pretentia ca un obiect sa aiba orice tip. De exemplu:

any_type* p = (any_type*)&some_object;

va permite ca some_object sa fie tratat ca any_type prin p.

Cind o conversie de tip nu este necesara ea trebuie elimina­ta. Programele care utilizeaza multe conversii explicite sint mai greu de inteles decit programele care nu le utilizeaza. Totusi, astfel de programe sint mai usor de inteles decit programele care pur si simplu nu utilizeaza tipuri pentru a reprezenta concepte de nivel mai inalt (de exemplu, un program care opereaza cu un registru de periferic folosind deplasari si mascari de intregi in loc de a defini o structura corespunzatoare si o operatie cu ea; vezi &2.5.2). Mai mult decit atit, corectitudinea unei conversii explicite de tip depinde adesea in mod esential de intelegerea de catre programator a modului in care diferite tipuri de obiecte sint tratate in limbaj si foarte adesea de detaliile de implemen­tare. De exemplu:

int i = 1; char* pc = "asdf";

int* pi = &i;

i = (int)pc;

pc = (char*)i; // nu se recomanda: pc s-ar putea sa-si

  // schimbe valoarea. Pe anumite masini

  // sizeof(int) < sizeof(char*)

pi = (int*)pc;

pc = (char*)pi; // nu se recomanda: pc s-ar putea sa-si

  // schimbe valoarea. Pe anumite masini

  // char* se reprezinta diferit de int*

Pe multe masini nu se va intimpla nimic rau, dar pe altele rezultatul va fi dezastruos. In cel mai bun caz, un astfel de cod nu este portabil. De obicei este gresit sa presupunem ca poin­terii la diferite structuri au aceeasi reprezentare. Mai mult decit atit, orice pointer poate fi asignat la un void* (fara un tip explicit de conversie) si un void* poate fi convertit ex­plicit la un pointer de orice tip.

In C++, conversia explicita de tip nu este necesara in multe cazuri in care in C este necesara. In multe programe conversia explicita de tip poate fi complet eliminata, iar in multe alte programe utilizarea ei poate fi localizata in citeva rutine.

3.2.6. Memoria libera

Un obiect denumit este sau static sau automatic (vezi &2.1.3). Un obiect static se aloca cind incepe programul si exista pe durata executiei programului! Un obiect automatic se aloca de fiecare data cind se intra in blocul lui si este elimi­nat numai cind se iese din bloc. Adesea este util sa se creeze un obiect nou care exista numai cit timp este nevoie de el. In particular, adesea este util sa se creeze un obiect care poate fi utilizat dupa ce se revine dintr-o functie in care el a fost creat. Operatorul new creaza astfel de obiecte, iar operatorul delete poate fi folosit pentru a le distruge mai tirziu. Obiec­tele alocate prin new se spune ca sint in memoria libera. Astfel de obiecte sint de exemplu nodurile unui arbore sau a unei liste inlantuite care sint parte a unei sructuri de date mai mari a carei dimensiune nu poate fi cunoscuta la compilare.Sa consideram modul in care s-ar putea scrie un compilator in stilul folosit la calculatorul de birou. Functiile de analiza sintactica ar putea construi o reprezentare sub forma de arbore a expresiilor, care sa fie utilizata de generatorul de cod. De exemplu:

struct enode;

  enode* expr()

Un generator de cod ar putea utiliza arborele rezultat astfel:

void generate(enode* n)

Un obiect creat prin new exista pina cind este distrus explicit prin delete dupa care spatiul ocupat de el poate fi reutilizat prin new. Nu exista "colectarea rezidurilor". Operato­rul delete se poate aplica numai la un pointer returnat de new sau la zero. Aplicarea lui delete la zero nu are nici un efect. Se pot, de asemenea, crea vectori de obiecte prin intermediul lui new. De exemplu:

char* save_string(char* p)

Sa observam ca pentru a dealoca spatiul alocat prin new, delete trebuie sa fie capabil sa determine dimensiunea obiectului alocat. De exemplu:

int main(int argc, char* argv[])

Aceasta implica faptul ca un obiect alocat utilizind imple­mentarea standard prin new va ocupa putin mai mult spatiu decit un obiect static (de obicei un cuvint in plus).

Este de asemenea, posibil sa se specifice dimensiunea unui vector explicit intr-o operatie de stergere. De exemplu:

int main(int argc, char* argv[])

Dimensiunea vectorului furnizata de utilizator se ignora exceptind unele tipuri definite de utilizator (&5.5.5).

Operatorii de memorie libera se implementeaza prin functiile (&r7.2.3):

void* operator new(long);

void operator delete(void*

Implementarea standard a lui new nu initializeaza obiectul returnat. Ce se intimpla daca new nu gaseste memorie de alocat. Intrucit chiar memoria virtuala este finita, uneori se poate intimpla acest lucru; o cerere de forma:

char* p = new char[100000000];

de obicei va cauza probleme. Cind new esueaza, ea apeleaza functia spre care pointeaza pointerul _new_handler (pointerii spre functii vor fi discutati in &4.6.9). Noi putem seta acel pointer direct sau sa utilizam functia set_new_handler(). De exemplu:

#include <stream.h>

void out_of_store()

typedef void (*PF)(); //pointer spre tipul functiei

extern PF set_new_handler(PF);

main()

de obicei niciodata nu va scrie done dar in schimb va produce:

operator new failed: out of store

Un _new_handler ar putea face ceva mai destept decit pur si simplu sa termine programul. Daca noi stim cum lucreaza new si delete, de exemplu, deoarece noi furnizam operatorii nostri proprii new() si delete(), handlerul ar putea astepta sa gaseasca memorie pentru new. Cu alte cuvinte, un utilizator ar putea furniza un colector de reziduri, redind in utilizare zonele sterse. Aceasta evident nu este o sarcina pentru un incepator.

Din motive istorice, new pur si simplu returneaza pointerul 0 daca el nu gaseste destula memorie si nu a fost specificat un _new_handler. De exemplu:

#include <stream.h>

main()

va produce

done, p= 0

Noi am avertizat! Sa observam ca furnizind _new_handler se verifica depasirea memoriei pentru orice utilizare a lui new in program (exceptind cazul cind utilizatorul furnizeaza rutine separate pentru tratarea alocarii obiectelor de tipuri specifice definite de utilizator; vezi &5.5.6).



Document Info


Accesari: 1081
Apreciat: hand-up

Comenteaza documentul:

Nu esti inregistrat
Trebuie sa fii utilizator inregistrat pentru a putea comenta


Creaza cont nou

A fost util?

Daca documentul a fost util si crezi ca merita
sa adaugi un link catre el la tine in site


in pagina web a site-ului tau.




eCoduri.com - coduri postale, contabile, CAEN sau bancare

Politica de confidentialitate | Termenii si conditii de utilizare




Copyright © Contact (SCRIGROUP Int. 2024 )