Sunteți pe pagina 1din 240

1. C ŞI C++ UN TUR DE ORIZONT.

1.1. STRUCTURA UNUI PROGRAM C FOARTE SIMPLU

Un limbaj de programare reprezintă o interfaţă între problema de rezolvat şi programul de rezolvare.

Limbajul de programare, prin specificarea unor acţiuni care trebuie executate eficient este apropiat de
maşină. Pe de altă parte, el trebuie să fie apropiat de problema de rezolvat, astfel încât soluţia problemei să
fie exprimată direct şi concis.Trecerea de la specificarea problemei la program nu este directă, ci presupune
parcurgerea mai multor etape:

•..analiza şi abstractizarea problemei. In această etapă se identifică obiectele implicate în rezolvare şi


acţiunile de transformare corespunzătoare. Ca rezultat al acestei etape se crează un univers abstract
al problemei (UP), care evidenţiază o mulţime de tipuri de obiecte, relaţiile dintre acestea şi restricţiile de
prelucrare necesare rezolvării problemei.

•..găsirea metodei de rezolvare acceptabile, precizând operatorii de prelucrare ai obiectelor din UP.

•..elaborarea algoritmului de rezolvare

•..codificarea algoritmului

Limbajul C s-a impus în elaborarea programelor datorită:

•..uşurinţei de reprezentare a obiectelor cu caracter nenumeric

•..capacităţii de reprezentare a obiectelor dinamice

•..capacităţii de exploatare a caracteristicilor maşinii de calcul pentru controlul strict al performanţelor


programului

•..asigurării unei interfeţe transparente cu sistemul de operare al maşinii utilizate.

Limbajul C a fost creat de Dennis Ritchie şi Brian Kernighan şi implementat pe o maşina DEC PDP

11, cu intenţia înlocuirii limbajului de asamblare.. Limbajul are precursori direcţi limbajele BCPL (Richards)
şi B (Thompson). Limbajul este folosit ca mediu de programare pentru sistemul de operare UNIX. Limbajul
a fost standardizat în 1983 şi 1989.

Limbajul C++ a fost dezvoltat de Bjarne Stroustrup pornind de la limbajul C, începând din anul 1980. C++
împrumută din Simula 67 conceptul de clasă şi din limbajul Algol 68 - supraîncărcarea

operatorilor.

Dintre noutăţile introduse de C++ menţionăm: moştenirea multiplă, funcţiile membre statice şi
funcţiile membre constante, şabloanele, tratarea excepţiilor, identificarea tipurilor la execuţie, spaţiile
de nume, etc.

Deşi C++ este considerat o extensie a limbajului C, cele două limbaje se bazează pe paradigme
de programare diferite. Limbajul C foloseşte paradigma programării procedurale şi structurate.
Conform acesteia, un program este privit ca o mulţime ierarhică de blocuri şi proceduri (funcţii). Limbajul
C++ foloseşte paradigma programării orientate pe obiecte, potrivit căreia un program este constituit
dintr-o mulţime de obiecte care interacţionează.
Elementul constructiv al unui program C este funcţia. Un program este constituit dintr-o mulţime de
funcţii, declarate pe un singur nivel (fără a se imbrica unele în altele), grupate în module program.

O funcţie este o secţiune de program, identificată printr-un nume şi parametrizată, construită folosind
declaraţii, definiţii şi instrucţiuni de prelucrare. Atunci când este apelată, funcţia calculează un anumit
rezultat sau realizează un anumit efect.

Funcţia main()este prezentă în orice program C. Execuţia programului începe cu main().Funcţia


main() poate întoarce un rezultat întreg (int) sau nici un rezultat (void). Numai în C este posibil să nu
specificăm tipul rezultatului întors de funcţie, acesta fiind considerat în mod implicit int.

/* program C pentru afisarea unui mesaj */

#include <stdio.h>

main(){

printf(“Acesta este primul program in C /n”);

Programul foloseşte un comentariu, delimitat prin /* şi */ care, prin explicaţii în limbaj


natural, creşte claritatea programului. Comentariul este constituit dintr-o linie sau mai multe linii, sau poate
apare în interiorul unei linii. Nu se pot include comentarii în interiorul altor comentarii.

În C++ se utilizează comentarii care încep cu //şi se termină prin sfârşitul de linie.

Linia #include <stdio.h> anunţă compilatorul că trebuie să insereze fişierul antet stdio.h.
Acest fişier conţine prototipurile unei serii de funcţii de intrare şi ieşire folosite de majoritatea programelor
C. Fişierele antet au prin convenţie extensia .h. Fişierul de inclus este căutat într-o zonă standard de
includere, în care sunt memorate fişierele antet ale compilatorului C, dacă numele este încadrat între
paranteze unghiulare (< şi >), sau căutarea se face în zona curentă de lucru, dacă fişierul este încadrat între
ghilimele(“). Fişierele antet sunt foarte utile în cazul funcţiilor standard de bibliotecă; fiecare
categorie de funcţie standard are propriul fişier antet.

/* program C pentru afisarea unui mesaj */

#include <stdio.h>

int main(){

printf(“Acesta este primul program in C /n”);

return 0;

Valoarea întoarsă de funcţia main() este în mod obişnuit 0, având semnificaţia că nu au fost întâlnite erori, şi
se asigură prin instrucţiunea return 0.

Instrucţiunea printf() serveşte pentru afişarea la terminal (pe ecran) a unor valori formatate.

Faţă de limbajul C, care este considerat un subset, limbajul C++ permite: abstractizarea datelor,
programarea orientată pe obiecte şi programarea generică.
// program C++ pentru afisarea unui mesaj

#include <iostream.h>

void main(void){

cout << “Primul program C++ \n”;

1.2. CÂTEVA ELEMENTE NECESARE SCRIERII UNOR PROGRAME C/C++ FOARTE SIMPLE.

1.2.1. DIRECTIVA DEFINE.

Directiva..#define nume text este o macrodefiniţie. Prelucrarea acesteia, numită


macroexpandare, înlocuieşte fiecare apariţie a numelui prin textul asociat.

O aplicaţie o reprezintă creerea de constante simbolice. De exemplu:

#defin PI 3.14159
e
#defin mesaj “Bonjour
#defin
e MAX 100
madame”
e
O constantă simbolică astfel definită nu poate fi redefinită prin atribuire.

1.2.2. TIPURI.

Fiecărui nume i se asociază un tip, care determină ce operaţii se pot aplica acelui nume şi cum sunt
interpretate acestea. De exemplu:

char c=’a’; // c este o variabilĂ caracter initializata cu ‘a’

int f(double); //f este o functie de argument real cu rezultat intreg

1.2.3. DEFINIŢII ŞI DECLARAŢII DE VARIABILE,

O valoare constantă se reprezintă textual (este un literal) sau printr-un nume - constantă simbolică.

O variabilă este un nume (identificator) care desemnează o locaţie de memorie în care se păstrează o valoare.

O variabilă se caracterizează aşadar prin: nume (adresă), tip şi valoare, atributul valoare putând fi
modificat. De exemplu:

int n, p; char c; float eps;

O variabilă poate fi iniţializată la declararea ei. De exemplu:

float eps=1.0e-6;

Iniţializarea se face numai o dată, înaintea execuţiei programului. Variabilele externe şi statice sunt
iniţializate implicit la 0.

Pentru o variabilă automatică (declarată în interiorul unui bloc), pentru care există iniţializare
explicită, aceasta este realizată la fiecare intrare în blocul care o conţine.
În C++ calificatorul const aplicat unui nume simbolic arată că acesta nu mai poate fi modificat pe
parcursul programului şi reprezintă o constantă. Definirea unei constante presupune şi iniţializarea acesteia.

const float pi=3.1415926;

O definiţie este o construcţie textuală care asociază unui nume o zonă de memorie (un
obiect) şi eventual iniţializează conţinutul zonei cu o valoare corespunzătoare tipului asociat numelui.

1.2.4. ATRIBUIREA.

Atribuirea simplă este de forma:variabilĂ = expresie şi are ca efect modificarea valorii


unei variabile.

Atribuirea compusă a op= b reprezintă într-o formă compactă operaţia a = a op b

Atribuirea multiplă este de forma variabilĂ1 = variabilĂ2 = … =


expresie şi iniţializează variabilele, pornind de la dreapta spre stânga cu valoarea expresiei.

Operatorii de incrementare folosiţi în atribuiri au efecte diferite. Astfel:

a = ++b este echivalentă cu b=b+1; a=b; în timp


a = b++ are ca efect a=b;
ce: b=b+1;

1.2.5. DECIZIA.

Permite alegerea între două alternative, în funcţie de valoarea (diferită de 0 sau 0) a unei expresii:

if (expresie)

instructiune1;

else instructiune2;
De exemplu:

if (a > b)

max=a;

else max=b;

1.2.6. CICLUL.

Execută în mod repetat instrucţiunea, cât timp condiţia este îndeplinită.

while (expresie)

instructiune;

De exemplu calculul celui mai mare divizor comun se realizează cu:

while (a!=b)

if (a > b)a -=b;

else b -=a;

1.2.7. AFIŞAREA VALORII UNEI EXPRESII (DESCRIPTORI),

Pentru afişarea unei valori la terminal, sub controlul unui format se foloseşte funcţia:

printf(lista_descriptori, lista_expresii);

De exemplu: printf(“pi=%.5f\n”, M_PI);

1.2.8. CITIREA VALORILOR DE LA TERMINAL.

Pentru citirea unor valori introduse de la terminal, sub controlul unui format se foloseşte funcţia:

scanf(lista_descriptori, lista_adrese);
De exemplu: scanf(“%d%d/n”, &a, &b);

1.3. STRUCTURA UNUI PROGRAM.

În C nu există noţiunea de program ci aceea de subprogram funcţie.

Compilatorul recunoaşte noţiunea de modul - un ansamblu de variabile şi de funcţii. Un program în C este o


funcţie cu numele main() care se execută întotdeauna prima.

Cea mai simplă structură de program este un modul compus dintr-o singură funcţie main():

void main (){

<declaratii locale>

<instructiuni>

2. ELEMENTELE FUNDAMENTALE ALE LIMBAJULUI (C / C++).

2.1. ALFABETUL LIMBAJULUI.

Conţine setul de caractere ASCII (setul extins 256 caractere).

2.2. ATOMI LEXICALI.

Există următoarele entităţi lexicale: identificatori, cuvinte cheie, constante, şiruri de caractere,
operatori şi separatori.

Spaţiile albe în C sunt reprezentate de: spaţiu liber (blanc), tabulare orizontală, tabulare verticală,
linie nouă, pagină nouă, comentarii. Spaţiile albe separă atomii lexicali vecini.

2.2.1. IDENTIFICATORI.

Identificatorii servesc pentru numirea constantelor simbolice, variabilelor, tipurilor şi funcţiilor.

Sunt formaţi dintr-o literă, urmată eventual de litere sau cifre. Caracterul de subliniere _ este
considerat literă.

Intr-un identificator literele mari si cele mici sunt distincte. Astfel Abc si abc sunt identificatori

diferiţi.

Identificatorii pot avea orice lungime, dar numai primele 32 caractere sunt semnificative.

2.2.2. CUVINTE CHEIE.

Cuvintele cheie sau cuvintele rezervate nu pot fi folosite ca identificatori. Acestea sunt:
auto break case char const continue
default do double else enum extern
float for if int long register
return short signed sizeof static struct
switch typedef union unsigned void volatile
while
2.2.3. LITERALI.

Un literal este o reprezentare explicită a unei valori. Literalii pot fi de mai multe tipuri tipuri: întregi,
caractere, reali, enumerări sau şiruri de caractere..

2.2.4 ŞIRURI DE CARACTERE.

Conţin caractere încadrate între ghilimele. În interiorul unui şir ghilimelele pot fi reprezentate prin \”

.Un şir de caractere se poate întinde pe mai multe linii, cu condiţia ca fiecare sfârşit de linie să fie precedat de
\.

Şirurile de caractere în C se termină cu caracterul nul ‘\0’. În cazul literalelor şiruri de caractere,
compilatorul adaugă în mod automat la sfârşitul şirului caracterul nul.

2.2.5. COMENTARII.

Un comentariu începe prin /*, se termină prin */şi se poate întinde pe mai multe linii. Comentariile
nu pot fi incluse unele în altele (imbricate).

În C++ au fost introduse comentariile pe o singură linie, care încep prin // şi au terminator sfârşitul liniei.

2.2.6. TERMINATORUL DE INSTRUCŢIUNE.

Caracterul ; este folosit ca terminator pentru instrucţiuni şi declaraţii, cu o singură excepţie – după o

instrucţiune compusă, terminată prin acoladă, nu mai este necesar terminatorul ; .

2.2.7. CONSTANTE.

Constantele identificatori se obţin folosind directiva #definea preprocesorului:

#defineconstantĂ-identificator.. literal sau constantĂ-identificator

#defineconstantă-identificator.. (expresie-constantă)

Conţine următoarele etape:

2.3. CICLUL DE DEZVOLTARE AL UNUI PROGRAM

1. DEFINIREA PROBLEMEI DE REZOLVAT.

Analiza problemei cuprinde în afara formulării problemei în limbaj natural, o precizare riguroasă
a intrărilor (datelor problemei) şi a ieşirilor (rezultatelor).

De exemplu ne propunem să rezolvăm ecuaţia de gradul 2: ax2+bx+c=0


Datele de intrare sunt cei 3 coeficienţi a, b, cai ecuaţiei, care precizează o anumită ecuaţie de grad 2.
Rezultatele sunt cele două rădăcini (reale sau complexe) sau celelalte situaţii particulare care pot apare.

2. IDENTIFICAREA PAŞILOR NECESARI PENTRU REZOLVAREA PROBLEMEI

începe cu formularea modelului matematic.

Ca model matematic vom folosi formula:

x1,2 =

−b ± b2 − 4ac

2a

Formula nu este întotdeauna aplicabilă. Vom distinge următoarele situaţii:

1. a=0, caz în care nu avem de a face cu o ecuaţie de gradul 2, ci

1.1... este posibil să avem ecuaţia de gradul 1: bx+c=0, cu soluţia

x= - c/b, dacă b ≠ 0.

1.2... dacă şi b=0, atunci

1.2.1. dacă c≠0, atunci nu avem nici o soluţie, în timp ce

1.2.2...dacă şi c=0, atunci avem o infinitate de soluţii.

2. a≠0 corespunde ecuaţiei de gradul 2. In acest caz avem alte două situaţii:

2.1... Formula este aplicabilă pentru rădăcini reale (discriminant pozitiv)

2.2... Pentru discriminant negativ, întrucât nu dispunem de aritmetică complexă, va trebui să

efectuăm separat calculele pentru partea reală şi cea imaginară.

3..... PROIECTAREA ALGORITMULUI folosind ca instrument PSEUDOCODUL.

Pseudocodul folosit cuprinde:

• operaţii de intrare / ieşire:

citeşte var1, var2,…

scrie expresie1, expresie2,…

• structuri de control:

• decizia:

dacă expresie atunci instrucţiune1;


altfel instrucţiune2;

• ciclul:

cât timp expresie repetă

instrucţiune;

• secvenţa:

{ instrucţiune1;

... instrucţiunen;

Algoritmul dezvoltat pe baza acestui pseudocod este:

reali a,b,c,delta,x1,x2,xr,xi;

citeşte a,b,c;

dacă a=0 atunci

dacă b≠0 atunci scrie –c/b;

altfel

dacă c≠0 atunci

scrie “nu avem nici o solutie”;

altfel

scrie “o infinitate de solutii”;

altfel

{ delta=b*b-4*a*c;

dacă delta >= 0 atunci {

x1=(-b-sqrt(delta))/(2*a); x2=(-b+sqrt(delta))/(2*a); scrie x1,x2;

altfel {

xr=-b/(2*a);

xi=sqrt(-delta)/(2*a);

scrie xr,xi;
}

4..... SCRIEREA PROGRAMULUI FOLOSIND UN LIMBAJ DE PROGRAMARE.

Vom codifica algoritmul descris mai sus folosind limbajul C:

#include <stdio.h>

#include <math.h>

void main(void)

{ double a,b,c,delta,x1,x2,xr,xi;

scanf(“%f %f %f”,&a,&b,&c);

if (a==0)

if (b!=0)

printf(“o singura radacina x=%6.2f\n”,-c/b);

else

if (c!=0)

printf(“nici o solutie\n”);

else

printf(“o infinitate de solutii\n”);

else

{ delta=b*b-4*a*c;

if(delta >= 0)

{ x1=(-b-sqrt(delta))/2/a; x2=(-b+sqrt(delta))/2/a;
printf(“x1=%5.2f\tx2=%5.2f\n”,x1,x2);

else

{ xr=-b/2/a;

xi=sqrt(-delta)/2/a;

rintf(“x1=%5.2f+i*%5.2f\nx2=%5.2f-i*%5.2f\n”, xr,xi,xr,xi);

}
}

FIG.2.1. ETAPELE REZOLVĂRII UNEI PROBLEME FOLOSIND CALCULATORUL

5..... IMPLEMENTAREA PROGRAMULUI: EDITARE, COMPILARE, EDITARE DE LEGĂTURI,


EXECUŢIE.

Un mediu de programare C conţine:

• Un editor de text – folosit pentru creearea şi modificarea codului sursă C

• Un compilator – pentru a converti programul C în cod înţeles de calculator. Procesul de compilare


cuprinde:

„ o fază de precompilare (macroprocesare) în care are loc expandarea macrodefiniţiilor si

compilarea condiţională si includerea fisierelor

„ compilarea propriu-zisă în urma careia se generează cod obiect

Compilarea din linia de comandă în GCC se face cu:

gcc –c prog.c.. prog.c→prog.o

• Editor de legături –leagă la codul obiect anumite funcţii de bibliotecă (de exemplu intrări/ieşiri) extrase dintr-
o bibliotecă de funcţii, creindu-se un program executabil.

gcc -o prog prog.o prog.o→prog.exe

Compilarea şi editarea de legături se poate face printr-o singură comandă:

gcc –o prog prog.c prog.c→prog.exe

Opţiunea –opermite specificarea fişierului de ieşire. Dacă acesta lipseşte, se consideră în mod implicit ca
nume al fişierului executabil a.out, Aşadar:

gcc prog.c.... prog.c→a.out

• Fişiere biblioteci de funcţii –sunt fişiere de funcţii gata compilatecare se adaugă (se leagă) la program.

Există biblioteci pentru: funcţii matematice, şiruri de caractere, intrări/ieşiri.

Biblioteca standard C la execuţie (run-time) este adăugată în mod automat la fiecare program; celelalte
biblioteci trebuiesc legate în mod explicit.
De exemplu, în GNU C++ pentru a include biblioteca matematică libm.sose foloseşte opţiunea –lm:

gcc –o prog prog.c –lm

Bibliotecile pot fi:

-..statice (.libîn BorlandC, .a în GCC) –codul întregii biblioteci este ataşat programului

-..dinamice (.dllîn BorlandC, .soîn GCC) –programului I se ataşează numai funcţiile pe care acesta le
solicită din bibliotecă.

În GCC se leagă în mod implicit versiunea dinamică a bibliotecii; pentru a lega versiunea staticăse
foloseşte opţiunea –static.

• Fişiere antet (header files)–datele şi funcţiile conţinute în biblioteci sunt declarate în fişiere antet asociate
bibliotecilor. Prin includerea unui fişier antet compilatorul poate verifica corectitudinea apelurilor de funcţii
din biblioteca de funcţii asociată (fără ca aceste funcţii să fie disponibile în momemtul compilării).

Bibliotecă Fişier antet


matematică math.h
Siruri de caractere string.h
Intrări/ieşiri stdio.h
Un fişier antet este inclus printr-o directivă cu sintaxa:

#include <nume.h>

Aceasta caută fişierul antet într-un director special de fişiere incluse (include).

#include “nume.h”

Caută fişierul antet în directorul current.

Încărcarea şi execuţia programului (fişierului executabil .exe) se vface în GCC prin:

./ prog

6..... Depanarea programului (debugging).

Depanarea unui program reprezintă localizarea şi înlăturarea erorilor acestuia.

Cele mai frecvente erori de sintaxă se referă la: lipsa terminatorului de instrucţiune ;, neechilibrarea
parantezelor, neînchiderea şirurilor de caractere, etc.

Erorile detectate de către editorul de legături sunt referinţele nerezolvate (apelarea unor funcţii care
nu au fost definite, sau care se află în biblioteci care nu au fost incluse).

Cele mai des întâlnite erori la execuţie provin din:

-..confuzia între = şi ==

-..depăşirea limitelor memoriei allocate tablourilor


-..variabile neiniţializate

-..erori matematice: împărţire prin 0, depăşire, radical dintr-un număr negative, etc.

Dacă s-a depăşit faza erorilor la execuţie, vom testa programul, furnizându-i date de intrare pentru
care cunoaştem ieşirile. Apariţia unor neconcordanţe indică prezenţa unor erori logice. Dacă însă, rezultatele
sunt corecte, nu avem certitudinea că programul funcţionează corect în toate situaţiile. Prin testare putem
constata prezenţa erorilor, nu însă şi absenţa lor.

Desfăşurarea calculelor poate fi controlată prin execuţie pas cu pas,sau prin asigurarea unor puncte de
întrerupere în care să inspectăm starea programului, folosind în acest scop un depanator (debugger).

2.4. Întrebări şi probleme.

1. Ce deosebire există între ‘A’şi “A”?

2. Ce caractere pot fi reprezentate prin secvenţele escape: /101, /012.

3. Reprezentaţi şirurile de caractere ‘şi “.


Operaţii de intrare / ieşire în C .

1. Fişiere standard (fluxuri de intrare/ieşire).

În C nu sunt definite operaţiile de intrare / ieşire şi nici noţiunea de fişier. Există o bibliotecă de funcţii
stdio.h care defineşte un tip fişier şi operaţii pe intrarea şi ieşirea standard. Includerea acestei biblioteci în
program se face cu directiva:

#include <stdio.h>

Există următoarele fişiere standard: fişierul standard de intrare (tastatura) stdin şi fişierul standard
de ieşire (ecranul) stdout, fişierul standard de eroare stderr.

Fişierele standard de intrare şi de ieşire sunt fişiere text şi constau din fluxuri de caractere.

Un flux text este o secvenţă de caractere impărţită în linii, fiecare linie fiind formată din 0 sau mai multe
caractere şi terminată prin caracterul linie nouă. Biblioteca standard asigură funcţii de citire şi scriere a unui
caracter (getchar() şi putchar()).

2. Scrierea cu format.

În limbajul C putem depune în fluxul de ieşire valorile unor expresii aparţinând unor tipuri primitive şi
anume caractere, întregi, reali şi şiruri de caractere. Funcţia printf() realizează transferul şi conversia de
reprezentare a valorii întregi / reale în şir de caractere sub controlul unui format (specificat ca un şir de
caractere):

printf( format, e1, e2, …, en)

Pentru fiecare ei există în format un descriptor care începe prin %. Afişarea fiecărei expresii se face
corespunzător descriptorului. Sintaxa descriptorului este:

% [ - ] [ Lung ] [ .frac ] [ l ] descriptor

Descriptorul indică ce conversie (în şir de caractere) se face:

d - întreg cu semn în baza 10

o - întreg fără semn în baza 8

u - întreg fără semn în baza 10

x sau X - întreg cu semn în baza 16, cu cifrele A-F mici sau mari

c - caracter

s - şir de caractere

f - real zecimal de forma [-]xxx.yyyyyy (implicit 6 cifre)

e sau E - real afişat sub forma [-]x.yyyyyye+zz

g - realizează conversia şi afişarea ca descriptorul f sau e astfel încât să apară un număr minim de cifre
afişate
Semnul - după % indică o aliniere la stânga în câmpul de lungime Lung (implicit alinierea se face la
dreapta).

Dacă expresia conţine mai puţin de Lung caractere, ea este precedată de spaţii sau zerouri, dacă
Lung începe printr-un zero.

Dacă expresia conţine mai mult de Lung caractere, câmpul de afişare este extins.În absenţa lui
Lung, expresia va fi afişată cu atâtea caractere câte le conţine.

frac - indică numărul de cifre după virgulă (precizia) cu care se face afişarea

l - marchează un long int (h - un short int). Pentru reali aceştia sunt double.

L - precede unul din descriptorii f,e,E,g,G pentru afişarea unei valori de tip long double

Funcţia furnizează numărul de caractere afişate. Exemple:

printf(“%10d\n”, x); /* afisare valoare în 10 pozitii, aliniata la

.. dreapta si completata cu blancuri */

printf(“%-10d\n”,x); /* afisare valoare in 10 pozitii, aliniata la

.. stanga si completata cu blancuri */

printf(“%#010d\n”,x);/*afisare valoare in 10 pozitii, aliniata la

.. dreapta si completata cu zerouri */

printf(“%#x10d\n”,x);/* afisare valoare in 10 pozitii, aliniata la

.. dreapta si completata cu 0x */

3. Citirea cu format.

scanf( format, a1, a2, …, an)

Citeşte valorile de la intrarea standard în formatul precizat şi le plasează în variabilele cu adresele


a1, a2,….

Pentru o variabilă simplă sau înregistrare, adresa se obţine folosind operatorul @.

Sintaxa descriptorului de format este:

% [*] [ Lung ] [ l ] descriptor

Descriptorul indică conversia care se face:

d - întreg în baza 10

o - întreg în baza 8

x - întreg în baza 16
f - real

c - caracter

s - şir de caractere.

* indică faptul că valoarea citită nu se atribuie unei variabile.

Lung indică lungimea câmpului din care se face citirea. În lipsa lui citirea se opreşte la primul
caracter care nu face parte din număr.

L indică un întreg long sau un real double; h - indică un întreg short.

Funcţia scanf() furnizează numărul de câmpuri citite.

4. Citirea / scrierea la nivel de caracter.

Pentru realizarea operaţiilor de transfer între memorie şi fişierele standard se utilizează următoarele funcţii:

 getch() - citeşte un caracter, fără ecou de la tastatură; funcţia returnează valoarea ASCII a caracterului
citit.

Apelul getch(); se foloseşte pentru a reţine fereastra de ieşire; dacă se introduce un caracter, se
trece în fereastra de editare.

 getche() - are acelaşi efect cu funcţia getch(), cu deosebirea că citirea face cu ecou.

 putch(expresie) - afişează pe ecran caracterul având codul ASCII egal cu valoarea expresiei; funcţia
returnează codul caracterului afişat.

Aceste funcţii au prototipurile declarate în fişierul <conio.h>. pentru a le folosi va trebui să includem
acest fişier în programul nostru folosind directiva include.

 getchar() - nu este o funcţie ci o macroinstrucţiune. Are ca efect citirea cu ecou a unui caracter de la
terminalul standard. Caracterele introduse de la tastatură sunt puse într-o zonă tampon, până la acţionarea
tastei ENTER, moment în care în zona tampon se introduce caracterul rând nou.Fiecare apel getchar()
preia următorul caracter din zona tampon.

 putchar(expresie) - este tot o macroinstrucţiune, care afişează caracterul având codul ASCII egal cu
valoarea expresiei parametru.

Macrourile getchar şi putchar sunt definite în fişierul <stdio.h>.

5. Citirea / scrierea unei linii.

 gets(zona) - introduce de la terminalul standard un şir de caractere terminat prin acţionarea tastei
ENTER. Funcţia are ca parametru adresa zonei de memorie în care se introduc caracterele citite. Funcţia
returnează adresa de început a zonei de memorie (parametrul); la întâlnirea sfârşitului de fişier (CTRL Z)
funcţia returnează NUL.
 puts(zona) – afişează la terminalul standard şirul de caractere din zona dată ca parametru, până la
caracterul NUL,care va fi înlocuit prin caracterul linie noua. Funcţia returnează codul ultimului caracter din
şirul de caractere afisate (-1 în caz de eroare).

Funcţiile gets() şi puts() au prototipurile în fişierul <stdio.h>.

Operaţii de intrare / ieşire în C++ .

Ca şi în C, în C++ facilităţile de intrare / ieşire sunt implementate prin intermediul unei biblioteci de
funcţii.

Pe nivelul cel mai de jos, un fişier este interpretat ca un flux de octeţi (stream).

Pe nivelul utilizator, fişierul constă dintr-o secvenţă de tipuri amestecate de date: caractere, valori
aritmetice, obiecte.

Biblioteca de funcţii de intrare / ieşire realizează interfaţa între cele două niveluri.

În bibliotecă este predefinit un set de operaţii pentru citirea şi scrierea unor tipuri predefinite.

Aceste operaţii pot fi extinse de către programator pentru tipurile de date definite de către utilizator
(clase).

Operaţiile de intrare sunt suportate de clasa istream, cele de ieşire – de clasa ostream.

Scrierea în fluxul de ieşire (inserţie) se realizează cu operatorul << ( << x – preia date din x).

Citirea din fluxul de intrare (extracţie) se realizează cu operatorul >> ( >>x – pune date in x).

Sunt predefinite 4 fluxuri de date: cin – obiect din clasa istream, legat de intrarea standard, cout –
obiect din clasa ostream, legat de ieşirea standard, cerr – obiect din clasa ostream legat de ieşirea
standard de eroare nebuferată şi clog – legat de ieşirea standard de eroare buferată.

Intrările / ieşirile standard folosesc obiectele cin, cout şi cerr, iniţializate prin includerea fişierului
antet iostream.h.

1. Ieşirea standard.

Se realizează folosind operatorul de inserare << (depune în flux). Acest operator este predefinit pentru
tipurile: caracter, întregi, reali, şiruri de caractere, pointeri. Operatorul poate fi supraîncărcat pentru tipurile
utilizator (tipuri abstracte). Exemple:

#include <iostream.h>

int n=10;

cout << n << “\n”; // acelasi efect are printf(“%d\n”, n);

char c=’A’;

cout << c << “\n”; // similar cu printf(“%c\n”, c);


Pentru realizarea unor facilităţi de formatare, în C++ seutilizează indicatori de format (flaguri), care
conţin într-o dată membru al clasei ios biţii cu semnificaţiile următoare:

enum {

skipws =0x0001, // salt peste spatii albe

left.. =0x0002, // alinierea iesirii la stinga

right..=0x0004, // alinierea iesirii la dreapta

internal =0x0008,

dec =0x0010, // conversie in zecimal

oct =0x0020, // conversie in octal

hex =0x0040, // conversie in hexazecimal,

showbase =0x0080, // se afiseaza baza

showpoint =0x0100, // se afiseaza punctul zecimal

uppercase =0x0200, // se afiseaza cu litere mari

showpos =0x0400, // intregii pozitivi sunt afisati cu +

scientific=0x0800, // realii se afiseaza cu exponent

fixed..=0x1000, // realii se afiseaza fara exponent

unitbuf =0x2000, // se goleste zona tampon pentru iesiri

stdio..=0x4000, // dupa fiecare iesire se videaza stdout

};

În absenţa indicaţiilor de formatare, se face o formatare implicită, în baza 10, cu aliniere la dreapta în
câmpul de ieşire.

Pentru a realiza o formatare definită de utilizator, diferită de cea implicită, se pot seta biţii membrului
indicator cu funcţiile:

long setf(long f); // seteaza indicatorii, intoarce vechiul format

long setf(long b, long g);//seteaza bitul specificat din grupa

Biţii indicatorului de format se împart în 3 grupe:

1) aliniere (adjustfield) cu biţii right, left şi internal


2) bază (basefield) cu biţii dec, oct, hex
3) exponent (floatfield) cu biţii scientific şi fixed.

Într-o grupă poate fi setat un singur indicator.


int width(); // returneaza latimea zonei de afisare

int width(int w); // modifica la w, latimea zonei de afisare

Implicit, lăţimea minimă a zonei de afişare este 0; ea poate fi extinsă automat pentru a permite
afişarea datei cu toate cifrele.

Dacă numărul de caractere al datei este mai mic decât lăţimea minimă a zonei de afişare, se face
aliniere la dreapta, completată la stânga cu spaţii libere.

Caracterul de umplere (cu care se completează lăţimea minimă a zonei de afişare poate fi definit
prin:

char fill(); // intoarce codul caracterului de umplere curent

char fill(char c); //defineste un nou caracter de umplere

Numărul de zecimale cu care se afişează un număr real (precizia) poate fi setat cu:

int precision();.. //intoarce precizia curenta

int precision(int n); // modifica precizia la n cifre,

// intoarce vechea precizie

Exemple:

int i=25;

cout.width(10);

cout.setf(ios::left,ios::adjustfield);

cout.fill(‘0’);

cout.setf(ios::showpos);

cout.setf(ios::hex,ios::basefield);

cout << i << endl;

double x=123.456;

cout.setf(ios::scientific,, ios::floatfield);

cout.precision(5);

cout << x << endl;

2. Manipulatori.

Permit definirea formatelor pentru operaţiile de intrare / ieşire, într-un mod mai simplu decât o fac
indicatorii de format.
Există următorii manipulatori:

dec – indicator de conversie zecimal

oct – indicator de conversie octal

hex – indicator de conversie hexazecimal

ws – setare salt peste spaţii albe

endl – inserează linie nouă şi goleşte tamponul fluxului

ends – inserează caracterul nul de terminare a unui şir de caractere

flush – goleşte tamponul fluxului

setbase(int n) - setează baza(n = 0, 8, 10, 16)

resetiosflags(long x) – şterge biţii de format specificaţi

setiosflags(long x) – setează biţii de format specificaţi

setfill(int n) – setează caracterul de umplere

setprecision(int n) – setează precizia

setw(int w) – setarea lăţimii câmpului de afişare

Pentru a folosi manipulatori se include fişierul iomanip.h

Exemple:

int i = 12345;

cout << setw(10) << resetiosflags(ios::internal|ios::left) <<

setiosflags(ios::right) << setfill(‘0’) <<

setiosflags(ios::showpos) << i;

double x = 2.718281;

cout << resetiosflags(ios::fixed) << setiosflags(ios::scientific)

..<< setprecision(10) << x;

3. Intrarea standard.

Pentru a realiza citiri din fluxul de intrare standard cin se foloseşte operatorul de extragere >>.
Acesta este predefinit pentru tipurile primitive: caracter, întregi, reali, şiruri de caractere.. Operatorul poate
fi supraîncărcat pentru tipuri utilizator..

În mod implicit, operatorul >> sare spaţiile albe până la primul caracter care nu este spaţiu alb.
int n;

cin >> n; //echivalent cu scanf(“%d”, &n);

La citirea într-un tablou şir de caractere, trebuie să ne asigurăm că nu se citesc mai multe caractere
decât are rezervat tabloul:

char x[100];

cin.width(sizeof x);

cin >> x;

Transferul începe cu primul caracter nealb şi se încheie la întâlnirea primului caracter alb sau a unui
caracter ce nu aparţine tipului.

În operaţiile de extragere nu pot fi utilizaţi manipulatorii: endl, ends, flush, setbase.


Manipulatorul ws se foloseşte numai în operaţiile de extragere.

Un flux are asociată o stare de eroare – membrul dată state al clasei ios.

enum io_state {

goodbit=0x00, // operatie de I/E corecta

eofbit =0x01, // s-a intilnit eof la citire

failbit=0x02, // ultima operatie de I/E esuata

badbit =0x04, // ultima operatie de I/E incorecta

hardfail=0x08 // eroare irecuperabila

};

Dacă fluxul intră în stare de eroare, operaţiile de intrare / ieşire pot fi continuate numai după ce
condiţia de eroare a fost înlăturată şi biţii de eroare au fost şterşi. Acest lucru nu este posibil dacă se
produce o eroare irecuperabilă.

Accesul la biţii de eroare se face folosind funcţiile:

int good();

int eof();

int fail(); // intoarce 1 daca failbit | hardfail

int bad();

int rdstate();

void clear(int i=0);

clear(); // anuleaza bitii de eroare mai putin hardfail


Un stream poate fi testat pentru a vedea dacă se află în stare de eroare:

if(!cin) … // cin==0 daca un bit de eroare este setat

if(cin>>x)… //rezultatul este convertit intr-un pointer, care

//este 0 daca un bit de eroare este setat

4. Operaţii de intrare / ieşire fără format.

4.1. Scrierea unui caracter.

char c;

cout.put( c ); // similar cu cout << c

4.2. Scrierea a n octeţi.

ostream& ostream::write(char*,int); //pune n octeti din sirul s in cout

int n;

char s[100];

cout.write(s, n); //insereaza n octeti din s in cout

4.3. Citirea unui caracter.

istream& get(char&); // extrage 1 caracter din fluxul curent

char c;

cin.get( c ); //citeste 1 caracter,inclusiv spatiu alb

Copierea intrării la ieşire se face prin:

while( (c=cin.get())!=EOF)

cout.put( c );

echivalent cu:

while(cin.get(c))

cout.put(c);

4.4. Citirea unei linii.

istream& getline(char* zona, int max, int term=’\n’);

const int MAX=80;

char linie[MAX];

cin.getline(linie, MAX);
Sunt extrase în zona, cel mult max-1 caractere (sau până la întâlnirea terminatorului). Ultimul
caracter pus este 0 (terminatorul de şir).

4.5. Citirea fără format a n octeţi.

istream& read(char* zona, int n);//extrage n octeti in zona

4.6. Punerea unui caracter în fluxul de intrare.

istream& putback(char);

cin.putback(c); // pune c in streamul cin

O posibilă aplicaţie o reprezintă recunoaşterea identificatorilor – primul caracter care nu mai


aparţine identificatorului trebuie pus înapoi.

peek(); - întoarce următorul caracter, dar nu-l extrage din flux

ignore(int n=1, int delim=EOF); - ignoră n caractere; se opreşte la întâlnirea caracterului


delim.

3. TIPURI ŞI VARIABILE.

3.1... TIPURI.

Un tip de date este precizat prin:

- o mulţime finită de valori corespunzătoare tipului (constantele tipului)

- o mulţime de operatori prin care se prelucrează valorile tipului

- o multime de restricţii de utilizare a operatorilor.

De exemplu tipul întreg (int) este definit prin:

- mulţimea valorilor reprezentând numere întregi (între -32768şi 32767)

- mulţimea operatorilor : +, -, *, /, %

- mulţimea restricţiilor: pentru operatorul / împărţitorul nu poate fi 0, etc.

Tipurile pot fi tipuri fundamentale şi tipuri derivate.

Tipurile fundamentale (predefinite sau de bază sunt:

•..Tipul boolean (bool)

•..Tipul caracter (char)

•..Tipuri întregi (int, short, long)

•..Tipuri reale (float, double, long double)

•..Tipul vid (void)


•..Tipurile enumerate (enum)

Tipurile boolean, caracter, întreg, real şi tipurile enumerate sunt tipuri aritmetice, deoarece valorile
lor pot fi interpretate ca numere.

Tipurile boolean, caracter, întreg şi enumerările sunt tipuri întregi.

Tipurile derivate sunt construite pornind de la tipurile fundamentale. Tipurile derivate sunt:

•.. tablourile

•.. funcţiile

•.. pointerii

•.. referinţele

•.. structurile (sau înregistrările)

•.. uniunile (înregistrările cu variante)

În cele ce urmează, vom înţelege prin obiect, o zonă de memorie.

Declararea unui obiect specifică numai proprietăţile obiectului, fără a aloca memorie pentru

acesta.

Definirea unui obiect specifică proprietăţile obiectului şi alocă memorie pentru obiect. Declararea obiectelor
ne permite referirea la obiecte care vor fi definite ulterior în acelaşi fişier

sau în alte fişiere care conţin părţi ale programului.

3.2. TIPURI FUNDAMENTALE.

Calculatoarele pot lucra în mod direct cu caractere, întregi şi reali. Acestea sunt tipuri
fundamentale (predefinite).

3.2.1. CARACTERELE (TIPUL char)

Valorile asociate tipului caracter (char) sunt elemente din mulţimea caracterelor – setul de caractere
ASCII. Drept consecinţă, caracterele pot fi tratate ca întregi, şi invers.

Afişarea sau citirea unei variabile de tip char la terminal se face cu descriptorul de format

%c.

Fiecărei constante caracter i se asociază o valoare întreagă, valoarea caracterului în setul de

caractere ASCII. Pentru reprezentarea de caractere în alt set de caractere (de exemplu Unicode) se
foloseşte tipul wchar_t.
Un literal caracter se reprezintă prin caracterul respectiv inclus între apostrofi (dacă este tipăribil).

Caracterele netipăribile se reprezintă prin mai multe caractere speciale numite secvenţe escape. Acestea sunt:

\n sfârşit de linie (LF)

\t tabulare orizontală (HT)

\v tabulare verticală (VT)

\b revenire la caracterul precedent (BS)

\r revenire la început de linie (CR)

\f avans la pagină nouă (FF)

\a alarmă (BEL)

\\ caracterul \

\? caracterul ?

\’ caracterul ‘

\” caracterul “

\ooo caracterul cu codul octal ooo

\xhh caracterul cu codul hexazecimal hh

3.2.2. ÎNTREGII (TIPUL int).

Întregii pot avea 3 dimensiuni: int, short int(sau short) şi long int(sau long).

Constantele intregi pot fi date in bazele:

10: 257, -65, +4928

8: 0125, 0177

16: 0x1ac, 0XBF3

Afişarea unei variabile de tip intîn baza 10 la terminal se face cu descriptorul de format %d

sau %i, în baza 8 cu %o, iar în baza 16 cu %x.

Calificatorii long, shortşi unsigned

Calificatorul long situat înaintea tipului intextinde domeniul tipului întreg de la

(-215,215-1)la (-231, 231-1).


Constantele intregi lungi se scriu cu sufixul L: 125436L.

Afişarea sau citirea unei variabile de tip long int la terminal se face cu descriptorul de
format %ldsau %liîn baza 10, %loîn baza 8 şi %lxîn baza 16.

Calificatorul shortsituat înaintea tipului intrestrânge domeniul întregilor.

Afişarea sau citirea unei variabile de tip short int la terminal se face cu descriptorul de format
%hdsau %hiîn baza 10, %hoîn baza 8 şi %hxîn baza 16.

Calificatorul unsignedînainte de intdeplasează domeniul întregilor

(-215,215-1)la (0,216-1).

Afişarea sau citirea unei variabile de tip unsigned int la terminal se face cu descriptorul de
format %udsau %uiîn baza 10, %uoîn baza 8 şi %uxîn baza 16.

Constantele fără semn se specifică cu sufixul Usau u. Există 6 tipuri întregi, formate folosind calificatorii:

{ [ signed | unsigned ] } { [ short | long ] } int

Avem următoarele echivalenţe:

int = signed int

short = short int = signed short int long = long int

unsigned = unsigned int

unsigned short = unsigned short int unsigned long = unsigned long int

Literali întregi fi scrişi în:

- zecimal - un şir de cifre zecimale, dintre care prima nu este 0.

- octal..- un şir de cifre octale care începe cu cifra 0

- hexazecimal - un şir de cifre hexa care începe prin 0x.

Un numar negativ este precedat de semnul -. Există şi semnul + unar. Exemple: 125 01473 0x2AFC..
+645.. -8359

Literalii întregi se pot termina prin usau U(fără semn) sau lsau L(de tip lung).

Dacă constanta nu are sufix, atunci ea va aparţine primului tip din succesiunea: int, long int,

unsigned long intcare permite reprezentarea valorii.

3.2.3. REALII (TIPURILE floatŞI double).

Partea întreagă sau cea fracţionară din constanta reală poate lipsi:
întreg.fractie sau..întreg. sau...fractie

Exemple: 2.25, 1., -.5, +234.5

Constantele reale pot fi exprimate cu mantisă şi exponent (notaţia ştiinţifică):

mantisaEexponent = mantisa 10exponent

Exemple: 1.5e-3, 0.5E6.

Tipul float asigură o precizie de 7 cifre zecimale semnificative şi exponentul maxim 38.
Reprezentarea se face pe 4 octeţi.

Afişarea unei variabile de tip floatla terminal se face cu descriptorii de format %fsau %e.

Tipul double este foarte asemănător tipului float, cu deosebirea că se asigură o precizie de 16
cifre zecimale semnificative şi exponentul maxim 306. Reprezentarea se face pe 8 octeţi.

Afişarea sau citirea unei variabile de tip doublela terminal se face cu descriptorul de format

%lf, iar a unei variabile de tip long doublecu %Lf. Reprezentarea internă pentru long double

se face pe 10 octeţi.

Numerele reale conţin punct zecimal şi/sau exponent, având forma:

[<parte întreaga>][.<parte fracţionara>][ E<exponent>]

Partea întreagă si partea fracţionară pot lipsi, dar nu simultan. Punctul zecimal şi exponentul sunt
opţionale, dar nu simultan.

Constanta poate avea un sufix:

- f sau Fprecizează o constantă de tip float

- lsau Lprecizează o constantă de tip long double.

Exemple: .25 -7.628 15E-3

3.2.4. DEFINIRI DE TIP CU typedef.

Un tip de date poate avea şi un alt nume, prin folosirea declaraţiei typedef. De exemplu:

typedef int intreg

Acelaşi efect se obţine cu directiva define:

#define intreg int

3.2.5. TIPURI ENUMERATE.

Folosirea tipului enumerare poate creşte claritatea programului, întrucât numele sunt mai
semnificative decât valorile care se ascund în spatele lor.
Astfel este mai naturală folosirea valorilor ROSU, ALB, NEGRU, VERDEpentru a desemna nişte
culori, decât a valorilor 0, 1, 2, 3.

Constantele simbolice pot fi introduse cu macrodefinitii #define.

#define FALSE 0

#define TRUE 1

Un tip enumerat foloseşte în locul valorilor tipului 0,1,2,… nume simbolice:

enum CULORI {ROSU, VERDE, GALBEN, ALBASTRU, NEGRU};

enum boolean {FALSE, TRUE};

Este posibil sa forţăm pentru numele simbolice alte valori întregi decât 0,1,2,…

enum ZILE {LUNI=1, MARTI, MIERC, JOI, VIN, SAMB, DUM};

scapes {BELL=’\a’,BACKSPACE=’\b’,TAB=’\t’,NEWLINE=’\n’, VTAB=’\v’,RETURN=’r’};

Folosind typedefputem defini tipuri enumerative:

typedef enum {ROSU, GALBEN, ALBASTRU} culori;

typedef enum { lu, ma, mi, jo, vi, si, du } zile;

3.2.6. TIPUL VID (void).

Tipul void precizează o mulţime vidă de valori. Acesta se foloseşte pentru a specifica tipul unei
funcţii care nu întoarce nici un rezultat, sau a unui pointer generic.

3.3. TIPURI DERIVATE.

Pe baza tipurilor fundamentale se pot construi tipuri derivate ca:

•..tablouri de obiecte de un anumit tip

•..functii care întorc obiecte de un anumit tip

•..pointeri la obiecte de un anumit tip

•..structuri care conţin obiecte de tipuri diferite

•..uniuni care conţin obiecte de tipuri diferite

3.4. DECLARAREA VARIABILELOR.

O variabilă constă din două componente: obiectul şi numele obiectului. Numele pot fi
identificatori sau expresii.

Definirea sau declararea unei variabile are forma:


clasă-memorie tip declaratori;

Clasa de memorie sau tipul pot fi omise. Declaratorii sunt identificatori.

Fiecare declarator poate fi urmat de un iniţializator, care specifică valoarea iniţială asociată

identificatorului declarator.

Asupra claselor de memorie vom reveni mai târziu, aşa că în exemplele curente le vom omite:

int i, j=0. char c;

float x=1.5, y;

enum CULORI s1, s2=ROSU; enum BOOLEAN p=TRUE; culori c1, c2;

zile z, d;

3.5. ECHIVALENŢA TIPURILOR.

Două tipuri se consideră echivalente în următoarele situaţii:

• echivalenţă structurală a tipurilor

• echivalenţa numelui tipului

Două obiecte sunt de tipuri structural echivalente, dacă au acelaşi tip de componente.

Două obiecte sunt de tipuri echivalente după nume, dacă au fost definite folosind acelaşi nume de

tip. Exemple:

typedef int integer;

int x, y; /* x şi y sunt echivalente dupa numele tipului*/

integer z;/* x şi z sunt de tipuri structural echivalente */

4. OPERATORI ŞI EXPRESII.

Un operator este un simbol care arată ce operaţii se execută asupra unor operanzi (termeni).

Un operand este o constantă, o variabilă, un nume de funcţie sau o subexpresie a cărei valoare este
prelucrată direct de operator sau suportă în prealabil o conversie de tip.

Operatorii, după numărul de operanzi asupra cărora se aplică pot fi: unari, binari şi ternari. În C există 45 de
operatori diferiţi dispuşi pe 15 niveluri de prioritate.

În funcţie de tipul operanzilor asupra cărora se aplică, operatorii pot fi: aritmetici, relaţionali,

binari, logici, etc.


Operatorii sunt împărţiţi în clase de precedenţă (sau de prioritate). În fiecare clasă de
precedenţă este stabilită o regulă de asociativitate, care indică ordinea de aplicare a operatorilor din clasa
respectivă: de la stânga la dreapta sau de la dreapta la stânga.

O expresie este o combinaţie de operanzi, separaţi între ei prin operatori; prin evaluarea unei expresii
se obţine o valoare rezultat.Tipul valorii rezultat depinde de tipul operanzilor şi a operatorilor folosiţi.

Evaluarea unei expresii poate avea efecte laterale, manifestate prin modificarea valorii unor variabile.

4.1. CONVERSII DE TIP.

Valorile pot fi convertite de la un tip la altul. Conversia poate fi implicită sau realizată în mod explicit
de către programator.

4.1.1. CONVERSII IMPLICITE DE TIP.

Conversiile implicite au loc atunci când este necesar ca operatorii şi argumentele funcţiilor să

corespundă cu valorile aşteptate pentru acestea.

Acestea pot fi sintetizate prin tabelul:

Tip Tip la care se converteşte implicit


char int, short int, long int
int char.. (cu trunchiere)

short int (cu trunchiere)


short int ca şi int
long int ca şi int
float long
double int (cu extensia semnului)

double float
int, short int, long int
int, short int, long int

4.1.2. CONVERSII ARITMETICE.

Când un operator binar se aplică între doi operanzi de tip diferit, are loc o conversie implicită a tipului
unuia dintre ei, şi anume, operandul de tip “mai restrâns” este convertit la tipul “mai larg” al celuilalt
operand. Astfel în expresia f + i, operandul inteste convertit în float.

Operatorii aritmetici convertesc automat operanzii la un anumit tip, dacă operanzii sunt de tip diferit.
Se aplică următoarele reguli:

• operanzii char şi short int se convertesc în int; operanzii float se convertesc în

double.

• dacă unul din operanzi este double restul operanzilor se convertesc în double iar rezultatul este tot
double.

• dacă unul din operanzi este long restul operanzilor se convertesc în long, iar rezultatul este tot
long.

• dacă unul din operanzi este unsigned restul operanzilor se convertesc în unsigned , iar rezultatul
este tot unsigned.

• dacă nu se aplică ultimele 3 reguli, atunci operanzii vor fi de tip int şi rezultatul de asemeni de tip int.

double.. ←float

long

unsigned

int.. ←char, short

Astfel n = c - ‘0’ în care c reprezintă un caracter cifră calculează valoarea întreagă a acestui
caracter.

Conversii implicite se produc şi în cazul operaţiei de atribuire, în sensul că valoarea din partea
dreaptă este convertită la tipul variabilei acceptoare din stânga.

Astfel pentru declaraţiile:

int i; float f; double d; char c;

sunt permise atribuirile:

i=f; /* cu trunchierea partii fractionare */

f=i; d=f; f=d; c=i; i=c;

4.1.3. CONVERSIILE DE TIP EXPLICITE (cast).

Conversiile explicite de tip (numite şi cast) pot fi forţate în orice expresie folosind un operator unar
(cast) într-o construcţie de forma:

(tip) expresie

în care expresia este convertită la tipul numit.

Operatorul cast are aceeaşi precedenţă cu a unui operator unar.

Astfel funcţia sqrt() din biblioteca <math.h> cere un argument double, deci va fi
apelată cu un cast: sqrt((double) n).

Apelurile cu argumente de alt tip vor fi convertite în mod automat la tipul double:

x=sqrt(2)va converti constanta 2în 2.0.


4.2. OPERATORII ARITMETICI.

Operatorii aritmetici binari sunt: +, -, *, / şi %(modul = restul impărţirii întregi). Prioritatea


operatorilor aritmetici este:

+, -.. unari

*, /, % binari

+, -.. binari

Regula de asociativitate este de la stânga la dreapta (la priorităţi egale operatorii sunt evaluaţi de la
stânga la dreapta).

Operatori multiplicativi

operator descriere tip operanzi tip rezultat precedenţă


* înmulţire aritmetic int,unsigned,lon 3
g,
/ împărţire aritmetic int,unsigned,lon 3
g,
double
% restul împărţirii întreg int,unsigned,lon 3
întregi g
double
Operatori aditivi

operator descriere tip operanzi tip rezultat precedenţă


+ adunare aritmetici int,unsigned,long,doub 4
pointer şi întreg le pointer
- scădere aritmetici int,unsigned,long,doub 4
pointer şi întreg le pointer
doi pointeri
int
4.3. OPERATORII DE ATRIBUIRE.

Operaţia de atribuire modifică valoarea asociată unei variabile (partea stângă) la valoarea unei
expresii (partea dreaptă). Valoarea transmisă din partea dreaptă este convertită implicit la tipul părţii stângi.

Atribuiri de forma: a = a op b se scriu mai compact a op= b în care op= poartă

numele de operator de atribuire, opputând fi un operator aritmetic (+, -,*,/,%) sau binar (>>,

<<, &, ^, |).

O atribuire multiplă are forma:

v1=v2=...=vn=expresie

şi este asociativă la dreapta.


O operaţie de atribuire terminată prin punct-virgulă (terminatorul de instrucţiune) se
transformă într-o instrucţiune de atribuire.

4.4. OPERATORII RELAŢIONALI.

Operatorii relaţionali sunt: >, >=, <, <=, care au toţi aceeaşi prioritate (precedenţă). Cu prioritate mai
mică sunt: ==, !=.

Operatorii relaţionali au prioritate mai mică decât operatorii aritmetici. Putem deci scrie

a < b -1în loc de a < (b -1)

Exemple: car >= ‘a’ && car <= ‘z’

Operatori relaţionali

operator descriere tip operanzi tip rezultat precedenţă


< mai mic aritmetic sau pointer int 6
> mai mare aritmetic sau pointer int 6
<= mai mic sau egal aritmetic sau pointer int 6
>= mai mare sau egal aritmetic sau pointer int 6
== egal aritmetic sau pointer int 7
!= neegal aritmetic sau pointer int 7

4.5. OPERATORII BOOLEENI.

Există următorii operatori logici:

! -NEGATIE (operator unar)

&& -ŞI logic.. (operatori binari)

|| -SAU logic

Exemple:

i<n-1 && (c=getchar()) != ‘\n’ && c != EOF

nu necesită paranteze suplimentare deoarece operatorii logici sunt mai puţin prioritari decât cei
relaţionali.

bisect= an % 4 == 0 && an % 100 != 0 || an % 400 == 0;

estecifra= c >= ’0’ && c <= ‘9’

Condiţia x == 0este echivalentă cu !x

x != 0 && y != 0 && z != 0este echivalentă cu x && y && z

Operatori booleeni
operator descriere Tip operanzi Tip precedenţă asociativitat
rezultat e
! negaţie aritmetic sau pointer int 2 DS
&& ŞI logic aritmetic sau pointer int 11 SD
|| SAU logic aritmetic sau pointer int 12 SD

4.6. OPERATORII BINARI (LA NIVEL DE BIŢI).

În C există 6 operaţii de manipulare a biţilor aplicate asupra unor operanzi întregi (char,
short, int, long) cu sau fără semn:

& -ŞI

| - SAU inclusiv

^ -SAU exclusiv

<< -deplasare stânga

>> -deplasare dreapta

~ -complement fată de 1 (inversare)

Operatorul ŞI se foloseste pentru operaţia de mascare a unor biţi.

n=n & 0177; /* pune pe 0 bitii din pozitia 8 in sus */

Operatorul SAU pune pe 1 biţii specificaţi printr-o mască:

x=x | MASCA; /* pune pe 1 bitii care sunt 1 in MASCA */

Deplasarea dreapta a unui întreg cu semn este aritmetică, iar a unui întreg fără semn este

logică.

De exemplu:

x |= 1 << 7; /* pune pe 1 bitul 0 din octetul x */

x &= ~(1 << 7); /* pune pe 0 bitul 0 din octetul x */

Operatori pe biţi

operator descriere Tip operand tip rezultat precedenţă


<< deplasare stânga Întreg ca operandul stâng 5
>> deplasare dreapta Întreg ca operandul stâng 5
& ŞI pe biţi Întreg int,long,unsigne 8
^ SAU exclusiv pe Întreg int,long,unsigne
d 9
| SAU
biţi inclusiv pe biţi Întreg int,long,unsigne
d 10
d
Decizia

if (a > b)

max = a;

else

max = b;

4.7. OPERATORUL CONDIŢIONAL.

poate fi reprezentată prin expresia condiţională:

max = a > b ? a : b

În general ex1 ? ex2 : ex3 determină evaluarea ex1; dacă aceasta nu este 0 atunci valoarea
expresiei condiţionale devine ex2, altfel ex3.

4.8. OPERATORUL SECVENŢĂ.

Este reprezentat prin ,şi se foloseste în situaţiile în care sintaxa impune prezenţa unei singure
expresii, dar prelucrarea presupune prezenţa şi evaluarea mai multor expresii. Exemplu:

a < b ? (t=a, a=b, b=t) : a

a) Operatorul sizeof.

4.9. OPERATORI UNARI

Aplicat asupra unei variabile furnizează numărul de octeţi necesari stocării variabilei
respective. Poate fi aplicat şi asupra unui tip sau asupra tipului unei expresii:

sizeof variabila sizeof tip

operator descriere tip operand tip rezultat precedenţă


sizeof necesar de memorie variabilă sau tip unsigned 2
sizeof expresie

sizeofeste un operator cu efect la compilare.

B) OPERATORII DE INCREMENTARE /DECREMENTARE.


operator descriere tip operand tip rezultat precedenţă
++ preincrementare aritmetic sau pointer int,long,double 2
,
++ postincrementare aritmetic sau pointer la fel 2
-- predecrementare aritmetic sau pointer la fel
unsigned,pointe 2
-- postdecrementar aritmetic sau pointer la
r fel 2
e

C) OPERATORI DE ADRESARE INDIRECTĂ / DETERMINARE ADRESĂ

& entitate- obţine adresa unei entităţi,

* pointer - pentru adresare indirectă - adică memorează adresa unei entităţi printr-o valoare a unui
pointer

operator descriere tip operand tip rezultat precedenţă


* indirectare pointer la T T 2
& adresare T pointer la T 2
~ negaţie aritmetic int,long,doubl 2
! negaţie logică aritmetic sau pointer int
e 2

D) OPERATORI DE ACCES :

- la elementele unui tablou

- la câmpurile unei structuri sau unei uniuni

- indirect prin intermediul unui pointer la câmpurile unei structuri sau unei uniuni

Operatorii de acces sunt:

•..[]indexare folosit în expresii de forma tablou[indice]

•... selecţie directă - pentru adresarea unui câmp dintr-o structură sau uniune sub forma:

struct.selector

•..→ selecţie indirectă - pentru accesul la un câmp dintr-o structură sau uniune, a cărei adresă este memorată
într-un pointer

pointer -> selector este echivalent cu (* pointer) . selector

Toţi aceşti operatori de acces, împreună cu operatorul de apel de funcţie () au cea mai
ridicată prioritate, şi anume 1.

Operatori de acces

operator descriere exemple precedenţă


() apel de funcţie sqrt(x),printf(“salut\n 1
”)
[] indexare tablou x[i], a[i][j] 1
. selector student.nastere.an 1
structură
-> selector pstud->nume 1
indirect
structură

Ordinea evaluarii operanzilor.

precedenţă operatori.......... simbol.................... asociativitate..

1........ apel funcţie / selecţie.. () [] . ->.... SD

4 aditivi + - SD
5 deplasări << >> SD
6 relaţionali < > <= >= SD
7 egalitate / neegalitate == != SD
8 ŞI pe biţi & SD
9 SAU exclusiv pe biţi ^ SD
10 SAU inclusiv pe biţi | SD
11 ŞI logic && SD
12 SAU logic || SD
13 condiţional ?: DS
14 atribuire = op= DS
15 virgula , SD
2........ unari............ * & - ! ~ ++ -- sizeof DS

3........ multiplicativi........ * / %...... SD

5. INSTRUCŢIUNI.

5.1. INSTRUCŢIUNEA EXPRESIE.

O instrucţiune expresie se obţine punând terminatorul de instrucţiune (punct-virgula) după o expresie:

expresie;

Exemple:

a++;

scanf(…);

max=a>b ? a : b;

Exemplul 1: Un număr real, introdus de la tastatură reprezintă măsura unui unghi exprimată în radiani.
Să se scrie un program pentru conversia unghiului în grade, minute şi secunde sexagesimale.

#include <stdio.h>

#define PI 3.14159265

void main(void){
float rad, gfr, mfr;

int g, m, s;

printf(“Introduceti numarul de radiani: “);

scanf("%f", &rad);

g=gfr=rad*180/PI;

m=mfr=(gfr-g)*60;

s=(mfr-m)*60;

printf("%5.2f radiani=%4d grade %02d min %02d sec\n",

rad, g, m, s);

5.2. INSTRUCŢIUNEA COMPUSĂ (BLOCUL).

Forma generală:

declaratii_si_definitii;

instructiuni;

Se foloseşte în situaţiile în care sintaxa impune o singură instrucţiune, dar codificarea impune prezenţa
unei secvenţe de instrucţiuni. Blocul de instrucţiuni conteaza ca o singură instrucţiune.

5.3. Instrucţiunea vidă.

Forma generală:..;

Sintaxa impune prezenţa unei instrucţiuni, dar logica problemei nu necesită nici o prelucrare. In
acest mod se introduc unele relaxări în sintaxă.

5.4. Instrucţiunea if.

Forma generală:

if (expresie)

instructiune1;

else

instructiune2;
Se evaluează expresia; dacă este diferită de 0 se execută instrucţiune1 altfel
instrucţiune2

O formă simplificată are instrucţiune2 vidă:

if (expresie)

instructiune;

În problemele de clasificare se întâlnesc decizii de forma:

if (expr1)

instr1;

else if (expr2)

instr2;

else

instrn;

De exemplu dorim să contorizăm caracterele citite pe categorii: litere mari, litere mici, cifre, linii şi
altele:

if (c == ‘\n’)

linii++;

else if (c>=’a’ && c<=’z’)

lmici++;

else if (c>=’A’ && c<=’Z’)

lmari++;

else if (c>=’0’ && c<=’9’)

cifre++;

else

altele++;

Exemplul 2 Să se scrie un program pentru rezolvarea cu discuţie a ecuaţiei de grad 2: ax2+bx+c=0


folosind operatorul condiţional.

#include <stdio.h>

#include <math.h>
void main(void){

float a, b, c, d;

printf(“Introduceti coeficientii ecuatiei: a,b,c\n”);

scanf("%f %f %f", &a,&b,&c);

a? d=b*b-4*a*c, d>=0? printf("x1=%f\tx2=%f\n",(-b- sqrt(d))/2/a,

............(-b+sqrt(d))/2/a):

........ printf("x1=%f+i*%f\tx2=%f-i*%f\n",-b/2/a,

............ sqrt(-d)/2/a,-b/2/a, sqrt(-d)/2/a)):

b? printf("x=%f\n",-b/2/a): c? printf("0 solutii\n"):

..............printf("identitate\n");

Exemplul 3: Data curentă se exprimă prin an, luna şi zi. Să se scrie un program care determină data
zilei de mâine.

#include <stdio.h>

int bisect(int a){

return a%4==0 && a%100!=0 || a%400==0;

int ultima(int a, int l){

if (l==2)

return (28+bisect(a));

else if (l==4||l==6||l==9||l==11)

return 30;

else

return 31;

void main()

{int a, l, z;

printf(“Introduceti data curenta: an,luna,zi\n”);


scanf("%d%d%d",&a,&l,&z);

printf("azi: zi:%02d luna:%02d an:%4d\n", z,l,a);

if (z < ultima(a,l))

z++;

else

{z=1;

if (l < 12)

.. l++;

else

.. {l=1;

.. a++;

.. }

printf("maine: zi:%02d luna:%02d, an:%4d\n", z,l,a);

5.5. Instrucţiunea switch.

Criteriul de selecţie într-o problemă de clasificare îl poate constitui un selector care ia valori întregi.

Forma generală:

switch (expresie){

case val1: secventa1;

case val2: secventa2;

. . .

default: secventa s;

Se evaluează expresia selectoare; dacă valoarea ei este egală cu una din constantele cazurilor, se alege
secvenţa de prelucrare corespunzătoare, după care se continuă cu secvenţele de prelucrare ale cazurilor
următoare.

Dacă valoarea expresiei selectoare nu este egală cu nici una din constantele cazurilor, se alege secvenţa
corespunzătoare etichetei default.
Pentru ca prelucrările corespunzătoare cazurilor să fie disjuncte se termină fiecare secventă de
prelucrare prin break. De exemplu:

y=x;

switch (n)

{ case 5: y*=x;

case 4: y*=x;

case 3: y*=x;

case 2: y*=x;

calculează xn, unde n ia valori de la 1 la 5.

Exemplul 4 Scrieti o functie pentru determinarea ultimei zile din lună.

int ultima(int a, int l)

{ switch (l) {

..case 1: case 3: case 5: case 7:

..case 8: case 10: case 12: return 31;

..case 4: case 6: case 9: case 11: return 30;

..case 2: return (28 + bisect(a));

5.6. Instrucţiunea while.

Este ciclul cu test iniţial; se repetă instrucţiunea componentă cât timp expresia are valoarea adevărat
(diferit de 0).

while (expresie)

instructiune;

Exemplu 5:Copiaţi fişierul standard de intrare stdin la ieşirea standard stdout

/*copierea intrarii la iesire*/

{ int c;

c = getchar();

while (c != EOF)
{ putchar(c);

c = getchar();

/* varianta simplificata */

{ int c;

while ((c=getchar()) != EOF)

putchar(c);

Exemplul 6: Să se calculeze cel mai mare divizor comun şi cel mai mic multiplu comun a 2 numere folosind
algoritmul lui Euclid cu scăderi. (cât timp numerele diferă se înlocuieşte cel mai mare dintre ele prin
diferenţa lor).

#include <stdio.h>

void main(void){

unsigned long a, b, ca, cb;

printf(“Introduceti cele doua numere\n”);

scanf("%lu %lu", &a, &b);

ca=a; cb=b;

while (a!=b)

if(a > b)

.. a-=b;

else

.. b-=a;

printf("cmmdc(%lu,%lu)=%lu\ncmmmc(%lu,%lu)=%lu\n",

ca,cb,a,ca,cb,ca*cb/a);

5.7. Instrucţiunea do…while.

Reprezintă ciclul cu test final;repetarea instrucţiunii are loc cât timp expresia este diferită de 0.

do

instructiune;
while (expresie);

Corpul buclei este format dintr-o singură instrucţiune. Repetarea se face cel puţin o dată.

/* citirea unui raspuns */

{ char opt;

printf(“Continuam ? D / N”);

do

scanf(“%c”, &opt);

while (opt == ‘D’ || opt == ‘d’);

Exemplul 7: Să se stabilească dacă un număr este sau nu palindrom (are aceeaşi reprezentare citit de la
stânga sau de la dreapta).

#include <stdio.h>

void main(void){

unsigned long n, c, r=0;

scanf("%lu", &n);

c=n;

do{ r=10*r+n%10;

.. n/=10;

}while (n);

printf("%lu %s este palindrom\n",c, (c==r)? “”:”nu”);

5.8. Instrucţiunea for.

Reprezintă o altă formă a ciclului cu test iniţial.

for (exp_init; exp_test; exp_modif)

instructiune;

este echivalentă cu:

exp_init;

while (exp_test){

instructiune;
exp_modif;

Exemplul 8: Să se stabilească dacă un număr întreg n este sau nu prim.

Vom încerca toţi divizorii posibili (de la 2 la n). Dacă nu găsim nici un divizor, numărul este prim.
Continuarea ciclului pentru testarea posibililor divizori este determinată de două condiţii:

- să mai existe divizori netestaţi


- candidaţii deja testaţi să nu fi fost divizori.

La ieşirea din ciclu se determină motivul pentru care s- a părăsit ciclul:

- s-au testat toţi candidaţii şi nu s-a găsit nici un divizor, deci numărul este prim
- un candidat a fost găsit divizor, deci numărul este neprim.

Ciclul de testare a candidaţilor are forma:

for (d=2; d*d <= n && n % d != 0; d++)

Programul poate fi îmbunătăţit prin evitarea testării candidaţilor pari (cu excepţia lui 2).

#include <stdio.h>

void main(void) {

unsigned long n, d;

scanf(“%lu”,&n);

for(d=2; d*d <= n && n%d; (d=2)? d=3: d+=2)

printf(“numarul %lu este %sprim\n”,n,!(n%d)? “ne”:””);

5.9. Instrucţiunea continue.

Plasarea acestei instrucţiuni în corpul unui ciclu are ca efect terminarea iteraţiei curente şi trecerea la
iteraţia următoare.

continue;

Exemplul 9: O secvenţă de numere întregi este terminată prin 0. Să se calculeze suma termenilor pozitivi din
secvenţă.

#include <stdio.h>

void main(void) {
int n, suma;

for(suma=0,scanf(“%d”,&n); n; scanf(“%d”,&n){

if(n < 0) continue;

suma += n;

printf(“suma pozitivi = %d\n”, suma);

5.10. Instrucţiunea break.

Are ca efect ieşirea dintr-o instrucţiune de ciclare sau dintr-o instrucţiune switch, pentru a face
alternativele disjuncte (în caz contrar dintr-o alternativă se trece în următoarea). Permite implementarea
unor cicluri cu mai multe ieşiri plasate oriunde în interiorul ciclului.

O structură repetitivă foarte generală cu mai multe ieşiri plasate oriunde este:

while (1) {

. . .

if(expresie1) break;

. . .

if(expresien) break;

. . .

5.11. Instrucţiunea goto.

Realizează saltul la o etichetă. Este o instrucţiune nestructurată şi se evită.

goto eticheta;

5.12. Instrucţiunea return.

Orice funcţie nedeclarată void va trebui să întoarcă un rezultat. Tipul acestui rezultat este specificat în
antetul funcţiei. Transmiterea acestui rezultat este realizată de o instrucţiune return inclusă în corpul
funcţiei.

return expresie;

Exemplul 10: Calculul factorialului.

long factorial( int p)


{ int i;

long f;

for ( f = 1,i=2; i <= n; i++)

.. f *= i;

return f;

Dacă expresia întoarsă este de alt tip decât cel al funcţiei, atunci se face conversia la tipul funcţiei.

6. Funcţii.(1)
Apelarea funcţiilor.

In C noţiunea de funcţie este esenţială, deoarece asigură un mecanism de abstractizare a


controlului: rezolvarea unei părţi a problemei poate fi încredinţată unei funcţii, moment în care suntem
preocupaţi de ce face funcţia, fără a intra în detalii privind cum face funcţia anumite operaţii. Însăşi
programul principal este o funcţie cu numele main(), iar programul C este reprezentat de o mulţime de
definiri de variabile şi de funcţii.

Funcţiile pot fi clasificate în:

 funcţii care întorc un rezultat


 funcţii care nu întorc nici un rezultat (similare procedurilor din Pascal).

Apelul (referirea sau utilizarea) unei funcţii se face prin:

nume_funcţie (listă_parametri_efectivi)

Acesta poate apare ca o instrucţiune, în cazul funcţiilor care nu întorc un rezultat:

nume_funcţie (listă_parametri_efectivi);

De exemplu:

printf(“x=%5.2lf\n”,x);

mesaj();

Pentru funcţiile care întorc un rezultat apelul de funcţie poate apare ca operand într-o expresie.

De exemplu:

y=sin(x);

nr_zile=bisect(an)+365;

Se remarcă faptul că lista de argumente (sau de parametri efectivi) poate fi vidă.

Funcţiile comunică între ele prin lista de argumente şi prin valorile întoarse de funcţii. Comunicarea
poate fi realizată şi prin variabilele externe, definite în afara tuturor funcţiilor.
Exemplul 11: O fracţie este cunoscută prin numărătorul x şi numitorul y, valori întregi fără semn. Să
se simplifice această fracţie.

Simplificarea se va face prin cel mai mare divizor comun al numerelor x şi y. Vom utiliza o funcţie
având ca parametri cele două numere, care întoarce ca rezultat , cel mai mare divizor comun a lor. Funcţia
main() apelează funcţia cmmdc() transmiţându-i ca argumente pe x şi y. Funcţia cmmdc() întoarce ca
rezultat funcţiei main(), valoarea celui mai mare divizor comun. Programul, în care vom ignora
deocamdată definirea funcţiei cmmdc(), este:

#include <stdio.h>

void main(void)

{ unsigned long x, y, z;

scanf(“%lu%lu”, &x, &y);

printf(“%lu / %lu =”, x, y);

z=cmmdc(x,y);

x/=z;

y/=z;

printf(“%lu / %lu\n”, x, y);

Definiţii de funcţii.

În utilizarea curentă, o funcţie trebuie să fie definită înainte de a fi apelată. Aceasta impune o
definire a funcţiilor programului în ordinea sortării topologice a acestora: astfel mai întâi se vor defini
funcţiile care nu apelează alte funcţii, apoi funcţiile care apelează funcţii deja definite. Este posibil să
eliminăm această restricţie, lucru pe care îl vom trata ulterior.

O funcţie se defineşte prin antetul şi corpul funcţiei.

Funcţiile nu pot fi incluse unele în altele; toate funcţiile se declară pe acelaşi nivel cu funcţia main.

In versiunile mai vechi ale limbajului, în antetul funcţiei parametrii sunt numai enumeraţi, urmând a
fi declaraţi ulterior. Lista de parametri, în acest caz, este o enumerare de identificatori separaţi prin virgule.

tip_rezultat nume_functie( lista_de_parametri_formali )

{ declarare_parametri_formali;

alte_declaratii;

instructiuni;

}
In mod uzual parametrii funcţiei se declară în antetul acesteia. Declararea parametrilor se face
printr-o listă de declaraţii de parametri, cu elementele separate prin virgule.

tip_rezultat nume_functie( tip nume, tip nume,... )

{ declaratii;

instructiuni;

O funcţie este vizibilă din locul în care a fost declarată spre sfârşitul fişierului sursă (adică definiţia
funcţiei precede apelul).

Definirea funcţiei cmmdc() se face folosind algoritmul lui Euclid.

unsigned long cmmdc(unsigned long u, unsigned long v)

{ unsigned long r;

do {r=u%v;

.. u=v;

.. v=r;

} while (r);

return u;

In cazul în care apelul funcţiei precede definiţia, trebuie dat, la începutul textului sursă, un prototip
al funcţiei, care să anunţe că definiţia funcţiei va urma şi să furnizeze tipul rezultatului returnat de funcţie şi
tipul parametrilor, pentru a permite compilatorului să facă verificările necesare.

Prototipul unei funcţii are un format asemănător antetului funcţiei şi serveşte pentru a informa
compilatorul asupra:

 tipului valorii furnizate de funcţie;


 existenţa şi tipurile parametrilor funcţiei

Spre deosebire de un antet de funcţie, un prototip se termină prin ;

tip nume( lista tipurilor parametrilor formali);

Din prototip interesează numai tipurile parametrilor, nu şi numele acestora, motiv pentru care aceste
nume pot fi omise.

void f(void); /*functie fara parametri care nu intoarce nici un


rezultat*/

int g(int x, long y[], double z);


int g(int, long[], double); /*aici s-au omis numele parametrilor*/

Dintre toate funcţiile prezente într-un program C prima funcţie lansată în execuţie este main(),
independent de poziţia pe care o ocupă în program.

Apelul unei funcţii g(), lansat din altă funcţie f() reprezintă un transfer al controlului din funcţia
f(), din punctul în care a fost lansat apelul, în funcţia g(). După terminarea funcţiei g() sau la întâlnirea
instrucţiunii return se revine în funcţia f() în punctul care urmează apelului g(). Pentru continuarea
calculelor în f(), la revenirea din g() este necesară salvarea stării variabilelor (contextului) din f() în
momentul transferului controlului. La revenire în f(), contextul memorat a lui f() va fi refăcut.

O funcţie apelată poate, la rândul ei, să apeleze altă funcţie; nu există nici o limitare privind numărul
de apeluri înlănţuite.

Comunicarea între funcţii prin variabile externe.

Efecte laterale ale funcţiilor.

Comunicarea între funcţii se poate face prin variabile externe tuturor funcţiilor; acestea îşi pot
prelua date şi pot depune rezultate în variabile externe. În exemplul cu simplificarea fracţiei vom folosi
variabilele externe a, b şi c . Funcţia cmmdc() calculează divizorul comun maxim dintre a şi b şi
depune rezultatul în c. Întrucât nu transmite date prin lista de parametri şi nu întoarce vreun rezultat,
funcţia va avea prototipul void cmmdc():

#include <stdio.h>

unsigned long a, b, c; // variabile externe

// definirea functiei cmmdc()

void main(void)

{ scanf(“%lu%lu”, &a, &b);

printf(“%lu / %lu = “, a, b);

cmmdc();

a/=c;

b/=c;

printf(“%lu / %lu\n”, a, b);

Definiţia funcţiei cmmdc()din Exemplul 11, este:

void cmmdc()

{ unsigned long r;

do {
..r=a%b;

..a=b;

..b=r;

} while (r);

c=a;

Dacă se execută acest program, se constată un rezultat ciudat, şi anume, orice fracţie, prin
simplificare ar fi adusă la forma 1/0 ! Explicaţia constă în faptul că funcţia cmmdc() prezintă efecte
laterale, şi anume modifică valorile variabilelor externe a, b şi c; la ieşirea din funcţie a==c şi b==0,
ceeace explică rezultatul.

Aşadar, un efect lateral (sau secundar),reprezintă modificarea de către funcţie a unor variabile
externe.

În multe situaţii aceste efecte sunt nedorite, duc la apariţia unor erori greu de localizat, făcând
programele neclare, greu de urmărit, cu rezultate dependente de ordinea în care se aplică funcţiile care
prezintă efecte secundare. Astfel într-o expresie în care termenii sunt apeluri de funcţii, comutarea a doi
termeni ar putea conduce la rezultate diferite!

Vom corecta rezultatul, limitând efectele laterale prin interzicerea modificării variabilelor externe a
şi b, ceea ce presupune modificarea unor copii ale lor în funcţia cmmdc()sau din programul de apelare

void cmmdc(void)

{ unsigned long r, ca, cb;

ca=a;

cb=b;

do{

..r=ca%cb;

..ca=cb;

..cb=r;

} while (r);

c=ca;

Singurul efect lateral permis în acest caz – modificarea lui c asigură transmiterea rezultatului către
funcţia apelantă.

Soluţia mai naturală şi mai puţin expusă erorilor se obţine realizând comunicaţia între funcţia
cmmdc() şi funcţia main() nu prin variabile externe, ci prin parametri, folosind o funcţie care întoarce ca
rezultat cmmdc.
Transmiterea parametrilor prin valoare, mecanism specific limbajului C, asigură păstrarea intactă a
parametrilor actuali x şi y, deşi parametrii formali corespunzători: u şi v se modifică! Parametrii actuali x şi
y sunt copiaţi în variabilele u şi v, astfel încât se modifică copiile lor nu şi x şi y.

In fişierul sursă funcţiile pot fi definite în orice ordine.Mai mult, programul se poate întinde în mai
multe fişiere sursă. Definirea unei funcţii nu poate fi totuşi partajată în mai multe fişiere.

O funcţie poate fi apelată într-un punct al fişierului sursă, dacă în prealabil a fost definită în acelaşi
fişier sursă, sau a fost anunţată.

Exemplul 12:

#include <stdio.h>

#include <conio.h>

unsigned long fact(unsigned char); // prototipul anunta functia

void main()

{ printf("5!=%ld\n", fact(5)); // apel functie

printf("10!=%ld\n", fact(10));

getch();

long fact(unsigned char n) // antet functie

{ long f=1; .. // corp functie

short i;

for (i=2; i<=n; i++)

f*=i;

return(f);

Tipurile funcţiilor pot fi:

 tipuri predefinite
 tipuri pointer
 tipul structură (înregistrare)

6.4. Funcţii care apelează alte funcţii.

Programul principal (funcţia main()) apelează alte funcţii, care la rândul lor pot apela alte funcţii.
Ordinea definiţiilor funcţiilor poate fi arbitrară, dacă se declară la începutul programului prototipurile
funcţiilor. In caz contrar definiţiile se dau într-o ordine în care nu sunt precedate de apeluri ale funcţiilor.
Exemplul 13: Pentru o valoare întreagă şi pozitivă n dată, să se genereze triunghiul lui Pascal, adică
combinările:

C00

C10 C11

. . .

Cn0 Cn1 . . . Cnn

Combinările vor fi calculate utilizând formula cu factoriale: Cnp=n!/p!/(n-p)!

#include <stdio.h>

unsigned long fact(int);../*prototip functie factorial*/

unsigned long comb(int,int); /*prototip functie combinari*/

void main(void).... /*antet functie main*/

{ int k,j,n;

unsigned long C;

scanf(“%d”,&n);

for (k=0;k<=n;k++)

{ for (j=0;j<=k;j++)

.. printf(“%6lu “, comb(k,j));

.. printf(“\n”);

unsigned long comb(int n, int p) /*antet functie comb*/

{ return (fact(n)/fact(p)/fact(n-p));

/* functia fact a mai fost definita */

6.5. Programe cu mai multe fişiere sursă.

Pentru programele mari este mai comod ca acestea să fie constituite din mai multe fişiere sursă, întrucât
programul este conceput de mai mulţi programatori (echipă) şi fiecare funcţie poate constitui un fişier
sursă, uşurându-se în acest mod testarea. Reamintim că o funcţie nu poate fi împărţită între mai multe
fişiere.

Un exemplu de program constituit din 2 fişiere sursă este:


/* primul fisier Fis1.c */

void F(); /* prototipul functiei definite in fisierul 2*/

#include <stdio.h>

void main(){

F(); /* apelul functiei F */

...

/* al doilea fisier Fis2.c */

#include <stdio.h>

void F(){

...

Pentru execuţia unui program rezident în mai multe fişiere se crează un proiect.

 se selectează opţiunea PROJECT


 se selectează OPEN PROJECT
 se introduce numele proiectului (creindu-se fişierul nume.prj)
 se adaugă pe rând numele fişierelor care compun proiectul cu ADD ITEM
 se execută proiectul din meniul RUN

6.6. Fişiere antet.

In C se pot utiliza o serie de funcţii aflate în bibliotecile standard. Apelul unei funcţii de bibliotecă
impune prezenţa prototipului funcţiei în textul sursă. Pentru a simplifica inserarea în textul sursă a
prototipurilor funcţiilor de bibliotecă, s-au construit fişiere de prototipuri. Acestea au extensia .h (header
sau antet).

De exemplu fişierul stdio.h conţine prototipuri pentru funcţiile de bibliotecă utilizate în operaţiile de intrare
/ ieşire; fişierul string.h conţine prototipuri pentru funcţiile utilizate în prelucrarea şirurilor de caractere.

Includerea în textul sursă a unui fişier antet se face folosind directiva #include

Fişier antet Funcţii conţinute


stdio.h printf, scanf, gets, puts,...
conio.h putch, getch, getche, ...
math.h sqrt, sin, cos, ...

6.7. Funcţii matematice uzuale.


Fişierul antet <math.h> conţine semnăturile (prototipurile) unor funcţii matematice des folosite.
Dintre acestea amintim:

Notaţie Semnătură (prototip) Operaţie realizată


sin(x) double sin(double);
cos(x) double cos(double);
tg(x) double tan(double); funcţii trigonometrice directe
arcsin(x double asin(double);
)
arcos(x) double acos(double); funcţii trigonometrice inverse
arctg(x) double atan(double);
arctg(y/ double atan2(double, double);
x)
sinh(x) double sinh(double);
cosh(x) double cosh(double);
th(x) double tanh(double); funcţii hiperbolice
exp(x) double exp(double); exponenţială naturală
10n double pow10(int); exponenţială zecimală
ab double pow(double, double); exponenţială generală
ln(x) double log(double); logaritm natural
lg(x) double log10(double); logaritm zecimal
double fabs(double);
int abs(int);
|x| long labs(long); valoare absolută
x double sqrt(double); rădăcină pătrată
long double sqrtl(long
double);
x double ceil(double); întregul minim >= x
x double floor(double); întregul maxim <= x
double atof(const char *); conversie şir de caractere în float
long double atold (const char conversie şir de caractere în long
conversi *); double
i
7. Variabile.

7. 1. Variabile externe (globale) şi variabile interne (locale).

Un program C este un ansamblu de obiecte externe: variabile şi funcţii.

O variabilă externă este o variabilă definită în afara oricărei funcţii, fiind astfel accesibilă din orice
funcţie. Am văzut că funcţiile pot comunica între ele prin variabile externe sau globale.Orice funcţie poate
accesa o variabilă externă, referindu-se la aceasta prin numele ei, dacă acest nume a fost declarat.

Prin urmare, variabilele externe au ca domeniu (sunt văzute şi pot fi accesate din) întregul fişier sursă în
care sunt definite.

Durata de viaţă a unei variabile externe (adică intervalul în care memoria este alocată) coincide cu
durata în care programul C este activ. Variabilele externe sunt permanente: ele reţin valori între apelurile
diferitelor funcţii.

Spre deosebire de variabilele externe, variabilele interne (numite şi automatice sau locale) au un
domeniu mult mai restrâns - ele sunt cunoscute numai în interiorul funcţiei în care sunt definite.

Durata de viaţă a variabilelor interne este mult mai scurtă ca a variabilelor externe – alocarea de
memorie se face la intrarea în funcţie, pentru ca la părăsirea funcţiei, memoria ocupată de variabilă să fie
eliberată.

Între ele, funcţiile sunt întotdeauna externe, pentru că nu pot fi definite în interiorul altor funcţii (ca în
Pascal!).

7.2. Domenii de vizibilitate ale variabilelor.

Funcţiile şi variabilele externe din care este alcătuit programul nu trebuie să fie compilate în acelaşi
timp; textul programului sursă poate fi separat în mai multe fişiere.

Domeniul unui nume reprezintă partea din program în care numele poate fi folosit.

Astfel pentru o variabilă locală, definită la începutul unei funcţii, domeniul îl reprezintă acea funcţie.
Variabilele locale cu acelaşi nume din funcţii diferite sunt entităţi diferite. Acelaşi lucru este valabil şi pentru
parametrii formali ai funcţiilor, care sunt de fapt variabile locale.

Domeniul unei variabile externe, ca şi a unei funcţii se întinde din punctul în care a fost declarată şi
până la sfârşitul fişierului sursă.

O variabilă externă poate fi referită şi dintr-un alt fişier sursă al programului, dacă este declarată în
acest fişier cu atributul extern.

O asemenea declaraţie anunţă proprietăţile variabilei (tipul), fără a aloca memorie pentru variabilă.

De exemplu: extern int x;

Alocarea de memorie se face o singură dată şi anume în fişierul sursă în care variabila externă este
definită. Tot în acel loc se face şi iniţializarea variabilei externe.

Intr-o funcţie sunt vizibili:


 parametrii formali
 variabilele locale
 variabilele globale locale modulului care declară funcţia ( variabilele declarate în afara funcţiei în aceeaşi
unitate de compilare;
 variabilele globale exportate de alte module şi importate de modulul care declară funcţia.

Orice definiţie este şi o declaraţie, dar reciproca nu este adevărată. Astfel prototipul unei funcţii este o
declaraţie.

7.3. Clase de memorare.

Orice variabilă are 4 atribute:

1. Clasa de memorare - reprezintă locul în care este memorată variabila:

 într-un segment de date


 într-un registru
 în stivă
 în heap

2. Vizibilitatea - reprezinta liniile textului sursă din care variabila poate fi accesată

 la nivel de bloc - variabila este cunoscuta într-un bloc, din momentul declarării până la sfârşitul blocului
 la nivel de fisier - variabila este cunoscuta într-un fisier.

3. Durata de viaţă - reprezinta timpul în care variabila are alocat spaţiu în memoria internă.

 durată statică - variabila are alocat spaţiu pe tot timpul execuţiei programului. Variabilele statice sunt
memorate într-un segment de date si sunt în mod automat iniţializate cu 0.
 durată locală - variabila are alocat spaţiu cat timp se execută instrucţiunile unui bloc. Alocarea de spaţiu se
face în stivă sau într-un registru.
 durată dinamică - variabila este alocată şi dezalocată în timpul execuţiei programului prin funcţii speciale.
Alocarea de spaţiu se face în heap.

4. Tipul variabilei - determina numărul de octeţi rezervaţi pentru variabilă.

Atributele unei variabile se stabilesc:

 implicit - în locul unde se face declaraţia


 explicit - prin anumiţi calificatori.

Variabilele pot clasificate în:

 variabile globale
 variabile locale.

Variabilele globale:

 se definesc în afara corpurilor funcţiilor


 li se rezerva spaţiu într-un segment de date, înainte de execuţia programului
 au durata de viata statica
 sunt iniţializate implicit
 au vizibilitate la nivel de fisier
 vizibilitatea lor poate fi extinsa la nivel de program

O variabila globala poate fi accesata dintr-un fisier care nu conţine definiţia variabilei, dar trebuie sa
conţina o declaraţie a variabilei, care conţine numele variabilei precedat de specificatorul extern.

/* FISIER1.C */

int x=5; /* aici se defineste variabila x */

void F(); /* prototipul functiei F, definita in FISIER2.C */

void main(){

F(); /* apelul functiei F */

/* FISIER2.C */

#include <stdio.h>

extern a; /* declararea variabilei definite in alt fisier */

void F() /* aici se defineste functia F */

...

Dacă declaraţia unei variabile este facută înafara corpului funcţiei, ea este vizibilă din punctul declaraţiei
până la sfârşitul fişierului.

Dacă declaraţia variabilei se face în corpul unei funcţii, ea este vizibilă din locul declarării, până la
sfârşitul blocului.

7.3.1. Variabile şi funcţii statice.

Declararea unei variabile externe sau funcţii cu atributul static limitează domeniul obiectului la
fişierul sursă compilat. Folosirea cuvântului extern nu asigură, în acest caz accesul din alt fişier la obiectul
declarat static.

Variabilele interne pot fi şi ele declarate statice. Prin aceasta ele vor rămâne alocate şi după ieşirea din
funcţie, asigurând memorarea de valori între apeluri ale funcţiei. Variabilele interne statice :

 au durata de viaţă din momentul definiţiei până la sfârşitul execuţiei programului


 sunt memorate într-un segment de date
 sunt iniţializate automat
 definiţia lor începe cu cuvântul static

#include <stdio.h>
static double a;

void main(){

++a;

printf(“a=%.2lf”, a);

Variabila a este locală (fără specificatorul static ar fi fost globala). a este iniţializata la 0, astfel că
în primul apel se va afişa 1; următorul apel preia valoarea existentă (1), pe care o incrementează, deci
afişează 2, ş.a.m.d. Vizibilitatea este la nivelul fişierului (modulului) şi nu se poate extinde la nivelul
programului.

#include <stdio.h>

void increm(void){

static int a;

printf(“%d\n”, ++a);

void main(void){

int k;

for (k=0; k<5; k++)

increm();

Exemplul 14: Definiţi o funcţie pentru generarea de numere întregi aleatoare.

Un generator congruenţial de numere aleatoare este definit printr-o relaţie de forma:

xn+1 = (a*xn + b) % c,

în care x0 poartă numele de sămânţa (seed) generatorului. a, b şi x0 sunt numere prime, care asigură o
distribuţie aleatoare a numerelor în secvenţa {xn}. c dă domeniul în care se generează numerele
aleatoare şi se alege, de obicei, tot un număr prim. Funcţia generator de numere aleatoare calculează cu
relaţia de mai sus pe xn+1 şi-l întoarce ca rezultat. Valoarea xn trebuie să provină din apelul precedent al
funcţiei, aşa că va fi declarată ca variabilă statică, iniţializată cu valoarea sămânţei x0. Iniţializarea va fi
făcută numai la primul apel al funcţiei, la fiecare din apelurile următoare se preia valoarea rămasă din
apelul precedent. Se remarcă faptul că se generează întotdeauna aceeaşi secvenţă de numere aleatoare!
Pentru a genera de fiecare dată o altă secvenţă aleatoare, sămânţa ar trebui generată aleator.

Există două funcţii de bibliotecă:

int rand(void); - care întoarce (dacă este folosită singură), o aceeaşi secvenţă de numere
pseudoaleatoare
în domeniul 0 : RAND_MAX

void srand(unsigned s); - care stabileşte valoarea sămânţei (x0)

unsigned aleator(unsigned long c) {

static unsigned long x = 113;

unsigned long a = 121369, b = 179953;

x = (a*x + b) % c;

return x;

7.3.2. Variabile regiştri.

Pentru variabilele folosite în mod frecvent, pentru creşterea eficienţei programului, se doreşte ca
alocarea să se facă în regiştrii maşinii. În acest scop variabilele se declară cu atributul register.

De exemplu:

register int a;

register char c;

Pot fi declarate ca variabile regiştri numai variabilele automatice. Numai puţine tipuri de variabile (care
ocupă puţină memorie) permit declararea ca variabile regiştri (de exemplu tipurile char şi int dar nu
double!).

Întrucât numărul de regiştri maşină este limitat, declararea prea multor variabile de acest tip determină ca,
de la un anumit moment să se ignore această declaraţie, şi anume, după alocarea regiştrilor disponibili,.

Variabilelor alocate în regiştri nu poate să le fie aplicat operatorul de adresare.

Variabile definite în interiorul blocurilor.

O variabilă definită în interiorul unei instrucţiuni compuse ascunde o variabilă cu acelaşi nume definită
într-un bloc exterior şi durează până la terminarea blocului.

O variabilă definită şi iniţializată într-un bloc, va fi reiniţializată la fiecare intrare în acel bloc.

O variabilă statică, definită şi iniţializată într-un bloc, va fi iniţializată o singură dată, la prima intrare în
acel bloc.

7.3.3. Iniţializări.

În absenţa unei iniţializări explicite, variabilele externe şi cele statice sunt iniţializate la 0, în timp ce
variabilele interne şi regiştri rămân neiniţializate.

În cazul unei iniţializări explicite, iniţializatorul unei variabile externe sau statice poate fi o expresie
constantă, în timp ce pentru variabile interne sau regiştri putem avea orice expresie, conţinând chiar
apeluri de funcţii.
8. Tablouri şi pointeri.

8.1. Tablouri cu o dimensiune (vectori).

Un tablou cu o singură dimensiune este o succesiune de variabile având toate de acelaşi tip (tipul de
bază al tabloului), care ocupă o zonă contiguă de memorie.

Un tablou are:

 o dimensiune (egală cu numărul de elemente al tabloului)


 un nume (care identifică global tabloul)
 o clasă de alocare
 un tip comun tuturor elementelor tabloului

Dimensiunea tabloului precizează numărul de elemente printr-o constantă întreagă sau printr-o expresie
constantă.

La declararea unui tablou se specifică: numele, tipul de bază, clasa de alocare şi dimensiunea.

tip nume[dimensiune];

sau

clasă tip nume[dimensiune];

Exemple:

int x[10];.. /* tablou de 10 intregi */

char litere[2*26]; /* tablou de 52 caractere */

Tipul elementelor tabloului poate fi un tip fundamental, enumerat, inregistrare, pointer sau un tip definit.

Numele tabloului este adresa primului element din tablou (de exemplu x este adresa primului
element, adică @x[0] ). Aceasta explică de ce nu este permisă o atribuire între două tablouri.

Accesul la un element din tablou se face printr-o variabilă indexată, formată din numele tabloului şi
un index - o expresie cuprinsă între 0 şi dimensiune-1.

Primul element va fi desemnat aşadar prin x[0], al doilea element prin x[1],al N-lea prin x[N-1]

Un tablou declarat în interiorul unei funcţii are implicit clasa auto, în timp ce tablourile declarate în
exteriorul tuturor funcţiilor au în mod implicit clasa extern.

Un tablou declarat în exteriorul tuturor funcţiilor cu specificatorul static este alocat la adrese fixe,
fiind vizibil numai în fişierul în care este declarat.

Un tablou declarat în interiorul unei funcţii cu specificatorul static este alocat la adrese fixe, fiind
vizibil numai în interiorul funcţiei.

Prelucrările pe tablouri se implementează cu cicluri for.


Exemplul 15: Să se afişeze elementele unui tablou citit de la intrarea
standard, câte 10 pe un rând.

#include <stdio.h>

void main(void)

/* citirea si afisarea elementelor unui vector */

{ int x[10], n, j;

scanf(“%d”, &n);

for (j=0; j < n; j++)

scanf(“%d”, &x[j]);

for (j=0; j < n; j++)

printf(“%5d%c”, x[j],(j%10==9||j==n-1)?’\n’:’ ‘);

La declararea unui tablou, acesta poate fi şi iniţializat, dacă declaraţia este urmată de semnul = şi de o listă de
valori iniţiale, separate prin virgule şi incluse între acolade.

Exemple:

int prime[5]={2,3,5,7,11};

char vocale[5]={‘a’,’e’,’i’,’o’,’u’};

La declararea unui tablou iniţializat se poate omite dimensionarea, situaţie în care se ia ca dimensiune
numărul de valori iniţiale:

char operator[]={‘+’,’-‘,’*’,’/’};

long x[]={1,10,100,1000,10000,100000};

Tablourile vor avea 4, respectiv 6 elemente.

Exemplul 16: Scrieţi un program care converteşte un şir de caractere reprezentând un număr scris cu cifre
romane în corespondentul său cu cifre arabe.

Notaţia cu cifre romane este un sistem nepoziţional, care foloseşte cifrele:

M, D, C, L, X, V, I, având respectiv valorile: 1000, 500, 100,50, 10, 5, 1.

Pentru a obţine valoarea numărului, se pleacă cu acesta de la 0 şi se adaugă pe rând contribuţiile cifrelor
astfel: dacă valoarea cifrei romane curente este mai mare sau egală cu cifra care urmează, atunci valoarea
cifrei curente se adaugă la valoarea numărului arab, altfel se scade din acesta. De exemplu numărul roman
MCMXCVIII are ca valoare pe 1998

Cifra curentă Cifra Relaţia Contribuţia Valoare


următoare dintre ele cifrei crte număr
M C > +1000 1000
C M < -100 900
M X > +1000 1900
X C < -10 1890
C V > +100 1990
V I > +5 1995
I I = +1 1996
I I = +1 1997
I > +1 1998
Se observă că pentru a considera şi contribuţia ultimei cifre a numărului am fost nevoiţi să “prelungim”
numărul cu caracterul spaţiu liber, căruia i-am asociat valoarea 0. Pentru stabilirea corespondenţei cifră
romană – valoare asociată, vom defini o funcţie int conv(char) care foloseşte două tablouri: roman şi
arab, iniţializate respectiv cu caracterele reprezentând cifrele romane şi cu valorile acestora, definite ca
externe. Numărul roman nrom, citit de la intrarea standard va fi terminat prin spaţiu liber.

#define LMAX 15

#include <conio.h>

#include <stdio.h>

char roman[]=”MDCLXVI “;

int arab[]={1000,500,100,50,10,5,1,0};

int conv(char);

void main(void)

{ char nrom[LMAX];

int i,n=0; //n = lungimea numarului scris cu cifre romane

int narab=0;

int crt,urm;

while((nrom[n++]=getchar())!=’ ‘)

...... ;

n--;

for (i=0; i<n-1; i++){

crt=conv(nrom[i]);

urm=conv(nrom[i+1]);

if(crt>=urm)

.. narab+=crt;

else
.. narab-=crt;

for (i=0;i<n;i++)

printf(“%c”,nrom[i]);

printf(“=%d\n”,narab);

int conv(char c)

{ int j=0;

while(roman[j++]!=c && j<8)

.... ;

if(j<8)

return arab[--j];

else

return –1;

}
8.2. Pointeri.

Un pointer este o variabilă care are ca valori adrese ale altor variabile, sau mai general adrese de
memorie.

Un pointer este asociat unui tip de variabile, deci avem pointeri către int, char, float, etc.

În general o variabilă pointer p către tipul T se declară:

T *p;

Un tip pointer la tipul T are tipul T*.

Exemple:

int j,*pj; /*pj este o variabila de tip pointer la întregi*/

char c, *pc;

Se introduc doi noi operatori:

 operatorul de adresare & - aplicat unei variabile furnizează adresa acelei variabile
pj=&j; /* iniţializare pointer */

pc=&c;

Aceste iniţializări pot fi făcute la definirea variabilelor pointeri:

int j, *pj=&j;

char c, *pc=&c;

O greşeală frevent comisă o reprezintă utilizarea unor pointeri neiniţializaţi.

int *px;

*px=5; /* greşit, pointerul px nu este iniţializat (legat

.... la o adresă de variabilă întreagă */

Pentru a evita această eroare vom iniţializa în mod explicit un pointer la NULL, atunci când nu este
folosit.

 operatorul de indirectare (dereferenţiere) * – permite accesul la o variabilă prin intermediul unui pointer.
Dacă p este un pointer de tip T*, atunci *p este obiectul de tip T aflat la adresa p.

În mod evident avem:

*(&x) = x;

&(*p) = p;

Exemplu;
int *px, x;

x=100;

px=&x; // px contine adresa lui x

printf(“%d\n”, px); // se afiseaza 100

Dereferenţierea unui pointer neiniţializat sau având valoarea NULL conduce la o eroare la execuţie.

8.3. Pointeri void.

Pentru a utiliza un pointer cu mai multe tipuri de date, la declararea lui nu îl legăm de un anumit tip.

void *px; // pointerul px nu este legat de nici un tip

Un pointer nelegat de un tip nu poate fi dereferenţiat.

Utilizarea acestui tip presupune conversii explicite de tip (cast). Exemplu:

int i;

void *p;

. . .

p=&i;

*(int)p=5; // ar fi fost gresit *p=5

Exemplul 17: Definiti o functie care afiseaza o valoare ce poate aparţine unuia din tipurile: char,
int, double.

#include <stdio.h>

enum tip {caracter, intreg, real};

void afisare(void *px, tip t) {

switch(t) {

case caracter:

.. printf("%c\n", *(char*)px); break;

case intreg:

.. printf("%d\n", *(int*)px); break;

case real:

.. printf("%lf\n",*(double*)px); break;

}
}

void main(void) {

char c='X';

int i=10;

double d=2.5;

afisare(&c, caracter);

afisare(&i, intreg);

afisare(&d, real);

8.4. Pointeri constanţi şi pointeri la constante.

In definiţiile:

const int x=10, *px=&x;

x este o constantă, în timp ce px este un pointer la o constantă. Aceasta însemnă că x, accesibil şi prin px
nu este modificabil (operaţiile x++ şi (*px)++ fiind incorecte, dar modificarea pointerului px este
permisă (px++ este corectă).

Un pointer constant (nemodificabil), se defineşte prin:

int y, * const py=&y;

In acest caz, modificarea pointerului (py++) nu este permisă, dar conţinutul referit de pointer poate fi
modificat ( (*py)++ ).

Un pointer constant (nemodificabil) la o constantă se defineşte prin:

const int c=5, *const pc=&c;

In cazul folosirii unor parametri pointeri, pentru a preveni modificarea conţinutului referit de aceştia se
preferă definirea lor ca pointeri la constante. De exemplu o functie care compară două şiruri de caractere are
prototipul:

int strcmp(const char *s, const char *d);

8.5. Operaţii aritmetice cu pointeri.

Asupra pointerilor pot fi efectuate următoarele operaţii:

 adunarea / scăderea unei constante


 incrementarea / decrementarea
 scăderea a doi pointeri de acelaşi tip
Prin incrementarea unui pointer legat de un tip T, adresa nu este crescută cu 1, ci cu valoarea
sizeof(T) care asigură adresarea următorului obiect de acelaşi tip.

În mod asemănător, p + n reprezintă de fapt p+n*sizeof(T) .

Doi pointeri care indică elemente ale aceluiaşi tablou pot fi comparaţi prin operatorii de relaţie şi
relaţia de egalitate, sau pot fi scăzuţi.

Pointerii pot fi comparaţi prin relaţiile == şi != cu constanta simbolică NULL (definită în stdio.h).

8.6. Legătura între pointeri şi tablouri.

Între pointeri şi tablouri există o legătură foarte strânsă. Orice operaţie realizată folosind variabile
indexate se poate obţine şi folosind pointeri. adică, dacă x este un tablou (de exemplu int x[10];) atunci
x  &x[0]

În C numele unui tablou este un pointer constant la primul element din tablou: x=&x[0]

Numele de tablouri reprezintă pointeri constanţi, deci nu pot fi modificaţi ca pointerii adevăraţi.

Exemplu:

int x[10], *px;

px=x; /* sunt operatii permise */

px++;

x=px; /* sunt operatii interzise, deoarece x este */

x++; /* pointer constant */

Prin urmare variabilele indexate pot fi transformate în expresii cu pointeri şi avem echivalenţele:

Adresă Valoare
Notaţie indexată Notaţie cu pointeri Notaţie indexată Notaţie cu
pointeri
&x[0] x x[0] *x
&x[1] x+1 x[1] *(x+1)
&x[i] x+i x[i] *(x+i)
&x[n-1] x+n-1 x[n-1] *(x+n-1)

În C avem următoarea echivalenţă ciudată!:Dacă x este un tablou de întregi

x[i]  i[x]

Într-adevăr: x[i]=*(x+i)=*(i+x)=i[x]

8.7. Parametri tablouri.


Dacă în lista de parametri a unei funcţii apare numele unui tablou cu o singură dimensiune se va transmite
adresa de început a tabloului. Aceasta ne permite să nu specificăm dimensiunea tabloului, atât la definirea,
cât şi la apelul funcţiei.

Exemplul 18: Scrieţi o funcţie care calculează produsul scalar a doi vectori x şi y, având câte n componente
fiecare.

Antetul funcţiei va fi:

double scalar(int n, double x[], double y[])

Funcţia poate fi declarată cu parametri formali pointeri în locul tablourilor:

double scalar(int n, double *x, double *y)

{ double P=0;

for (int i=0; i<=n; i++)

P=P+x[i]*y[i]

return P;

}
8.7. Şiruri de caractere.

O constantă şir de caractere se reprezintă intern printr-un tablou de caractere terminat prin caracterul
nul ‘\0’, memoria alocată fiind lungimea şirului + 1 (1 octet pentru caracterul terminator al şirului ).

Un tablou de caractere poate fi iniţializat fără a-i specifica dimensiunea:

char salut[]={‘B’,’o’,’n’,’j’,’o’,’u’,’r’,’!’,’\0’};

sau mai simplu, specificând şirul între ghilimele:

char salut[]=”Bonjour!”

(tabloul este iniţializat cu conţinutul şirului de caractere -se alocă 9 octeţi)

Folosirea unui pointer la un şir de caractere iniţializat nu copiază şirul, ci are următorul efect:

 se alocă memorie pentru şirul de caractere, inclusiv terminatorul nul la o adresă fixă de memorie
 se iniţializează spaţiul cu valorile constantelor caractere
 se iniţializează pointerul Psalut cu adresa spaţiului alocat
char *Psalut=”Buna ziua!”;

(pointerul este iniţializat să indice o constantă şir de caractere)

Aşadar, în C nu există operaţia de atribuire de şiruri de caractere (sau în general de atribuire de


tablouri), ci numai atribuire de pointeri - atribuirea t=s nu copiază un tablou, pentru aceasta se foloseşte
funcţia strcpy(t, s).

Pentru a uşura lucrul cu şiruri de caractere, în biblioteca standard sunt prevăzute o serie de funcţii, ale
căror prototipuri sunt date în fişierele <ctype.h> şi <string.h>.

În fişierul <ctype.h> există o serie de funcţii (codificate ca macroinstrucţiuni) care primesc un


parametru întreg, care se converteşte în unsigned char, şi întorc rezultatul diferit de 0 sau egal cu 0,
după cum caracterul argument satisface sau nu condiţia specificată:

islower(c) 1 dacă c {‘a’..’z’}

isupper(c) 1 dacă c {‘A’..’Z’}

isalpha(c) 1 dacă c {‘A’..’Z’}{‘a’..’z’}

isdigit(c) 1 dacă c {‘0’..’9’}

isxdigit(c) 1 dacă c {‘0’..’9’}{‘A’..’F’}{a’..’f’}

isalnum(c) 1 dacă isalpha(c)||isdigit(c)

isspace(c) 1 dacă c {‘ ‘,’\n’,’\t’,’\r’,’\f’,’\v’}

isgraph(c) 1 dacă c este afişabil, fără spaţiu

isprint(c) 1 dacă c este afişabil, cu spaţiu


iscntrl(c) 1 dacă c este caracter de control

ispunct(c) 1 dacă isgraph(c) && !isalnum(c)

Conversia din literă mare în literă mică şi invers se face folosind funcţiile:

tolower(c) şi toupper(c).

Exemplul 19: Scrieţi o funcţie care converteşte un şir de caractere reprezentând un număr întreg, într-o
valoare întreagă. Numărul poate avea semn şi poate fi precedat de spaţii albe.

#include <ctype.h>

int atoi(char *s)

{ int i, nr, semn;

for(i=0; isspace(s[i]); i++) /*ignora spatii albe*/

.... ;

semn=(s[i]==-1)?-1:1;.. /*stabilire semn*/

if(s[i]==’+’||s[i]==’-‘)../*se sare semnul*/

i++;

for(nr=0;isdigit(s[i]);i++) /*conversie in cifra*/

nr=10*nr+(s[i]-‘0’);.. /*si alipire la numar*/

return semn*nr;

Exemplul 20: Scrieţi o funcţie care converteşte un întreg într-un şir de caractere în baza 10.

Algoritmul cuprinde următorii paşi:

{ se extrage semnul numărului;

se extrag cifrele numărului, începând cu cmps;

se transformă în caractere şi se depun într-un tablou;

se adaugă semnul şi terminatorul de şir;

se inversează şirul;

void inversare(char[]);

void itoa(int n, char s[]){


int j, semn;

if((semn=n)<0)

n=-n;

j=0;

do

s[j++]=n%10+’0’;

while ((n/=10)>0);

if(semn<0)

s[j++]=’-‘;

s[j]=’\0’;

inversare(s);

void inversare(char s[])

{ int i,j;

char c;

for(i=0,j=strlen(s)-1;i<j;i++,j--)

c=s[i], s[i]=s[j], s[j]=c;

Exemplul 21: Scrieţi o funcţie care converteşte un întreg fără semn într-un şir de caractere în baza 16.

Pentru a trece cu uşurinţă de la valorile cifrelor hexazecimale 0,1,…15 la caracterele


corespunzătoare; ‘0’,’1’,…,’a’,…,’f’, vom utiliza un tablou iniţializat de caractere.

static char hexa[]=”0123456789abcdef”;

void itoh(int n, char s[])

{ j=0;

do {

.. s[j++]=hexa[n%16];

while ((n/=16)>0);

s[j]=’\0’;
inversare(s);

Fişierul <string.h> conţine prototipurile următoarelor funcţii:

char* strcpy(char* d,const copiază şirul s în d, inclusiv ‘\0’,


char* s) întoarce d
char* strncpy(char* d,const copiază n caractere din şirul s în d,
char* s, completând eventual cu ‘\0’,
întoarce d
.... int n)
char* strcat(char* d,const concatenează şirul s la sfârşitul lui d,
char* s) întoarce d
char* strncat(char* d,const concatenează cel mult n caractere din
char* s, şirul s la sfârşitul lui d, completând
cu ‘\0’, întoarce d
.... int n)
int strcmp(const char* d, compară şirurile d şi s, întoarce

.... const char* s) –1 dacă d<s,

0 dacă d==s şi

1 dacă d>s
int stricmp(const char* d, compară şirurile d şi s (ca şi
strcmp())
.... const char* s)
fără a face distincţie între litere mari şi
mici
int strncmp(const char* d, similar cu strcmp(), cu deosebirea
că se compară cel mult n caractere
.... const char* s, int n )
int strincmp(const char* d, similar cu strncmp(), cu
deosebirea că nu
.... const char* s, int n )
se face distincţie între literele mari şi
mici
char* strchr(const char* caută caracterul c în şirul d;
d,char c) întoarce un pointer la prima apariţie a
lui c în d, sau NULL
char* strrchr(const char* întoarce un pointer la ultima apariţie a
d,char c) lui c în d, sau NULL
char* strstr(const char* d, întoarce un pointer la prima apariţie a
subşirului s în d, sau NULL
.... const char* s)
char* strpbrk(const char* d, întoarce un pointer la prima apariţie a
unui caracter din subşirul s în d,
.... const char* s) sau NULL
int strspn(const char* d, întoarce lungimea prefixului din d
care conţine numai caractere din s
.... const char* s)
int strcspn(const char* d, întoarce lungimea prefixului din d
.... const char* s) care conţine numai caractere ce nu
apar în s
int strlen(const char* s) întoarce lungimea lui s (‘\0’ nu
se numără)
char* strlwr(char* s) converteşte literele mari în litere mici
în s
char* strupr(char* s) converteşte literele mici în litere mari
în s
void* memcpy(void* d, copiaza n octeţi din s în d; întoarce d

const void* s,int n)


void* memmove(void* d, ca şi memcopy, folosită daca s şi d se

const void* s,int n) întrepătrund


void* memset(void* d,const copiază caracter c în primele n poziţii
int c, din d

int n)
int memcmp(const void* d, compară zonele adresate de s şi d

const void* s,int n)


char* strtok(const char* d, caută în d subşirurile delimitate de
caracterele din s;primul apel întoarce
.... const char* s) un pointer la primul subşir din d care
nu conţine caractere din s
următoarele apeluri se fac cu primul
argument NULL, întorcându-se de
fiecare dată un pointer la următorul
subşir din d ce nu conţine caractere
din s; în momentul în care nu mai
există subşiruri, funcţia întoarce NULL
Ca exerciţiu, vom codifica unele din funcţiile a căror prototipuri se găsesc în <string.h>,
scriindu-le în două variante: cu tablouri şi cu pointeri.

Exemplul 22:Scrieţi o funcţie având ca parametru un şir de caractere, care întoarce lungimea sirului

/*varianta cu tablouri*/

int strlen(char *s)

{ int j;

for(j=0; *d!=’\0’; d++)

j++;

return j;

/*varianta cu pointeri*/

int strlen(char *s)


{ char *p=s;

while(*p)

p++;

return p-s;

Exemplul 23:Scrieţi o funcţie care copiază un şir de caractere s în d.

/*varianta cu tablouri*/

void strcpy(char *d, char *s)

{ int j=0;

while((d[j]=s[j])!=’\0’)

j++;

/*varianta cu pointeri*/

void strcpy(char *d, char *s)

{ while((*d=*s)!=’\0’){

d++;

s++;

Postincrementarea pointerilor poate fi făcută în operaţia de atribuire deci:

void strcpy(char *d, char *s){

while((*d++=*s++)!=’\0’)

.... ;

Testul faţă de ‘\0’ din while este redundant, deci putem scrie:

void strcpy(char *d, char *s){

while(*d++=*s++)

.... ;

}
Exemplul 24: Scrieţi o funcţie care compară lexicografic două şiruri de caractere şi întoarce rezultatul –1,
0 sau 1 după cum d<s, d==s sau d>s.

/*varianta cu tablouri*/

int strcmp(char *d, char *s)

{ int j;

for(j=0; d[j]==s[j]; j++)

if(d[j]==’\0’)

.. return 0;

return d[j]-s[j];

/*varianta cu pointeri*/

int strcmp(char *d, char *s)

{ for(; *d==*s; d++,s++)

if(*d==’\0’)

.. return 0;

return *d-*s;

Exemplul 25: Scrieţi o funcţie care determină poziţia (indexul) primei apariţii a unui subşir s într-un şir d.
Dacă s nu apare în d, funcţia întoarce –1.

int strind(char d[], char s[]){

int i,j,k;

for (i=0; d[i]; i++) {

for (j=i,k=0; s[k] && d[j]==s[k]; j++,k++)

........ ;

if (k>0 && s[k]==’\0’)

.. return i;

return –1;

}
8.8. Funcţii de intrare / ieşire relative la şiruri de caractere.

Pentru a citi un şir de caractere de la intrarea standard se foloseşte funcţia gets() având prototipul:

char *gets(char *s);

Funcţia gets() citeşte caractere din fluxul standard de intrare stdin în zona de memorie adresată de
pointerul s. Citirea continuă până la întâlnirea sfârşitului de linie. Marcajul de sfârşit de linie nu este copiat,
în locul lui fiind pus caracterul nul (’\0’). Funcţia întoarce adresa zonei de memorie în care se face citirea
(adică s) sau NULL, dacă în locul şirului de caractere a fost introdus marcajul de sfârşit de fişier.

Pentru a scrie un şir de caractere terminat prin caracterul NULL, la ieşirea standard stdout, se foloseşte
funcţia:

int puts(char *s);

Caracterul terminator nu este transmis la ieşire, în locul lui punându-se marcajul de sfârşit de linie.

Caracterele citite într-un tablou ca un şir de caractere (cu gets()) pot fi convertite sub controlul unui
format folosind funcţia:

int sscanf(char *sir, char *format, adrese_var_formatate);

Singura deosebire faţă de funcţia scanf() constă în faptul că datele sunt preluate dintr-o zonă de
memorie, adresată de primul parametru (şi nu de la intrarea standard).

Exemplul 26: Scrieţi o funcţie care citeşte cel mult n numere reale, pe care le plasează într-un tablou x.
Funcţia întoarce numărul de valori citite.

Vom citi numerele într-un şir de caractere s. De aici vom extrage în mod repetat câte un număr, folosind
funcţia sscanf() şi îl vom converti folosind un format corespunzător. Ciclul se va repeta de n ori, sau se
va opri când se constată că s-au terminat numerele.

Vom scrie funcţia în 2 variante: folosind tablouri sau folosind pointeri.

/* varianta cu tablouri */

int citreal(int n, double x[])

{ char s[255];

int j;

double y;

for (j=0; j<n; j++) {

if(gets(s)==NULL)

.. return j;

if(sscanf(s,”%lf”,&y)!=1) /*conversie ]n real*/

.. break;.... /*s-au terminat numerele*/


x[j]=y;

return j;

/* varianta cu pointeri */

int citreal(int n, double *px)

{ char s[255];

int j=0;

double y;

double *p=px+n;

while(px<p) {

if(gets(s)==NULL)

.. return j;

if(sscanf(s,”%lf”,&y)!=1) /*conversie in real*/

.. break;.... /*s-au terminat numerele*/

*px++=y;

j++;

return j;

8.9. Tablouri de pointeri.

Un tablou de pointeri este definit prin:

tip *nume[dimensiune];

Exemplul 27: Să se sorteze o listă de nume.

Folosirea unui tablou de şiruri de caractere este lipsită de eficienţă, deoarece şirurile sunt de lungimi
diferite. Vom folosi un tablou de pointeri la şiruri de caractere.

Prin sortare nu se vor schimba şirurile de caractere, ci pointerii către acestea.

Vasile
Constantin

Ion

Vasile

Constantin

Ion

Citirea şirurilor de caractere presupune:

 rezervarea de spaţiu pentru şiruri


 iniţializarea tabloului de pointeri cu adresele şirurilor

Pentru rezervarea de spaţiu se foloseşte funcţia char *strdup(char *s);

care:

 salvează şirul indicat de s într-o zonă de memorie disponibilă, alocată dinamic


 întoarce un pointer către zona respectivă sau NULL.

Citirea numelor este terminată prin EOF. Funcţia de citire întoarce numărul de linii citite:

int citire(char *tabp[]){

int j=0;

char tab[80];

while(1) {

gets(tab);

if(tab==NULL)

.. break;

tabp[j]=strdup(tab);

return j;

Sortarea o vom realiza cu algoritmul bulelor: dacă şirul de nume ar fi ordonat, atunci două nume
consecutive s-ar afla în relaţia < sau ==. Vom căuta aşadar relaţiile >, schimbând de fiecare dată între ei
pointerii corespunzători (schimbare mai eficientă decât schimbarea şirurilor). Se fac mai multe parcurgeri
ale listei de nume; la fiecare trecere, o variabilă martor – sortat, iniţializată la 1 este pusă pe 0, atunci
când se interschimbă doi pointeri. Lista de nume va fi sortată în momentul în care în urma unei parcurgeri a
listei se constată că nu s-a mai făcut nici o schimbare de pointeri.
void sortare(char *tp[], int n) {

int j, sortat;

char *temp;

for(sortat=0; !sortat;){

sortat=1;

for(j=0;j<n-1;j++)

.. if(strcmp(tp[j],tp[j+1])>0){

.. temp=tp[j],

.. tp[j]=tp[j+1],

.. tp[j+1]=temp,

.. sortat=0;

.. }

void afisare(char *tp[], int n){

int j;

for (j=0; j<n; j++)

if(tp[j])

.. puts(tp[j]);

void main(void)

{ int n;

char *nume[100];

n=citire(nume);

sortare(nume,n);

afisare(nume,n);

}
Exemplul 28: Definiţi o funcţie, având ca parametru un întreg, reprezentând o lună, care întoarce (un
pointer la) numele acelei luni

char *nume_luna(int n)

{ static char *nume[]={“Luna inexistenta”,”Ianuarie”,

.. “Februarie”,”Martie”,”Aprilie”,“Mai”,”Iunie”,

.. ”Iulie”,”August”,“Septembrie”,”Octombrie”,

.. “Noiembrie”,”Decembrie”};

return(n<1||n>12)?nume[0]:nume[n];

9. Functii (2)

9.1. Mecanisme de transfer ale parametrilor.

În limbajele de programare există două mecanisme principale de transfer ale parametrilor: transferul
prin valoare şi transferul prin referinţă.

În C parametrii se transferă numai prin valoare- aceasta înseamnă că parametrii actuali sunt copiaţi în
zona de memorie rezervată pentru parametrii formali. Modificarea parametrilor formali se va reflecta
asupra copiilor parametrilor actuali şi nu afectează parametrii actuali, adică parametrii actuali nu pot fi
modificaţi !

Astfel funcţia:

void schimb(int a, int b)

{ int c=a;

.. a=b;

.. b=c;

nu produce interschimbul parametrilor actuali x şi y din funcţia main():

void main(void)

{ int x=10,y=15;

printf(“%d\t%d\n”, x, y);

schimb(x, y);

printf(“%d\t%d\n”, x, y);

}
Se afişează:

10..15

10..15

Stiva funcţiei apelante Stiva funcţiei apelate

x 10

y 15

a ..1 10

3 c

..2

b 15

În cazul transferului prin referinţă, pentru modificarea parametrilor actuali, funcţiei i se transmit nu
valorile parametrilor actuali, ci adresele lor.

Forma corectă a funcţiei schimb() obţinută prin simularea transferului prin referinţă cu pointeri este:

void schimb(int *a, int *b)

{ int c;

c = *a;

*a = *b;

*b = c;

void main(void)

{ int x=10,y=15;

printf(“%d\t%d\n”, x, y);

schimb(&x, &y);

printf(“%d\t%d\n”, x, y);

Stiva funcţiei apelante Stiva funcţiei apelate

..întreg pointer
x a

..C........ întreg

pointer

y b

întreg

In consecinţă, pentru a transmite un rezultat prin lista de parametri (adică pentru a modifica un parametru)
va trebui să declarăm parametrul formal ca pointer.

Tablourile sunt transmise întotdeauna prin referinţă, adică un parametru formal tablou reprezintă o
adresă (un pointer) şi anume adresa de început a tabloului.

În C++ a fost introdus transferul parametrilor prin referinţă, ceea ce simplifică în mod considerabil
scrierea. Un parametru formal transmis prin referinţă este specificat prin: tip&. Funcţia schimb() se
defineşte în acest caz astfel:

void schimb(int &a, int &b)

{ int c;

c = a;

a = b;

b = c;

void main(void)

{ int x=10,y=15;

printf(“%d\t%d\n”, x, y);

schimb(x, y);

printf(“%d\t%d\n”, x, y);

9.2. Funcţii care întorc pointeri.

Funcţia strcpy() întoarce adresa şirului destinaţie:


char *strcpy(char *d, char *s)

{ char *p=d;

while (*p++=*s++)

.. ;

return d;

Aceasta ne permite ca simultan cu copierea să calculăm lungimea şirului copiat:

n=strlen((strcpy)d,s));

Dacă funcţia întoarce adresa unei variabile locale, atunci aceasta trebuie să fie în mod obligatoriu în
clasa static.

De asemeni nu trebuiesc întoarse adrese ale unor parametri, deoarece aceştia sunt transmişi prin stivă.

9.3. Pointeri la funcţii.

Numele unei funcţii reprezintă adresa de memorie la care începe funcţia. Numele functiei este, de fapt,
un pointer la funcţie.

Se poate stabili o corespondenţă între variabile şi funcţii prin intermediul pointerilor la funcţii. Ca şi
variabilele, aceşti pointeri:

 pot primi ca valori funcţii;


 pot fi transmişi ca parametrii altor funcţii
 pot fi intorşi ca rezultate de către funcţii

La declararea unui pointer către o funcţie trebuiesc precizate toate informaţiile despre funcţie, adică:

 tipul funcţiei
 numărul de parametri
 tipul parametrilor

care ne vor permite să apelăm indirect funcţia prin intermediul pointerului.

Declararea unui pointer la o funcţie se face prin:

tip (*pf)(listă_parametri_formali);

Dacă nu s-ar folosi paranteze, ar fi vorba de o funcţie care întoarce un pointer.

Apelul unei funcţii prin intermediul unui pointer are forma:

(*pf)(listă_parametri_actuali);

Este permis şi apelul fără indirectare:


pf(listă_parametri_actuali);

Este posibil să creem un tablou de pointeri la funcţii; apelarea funcţiilor, în acest caz, se face prin
referirea la componentele tabloului.

De exemplu, iniţializarea unui tablou cu pointeri cu funcţiile matematice uzuale se face prin:

double (*tabfun[])(double) = {sin, cos, tan, exp, log};

Pentru a calcula rădăcina de ordinul 5 din e este suficientă atribuirea:

y = (*tabfun[3])(0.2);

Numele unei funcţii fiind un pointer către funcţie, poate fi folosit ca parametru în apeluri de funcţii.

În acest mod putem transmite în lista de parametri a unei funcţii – numele altei funcţii.

De exemplu, o funcţie care calculează integrala definită:


b
I   f(x) dx
a

va avea prototipul:

double integrala(double, double, double(*)(double));

Definiţi o funcţie pentru calculul unei integrale definite prin metoda trapezelor,cu un număr fixat n
de puncte de diviziune:
b
 f(a)  f(b) n 1
 b  a
 f(x)dx  h
2
  f(a  ih) cu h 
n
a  i1 
Folosiţi apoi această funcţie pentru calculul unei integrale definite cu o precizie dată . Această
precizie este atinsă în momentul în care diferenţa între două integrale, calculate cu n, respectiv 2n
puncte de diviziune este inferioară lui 

#include <math.h>

double sinxp();

double trapez(double,double,int,double(*)());

double a=0.0, b=1.0, eps=1E-6;

int N=10;

void main(void)

{ int n=N;

double In,I2n,vabs;

In=trapez(a,b,n,sinxp);

do { n*=2;
.. I2n=trapez(a,b,n,sinxp);

.. if((vabs=In-I2n)<0) vabs=-vabs;

.. In=I2n;

} while(vabs > eps);

printf(“%6.2lf\n”, I2n);

double trapez(double a,double b,int n,double(*f)())

{ double h,s;

int i;

h=(b-a)/n;

for(s=0.0,i=1;i<n;i++)

s+=(*f)(a+i*h);

s+=((*f)(a)+(*f)(b))/2.0;

s*=h;

return s;

9.4. Declaratii complexe.

O declaratie complexă este o combinaţie de pointeri, tablouri si functii. In acest scop se folosesc
atributele:

() – functie

[] – tablou

* - pointer

care pot genera urmatoarele combinatii:

* () – funcţie ce returnează un pointer

(*)() – pointer la o funcţie

* [] - tablou de pointeri

(*) [] – pointer la tablou

[ ] [] – tablou bidimensional
Există şi combinaţii incorecte, provenite din faptul că în C nu este permisă declararea:

- unui tablou de funcţii


- unei funcţii ce returnează un tablou.

Acestea sunt:

( ) [ ] – funcţie ce returnează un tablou

[ ] ( ) – tablou de funcţii

( ) ( ) – funcţie ce returnează o funcţie

Pentru a interpreta o declaraţie complexă vom inlocui atributele prin următoarele şabloane text:

Atribut Sablon text


() functia returnează
[n] tablou de n
* pointer la
Descifrarea unei declaraţii complexe se face aplicând regula dreapta – stânga, care presupune următorii
paşi:

 se incepe cu identificatorul
 se caută în dreapta identificatorului un atribut
 dacă nu există, se caută în partea stangă
 se substituie atributul cu şablonul text corespunzător
 se continuă substituţia dreapta-stânga
 se opreşte procesul la întâlnirea tipului datei.

De exemplu:

int (* a[10]) ( );

tablou de 10

pointeri la

funcţii ce returnează int

double (*(*pf)())[3][4];

pointer la

o funcţie ce returnează un pointer

la un tablou cu 3 linii si 4 coloane de double

In loc de a construi declaraţii complexe, se preferă, pentru creşterea clarităţii, să definim progresiv noi
tipuri folosind typedef. Reamintim că declaraţia typedef asociază un nume unei definiri de tip:

typedef <definire de tip> identificator;

Exemple:
typedef char *SIR; /*tipul SIR=sir de caractere*/

typedef float VECTOR[10];/*tipul VECTOR=tablou de 10float*/

typedef float MATRICE[10][10];/*tipul MATRICE= tablou de

............ 10x10 float */

typedef double (*PFADRD)(double);/*tipul PFADRD=pointer la

functie de argument double si rezultat double */

Vom putea folosi aceste tipuri noi în definirea de variabile:

SIR s1, s2;

VECTOR b, x;

MATRICE a;

PFADRD pf1;

10. Tablouri şi pointeri (2).

10.1. Alocarea dinamică a memoriei.

Utilizatorul poate solicita în timpul execuţiei programului alocarea unei zone de memorie.

Această zonă de memorie poate fi eliberată, în momentul în care nu mai este necesară.

Alocarea şi eliberarea de memorie la execuţie permite gestionarea optimă a memoriei.

Biblioteca standard oferă 4 funcţii, având prototipurile în <alloc.h> şi <stdlib.h>. Acestea sunt:

void *malloc(unsigned n);

Funcţia alocă un bloc de memorie de n octeţi. Funcţia întoarce un pointer la începutul zonei alocate. În caz
că cererea de alocare nu poate fi satisfăcută, funcţia returnează NULL.

void *calloc(unsigned nelem, unsigned dim);

Alocă nelem*dim octeţi de memorie (nelem blocuri formate din dim octeţi fiecare). Întoarce un
pointer la începutul zonei alocate sau NULL.Memoria alocată este iniţializată cu zerouri.

void free(void *p);

Funcţia eliberează o zonă de memorie indicată de p, alocată în prealabil prin malloc() sau
calloc().

void *realloc(void *p, unsigned dim);

Modifică dimensiunea spaţiului alocat prin pointerul p, la dim. Funcţia întoarce adresa zonei de
memorie realocate, iar pointerul p va adresa zona realocată.
 dacă dimensiunea blocului realocat este mai mică decât a blocului iniţial, p nu se modifică, iar funcţia va
întoarce valoarea lui p.
 dacă dim==0 zona adresată de p va fi eliberată şi funcţia întoarce NULL.
 dacă p==NULL, funcţia alocă o zonă de dim octeţi (echivalent cu malloc()).

Funcţiile de alocare întorc pointeri generici (void*) la zone de memorie, în timp ce utilizatorul alocă
memorie ce păstrează informaţii de un anumit tip. Pentru a putea accesa memoria alocată, indirect, prin
intermediul pointerului, acesta va trebui să fie un pointer cu tip, ceea ce impune conversia explicită (prin
cast) a pointerului întors de funcţia de alocare într-un pointer cu tip.

De exemplu, pentru a aloca un vector de întregi, având n elemente vom folosi:

int *p:

if (p=(int*)malloc(n*sizeof(int))==NULL) {

printf(“Memorie insuficienta\n”);

exit(1);

Funcţiile care întorc pointeri sunt utile în alocarea de spaţiu pentru variabile dinamice.

Variabilele dinamice sunt alocate în momentul execuţiei, nu au nume şi sunt accesate prin pointeri.

Un constructor este o funcţie care alocă spaţiu în mod dinamic pentru o variabilă şi întoarce un pointer la
spaţiul rezervat.

Un destructor este o funcţie care primeşte un pointer la o zonă alocată dinamic şi eliberează această
zonă.

O funcţie utilă, strdup() – salvează un şir de caractere într-o zonă alocată dinamic şi întoarce un
pointer la acea zonă sau NULL.

char *strdup(char *s)

{ char *p;

p=(char*) malloc(strlen(s)+1);

if (p!=NULL)

strcpy(p,s);

return p;

Exemplul 29: Citiţi de la intrarea standard un şir de caractere de lungime necunoscută într-un vector
alocat dinamic. Alocarea de memorie se va face progresiv, în incremente de lungime INC, după citirea a
INC caractere se face o realocare.
char *citire()

{ char *p, *q;

int n;

unsigned dim=INC;

p=q=(char*)malloc(dim);

for(n=1; (*p=getchar())!=’\n’ &&*p!=EOF; n++) {

if(n%INC==0) {

.. dim+=INC;

.. p=q=realloc(q,dim);

.. p+=n;

.. continue;

p++;

*p=’\0’;

return realloc(q,n);

În C++, alocarea dinamică se face mai simplu şi mai sigur, folosind operatorii new şi delete.

Operatorul new permite alocarea de memorie în heap. El se foloseşte într-una din formele:

tip *p, *q, *r;

p=new tip; //rezerva in heap o zona de sizeof(tip) octeti

q=new tip(expresie);//acelasi efect cu initializare cu val.expresiei

r=new tip[expint]; //rezerva o zona neinitializata de

........// expint*sizeof(tip) octeti

Eliberarea memoriei alocate se face prin:

delete p;

delete [] r; //elibereaza memoria alocata pentru tablou

10.2. Pointeri la pointeri.


Adresa unei variabile pointer va fi de tip pointer către pointer (pointer dublu)

Să considerăm definiţiile:

int x=10, *px=&x, **ppx=&px;

care corespund situaţiei:

ppx px x.. 10

pentru a obţine valoarea 10 putem folosi x, *px sau **ppx.

O funcţie care interschimbă doi pointeri are forma:

void pschimb(int **pa, int **pb)

{ int *ptemp;

ptemp=*pa;

*pa=*pb;

*pb=ptemp;

cu apelul:

int *px, *py;

pschimb(&px, &py);

Deoarece un tablou este accesat printr/un pointer, tablourile de pointeri pot fi accesate cu pointeri dubli:

char *a[10];

char **pa;

p=a;

Funcţia de afişare a şirurilor de caractere adresate de un tablou de

pointeri poate fi rescrisă ca:

void afisare(char **tp, int n)

{ while(n--)

printf(“%s\n”,*tp++);

10.3. Tablouri multidimensionale.

Un tablou cu mai multe dimensiuni se defineşte prin:


tip nume[d1][d2]…[dn];

Referirile la elemente unui tablou cu mai multe dimensiuni se fac folosind variabile indexate de forma:
nume[i1][i2]…[in], în care 0<=ik<=dk-1

Un tablou cu n dimensiuni poate fi considerat ca un tablou cu o dimensiune având ca elemente tablouri


cu n-1 dimensiuni.

Elementele tabloului ocupă o zonă continuă de memorie de:

d1 x d2 x…x dn x sizeof(T) octeţi.

Adresa în memorie a unui element a[i1][i2]…[in] este dată de funcţia de alocare:

&a[i1][i2]…[in]=a+sizeof(T)*[i1*d2*…*dn+i2*d3*…*dn+…+in]

În cazul vectorilor: &a[i]=a+sizeof(T)*i

aceasta ne permite ca la declararea unui parametru vector să nu specificăm dimensiunea tabloului.

În cazul matricilor, compilatorul le transformă în vectori:

&a[i][j]=a+sizeof(T)*(i*C+j)

Şi în cazul matricelor, ca şi la vectori putem înlocui indexarea prin operaţii cu indici şi avem:

a[i][j] = (*(a+i)[j]=*(*(a+i)+j)

La transmiterea unui tablou multidimensional ca parametru al unei funcţii vom omite numai prima
dimensiune, celelalte trebuind să fie specificate.

Prin urmare, prototipul unei funcţii de afişare a unei matrici având l linii şi c coloane nu poate fi scris
ca:

void matprint(int a[][], int l, int c);

ci:

void matprint(int a[][DMAX], int l, int c);

în care DMAX este numărul de coloane al matricii din apel, ceeace ne limitează utilizarea funcţiei numai
pentru matrici cu DMAX coloane!

Vom reda generalitate funcţiei de afişare a matricilor, declarând matricea parametru ca un vector de
pointeri:

void matprint(int (*a)[], int l, int c)

{ int i,j;

for(i=0;i<l;i++)

{ for (j=0;j<c;j++)
.. printf(“%d”,((int*)a)[i*n+j];

printf(“\n”);

Problema transmiterii matricilor ca parametri poate fi evitată, dacă le linearizăm, transformându-le în


vectori. În acest caz, în locul folosirii a doi indici i şi j vom folosi un singur indice:

k=i*C+j

Exemplul 30: Scrieţi o funcţie pentru înmulţirea a două matrici A şi B având mxn, respectiv nxp
elemente.

n1
Cij   A ikBkj
k0

Matricea produs va avea mxp elemente, care vor fi calculate cu relaţia:

void matprod(int m,int n, int p,

.... double A[], double B[], double C[])

{ int i, j, k, ij;

for (i=0; i<m; i++)

for (j=0; j<p; j++) {

.. ij=i*p+j;

.. for (k=0; k<n; k++)

.. C[ij]=C[ij]+A[i*n+k]*B[k*p+j];

Soluţia propusă nu ne permite să acceesăm elementele matricelor folosind 2 indici.

Am putea înlocui matricea printr-un vector de pointeri la liniile matricei.

Exemplul 31: Definiţi o funcţie care alocă dinamic memorie pentru o matrice având l linii şi c coloane.

a .. .. plin ...... pelem

........ plin[0] .... pelem[0][0]

.. plin[1] .... pelem[0][1]

plin[2] .... pelem[1][0]


...... pelem[1][1]

...................................... pelem[2][1] pelem[2][0]

..............................................

double **alocmat(int lin, int col)

{ double **plin;

double *pelem;

int i;

pelem=(double*)calloc(lin*col, sizeof(double));

if(pelem==(double*)NULL){

.... printf(“spatiu insuficient\n”);

.... exit(1);}

plin=(double**)calloc(lin, sizeof(double*);

if(plin==(double**)NULL){

.... printf(“spatiu insuficient\n”);

.... exit(1);

for (i=0; i< lin; i++) {

.. plin[i]=pelem;

.. pelem+=col;

return plin;

Clase.

1. Programare procedurală – Programare orientată pe obiecte.

2. Declararea claselor.

3. Operatorul de rezoluţie.

4. Funcţii membre inline.


5. Referinţe.

6. Constructori şi destructori.

7. Pointerul this.

8. Membri statici ai claselor.

9. Funcţii membre constante.

10. Funcţii care returnează referinţe.

11. Probleme.

1. Programare procedurală –Programare orientată pe obiecte.

Limbajul C, ca şi Pascal, utilizează modelul programării structurate procedurale, care constă în


descompunerea programului în proceduri (funcţii), apelate în ordinea în care se desfăşoară algoritmul.
Datele sunt separate de funcţiile care le manipulează.

Odată cu creşterea dimensiunii programelor s-a acordat o atenţie sporită organizării datelor –
funcţiile, împreună cu datele pe care le manevrează sunt organizate ca un modul. Programarea modulară
este tot programare procedurală, cu proceduri şi date grupate în module şi ascunse altor module.

Programarea orientată pe obiecte constă în identificarea unor obiecte, cu operaţii (metode)


specifice asociate şi realizarea comunicării între aceste obiecte prin intermediul unor mesaje. Elementul
constructiv – obiectul este o instanţă a unei clase (tip de dată definită de utilizator). Clasele sunt membre
ale unei ierarhii şi sunt corelate între ele prin relaţii de moştenire.

Un limbaj de programare pune la dispoziţia utilizatorilor un număr de tipuri primitive (sau


predefinite). Astfel în C avem ca tipuri primitive char, int, float, etc. Un tip de date este precizat
printr-o mulţime finită de valori T (constantele tipului) şi o mulţime de operatori (aplicaţii T  T sau

T  T  T).

Într-un limbaj de programare un tip de date reprezintă un model matemaţic. Astfel tipul int ,
caracterizat prin mulţimea finită de întregi (-32768, 32767) şi operatorii binari +, -, *, / şi %
ilustrează conceptul de număr întreg.

Modelele matemaţice care nu au reprezentare directă prin tipuri predefinite se pot reprezenta prin
tipuri definite de utilizator (numite şi tipuri de date abstracte- TDA).

2. Declararea claselor.

O clasă reprezintă un tip definit de utilizator. Declararea unei clase se face într-o manieră
asemănătoare declarării structurilor şi conţine atât date cât şi funcţii (metode) şi putem declara variabile
de acest tip nou. Un obiect este un exemplar sau o instanţă a unei clase (în vechea terminologie obiectul
este echivalent unei variabile, iar clasa este echivalentul unui tip definit de utilizator).

Considerăm conceptul Data, pe care-l reprezentăm printr-o structură şi un set separat de funcţii de
manipulare:

struct Data{
int a, l, z; // reprezentare

};

void initD(const Data&, int, int, int); //initializare

void ad_a(const Data&, int); //adauga un an

void ad_l(const Data&, int); //adauga o luna

void ad_z(const Data&, int); //adauga o zi

Nu există o legătură implicită între date şi funcţiile de manipulare a lor. Pentru a stabili această legătură,
declarăm funcţiile ca membre ale structurii:

struct Data{

int a, l, z; // reprezentare

..// functii de manipulare

void initD(int, int, int); // initializare

void ad_a(int);

void ad_l(int);

void ad_z(int);

};

Funcţiile declarate în definirea clasei (structura este o clasă) se numesc funcţii membre şi pot fi apelate
numai de variabile de tipul corespunzător (obiecte) folosind sintaxa de acces la membrii structurii:

Data d;

d.initD(2001, 10, 15);

d.ad_a(3);

. . .

Definirea funcţiilor membre, în afara clasei se face folosind operatorul de vizibilitate (rezoluţie), care
indică faptul că funcţia aparţine clasei specificate înaintea acestui operator.

void Data::initD(int aa, int ll, int zz){

a = aa;

l = ll;

z = zz;

};
Declararea unei clase se face în mod asemănător cu declararea unei structuri. Cuvântul struct
este înlocuit prin class, iar câmpurile se separă în date membre şi funcţii membre (sau metode). În plus,
se pot preciza specificatori de acces la membri. Aceştia pot fi:

 private – membrii sunt accesibili numai din interiorul clasei


 public – membrii sunt accesibili din afara clasei
 protected – membrii sunt accesibili din interiorul clasei şi din clasele derivate

Domeniul de definiţie al clasei este cuprins între începutul definiţiei clasei (class nume) şi sfârşitul
definiţiei.

Domeniul unui specificator de acces se întinde din locul unde apare, până la următorul specificator
de acces. Dacă nu apare nici un specificator de acces, accesul la membrii clasei nu va fi permis din afara ei
(implicit se consideră specificatorul private).

Datele clasei (definind structura sau starea) fiind private sunt “încapsulate”, adică ascunse
utilizatorului. Acesta nu le poate accesa direct, ci numai prin intermediul unor funcţii de acces publice, care
aparţin interfeţei ce defineşte comportarea clasei. (de obicei datele sunt private şi funcţiile sunt publice.)

//declararea clasei (interfata)

class Data{

int a, l, z; // reprezentare

public:

void initD(int, int, int);

void ad_a(int);

void ad_l(int);

void ad_z(int);

};

Variabilele ce apar în declaraţia clasei (datele membre), fiind private, pot fi folosite numai de către
funcţiile membre.

Funcţiile membre din partea publică pot fi accesate de către oricine. O structură este o clasă cu toţi
membrii publici.

Un alt exemplu - clasa punct; are ca date membre – abscisa şi ordonata, iar accesul la ele va fi
oprit; ca funcţii membre vom prevedea: iniţializarea, funcţii de acces la date, de setare date şi afişarea.

#include <iostream.h>

#include <iomanip.h>

// declaraţia clasei

class punct{
private:

double x0, y0; // date membre

public:

void init(double x=0, double y=0){

x0 = x;

y0 = y;

};

void setx(double x){ x0 = x; };

void sety(double y){ y0 = y; };

double getx(){ return x0; };

double gety(){ return y0; };

void afisare(){

long f = ios::fixed;

cout << setiosflags(f) << setw(6) << setprecision(2);

cout << “(“ << x0 << “,” << y0 << “)” << endl;

};

};

void main(){

punct z1, z2, z;

z1.init(1, 2);

z2.setx(3);

z2.sety(4);

z.init(z1.getx(),z2.gety());

z.afisare();

Se preferă ca definirea funcţiilor clasei (în afara cazurilor în care acestea sunt foarte mici), să se facă
în afara clasei, funcţiile membre fiind date numai prin semnăturile lor. În acest caz putem separa definirea
clasei de implementarea ei. Utilizatorului clasei i se asigură ca interfaţă, numai definirea clasei, fiindu-i
ascunsă implementarea ei.
// definirea clasei – fisierul punct.h

class punct{

private:

double x0, y0; // date membre

public:

void init(double x=0, double y=0);

void setx(double x=0);

void sety(double y=0);

double getx();

double gety();

void afisare();

};

3. Operatorul de rezoluţie.

Întrucât definirea funcţiilor se va face în afara domeniului de definiţie al clasei, numele funcţiei trebuie să
fie însoţit de numele clasei, şi să fie separat de aceasta prin operatorul de rezoluţie sau vizibilitate ( :: ).
Aşadar, implementarea clasei, (fişierul punct.cpp) va fi:

// fisierul punct.cpp

#include <iostream.h>

#include <iomanip.h>

void punct::init(double x, double y){

x0 = x;

y0 = y;

};

void punct::setx(double x){ x0 = x; };

void punct::sety(double y){ y0 = y; };

double punct::getx(){ return x0; };

double punct::gety(){ return y0; };

void punct::afisare(){

long f = ios::fixed;
cout << setiosflags(f) << setw(6) << setprecision(2);

cout << “(“ << x0 << “,” << y0 << “)” << endl;

};

};

De obicei declararea clasei (interfaţa) şi definirea ei (implementarea) se fac în fişiere separate. Declaraţia
unei clase se face într-un fişier antet. Un fişier antet trebuie inclus o singură dată în fişierul programului.

Pentru a evita includerea multiplă de fişiere antet se utilizează directive #ifndef sub forma:

#ifndef _simbol_

#define _simbol_

declaraţie clasă (fişier antet)

#endif

Programul care utilizează clasa punct este un program constituit din mai multe fişiere, care vor alcătui
un proiect. El va conţine o directivă de includere a fişierului de interfaţă punct.h pentru definirea clasei.
La prima întâlnire a fişierului antet, simbolul nu este definit, incluzându-se fişierul antet. După prima
includere, simbolul fiind definit se ignoră porţiunea cuprinsă între #define şi #endif.

Fişierul de implementare conţine definiţiile metodelor, directiva de includere a fişierului antet al interfeţei
şi alte directive de includere de fişiere.

#include <iostream.h>

#include “clasa.h”

// definitii funcţii membre ale clasei

Programul utilizator (al treilea fişier) va conţine: includerea diverselor fişiere antet, includerea fişierului
antet al clasei şi funcţia main().

#include <iostream.h>

#include “clasa.h”

void main(){

. . .

4. Funcţii membre inline.

Funcţiile mici, cu puţine instrucţiuni, apelate frecvent se compilează ineficient (se rezervă spaţiu în stivă
pentru parametrii şi rezultat, se efectuează salt la funcţie şi apoi salt pentru revenire). Mult mai eficientă ar fi
expandarea apelului funcţiei prin corpul ei, ca în macroinstrucţiuni. Apelul unei funcţii declarate inline
este înlocuit la compilare prin corpul funcţiei (apel realizat prin expandare). O funcţie inline nu trebuie să
conţină bucle.
Funcţiile membre definite în interiorul clasei sunt în mod implicit inline, dar este posibil ca şi funcţii
definite în afara clasei să fie declarate explicit inline (expandate).

class Data{

public:

int zi();

. . .

private:

int a, l, z;

};

// functie declarata in mod explicit inline inafara clasei

inline int Data::zi(){return z;};

// definirea clasei – fisierul punct.h

class punct{

private:

double x0, y0; // date membre

public:

inline void init(double x=0, double y=0); //nu era necesara

inline void setx(double x=0){ x0=x; }; //declararea explicita

inline void sety(double y=0){ y0=y; }; //erau considerate inline

double getx(){ return x0; }; // este implicit inline

double gety(){ return y0; };

void afisare();

};

// fisierul punct.cpp

#include <iostream.h>

#include <iomanip.h>

//desi este definita in afara clasei este inline (apel expandat)

inline void punct::init(double x, double y){


x0 = x;

y0 = y;

};

//nu este inline (apel=salt cu revenire)

void punct::afisare(){

long f = ios::fixed;

cout << setiosflags(f) << setw(6) << setprecision(2);

cout << “(“ << x0 << “,” << y0 << “)” << endl;

};

};

5. Referinţe.

O referinţă se comportă ca un pointer constant, care este în mod automat dereferenţiat. Referinţele se
folosesc:

- în listele de parametri ale funcţiilor


- ca valori de întoarcere ale funcţiilor

La creerea unei referinţe, aceasta trebuie iniţializată cu adresa unui obiect (nu cu o valoaren
constantă).

int x;

int& rx = x; //referinta initializata cu o adresa

Referinţa poate fi privită ca un pointer inteligent, a cărui iniţializare este forţată de către compilator
(la definire) şi care este dereferenţiat automat.

Spre deosebire de pointeri:

- referinţele sunt iniţializate la creere (pointerii se pot iniţializa oricând)


- referinţa este legată de un singur obiect şi această legătură nu poate fi modificată pentru un alt obiect
- nu există referinţe nule – ele sunt totdeauna legate de locaţii de memorie

6. Constructori şi destructori.

Iniţializarea asigurată de funcţia membru init() este lăsată la laţitudinea utilizatorului. Este de preferat
o iniţializare mai sigură a obiectului, care să se facă în mod implicit la declararea obiectului. Iniţializarea
obiectelor se face prin intermediul unor funcţii speciale numite constructori.

Folosirea funcţiei initD() pentru iniţializarea obiectelor clasei Data este nesigură – putem uita să
facem iniţializarea sau să facem o iniţializare repetată.

Un obiect poate fi declarat neiniţializat sau iniţializat. Exemple:


Complex z; // obiect neiniţializat

Complex z1(-1.5, 2.); // obiect initializat

O soluţie sigură o reprezină declararea unei funcţii având scopul explicit de iniţializare a obiectelor.
O asemenea funcţie poartă numele de constructor.

Constructorul este o funcţie publică, care iniţializează datele membre, având acelaşi nume cu numele
clasei şi care nu întoarce nici o valoare.

class Data{

. . .

public:

Data(int, int, int); // constructorul

. . .

};

Considerăm definiţia clasei Complex:

#include <stdio.h>

class Complex{

private:

double re, im;

public:

void scrie();

};

void Complex::scrie(){

printf(“(%4.1lf , %4.1lf)\n”, re, im);

};

void main(){

Complex z; //obiect neinitializat

z.scrie();

Programul este corect din punct de vedere sintactic. Lipsa unui constructor definit de utilizator, care
să iniţializeze obiectele este remediată de compilator. Astfel, pentru clasa C, dacă nu se defineşte în mod
explicit un constructor, compilatorul generează în mod implicit constructorul: C::C(){}, care crează
datele membre ale clasei. Acest constructor implicit nu face nici o iniţializare a datelor membre, aşa că se vor
afişa nişte valori foarte mari, reprezentând conţinutul memoriei neiniţializate.

Pentru a evita aceasta, se recomandă programatorului să îşi definească un constructor implicit (fără
parametri), care să iniţializeze datele membre (în exemplul nostru iniţializarea la 0 pentru partea reală şi cea
imaginară a obiectului număr complex). Constructorul implicit acţionează asupra obiectelor neiniţializate.

Clasa Complex, prevăzută cu acest constructor este:

class Complex{

private:

double re, im;

public:

Complex(){re=0.; im=0.};

void scrie();

};

Se preferă scrierea constructorului, folosind o listă de iniţializare, conform sintaxei:

Complex() : re(0.), im(0.){};

De această dată, în urma execuţiei se va afişa ( 0.0 , 0.0).

Dacă se declară obiecte iniţializate, iniţializarea acestora trebuie făcută de către un constructor de
iniţializare, care trebuie definit în mod explicit de către utilizator.

Pentru clasa Complex, un constructor de iniţializare, are forma:

class Complex{

private:

double re, im;

public:

Complex(double x, double y) : re(x), im(y){};

void scrie();

};

void main(){

Complex z1(-1., 2.); //obiect initializat

Complex z2; //obiect neinitializat

. . .
}

Dacă se declară un constructor de iniţializare, compilatorul nu mai generează un constructor implicit,


astfel că pentru obiectele neiniţializate (în exemplul de mai sus z2)şi tablourile de obiecte se va genera
eroare. Putem defini deci doi constructori (şi în general oricâţi, având în mod evident semnături diferite),
unul implicit şi celălalt de iniţializare:

Complex() : re(0.), im(0.){}; //ctor implicit

Complex(double x, double y):re(x),im(y){}; //ctor de initializare

Clasa de mai jos defineşte mai mulţi constructori:

class Data{

int a, l, z;

public:

Data(int, int, int); // a, l, z

Data(int, int);.. // l, z

Data(int); // z

Data(); // implicit cu data curenta

Data(const char*); // data reprezentata de sir

. . .

};

Se preferă înglobarea constructorului implicit în constructorul de iniţializare, folosind parametri cu valori


implicite. Constructorul de iniţializare poate fi folosit drept constructor implicit, dacă toţi parametrii sunt
prevăzuţi cu valori implicite.

Complex(double x=0., double y=0.):re(x),im(y){};

Folosirea argumentelor implicite poate reduce numărul de constructori.

În exemplul următor, în lipsa argumentelor, constructorul asigură iniţializarea datei cu valori preluate din
obiectul static azi.

class Data{

int a, l, z;

public:

Data(int aa=0, int ll=0, int zz=0);

. . .

};
Data::Data(int aa, int ll, int zz){

a = aa? aa : azi.a;

l = ll? Ll : azi.l;

z = zz? zz : azi.z;

};

Utilizatorul este obligat să-şi definească proprii constructori numai în situaţiile în care obiectul alocă
dinamic spaţiu în memorie.

Nu pot fi definiţi mai mulţi constructori impliciţi .(fiind supraîncărcaţi, diferiţii constructori trebuie să
aibă semnături diferite).

Un constructor cu un singur argument defineşte o funcţie pentru conversia de tip de la tipul argumentului
la tipul clasei. Deci vom putea iniţializa un obiect printr-o atribuire de forma:

clasa ob=argument;

Atriburea de mai sus poate fi considerată ca o conversie implicită. Pentru a o dezactiva, constructorul
cu un argument trebuie să fie precedat de cuvântul cheie explicit.

Operaţia de eliminare a unui obiect din memorie este realizată de o funcţie destructor, având acelaşi
nume cu al clasei, fără argumente, care nu întoarce nici o valoare. Pentru a face distincţie faţă de constructor,
numele destructorului începe cu ~.

Un destructor pentru un obiect este apelat în mod implicit la ieşirea din domeniul de definiţie al obiectului
sau la sfârşitul programului, pentru obiectele globale sau statice. Sunt foarte rare situaţiile în care
programatorul apelează explicit un destructor. Dacă o clasă nu are definit destructor, compilatorul generează
un destructor implicit.

Programatorul nu apelează în mod explicit constructorii sau destructorul. Constructorii se apelează în mod
implicit la declararea obiectelor. Destructorul este apelat implicit la ieşirea din blocul în care este declarat
obiectul.

Obiectele create (cu new[])de către un constructor explicit trebuiesc distruse (cu delete []) de către
un destructor definit în mod explicit.

La definirea unei funcţii constructor sau destructor nu trebuie definit tipul rezultatului întors de funcţie
(nici măcar void).

Iniţializarea obiectului de către constructor se poate face prin iniţializarea datelor membre cu valorile
parametrilor sau prin copierea datelor membre ale unui obiect transmis ca parametru. În acest din urmă caz,
avem de a face cu un constructor de copiere.

Un obiect poate fi iniţializat cu o copie a unui alt obiect al clasei sub forma clasa ob1(ob2);

De exemplu:

Complex z1(1., -2.), z2(z1);

O operaţie de tipul Clasa ob = Clasa(initializare) prin intermediul constructorului, crează


un obiect şi îi iniţializează datele membre. Exemplu:
Complex z2 = Complex(1., -2.);

are acelaşi efect cu declaraţia precedentă. Spre deosebire de acesta:

Complex z2;

Z2 = Complex(1., -2.);

crează un obiect temporar, pe care îl copiază într-un obiect existent.

Copierea unui obiect folosind operatorul de atribuire realizează o copiere membru cu membru a obiectului
sursă (din dreapta) în obiectul destinaţie (din stânga)

Data d=d1; //atribuire

Data *pd = new Data(d1); //creare obiect anonim initializat

Copia se face membru cu membru. Obiectul ce urmează a fi copiat este transmis prin referinţă.
Compilatorul va genera pentru clasa C, un constructor implicit de copiere: C::C(const C&); care
realizează copierea datelor membru cu membru (copiere superficială). Exemplu:

punct p1;// apeleaza constructorul implicit scris de programator

.. // care asigura initializarea la (0, 0)

punct p2(2,5); //initializare explicita (ctor de initializare)

punct p3(p2); //initializare prin copiere (ctor de copiere)

Constructorul implicit de copiere nu funcţionează corect în caz că obiectul alocă dinamic memorie. În
acest caz programatorul trebuie să-şi definească propriul constructor de copiere, care să realizeze o copiere
profundă, a datelor indicate de pointeri şi nu o copiere a pointerilor.

Obiectele clasei pot fi copiate prin atribuire. Copierea se face membru cu membru. Exemplu:

Complex z1(-1., 2.);

Complex z = z1;

Data d1=Data(2004,10,5);

Iniţializarea este mai eficientă decât atribuirea. În cazul atribuirii există o valoare (veche) a obiectului,
care trebuie ştearsă şi prin atribuire se copiază valoarea nouă a obiectului din dreapta operatorului de
atribuire.

Vom considera un exemplu în care se alocă dinamic memorie pentru datele membre. Fie clasa
persoana, prevăzută cu mai mulţi constructori şi un destructor:

class persoana{

private:

char* nume;

char* adresa;
char* cnp;

public:

persoana(char* n=””,char* a=””,char* c=””);

persoana(persoana& p);

~persoana();

};

persoana::persoana(char* n, char* a, char* c){

nume=new char[strlen(n)+1];

strcpy(nume, n);

adresa=new char[strlen(a)+1];

strcpy(adresa, a);

cnp=new char[strlen(c)+1];

strcpy(cnp, c);

};

persoana::persoana(persoana& p){

nume=new char[strlen(p.nume)+1];

strcpy(nume, p.nume);

adresa=new char[strlen(p.adresa)+1];

strcpy(adresa, p.adresa);

cnp=new char[strlen(p.cnp)+1];

strcpy(cnp, p.cnp);

};

persoana::~persoana(){

delete [] nume;

delete [] adresa;

delete [] cnp;

};
Dacă o clasă conţine date membre constante sau referinţe, acestea nu pot fi iniţializate în corpul
constructorului, ci printr-o listă de iniţializare plasată după antetul constructorului şi separată de acesta prin

Constructorii şi destructorii sunt apelaţi implicit la definirea, respectiv distrugerea obiectelor. Obiectele
sunt construite în ordinea declarării lor şi sunt distruse în ordine inversă declarării.

Clasele care conţin membri const şi membri referinţe trebuie să aibe un constructor definit de
programator, întrucât referinţele trebuiesc iniţializate.

În absenţa unor declarări explicite, compilatorul furnizează următoarele funcţii membre:

- un constructor implicit

- un constructor de copiere

- un operator de atribuire

- un destructor implicit

- un operator de adresare

7. Pointerul this.

O funcţie care operează asupra unei structuri, o va accesa prin intermediul unui parametru pointer la
această structură. O funcţie membru al unei clase, apelată dintr-un obiect, pentru a accesa membrii date ai
obiectului, va avea un parametru implicit pointer la obiect. Acest pointer poartă numele de pointerul this, şi
se declară într-o clasă C ca:

C* const this;

Fiind cuvânt cheie, el nu poate fi declarat explicit; fiind un pointer constant el nu poate fi modificat.
Referirea la datele membre x0 şi y0 se face prin intermediul acestui pointer ascuns ca this->x0 şi this-
>y0. O funcţie membru care returnează un obiect o poate face prin return *this (returnarea unui
pointer la obiect se face prin return this).

8. Membri statici ai claselor.

În fiecare obiect, pentru datele membre nestatice se crează copii. Pentru funcţiile membre există o copie
unică.

Pentru datele membre statice există o singură copie care va fi partajată de către toate obiectele.

Funcţiile membre statice sunt accesibile numai în fişierul în care sunt definite, fiind independente de
obiecte (instanţele claselor).

O funcţie statică poate fi apelată:

 ca funcţie membră a unui obiect


 independent de obiect, folosind operatorul de rezoluţie.

Pointerul this nu poate fi folosit de către funcţia statică, deoarece nu îi este transmis, aşadar funcţia
statică nu poate accesa decât date membre statice şi date globale.

Clasa Data depinde de variabila statică azi. Aceasta va aparţine clasei, dar nu şi obiectelor clasei.
class Data{

int a, l, z;

static Data azi; //data membra statica

public:

Data(int aa=0, int ll=0, int zz=0);

. . .

static void set_data_crta(int aa, int ll, int zz);

};

Data::Data(int aa, int ll, int zz){

a = aa? aa : azi.a;

l = ll? ll : azi.l;

z = zz? Zz : azi.z;

};

Funcţia set_data_crta() permite modificarea datei curente, care iniţializează un obiect neiniţializat.
La definirea unui membru static nu se mai repetă cuvântul static.

void Data::set_implic(int a, int l, int z){

azi = Data(a, l, z);

};

9. Funcţii membre constante.

Funcţiile membre care nu modifică starea obiectului se declară ca funcţii constante (funcţii const),
punând după lista de parametri cuvântul cheie const. Compilatorul va sesiza orice încercare de modificare
a obiectului din funcţia const.

Funcţiile membre apelate din obiectele constante pot fi numai funcţii constante.

Dacă funcţia membră const este definită în afara clasei, este necesar sufixul const în definiţie.
Exemplu:

class Data{

int a, l, z;

public:

int zi()const {return z;};

int luna()const {return l;};


int an()const;

};

inline int Data::an()const{ return a;};

Nu este posibilă declararea unei constante simbolice în interiorul unei clase. Astfel:

class Vector{

const int N=10;

double x[N];

. . .

nu funcţionează corect deoarece declararea clasei descrie obiectul, dar nu îl crează. Sunt posibile două
soluţii:

- se declară o enumerare în interiorul clasei, care furnizează la nivelul clasei nume simbolice pentru constante
întregi :

class Vector{

enum{ N=10 };

double x[N];

. . .

- se crează o constantă statică, partajată de toate obiectele Vector

class Vector{

static const int N=10;

double x[N];

. . .

10. Funcţii care returnează referinţe.

Pentru clasa complex, am definit funcţiile care asigură accesul partea reală, respectiv imaginară a unui
număr complex,:

double real(){ return re; };

double imag(){ return im; };


Dacă am dori modificarea părţii reale a unui număr complex printr-o atribuire de forma:

z.real()=5.;

constatăm că funcţia astfel definită nu poate apărea în partea stângă a unei atribuiri (nu este o L-valoare).
Acest neajuns se remediază impunând funcţiei să returneze o referinţă la obiect, adică:

double& real(){ return re; };

Funcţiile de actualizare a datei ad_a(), ad_l(), ad_z() nu întorc valori. Pentru asemenea funcţii
este util să întoarcem o referinţă la obiectul actualizat, astfel încât operaţiile să poată fi înlănţuite de forma:

d.ad_a(1).ad_l(1).ad_z(1);

Funcţiile se declară cu valoare de întoarcere referinţă la Data:

class Data{

Data& ad_a(int);

Data& ad_l(int);

Data& ad_z(int);

};

Data& ad_a(int n){

if(z==29 && l==2 && !bisect(a+n)){

z=1;

l=3;

};

a += n;

return *this;

Într-o funcţie membru nestatică this este un pointer la obiectul din care s-a apelat funcţia membru. Nu
este posibil să preluăm adresa lui this şi nici să o modificăm.

Cele mai multe referiri la this sunt implicite. Orice referire la un membru nestatic din interiorul clasei
foloseşte în mod implicit pe this pentru a obţine membrul din acel obiect.

O clasă conţine în mod uzual: un constructor, funcţii pentru examinarea obiectelor (funcţii de acces),
funcţii pentru manipularea obiectelor, operaţii de copiere, etc.

11. Probleme.

1. Definiţi şi implementaţi clasa Cerc, având ca dată membrău Raza, un constructor şi ca funcţii
membre Aria şi Circumferinta.
2. Definiţi şi implementaţi clasa Cilindru, având ca date membre Raza şi Inaltimea cilindrului şi ca
funcţii membre: un constructor, Aria şi Volumul.

3. Definiţi şi implementaţi clasa Televizor, având ca date membre: Volumul şi Canalul şi ca funcţii
membre: un constructor, Pornire, Oprire, Schimbare_Volum, Schimbare_Canal.

4. Definiţi şi implementaţi clasa Dreptunghi, având ca date membre: Lungimea şi Laţimea şi ca


funcţii membre: un constructor, SetLungime, SetLaţime, GetLungime, GetLaţime, Arie
şi Perimetru.

5. Specificaţi, proiectaţi şi implementaţi o clasă care poate fi folosita într-un program pentru a simula o
combinaţie de deschidere a unui seif.

Seiful are un buton circular cu numere de la 0 la 39 şi pentru deschiderea lui este necesară formarea unei
combinaţii de 3 numere, fie acestea x, y şi z.

Pentru deschiderea seifului, se roteste în sens orar butonul până la formarea numărului x, apoi se
roteste în sens antiorar până la formarea lui y, şi în final se formează z prin rotire în sens orar.

Clasa Seif va avea un constructor, care iniţializează combinaţia codului de deschidere cu 3 numere date
ca argumente (ca argumente implicite se iau 0,0,0).

Sunt prevăzute următoarele funcţii membre:

- modificarea codului de deschidere la o noua combinaţie de 3 numere

- rotirea butonului într-o directie dată, până când se formează un număr dat

- închiderea seifului

- încercarea de a deschide seiful

- testarea stării seifului (deschis sau închis)

- indicarea combinaţiei de cod încercate pentru deschidere.

6. Specificaţi, proiectaţi şi implementaţi o clasă Punct3, folosite pentru reprezentarea punctelor (prin 3
coordonate x,y,z) în spaţiul 3-dimensional.

Asiguraţi următoarele funcţii membre:

- constructor pentru setarea unui punct într-o poziţie specificată (implicit în 0,0,0)

- mutarea unui punct cu valori specificate pentru cele 3 direcţii

- aflarea coordonatelor unui punct

- rotaţia unui punct cu un unghi specificat de-a lungul uneia dintre axele x,y sau z.

Coordonatele noului punct x',y',z' sunt:

rotaţie în jurul axei x:

x'=x
y'=y*cos(t)-z*sin(t)

z'=y*sin(t)+z*cos(t)

rotaţie în jurul axei y:

x'=x*cos(t)+z*sin(t)

y'=y;

z'=-x*sin(t)+z*cos(t)

rotaţie în jurul axei z:

x'=x*cos(t)-y*sin(t)

y'=x*sin(t)+y*cos(t)

z'=z

..Se vor defini ca funcţii nemembre:

- distanţa între doua puncte

- testul dacă 2 puncte se confundă, prin redefinirea operatorului ==

- afişarea coordonatelor unui punct la ieşirea standard, prin supraîncărcarea operatorului <<

Se va defini ca funcţie prieten citirea coordonatelor unui punct de la intrarea standard, prin
supraîncărcarea operatorului >>

7. Proiectaţi şi implementaţi o clasă Aleator, care generează o secvenţă de numere întregi


pseudoaleatoare, folosind metoda congruenţei liniare.

In această metodă se folosesc 4 întregi: sămânţa, înmulţitorul, incrementul şi modulul. cu formula:

(samanta * înmultitor + increment) % modul

se generează câte un număr aleator, care devine sămânţă. În acest mod se generează "modul" numere
diferite.

Constructorul clasei are ca argumente: sămânţa iniţială, înmulţitorul,incrementul şi modulul.

Se vor prevedea funcţii membre pentru:

- schimbarea sămânţei

- generarea următorului număr din secvenţa de numere aleatoare

8. Definiţi şi implementaţi clasa Calendar având ca date membre: An, Luna, Zi, NumeZi
(enumerarea de Luni până Duminica) şi ca funcţii membre:

-un constructor

-obţinerea datei curente


-modificarea datei curente

-incrementarea datei curente

-afişarea datei curente.

Se va defini o functie prieten pentru citirea datei curente.

Se vor defini de asemeni funcţii nemembre cu 2 parametri date calendaristice:

- Anterior care întoarce rezultatul boolean true/false după cum data argument 1 este înaintea sau
după data argument 2 şi

- Interval care întoarce numărul de zile cuprins între cele două date argumente.

Supraîncărcarea operatorilor.

1. Supraîncărcare.

2. Funcţii prieten.

3. Modalităţi de supraîncărcare a operatorilor.

4. Operatori redefiniţi ca funcţii prieten.

5. Operatori redefiniţi ca funcţii membri.

6. Supraîncărcarea operatorului de atribuire.

7. Supraîncărcarea operatorului de indexare.

8. Supraîncărcarea operatorilor new şi delete.

9. Supraîncărcarea operatorului apel de funcţie.

10. Supraîncărcarea operatorilor de conversie.

11. Supraîncărcarea operatorilor << şi >>.

12. Probleme.

Operatorii sunt notaţii concise, infixate, pentru operaţii matematice uzuale. Limbajul C++, ca orice limbaj
de programare asigură un set de operatori pentru tipurile primitive. În plus, faţă de limbajul C, C++ oferă
posibilitatea asocierii operatorilor existenţi cu tipurile definite de utilizator . Astfel, prezintă interes
extinderea operatorilor în aritmetică complexă, algebra matricială, în lucrul cu şiruri de caractere, etc. Un
operator poate fi privit ca o funcţie, în care termenii sunt argumentele funcţiei (în lipsa operatorului +,
expresia a+b s-ar calcula apelând funcţia aduna(a,b)).

Limbajul C++ introduce următorii operatori noi: new şi delete- pentru gestiunea memoriei
dinamice, operatorul de rezoluţie (::) şi operatorii de acces la membri: .* şi ->*.

1. Supraîncărcare.
Prin supraîncărcarea unui operator, acesta este utilizat în contexte diferite, avînd aceeaşi semnificaţie
sau o semnificaţie diferită. Astfel operatorul + este supraîncărcat pentru adunarea întregilor şi a realilor;
operatorul << este supraîncărcat cu semnificaţia de deplasare la stînga, dacă termenii sunt întregi sau
inserare în flux, dacă un termen este un flux de date, iar celălalt un obiect.

Programatorul poate asocia noi semnificaţii operatorilor existenţi prin supraîncărcarea operatorilor.
Vom evita folosirea termenului redefinire, căruia îi vom da o semnificaţie diferită, în contextul moştenirii.

Anumiţi operatori pot fi utilizaţi cu orice tip de termeni (sunt deja supraîncărcaţi global de către
compilator). Aceştia sunt: new, delete, sizeof, ::, =, &, ->*, .*, ., ->.

Pot fi supraîncărcaţi următorii operatori:

+ - * / % ^ & | ~ ! = < >

+= -= *= /= %= ^= &= |= >>= <<= == != <= >=

&& || ++ -- ->* , -> << >> [] ()

new new[] delete delete[]

Nu pot fi supraîncărcaţi operatorii: ::, ., .*, ?:, sizeof.

Setul de operatori ai limbajul C++ nu poate fi extins prin asocierea de semnificaţii noi unor caractere,
care nu sunt operatori de exemplu nu putem defini operatorul **).

Prin supraîncărcarea unui operator nu i se poate modifica aritatea (astfel operatorul ! este unar şi poate
fi redefinit numai ca operator unar. De asemeni asociativitatea şi precedenţa operatorului se menţin.

La supraîncărcarea unui operator nu se pot specifica argumente cu valori implicite.

Operatorii supraîncărcaţi într-o clasă sunt moşteniţi în clasele derivate (excepţie face operatorul de
atribuire =).

Semnătura unei funcţii în care se supraîncarcă un operator este:

tip_rezultat operator#(listă_argumente);

2. Funcţii prieten.

În afara funcţiilor membre, datele private şi cele protejate mai pot fi accesate de funcţiile prieten
(friend) ale clasei. O funcţie este considerată prietenă al unei clase, dacă declararea funcţiei precedate
de specificatorul friend, apare în declararea clasei,. Definiţia funcţiei prieten se face global, în afara
clasei.

Dacă o funcţie are ca parametri obiecte aparţinând la două clase diferite, atunci ea poate fi
declarată ca funcţie membru a unei clase şi prieten al celeilalte clase, sau ca funcţie prietenă ambelor clase.

De exemplu fie o funcţie de înmulţire a unei matrice cu un vector. Parametrii funcţiei vor fi două
obiecte – o matrice şi un vector. Clasele respective Mat şi Vec pot declara fiecare, funcţia MatVec() ca
prietenă:

class Mat{
int n;

double **m;

public:

Mat();

Mat(Mat&);

. . .

friend Vec MatVec(Mat&, Vec&);

};

class Vec{

int n;

double *v;

public:

Vec();

Vec(Vec&);

. . .

friend Vec MatVec(Mat&, Vec&);

};

De asemenea funcţiile prieten se utilizează în contextual supraîncărcării operatorilor.

Declararea unei funcţii prieten poate fi făcută în orice parte a clasei (publică, privată sau protejată).

O funcţie prietenă a unei clase poate accesa toţi membrii clasei. Dacă o clasă este prietenă cu altă
clasă, membrii ei pot accesa membrii celeilalte clase.

3. Modalităţi de supraîncărcare a operatorilor.

O funcţie operator supraîncărcată poate fi introdusă ca funcţie membră (în general nestatică) sau funcţie
prieten (funcţie nemembră).

Declararea unei funcţii membră nestatică specifică următoarele:

1) funcţia poate accesa partea privată a declaraţiei clasei


2) funcţia este în domeniul clasei
3) funcţia trebuie apelată dintr-un obiect

O funcţie membră statică satisface numai primele două condiţii, în timp ce o funcţie prietenă a unei
clase satisface numai prima condiţie.
O funcţie operator având ca prim argument un tip primitiv nu poate fi funcţie membru.

Funcţiile care modifică reprezentarea unui obiect necesită acces la datele membri, deci trebuie să aparţină
clasei. Ele sunt funcţii membrecu argumente referinţe neconstante.

Dacă se doresc conversii implicite pentru termenii funcţiei operator, aceasta va fi funcţie nemembră cu
argumente referinţe constante. Aceste funcţii implementează operatorii care nu necesită operanzi L-valori
(adică se aplică tipurilor fundamentale). Aceştia sunt în general operatorii binari. Necesitatea accesului la
reprezentare determină definirea lor ca funcţii prieten.

Operatorii care produc o nouă valoare din valorile argumentelor (de exemplu operatorul+) se definesc în
afara clasei.

Dacă nu sunt necesare conversii de tip, alegerea între funcţie membru şi funcţie prieten rămâne la
latitudinea programatorului.

O funcţie nemembră foloseşte numai argumente explicite, în timp ce o funcţie membră foloseşte
argumentul implicit this.

Argumentele clase, transmise prin valoare se copiază în stivă neeconomic. Se preferă în acest caz
argumentele referinţe constante.

Dacă valoarea întoarsă de funcţie este o referinţă, atunci aceasta nu poate fi o variabilă automatică
(locală funcţiei). Ea nu poate fi nici o variabilă statică locală, deoarece operatorul poate fi folosit de mai
multe ori într-o expresie. Valoarea de întoarcere trebuie să fie alocată în memoria liberă (heap) sau să fie
preluată dintr-un buffer de obiecte statice.

Întoarcerea unei referinţe la obiectul modificat se realizează prin return *this.

Se caută minimizarea numărului de funcţii care au acces la reprezentarea internă a unei clase, prevăzându-
se funcţii de acces.

În mod obligatoriu sunt funcţii membre: constructorii, destructorii, funcţiile virtuale, etc.

4. Operatori supraîncărcaţi ca funcţii prieten.

Operatorii folosiţi în mod uzual pot fi unari sau binari. Utilizarea unui operator binar sub forma a#b
este interpretată ca operator#(a,b)

Aşadar, un operator binar va fi reprezentat printr-o funcţie nemembră cu două argumente, iar un
operator unar, printr-o funcţie nemembră cu un singur argument.

Argumentele se iau clase sau referinţe constante la clase (pentru o preluare economică, asigurând
protecţia datelor)

Vom exemplifica pentru clasa Cplx:

class Cplx{

double re, im;

public:

Cplx(double x=0, double y=0);


Cplx(const Cplx& z);

//operatori binari

friend Cplx operator+(const Cplx& s, const Cplx& d);

friend Cplx operator-(const Cplx& s, const Cplx& d);

friend Cplx operator*(const Cplx& s, const Cplx& d);

friend Cplx operator/(const Cplx& s, const Cplx& d);

//operatori de comparatie

friend int operator==(const Cplx& s, const Cplx& d);

friend int operator!=(const Cplx& s, const Cplx& d);

//operatori unari

friend Cplx operator-(const Cplx& z);

friend Cplx operator!(const Cplx& z); //conjugat

friend Cplx& operator++(Cplx& z);..//prefix

friend Cplx operator—-(Cplx& z,int); //postfix

};

//definitii operatori în afara domeniului clasei

Cplx operator+(const Cplx& s, const Cplx& d){

return Cplx(s.re+d.re,s.im+d.im);

};

Cplx operator-(const Cplx& s, const Cplx& d){

return Cplx(s.re-d.re,s.im-d.im);

};

Cplx operator*(const Cplx& s, const Cplx& d){

return Cplx(s.re*d.re-s.im*d.im,s.re*d.im+s.im*d.re);

};

Cplx operator/(const Cplx& s, const Cplx& d){

double t=d.re*d.re+d.im*d.im;

return Cplx((s.re*d.re+s.im*d.im)/t ,
...... (s.im*d.re-s.re*d.im)/t);

};

int operator==(const Cplx& s, const Cplx& d){

return s.re==d.re && s.im==d.im;

};

int operator!=(const Cplx& s, const Cplx& d){

return s.re!=d.re || s.im!=d.im;

};

Cplx operator-(const Cplx& z){

return Cplx(-z.re, -z.im);

};

Cplx operator!(const Cplx& z){

return Cplx(z.re, -z.im);

};

Cplx& operator++(Cplx& z){..//prefix

..return Cplx(z.re+1, z.im);

};

Cplx operator++(Cplx& z,int){ //postfix

Cplx t(z);

z.re+=1;

return t;

};

Pentru a face deosebirea între semnăturile funcţiilor operatori de incrementare prefix şi postfix s-a
introdus un argument fictiv pentru cel din urmă.

Incrementarea prefix întoarce valoarea incrementată, deci trebuie să fie o L-valoare (rezultatul întors va
fi o referinţă), în timp ce postincrementarea întoarce valoarea dinaintea incrementării.

5. Operatori supraîncărcaţi ca funcţii membri.

Funcţiilor membru li se transmite un argument implicit (ascuns) this (adresa obiectului, care poate
reprezenta primul termen), motiv pentru care un operator binar poate fi implementat printr-o funcţie
membru nestatică cu un singur argument (termenul din dreapta operatorului). a#b este interpretat ca
a.operator#(b)

O funcţie membru operator unar ca o funcţie membru nestatică fără argumente ( #a se


interpretează ca a.operator#(); pentru operatorul postfixat a# convenţia este
a.operator#(int) ).

În acest context, operatorii clasei Cplx se scriu:

Class Cplx{

double re, im;

public:

Cplx operator+(const Cplx& d);

Cplx operator-(const Cplx& d);

Cplx operator*(const Cplx& d);

Cplx operator/(const Cplx& d);

Cplx& operator+=(const Cplx& d);

Cplx& operator-=(const Cplx& d);

Cplx& operator*=(const Cplx& d);

Cplx& operator/=(const Cplx& d);

int operator==(const Cplx& d);

int operator!=(const Cplx& d);

Cplx& operator-();

Cplx& operator!();

Cplx& operator++();

Cplx operator++(int);

};

Cplx Cplx::operator+(const Cplx& d){

return Cplx(re+d.re, im+d.im);

};

Cplx Cplx::operator-(const Cplx& d){

return Cplx(re-d.re, im-d.im);


};

Cplx Cplx::operator*(const Cplx& d){

return Cplx(re*d.re-im*d.im, re*d.im+im*d.re);

};

Cplx Cplx::operator/(const Cplx& d){

double t=d.re*d.re+d.im*d.im;

return Cplx((re*d.re+im*d.im)/t, (im*d.re-re*d.im)/t);

};

Cplx& Cplx::operator+=(const Cplx& d){

if(this!=&d){

re+=d.re;

im+=d.im;

};

return *this;

};

Cplx& Cplx::operator-=(const Cplx& d){

if(this!=&d){

re-=d.re;

im-=d.im;

};

return *this;

};

Cplx& Cplx::operator*=(const Cplx& d){

if(this!=&d){

double t=re;

re=re*d.re-im*d.im;

im=t*d.im+im*d.re;

};
return *this;

};

Cplx& Cplx::operator/=(const Cplx& d){

if(this!=&d){

double t1=re, t2=d.re*d.re+d.im*d.im;

re=(re*d.re+im*d.im)/t2;

im=(im*d.re-t1*d.im)/t2;

};

return *this;

};

int Cplx::operator==(const Cplx& d){

return re==d.re && im==d.im;

};

int Cplx::operator!=(const Cplx& d){

return re!=d.re || im!=d.im;

};

Cplx& Cplx::operator-(){

re=-re;

im=-im;

return *this;

};

Cplx& Cplx::operator!(){

im=-im;

return *this;

};

Cplx& Cplx::operator++(){

re++;

return *this;
};

Cplx Cplx::operator++(int){

Cplx t(re,im);

re++;

return t;

};

6. Supraîncărcarea operatorului de atribuire.

Operaţia de atribuire simplă, dacă nu este supraîncărcată, realizează o copiere membru cu


membru.

Pentru obiectele care nu conţin date alocate dinamic la iniţializare, atribuirea prin copiere
membru cu membru funcţionează corect, motiv pentru care nu se supraîncarcă operatorul de
atribuire.

Pentru clasele ce conţin date alocate dinamic, copierea membru cu membru, executată în mod
implicit la atribuire conduce la copierea pointerilor la datele alocate dinamic, în loc de a copia
datele.

Operatorul de atribuire poate fi redefinit numai ca funcţie membră, el fiind legat de obiectul din
stânga operatorului =, o L-valoare, motiv pentru care va întoarce o referinţă la obiect.

O primă cerinţă la scrierea funcţiei operator= este evitarea autoatribuirii a=a. În acest scop se
efectuează testul this != &d (unde d este parametrul funcţiei -obiectul din dreapta operatorului
=.

Urmează apoi “curăţirea” membrului stâng (eliberarea memoriei alocate dinamic). Copierea
propriu zisă este făcută de obicei folosind constructorul de copiere.

Funcţia operator returnează o referinţă la obiectul modificat (return *this).

Exemplificăm pentru clasa String, definită astfel:

class String{

char* s;

int n;

public:

String(); //c(onstruc)tor implicit

String(const char* p); //ctor de initializare

String(const String& r); //cr de copiere

~String(); //dtor
String& operator=(const String& d);

String& operator=(const char* p);

};

//ctor de copiere

String::String& operator=(const String& d){

if(this != &d){ //evitare autoatribuire

if(s).... //curatire

.. delete [] s;

n=d.n;.... //copiere

s=new char[n];

strncpy(s, d.s, n);

};

return *this;.. //intoarce obiectul modificat

};

//ctor de initializare (cu un sir tip C)

String::String& operator=(const char* p){

if(s)

.. delete [] s;

n=strlen(p);

s=new char[n];

strncpy(s, p, n);

return *this;

};

7. Supraîncărcarea operatorului de indexare.

Operatorul de indexare este un operator binar, având ca prim termen obiectul care se indexează, iar
ca al doilea termen indicele: obiect [ indice ] şi este interpretat ca: obiect.operator[
](indice) .

Funcţia operator de indexare trebuie să fie funcţie membră nestatică.

Primul termen, transmis implicit prin this este obiectul asupra căruia se execută indexarea.
Argumentul funcţiei reprezintă indicele, care poate fi de orice tip, spre deosebire de indicii predefiniţi şi este
al doilea termen din operator.

Valoarea întoarsă de funcţia operator este o referinţă la elementul indexat din obiect. Exemplificăm
cu operatorul de indexare pentru clasa String:

class String{

int n;

char* s;

public:

. . .

char& operator[](int);

};

char& String:: operator[](int i){

if(i>=0 && i<n)

return s[i];

else

return 0;

};

8. Supraîncărcarea operatorilor new şi delete.

Operatorii new şi delete pot fi supraîncărcaţi ca funcţii operatori membre statice.

Funcţia operator new are semnătura: void* operator new(size_t); Operatorul redefinit
apelează constructorul clasei după alocarea dinamică pe care o realizează.

Funcţia operator delete are semnătura: void operator delete(void*); unde


parametrul este un pointer la obiectul eliminat. Operatorul redefinit delete apelează întotdeauna
destructorul clasei înainte de a elibera memoria alocată dinamic.

Dacă în clasa C se supraîncarcă operatorul new, atunci versiunea furnizată de sistem se obţine prin
C *p = ::new C();

Exemplu1:Supraîncărcaţi operatorul new, astfel încât să iniţializeze memoria alocată la 0.

void* C::operator new(size_t dim){

void *p = new char[dim];

return memset(p, 0, sizeof(C));


}

Exemplu1:Supraîncărcaţi operatorul new[], astfel încât să iniţializeze memoria alocată la o


valoare dată ca parametru.

void* C::operator new[](size_t dim, unsigned v){

int n = dim / sizeof(C);

C *p = ::new C[n];

for(int i=0; i<n; i++)

p[i].val = v; //clasa are data membra val

return p;

9. Supraîncărcarea operatorului apel de funcţie.

Apelul unei funcţii este o construcţie de forma:

nume_functie(lista_argumente);

desemnând operatorul binar() aplicat celor doi termeni nume_functie şi lista_argumente.

Numele funcţiei poate fi înlocuit printr-un pointer la funcţie:

(*pf)(lista_argumente);

Funcţia operator redefinit apel de funcţie va avea aşadar 2 parametri: un obiect şi o listă de argumente.
Implementată ca funcţie membră nestatică, primul parametru este transmis implicit, astfel că un apel de
forma:

obiect(lista_argumente);

este interpretat ca:

obiect.operator()(lista_argumente);

Supraîncărcarea operatorului () se utilizează la definirea functorilor (obiecte funcţii). Un functor este


un obiect pentru care se supraîncarcă operatorul apel de funcţie (operator()()) şi care se comportă ca
o funcţie.

În mod uzual obiectele funcţii se folosesc în locul pointerilor la funcţii. Funcţiile inline nu pot folosi ca
parametri pointeri la funcţii (care sunt apelaţi indirect), ci obiecte funcţii.

Operatorul apel de funcţie asigură o sintaxă uniformă pentru obiectele care se comportă ca şi funcţiile.

Algoritmii generici din STL au printre argumente şi o biecte funcţii. Acestea pot fi:

- generatori = functori fără argumente


- funcţii unare = functori cu un argument

- funcţii binare = functori cu două argumente.

O funcţie unară booleană este un predicat, în timp ce o funcţie binară booleană este un predicat binar.

De exemplu, pentru o dreaptă, definită prin tăietura în origine şi pantă putem defini clasa:

class Dreapta{

double m,y0;

public:

Dreapta(double m1=1, double y01=0): m(m1),y0(y01){} //ctor

double operator()(double x){ return y0+m*x; }.. //functor

};

Clasa va fi folosită într-o funcţie main() astfel:

Dreapta d1;

Dreapta d2(2., 3.);

double y1=d1(5.5); //y1=5.5

double y2=d2(1.5); //y2=2*1.5+3=6

Definiţi un obiect funcţie (functor) care generează termenul de rang n din şirul lui Fibonacci.

class Fibo{

public:

Fibo():x(0),y(1){}; //constructor

long operator()(int n); //supraincarcare operator functie

private:

long x, y;

};

long Fibo::operator ()(int n){

for(int i=2; i<=n; i++){

long z=x+y;

x=y;

y=z;
};

return x;

};

10. Supraîncărcarea operatorilor de conversie.

În C++ la evaluarea unei expresii se efectuează conversii implicite pentru tipurile predefinite. Deoarece
nu există conversii implicite de la o clasă la un tip predefinit, programatorul îşi poate defini conversii
explicite prin supraîncărcarea unui operator de conversie al clasei. Astfel pentru clasa C, funcţia membru
nestatică de conversie C::operator T() unde T este un nume de tip primitiv realizează conversia de
la tipul C la tipul T. Aceasta nu specifică nici un tip de rezultat întors, deoarece se returnează implicit
valoarea obiectului convertită la tipul pentru care este definită conversia şi nu are parametri.

Funcţia operator primeşte ca prim parametru implicit adresa obiectului şi întoarce valoarea convertită
de tipul numelui funcţiei operator de conversie.

Apelul explicit al funcţiei de conversie de la un obiect din clasa C la tipul primitiv T se specifică prin
T(obiect) sau (T) obiect.

Pentru conversia unui obiect între două clase C1 şi C2,C1->C2 se defineşte funcţia membru operator de
conversie C1::operator C2(); Pentru ca această funcţie să aibă acces la membrii clasei C2, se
declară funcţie prieten clasei C2.

Sunt premise astfel conversii între o clasă şi un tip predefinit sau între două clase, dar nu de la un tip
predefinit la o clasă.

Constructorii cu un singur argument pot fi folosiţi ca funcţii de conversie de la tipul argumentului la clasa
constructorului. Acest efect nu este întotdeauna dorit, şi pentru a-l evita, constructorul se declară precedat de
cuvântul cheie explicit.

Dacă o metodă a unei clase foloseşte parametri care suferă conversii implicite, metoda se va defini ca funcţie
prietenă, nu ca funcţie membră.

Definim ca exemplu clasa Inch:

class Inch{

double inch;

double cm;

public:

explicit Inch(double i=0.) : inch(i){}; //ctor

void Inch_cm(){ cm=2.54*inch;};

operator int() const {return int(inch+0.5);}; //fctie conversie

11. Supraîncărcarea operatorilor << şi >>.


Operatorii de intrare / ieşire pot fi supraîncărcaţi. Operatorul de extracţie >>, membru al clasei
istream serveşte pentru extragerea datelor de tipuri predefinite din fluxul de intrare cin. Prin
supraîncărcarea operatorului putem extrage din fluxul de intrare f, obiecte ob, scriind f >> ob;

Funcţia operator corespunzătoare are semnătura:

istream& operator >> (istream& f, clasa & ob);

Operatorul de inserţie <<, membru al clasei ostream serveşte pentru depunerea datelor de tipuri
predefinite în fluxul de ieşire cout. Supraîncărcarea operatorului permite depunerea de obiecte ob în fluxul
de ieşire f cu f << ob;

ostream& operator << (ostream& f, const clasa & ob);

Întrucât ambele funcţiile operatori întorc referinţe la fluxuri (prin return f), operatorii pot fi
înlănţuiţi.(f << o1 << o2;)

Funcţiile operator pentru supraîncărcarea operatorilor de intrare / ieşire se declară funcţii prieten al clasei
care interacţionează cu fluxul.

Exemplificăm cu clasele Cplx şi String:

class Cplx{

double re,im;

public:

friend ostream& operator<<(ostream& os, const Cplx& z);

friend istream& operator>>(istream& is, Cplx& z);

};

ostream& operator<<(ostream& os, const Cplx& z){

os << “(“ << z.re <<”,” << z.im << “)” << endl;

return os;

};

istream& operator>>(istream& is, Cplx& z){

is >> z.re >> z.im;

return is;

};

class String{

int n;
char *s;

public:

. . .

friend ostream& operator<<(ostream&os,const String& w);

friend istream& operator>>(istream& is, String& w);

};

ostream& operator<<(ostream& os,const String& w){

for(int i=0; i<w.n; i++)

os << w.s[i];

return os;

};

istream& operator>>(istream& is, String& w){

char zona[100];

is.get(zona,100);

w=zona;

return is;

};

12. Probleme.

1. O expresie pătratică de o variabilă are forma: ax2+bx+c în care numerele a,b,c (coeficienţii) au
valori fixate, iar variabila x poate lua diferite valori.

Specificaţi,proiectaţi şi implementaţi clasa Patrat, care poate păstra informaţii

asupra unei expresii pătratice.

Un constructor implicit setează cei 3 coeficienţi la zero.

Se vor prevedea funcţii membru pentru:

- schimbarea coeficienţilor

- aflarea valorii curente a coeficienţilor

- evaluarea unei expresii pătratice pentru un x dat

- determinarea numărului de rădăcini reale a unei expresii pătratice


Se vor redefini operatorii + şi * ca funcţii nemembre pentru: adunarea a două expresii pătratice:

Patrat operator +(const Patrat &p1, const Patrat &p2);

- înmulţirea unei expresii pătratice cu o constantă

Patrat operator *(double k, const Patrat &p);

2. Proiectaţi şi implementaţi clasa Complex care să permită lucrul cu numere complexe.

Constructorul clasei va avea ca argumente partea reală, respectiv imaginară a numărului complex (în mod
implicit aceste valori se iau 0).

Se va asigura un constructor de copiere.

Se vor prevedea funcţii membri pentru:

- accesul la partea reală, respectiv imaginară a numărului complex

- supraîncărcarea operatorilot +=, -=, *=, /= pentru adunarea, scăderea, înmulţirea şi împărţirea
numărului complex cu un alt număr complex dat ca argument

- modulul numărului complex

- argumentul numărului complex

Se va redefini operatorul >> ca funcţie prieten pentru citirea unui număr complex de la intrarea standard

Se vor asigura funcţii nemembru pentru:

- testul de egalitate a două numere complexe (supraîncărcarea operatorului ==)

- scrierea unui număr complex la ieşirea standard (supraîncărcarea operatorului <<)

- supraîncărcarea operatorilor +,-,*,/ pentru a permite operaţii cu două argumente numere complexe

- supraîncărcarea operatorului de atribuire

3. Proiectaţi şi implementaţi clasa Raţional care să permită lucrul cu numere raţionale.

Constructorul clasei va avea două argumente: număratorul, respectiv numitorul numărului raţional
(constructorul poate avea şi un singur argument,caz în care al doilea se ia implicit 1,sau nici un argument, caz
în care se iau valorile implicite 0 şi 1).

Se va asigura un constructor de copiere.

Se vor prevedea funcţii membri pentru:

- accesul la număratorul, respectiv numitorul numărului raţional

- supraîncărcarea operatorilot +=, -=, *=, /= pentru adunarea, scăderea, înmulţirea şi împărţirea
numărului raţional cu un alt număr raţional dat ca argument

Se va redefini operatorul >> ca funcţie prieten pentru citirea unui număr raţional de la intrarea standard
Se vor asigura funcţii nemembre pentru:

- testul de egalitate a două numere raţionale (supraîncărcarea operatorului ==)

- scrierea unui număr raţional la ieşirea standard (supraîncărcarea operatorului <<)

- supraîncărcarea operatorilor +,-,*,/ pentru a permite operaţii cu două argumente numere raţionale.

- supraîncărcarea operatorului de atribuire.

4. Proiectaţi şi implementaţi clasa Vector care să permită lucrul cu vectori de elemente reale.

Constructorul clasei va avea un argument - dimensiunea vectorului şi va aloca emorie

pentru vector (în lipsa argumentului se ia implicit dimensiunea 10)

Se va asigura un destructor şi un constructor de copiere.

Se vor prevedea funcţii membri pentru:

- determinarea dimensiunii vectorului

- determinarea lungimii vectorului

- supraîncărcarea operatorilor +=, -=, pentru adunarea şi scăderea vectorului cu un alt vector dat ca
argument

Se va redefini operatorul >> ca funcţie prieten pentru citirea unui vector de la intrarea standard

Se vor asigura funcţii nemembre pentru:

- testul de egalitate a doi vectori (supraîncărcarea operatorului ==)

- scrierea unui vector la ieşirea standard (supraîncărcarea operatorului <<)

- supraîncărcarea operatorilor +,- pentru a permite operaţii cu două argumente vectori

- supraîncărcarea operatorului * pentru a permite calculul produsului scalar a doi vectori

5. Proiectaţi şi implementaţi clasa Matrice care să permită lucrul cu matrice pătrate de elemente reale.

Constructorul clasei va avea un argument - dimensiunea, adică numărul de linii şi de coloane al matricei,
va aloca memorie pentru matrice (în lipsa argumentului se ia implicit dimensiunea 10) şi va permite accesul
la elementele individuale prin indexare.

Se va asigura un destructor şi un constructor de copiere.

Se vor prevedea funcţii membre pentru:

- determinarea dimensiunii matricei

- calculul determinantului matricii


- supraîncărcarea operatorilor +=, -=, *=, pentru adunarea, scăderea, şi înmulţirea unei matrice cu o altă
matrice dată ca argument

- supraîncărcarea operatorului /= pentru calculul inversei matricei

Se va redefini operatorul >> ca funcţie prieten pentru citirea unei matrici de la intrarea standard

Se vor asigura funcţii nemembre pentru:

- testul de egalitate a două matrice (supraîncărcarea operatorului ==)

- scrierea unei matrice la ieşirea standard (supraîncărcarea operatorului <<)

- supraîncărcarea operatorilor +,-,* pentru a permite operaţii cu două argumente matrice.

6. Proiectaţi şi implementaţi clasa String care să permită lucrul cu şiruri de caractere terminate cu nul.

Constructorul clasei va avea un argument-dimensiunea adică numărul de caractere al şirului şi va aloca


memorie pentru şir (în lipsa argumentului se ia implicit dimensiunea 80).

Se va asigura un destructor şi un constructor de copiere.

Se vor prevedea funcţii membre pentru:

- determinarea lungimii şirului

- supraîncărcarea operatorului += pentru concatenarea şirului cu un alt şir dat ca argument

- determinarea pozitiei primei apariţii a unui şir dat ca argument în şirul dat

Se va redefini operatorul >> ca funcţie prieten pentru citirea unui şir de caractere de la intrarea standard

Se vor asigura funcţii nemembre pentru:

- testul de egalitate a două şiruri (supraîncărcarea operatorului ==)

- comparaţia lexicografică a două şiruri date ca argumente (rezultat -1, 0 sau 1)

- scrierea unui şir la ieşirea standard (supraîncărcarea operatorului <<)

- supraîncărcarea operatorului + pentru a permite operaţia de concatenare a două şiruri date ca argumente

- supraîncărcarea operatorului de atribuire.

- selectarea dintr-un şir a unui alt şir definit prin poziţie de început şi lungime

- ştergerea dintr-un şir a unui număr de caractere începând cu o poziţie dată.

SUPRAÎNCĂRCAREA OPERATORILOR

Operatorii sunt notaţii concise, infixate, pentru operaţii matematice uzuale. Limbajul C++, ca orice limbaj de
programare asigură un set de operatori pentru tipurile primitive. În plus, faţă de limbajul C, C++ oferă
posibilitatea asocierii operatorilor existenţi cu tipurile definite de utilizator . Astfel, prezintă interes
extinderea operatorilor în aritmetică complexă, algebra matricială, în lucrul cu şiruri de caractere, etc. Un
operator poate fi privit ca o funcţie, în care termenii sunt argumentele funcţiei (în lipsa operatorului +,
expresia a+b s-ar calcula apelând funcţia aduna(a,b)).

Limbajul C++ introduce următorii operatori noi: new şi delete- pentru gestiunea memoriei dinamice,
operatorul de rezoluţie (::) şi operatorii de acces la membri: .* şi ->*.

1.SUPRAÎNCĂRCARE

Prin supraîncărcarea unui operator, acesta este utilizat în contexte diferite, avînd aceeaşi semnificaţie sau o
semnificaţie diferită. Astfel operatorul + este supraîncărcat pentru adunarea întregilor şi a realilor;
operatorul << este supraîncărcat cu semnificaţia de deplasare la stînga, dacă termenii sunt întregi sau
inserare în flux, dacă un termen este un flux de date, iar celălalt un obiect.

Programatorul poate asocia noi semnificaţii operatorilor existenţi prin supraîncărcarea operatorilor. Vom
evita folosirea termenului redefinire, căruia îi vom da o semnificaţie diferită, în contextul moştenirii.

Anumiţi operatori pot fi utilizaţi cu orice tip de termeni (sunt deja supraîncărcaţi global de către
compilator). Aceştia sunt:

new, delete, sizeof, ::, =, &, ->*, .*, ., ->.

POT FI SUPRAÎNCĂRCAŢI următorii operatori:

+ - * / % ^ & | ~ ! = < >

+= -= *= /= %= ^= &= |= >>= <<= == != <= >=

&& || ++ -- ->* , -> << >> [] ()

new new[] delete delete[]

NU POT FI SUPRAÎNCĂRCAŢI operatorii:

::, ., .*, ?:, sizeof.

Setul de operatori ai limbajul C++ nu poate fi extins prin asocierea de semnificaţii noi unor caractere, care
nu sunt operatori de exemplu nu putem defini operatorul **).

Prin supraîncărcarea unui operator nu i se poate modifica aritatea (astfel operatorul ! este unar şi poate fi
redefinit numai ca operator unar. De asemeni asociativitatea şi precedenţa operatorului se menţin.

La supraîncărcarea unui operator nu se pot specifica argumente cu valori implicite.

Operatorii supraîncărcaţi într-o clasă sunt moşteniţi în clasele derivate (excepţie face operatorul de
atribuire =).

Semnătura unei funcţii în care se supraîncarcă un operator este:

tip_rezultat operator#(listă_argumente);

2. FUNCŢII PRIETEN
În afara funcţiilor membre, datele private şi cele protejate mai pot fi accesate de funcţiile prieten (friend)
ale clasei. O funcţie este considerată prietenă al unei clase, dacă declararea funcţiei precedate de
specificatorul friend, apare în declararea clasei,. Definiţia funcţiei prieten se face global, în afara clasei.

Dacă o funcţie are ca parametri obiecte aparţinând la două clase diferite, atunci ea poate fi declarată ca
funcţie membru a unei clase şi prieten al celeilalte clase, sau ca funcţie prietenă ambelor clase.

De exemplu fie o funcţie de înmulţire a unei matrice cu un vector. Parametrii funcţiei vor fi două obiecte – o
matrice şi un vector. Clasele respective Mat şi Vec pot declara fiecare, funcţia MatVec() ca prietenă:

class Mat{

int n;

double **m;

public:

Mat();

Mat(Mat&);

. . .

friend Vec MatVec(Mat&, Vec&);

};

class Vec{

int n;

double *v;

public:

Vec();

Vec(Vec&);

. . .

friend Vec MatVec(Mat&, Vec&);

};

De asemenea funcţiile prieten se utilizează în contextual supraîncărcării operatorilor.

Declararea unei funcţii prieten poate fi făcută în orice parte a clasei (publică, privată sau protejată).

O funcţie prietenă a unei clase poate accesa toţi membrii clasei. Dacă o clasă este prietenă cu altă clasă,
membrii ei pot accesa membrii celeilalte clase.

3. MODALITĂŢI DE SUPRAÎNCĂRCARE A OPERATORILOR.


O funcţie operator supraîncărcată poate fi introdusă ca funcţie membră (în general nestatică) sau funcţie
prieten (funcţie nemembră).

Declararea unei funcţii membră nestatică specifică următoarele:

4) funcţia poate accesa partea privată a declaraţiei clasei


5) funcţia este în domeniul clasei
6) funcţia trebuie apelată dintr-un obiect

O funcţie membră statică satisface numai primele două condiţii, în timp ce o funcţie prietenă a unei clase
satisface numai prima condiţie.

O funcţie operator având ca prim argument un tip primitiv nu poate fi funcţie membru.

Funcţiile care modifică reprezentarea unui obiect necesită acces la datele membri, deci trebuie să aparţină
clasei. Ele sunt funcţii membrecu argumente referinţe neconstante.

Dacă se doresc conversii implicite pentru termenii funcţiei operator, aceasta va fi funcţie nemembră cu
argumente referinţe constante. Aceste funcţii implementează operatorii care nu necesită operanzi L-valori
(adică se aplică tipurilor fundamentale). Aceştia sunt în general operatorii binari. Necesitatea accesului la
reprezentare determină definirea lor ca funcţii prieten.

Operatorii care produc o nouă valoare din valorile argumentelor (de exemplu operatorul+) se definesc în
afara clasei.

Dacă nu sunt necesare conversii de tip, alegerea între funcţie membru şi funcţie prieten rămâne la latitudinea
programatorului.

O funcţie nemembră foloseşte numai argumente explicite, în timp ce o funcţie membră foloseşte argumentul
implicit this.

Argumentele clase, transmise prin valoare se copiază în stivă neeconomic. Se preferă în acest caz
argumentele referinţe constante.

Dacă valoarea întoarsă de funcţie este o referinţă, atunci aceasta nu poate fi o variabilă automatică (locală
funcţiei). Ea nu poate fi nici o variabilă statică locală, deoarece operatorul poate fi folosit de mai multe ori
într-o expresie. Valoarea de întoarcere trebuie să fie alocată în memoria liberă (heap) sau să fie preluată
dintr-un buffer de obiecte statice.

Întoarcerea unei referinţe la obiectul modificat se realizează prin return *this.

Se caută minimizarea numărului de funcţii care au acces la reprezentarea internă a unei clase, prevăzându-se
funcţii de acces.

În mod obligatoriu sunt funcţii membre: constructorii, destructorii, funcţiile virtuale, etc.
4. OPERATORI SUPRAÎNCĂRCAŢI CA FUNCŢII PRIETEN.

Operatorii folosiţi în mod uzual pot fi unari sau binari. Utilizarea unui operator binar sub forma a#b este
interpretată ca operator#(a,b)

Aşadar, un operator binar va fi reprezentat printr-o funcţie nemembră cu două argumente, iar un operator
unar, printr-o funcţie nemembră cu un singur argument.

Argumentele se iau clase sau referinţe constante la clase (pentru o preluare economică, asigurând protecţia
datelor)

Vom exemplifica pentru clasa Cplx:

class Cplx{

double re, im;

public:

Cplx(double x=0, double y=0);

Cplx(const Cplx& z);

//operatori binari

friend Cplx operator+(const Cplx& s, const Cplx& d);

friend Cplx operator-(const Cplx& s, const Cplx& d);

friend Cplx operator*(const Cplx& s, const Cplx& d);

friend Cplx operator/(const Cplx& s, const Cplx& d);

//operatori de comparatie

friend int operator==(const Cplx& s, const Cplx& d);

friend int operator!=(const Cplx& s, const Cplx& d);

//operatori unari

friend Cplx operator-(const Cplx& z);

friend Cplx operator!(const Cplx& z); //conjugat

friend Cplx& operator++(Cplx& z);..//prefix

friend Cplx operator—-(Cplx& z,int); //postfix

};

//definitii operatori în afara domeniului clasei

Cplx operator+(const Cplx& s, const Cplx& d){


return Cplx(s.re+d.re,s.im+d.im);

};

Cplx operator-(const Cplx& s, const Cplx& d){

return Cplx(s.re-d.re,s.im-d.im);

};

Cplx operator*(const Cplx& s, const Cplx& d){

return Cplx(s.re*d.re-s.im*d.im,s.re*d.im+s.im*d.re);

};

Cplx operator/(const Cplx& s, const Cplx& d){

double t=d.re*d.re+d.im*d.im;

return Cplx((s.re*d.re+s.im*d.im)/t ,

...... (s.im*d.re-s.re*d.im)/t);

};

int operator==(const Cplx& s, const Cplx& d){

return s.re==d.re && s.im==d.im;

};

int operator!=(const Cplx& s, const Cplx& d){

return s.re!=d.re || s.im!=d.im;

};

Cplx operator-(const Cplx& z){

return Cplx(-z.re, -z.im);

};

Cplx operator!(const Cplx& z){

return Cplx(z.re, -z.im);

};

Cplx& operator++(Cplx& z){..//prefix

..return Cplx(z.re+1, z.im);

};
Cplx operator++(Cplx& z,int){ //postfix

Cplx t(z);

z.re+=1;

return t;

};

Pentru a face deosebirea între semnăturile funcţiilor operatori de incrementare prefix şi postfix s-a introdus
un argument fictiv pentru cel din urmă.

Incrementarea prefix întoarce valoarea incrementată, deci trebuie să fie o L-valoare (rezultatul întors va fi o
referinţă), în timp ce postincrementarea întoarce valoarea dinaintea incrementării.

5. OPERATORI SUPRAÎNCĂRCAŢI CA FUNCŢII MEMBRI.

Funcţiilor membru li se transmite un argument implicit (ascuns) this (adresa obiectului, care poate
reprezenta primul termen), motiv pentru care un operator binar poate fi implementat printr-o funcţie
membru nestatică cu un singur argument (termenul din dreapta operatorului). a#b este interpretat ca
a.operator#(b)

O funcţie membru operator unar ca o funcţie membru nestatică fără argumente ( #a se interpretează ca
a.operator#(); pentru operatorul postfixat a# convenţia este a.operator#(int) ).

În acest context, operatorii clasei Cplx se scriu:

Class Cplx{

double re, im;

public:

Cplx operator+(const Cplx& d);

Cplx operator-(const Cplx& d);

Cplx operator*(const Cplx& d);

Cplx operator/(const Cplx& d);

Cplx& operator+=(const Cplx& d);

Cplx& operator-=(const Cplx& d);

Cplx& operator*=(const Cplx& d);

Cplx& operator/=(const Cplx& d);

int operator==(const Cplx& d);

int operator!=(const Cplx& d);


Cplx& operator-();

Cplx& operator!();

Cplx& operator++();

Cplx operator++(int);

};

Cplx Cplx::operator+(const Cplx& d){

return Cplx(re+d.re, im+d.im);

};

Cplx Cplx::operator-(const Cplx& d){

return Cplx(re-d.re, im-d.im);

};

Cplx Cplx::operator*(const Cplx& d){

return Cplx(re*d.re-im*d.im, re*d.im+im*d.re);

};

Cplx Cplx::operator/(const Cplx& d){

double t=d.re*d.re+d.im*d.im;

return Cplx((re*d.re+im*d.im)/t, (im*d.re-re*d.im)/t);

};

Cplx& Cplx::operator+=(const Cplx& d){

if(this!=&d){

re+=d.re;

im+=d.im;

};

return *this;

};

Cplx& Cplx::operator-=(const Cplx& d){

if(this!=&d){

re-=d.re;
im-=d.im;

};

return *this;

};

Cplx& Cplx::operator*=(const Cplx& d){

if(this!=&d){

double t=re;

re=re*d.re-im*d.im;

im=t*d.im+im*d.re;

};

return *this;

};

Cplx& Cplx::operator/=(const Cplx& d){

if(this!=&d){

double t1=re, t2=d.re*d.re+d.im*d.im;

re=(re*d.re+im*d.im)/t2;

im=(im*d.re-t1*d.im)/t2;

};

return *this;

};

int Cplx::operator==(const Cplx& d){

return re==d.re && im==d.im;

};

int Cplx::operator!=(const Cplx& d){

return re!=d.re || im!=d.im;

};

Cplx& Cplx::operator-(){

re=-re;
im=-im;

return *this;

};

Cplx& Cplx::operator!(){

im=-im;

return *this;

};

Cplx& Cplx::operator++(){

re++;

return *this;

};

Cplx Cplx::operator++(int){

Cplx t(re,im);

re++;

return t;

};
6. SUPRAÎNCĂRCAREA OPERATORULUI DE ATRIBUIRE.

Operaţia de atribuire simplă, dacă nu este supraîncărcată, realizează o copiere membru cu


membru.

Pentru obiectele care nu conţin date alocate dinamic la iniţializare, atribuirea prin copiere membru
cu membru funcţionează corect, motiv pentru care nu se supraîncarcă operatorul de atribuire.

Pentru clasele ce conţin date alocate dinamic, copierea membru cu membru, executată în mod
implicit la atribuire conduce la copierea pointerilor la datele alocate dinamic, în loc de a copia
datele.

Operatorul de atribuire poate fi redefinit numai ca funcţie membră, el fiind legat de obiectul din
stânga operatorului =, o L-valoare, motiv pentru care va întoarce o referinţă la obiect.

O primă cerinţă la scrierea funcţiei operator= este evitarea autoatribuirii a=a. În acest scop se
efectuează testul this != &d (unde d este parametrul funcţiei -obiectul din dreapta operatorului
=.

Urmează apoi “curăţirea” membrului stâng (eliberarea memoriei alocate dinamic). Copierea propriu
zisă este făcută de obicei folosind constructorul de copiere.

Funcţia operator returnează o referinţă la obiectul modificat (return *this).

Exemplificăm pentru clasa String, definită astfel:

class String{

char* s;

int n;

public:

String(); //c(onstruc)tor implicit

String(const char* p); //ctor de initializare

String(const String& r); //cr de copiere

~String(); //dtor

String& operator=(const String& d);

String& operator=(const char* p);

};

//ctor de copiere

String::String& operator=(const String& d){

if(this != &d){ //evitare autoatribuire

if(s).... //curatire
.. delete [] s;

n=d.n;.... //copiere

s=new char[n];

strncpy(s, d.s, n);

};

return *this;.. //intoarce obiectul modificat

};

//ctor de initializare (cu un sir tip C)

String::String& operator=(const char* p){

if(s)

.. delete [] s;

n=strlen(p);

s=new char[n];

strncpy(s, p, n);

return *this;

};

7. SUPRAÎNCĂRCAREA OPERATORULUI DE INDEXARE.

Operatorul de indexare este un operator binar, având ca prim termen obiectul care se indexează, iar ca al
doilea termen indicele: obiect [ indice ] şi este interpretat ca: obiect.operator[
](indice) .

Funcţia operator de indexare trebuie să fie funcţie membră nestatică.

Primul termen, transmis implicit prin this este obiectul asupra căruia se execută indexarea.

Argumentul funcţiei reprezintă indicele, care poate fi de orice tip, spre deosebire de indicii predefiniţi şi este
al doilea termen din operator.

Valoarea întoarsă de funcţia operator este o referinţă la elementul indexat din obiect. Exemplificăm cu
operatorul de indexare pentru clasa String:

class String{

int n;

char* s;

public:
. . .

char& operator[](int);

};

char& String:: operator[](int i){

if(i>=0 && i<n)

return s[i];

else

return 0;

};

8. SUPRAÎNCĂRCAREA OPERATORILOR NEW ŞI DELETE.

Operatorii new şi delete pot fi supraîncărcaţi ca funcţii operatori membre statice.

Funcţia operator new are semnătura: void* operator new(size_t); Operatorul redefinit
apelează constructorul clasei după alocarea dinamică pe care o realizează.

Funcţia operator delete are semnătura: void operator delete(void*); unde parametrul este
un pointer la obiectul eliminat. Operatorul redefinit delete apelează întotdeauna destructorul clasei
înainte de a elibera memoria alocată dinamic.

Dacă în clasa C se supraîncarcă operatorul new, atunci versiunea furnizată de sistem se obţine prin C *p
= ::new C();

Exemplu1:Supraîncărcaţi operatorul new, astfel încât să iniţializeze memoria alocată la 0.

void* C::operator new(size_t dim){

void *p = new char[dim];

return memset(p, 0, sizeof(C));

Exemplu1:Supraîncărcaţi operatorul new[], astfel încât să iniţializeze memoria alocată la o valoare dată
ca parametru.

void* C::operator new[](size_t dim, unsigned v){

int n = dim / sizeof(C);

C *p = ::new C[n];

for(int i=0; i<n; i++)

p[i].val = v; //clasa are data membra val


return p;

9. SUPRAÎNCĂRCAREA OPERATORULUI APEL DE FUNCŢIE.

Apelul unei funcţii este o construcţie de forma:

nume_functie(lista_argumente);

desemnând operatorul binar() aplicat celor doi termeni nume_functie şi lista_argumente.

Numele funcţiei poate fi înlocuit printr-un pointer la funcţie:

(*pf)(lista_argumente);

Funcţia operator redefinit apel de funcţie va avea aşadar 2 parametri: un obiect şi o listă de argumente.
Implementată ca funcţie membră nestatică, primul parametru este transmis implicit, astfel că un apel de
forma:

obiect(lista_argumente);

este interpretat ca:

obiect.operator()(lista_argumente);

Supraîncărcarea operatorului () se utilizează la definirea functorilor (obiecte funcţii). Un functor este un


obiect pentru care se supraîncarcă operatorul apel de funcţie (operator()()) şi care se comportă ca o
funcţie.

În mod uzual obiectele funcţii se folosesc în locul pointerilor la funcţii. Funcţiile inline nu pot folosi ca
parametri pointeri la funcţii (care sunt apelaţi indirect), ci obiecte funcţii.

Operatorul apel de funcţie asigură o sintaxă uniformă pentru obiectele care se comportă ca şi funcţiile.

Algoritmii generici din STL au printre argumente şi o biecte funcţii. Acestea pot fi:

- generatori = functori fără argumente

- funcţii unare = functori cu un argument

- funcţii binare = functori cu două argumente.

O funcţie unară booleană este un predicat, în timp ce o funcţie binară booleană este un predicat binar.

De exemplu, pentru o dreaptă, definită prin tăietura în origine şi pantă putem defini clasa:

class Dreapta{

double m,y0;

public:

Dreapta(double m1=1, double y01=0): m(m1),y0(y01){} //ctor


double operator()(double x){ return y0+m*x; }.. //functor

};

Clasa va fi folosită într-o funcţie main() astfel:

Dreapta d1;

Dreapta d2(2., 3.);

double y1=d1(5.5); //y1=5.5

double y2=d2(1.5); //y2=2*1.5+3=6

Definiţi un obiect funcţie (functor) care generează termenul de rang n din şirul lui Fibonacci.

class Fibo{

public:

Fibo():x(0),y(1){}; //constructor

long operator()(int n); //supraincarcare operator functie

private:

long x, y;

};

long Fibo::operator ()(int n){

for(int i=2; i<=n; i++){

long z=x+y;

x=y;

y=z;

};

return x;

};

10. SUPRAÎNCĂRCAREA OPERATORILOR DE CONVERSIE.

În C++ la evaluarea unei expresii se efectuează conversii implicite pentru tipurile predefinite. Deoarece nu
există conversii implicite de la o clasă la un tip predefinit, programatorul îşi poate defini conversii explicite
prin supraîncărcarea unui operator de conversie al clasei. Astfel pentru clasa C, funcţia membru nestatică
de conversie C::operator T() unde T este un nume de tip primitiv realizează conversia de la tipul C la
tipul T. Aceasta nu specifică nici un tip de rezultat întors, deoarece se returnează implicit valoarea
obiectului convertită la tipul pentru care este definită conversia şi nu are parametri.
Funcţia operator primeşte ca prim parametru implicit adresa obiectului şi întoarce valoarea convertită de
tipul numelui funcţiei operator de conversie.

Apelul explicit al funcţiei de conversie de la un obiect din clasa C la tipul primitiv T se specifică prin
T(obiect) sau (T) obiect.

Pentru conversia unui obiect între două clase C1 şi C2,C1->C2 se defineşte funcţia membru operator de
conversie C1::operator C2(); Pentru ca această funcţie să aibă acces la membrii clasei C2, se
declară funcţie prieten clasei C2.

Sunt premise astfel conversii între o clasă şi un tip predefinit sau între două clase, dar nu de la un tip
predefinit la o clasă.

Constructorii cu un singur argument pot fi folosiţi ca funcţii de conversie de la tipul argumentului la clasa
constructorului. Acest efect nu este întotdeauna dorit, şi pentru a-l evita, constructorul se declară precedat de
cuvântul cheie explicit.

Dacă o metodă a unei clase foloseşte parametri care suferă conversii implicite, metoda se va defini ca funcţie
prietenă, nu ca funcţie membră.

Definim ca exemplu clasa Inch:

class Inch{

double inch;

double cm;

public:

explicit Inch(double i=0.) : inch(i){}; //ctor

void Inch_cm(){ cm=2.54*inch;};

operator int() const {return int(inch+0.5);}; //fctie conversie

11. SUPRAÎNCĂRCAREA OPERATORILOR << ŞI >>.

Operatorii de intrare / ieşire pot fi supraîncărcaţi. Operatorul de extracţie >>, membru al clasei istream
serveşte pentru extragerea datelor de tipuri predefinite din fluxul de intrare cin. Prin supraîncărcarea
operatorului putem extrage din fluxul de intrare f, obiecte ob, scriind f >> ob;

Funcţia operator corespunzătoare are semnătura:

istream& operator >> (istream& f, clasa & ob);

Operatorul de inserţie <<, membru al clasei ostream serveşte pentru depunerea datelor de tipuri predefinite
în fluxul de ieşire cout. Supraîncărcarea operatorului permite depunerea de obiecte ob în fluxul de ieşire f
cu f << ob;

ostream& operator << (ostream& f, const clasa & ob);


Întrucât ambele funcţiile operatori întorc referinţe la fluxuri (prin return f), operatorii pot fi înlănţuiţi.(f
<< o1 << o2;)

Funcţiile operator pentru supraîncărcarea operatorilor de intrare / ieşire se declară funcţii prieten al clasei
care interacţionează cu fluxul.

Exemplificăm cu clasele Cplx şi String:

class Cplx{

double re,im;

public:

friend ostream& operator<<(ostream& os, const Cplx& z);

friend istream& operator>>(istream& is, Cplx& z);

};

ostream& operator<<(ostream& os, const Cplx& z){

os << “(“ << z.re <<”,” << z.im << “)” << endl;

return os;

};

istream& operator>>(istream& is, Cplx& z){

is >> z.re >> z.im;

return is;

};

class String{

int n;

char *s;

public:

. . .

friend ostream& operator<<(ostream&os,const String& w);

friend istream& operator>>(istream& is, String& w);

};

ostream& operator<<(ostream& os,const String& w){

for(int i=0; i<w.n; i++)


os << w.s[i];

return os;

};

istream& operator>>(istream& is, String& w){

char zona[100];

is.get(zona,100);

w=zona;

return is;

};

Clase derivate. Moştenire.

1. Clase derivate.
2. Reutilizarea codului folosind membri obiecte ai claselor.
3. Constructori şi destructori în clasele derivate.
4. Controlul accesului la membrii clasei de bază.
5. Ierarhii de clase.
6. Clase virtuale.
7. Funcţii virtuale.
8. Destructori virtuali.
9. Clase abstracte.
10. Polimorfism.
11. Probleme.

1. Clase derivate.

Prin moştenire, atributele unei clase de bază sunt transmise unor clase derivate. Derivarea permite
definirea unor clase noi, prin adăugarea unor funcţionalităţi noi unor clase deja existente, fără
reprogramare sau recompilare.

Clasa derivată moşteneşte caracteristicile unei clase de bază (sau mai multor clase de bază, în cadrul
moştenirii multiple), la care adaugă caracteristici noi, specifice.

Clasa derivată moşteneşte datele membri şi funcţiile membri din clasa de bază, exceptând
constructorii, destructorii şi operatorul de atribuire.

Exemplu de derivare:

trapez..paralelogram..dreptunghi.. pătrat

.......... romb

Reutilizarea codului se poate realiza în două moduri:

 prin compunere – incluzând obiecte în cadrul altor obiecte


 prin moştenire – creind obiecte noi din cele existente
Sintaxa specificării unei clase derivate este:

class nume_clasă_derivată : specif_acces nume_clasă_bază{

// corp clasă

};

În cazul moştenirii multiple este posibilă moştenirea din mai multe clase de bază:

class derivată : acces1 bază1, ..., accesn bazăn {

// corp clasă;

};

O bază directă este menţionată în lista claselor de bază ale clasei derivate.

Prin moştenire multiplă şi indirectă se crează ierarhii de clase, care sunt grafuri orientate aciclice (în
cazul moştenirii simple avem un arbore orientat).

Exemplu de moştenire multiplă:

triunghi.. dreptunghic.. dreptunghic-isoscel

....

......isoscel.... echilateral

2. Reutilizarea codului folosind membri obiecte ai claselor.

Să considerăm clasele String şi Persoana definite după cum urmează:

class String {

int lg;

char* sir;

public:

String(char* );

String(const String& );

~String();

};

class Persoana {

int lg_nume;

char* nume;
int varsta;

public:

Persoana(char* n, int v);

Persoana(const Persoana& p);

~Persoana();

};

Numele persoanei poate fi definit printr-un obiect de tip String, care poate apare ca membru al clasei
Persoana:

class Persoana {

String nume;

int varsta;

public:

Persoana(const String& n, int v);

Persoana(const Persoana& p);

~Persoana();

};

Fiecare obiect din clasa Persoana apelează doi constructori: Persoana şi String. Constructorii din
clasele membri se apelează înaintea constructorului clasei ce conţine membrii deci ordinea de apel a
constructorilor va fi: String şi apoi Persoana.

Argumentele se transmit constructorilor claselor membri printr-o listă de iniţializare a membrilor


plasată imediat după antetul constructorului. Membrii iniţializaţi sunt separaţi între ei prin virgule şi
precedaţi de :

Persoana:: Persoana(const String& n, int v)

: nume(n), varsta(v) {}; // nu mai trebuie facut nimic

Lista de iniţializare a membrilor este singurul mecanism prin care pot fi iniţializaţi membrii care nu au
constructori impliciţi, membrii constanţi şi membrii referinţe, deoarece în momentul începerii execuţiei
constructorului, aceştia trebuie să fie deja iniţializaţi.

Obiectul membru al clasei trebuie să apară în lista de iniţializare, dacă constructorul are listă de
parametri.

class Club{

String nume;
Tabel membri;

Data creere;

public:

Club(const String& n, Data d);

};

Club::Club(const String& n, Data d)

..:nume(n), membri(),creere(d) //lista de initializare

{ };

Constructorii membrilor sunt apelaţi în ordinea în care sunt declaraţi în clasă, nu în cea în care apar în
lista de iniţializare. Destructorii sunt apelaţi în ordine inversă constructorilor.

Dacă constructorii membrilor nu au parametri, ei pot să nu apară în lista de iniţializare a membrilor.

3. Constructori şi destructori în clasele derivate.

Constructorii şi destructorii sunt funcţii membri care nu se moştenesc, întrucât aceştia posedă numele
clasei.

La creerea şi iniţializarea unui obiect într-o clasă derivată se apelează implicit, mai întâi constructorii
claselor de bază (în ordinea în care apar în declaraţia clasei derivate) şi apoi constructorul clasei derivate.

La distrugerea unui obiect al clasei derivate, mai întâi este apelat destructorul clasei derivate, şi apoi
destructorii claselor de bază, în ordinea inversă celei în care apar în declaraţia clasei derivate.

4. Controlul accesului la membrii clasei de bază.

Specificatorul de acces public asigură că:

 membrii public ai clasei de bază devin membri public ai clasei derivate


 membrii protected ai clasei de bază devin membri protected ai clasei derivate
 membrii private ai clasei de bază nu sunt accesibili în clasa derivată

Un membru protected al unei clase, moştenită ca public se comportă ca unul private, adică
poate fi accesat numai de membrii clasei şi de funcţiile friend din clasa de bază. În plus, este accesibil şi
funcţiilor membri şi funcţiilor friend din clasa derivată, unde este tot protected. La o nouă derivare va
fi transmis tot ca protected. (Membrii private din clasa de bază sunt inaccesibili în clasa derivată). Un
membru protected poate fi privit ca public în clasele derivate şi ca private în celelalte clase.

La o moştenire cu acces de tip protected a clasei de bază, membrii public şi protected din
clasa de bază devin protected în clasa derivată.

La o moştenire cu acces de tip private a clasei de bază, membrii public şi protected din
clasa de bază devin private în clasa derivată, şi la o nouă derivare nu vor mai putea fi accesaţi.

12. Ierarhii de clase.


O clasă derivată poate fi la rândul ei clasă de bază. De exemplu:

class Angajat{ . . .};

class Sef : public Angajat { . . .};

class Director : public Sef { . . .};

O asemenea ierarhie se reprezintă de obicei printr-un arbore, dar în cazul moştenirii multiple apare un graf
orientat aciclic.

Pentru a fi folosită ca bază, o clasă trebuie să fie definită; simpla declarare a clasei este insuficientă.

Să considerăm o funcţie membru void afisare() const; în clasa de bază, pe care o vom redefini
în clasele derivate, având aceeaşi semnătură:

class Angajat{

String nume;

double salariu;

public:

Angajat(const String& p, double s);

void afisare() const;

};

void Angajat::afisare()const {

cout << nume <<” salariu: “ << salariu << endl;

};

Angajat::Angajat(const String& n, double s)

.. : nume(n), salariu(s) { };

class Sef: public Angajat{

int sectie;

public:

Sef(const String& n, double s, int sec);

void afisare() const;

};

void Sef::afisare()const {

Angajat::afisare();
cout << “Sef Sectie: “ << sectie << endl;

};

Sef::Sef(const String& n, double s, int sec)

.. : Angajat(n,s), sectie(sec) { };

6. Clase virtuale.

Într-o moştenire multiplă, o clasă poate fi moştenită indirect de mai multe ori. De exemplu:

class CBInd {public: int x;};

class CBDir1 : public CBInd { . . .};

class CBDir2 : public CBInd { . . .};

class Der: public CBDir1, public CBDir2 { . . .};

CBInd.. CBInd Un obiect din clasa Der conţine membrii clasei CBInd

...... de două ori:

........ - prin clasa CBDir1 (CBDir1::CBInd)

CBDir1..CBDir2 - prin clasa CBDir2 (CBDir2::CBInd)

...... Accesul la un membru din clasa CBInd este ambiguu,

.. Der.. deci interzis.

Ambiguitatea poate fi rezolvată folosind operatorul de rezoluţie:

Der d;

d.CBDir1::x=10; // x mostenit prin CBDir1

d.CBDir2::x=25; // x mostenit prin CBDir2

Pentru a crea o singură copie a clasei de bază în clasa derivată (prin moştenire multiplă indirectă) se
declară clasa de bază de tip virtual.

class CBInd {public: int x;};...... CBInd

class CBDir1 : virtual public CBInd{...};

class CBDir2 : virtual public CBInd{...}; CBDir1 CBDir2

class Der: public CBDir1, public CBDir2{...};

..................

.................. Der
7. Funcţii virtuale.

O funcţie membru, definită în clasa de bază poate fi redefinită în clasa derivată cu aceeaşi
semnătură. Cele două funcţii: cea din clasa de bază şi cea din clasa derivată, deşi au aceeaşi semnătură au
funcţionalităţi diferite.

Dacă funcţia din clasa derivată are o semnătură diferită, atunci ea va fi supraîncărcată în clasa
derivată, fiind moştenită şi funcţia din clasa de bază.

Accesul la membrii clasei de bază şi a clasei derivate se poate face prin pointeri la obiecte din clasa
de bază şi din clasa derivată.

Folosind pointeri din clasa de bază.putem accesa obiecte din acea clasă, cât şi dintr-o clasă
derivată, deoarece conversia unui pointer din clasa derivată într-un pointer din clasa de bază se face
implicit.

Conversia inversă (din clasa de bază în clasa derivată) nu este implicită. O încercare de conversie
conduce la eroare, iar o forţare a conversiei, prin cast, deşi este corectă sintactic, conduce la rezultate
imprevizibile.

class B {. . .};

class D : public B {. . .};

void main(){

D d;

B* pb = &d; // corect

B b;

D* pd = &b; // gresit

pd = (D*)&b; // corect sintactic, dar nesigur

Considerăm o funcţie membru f(), definită în B şi redefinită în D.

class B {

public:

void f();

};

class D : public B {

public:

void f();

};
Putem accesa funcţiile f() din clasa de bază B şi din clasa derivată D prin intermediul unor obiecte din
clasele de bază şi derivată, sau a unor pointeri la clasa de bază şi la clasa derivată.

void main(){

B b;

D d;

b.f();.. //legare statica se acceseaza f() din B

d.f();.. //legare statica se acceseaza f() din D

d.B::f(); //legare statica se acceseaza f() din B

B *pb = new B;

D *pd = new D;

B* pb1 = pd; // încercăm acces la clasa D cu pb1

pb->f();..// se acceseaza f() din B

pd->f();..// se acceseaza f() din D

pb1->f(); // se acceseaza f() din B !!

Prin urmare, accesul la funcţia membru din clasa B sau D este dictat de tipul pointerului (cu un pointer din
clasa B putem accesa numai funcţii din clasa B, chiar dacă pointerul indică spre un obiect din clasa derivată).

O funcţie virtuală este declarată cu specificatorul virtual în clasa de bază şi este redefinită în clasa
derivată.

Revenind asupra exemplului precedent, constatăm că putem selecta o funcţie virtuală redefinită în clasa
derivată, folosind un pointer din clasa de bază legat la un obiect din clasa derivată:

class B {

public:

virtual void g(); // functie virtuala

};

class D : public B {

public:

void g();....// redefinita

};

void main(){
B* pb = new B;

D* pd = new D;

B* pb1 = pd; // încercăm acces la clasa D cu pb1

pb->f();..// se acceseaza f() din B

pd->f();..// se acceseaza f() din D

pb1->f(); // se acceseaza f() din D !!

Concluzia este aceea că accesul la funcţia membru , declarată virtuală în clasa de bază este dictat de tipul
obiectului legat de pointerul prin care se apelează funcţia (cu un pointer din clasa B putem accesa funcţia din
clasa D, dacă pointerul indică spre un obiect din clasa derivată).

Nu pot fi declarate ca funcţii virtuale: constructorii, funcţiile membri statici, funcţiile friend şi funcţiile
nemembri.

Atributul virtual se moşteneşte pe parcursul derivării: dacă o funcţie este declarată virtuală în clasa
de bază, funcţia redefinită în clasa derivată îşă păstrează atributul virtual în mod implicit.

8. Destructori virtuali.

Considerăm situaţia în care se eliberează un obiect din clasa derivată, folosind un pointer din clasa de
bază.

class B{

public:

B();

~B(); //pentru functionare corecta se pune virtual ~B()

};

class D{

public:

D();

~D();

};

void main(){

B* pb=new(D); // se crează un obiect în D

delete pb; // se sterge un obiect in B


}

Deoarece selecţia funcţiei este dictată de tipul pointerului, se va şterge numai o parte din obiectul creat
(cea moştenită din clasa de bază) şi va exista memorie ocupată în mod inutil cu partea de date din clasa D.

Dacă se declarăm destructorul virtual, selecţia funcţiei este determinată de obiectul indicat de pointer,
aşadar se va şterge un obiect din clasa D.

9. Clase abstracte.

De obicei, funcţiile declarate virtuale în clasa de bază nu au funcţionalităţi deosebite în clasa de


bază. Ele sunt prevăzute în vederea redefinirii în clasele derivate. Astfel pot exista funcţii, care să nu aibă
nici o definiţie în clasa de bază numite funcţii virtuale pure, pe care utilizatorul să fie obligat să le
redefinească în clasele derivate înainte de a le folosi.

O funcţie virtuală pură are semnătura:

virtual tip nume(lista_parametric)=0;

O clasă abstractă conţine cel puţin o funcţie virtuală pură. O clasă abstractă nu poate fi instanţiată, adică
nu se pot crea obiecte din acea clasă, deoarece funcţiile virtuale pure nu sunt definite, dar se pot crea
pointeri şi referinţe la clase abstracte.

O clasă abstractă este folosită drept suport pentru construirea altor clase prin derivare.

O clasă derivată devine la rândul ei clasă abstractă, dacă unele din funcţiile virtuale pure rămân
neredefinite. Dacă se redefinesc toate funcţiile virtuale pure, clasa derivată devine normală, şi poate
instanţia obiecte.

Exemplu de folosire a unei clase abstracte:

class Conversie{

protected:

double in; //intrarea

double out; //iesirea

public:

Conversie(double i){in=i;}; //ctor

double getin() const{ return in;}; //accesor

double getout()const{ return out;};

virtual void conv()=0; //fctie virtuala pura

};

class FahrCels : public Conversie{

public:
FahrCels(double i) : Conversie(i){};

void conv(){ out = (in – 32) / 1.8;};

};

class InchCm : public Conversie{

public:

InchCm(double i) : Conversie(i){};

void conv(){ out = 2.54*in;};

};

void main(){

Conversie* pb;

double val;

char tip;

cin >> val >> tip;

switch(tip){

case ’i’: pb=new InchCm(val); break;

case ’f’: pb=new FahrCels(val); break;

};

if(pb){

pb->conv();

cout << pb->getin() << „ „ << pb->getout() << endl;

delete pb;

10. Polimorfism.

Polimorfismul reprezintă posibilitatea de a apela o aceeaşi funcţie cu argumente – obiecte din clase
diferite.

Polimorfismul de moştenire reprezintă apelarea unei funcţii din clasa derivată folosind un pointer din
clasa de bază. Legarea dinamică determină funcţia apelată pe baza obiectului la care se referă pointerul din
clasa de bază în momentul în care se face apelul.
Fiecare obiect care conţine o funcţie virtuală posedă un tabel de adrese de funcţii virtuale declarate în
clasă. La apelul unei funcţii virtuale, printr-un pointer sistemul la execuţie :

 determină adresa tabelului de funcţii virtuale al clasei obiectului


 determină adresa funcţiei corespunzătoare
 apelează funcţia de la această adresă

Polimorfismul de moştenire, introdus prin virtualitate este un polimorfism la nivel de execuţie,


obţinut prin legarea întârziată (legare dinamică) a adresei funcţiei apelate. Programele rezultate sunt
simple şi flexibile, dar au timpi de execuţie mai mari.

Legarea timpurie (la compilare) se referă la apelare de funcţii cu adrese de apel cunoscute: funcţii
membre nevirtuale, funcţii friend, funcţii şi operatori supraîncărcaţi, etc. Apelurile rezolvate la compilare
au o eficienţă ridicată la execuţie.

Considerăm derivarea Animal..Felina.. Pisica şi clasele:

class Animal {

char numeA[20];

public:

Animal(char na[]){ strcpy(numeA, na); };

virtual void Identif(){

cout << “Animalul: “ << numeA << endl;

};

};

class Felina : public Animal {

char numeF[20];

public:

Felina(char nf[], char na[]) : Animal(na) {

strcpy(numeF, nf);

};

void Identif(){

Animal::Identif();

cout << “Specia: “ << numeF << endl;

};

};
class Pisica : public Felina {

char numeP[20];

public:

Pisica(char np[],char nf[],char na[]) : Felina(nf,na){

Strcpy(numeP, np);

};

void Identif(){

Felina::Identif();

Cout << “subspecia: “ << numeP << endl;

};

};

#include <iostream.h>

#include <string.h>

void main(){

Animal A(“reptile”), *pA;

Felina F(“tigru”,”mamifer”);

Pisica P(“birmaneza”, “domestica”, “carnivora”);

P.Identif(); //legare statica, apel Animal()

pa = &A;

pa->Identif(); //legare dinamica, apel Animal()

pa = &F;

pa->Identif(); //legare dinamica, apel Felina()

pa = &P;

pa->Identif(); //legare dinamica, apel Pisica()`

11. Probleme.

1.Considerăm clasa Forma cu derivarea Forma.. Dreptunghi

.............. Cerc
class Forma{

protected:

double x,y;

public:

Forma(double h=0, double v=0);

virtual double Arie()const=0;

virtual double Perimetru()const=0;

};

class Dreptunghi : public Forma{

public:

Dreptunghi(double h=0, double v=0);

virtual double Arie()const;

virtual double Perimetru()const;

};

class Cerc : public Forma{

protected:

double raza;

public:

Cerc(double h=0, double v=0, double r=0);

virtual double Arie()const;

virtual double Perimetru()const;

};

Definiţi funcţiile din cele 3 clase.

2. Considerăm derivarea Dreptunghi.... Paralelipiped. Implementaţi constructori pentru


clasele respective şi funcţiile Arie() şi Volum(), pentru Dreptunghi se ia volumul 0.

3. Se consideră ierarhia de clase:

trapez..paralelogram..dreptunghi.. pătrat

.......... romb
Toate figurile au două laturi paralele cu axa Ox.

Datele membri sunt constituite din coordonatele celor 4 vârfuri. Funcţiile membri conţin în afara
constructorilor, funcţii pentru calculul ariei şi perimetrului.

O dată membru - valid , are valoarea 1 dacă figura respectivă este specificată corect.
Constructorii verifică paralelismul laturilor.

Definiţi şi implementaţi ierarhia de clase.

4. Modificaţi programul de mai sus, considerând că pătrat are moştenire multiplă,de la dreptunghi şi romb.

5. Considerăm derivarea:

........ Functionar

........ date:

.......... nume

.......... cnp

........ operatii:

.......... Functionar()

.......... Afisare()

Permanent............ Temporar

date:.............. date:

salariu............ plataorara

operatii:............ nrorelucrate

Permanent()............operatii:

Afisare()............ Temporar()

..................Afisare()

Funcţia Afisare() din clasa de bază tipăreşte nume şi cnp, iar în clasele derivate se tipăreşte în plus
salariul. Definiţi şi implementaţi aceste clase.

6. Definiţi şi implementaţi ierarhia de clase:

triunghi.. dreptunghic.. dreptunghic-isoscel

....

......isoscel.... echilateral
Tratarea excepţiilor.

1. Tratarea erorilor în C.
2. Tratarea excepţiilor în C++.

3. Zone de nume.

1. Tratarea erorilor în C.

În C există două moduri de tratare a erorilor la execuţie:

 cu reluare – la apariţia erorii se execută o secvenţă de instrucţiuni, având ca scop eliminarea erorii, după
care se continuă programul
 tratarea erorilor fatale – constând în suspendarea execuţiei programului, cu întoarcerea unui cod de eroare
la procesul părinte.

Funcţiile void exit(int) şi void abort(void), declarate în <stdlib.h> suspendă


execuţia programului din care sunt lansate.

Apelul funcţiei exit() : eliberează zonele tampon alocate, închide fişierele deschise, apelează
destructorii obiectelor statice utilizate, suspendă aplicaţia, transmiţând parametrul din apel procesului
părinte, interpretat de acesta ca un cod de eroare dacă are valoare nenulă (EXIT_FAILURE); o valoare
nulă reprezintă o terminare normală a aplicaţiei (EXIT_SUCCES). Un efect identic se obţine prin
execuţia instrucţiunii return cod_er; lansată din funcţia int main().

Înaintea eliberării resurselor şi suspendării execuţiei programului prin exit() este posibil să
executăm mai multe funcţii, care au fost înregistrate prin apeluri ale funcţiei atexit(). Aceasta are ca
parametru un pointer la funcţia înregistrată. Exemplu:

#include <stdlib>

void f(){. . .};

void g(){. . .};

void main(){

atexit(g); //inregistrarea functiilor care se executa

atexit(f); //in ordinea f(), g() la terminare normala

.... //sau anormala a programului

. . .

exit(1); //se executa f(), g() inainte de suspendare

. . .

//si aici se executa f(), g() la terminare normala

}
Apelul funcţiei abort() întrerupe imediat aplicaţia, fără eliberarea resurselor şi apelarea destructorilor
obiectelor statice, cu returnarea codului de eroare 3 şi afişarea mesajului “abnormal program
termination”.

Utilizarea macroinstrucţiunii assert(condiţie) , definită în <assert.h> permite testarea validităţii unor


condiţii. Valoarea 0 a condiţiei suspendă execuţia programului şi afişează mesajul: “Assertion
failed (conditie) __FILE__ ff __LINE__ ll”. Această macroinstrucţiune este folosită
mai ales în faza de depanare a programelor.

Pentru a elimina secvenţele assert() după depanarea programului, se defineşte macroinstrucţiunea


assert() în mod condiţionat de prezenţa unei constante simbolice NDEBUG.

#define NDEBUG

#include <assert.h>

. . .

2. Tratarea excepţiilor în C++.

O excepţie reprezintă o condiţie de eroare, care transmisă sistemului determină producerea unei
erori la execuţie. De exemplu: o înpărţire prin 0, ieşirea indicelui în afara limitelor unui tablou, etc.

La apariţia unei excepţii, utilizatorul îşi poate defini o secvenţă proprie de tratare a acesteia.

În C++ se utilizează un model ierarhic de tratare a excepţiilor. Acesta foloseşte 3 cuvinte cheie: try
– detectare, catch – tratare, throw – activare.

Transmiterea informaţiilor se va face prin intermediul unor obiecte excepţii.

Cuvântul try delimitează un bloc din programul utilizatorului, care va fi supravegheat pentru a
detecta apariţia unor excepţii. În cazul apariţiei unui anumit tip de excepţie, este pasată excepţia sesizată
prin throw(obiect_exceptie) rutinei de tatare corespunzătoare, care începe prin
catch(obiect_exceptie).

Excepţia poate fi captată, specificând numai tipul ei, prin catch(tip_exceptie). La apariţia
excepţiei este construit un obiect având tipul excepţiei corespunzătoare şi se activează excepţia. Membrii
obiectului servesc la identificarea naturii excepţiei.

Rutina de tratare a excepţiei urmează imediat după blocul try:

catch(tip_exceptie){

// rutina de tratare a exceptiei;

Să considerăm două excepţii, care pot să apară în cazul folosirii vectorilor:

 ieşirea indicilor în afara limitelor vectorului - clasa excepţie Range


 construirea unui vector cu dimensiune care depăşeşte dimensiunea maximă admisă – clasa excepţie Size

Primul tip de excepţie apare la indexare, deci excepţia va fi activată în funcţia operator de indexare.
A doua excepţie apare la declararea unui obiect vector, deci excepţia va fi activată în constructorul
clasei vector.

Clasele excepţii vor fi declarate drept clase incluse în clasa Vector:

class Vector{

float* v;

int d;...... // numar de elemente vector

enum{DMAX=100};....// dimensiune maxima vector

public:

class Range{};.... // clase exceptii

class Size {};

Vector(int n);.... // constructor

float& operator[](int i);//functie operator de indexare

};

Vector::Vector(int n){

if(n < 0 || n >= DMAX)

throw Size();

d = n;

v = new float[n];

};

float& Vector::operator[](int I){

if(i >= 0 && i < d)

return v[i];

throw Range();

return v[0];

};

void main(){

try{

Vector x[200]; // declanseaza exceptia Size


int i=150;

x[i] = 1.5; // declanseaza exceptia Range

. . .

};

catch(Vector::Size){

. . .

// rutina de tratare exceptie Size

. . .

};

catch(Vector::Range){

. . .

// rutina de tratare exceptie Range

. . .

};

3. Zone de nume.

În programele mari, scrise de mai mulţi programarori, partajate în mai multe fişiere sursă, riscul
conflictelor de nume (de variabile, funcţii, clase, obiecte) este ridicat.

O regiune declarativă a unui program este o zonă în care se fac declaraţii. Astfel regiunea declarativă
a unei variabile locale o reprezintă funcţia, iar a unei variabile globale este fişierul în care este declarată.

Domeniul potenţial de vizibilitate al unui nume se întinde de la declararea acestuia până la sfârşitul
regiunii declarative.

O variabilă poate să nu fie văzută peste tot în domeniul potenţial de vizibilitate. Ea poate fi ascunsă
de către un altă variabilă cu acelaşi nume, declarată într-o regiune declarativă imbricată.

Domeniul de vizibilitate reprezintă zona în care variabila poate fi accesată.

O zonă de nume este un mecanism de exprimare a unei grupări logice.

Declararea zonei de nume se face printrr-o interfaţă de forma:

namespace nume{

declaraţii entităţi (variabile, funcţii, etc)

};
De exemplu, o zonă de nume FunMatGrade, în care se declară funcţii trigonometrice cu argumentul
exprimat în grade, care păstrează aceleaşi nume cu cele din fişierul antet <math.h> este:

namespace FunMatGrade {

double sin(double);

double cos(double);

. . .

};

Definirea zonei de nume, este de obicei separată de declarare, fiind situată în partea de implementare.

În definiţie, numele definite sunt calificate cu numele zonei, folosind operatorul de rezoluţie.

Astfel numele din zona de nume dată mai sus se definesc prin:

double FunMatGrade :: sin(double){/* . . . */};

double FunMatGrade :: cos(double){/* . . . */};

. . .

Accesul la un nume din zona de nume se face de asemeni calificat. Exemplu:

y = FunMatGrade :: sin(x);

Aceste notaţii lungi şi greoaie pot fi evitate folosind declaraţii de utilizare. Acestea sunt formate din
using şi numele calificat, care în continuare poate fi folosit necalificat. Astfel:

using FunMatGrade :: sin(double);

ne permite să apelăm funcţia definită pentru calculul sinusului prin y=sin(x). Pentru a putea folosi funcţia
obişnuită de calcul a sinusului, declarată în <math.h> , vom preceda numele cu operatorul de rezoluţie,
adică y= :: sin(x).

Aceasta reprezintă zona de nume globală şi corespunde regiunii declarative la nivel de fişier.

Zonele de nume sunt deschise, adică putem adăuga nume la zone existente.

Declaraţia de utilizare adaugă un nume la regiunea declarativă locală, împiedicând declararea altei
variabile locale cu acelaşi nume (şi ascunzând variabila globală cu acelaşi nume).

Exemplu:

namespace A{

int x;

void f();

};
char x; //nume global

void main(){

using A::x; //x din zona de nume considerat local

double x; //eroare, exista x local din zona de nume

cin >> ::x; //citeste x global

Directiva de utilizare using namespace A; aplică declaraţia de utilizare tuturor numelor din
zona de nume, care devin locale în zona curentă. De exemplu:

#include <iostream>

using namespace std;

face ca toate numele din zona de nume std să devină globale.

Dacă un nume este declarat local unei funcţii, el nu poate fi importat cu o declaraţie de utilizare. Numele
poate fi importat cu o directivă de utilizare, dar va fi ascuns de către numele local. Astfel:

char x; //declarat global

void main(){

double x; //declarat local;

using namespace A; //se accepta,dar using A::x

........ //este ilegal

cin >> x; //x local, celelalte sunt ascunse

cin >> ::x; //x global

cin >> A::x; //x din zona de nume

Dacă nu folosim directiva de utilizare, vom adopta una din variantele:

#include <iostream>

. . .

int x;

std::cin >> x;

std::cout << x << std::endl;

respectiv:
#include <iostream>

. . .

using std::cin;

using std::cout;

using std::endl;

int x;

cin >> x;

cout << x << endl;

O zonă de nume poate fi inclusă în altă zonă de nume. În acest caz accesul la numele declarate în zona
interioară se face prin dublă calificare:

namespace A{

namespace B{

double b;

};

int a;

Calificarea A::B::b poate fi evitată cu declaraţia de utilizare using A::B.

Pentru a crea un sinonim B al unei zone de date A scriem namespace B=A;

Directiva de utilizare este tranzitivă:

namespace A{

int x;

namespace B{

using A::x;

. . .

ne permite să accesăm pe x prin A::x sau B::x.

Toate clasele, obiectele şi funcţiile din biblioteca standard C++ se definesc în zona de nume std.
Fişierele antet au aceleaşi nume cu fişierele antet corespunzătoare anterioare, dar le lipseşte extensia .h.

Funcţii şi clase generice (parametrizate).

1. Polimorfism parametric.
2. Polimorfism prin supraîncărcarea funcţiilor (polimorfism ad-hoc).
3. Funcţii polimorfice prin conversie de tipuri.
4. Funcţii generice.
5. Clase generice.

1. Polimorfism parametric.

O entitate polimorfică poate avea mai multe tipuri (polimorfism = mai multe forme).

Polimorfismul parametric se referă la posibiltatea apelării unei funcţii cu un număr variabil de parametri.
Un exemplu tipic în acest sens îl reprezintă funcţiile de intrare-ieşire: printf, fprintf, scanf,
fscanf, etc.

Polimorfismul parametric este posibil în C şi C++, deoarece la apelul funcţiilor parametrii sunt puşi în
stivă de către programul apelant.

În C, funcţiile definite fără nici un parametru (nici măcar void) nu sunt verificate de către compilator,
aşa că pot fi apelate cu oricâţi parametri, eventual de tipuri diferite.

În C++, pentru a specifica un număr variabil de parametric vom defini în mod explicit o funcţie cu număr
variabil de parametric, prin tipul (. . .), specificat întotdeauna ca ultim parametru. Parametrii puşi în stivă de
către programul apelant vor trebui prelucraţi în mod explicit de către programator. În acest scop se
utilizează macroinstrucţiunile va_start, va_arg şi va_end definite în fişierul <stdarg.h>.

O funcţie cu număr variabil de parametri va avea prototipul:

tip nume (listă_fixă_parametri, . . .);

Lista fixă de parametri trebuie să fie nevidă, deci numărul de parametri va fi mai mare sau egal cu
numărul de parametri ficşi.

Parametrii care sunt în număr variabil sunt convertiţi implicit ca tip, şi anume:

 toţi întregii la int


 toţi realii la double

Fişierul antet <stdarg.h> conţine definiţii pentru tipul va_list. Argumentele variabile vor fi
accesate printr-o variabilă pointer pa, declarată astfel:

va_list pa;

Iniţializarea pointerului de acces la argumentele variabile - pa se face folosind macroinstrucţiunea


va_start() , indicând adresa ultimului parametru fix lastarg:

va_start(pa, lastarg);
Pentru parcurgerea listei de argumente variabile se va folosi macroinstrucţiunea va_arg(), care
actualizează pointerul de acces la argumente pa, pentru a indica următorul argument int sau double, şi
întoarce ca rezultat argumentul curent din lista de parametri variabili:

vint=va_arg(pa, int);

sau

vreal=va_arg(pa, double);

Oprirea procesului repetitiv se face folosind informaţiile despre parametrii ficşi (în vârful stivei se va afla
pointerul la format). După ultimul parametru variabil extras se apelează macroinstrucţiunea:

va_end(pa);

Exemplul: Scrieţi o funcţie care afişează un număr variabil de şiruri de caractere (cel mult max).

#include <stdio.h>

#include <stdarg.h>

int printvar(int max, …);

void main(void) {

printvar(3,”Ion”,”Vasile”,”Mihai”);

printf(“\n”);

printvar(5,”marti”,”joi”,”luni”,”vineri”,”duminica”);

printf(“\n”);

void printvar(int max,…)

{ va_list pa;

int narg=0;

char *siruri[10];

va_start(pa,max);

while(narg < max) {

siruri[narg]=va_arg(pa, char*);

printf(“%s \n”, siruri[narg++]);

va_end(pa);
}

Extragerea argumentelor variabile, poate fi făcută, şi cu alte funcţii, în loc de va_arg(). În acest scop
se folosesc funcţiile: vprintf(), vfprintf() şi vsprintf(). Acestea au prototipurile:

int vprintf(char * format, va_list pa);

 afişează, sub controlul formatului, la ieşirea standard, un număr variabil de argumente, accesate prin
pointerul pa
 întoarce numărul de octeţi afişaţi (rezultat negativ la eroare)

Exemplu:

#include <stdio.h>

#include <stdarg.h>

int printvar(char* fmt, …);

void main(void) {

fmt1[]=”%s %s %s\n”;

printvar(fmt1,”Ion”,”Vasile”,”Mihai”);

void printvar(char* fmt,…) {

va_list pa;

va_start(pa,fmt);

vprintf(fmt,pa);

va_end(pa);

int vfprintf(FILE * fis, char * format, va_list pa);

 afişează, sub controlul formatului, în fişierul fis, un număr variabil de argumente, accesate prin pointerul
pa

 întoarce numărul de octeţi afişaţi (rezultat negativ la eroare)

Exemplu:

#include <stdio.h>

#include <stdarg.h>

#define NUMEFIS “fis.dat”


void printvar(FILE* f, char* fmt, …);

void main(void) {

FILE* f1;

fmt1[]=”%s %s %s\n”;

f1=fopen(NUMEFIS, “w”);

printvar(f1,fmt1,”Ion”,”Vasile”,”Mihai”);

fclose(f1);

void printvar(FILE* f, char* fmt,…) {

va_list pa;

va_start(pa,fmt);

vfprintf(f, fmt, pa);

va_end(pa);

int vsprintf(char * sir, char * format, va_list pa);

 afişează, sub controlul formatului, în şirul de caractere sir, un număr variabil de argumente, accesate prin
pointerul pa

Exemplu:

#include <stdio.h>

#include <stdarg.h>

int printvar(char* s, char* fmt, …);

void main(void) {

fmt1[]=”%s %s %s\n”;

char s[100];

printvar(s, fmt1, ”Ion”,”Vasile”,”Mihai”);

printf(“%s”,s);

void printvar(char* s, char* fmt,…) {


va_list pa;

va_start(pa,fmt);

vsprintf(s,fmt,pa);

va_end(pa);

Exemplul : Scrieţi o funcţie cu număr variabil de parametri, care simulează funcţia printf(),
acceptând parametri variabili de tip int, double sau şir de caractere.

#include <stdio.h>

#include <stdarg.h>

void printvar(char* fmt,…) {

va_list pa;

char *p, *psir;

int i;

double d;

va_start(pa,fmt);

for (p=fmt; *p; p++) {

if(*p!=”%”){

.. putchar(*p);

.. continue;

switch(*++p) {

case ‘d’:

.. i=va_arg(pa, int);

.. printf(“%d”,i);

.. break;

case ‘f’:

.. d=va_arg(pa, double);

.. printf(“%lf”,d);
.. break;

case ‘s’:

.. for (psir=va_arg(pa,char*);*psir;psir++)

....putchar(*psir);

.. break;

default:

.. putchar(*p);

.. break;

va_end(pa);

2. Polimorfism prin supraîncărcarea funcţiilor (polimorfism ad/hoc).

Dacă se consideră funcţia ca un tip, supraîncărcarea funcţiilor reprezintă o formă de polimorfism


numit polimorfism ad-hoc; funcţiile supraîncărcate au acelaşi nume dar semnături diferite şi cod diferit.
Selecţia funcţiei se face după parametrii din apel.

De exemplu declararea mai multor versiuni ale funcţiei sqrt():

double sqrt(double);

long sqrt(long);

3. Funcţii polimorfice prin conversie de tipuri.

Tot polimorfism ad-hoc reprezintă conversia implicită între tipul pointer generic (pointer la void) şi
pointer cu tip.

O funcţie polimorfică, în această accepţiune, va avea ca parametri pointeri generici . În apelul


funcţiei, aceştia vor fi înlocuiţi prin pointeri la tipuri definite.

Exemplificăm prin definirea unei funcţii polimorfice de căutare binară.

Pointerii generici nu pot fi dereferenţiaţi, motiv pentru care îă convertim în pointeri la octet.

typedef unsigned char OCTET;

typedef int (*PFCP)(void* ch, void* el);

void* CautBin(void* ch, void* tb,


.... int n, int dim, PFCP fcp){

int rezcp;

OCTET *min = (OCTET*) tb;

OCTET *med;

OCTET *max = (OCTET*) tb + (n-1)*dim;

while(min <= max){

med = min + (max-min)/dim/2*dim;

rezc = (*fcp)(ch, med);

if(rezcp < 0)

.. max = med – dim;

else

.. if(rezcp > 0)

.. min = med + dim;

.. else

.. return med;

};

return NULL;

Pointerii la elementele de acelaşi tip pot fi comparaţi (min <= max). Pointerul med se obţine pe o cale
mai ocolită: se adună la pointerul min o valoare constantă – distanţa între min şi max, exprimată în octeţi
(max-min) este convertită mai întâi în indice, prin împărţirea cu dimensiunea elementului dim, este apoi
împărţită la 2, pentru a obţine mijlocul şi transformată din nou în octeţi.

Funcţia de comparaţie are ca parametrii pointeri generici la valorile comparate şi întoarce o valoare
negativă, 0 sau o valoare pozitivă, în funcţie de rezultatul comparaţiei. Ea va fi adresată printr-un pointer
(pointer la funcţie) .

Explicitarea funcţiei concrete de comparaţie se va face în apel. Vom considera două cazuri: compararea a
două valori întregi, respectiv compararea a două şiruri de caractere. Pointerii generici sunt convertiţi în
pointeri la tipul respectiv (întreg sau şir de caractere), se face dereferenţierea şi se returnează ca rezultat
diferenţa valorilor, respectiv rezultatul comparaţiei şirurilor de caractere.

int cpint(void* ch, void* el){

return *((int*)ch) - ((int*)el);

};
int cpsir(void* ch, void* el){

return strcmp((char*) ch, *((char**)el));

};

4. Funcţii generice.

Există multe funcţii (şi clase) înrudite între ele, cu excepţia unor tipuri. De exemplu o funcţie care sortează
un tablou de întregi va diferi foarte puţin de un algoritm de sortare a unui tablou de reali.

Mecanismul şabloanelor (templates) crează funcţii sau clase generice (parametrizate) utilizând
tipurile ca parametri. Un şablon defineşte o familie de funcţii, respectiv de clase.Aceste tipuri se specifică la
definire, în mod generic, urmând apoi instanţierea tipurilor generice cu tipuri concrete.

Mecanismul reutilizează codul sursă, prin expandare, în condiţiile verificării complete a tipurilor.

O entitate template descrie un şablon pentru un număr nespecificat de entităţi concrete, înrudite
între ele.

De exemplu, pentru calculul diferenţei în valoare absolută a două valori întregi, reale sau întregi
lungi s-ar putea scrie o varietate de funcţii supraîncărcate, care ar avea acelaşi cod, diferind doar semnătura
(tipul parametrilor, nu şi numărul lor).

int max(int x, int y){

return x – y > 0? x : y;

long max(long x, long y){

// identic

float max(float x, float y){

// identic

Toate variantele ar putea fi comasate într-una singură, având tipul parametrizat:

template <class T>

T max(T x, T y){

return x - y > 0? x : y;

Definirea unei funcţii generice cuprinde:


template <par1, par2,..., parn>

declaraţie funcţie parametrizată;

Parametrii din lista de parametri a şablonului pot fi tipuri de date, caz în care se specifică sub forma
class Ti sau constante.

Parametrii din lista parametrilor şablonului (template) trebuie să apară toţi în lista de parametri formali a
funcţiei generice.

Instanţierea funcţiei parametrizate se face prin:

nume_funcţie_parametrizata(expr1, expr2,...,exprn)

unde expr1, expr2,...,exprn sunt expresii din care se deduc tipurile concrete.

Tipul generic T poate fi instanţiat cu orice tip concret, inclusiv un tip definit de utilizator, care suportă
operatorii - şi > şi care poate fi întors de către funcţie.

Compilatorul suportă funcţia generică T max(T, T) prin generare de cod sursă pentru o funcţie C
max(C, C), unde C este tipul concret al parametrilor de apel.

Generarea de cod pentru entitatea template are loc la compilare.

Parametrii şablonului pot fi tipuri predefinite ,tipuri definite de utilizator (clase) sau constante.

La apelul funcţiei parametrizate, tipul argumentului determină care versiune a şablonului este
folosită. De exemplu max(10, 25) selectează funcţia int max(int, int). Compilatorul deduce
tipul argumentului din apel, dacă reuşeşte să le identifice unic, fără a efectua vreo conversie implicită.
Astfel apelul max(10, 2.5) este ambiguu deoarece ar putea fi interpretat ca int max(int,
int)sau float max(float,float)

Situaţiile de acest fel trebuiesc clarificate prin supraîncărcare:

float max(int x, float y){ return x-y>0? x : y; };

float max(float x, int y){ return x-y>0? x : y; };

Redefinirea funcţiei are prioritate asupra definiţiei şablonului. Astfel având:

template <class T>

T min(T a, T b){

return (a < b)? a : b;

şi

char* min(char* s1, char* s2){

return (strcmp(s1,s2) < 0? S1 : s2);


}

pentru argumente şiruri de caractere va fi aleasă varianta supraîncărcată a funcţiei, fără a se încerca potrivirea
cu şablonul.

Funcţiile membre ale unei clase generice, sunt, la rândul lor funcţii generice, adică pot fi aplicate unor
tipuri de date diferite.

În afara acestora se pot defini şi funcţii nemembre generice reprezentând familii de funcţii.

Fie funcţia generică:

template <class T>

T minim(const T* x, int n){

T xm = x[0];

for(int i=1; i<n; i++)

if(x[i]<xm)

.. xm = x[i];

return xm;

};

Instanţierea şablonului,, adică înlocuirea parametrilor şablonului cu parametrii efectivi are forma:

int ix[] = { 3, 8, 1, 5, 13, 7};

float fx[] = {1.5, -3.2, 7.3, 4.8};

void main(){

int n = sizeof(ix) / sizeof(int);

int imin = minim(ix, n);

n = sizeof(fx) / sizeof(float);

float fmin = minim(fx, n);

. . .

Toate funcţiile generate pornind de la o funcţie generică sunt versiuni supraîncărcate ale uneia dintre ele.
Mecanismul funcţiilor generice poate fi deci privit ca un mijloc de realizare a polimorfismului ad-hoc.

5. Clase generice.
Clasele colecţii conţin obiecte de un tip particular (de exemplu o listă înlănţuită de întregi sau un
tablou de structuri. Se pot defini familii de clase colecţii, membrii acestora diferind numai prin tipul
elementelor.

Considerăm clasa tablou de întregi: pentru a folosi un tablou de reali, de complecşi sau de şiruri de
caractere s-ar putea copia implementarea clasei, modificând tipul datelor şi numele clasei. Astfel am avea
clasele intArray, StringArray, ComplexArray, etc.

Familia de clase poate fi reprezentată printr-o clasă generică (parametrizată). Aceasta specifică
modul în care pot fi construite clasele individuale, care se deosebesc numai prin tipul elementelor pe care le
conţin.

Instanţierea unui şablon reprezintă generarea declarării unei clase dintr-o clasă parametrizată şi un
argument generic.

O versiune a şablonului pentru un argument particular poartă numele de specializare.

Utilizarea unei clase generice presupune generarea de către compilator a fiecărei clase individuale,
corespunzător tipurilor care instanţiază clasa generică.

O clasă generică se reprezintă astfel:

template <listă_argumente_generice> declarare_clasă;

Instanţierea unei clase generice se face prin:

nume_clasă <listă_argumente_concrete> nume_obiect;

Definim clasa generică (parametrizată):

template <class T>

class Array{

public:

Array(int d=10): a(new T[dim]), dim(d){}

~Array(){delete a;}

private:

T *a;

int d;

};

Instanţierea clasei şablon se face prin:

Array<int> x;

Array<Complex> c;

Array<String> s;
Definirea unei clase şablon se face prin:

template <lista_parametri_sablon>

declaratie sau definitie clasa;

Fiecare parametru din şablon reprezintă un tip:

a. definit de utilizator: class T


b. predefinit: int, double, etc

Exemplu

template <class T, int d> class Buffer{

T a[d];

int dim;

public:

Buffer() : dim(d){}

};

şi instanţieri de forma:

Buffer<char, 63> bufc;

Buffer<Rec, 10> bufr;

cu:

class Rec{ char s[10]; };

O funcţie generică de căutare în bufferul generic a unui şir va avea semnătura:

template <class T, int n>

T& cautare(Buffer<T,n> &b, const char* p);

Funcţiile generice pot fi supraîncărcate, cu condiţia ca instanţele să aibă semnături diferite. De exemplu:

template <class T>

class Array{. . .};..//clasa generica

template <class T>

T suma(Array<T>, int); //functie generica

template <class T>

T suma(T*, int);.. //functie generica supraincarcata


void main(){

Array<int> A[100]; //instantiere clasa generica

int a[100];

int s1 = suma(A, 100);//instantiere functie generica

int s2 = suma(a, 100);

Algoritmi.

Algoritmii acoperă un domeniu larg de operaţii generale asupra containerelor precum: traversare,
sortare, căutare, inserare sau ştergere de elemente.

Algoritmii sunt funcţii generice, care se referă la containere prin intermediul iteratorilor. Tipurile
iteratorilor folosiţi de un algoritm sunt date ca parametri ai şablonului.

Algoritmii sunt declaraţi în zona de nume std şi semnăturile lor se află în fişierul antet algorithm.

Cei mai mulţi algoritmi sunt scurţi şi simpli, putând fi definiţi inline.

Algoritmii care produc rezultate pe care le depun într-un container sunt clasificaţi ca algoritmi de copiere
şi au numele terminat cu sufixul _copy.

Algoritmii a căror funcţionare este determinată de satisfacerea unui predicat sunt cunoscuţi ca algoritmi
cu predicate. Lista de parametri a unui asemenea algoritm va conţine un obiect funcţie predicat. Numele
acestor algoritmi conţine sufixul _if.

În prezentarea algoritmilor vom utiliza următoarele abrevieri:

InIt - InputIterator, OutIt – OutputIterator, FwIt – ForwardIterator,

BIt – BidirectionalIterator, RAIt – RandomAccessIterator, Pred –


Predicate,

BinPred – BinaryPredicate, OpUnar – UnaryOperator, Func – Function, T –


value_type (tipul elementelor din container)

Algoritmi care nu modifică secvenţa.

Se folosesc pentru a extrage informaţii din secvenţă sau a găsi poziţia unor elemente din secvenţă.

1. for_each() elimină un ciclu explicit, repetând o operaţie f asupra elementelor dintr-un interval de
iteratori. Se foloseşte pentru extragerea de informaţii asupra elementelor secvenţei.

template <class InIt, class Func>

Op for_each(InIt prim, InIt ultim, Func f){

while(prim!=ultim)
f(*prim++);

return f;

2. căutarea unei valori –familia find

find() caută în secvenţă prima valoare egală cu un parametru. Întoarce un iterator la prima
valoare găsită.

template<class InIt,class T> InIt find(InIt prim, InIt ultim, const T&
val);

find_if() caută în secvenţă prima valoare care satisface un predicat.

template<class InIt,class Pred> InIt find_if(InIt prim, InIt ultim, Pred


p);

find_first_of()găseşte primul element din secvenţa 1 care se află în secvenţa 2, sau găseşte
primul element din secvenţa 1 care se satisface predicatul cu un element din secvenţa 2.

template<class FwIt1, class FwIt2>

FwIt1 find_first_of(FwIt1 pr1, FwIt1 ult1, FwIt2 pr2, FwIt2 ult2);

template<class FwIt1, class FwIt2>

FwIt1 find_first_of(FwIt1 pr1, FwIt1 ult1, FwIt2 pr2, FwIt2 ult2, BinPred
p);

adjacent_find()găseşte prima pereche de elemente vecine egale / sau care satisfac predicatul
binar.

template <class FwIt> FwIt adjacent_find(FwIt prim, FwIt ultim);

template <class FwIt> FwIt adjacent_find(FwIt prim, FwIt ultim, BinPred


p);

3 contorizarea apariţiei unei valori

count() numără valorile din intervalul de iteratori egale cu valoarea dată ca parametru. Numărul
de elemente nu depăşeşte distanţa între iteratori.

template <class InIt, class T>

size_t count(InIt prim,InIt ultim, const T& val);

count_if()numără valorile din intervalul de iteratori care satisfac predicatul.

template <class InIt, class T>

size_t count_if(InIt prim, InIt ultim, Pred p);


4. – compararea a două secvenţe

equal() întoarce true/false dacă cele două secvenţe sunt egale sau dacă toate perechile satisfac
predicatul

template<class InIt1,class InIt2>

bool equal(InIt1 prim1, InIt1 ultim1, InIt2 prim2);

template<class InIt1, class InIt2, class BinPred>

bool equal(InIt1 prim1, InIt1 ultim1, InIt2 prim2, BinPred p);

mismatch()întoarce iteratori la prima pereche de elemente neegale, respectiv perechea care nu


satisface predicatul din cele două secvenţe.

template<class InIt1,class InIt2>

pair<InIt1,InIt2> mismatch(InIt1 prim1, InIt1 ultim1, InIt2 prim2);

template<class InIt1, class InIt2, class BinPred>

pair<InIt1,InIt2> mismatch(InIt1 prim1, InIt1 ultim1, InIt2 prim2,


BinPred p);

5 căutarea unei secvenţe

search() – determină prima apariţie a secvenţei2 ca subsecvenţă a secvenţei1, respectiv prima


apariţie a secvenţei2 ca subsecvenţă în secvenţa1 cu elemente aflate în relaţia dată cu elementele din
secvenţa2. Întoarce un iterator la primul element din prima secvenţă egalată cu secvenţa2.

template <class FwIt1, class FwIt2>

FwIt1 search(FwIt1 prim1,FwIt1 ultim1,FwIt2 prim2,FwIt2 ultim2);

template <class FwIt1, class FwIt2, class BinPred>

FwIt1 search(FwIt1 prim1,FwIt1 ultim1,FwIt2 prim2,FwIt2 ultim2,BinPred


p);

find_end() determină ultima apariţie a secvenţei2 ca subsecvenţă a secvenţei1. Întoarce un


iterator la primul element din ultima secvenţă egalată cu secvenţa2.

template <class FwIt1, class FwIt2>

FwIt1 find_end(FwIt1 prim1,FwIt1 ultim1,FwIt2 prim2,FwIt2 ultim2);

template <class FwIt1, class FwIt2, class BinPred>

FwIt1 find_end(FwIt1 prim1,FwIt1 ultim1,FwIt2 prim2,FwIt2 ultim2,BinPred


p);
search_n() găseşte o secvenţă cu cel puţin n elemente egale / sau în relaţia p cu valoarea val.
Întoarce un iterator la prima secvenţă de n asemenea elemente.

template <class FwIt, class Size, class T>

FwIt search_n(FwIt prim, FwIt ultim,Size n, const T& val);

template <class FwIt, class Size, class T, class BinPred>

FwIt search_n(FwIt prim, FwIt ultim,Size n, const T& val, BinPred p);

Algoritmi care modifică secvenţa.

Actualizarea unei secvenţe se poate face direct asupra acesteia (in situ) sau producând o nouă secvenţă
modificată în cursul traversării (varianta _copy). Operaţiile de actualizare se referă la inserări, ştergeri,
interschimb de valori, copiere, etc.

1. algoritmi de copiere – familia copy

copy() copiază valorile dintr-un interval de iteratori la o destinaţie, copierea începând cu primul
element din interval. Dacă nu se poate suprascrie la destinaţie se foloseşte un iterator de inserţie.

template <class InIt, class Out>

Out copy(InIt prim, InIt ultim, OutIt rez){

while(prim != ultim)

*rez++ = *prim++;

return rez;

copy_backward() copiază valorile dintr-un interval de iteratori la o destinaţie, copierea


începând de la ultimul element din interval către primul.

template <class BIt1, class BIt2>

BIt2 copy_backward(BIt1 prim, BIt1 ultim, BIt2 rez){

while(prim != ultim)

*--rez = *--ultim;

return rez;

copy_if() copiază toate valorile din intervalul de iteratori care satisfac predicatul, la destinaţie.

template <class It1, class It2, class Pred>

It2 copy_if(It1 prim, It1 ultim, It2 rez, Pred p){


while(prim != ultim){

if(p(*prim))

.. *rez++ = *prim;

prim++;

return rez;

2. înlocuiri – familia replace

replace() înlocuieşte toate apariţiile ale unei valori vechi valv din intervalul de iteratori cu o
valoare nouă valn

template <class FwIt, class T>

void replace(FwIt prim, FwIt ultim, const T& valv, const T& valn);

replace_if() înlocuieşte toate valorile care satisfac o anumită condiţie din intervalul de iteratori cu
o valoare nouă.

template <class FwIt, class T, class Pred>

void replace_if(FwIt prim, FwIt ultim, Pred p, const T& valn);

Cei doi algoritmi au şi variante replace_copy() şi replace_copy_if(), în care înlocuirea nu


se face in situ ci la o destinaţie, specificată printr-un iterator.

template <class InIt, class OutIt, class T>

OutIt replace_copy(InIt prim, InIt ultim, OutIt rez, const T& valv, const
T& valn);

template <class InIt, class OutIt, class T, class Pred>

OutIt replace_copy_if(InIt prim, InIt ultim, OutIt rez, Pred p, const T&
valn);

3. ştergeri – familia remove

remove() şterge toate valorile din domeniul de iteratori egale cu o valoare dată

template <class FwIt, class T>

FwIt remove(FwIt prim, FwIt ultim, const T& val);

remove_if() şterge toate valorile din domeniul de iteratori care satisfac un predicat
template <class FwIt, class Pred>

FwIt remove_if(FwIt prim, FwIt ultim, Pred p);

şi variantele remove_copy() şi remove_copy_if()

template <class InIt, class OutIt, class T>

OutIt remove_copy(InIt prim, InIt ultim, OutIt rez, const T& val);

template <class InIt, class OutIt, class Pred>

OutIt remove_copy_if(InIt prim, InIt ultim, OutIt rez, Pred p);

4. interschimbări – familia swap interschimbă elementele sau chiar containerele

swap() interschimbă două elemente individuale (chiar din containere diferite)

template <class T>

void swap(T& a, T& b);

iter_swap ()interschimbă două elemente localizate prin iteratori

template <class FwIt1, class FwIt2>

void iter_swap(FwIt1 a, FwIt2 b);

swap_ranges() interschimbă elementele din două intervale de iteratori

template <class FwIt1, class FwIt2>

void swap_ranges(FwIt1 prim1, FwIt1 ultim1, FwIt2 prim2);

5. transform() aplică un operator unar elementelor dintr-un interval de iteratori, depunând


elementele transformate în alt interval de iteratori

template <class InIt, class OutIt, class OpUnar>

OutIt transform(InIt prim, InIt ultim, OutIt rez, OpUnar op);

6. generate() completează elementele din intervalul de iteratori cu valori obţinute de la un functor


generator (predicat fără argumente)

template <class FwIt, class Generator>

void generate(FwIt prim, FwIt ultim, Generator gen);

generate_n() completează cu valori furnizate de generator primele n elemente dintr-un domeniu de


iteratori

template <class OutIt, class Size, class Generator>


OutIt generate_n(OutIt prim, Size n, Generator gen);

7. fill() completează cu aceeaşi valoare elementele dintr-un domeniu de iteratori

template <class FwIt, class T>

void fill(FwIt prim, FwIt ultim, const T& val);

fill_n() completează cu aceeaşi valoare primele n elemente dintr-un domeniu de iteratori

template <class OutIt, class Size, class T>

OutIt fill_n(OutIter prim, Size n, const T& val);

8. unique() şterge elementele consecutive identice cu un element / sau elementele consecutive


elementului, aflate în relaţie cu acesta.

template <class FwIt>

FwIt unique(FwIt prim, FwIt ultim);

template <class FwIt, class Pred>

FwIt unique(FwIt prim, FwIt ultim, Pred p);

şi variantele _copy:

template <class InIt, class OutIt>

OutIt unique_copy(InIt prim, InIt ultim, OutIt rez);

template <class InIt, class OutIt, class Pred>

OutIt unique_copy(InIt prim, InIt ultim, OutIt rez, Pred p);

9. reverse() inversează ordinea elementelor din secvenţă

template <class BIt>

void reverse(BIt prim, BIt ultim);

template <class BIt, class OutIt>

OutIt reverse_copy(InIt prim, InIt ultim, OutIt rez);

10. rotate() deplasează la stânga elemenetele secvenţei; elementele scoase sunt inserate la sfârşitul
secvenţei (deplasare circulară). Iteratorul med indică începutul secvenţei de elemente rotite.

template <class FwIt>

void rotate(FwIt prim, FwIt med, FwIt ultim);

template <class FwIt, class OutIt>


OutIt rotate(FwIt prim, FwIt med, FwIt ultim, OutIt rez);

11. partition() împarte secvenţa în două intervale, astfel că elementele care satisfac un predicat vor
fi plasate în primul interval (înaintea celorlalte).

template <class BIt, class Pred>

BIt partition(BIt prim, BIt ultim, Pred p);

stable_partition() realizează în plus faţă de algoritmul precedent, ordonarea relativă a


elementelor din fiecare partiţie

template <class BIt, class Pred>

BIt stable_partition(BIt prim, BIt ultim, Pred p);

12. random_shuffle() amestecă aleatoriu ordinea elementelor din secvenţă. Generatorul de


numere aleatoare poate fi specificat ca argument

template <class RAIt>

void random_shuffle(RAIt prim, RAIt ultim);

template <class RAIt, class RandGen>

void random_shuffle(RAIt prim, RAIt ultim, RandGen& rand);

13. iota() umple un interval de iteratori cu valori crescătoare, pornind cu o valoare dată ca parametru

template <class FwIt, class T>

void iota(FwIt prim, FwIt ultim, T val);

Algoritmi pentru secvenţe sortate.

Algoritmii din această clasă se împart în două categorii: prima foloseşte în mod implicit funcţia de
comparaţie < , iar cealaltă necesită ca argument un obiect funcţie Compare comp furnizat de utilizator.

1. sort() realizează sortarea elementelor din intervalul de iteratori. Se pretează folosirii de către
containere cu acces direct (vector şi deque). Pentru containerul list se foloseşte funcţia membră
list::sort().

Algoritmul sort() utilizează sortarea rapidă (quicksort), având în medie complexitatea O(N logN) şi
O(N2) în cazul cel mai nefavorabil, cu N=ultim-prim, dar sortarea nu este stabilă, în sensul că
elementele cu aceeaşi cheie pot să nu mai ocupe aceleşi poziţii relative după sortare.

template <class RAIt>

void sort(RAIt prim, RAIt ultim);

template <class RAIt, class Compare>

void sort(RAIt prim, RAIt ultim, Compare cp);


2. stable_sort() algoritmul de sortare stabilă stable_sort() , deşi are în medie un timp de
execuţie cu 40% mai mare, are în toate situaţiile complexitatea O(N logN)

template <class RAIt>

void stable_sort(RAIt prim, RAIt ultim);

template <class RAIt, class Compare>

void stable_sort(RAIt prim, RAIt ultim, Compare cp);

3. partial_sort() sortează primele M elemente din secvenţă, restul rămânând nesortate. Valoarea
lui M nu este dată în mod explicit, ci prin intermediul unui iterator med, astfel că M=med-prim.
Complexitatea este O(N log M)

template <class RAIt>

void partial_sort(RAIt prim, RAIt med, RAIt ultim);

template <class RAIt, class Compare>

void partial_sort(RAIt prim, RAIt med, RAIt ultim, Compare cp);

Există şi variante _copy, care au ca parametri suplimentari iteratori la intervalul destinaţie.

template <class InIt, class RAIt>

RAIt partial_sort_copy(InIt prim, InIt ultim, RAIt prim_rez, RAIt


ultim_rez);

template <class InIt, class RAIt, class Compare>

RAIt partial_sort(InIt prim,InItIt ultim,RAIt prim_rez,RAIt


ultim_rez,Compare cp);

4. nth_element() furnizează cel de al n-lea element din secvenţa sortată crescător sau descrescător
(fără a sorta secvenţa).

template <class RAIt>

void nth_element(RAIt prim, RAIt nth, RAIt ultim);

template <class RAIt, class Compare>

void nth_element(RAIt prim, RAIt nth, RAIt ultim, Compare cp);

Valoarea lui n este precizată prin cel de-al doilea parametru n=nth-prim, înainte de apel; după apel
*nth conţine cel de-al n-lea element din secvenţa sortată. Complexitatea în medie este O(N).

5. binary_search() permite localizarea foarte rapidă în O(log n) a unui element într-un container cu
acces direct sortat.

template <class FwIt, class T>


bool binary_search(FwIt prim, FwIt ultim, const T& val);

template <class FwIt, class T, class Compare>

bool binary_search(FwIt prim, FwIt ultim, const T& val, Compare cp);

6. lower_bound() găseşte prima poziţie i dintr-o secvenţă sortată, în care poate fi inserată o valoare
val astfel ca relaţia de ordine să se păstreze, adică *j < val (respectiv comp(*j,
val)=true)pentru j  [prim, i)

template <class FwIt, class T>

FwIt lower_bound(FwIt prim, FwIt ultim, const T& val);

template <class FwIt, class T, class Compare>

FwIt lower_bound (FwIt prim, FwIt ultim, const T& val, Compare cp);

7. upper_bound() găseşte ultima poziţie i dintr-o secvenţă sortată, în care poate fi inserată o valoare
val astfel ca relaţia de ordine să se păstreze.

template <class FwIt, class T>

FwIt upper_bound(FwIt prim, FwIt ultim, const T& val);

template <class FwIt, class T, class Compare>

FwIt upper_bound (FwIt prim, FwIt ultim, const T& val, Compare cp);

8. equal_range() determină cel mai mare interval dintr-o secvenţă sortată, în care poate fi inserată o
valoare val , în orice poziţie, astfel ca relaţia de ordine să se păstreze.

template <class FwIt, class T>

pair<FwIt,FwIt> equal_range(FwIt prim, FwIt ultim, const T& val);

template <class FwIt, class T, class Compare>

pair<FwIt,FwIt> equal_range(FwIt prim, FwIt ultim, const T& val, Compare


cp);

9. interclasare

merge() combină două secvenţe sortate într-o secvenţă de asemeni sortată. Are complexitate
liniară.

template <class InIt1, class InIt2, class OutIt>

OutIt merge(InIt1 prim1, InIt1 ultim1, InIt2 prim2, InIt2 ultim2, OutIt
rez);

template <class InIt1, class InIt2, class OutIt, class Compare>


OutIt merge(InIt1 prim1,InIt1 ultim1,InIt2 prim2,InIt2 ultim2,OutIt
rez,Compare cp);

inplace_merge() dacă cele două domenii de iteratori sunt contigue([prim, med) şi [med,
ultim)), s-ar putea face interclasare peste ele, dacă s-ar utiliza o zonă tampon de memorie. Rezultatul
interclasării va fi plasat în [prim,ultim)

template <class BIt>

BIt inplace_merge(BIt prim, BIt med, BIt ultim);

template <class BIt, class Compare>

BIt inplace_merge(BIt prim, BIt med, BIt ultim, Compare cp);

Algoritmi cu mulţimi.

Descriu principalele operaţii cu mulţimi (incluziune, reuniune, intersecţie, diferenţă) pentru mulţimi
(containere sortate). Au complexitate liniară O(N1+N2), unde N1 şi N2 sunt cardinalele celor două mulţimi.

1. includes() determină dacă mulţimea M2 este inclusă în mulţimea M1.

template <class InIt1, class InIt2>

bool includes(InIt1 prim1, InIt1 ultim1, InIt2 prim2, InIt2 ultim2);

template <class InIt1, class InIt2, class OutIt, class Compare>

bool includes (InIt1 prim1, InIt1 ultim1, InIt2 prim2, InIt2 ultim2,
Compare cp);

2. set_union() construieşte mulţimea reuniune M1M2 a mulţimilor date prin intervalele de iteratori

template <class InIt1, class InIt2, class OutIt>

OutIt set_union(InIt1 prim1, InIt1 ultim1, InIt2 prim2, InIt2 ultim2,


OutIt rez);

template <class InIt1, class InIt2, class OutIt, class Compare>

OutIt set_union(InIt1 prim1, InIt1 ultim1, InIt2 prim2, InIt2 ultim2,


OutIt rez,

...... Compare cp);

3. set_intersection()construieşte mulţimea intersecţie M1M2 a mulţimilor date prin


intervalele de iteratori

template <class InIt1, class InIt2, class OutIt>

OutIt set_intersection(InIt1 prim1,InIt1 ultim1,InIt2 prim2,InIt2


ultim2,OutIt rez);
template <class InIt1, class InIt2, class OutIt, class Compare>

OutIt set_intersection(InIt1 prim1, InIt1 ultim1, InIt2 prim2, InIt2


ultim2,

........ OutIt rez, Compare cp);

4. set_difference() construieşte mulţimea diferenţă M1-M2 a mulţimilor date prin intervalele de


iteratori (conţine elementele din prima mulţime care nu apar în cea de a doua)

template <class InIt1, class InIt2, class OutIt>

OutIt set_difference(InIt1 prim1,InIt1 ultim1,InIt2 prim2,InIt2


ultim2,OutIt rez);

template <class InIt1, class InIt2, class OutIt, class Compare>

OutIt set_difference(InIt1 prim1, InIt1 ultim1, InIt2 prim2, InIt2


ultim2,

........ OutIt rez, Compare cp);

6. set_symmetric_difference() construieşte mulţimea diferenţă simetrică (M1-M2)(M2-


M1)=(M1M2)-(M1M2) a mulţimilor date prin intervalele de iteratori (conţine elementele din cele două
mulţimi exceptând elementele comune)

template <class InIt1, class InIt2, class OutIt>

OutIt set_symmetric_difference(InIt1 prim1, InIt1 ultim1,

............ InIt2 prim2, InIt2 ultim2, OutIt rez);

template <class InIt1, class InIt2, class OutIt, class Compare>

OutIt set_symmetric_difference(InIt1 prim1, InIt1 ultim1, InIt2 prim2,


InIt2 ultim2,

............ OutIt rez, Compare cp);

Algoritmi cu heapuri.

Un heap este un arbore binar complet parţial ordont, folosit pentru implementarea cozii prioritare.

1. pop_heap()rearanjează elementele din intervalul [prim, ultim-1), astfel că dacă se


extrage un element din vârful heapului (din poziţia prim) prin interschimbul cu elementul din poziţia
ultim-1 , noul interval [prim,ultim-1) să reprezinte un heap. Complexitatea operaţiei este
O(log(ultim-prim)).

template <class RAIt>

void pop_heap(RAIt prim, RAIt ultim);

template <class RAIt, class Compare>


void pop_heap (RAIt prim, RAIt ultim, Compare cp);

2. push_heap()rearanjează elementele din heap astfel că dacă se adaugă un element în ultima


poziţie ultim-1 intervalul [prim, ultim) să reprezinte în continuare un heap. Complexitatea operaţiei
este O(log(ultim-prim))

template <class RAIt>

void push_heap(RAIt prim, RAIt ultim);

template <class RAIt, class Compare>

void push_heap (RAIt prim, RAIt ultim, Compare cp);

3. make_heap() aranjează elementele din intervalul de iteratori, astfel încât acestea să reprezinte un
heap

template <class RAIt>

void make_heap(RAIt prim, RAIt ultim);

template <class RAIt, class Compare>

void make_heap (RAIt prim, RAIt ultim, Compare cp);

4. sort_heap() transformă un heap într-o secvenţă sortată (metoda de sortare heapsort).


Complexitatea este O(N log N) şi sortarea nu este stabilă.

template <class RAIt>

void sort_heap(RAIt prim, RAIt ultim);

template <class RAIt, class Compare>

void sort_heap (RAIt prim, RAIt ultim, Compare cp);

Algoritmi pentru minim şi maxim.

1. min(), max()determină cel mai mic (respectiv cel mai mare) între două elemente

template <class T>

const T& min(const T& a, const T& b);

template <class T, class Compare>

const T& min(const T& a, const T& b, Compare cp);

template <class T>

const T& max(const T& a, const T& b);

template <class T, class Compare>


const T& max(const T& a, const T& b, Compare cp);

2. min_element(), max_element() furnizează un iterator la cel mai mic (cel mai mare) element
din intervalul de iteratori.

template <class FwIt>

FwIt min_element(FwIt prim, FwIt ultim);

template <class FwIt, class Compare>

FwIt min_element(FwIt prim, FwIt ultim, Compare cp);

template <class FwIt>

FwIt max_element(FwIt prim, FwIt ultim);

template <class FwIt, class Compare>

FwIt max_element(FwIt prim, FwIt ultim, Compare cp);

Algoritmi de permutare.

O permutare provine dintr-o secvenţă prin schimbarea între ele a două elemente. Pentru o secvenţă
având N elemente, există N! permutări. Mulţimea celor N! permutări se poate considera ordonată după
relaţia < (şi în general comp) între elemente. Conform acestei relaţii de ordine putem defini permutarea
următoare (respectiv permutarea precedentă) în raport cu permutarea curentă.

prev_permutation(), next_permutation() generează în intervalul de iteratori permutarea


precedentă (următoare). Întoarece true dacă există permutarea precedentă (următoare) şi false în caz
contrar.

template <class BIt>

bool prev_permutation(BIt prim, BIt ultim);

template <class Bit, class Compare>

bool prev_permutation(BIt prim, BIt ultim, Compare cp);

template <class BIt>

bool next_permutation(BIt prim, BIt ultim);

template <class Bit, class Compare>

bool next_permutation(BIt prim, BIt ultim, Compare cp);

Algoritmi numerici.

Descriu operaţii numerice generale. Accesul la aceştia se obţine prin includerea fişierului antet
<numeric>.

1. accumulate() adună la elementele din intervalul de iteratori o valoare iniţială dată ca parametru.
template <class InIt, class T>

T accumulate(InIt prim, InIt ultim, T vinit);

template <class InIt, class T, class BinOp>

T accumulate(InIt prim, InIt ultim, T vinit, BinOp bop);

2. inner_product() adaugă o valoare iniţială la produsul scalar al celor două containere x şi y:

rez  vinit   xi yi .

template <class InIt1, class InIt2, class T>

T inner_product(InIt1 prim1, InIt1 ultim1, init2 prim2, T vinit);

template <class InIt1, class InIt2, class T, class BinOp1, class BinOp2>

T inner_product(InIt1 prim1, InIt1 ultim1, InIt2 prim2, T vinit,

...... BinOp1 bop1, BinOp2 bop2);

3. partial_sum() calculează în containerul rezultat valorile acumulate din prima poziţie, primele două,
primele trei, etc.

template <class InIt, class OutIt>

OutIt partial_sum(InIt prim, InIt ultim, OutIt rez);

template <class InIt, class OutIt, class BinOp>

OutIt partial_sum(InIt prim, InIt ultim, OutIt rez, BinOp bop);

4. adjacent_difference() calculează diferenţele între elementele consecutive ale unui container


c, adică d0=c0, di=ci-ci-1, i>0.

template <class InIt, class OutIt>

OutIt adjacent_difference(InIt prim, InIt ultim, OutIt rez);

template <class InIt, class OutIt, class BinOp>

OutIt adjacent_difference (InIt prim, InIt ultim, OutIt rez, BinOp bop);

Iteratori.

Iteratorii sunt generalizări ale pointerilor; aceştia ne permit să lucrăm cu diverse structuri de date
(containere) în mod uniform.

Un iterator este un obiect care serveşte pentru a parcurge un container, într-un mod asemănător
pointerilor în cazul vectorilor.
Iteratorii pot fi incrementaţi cu ++, dereferenţiaţi cu * şi comparaţi cu !=. Containerele pot genera
iteratori cu funcţiile begin() şi end().

Funcţia begin() întoarce o valoare de iterator pe primul element din container. Exemplu:

vector<int> v(10);

vector::iterator it=v.begin();

Funcţia end() poziţionează iteratorul după ultimul element din container. Intervalul [v.begin(),
v.end()) reprezintă un domeniu sau interval de iteratori. Toate elementele it[v.begin(),
v.end()) din acest domeniu (exceptând desigur v.end()) pot fi dereferenţiate şi *it reprezintă un
element din container având tipul value_type.

Algoritmii generici acţionează asupra containerelor prin intermediul iteratorilor. Astfel algoritmul
copy() utilizează, pentru a copia o porţiune din containerul cs în containerul cd, trei iteratori: un iterator
pe primul element copiat din containerul sursă, un iterator după ultimul element copiat din containerul
sursă şi un iterator la prima poziţie din containerul destinaţie:

copy(cs.begin(),cs.end(),cd.begin());

Iteratorii pot fi împărţiţi în categorii de iteratori, în funcţie de operaţiile care se pot efectua asupra lor.
(iteratori de intrare, iteratori de ieşire, iteratori de avans, iteratori bidirecţionali şi iteratori cu acces
direct). Aceste clase formează o ierarhie având la bază o clasă de iteratori cu posibilităţi foarte limitate
(iteratorii de intrare şi de ieşire), iar clasele derivate au posibilităţi mai mari.

struct input_iterator_tag{};

struct output_iterator_tag{};

struct forward_iterator_tag : public input_iterator_tag{};

struct bidirectional_iterator_tag : public forward_iterator_tag{};

struct random_access_iterator_tag : public bidirectional_iterator_tag{};

Aşa cum avem pointeri la obiecte const, putem defini iteratori la elemente const:

vector::const_iterator it;

Există trei tipuri de adaptori ai iteratorilor: iteratori inverşi (reverse iterators), iteratori de
inserţie (insert iterators) şi iteratori pe memorie (raw storage iterators).

Iteratorii inverşi inversează comportarea operatorilor ++ şi --. Toate containerele standard asigură
funcţiile rbegin() şi rend(), asigurând poziţionarea pe ultimul element, respectiv înaintea primului
element din container.

Iteratorii pe memorie realizează în mod eficient copierea unui container într-o zonă de memorie
neiniţializată, folosind funcţiile get_temporary_buffer() şi return_temporary_buffer() .

Toţi iteratorii (exceptând iteratorii de ieşire) asigură o funcţie distanţă între doi iteratori, având un tip
asociat difference_type.
Un iterator este minimal caracterizat prin tipul valorii elementelor containerului şi tipul funcţiei distanţă
specifice containerului asociat.

Fiecărui tip de iterator i se asociază o clasă de proprietăţi (sau trăsături) iterator_traits, care
conţine: o serie de nume de tipuri recunoscute de iterator precum: tipul elementelor containerului
(value_type), tipul distanţei (difference_type), tipul dimensiunii containerului (size_type),
tipul referinţă (reference) şi pointer (pointer) asociate elementelor containerului, categoria
iteratorului (iterator_category): Clasele de proprietăţi servesc deci pentru exportul unor nume de
tipuri din clasa iterator.

Clasa de proprietăţi trebuie specificată pentru fiecare tip iterator nou definit.

template <class Itor>

struct iterator_traits{

typedef typename Itor::value_type value_type;

typedef typename Itor::difference_type difference_type;

typedef typename Itor::pointer pointer;

typedef typename Itor::reference reference;

typedef typename Itor::iterator_category iterator_category;

};

În cazul în care iteratorul este un simplu pointer (pentru vectori şi tablouri predefinite C), distanţa între
iteratori se calculează printr-o simplă scădere şi are tipul predefinit ptrdiff_t, avem particularizarea
(specializarea) clasei de proprietăţi:

template <class T>

struct iterator_traits<T*>{

typedef T value_type;

typedef ptrdiff_t difference_type;

typedef T* pointer;

typedef T& reference;

tzpedef size_t size_type;

typedef random_access_iterator_tag iterator_category;

};

Numele tipurilor care apar în clasa de proprietăţi pot fi date ca parametrii ai şablonului. În acest caz,
clasa de proprietăţi precedentă va avea forma:
template <class Categ, class T, class Dist=ptrdiff_t,class Ptr=T*,class
Ref=T& >

struct iterator_traits{

typedef T value_type;

typedef Dist difference_type;

typedef Ptr pointer;

typedef Ref reference;

typedef Categ iterator_category;

};

Vom exemplifica definirea funcţiei distanţă între doi iteratori. În caz că iteratorii sunt de intrare, distanţa
este numărul de deplasări necesar ajungerii în poziţia indicată de cel de-al doilea iterator:

template <class InItor>

typename iterator_traits<InItor>::difference_type dist(InItor i1, InItor


i2,

...................... input_iterator_tag){

typename iterator_traits<InItor>::difference_type d=0;

while(i1++ != i2)

d++;

return d;

În cazul iteratorilor cu acces direct, distanţa se calculează prin simpla diferenţă a iteratorilor:

template <class RAItor>

typename iterator_traits<RAItor>::difference_type dist(RAItor i1, RAItor


i2,

......................random_acces_iterator_tag){

return i2-i1;

Se observă că cel de-al treilea paramatru a fost bdat numai pentru a deosebi semnăturile celor două
funcţii. Cele două funcţii pot fi comasate într-una singură:

template <class Itor>


typename iterator_traits<Itor>::difference_type dist(Itor i1, Itor i2){

return dist(i1, i2, iterator_traits<Itor>::iterator_category());

};

Toate containerele standard STL definesc tipurile: iterator şi const_iterator şi metodele


begin()/end() pentru container.

Clasele container definite de utilizator pot utiliza algoritmi generici numai dacă le asociem clase iteratori
corespunzătoare.

Dacă o clasă container definită de utilizator foloseşte un container standard, atunci trebuie să delegăm
containerului utilizator tipurile şi metodele clasei iterator asociate containerului standard. De exemplu:

//delegarea iteratorilor clasei vector catre clasa grupa

class grupa{

typedef vector<char*> gr_stud;

gr_stud g;

public:

typedef gr_stud::iterator iterator;//preluam iterator din vector

typedef gr_stud::const_iterator const_iterator;

iterator begin(){return g.begin();};

iterator end(){return g.end();};

// alte metode

Definiţi un container având tipul elementelor şi dimensiunea parametrizată, pe baza tablourilor


predefinite din C.

// preluat din Jossutis The C++ Standard Library - A Tutorial and


Reference

#include <cstddef>

template<class T, std::size_t dim>

class carray {

private:

T v[dim]; // tablou predefinitcu elemente de tip T si dimensiune


fixata

public:
// definiri de tipuri

typedef T.. value_type;

typedef T*.. iterator;

typedef const T* const_iterator;

typedef T&.. reference;

typedef const T& const_reference;

typedef std::size_t size_type;

typedef std::ptrdiff_t difference_type;

// suport pentru iteratori

iterator begin() { return v; }

const_iterator begin() const { return v; }

iterator end() { return v+dim; }

const_iterator end() const { return v+dim; }

// acces direct la elemente

reference operator[](std::size_t i) { return v[i]; }

const_reference operator[](std::size_t i) const { return v[i]; }

// dimensiunea e constanta

size_type size() const { return dim; }

size_type max_size() const { return dim; }

// conversie la tablou obisnuit

T* as_array() { return v; }

};

#include <algorithm>

#include <functional>

#include "carray.hpp"

#include "print.hpp"

using namespace std;

int main()
{

carray<int,10> a;

for (unsigned i=0; i<a.size(); ++i) {

.. a[i] = i+1;

afisare(a);

reverse(a.begin(),a.end());

afisare(a);

transform(a.begin(),a.end(), // sursa

.... a.begin(),.... // destinatie

.... negate<int>());.. // operatie

afisare(a);

Pentru clasele vector şi string iteratorii sunt chiar pointeri, aşa că delegarea iteratorilor este mai simplă:

O clasă iterator are forma:

template <class T>

class Itor{

public:

// constructori, destructor

bool operator!=(const Itor<T>&) const;

bool operator==(const Itor<T>&) const;

Itor<T>& operator++(); //prefix

Itor<T> operator++(int); //postfix

T& operator*() const;

T* operator->() const;

private:

//asocierea cu containerul

};

Iteratori de intrare (InputIterator).


Un iterator de intrare serveşte pentru a citi dintr-un container.

Un iterator de intrare it oferă: incrementare (it++ sau ++it), dereferenţiere pentru acces la
informaţie(*it în dreapta unei atribuiri -rvalue) şi comparaţie cu alt iterator (prin == sau !=).

Informaţia indicată de iterator poate fi numai accesată (citită), nu şi modificată (adică nu este permisă o
operaţie de tipul *it=15 ). Exemplu:

vector<int> x;

vector<int>::iterator itv;

v.push_back(15);

v.push_back(24);

for(itv=v.begin(); itv!=v.end(); itv++)

cout << *itv;

// operatia cin >> *itv este incorecta

Algoritmii bazaţi pe iteratori de intrare sunt cu o trecere unică, într-un singur pas şi nu se bazează pe
valori precedente din această trecere (iteratorul poate fi numai incrementat).

Iteratori de ieşire (OutputIterator).

Un iterator de ieşire transferă informaţie în container (scriere într-o singură trecere). Operaţiile permise
sunt incrementarea (it++ sau ++it) şi dereferenţierea pentru modificare informaţie – lvalue
(*it=...).Iteratorii de ieşire nu pot fi comparaţi cu operatorii == sau !=.

Iteratori de inserţie.

Unii algoritmi generici depun rezultatele într-un container. Aceasta impune rezervarea unui spaţiu fixat
în containerul destinaţie. Dacă numărul elementelor din rezultat nu poate fi cunoscut dinainte, vom utiliza
un adaptor de inserţie pentru a crea elemente în containerul destinaţie, atunci când avem nevoie.

Un iterator de inserţie adaptează un iterator de ieşire pentru a asigura anumite funcţionalităţi. Iteratorii
de inserţie pot fi:

 back_inserter<container> care întoarce un iterator de ieşire, prin intermediul căruia se adaugă


o valoare la sfârşitul containerului prin operaţia push_back().

De exemplu pentru a copia elementele containerului s în d în ordine inversă folosim:

copy(s.rbegin(), s.rend(), back_inserter(d));

 front_inserter<container> care întoarce un iterator de ieşire la începutul containerului, prin


intermediul căruia se adaugă o valoare la începutul containerului prin operaţia push_front()
 inserter<container, iterator> care întoarce un iterator de ieşire la poziţia din container
indicată de iterator, prin intermediul căruia se adaugă o valoare prin operaţia insert().

De exemplu copierea tuturor elementelor lui s în d se poate face prin:


copy(s.begin(), s.end(), inserter(d, d.begin()));

Iteratori flux de ieşire (ostream OutputIterator).

Pentru a defini un iterator pentru un obiect ostream folosim una din formele:

ostream_iterator<T> nume(ostream& os);

ostream_iterator<T> nume(ostream& os, const char* delimitator);

Un algoritm de tip copy() având un parametru iterator destinaţie va scrie datele de tip T în fluxul
os, separându-le cu delimitatorul dat ca al doilea parametru. Exemple:

//definire iterator pe fluxul de ieşire pentru întregi

//întregii sunt separati prin spatii

ostream_iterator<int> OutIt(cout, ” ”);

copy(v.begin(),v.end(),OutIt); // echivalent cu

// for(it=v.begin(); it!=v.end(); it++)

// cout << *it << ” ”;

Copierea unui fişier, cu numele sursei şi destinaţiei date ca parametri ai liniei de comandă, se realizează
prin programul:

#include <fstream>

#include <algorithm>

#include <iterator>

using namespace std;

void main(int ac, char** av){

ifstream s(av[1]);

ofstream d(av[2]);

copy(istream_iterator<char>(in),istream_iterator<char>(),

.. ostream_iterator<char>(d));

Iteratori flux de intrare (istream InputIterator).

Un iterator flux de intrare istream_iterator<T>() defineşte un iterator pentru un obiect


istream. Acesta permite extracţia de informaţii dintr-un flux de intrare într-un container.

Un iterator flux de intrare se declară.


istream_iterator<T> (istream& is);

în care T reprezintă tipul elementelor citite din flux. Declaraţia de mai sus construieşte un iterator care
accesează un obiect istream.

Pentru a defini sfârşitul fluxului se foloseşte constructorul implicit:

istream_iterator<T> ()

De exemplu citirea din fluxul de intrare a unor întregi într-un container vector se poate realiza cu:

vector<int> v;

istream_iterator<int>(cin) start;

istream_iterator<int>() stop;

copy(start, stop, back_inserter(v));

Primul argument construieşte un iterator pe fluxul cin, al doilea argument crează un pointer la sfârşitul
intrării. Copierea se face prin adăugare la sfârşit în containerul v.

Următorul program citeşte din fluxul cin mai multe şiruri de caractere, cu care creează un vector de
şiruri şi le inserează în fluxul cout separeater între ele cu un spaţiu. Citirea se face cu algoritmul copy cu
iteratori pe fluxul de intrare.

#include <vector>

#include <algorithm>

#include <string>

#include <iterator>

#include <iostream>

using namespace std;

void main(){

vector<string> vs;

copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(vs));

typedef vector<string>::iterator vitor;

for(vitor i=vs.begin(); i!=vs.end(); ++i)

cout << *i << " ";

cout << endl;

}
//Nicolai M. Josuttis "The C++ Standard Library - A Tutorial and
Reference"

#include <iostream>

#include <iterator>

using namespace std;

int main()

// creaza istream iterator care citeste intregi de la cin

istream_iterator<int> intReader(cin);

// creaza iterator end-of-stream

istream_iterator<int> intReaderEOF;

// cat timp putem citi elemente cu istream iterato le afisam

while (intReader != intReaderEOF) {

.. cout << *intReader << endl;

.. ++intReader;

Iteratori de avans (ForwardIterator).

Iteratorii de avans combină posibilităţile iteratorilor de intrare şi de ieşire, permiţând citirea din şi
scrierea în container.

Iteratori bidirecţionali (BidirectionalIterator).

Au aceleaşi posibilităţi ca iteratorii de avans, permiţând, în plus, operaţia de decrementare (asigurându-


ne astfel traversarea în ambele direcţii ale unui container).

Iteratori cu acces direct (RandomAccessIterator).

Un iterator cu acces direct permite accesul independent la orice element al containerului. Faţă de
iteratorii bidirecţionali permit operaţii aritmetice extinse asupra iteratorilor în timp constant(it+n, it-
n, it+=n, it-=n şi it1-it2) şi operaţii de comparaţie extinse (it1<it2, it1>it2,
it1<=it2, it1>=it2).

O funcţie care lucrează cu elementele unui container nu va transmite ca argument şi nici ca valoare
întoarsă containerul, ci un iterator la acesta. De exemplu o funcţie care calculează suma elementelor unui
vector:
double suma(vector<double>v){

vector<double>::iterator i;

double s=0.;

for(i=v.begin(); i!=v.end(); i++)

s+=*i;

return s;

ar putea fi generalizată pentru orice container astfel:

template <class Cont>

double suma(const Cont& c){

Cont::iterator i;

double s=0.;

for(i=c.begin();i!=c.end(); i++)

s+=*i1;

return s;

Funcţia definită mai sus nu funcţionează pentru tablouri predefinite. Dacă transmitem ca parametru un
iterator la container:

template <class Iter>

double suma(Iter start, Iter stop){

double s=0.;

while(start!=stop)

s+= *start++;

return s;

vom putea folosi funcţia şi pentru tablouri predefinite:

double x[]={1.5,2.,3.5,6.2};

double sum = suma(x, x+4);


Obiecte funcţii (functori).

Într-o expresie, apelul unei funcţii este înlocuit prin rezultatul întors de funcţie. Misiunea funcţiei poate
fi preluată de către un obiect. În acest scop se supraîncarcă operatorul apel de funcţie sub forma
function operator()().

Obiectele aparţinând claselor care au supraîncărcat operatorul apel de funcţie sunt obiecte funcţii sau
functori.

STL prevede o serie de clase generice pentru comparaţii. Clasele comparaţii sunt funcţii binare derivate
din clasa binary_function:

template <class arg1, class arg2, class rez>

struct binary_function{

typedef arg1 first_argument_type;

typedef arg2 second_argument_type;

typedef rez result_type;

};

Pentru clase unare se defineşte şablonul unary_function. Exemplu:

template <class T>

struct equal_to : binary_function(T, T, bool){

bool operator()(const T& x, const T& y) const {

return x==y;

};

În acest mod sunt definiţi functorii: equal_to<T>, not_equal<T>, greater<T>,


less<T>, greater_equal(), less_equal() care asigură algoritmilor o interfaţă uniformă.

Astfel pentru a sorta un tablou cu funcţia de comparaţie mai mic în valoare absolută, vom defini:

#include <iostream>

#include <functional> // less<T>

#include <cstdlib> // abs()

struct absLess{

bool operator()(int x, int y)const{

return abs(x) < abs(y);


};

};

STL asigură, de asemenea, prin supraîncărcarea operatorului apel de funcţie, clase pentru operaţiile
aritmetice şi logice. Aceste sunt: plus<T>, minus<T>, multiplies<T>, divides<T>,
modulus<T>, negate<T>, logical_and<T>, logical_or<T>.

Afişaţi parametrii liniei de comandă, sortaţi în ordine invers-lexicografică.

#include <iostream>

#include <functional>

#include <string>

#include <algorithm>

using namespace std;

void main(int ac, char** av){

sort(av, av+ac, greater_equal<string>());

for(int i=0; i<ac; i++)

cout << av[i] << ” ”;

cout << endl;

Functori adaptori.

Sunt obiecte funcţii care cooperează cu alte obiecte funcţii pentru a le adapta la cerinţe diferite.

not1 – are ca parametru un functor predicat unar şi întoarce opusul lui. Exemplu:

struct impar : public unary_function<int, bool>{

bool operator()(int x)const{

return x % 2 != 0;

};

};

. . .

int a;

cin >> a;

if(not1(impar()(a)))
cout << a << ” este par\n”;

. . .

not2 – are ca parametru un functor predicat binar şi întoarce opusul lui.

bind1st, bind2nd – transformă un functor predicat binar, în functor predicat unar, legând unul din
argumente de o valoare dată ca parametru. De exemplu functorul less<T> compară două valori. Dacă
fixăm primul argument (de exemplu la valoarea 100), se generează un functor unar.

// gaseste primul element care nu este < 100

find_if(v.begin(),v.end(),bind1st(less<int>(),100));

// gaseste primul element < 100

find_if(v.begin(),v.end(),bind2nd(less<int>(),100));

ptr_fun – transformă un pointer la funcţie în functor. Pointerul la funcţie poate avea unul sau doi
parametri.

#include <iostream>

#include <functional>

#include <cmath>

double (*f)(double) = sin;

void main(){

double x=3.1416;

cout << f(x) << endl;

cout << ptr_fun(f)(x) << endl;

Biblioteca de şabloane standard (Standard Template Library).

Biblioteca de Şabloane Standard (STL) asigură o abstractizare standardizată a datelor prin intermediul
containerelor şi o abstractizare procedurală prin intermediul algoritmilor.

Programele dezvoltate folosind STL beneficiază de o viteză de dezvoltare şi o viteză de execuţie sporite.
Ele sunt mai eficiente, mai robuste, mai portabile, mai uşor de modificat şi întreţinut.

Componentele STL sunt: containerele, iteratorii, algoritmii, functorii (obiectele funcţii) şi adaptorii.

Containere.

Un container este un obiect care păstrează o colecţie de alte obiecte. Containerele (exceptând string şi )
sunt clase generice (parametrizate)
În STL se folosesc două tipuri de containere:

 containere secvenţiale (vector, listă, coadă cu două capete - deque)


 containere asociative (mulţime, relaţie)

Un container secvenţial păstrează colecţia de obiecte într-o ordine strict liniară.

Containerele asociative păstrează informaţiile din colecţie sortate, ceea ce permite regăsirea rapidă
a obiectelor din colecţie, pe baza unei chei.

Vectorul (vector)

Constructor Efect Complexit


ate
vector<T>v crează un vector vid O(1)
vector<T>v(n) crează un vector cu n elemente O(n)
vector<T>v(n,val) crează un vector cu n elemente iniţializate O(n)
cu val
vector<T>v(v1) crează un vector iniţializat cu vectorul v1 O(n)

Accesor Efect Complexitate


v[i] întoarce elementul i O(1)
v.front() întoarce primul element O(1)
v.back() întoarce ultimul element O(1)
v.capacity() întoarce numărul maxim de O(1)
elemente
v.size() întoarce numărul curent de O(1)
elemente
v.empty() întoarce true dacă vectorul este O(1)
vid
v.begin() întoarce un iterator la începutul O(1)
vectorului
v.end() întoarce un iterator după sfârşitul O(1)
vectorului

Modificator Efect Complexitate


v.push_back(val) adaugă o valoare la sfârşit O(1)
v.insert(iter,val) inserează valoarea în poziţia indexată O(n)
de iterator
v.pop_back() şterge valoarea de la sfârşit O(1)
v.erase(iter) şterge valoarea indexată de iterator O(n)
v.erase(iter1,iter2) şterge valorile din domeniul de O(n)
iteratori
Coada cu două capete (deque)

Constructor Efect Complexitate


deque<T>d crează un deque vid O(1)
deque<T>d(n) crează un deque cu n elemente O(n)
deque<T>d(n,val) crează un deque cu n elemente O(n)
iniţializate cu val
deque<T>d(d1) crează un deque iniţializat cu deque-ul O(n)
d

Accesor Efect Complexitate


d[i] întoarce elementul i O(1)
d.front() întoarce primul element O(1)
d.back() întoarce ultimul element O(1)
d.capacity() întoarce numărul maxim de O(1)
elemente
d.size() întoarce numărul curent de O(1)
elemente
d.empty() întoarce true dacă deque-ul este O(1)
vid
d.begin() întoarce un iterator la începutul O(1)
deque-ului
d.end() întoarce un iterator după sfârşitul O(1)
deque-ului

Modificator Efect Complexitate


d.push_back(val) adaugă o valoare la sfârşit O(1) sau
O(n)
d.insert(iter,val) inserează valoarea în poziţia O(n)
indexată de iterator
d.pop_back() şterge valoarea de la sfârşit O(1)
d.erase(iter) şterge valoarea indexată de iterator O(n)
d.erase(iter1,iter2) şterge valorile din domeniul de O(n)
iteratori
Lista (list)

Constructor Efect Complexitate


list<T>L crează o listă vidă O(1)
list <T>L(L1) crează o listă iniţializată cu lista O(n)
L1

Accesor Efect Complexitate


L.front() întoarce primul element O(1)
L.back() întoarce ultimul element O(1)
L.size() întoarce numărul curent de O(1)
element
L.empty() întoarce true dacă lista este O(1)
vidă
L.begin() întoarce un iterator la începutul O(1)
listei
L.end() întoarce un iterator după O(1)
sfârşitul liste
Modificator Efect Complexitate
L.push_front(val) adaugă o valoare la început O(1)
L.push_back(val) adaugă o valoare la sfârşit O(1)
L.insert(iter,val) inserează valoarea în poziţia O(1)
indexată de iterator
L.pop_front() şterge valoarea de la început O(1)
L.pop_back() şterge valoarea de la sfârşit O(1)
L.erase(iter) şterge valoarea indexată de iterator O(1)
L.erase(iter1,iter2) şterge valorile din domeniul de O(1)
iteratori
L.remove(val) şterge toate apariţiile lui val în O(n)
listă
L.remove_if(pred) şterge toate elementelele care O(n)
satisfac predicatul
L.reverse(pred) inversează lista O(n)
L.sort() sortează lista O(nlogn)
L.sort(comp) sortează lista folosind funcţia de O(nlogn)
comparaţie
L.merge(L1) interclasează două liste sortate O(n)
Stiva (stack)

Constructor Efect Complexitate


stack<T>s crează o stivă vidă O(1)

Accesor Efect Complexitate


s.top() întoarce elementul din vârf O(1)
s.size() întoarce numărul curent de O(1)
elemente
s.empty() întoarce true dacă stiva O(1)
este vidă

Modificator Efect Complexitate


s.push(val) pune o valoare în vârful O(1)
stivei
s.pop() şterge valoarea din O(1)
vârful stivei
Coada (queue)

Constructor Efect Complexitate


queue<T>q crează o coadă vidă O(1)

Accesor Efect Complexitate


q.front() întoarce elementul din faţa O(1)
cozii
q.back() întoarce elementul din O(1)
spatele cozii
q.size() întoarce numărul curent de O(1)
elemente
q.empty() întoarce true dacă coada O(1)
este vidă

Modificator Efect Complexitate


q.push(val) pune o valoare în spatele O(1)
cozii
q.pop() şterge valoarea din faţa cozii O(1)
Coada prioritară(priority queue)

Constructor Efect Complexitate


priority_queue<T,comp>q crează o coadă prioritarăvidă, O(1)
sortată cu comp

Accesor Efect Complexitate


q.top() întoarce elementul “cel mai prioritar” (mai O(1)
“mare”)
q.size() întoarce numărul curent de elemente O(1)
q.empty() întoarce true dacă coada prioritară este O(1)
vidă

Modificator Efect Complexitate


q.push(val) inserează o valoare în coada prioritară O(logn)
q.pop() scoate elementul cel mai “mare” din coada O(1)
prioritară

Mulţimi şi multimulţimi (set)

Mulţimile păstrează obiecte sortate cu operatorul < (care poate fi supraîncărcat), pentru a fi regăsite
rapid. Într-o mulţime, un element apare o singură dată. Într-o multimulţime pot apare mai multe copii ale
unui element.

Constructor Efect Complexitate


set<tip_ch,comp_ch>m crează o mulţime vidă; sortarea elementelor se O(1)
face cu comp_ch

Accesor Efect Complexitate


m.find(ch) întoarce un iterator la apariţia cheii în m (m.end(), O(log n)
dacă găseşte cheia în m)
m.lower_bound(ch) întoarce un iterator la prima apariţie a cheii în m O(log n)
(m.end(), dacă nu găseşte cheia în m)
m.upper_bound(ch) întoarce un iterator la primul element mai mare ca O(log n)
cheia în m (m.end(), dacă nu există un asemenea
element)
m.equal_range(ch) întoarce O(log n)
pair<lower_bound(ch),upper_bound(ch)>
m.count(ch) întoarce numărul de elemente egale cu cheia ch în m O(log n)
m.size() întoarce numărul curent de elemente O(1)
m.empty() întoarce true dacă coada prioritară este vidă O(1)
m.begin() întoarce un iterator la primul element (cel mai mic)din O(1)
m
m.end() întoarce un iterator la ultimul element (cel mai O(1)
mare)din m

Modificator Efect Complexit


ate
m.insert(iter, inserează un element ch în m. Întoarce iterator la elementul O(log
ch) ch din m n)
m.insert(ch) inserează un element ch în m.Întoarce O(log
pair<iterator,bool> bool este true dacă n)
inserarea a avut loc. Iterator indică cheia inserată

Relaţii şi multirelaţii (map)

Relaţiile pot fi privite ca vectori generalizaţi, în care locul indicelui întreg îl ia cheia, care poate fi de orice
tip, motiv pentru care relaţiile se mai numesc şi tabele asociative. Memorarea unei perechi <cheie,
valoare> poate fi făcută prin atribuirea map[ch]=val. Relaţiile se implementează cu arbori de căutare
echilibraţi (de exemplu arbori roşii-negri), care asigură timpi de căutare-regăsire logaritmici.. Dacă perechea
<cheie, valoare> nu există în relaţie, indexarea map[ch] crează o intrare falsă în relaţie, motiv
pentru care se caută cheia în prealabil cu map.find(ch).

Constructor Efect Complexitate


map<tip_ch,tip_val,comp_ch>r crează o relaţie vidă; sortarea O(1)
elementelor după cheie se face cu
comp_ch

Accesor Efect Complexitate


r[ch] întoarce valoarea accesată prin cheie O(log n)
r.find(ch) întoarce un iterator la perechea cheie-valoare (r.end(), dacă O(log n)
nu găseşte cheia în r)
r.size() întoarce numărul curent de elemente O(1)
r.empty() întoarce true dacă relaţia este vidă O(1)
r.begin() întoarce un iterator la prima pereche din r O(1)
r.end() întoarce un iterator la ultima pereche din r O(1)

Modificator Efect Complexitate


r[ch]=val memorează perechea cheie- O(log n)
valoare în r
m.insert(pereche) are acelaşi efect O(log n)

TDA String.

Şirul de caractere este un container de tip secvenţă. Clasa string este construită folosind şablonul
basic_string
Un operand de tip secvenţă poate fi specificat în mai multe moduri, şi anume:

- c - o secvenţă cu un element cu valoarea c

- n, c – o secvenţă de n elemente cu valoarea c

- s – o secvenţă terminată prin 0 (în genul şirurilor de caractere C)

- s, n – o secvenţă de n caractere, începând din s

- str – o secvenţă specificată de un obiect string

- str, pos, n – un subşir

- prim, ultim – o secvenţă delimitată de doi iteratori

1.Exemple de utilizare.

Faţă de şirurile de caractere stil C, avem următoarele avantaje:

 operaţii de nivel înalt: append(), concat(), insert(), replace()


 atribuirea de şiruri
 comparaţii de şiruri folosind operatorii: <, =, >

Exemplul 1: Detectarea unui şir palindrom.

//test palindrom

bool pal1(string& s){

string t;

t = s;

reverse(t.begin(), t.end());

return s==t;

};

//se convertesc toate literele in mici

int pal2(string& s){

string mic(s);

transform(s.begin(),s.end(),mici.begin(),tolower);

return pal1(mici);

};

void transform(iterator start, iterator stop, iterator dest, char


(*f)(char)){
while(start!=stop)

dest++ =f(*start++);

};

//se elimină toate semnele de punctuaţie

int pal3(string& s){

string punct=” ,.:;!?\n”

string t=remove_all(s,punct);

return pal2(t);

};

string remove_all(string& txt, string& sep){

string rez;

int ltx=txt.length();

int lsp=sep.length();

for(int i=0; i<ltx; i++){

string car=txt.substr(i,1);

int poz=sep.find(car,0);

if(poz<0 || poz>lsp)

.. rez+=car;

};

return rez;

};

Exemplul 2: Separarea cuvintelor dintr-o linie.

void split(string& txt, string& sep, vector<string>cuv){

int ltx=txt.length();

int start=txt.find_first_not_of(sep,0);

while(start>=0 && start<ltx){

int stop=txt.find_first_of(sep,start);

if(stop<0 || stop>ltx)
.. stop=ltx;

cuv.push_back(txt.substr(start, stop-start);

start=txt.find_first_not_of(sep,stop+1);

};

};

Exemplul 3: Găsirea primului şi ultimului cuvânt ordonat lexicografic dintr-un şir.

void main(){

string txt=”Un text la proba de comparatie cuvinte”;

string mic=”mijloc”;

string mare=”mijloc”;

list<string> cuv;

split(txt, ” ,.:;!?\n”, cuv);

list<string>::iterator crt;

list<string>::iterator stop=cuv.end();

for(crt=cuv.begin(); crt!=stop; ++crt){

if(*crt<mic)

.. mic=*crt;

if(*crt>mare)

.. mare=*crt;

};

cout<<mic<<endl;

cout<<mare<<endl;

};

2. Operaţii cu şiruri.

1. Declararea unei variabile şir:

string s1; //şir C++ neiniţializat

string s2(“ion”);//şir C++ iniţializat cu şir C

string s3(s2); //şir C++ iniţializat cu alt şir


2.Accesul la caracter:

//acces la caracter prin indexare

s2[0]=’B’;

//selecţie subşir de lungime 3 pornind din poz.2

string s3=s2.substr(2, 3);//

//conversie şir C++ în şir C

string nume;

FILE* f=fopen(nume.c_str(),”r”);

3.Lungime şir

s.length();.. //numar de caractere din sir

s.resize(100,’x’);//schimba dim.la 100 si-l umple cu ‘x’

s.empty();.. //test sir vid

4.Atribuire si concatenare

s=s1; //atribuire de sir

s+=s1; //adauga s1 la s

s1+s2 //sir format prin concatenare s1 cu s2

5.Iteratori

string::iterator i; //declarare iterator

s.begin();....//iterator de pornire

s.end();.... //iteratorul dupa ultimul element

6.Inserări, ştergeri, înlocuiri

s.insert(pos,s1); //insereaza s1 in s dupa pos

s.remove(st,p); //sterge p caractere din s, dupa st

s.replace(st,l,s1);//insereaza s1 in s,inlocuind l car

7.Comparaţii

s1==s2, s1!=s2, s1<s2, s1>s2, s1<=s2, s1>=s2

8.Operaţii de căutare
s.find(s1);.... //cauta in s unde incepe s1

s.find(s1,pos);.. //idem, incepand cautarea din pos

s.find_first_of(s1,pos);//determina prima aparitie a primului caracter


din s in s1

........ //pornind din pos

s.find_first_not_of(s1,pos);//determina prima aparitie a primului


caracter din s

.......... // ce nu e in s1 pornind din pos

9.Intrări / ieşiri

stream << s;.... //inserare s in stream

stream >> s;.... //extractie s din stream

getline(stream, s, sep);//citeste o linie din stream in s, terminata prin


sep

O variabilă string poate primi prin atribuire:

 un alt string
 un literal
 un caracter
 un tablou de caractere stil C

Funcţiile begin() şi end() întorc iteratori cu acces direct de început şi sfârşit de şir.

Caracteristicile setului de caractere folosit(ASCII sau UNICODE) sunt descrise în structura


char_traits.

Prin indexare se asigură acces la un caracter din string fără verificarea domeniului; accesul cu verificare
este asigurat de funcţia at().

3.Implementare TDA string.

class string{

public:

typedef char* iterator;

typedef int(*pfi)(int);//pointer la functie

//constructori

string();.. //constructor implicit

string(char*s); //constructor de initializare


string(string&);//constructor de copiere

~string();.. //destructor

//iteratori

iterator begin();

iterator end();

//functii generale

int empty();

int length();

void resize(int, char);

//intoarce un string obtinut cu elemente din pozitia p o obiectului, de


lungime l

string substr(int p, int l);

//intoarce prima pozitie din obiect, (cautarea incepand din p), a


stringului str

int find(string& str, int p=0);

int find(char c, int p=0);

int find(const char* s, int p=0 );

//intoarce prima pozitie din obiect(cautarea incepand din p),a unui


element a lui s

int find_first_of(string& s, int p);

//intoarce prima pozitie din obiect, (cautarea

//incepand din p), a unui element care nu e in s

int find_first_not_of(string& s, int p);

//insereaza in sir, inaintea pozitiei p un

//operand secventa(sub toate formele prezentate)

string& insert(int p, char* s);

string& insert(int p, char* s,int n);

string& insert(int p, const string& str);

string& insert(int p, const string& str,int pos, int n);//insereaza un


subsir
void insert(iterator it,iterator pr,iterator u);

//inlocuieste in obiectul string in pozitia p, n caractere printr-un


operand

//secventa

string& replace(int p, int n, const char *s);

string& replace(int p, int n,const E *s, int n);

string& replace(int p, int n,const string& str);

string& replace(int p, int n,const string& str, int pos, int n0);

string& replace(int p, int n0, int n, char c);

string& replace(iterator pr, iterator ul, const char *s);

string& replace(iterator pr, iterator ul, const char *s, int n);

string& replace(iterator pr, iterator ul, const string& str);

string& replace(iterator pr, iterator ul, int n, char c);

string& replace(iterator pr, iterator ul, const_iterator p,


const_iterator u);

char& operator[](int);

void operator=(string&);

void operator+=(string&);

friend int operator==(string&, string&);

friend int operator!=(string&, string&);

friend int operator<(string&, string&);

friend int operator<=(string&, string&);

friend int operator>(string&, string&);

friend int operator>=(string&, string&);

protected:

char* buf;

int lb;

}
Algoritmii generici sunt folosiţi şi de celelalte containere. Pentru a fi generali (independenţi de tipul
containerului) ei folosesc ca parametrii iteratorii, iar tipurile acestora sunt parametrizate, apărând în şabloane.

//algoritmi generici

//inverseaza elementele in [p,u)

template <class BidIt>

void reverse(BidIt p, BidIt u);

//intoarce nr.valori din [p,u) egale cu val(aceeasi valoare in cont)

template <class InIt,class T>

size_t count(InIt p, InIt u, const T& val, int& cont);

//intoarce numarul de valori din [p,u) care satisfac predicatul p


(acelasi in cont)

template <class InIt,class Pred,class Dist>

size_t count_if(InIt p, InIt u, Pred p, Dist& cont);

//pune la d transformatele cu uop ale elementelor din [p,u),

//intoarce capatul drept al destinatiei

template <class InIt,class OutIt, class Unop>

OutIt transform(InIt p,InIt u,OutIt d,Unop uop);

//pune in d valoarea obtinuta aplicand Binop la2 termeni din intervalele

//[p1,u1) si [p2,u2)

template <class InIt1,class InIt2,class OutIt,class Binop>

OutIt transform(InIt1 p1, InIt1 u1, InIt2 p2, OutIt d, Binop bop);

//intoarce pozitia primei aparitii a lui val pt. elementele containerului


din

//intervalul [p,u)

template <class InIt, class T>

InIt find(InIt p, InIt u, const T& val);

//intoarce pozitia primei valori din [p,u) care satisface predicatul pr

//(u daca nu gaseste)

template <class InIt, class Pred>


InIt iterator find_if(InIt p, InIt u, Pred pr);

//inlocuieste in [p,u) toate aparitiile vv cu vn

template <class FwdIt, class T>

void replace(FwdIt p, FwdIt u, T& vv, T& vn);

//inlocuieste in [p,u) toate elementele care satisfac predicatul pr cu vn

template <class FwdIt, class Pred, class T>

void replace_if(FwdIt p, FwdIt u,Pred pr,T& vn);

//sorteaza elementele din intervalul [p,u) cu operatorul <

template <class RanIt>

void sort(RanIt p, RanIt u);

//sorteaza elementele din intervalul [p,u) cu operatorul pr

template <class RanIt, class Pred>

void sort(RanIt p, RanIt u, Pred pr);

//copiaza elementele din [p,u) incepand de la d

template <class InIt, class OutIt>

OutIt copy(InIt p,InIt u,OutIt d);

//determina prima pozitie din [p1,u1) pt care elementul este egal cu un


element din

//[p2,u2), adica primul i pt care *(p1+i)==*(p2+j)

template <class FwdIt1, class FwdIt2>

FwdIt1 find_first_of(FwdIt1 p1,FwdIt1 u1, FwdIt2 p2,FwdIt2 u2);

//determina prima pozitie i din [p1,u1) pt care elementul *(p1+i)


satisface

//predicatul pr cu un element din [p2,u2),adica pr(*(p1+i),*(p2+j))

template <class FwdIt1, class FwdIt2, class Pred>

FwdIt1 find_first_of(FwdIt1 p1,FwdIt1 u1, FwdIt2 p2,FwdIt2 u2,Pred pr);

//plaseaza aleatoriu elementele din [p,u)

template <class RanIt>

void Random_Shuffle(RanIt p,RanIt u);


// clasa string simplificata

# include <string.h>

# include <assert.h>

class string{

public:

string();............ //c-tor implicit

string(char* cp);.......... //c-tor de initializare

string (string & str);........ //c-tor de copiere

~string(){delete [ ] buf;}...... //d-tor

void operator = (string & str);.... //operator de atribuire

resize (unsigned int lgnoua, char fil); //alocare memorie

int length ();.......... //lungime sir

bool empty (){return buf[0] == '\0';};..//test string vid

char & operator [ ] (unsigned int index); //operator de indexare

string substr (int start, int lung);.. //creere subsir

char * begin (){return buf;};.... //iterator de pornire

char * end (){return buf + length();};..//iterator de dupa sfarsit

void remove (int start, int lung);.. //stergere portiune din string

void insert (int pos, string & sirnou); //inserare sir nou incepand
cu pos

void replace (int start, int lung, string & sirnou); //inlocuire cu
sirnou

void operator += (string & right);

friend string operator + (string & st, string & dr);

friend int operator < (string & st, string & dr);

int find (string & dest, int start);..//cautare subsir incepand cu


start

private:

int lgbuf;
char* buf;

};

// initializare string vid

string::string (){

buf = 0;

resize (0, ' ');...... // aloca buffer de lungime zero

// initializaree string cu sir tip C

string::string (char * cp){

buf = 0;

resize (strlen(cp), ' ');.. // aloca buffer

strcpy (buf, cp);...... // copiere sir

// initializare string cu argument string

string::string (string & str){

buf = 0;

resize (str.length(), ' ');.. // aloca buffer

strcpy (buf, str.buf);.... // copiere string argument

// supraincarcare atribuire

void string::operator = (string & str){

resize (str.length(), ' ');

strcpy (buf, str.buf);

void string::resize (int lgnoua, char fil)


{

// daca nu exista buffer, lungimea este zero

if (buf == 0)
.. lgbuf = 0;

.. // caz 1, spatiu mai mic

if (lgnoua < lgbuf) {

.. // adauga caracter nul

.. buf[lgnoua] = '\0';

else { // caz 2, spatiu mai mare

.. // aloca buffer nou, cu spatiu pentru caracter nul

.. int i;

.. char * bufnou = new char[lgnoua + 1];

.. assert (bufnou != 0);

.. // copiere caractere existente

.. for (i = 0; i < lgbuf && buf[i] != '\0'; i++)

.. bufnou[i] = buf[i];

.. // adauga caractere de umplere

.. for ( ; i < lgnoua; i++)

.. bufnou[i] = fil;

.. // adauga caracter nul

.. bufnou[i] = '\0';

.. // eliberare zona veche

.. if (buf != 0)

.. delete [ ] buf;

.. buf = bufnou;

.. lgbuf = lgnoua;

// lungime string

int string::length (){


for (int i = 0; i < lgbuf; i++)

.. if (buf[i] == '\0')

.. return i;

return lgbuf;

// supraincarcare operator de indexare

char & string::operator [ ] (int index){

assert (index <= lgbuf);

return buf[index];

// formare subsir

string string::substr (int start, int lung){

assert (start + lung <= length());

string sub; // subsir rezultat

sub.resize (lung, ' '); // dimensionare

for (int i = 0; i < lung; i++)

.. sub[i] = buf[start + i]; // copiere din sir

return sub;

// sterge lung caractere incepand cu start

void string::remove (int start, int lung){

// calcul unde se termina zona stearsa

int stop = start + lung;

// muta caractere

while ((stop < lgbuf) && (buf[stop] != '\0'))

.. buf[start++] = buf[stop++];

buf[start] = '\0';

}
// inserare sirnou, incepand din pos

void string::insert (int pos, string & sirnou){

int lung = length();....// lungime curenta

int lgss = sirnou.length(); // lungime suplimentara

int lgnoua = lung + lgss;..// lungime noua

// daca estenecesar, realoca buffer

resize(lgnoua, '\0');

// muta caractere existente

for (int i = lung; i > pos; i--)

.. buf[i + lgss] = buf[i];

// inserare caractere noi

for (int i = 0; i < lgss; i++)

.. buf[pos + i] = sirnou[i];

// inlocuieste intre start si start + lung sirnou

void string::replace (int start, int lung, string & sirnou){

remove (start, lung);

insert (start, sirnou);

// adauga argumentul string la sfarsitul stringului curent

void string::operator += (string & right){

insert (length(), right);

// concatenare doua stringuri

string operator + (string & st, string & dr){

string clone(left); // copiere argument stang

clone += right;.. // adauga argument drept

return clone;.. // intoarce rezultat


}

# if 0 // daca este deja defined, apare o eroare de compilare

// test mai mic lexicografic

int operator < (string & st, string & dr){

return strcmp(st.buf, dr.buf) < 0;

# endif

// cauta sirul destinatie

int string::find (string & dest, int start){

int lgdest = dest.length();

// stop este ultima pozitie posibla de start

int stop = length() - lgdest;

for (int i = start; i <= stop; i++) {

.. string text = substr(i, lgdest);

.. if (text == dest)

.. return i;

// nu s/a gasit subsir, intoarce indice in afara domeniului

return lgbuf;

TDA Vector.

Pentru folosirea containerului vector se dă antetul:

#include <vector>

TDA vector generalizează conceptul de tablou cu o dimensiune. Abstractizarea permite mărirea sau
micşorarea colecţiei în cursul execuţiei.

Accesul la orice element se face în timp constant. Operatorul de indexare nu asigură verificarea indicelui
(dacă se încadrează în domeniu).

Inserarea şi ştergerea la sfârşit se face în O(1), iar în celelalte poziţii în O(n).

Fiind un container parametrizat, la instanţiere se declară:


vector<int> x(10,1); //vector de 10 intregi initializati cu 1

vector<double> y(20);//vector de 20 reali neinitializati

1. Operaţii asupra TDA vector.

1.constructori

vector<T> v;.. //c-tor implicit

vector<T>v(int); //c-tor cu dimens. initializata

vector<T>v(int, T); //c-tor cu dimensiune si

........//valoare initiala

vector<T>v(vec); //c-tor de copiere

2.acces la elemente

v[i].. //elementul din pozitia i

v.front() //primul element

v.back() //ultimul element

3.inserări

v.push_back(T);.. //adaugare element la sfarsit

v.insert(iterator, T);//inserare element dupa iterator

v.swap(vector<T>); //interschimb cu alt vector

4.ştergeri

v.pop_back();...... //sterge ultimul element

v.erase(iterator);.... //sterge elementul indicat de iterator

v.erase(iterator, iterator); //sterge elementele din interval

Folosirea funcţiilor insert() şi erase() invalidează toţi iteratorii.

5. dimensionări

v.capacity(); //memoria rezervata pt.vector

v.size();.. //memoria ocupata de vector

v.resize(int, T);//schimbarea dimensiunii

v.reserve();..//realocare memorie pt.vector


v.empty();.. //test vector vid

6. iteratori

vector<T>::iterator it; //declara un iterator

v.begin(); //iterator de pornire(la primul elem)

v.end(); //iterator de oprire(după ultimul)

2. Algoritmi generici.

//pune in intervalul[p,u) valoarea val

template <class FwdIt, class T>

void fill(FwdIt p,FwdIt u, const T& val)

//copiaza elementele din intervalul [p,u) la destinatia d,

//intoarce pozitia de dupa ultima

template<class InIt, class OutIt>

OutIt copy(InIt p, InIt u, OutIt d);

//determina prima pozitie i din intervalul[p,u)astfel ca *i<*j sa fie fals

template<class FwdIt>

FwdIt max_element(FwdIt p, FwdIt u);

//determina prima pozitie i din intervalul[p,u)astfel ca pr(*i,*j) sa fie fals

template<class FwdIt, class Pred>

FwdIt max_element(FwdIt p, FwdIt u, Pred pr);

//pentru fiecare pozitie p din [p,u) se intoarce f(*p)

template<class InIt, class Fun>

Fun for_each(InIt p, InIt u, Fun f);

//cele doua secvente [p1,u1) si [p2,u2) ordonate cu relatia de ordine <


sunt

//interclasate si depuse la d a.i. secventa sa ramana ordonata

//intoarce pozitia de dupa ultimul element dest

template<class InIt1, class InIt2, class OutIt>

OutIt merge(InIt1 p1, InIt1 u1, InIt2 fp2, InIt2 u2, OutIt d);
//cele doua secvente [p1,u1) si [p2,u2) ordonate cu relatia de ordine Pr
sunt

//interclasate si depuse la d a.i. secventa sa ramana ordonata

//cu aceeasi relatie de ordine. Intoarce pozitia de dupa ultimul element


destinatie

template<class InIt1, class InIt2, class OutIt, class Pred>

OutIt merge(InIt1 p1, InIt1 u1, InIt2 p2, InIt2 u2, OutIt d, Pred pr);

//secventele sortate [p,m) si [m,u) sunt amestecate ai secventa [p,u) sa


fie sortata

template<class BidIt>

void inplace_merge(BidIt p, BidIt m, BidIt u);

//secventele [p,m) si [m,u) ordonate cu relatia de ordine pr sunt


amestecate a.i.

// secventa [p,u) sa fie ordonata cu acelasi criteriu

template<class BidIt, class Pred>

void inplace_merge(BidIt p, BidIt m, BidIt u, Pred pr);

3.Implementarea clasei vector.

template <class T> class vector{

protected:

int ne; //numar de elemente

int dim; //spatiu alocat pentru vector

T* data; //zona de date

public:

typedef T* iterator;

vector(int n=0){ data=0; resize(n);};

vector(int n, const T& vi);

vector(const vector& v);

~vector(){delete[]data;};

T& operator[](int k){return data[k];};

int size()const{return ne;};


int capacity()const{return dim;};

int empty()const{return ne==0;};

void resize(int) { reserve(d); dim = d; };

void reserve(int);

T front(){return *data;};

T back() {return data[ne-1];};

void insert(iterator,T);

void swap(vector&);

iterator begin(){return data;};

iterator end() {return data+ne;};

void push_back(const T&);

void pop_back(){ne--;};

template <class T> vector<T>::vector(int n){

data=0;

resize(n);

template <class T> vector<T>::vector(int n, const T& vi){

data=0;

resize(n);

fill(begin(),end(),vi);

template <class T> vector<T>::vector(const vector& v){

data=0;

resize(v.size());

copy(v.begin(),v.end(),begin());

template <class T> void vector<T>::reserve(int c){


if(data==0){

ne=0;

dim=0;

};

if(c <= dim) return;

T* datan = new T[c];

assert(datan);

copy(data, data+ne, datan);

dim = c;

delete [] data;

data = datan;

template <class T> void vector<T>::push_back(T val){

if(ne >= dim)

reserve(dim+INC);

data[ne++] = val;

template <class T> void vector<T>::erase(iterator crt){

iterator dupa = crt;

while(++dupa!=end()){

*crt++ = *dupa;

};

template <class T> void vector<T>::erase(iterator start, iterator stop) {

while(start!=stop)

erase(start);

};

4. Implementarea algoritmilor generici.

//algoritmi generici
template <class ItrT, class T>

void fill(FwdIt p, FwdIt u,const T& val){

while(p!=u)

*p++ = val;

};

template <class InIt, class OutIt>

void copy(InIt p, InIt u, OutIt dest){

while(p!=u)

*dest++ = *p++;

};

//algoritmi generici pt.vectori sortati

template <class BidIt>

void inplace_merge(BidIt p, BidIt m, BidIt u){

int d = u - p;

vector<T> temp(d);

merge(p,m,m,u,temp.begin());

copy(temp.begin(),temp.end(),p);

};

template <class InIt1, class InIt2, class OutIt>

void merge(InIt1 p1, InIt1 u1, InIt2 p2, InIt2 u2, OutIt dest){

while(p1!=u1 && p2!=u2)

if(*p1 < *p2)

.. *dest++ = *p1++;

else

.. *dest++ = *p2++;

while(p1!=u1)

*dest++ = *p1++;

while(p2!=u2)
*dest++ = *p2++;

};

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