|
Politica de confidentialitate |
|
• domnisoara hus • legume • istoria unui galban • metanol • recapitulare • profitul • caract • comentariu liric • radiolocatia • praslea cel voinic si merele da aur | |
EXPRESII SI INSTRUCTIUNI C++ | ||||||
|
||||||
C++ are un set mic, dar flexibil, de tipuri de instructiuni pentru controlul
programului si un set bogat de operatori pentru manipularea datelor. Un singur
exemplu complex introduce cele mai frecvente facilitati utilizate. Dupa aceea
sint rezumate expresiile si conversiile explicite de tip si este prezentata
in detaliu utilizarea memoriei libere. Apoi sint rezumate instructiunile, iar
in final se discuta stilul de decalare si comentare a textului.
3.1 Un calculator de birou
Instructiunile si expresiile se introduc prin prezentarea programului calculatorului de birou care furnizeaza cele patru operatii aritmetice standard ca operatori infix asupra numerelor flotante. Utilizatorul poate, de asemenea, defini variabile. De exemplu, dindu-se intrarea: r = 2.5 area = pi * r * r (pi este predefinit), programul calculator va scrie: k2r10re unde 2.5 este rezultatul primei linii de intrare, iar 19.635 este rezultatul celei de a doua. Calculatorul consta din patru parti principale: un analizor, o functie de intrare, o tabela de simboluri si un driver. In realitate este un compilator miniatura cu un analizor care face analiza sintactica, functia de intrare realizind intrarea si analiza lexicala, tabela de simboluri pastrind informatia permanenta, iar driverul facind initializarea, iesirea si tratind erorile. Exista multe facilitati care pot fi adaugate la acest calculator pentru a-l face mai util, dar codul este destul de lung intrucit are 200 de linii si cele mai multe facilitati noi ar adauga cod fara a furniza aspecte noi in utilizarea lui C++. Iata o gramatica pentru limbajul acceptat de calculator: program: Cu alte cuvinte, un program este un sir de linii. Fiecare linie consta din
una sau mai multe expresii separate prin punct- virgula. Unitatile de baza ale
unei expresii sint numere, nume si operatorii *, /, +, - (atit unar cit si binar)
si =. Numele nu trebuie sa fie declarate inainte sa fie utilizate. Stilul analizei
sintactice utilizate este de obicei numit analiza descendenta recursiva. Este
o tehnica top-down directa. Intr-un limbaj cum este C++ in care apelurile de
functii sint relativ ieftine, aceasta este o tehnica eficienta. Pentru fiecare
productie din gramatica exista o functie care apeleaza alte functii. Simbolurile
terminale (de exemplu END, NUMBER, + si -) se recunosc prin analizorul lexical,
get_token(), iar simbolurile neterminale sint recunoscute prin functiile analizorului
sintactic expr(), term() si prim(). De indata ce ambii operanzi ai unei (sub)expresii
sint cunoscuti, ei se evalueaza. Intr-un compilator real se genereaza codul
in acest punct. Aceasta functie in realitate nu face ea insasi foarte mult. Intr-o maniera
tipica pentru functii de nivel mai inalt dintr-un program mare, ea apeleaza
alte functii pentru a face "greul". Sa observam ca o expresie de forma
2 - 3 + 4 se evalueaza ca (2 - 3) + 4, asa cum se specifica in gramatica. ar putea fi utilizate fara a schimba intelesul programului. Cu toate acestea left += term(); left -= term(); sint nu numai mai scurte, dar exprima direct operatia intentionata. Pentru un operator binar @, o expresie x @= y inseamna x = x @ y si se aplica la operatorii binari: + - * / % & | ^ << >> asa ca sint posibili urmatorii operatori de atribuire: = += -= *= /= %= &= |= ^= <<= >>= Testul pentru a ne asigura ca nu se face impartirea prin zero este necesar
deoarece rezultatul in acest caz nu este definit. Functia error(char*) este
descrisa mai tirziu. Variabila d este introdusa in program acolo unde este nevoie
de ea si este initializata imediat. In multe limbaje, o declaratie poate apare
numai in antetul unui bloc. Aceasta restrictie poate conduce la erori. Foarte
frecvent o variabila locala neinitializata este pur si simplu o indicatie de
un stil rau. Exceptii sint variabilele care se initializeaza prin operatii de
intrare si variabilele de tip vector sau structura care nu pot fi initializate
convenabil printr-o atribuire simpla. Sa observam ca = este operatorul de asignare,
iar == este operatorul de comparare. Cind se4 gaseste un NUMBER (adica o constanta flotanta), se returneaza valoarea
ei. Rutina de intrare get_token() plaseaza valoarea in variabila globala number_value.
Utilizarea unei variabile globale intr-un program indica adesea ca structura
nu este cit se poate de "curata", ca un anumit fel de optimizare a
fost aplicat. Asa este aici; un lexic in mod tipic consta din doua parti: o
valoare care specifica tipul lexicului (token_value in acest program) si (cind
este nevoie) valoarea lexicului. Aici exista numai o singura variabila simpla
curr_tok, asa ca este nevoie de variabila globala number_value pentru a pastra
valoarea ultimului NUMBER citit. Aceasta functioneaza deoarece calculatorul
totdeauna utilizeaza un numar in calcul inainte de a citi un alt numar de intrare. ------------------ Citirea intrarii este adesea cea mai incurcata parte a unui program. Motivul este faptul ca daca un program trebuie sa comunice cu o persoana, el trebuie sa invinga capriciile, conventiile si erorile unei persoane sau a mai multora. Incercarea de a forta persoana sa se comporte intr-o maniera mai convenabila pentru masina este adesea, pe drept cuvint, considerata ofensiva. Sarcina unei rutine de intrare de nivel inferior este de a citi caractere unul dupa altul si sa compuna unitati de nivel mai inalt. Aici intrarea de nivel inferior se face cu get_token(). Regulile pentru intrarile in calculator au fost deliberat alese asa ca sa fie ceva incomod pentru sirul de functii care le manevreaza. Modificari neimportante in definitiile unitatilor ar face pe get_token() foarte simpla. Prima problema este aceea ca, caracterul newline '\n' este semnificativ pentru calculator, dar sirul de functii de intrare il considera un caracter whitespace. Adica, pentru acele functii, '\n' este un terminator de unitate lexicala. Pentru a invinge aceasta trebuie examinate spatiile albe (spaces, tab, etc): char ch; doA //sare peste spatiile albe, exceptind '\n' if(!cin.get(ch)) return curr_tok = END; Swhile(ch!='\n' && isspace(ch)); Apelul cin.get(ch) citeste un singur caracter din sirul de la intrarea standard in ch. Testul if(!cin.get(ch)) esueaza daca nici un caracter nu poate fi citit de la intrare (din cin). In acest caz se returneaza END pentru a termina sesiunea de calcul. Operatorul ! (not) se utilizeaza intrucit get() returneaza o valoare nenula in caz de succes. Functia isspace() din <ctype.h> furnizeaza testul standard pentru spatiu alb (&8.4.1). Functia isspace(c) returneaza o valoare nenula daca c este un caracter alb, zero altfel. Testul este implementat ca o tabela de cautare, astfel, utilizind isspace este mai rapid decit daca s-ar testa individual caracterele spatiu alb. Acelasi lucru se aplica la functiile isalpha(), isdigit() si isalnum() utilizate in get_token(). Dupa ce s-a facut avans peste caracterele albe, se utilizeaza caracterul urmator pentru a determina ce fel de unitate lexicala incepe in sirul de intrare. Sa ne oprim la niste cazuri separate inainte de a prezenta functia completa. Expresiile terminatoare '\n' si ';' sint tratate astfel: switch(ch) A case ';' : case '\n' : cinn >> WS; //sare peste caractere albe return curr_tok = PRINT; S Saltul peste caractere albe (din nou) nu este necesar, dar daca ar trebui s-ar repeta apeluri ale lui get_token(). WS este un obiect de spatiu alb declarat in <stream.h>. El este utilizat numai pentru a indeparta spatiile albe. O eroare la intrare sau la sfirsitul intrarii nu va fi detectata pina la apelul urmator a lui get_token(). Sa observam modul in care diferite etichete ale lui case pot fi utilizate pentru un singur sir de instructiuni care trateaza acele cazuri. Se returneaza unitatea PRINT si se pune in curr_tok in ambele cazuri. Numerele se trateaza astfel: case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': cin.putback(ch); cin >> number_value; return curr_tok = NUMBER; Scrierea etichetelor orizontal in loc de vertical, in general, nu este o idee buna deoarece este mai greu de citit, dar nu este nimerit in cazul de fata sa avem o linie pentru fiecare cifra. Deoarece operatorul >> este deja definit pentru a citi constante in virgula flotanta dubla precizie, codul este trivial. Intii caracterul initial (o cifra sau un punct) se pune inapoi in cin si apoi constanta poate fi citita in number_value. Un nume, care este un lexic de tip NAME, este definit ca o litera care este posibil sa fie urmata de litere sau cifre: if(isalpha(ch)) Achar* p = name_string; *p++ = ch; while(cin.get(ch) && isalnum(ch)) *p++ = ch; cin.putback(ch); *p = 0; return curr_tok = NAME; S Aceasta construieste un sir terminat cu zero in name_string. Functiile isalpha() si isalnum() sint furnizate in <ctype.h>, isalnum(c) este diferit de zero daca c este o litera sau o cifra si zero altfel. Iata in final functia de intrare completa: token_value get_token() Achar ch; doA //sare peste spatiile albe exceptind '\n' if(!cin.get(ch)) return curr_tok = END; Swhile(ch != '\n' && isspace(ch)); switch(ch) A case ';' : case '\n': cin >> WS; //salt peste spatii albe return curr_tok = PRINT; case '*': case '/': case '+': case '(': case ')': case '=': return curr_tok = ch; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': cin.putback(ch); cin >> number_value; return curr_tok = NUMBER; default : //NAME, NAME= sau eroare if(isalpha(ch)) A char* p = name_string; *p++ = ch; while(cin.get(ch) && isalnum(ch)) *p++ = ch; cin.putback(ch); *p = 0; return curr_tok = NAME; S error("bad token"); return curr_tok = PRINT; S S Intrucit token_value al unui operator a fost definit prin valoarea intreaga a operatorului, toate alternativele (case) pentru operator se trateaza trivial. 3.1.3 Tabela de simboluri O singura functie are acces la tabela de simboluri: name* look(char* p, int ins = 0); inline name* insert(char* s)A return look(s, 1); S Deoarece toate obiectele statice sint implicit initializate cu zero, aceasta
declaratie triviala a lui table asigura de asemenea si initializarea. Pentru
a gasi o intrare pentru un nume din tabela, look() utilizeaza un cod hash simplu
(numele cu acelasi cod hash se inlantuie): int ii = 0; char* pp = p; Fiecare caracter din sirul de intrare p este "adaugat" la ii ("suma" caracterelor precedente) printr-un sau exclusiv. Un bit din x^y este setat daca si numai daca bitii corespunzatori din operanzii x si y sint diferiti. Inainte de a face un sau exclusiv, ii se deplaseaza cu un bit in stinga pentru a elimina utilizarea numai a unui octet din el. Aceasta se poate exprima astfel: ii <<= 1; ii ^= *pp++; Utilizarea lui ^ este mai rapida decit a lui +. Deplasarea este esentiala pentru
a obtine un cod hash rezonabil in ambele cazuri. Instructiunile: if(ii < 0) ii = -ii; ii %= TBLSZ; asigura ca ii sa fie in domeniul 0 ... TBLSZ - 1, (% este opera torul modulo,
numit si rest). Intrucit programul este atit de simplu, tratarea erorilor nu este o preocupare majora. Functia eroare pur si simplu numara erorile, scrie un mesaj de eroare si returneaza: int no_of_errors; double error(char* s) Acerr << "error: " << s << "\n"; no_of_errors++; return 1; S Motivul pentru care se returneaza o valoare este faptul ca erorile de obicei apar in mijlocul evaluarii unei expresii, asa ca ar trebui sau sa se faca un abandon al acelei evaluari sau sa se returneze o valoare care in continuare sa fie putin probabil sa cauzeze erori. Ultima varianta este adecvata pentru acest calculator simplu. Daca get_token() ar tine numerele de linie, error() ar putea informa utilizatorul aproximativ asupra locului unde a aparut eroarea. Aceasta ar fi util la o folosire interactiva a calculatorului. Adesea un program trebuie sa fie terminat dupa o eroare deoarece nu exista o cale adecvata care sa permita continuarea executiei. Acest lucru se poate face apelind functia exit(), care la inceput videaza lucrurile de tipul fisierelor de iesire (&8.3.2) dupa care se termina programul iar valoarea returnata de el este argumentul lui exit(). Un mod mai drastic de terminare a programului este apelul lui abort() care termina imediat sau imediat dupa pastrarea undeva a informatiei pentru debugger (vidaj de memorie). 3.1.5 Driverul Cu toate bucatile programului construite noi avem nevoie numai de un driver
care sa initializeze si sa porneasca tot procesul. In acest exemplu simplu functia
main() poate fi construita astfel: int main() //insereaza nume predefinite Prin conventie, main() returneaza zero daca programul se termina normal si
altfel, o valoare diferita de zero, asa ca returnarea numarului de erori se
potriveste bine cu aceasta conventie. Aici singurele initializari sint numerele
predefinite pentru "pi" si "e" care se insereaza in tabela
de simboluri. Dupa ce programul a fost scris si testat, am observat ca tastarea expresiilor
la intrarea standard a fost adesea mai mult decit necesar, deoarece in mod frecvent
a trebuit sa se evalueze o singura expresie. Daca este posibil ca aceasta expresie
sa fie prezentata ca un argument al liniei de comanda, atunci multe accese cheie
ar fi fost eliminate. Asa cum s-a mentionat in prealabil, un program incepe
prin apelul lui main(). Cind aceasta s-a facut, main() primeste doua argumente,
care specifica numarul de argumente si care de obicei se numeste argc si un
vector de argumente, care de obicei se numeste argv. Argumentele sint siruri
de caractere, asa ca tipul lui argv este char *aargci. Numele unui program (intrucit
el apare pe linia de comanda) se paseaza ca argva0i, asa ca argc este intotdeauna
cel putin 1. De exemplu, pentru comanda: dc 150/1.1934 argumentele au aceste valori: argc 2 argva0i "dc" argva1i "150/1.1934" 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 | | ai indexare pointeraexpri | | () 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 | | I complement Iexpr | | ! negare !expr | | - minus unar -expr | | + plus unar +expr | | & adresa &lvalue | | * indirectare *expr | | new creaza(aloca) new type | | delete distruge(dealoca) delete pointer | | deleteai distruge un vector deleteaexpripointer| | (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 inseamna (a + b) -; c deoarece + si - au aceeasi prioritate, dar operatorii + 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 apelurile de functii, include 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) Utilizarea parantezelor este mai frecventa cind subexpresiile 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: Ordinea de evaluare a subexpresiilor intr-o expresie este nedefinita. De exemplu: int i = 1; vaii = i++; poate fi evaluata sau ca va1i = 1, sau ca va2i = 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 Apelul lui f1 are doua argumente, vaii 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
(vaii, 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: 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. Decrementarea 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.
3.2.4 Operatori logici pe biti Operatorii logici pe biti &, |, ^, I, >> si << se aplica la
intregi; adica obiecte de tip char, short, int, long si corespunzatoarele 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 setului, 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 implementarea (nu interfata utilizator) lui <stream.h>: enum state_valueA_good = 0, _eof = 1, _fail = 2, _bad = 4 S; 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(0777); 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 eliminata. 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 implementare. 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 pointerii 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 explicit 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 eliminat 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. Obiectele
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 enodeA token_value oper; enode* left; enode* right; 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". Operatorul 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* argvai) Aceasta implica faptul ca un obiect alocat utilizind implementarea standard
prin new va ocupa putin mai mult spatiu decit un obiect static (de obicei un
cuvint in plus). Dimensiunea vectorului furnizata de utilizator se ignora exceptind unele tipuri
definite de utilizator (&5.5.5). 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 va produce done, p= 0 Instructiunile C++ sint descrise sistematic si complet in &r.9. Cu toate
acestea, dam mai jos un rezumat si citeva exemple. O valoare poate fi testata sau printr-o instructiune if sau printr-o instructiune
switch: if(expression) statement if(expression) statement else statement switch(expression) statement intii testeaza ca p nu este nul si numai daca este asa se testeaza 1 < p->count. se poate scrie if(val==1) f(); else if(val==2) g(); else h(); Apelat cu val == 2, produce case 2 case 1 Sa observam ca o scriere de forma goto case 1; este o eroare sintactica. 3.3.2 GotoC++ are faimoasa instructiune goto. goto identifier; identifier: statement Are putine utilizari in limbajele de nivel inalt, dar poate fi foarte util
cind un program C++ este generat printr-un program in loc ca programul sa fie
scris direct de catre o persoana; de exemplu, goto-urile pot fi utilizate intr-un
analizor generat dintr-o gramatica printr-un generator de analizoare. Utilizarea judicioasa a comentariilor si utilizarea consistenta a decalarilor
poate face sarcina citirii si intelegerii unui program mai placuta. Exista diferite
stiluri ale decalarilor. Autorul nu vede motive fundamentale pentru a prefera
un stil fata de altul (deci, ca multi altii, eu am preferintele mele). Acelasi
lucru se aplica si la stilurile de comentare. Multe programe contin comentarii care sint incomprehensibile, ambigue si chiar
eronate. Comentariile rele pot fi mai rele decit daca nu ar exista. Daca ceva
poate fi exprimat in limbajul insusi, ar trebui sa fie mentionat in el, nu numai
intr-un comentariu. Aceasta remarca este intarita de comentariile de mai jos: Astfel de comentarii sint mai rele decit redondanta: ele maresc cantitatea
de text pe care trebuie sa o citeasca programatorul si ele adesea fac mai obscura
structura programatorului. Un set de comentarii bine ales si bine scris este o parte esentiala a unui
program bun. Scrierea de comentarii bune poate fi tot atit de dificil ca si
scrierea programului insusi. 1. (*1). Sa se scrie instructiunea urmatoare ca o instructiune while echivalenta: for(i = 0; i < max_length; i++) if(input_lineaii == '?') quest_count++; 3. (*2). Sa se gaseasca 5 constructii C++ diferite pentru care sensul este
nedefinit. 7. (*2). Sa se scrie functiile strlen() care returneaza lungimea unui sir,
strcpy() care copiaza un sir in altul si strcmp() care compara doua siruri.
Sa se considere ce tipuri de argumente si ce tipuri se cuvine sa se returneze,
apoi sa se compare cu versiunile standard asa cum sint declarate in <string.h>. a := b+1; if(a = 3) 9. (*2). Sa se scrie o functie cat() care are doua argumente de tip sir si
returneaza un sir care este concatenarea argumentelor. Sa se utilizeze new pentru
a gasi memorie pentru rezultat. Sa se scrie o functie rev() care are un argument
de tip sir si reutilizeaza caracterele din el. Adica, dupa rev(p), ultimul caracter
a lui p va fi primul, etc. void send(register* to, register* from, register count) 16. (*2.5). Sa se scrie un program care elimina comentariile de tip C++ din
program. Adica, citeste din cin si elimina atit comentariile de forma //, cit
si cele de forma /*..*/ si scrie rezultatul in cout. Trebuie sa avem grija de
// si /*..*/ din comentarii, siruri si constante caracter. |
||||||
|
||||||
|
||||||
Copyright© 2005 - 2024 | Trimite document | Harta site | Adauga in favorite |
|