Documente Academic
Documente Profesional
Documente Cultură
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:
•..găsirea metodei de rezolvare acceptabile, precizând operatorii de prelucrare ai obiectelor din UP.
•..codificarea algoritmului
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.
#include <stdio.h>
main(){
Î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.
#include <stdio.h>
int main(){
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){
1.2. CÂTEVA ELEMENTE NECESARE SCRIERII UNOR PROGRAME C/C++ FOARTE SIMPLE.
#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:
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:
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.
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.
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.
while (expresie)
instructiune;
while (a!=b)
else b -=a;
Pentru afişarea unei valori la terminal, sub controlul unui format se foloseşte funcţia:
printf(lista_descriptori, lista_expresii);
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);
Cea mai simplă structură de program este un modul compus dintr-o singură funcţie main():
<declaratii locale>
<instructiuni>
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.
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.
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..
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.
Caracterul ; este folosit ca terminator pentru instrucţiuni şi declaraţii, cu o singură excepţie – după o
2.2.7. CONSTANTE.
#defineconstantă-identificator.. (expresie-constantă)
Analiza problemei cuprinde în afara formulării problemei în limbaj natural, o precizare riguroasă
a intrărilor (datelor problemei) şi a ieşirilor (rezultatelor).
x1,2 =
−b ± b2 − 4ac
2a
x= - c/b, dacă b ≠ 0.
2. a≠0 corespunde ecuaţiei de gradul 2. In acest caz avem alte două situaţii:
• structuri de control:
• decizia:
• ciclul:
instrucţiune;
• secvenţa:
{ instrucţiune1;
... instrucţiunen;
reali a,b,c,delta,x1,x2,xr,xi;
citeşte a,b,c;
altfel
altfel
altfel
{ delta=b*b-4*a*c;
altfel {
xr=-b/(2*a);
xi=sqrt(-delta)/(2*a);
scrie xr,xi;
}
#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)
else
if (c!=0)
printf(“nici o solutie\n”);
else
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);
}
}
• 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.
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:
• Fişiere biblioteci de funcţii –sunt fişiere de funcţii gata compilatecare se adaugă (se leagă) la program.
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:
-..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).
#include <nume.h>
Aceasta caută fişierul antet într-un director special de fişiere incluse (include).
#include “nume.h”
./ prog
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).
-..confuzia între = şi ==
-..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).
Î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):
Pentru fiecare ei există în format un descriptor care începe prin %. Afişarea fiecărei expresii se face
corespunzător descriptorului. Sintaxa descriptorului este:
x sau X - întreg cu semn în baza 16, cu cifrele A-F mici sau mari
c - caracter
s - şir de caractere
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
.. dreapta si completata cu 0x */
3. Citirea cu format.
d - întreg în baza 10
o - întreg în baza 8
x - întreg în baza 16
f - real
c - caracter
s - şir de caractere.
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.
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.
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).
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;
char c=’A’;
enum {
internal =0x0008,
};
Î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:
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:
Numărul de zecimale cu care se afişează un număr real (precizia) poate fi setat cu:
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);
double x=123.456;
cout.setf(ios::scientific,, ios::floatfield);
cout.precision(5);
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:
Exemple:
int i = 12345;
setiosflags(ios::showpos) << i;
double x = 2.718281;
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;
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.
Un flux are asociată o stare de eroare – membrul dată state al clasei ios.
enum io_state {
};
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ă.
int good();
int eof();
int bad();
int rdstate();
char c;
int n;
char s[100];
char c;
while( (c=cin.get())!=EOF)
cout.put( c );
echivalent cu:
while(cin.get(c))
cout.put(c);
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).
istream& putback(char);
3. TIPURI ŞI VARIABILE.
3.1... TIPURI.
- mulţimea operatorilor : +, -, *, /, %
Tipurile boolean, caracter, întreg, real şi tipurile enumerate sunt tipuri aritmetice, deoarece valorile
lor pot fi interpretate ca numere.
Tipurile derivate sunt construite pornind de la tipurile fundamentale. Tipurile derivate sunt:
•.. tablourile
•.. funcţiile
•.. pointerii
•.. referinţele
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
Calculatoarele pot lucra în mod direct cu caractere, întregi şi reali. Acestea sunt tipuri
fundamentale (predefinite).
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.
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:
\a alarmă (BEL)
\\ caracterul \
\? caracterul ?
\’ caracterul ‘
\” caracterul “
Întregii pot avea 3 dimensiuni: int, short int(sau short) şi long int(sau long).
8: 0125, 0177
Afişarea unei variabile de tip intîn baza 10 la terminal se face cu descriptorul de format %d
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.
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.
(-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:
unsigned short = unsigned short int unsigned long = unsigned long int
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,
Partea întreagă sau cea fracţionară din constanta reală poate lipsi:
întreg.fractie sau..întreg. sau...fractie
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.
Partea întreagă si partea fracţionară pot lipsi, dar nu simultan. Punctul zecimal şi exponentul sunt
opţionale, dar nu simultan.
Un tip de date poate avea şi un alt nume, prin folosirea declaraţiei typedef. De exemplu:
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.
#define FALSE 0
#define TRUE 1
Este posibil sa forţăm pentru numele simbolice alte valori întregi decât 0,1,2,…
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.
O variabilă constă din două componente: obiectul şi numele obiectului. Numele pot fi
identificatori sau expresii.
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:
float x=1.5, y;
enum CULORI s1, s2=ROSU; enum BOOLEAN p=TRUE; culori c1, c2;
zile z, d;
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:
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,
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.
Valorile pot fi convertite de la un tip la altul. Conversia poate fi implicită sau realizată în mod explicit
de către programator.
Conversiile implicite au loc atunci când este necesar ca operatorii şi argumentele funcţiilor să
double float
int, short int, long int
int, short int, long int
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:
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
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.
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
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:
+, -.. 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
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.
numele de operator de atribuire, opputând fi un operator aritmetic (+, -,*,/,%) sau binar (>>,
v1=v2=...=vn=expresie
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
Operatori relaţionali
|| -SAU logic
Exemple:
nu necesită paranteze suplimentare deoarece operatorii logici sunt mai puţin prioritari decât cei
relaţionali.
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
Î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
Deplasarea dreapta a unui întreg cu semn este aritmetică, iar a unui întreg fără semn este
logică.
De exemplu:
Operatori pe biţi
if (a > b)
max = a;
else
max = b;
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.
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) Operatorul sizeof.
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:
* pointer - pentru adresare indirectă - adică memorează adresa unei entităţi printr-o valoare a unui
pointer
D) OPERATORI DE ACCES :
- indirect prin intermediul unui pointer la câmpurile unei structuri sau unei uniuni
•... 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
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
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
5. INSTRUCŢIUNI.
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;
scanf("%f", &rad);
g=gfr=rad*180/PI;
m=mfr=(gfr-g)*60;
s=(mfr-m)*60;
rad, g, m, s);
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.
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ă.
Forma generală:
if (expresie)
instructiune1;
else
instructiune2;
Se evaluează expresia; dacă este diferită de 0 se execută instrucţiune1 altfel
instrucţiune2
if (expresie)
instructiune;
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++;
lmici++;
lmari++;
cifre++;
else
altele++;
#include <stdio.h>
#include <math.h>
void main(void){
float a, b, c, d;
............(-b+sqrt(d))/2/a):
........ printf("x1=%f+i*%f\tx2=%f-i*%f\n",-b/2/a,
..............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>
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;
if (z < ultima(a,l))
z++;
else
{z=1;
if (l < 12)
.. l++;
else
.. {l=1;
.. a++;
.. }
Criteriul de selecţie într-o problemă de clasificare îl poate constitui un selector care ia valori întregi.
Forma generală:
switch (expresie){
. . .
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;
{ switch (l) {
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;
{ int c;
c = getchar();
while (c != EOF)
{ putchar(c);
c = getchar();
/* varianta simplificata */
{ int c;
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){
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);
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ă.
{ char opt;
printf(“Continuam ? D / N”);
do
scanf(“%c”, &opt);
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){
scanf("%lu", &n);
c=n;
do{ r=10*r+n%10;
.. n/=10;
}while (n);
instructiune;
exp_init;
while (exp_test){
instructiune;
exp_modif;
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-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.
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);
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){
suma += n;
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;
. . .
goto eticheta;
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;
long f;
.. 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.
nume_funcţie (listă_parametri_efectivi)
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;
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;
z=cmmdc(x,y);
x/=z;
y/=z;
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.
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.
{ 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.
{ 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).
{ 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:
Din prototip interesează numai tipurile parametrilor, nu şi numele acestora, motiv pentru care aceste
nume pot fi omise.
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 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>
void main(void)
cmmdc();
a/=c;
b/=c;
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)
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>
void main()
printf("10!=%ld\n", fact(10));
getch();
short i;
f*=i;
return(f);
tipuri predefinite
tipuri pointer
tipul structură (înregistrare)
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
. . .
#include <stdio.h>
{ 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”);
{ return (fact(n)/fact(p)/fact(n-p));
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.
#include <stdio.h>
void main(){
...
#include <stdio.h>
void F(){
...
Pentru execuţia unui program rezident în mai multe fişiere se crează un proiect.
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
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!).
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ă.
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.
Orice definiţie este şi o declaraţie, dar reciproca nu este adevărată. Astfel prototipul unei funcţii este o
declaraţie.
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.
variabile globale
variabile locale.
Variabilele globale:
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 */
void main(){
/* FISIER2.C */
#include <stdio.h>
...
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.
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 :
#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;
increm();
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.
int rand(void); - care întoarce (dacă este folosită singură), o aceeaşi secvenţă de numere
pseudoaleatoare
în domeniul 0 : RAND_MAX
x = (a*x + b) % c;
return x;
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,.
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.
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:
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
Exemple:
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.
#include <stdio.h>
void main(void)
{ int x[10], n, j;
scanf(“%d”, &n);
scanf(“%d”, &x[j]);
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};
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.
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
#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 narab=0;
int crt,urm;
while((nrom[n++]=getchar())!=’ ‘)
...... ;
n--;
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;
.... ;
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.
T *p;
Exemple:
char c, *pc;
operatorul de adresare & - aplicat unei variabile furnizează adresa acelei variabile
pj=&j; /* iniţializare pointer */
pc=&c;
int j, *pj=&j;
char c, *pc=&c;
int *px;
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.
*(&x) = x;
&(*p) = p;
Exemplu;
int *px, x;
x=100;
Dereferenţierea unui pointer neiniţializat sau având valoarea NULL conduce la o eroare la execuţie.
Pentru a utiliza un pointer cu mai multe tipuri de date, la declararea lui nu îl legăm de un anumit tip.
int i;
void *p;
. . .
p=&i;
Exemplul 17: Definiti o functie care afiseaza o valoare ce poate aparţine unuia din tipurile: char,
int, double.
#include <stdio.h>
switch(t) {
case caracter:
case intreg:
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);
In definiţiile:
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ă).
In acest caz, modificarea pointerului (py++) nu este permisă, dar conţinutul referit de pointer poate fi
modificat ( (*py)++ ).
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:
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).
Î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:
px++;
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)
x[i] i[x]
Într-adevăr: x[i]=*(x+i)=*(i+x)=i[x]
Exemplul 18: Scrieţi o funcţie care calculează produsul scalar a doi vectori x şi y, având câte n componente
fiecare.
{ double P=0;
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 ).
char salut[]={‘B’,’o’,’n’,’j’,’o’,’u’,’r’,’!’,’\0’};
char salut[]=”Bonjour!”
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!”;
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>.
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>
.... ;
i++;
return semn*nr;
Exemplul 20: Scrieţi o funcţie care converteşte un întreg într-un şir de caractere în baza 10.
se inversează şirul;
void inversare(char[]);
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);
{ int i,j;
char c;
for(i=0,j=strlen(s)-1;i<j;i++,j--)
Exemplul 21: Scrieţi o funcţie care converteşte un întreg fără semn într-un şir de caractere în baza 16.
{ j=0;
do {
.. s[j++]=hexa[n%16];
while ((n/=16)>0);
s[j]=’\0’;
inversare(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
int n)
int memcmp(const void* d, compară zonele adresate de s şi d
Exemplul 22:Scrieţi o funcţie având ca parametru un şir de caractere, care întoarce lungimea sirului
/*varianta cu tablouri*/
{ int j;
j++;
return j;
/*varianta cu pointeri*/
while(*p)
p++;
return p-s;
/*varianta cu tablouri*/
{ int j=0;
while((d[j]=s[j])!=’\0’)
j++;
/*varianta cu pointeri*/
{ while((*d=*s)!=’\0’){
d++;
s++;
while((*d++=*s++)!=’\0’)
.... ;
Testul faţă de ‘\0’ din while este redundant, deci putem scrie:
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 j;
if(d[j]==’\0’)
.. return 0;
return d[j]-s[j];
/*varianta cu pointeri*/
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 i,j,k;
........ ;
.. 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:
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:
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:
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.
/* varianta cu tablouri */
{ char s[255];
int j;
double y;
if(gets(s)==NULL)
.. return j;
return j;
/* varianta cu pointeri */
{ char s[255];
int j=0;
double y;
double *p=px+n;
while(px<p) {
if(gets(s)==NULL)
.. return j;
*px++=y;
j++;
return j;
tip *nume[dimensiune];
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.
Vasile
Constantin
Ion
Vasile
Constantin
Ion
care:
Citirea numelor este terminată prin EOF. Funcţia de citire întoarce numărul de linii citite:
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;
.. }
int 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)
.. “Februarie”,”Martie”,”Aprilie”,“Mai”,”Iunie”,
.. ”Iulie”,”August”,“Septembrie”,”Octombrie”,
.. “Noiembrie”,”Decembrie”};
return(n<1||n>12)?nume[0]:nume[n];
9. Functii (2)
Î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:
{ int 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);
}
Se afişează:
10..15
10..15
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:
{ 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);
..î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:
{ 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);
{ char *p=d;
while (*p++=*s++)
.. ;
return d;
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ă.
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:
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
tip (*pf)(listă_parametri_formali);
(*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:
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.
va avea prototipul:
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 i1
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(*)());
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;
printf(“%6.2lf\n”, I2n);
{ 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;
O declaratie complexă este o combinaţie de pointeri, tablouri si functii. In acest scop se folosesc
atributele:
() – functie
[] – tablou
* - pointer
* [] - tablou de pointeri
[ ] [] – tablou bidimensional
Există şi combinaţii incorecte, provenite din faptul că în C nu este permisă declararea:
Acestea sunt:
[ ] ( ) – tablou de funcţii
Pentru a interpreta o declaraţie complexă vom inlocui atributele prin următoarele şabloane text:
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
double (*(*pf)())[3][4];
pointer la
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:
Exemple:
typedef char *SIR; /*tipul SIR=sir de caractere*/
VECTOR b, x;
MATRICE a;
PFADRD pf1;
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ă.
Biblioteca standard oferă 4 funcţii, având prototipurile în <alloc.h> şi <stdlib.h>. Acestea sunt:
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.
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.
Funcţia eliberează o zonă de memorie indicată de p, alocată în prealabil prin malloc() sau
calloc().
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.
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 *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()
int n;
unsigned dim=INC;
p=q=(char*)malloc(dim);
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:
delete p;
Să considerăm definiţiile:
ppx px x.. 10
{ int *ptemp;
ptemp=*pa;
*pa=*pb;
*pb=ptemp;
cu apelul:
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;
{ while(n--)
printf(“%s\n”,*tp++);
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
&a[i1][i2]…[in]=a+sizeof(T)*[i1*d2*…*dn+i2*d3*…*dn+…+in]
&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:
ci:
î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:
{ int i,j;
for(i=0;i<l;i++)
{ for (j=0;j<c;j++)
.. printf(“%d”,((int*)a)[i*n+j];
printf(“\n”);
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.
n1
Cij A ikBkj
k0
{ int i, j, k, ij;
.. ij=i*p+j;
.. C[ij]=C[ij]+A[i*n+k]*B[k*p+j];
Exemplul 31: Definiţi o funcţie care alocă dinamic memorie pentru o matrice având l linii şi c coloane.
..............................................
{ double **plin;
double *pelem;
int i;
pelem=(double*)calloc(lin*col, sizeof(double));
if(pelem==(double*)NULL){
.... exit(1);}
plin=(double**)calloc(lin, sizeof(double*);
if(plin==(double**)NULL){
.... exit(1);
.. plin[i]=pelem;
.. pelem+=col;
return plin;
Clase.
2. Declararea claselor.
3. Operatorul de rezoluţie.
6. Constructori şi destructori.
7. Pointerul this.
11. Probleme.
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.
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
};
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
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.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.
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:
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.)
class Data{
int a, l, z; // reprezentare
public:
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:
public:
x0 = x;
y0 = y;
};
void afisare(){
long f = ios::fixed;
cout << “(“ << x0 << “,” << y0 << “)” << endl;
};
};
void main(){
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:
public:
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>
x0 = x;
y0 = y;
};
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_
#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”
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(){
. . .
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;
};
class punct{
private:
public:
void afisare();
};
// fisierul punct.cpp
#include <iostream.h>
#include <iomanip.h>
y0 = y;
};
void punct::afisare(){
long f = ios::fixed;
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:
La creerea unei referinţe, aceasta trebuie iniţializată cu adresa unui obiect (nu cu o valoaren
constantă).
int x;
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.
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ă.
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:
. . .
};
#include <stdio.h>
class Complex{
private:
public:
void scrie();
};
void Complex::scrie(){
};
void main(){
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.
class Complex{
private:
public:
Complex(){re=0.; im=0.};
void scrie();
};
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.
class Complex{
private:
public:
void scrie();
};
void main(){
. . .
}
class Data{
int a, l, z;
public:
Data(int, int);.. // l, z
Data(int); // z
. . .
};
Î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::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 z2;
Z2 = Complex(1., -2.);
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)
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:
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 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(persoana& p);
~persoana();
};
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.
- 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).
Î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).
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;
public:
. . .
};
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.
};
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:
};
Nu este posibilă declararea unei constante simbolice în interiorul unei clase. Astfel:
class Vector{
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];
. . .
class Vector{
double x[N];
. . .
Pentru clasa complex, am definit funcţiile care asigură accesul partea reală, respectiv imaginară a unui
număr complex,:
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ă:
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);
class Data{
Data& ad_a(int);
Data& ad_l(int);
Data& ad_z(int);
};
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.
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).
- rotirea butonului într-o directie dată, până când se formează un număr dat
- închiderea seifului
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.
- constructor pentru setarea unui punct într-o poziţie specificată (implicit în 0,0,0)
- rotaţia unui punct cu un unghi specificat de-a lungul uneia dintre axele x,y sau z.
x'=x
y'=y*cos(t)-z*sin(t)
z'=y*sin(t)+z*cos(t)
x'=x*cos(t)+z*sin(t)
y'=y;
z'=-x*sin(t)+z*cos(t)
x'=x*cos(t)-y*sin(t)
y'=x*sin(t)+y*cos(t)
z'=z
- 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 >>
se generează câte un număr aleator, care devine sămânţă. În acest mod se generează "modul" numere
diferite.
- schimbarea sămânţei
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
- 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.
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, ::, =, &, ->*, .*, ., ->.
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.
Operatorii supraîncărcaţi într-o clasă sunt moşteniţi în clasele derivate (excepţie face operatorul de
atribuire =).
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&);
. . .
};
class Vec{
int n;
double *v;
public:
Vec();
Vec(Vec&);
. . .
};
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.
O funcţie operator supraîncărcată poate fi introdusă ca funcţie membră (în general nestatică) sau funcţie
prieten (funcţie nemembră).
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.
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.
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)
class Cplx{
public:
//operatori binari
//operatori de comparatie
//operatori unari
};
return Cplx(s.re+d.re,s.im+d.im);
};
return Cplx(s.re-d.re,s.im-d.im);
};
return Cplx(s.re*d.re-s.im*d.im,s.re*d.im+s.im*d.re);
};
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);
};
};
};
};
};
};
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.
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)
Class Cplx{
public:
Cplx& operator-();
Cplx& operator!();
Cplx& operator++();
Cplx operator++(int);
};
};
};
double t=d.re*d.re+d.im*d.im;
};
if(this!=&d){
re+=d.re;
im+=d.im;
};
return *this;
};
if(this!=&d){
re-=d.re;
im-=d.im;
};
return *this;
};
if(this!=&d){
double t=re;
re=re*d.re-im*d.im;
im=t*d.im+im*d.re;
};
return *this;
};
if(this!=&d){
re=(re*d.re+im*d.im)/t2;
im=(im*d.re-t1*d.im)/t2;
};
return *this;
};
};
};
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;
};
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.
class String{
char* s;
int n;
public:
~String(); //dtor
String& operator=(const String& d);
};
//ctor de copiere
if(s).... //curatire
.. delete [] s;
n=d.n;.... //copiere
s=new char[n];
};
};
if(s)
.. delete [] s;
n=strlen(p);
s=new char[n];
strncpy(s, p, n);
return *this;
};
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) .
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);
};
return s[i];
else
return 0;
};
Funcţia operator new are semnătura: void* operator new(size_t); Operatorul redefinit
apelează constructorul clasei după alocarea dinamică pe care o realizează.
Dacă în clasa C se supraîncarcă operatorul new, atunci versiunea furnizată de sistem se obţine prin
C *p = ::new C();
C *p = ::new C[n];
return p;
nume_functie(lista_argumente);
(*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);
obiect.operator()(lista_argumente);
Î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:
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 d1;
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
private:
long x, y;
};
long z=x+y;
x=y;
y=z;
};
return x;
};
Î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ă.
class Inch{
double inch;
double cm;
public:
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;
Î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.
class Cplx{
double re,im;
public:
};
os << “(“ << z.re <<”,” << z.im << “)” << endl;
return os;
};
return is;
};
class String{
int n;
char *s;
public:
. . .
};
os << w.s[i];
return os;
};
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.
- schimbarea coeficienţilor
Constructorul clasei va avea ca argumente partea reală, respectiv imaginară a numărului complex (în mod
implicit aceste valori se iau 0).
- 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
Se va redefini operatorul >> ca funcţie prieten pentru citirea unui număr complex de la intrarea standard
- supraîncărcarea operatorilor +,-,*,/ pentru a permite operaţii cu două argumente numere complexe
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).
- 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:
- supraîncărcarea operatorilor +,-,*,/ pentru a permite operaţii cu două argumente numere raţionale.
4. Proiectaţi şi implementaţi clasa Vector care să permită lucrul cu vectori de elemente reale.
- 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
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 redefini operatorul >> ca funcţie prieten pentru citirea unei matrici de la intrarea standard
6. Proiectaţi şi implementaţi clasa String care să permită lucrul cu şiruri de caractere terminate cu nul.
- 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
- supraîncărcarea operatorului + pentru a permite operaţia de concatenare a două şiruri date ca argumente
- selectarea dintr-un şir a unui alt şir definit prin poziţie de început şi lungime
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:
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.
Operatorii supraîncărcaţi într-o clasă sunt moşteniţi în clasele derivate (excepţie face operatorul de
atribuire =).
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&);
. . .
};
class Vec{
int n;
double *v;
public:
Vec();
Vec(Vec&);
. . .
};
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.
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.
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)
class Cplx{
public:
//operatori binari
//operatori de comparatie
//operatori unari
};
};
return Cplx(s.re-d.re,s.im-d.im);
};
return Cplx(s.re*d.re-s.im*d.im,s.re*d.im+s.im*d.re);
};
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);
};
};
};
};
};
};
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.
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) ).
Class Cplx{
public:
Cplx& operator!();
Cplx& operator++();
Cplx operator++(int);
};
};
};
};
double t=d.re*d.re+d.im*d.im;
};
if(this!=&d){
re+=d.re;
im+=d.im;
};
return *this;
};
if(this!=&d){
re-=d.re;
im-=d.im;
};
return *this;
};
if(this!=&d){
double t=re;
re=re*d.re-im*d.im;
im=t*d.im+im*d.re;
};
return *this;
};
if(this!=&d){
re=(re*d.re+im*d.im)/t2;
im=(im*d.re-t1*d.im)/t2;
};
return *this;
};
};
};
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.
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.
class String{
char* s;
int n;
public:
~String(); //dtor
};
//ctor de copiere
if(s).... //curatire
.. delete [] s;
n=d.n;.... //copiere
s=new char[n];
};
};
if(s)
.. delete [] s;
n=strlen(p);
s=new char[n];
strncpy(s, p, n);
return *this;
};
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) .
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);
};
return s[i];
else
return 0;
};
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 o valoare dată
ca parametru.
C *p = ::new C[n];
nume_functie(lista_argumente);
(*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);
obiect.operator()(lista_argumente);
Î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:
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 d1;
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
private:
long x, y;
};
long z=x+y;
x=y;
y=z;
};
return x;
};
Î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ă.
class Inch{
double inch;
double cm;
public:
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;
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;
Funcţiile operator pentru supraîncărcarea operatorilor de intrare / ieşire se declară funcţii prieten al clasei
care interacţionează cu fluxul.
class Cplx{
double re,im;
public:
};
os << “(“ << z.re <<”,” << z.im << “)” << endl;
return os;
};
return is;
};
class String{
int n;
char *s;
public:
. . .
};
return os;
};
char zona[100];
is.get(zona,100);
w=zona;
return is;
};
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
// corp clasă
};
În cazul moştenirii multiple este posibilă moştenirea din mai multe clase de bază:
// 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).
....
......isoscel.... echilateral
class String {
int lg;
char* sir;
public:
String(char* );
String(const String& );
~String();
};
class Persoana {
int lg_nume;
char* nume;
int varsta;
public:
~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();
};
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.
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:
};
{ };
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.
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.
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.
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:
};
void Angajat::afisare()const {
};
.. : nume(n), salariu(s) { };
int sectie;
public:
};
void Sef::afisare()const {
Angajat::afisare();
cout << “Sef Sectie: “ << sectie << endl;
};
.. : 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:
CBInd.. CBInd Un obiect din clasa Der conţine membrii clasei CBInd
Der d;
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.
..................
.................. 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 {. . .};
void main(){
D d;
B* pb = &d; // corect
B b;
D* pd = &b; // gresit
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 *pb = new B;
D *pd = new D;
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:
};
class D : public B {
public:
};
void main(){
B* pb = new B;
D* pd = new 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();
};
class D{
public:
D();
~D();
};
void main(){
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.
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.
class Conversie{
protected:
public:
};
public:
FahrCels(double i) : Conversie(i){};
};
public:
InchCm(double i) : Conversie(i){};
};
void main(){
Conversie* pb;
double val;
char tip;
switch(tip){
};
if(pb){
pb->conv();
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 :
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.
class Animal {
char numeA[20];
public:
};
};
char numeF[20];
public:
strcpy(numeF, nf);
};
void Identif(){
Animal::Identif();
};
};
class Pisica : public Felina {
char numeP[20];
public:
Strcpy(numeP, np);
};
void Identif(){
Felina::Identif();
};
};
#include <iostream.h>
#include <string.h>
void main(){
Felina F(“tigru”,”mamifer”);
pa = &A;
pa = &F;
pa = &P;
11. Probleme.
.............. Cerc
class Forma{
protected:
double x,y;
public:
};
public:
};
protected:
double raza;
public:
};
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.
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.
....
......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.
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.
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 main(){
. . .
. . .
}
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”.
#define NDEBUG
#include <assert.h>
. . .
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.
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.
catch(tip_exceptie){
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.
class Vector{
float* v;
public:
};
Vector::Vector(int n){
throw Size();
d = n;
v = new float[n];
};
return v[i];
throw Range();
return v[0];
};
void main(){
try{
. . .
};
catch(Vector::Size){
. . .
. . .
};
catch(Vector::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ă.
namespace nume{
};
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:
. . .
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:
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(){
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>
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:
void main(){
#include <iostream>
. . .
int x;
std::cin >> x;
respectiv:
#include <iostream>
. . .
using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
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;
namespace A{
int x;
namespace B{
using A::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.
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>.
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:
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;
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>
void main(void) {
printvar(3,”Ion”,”Vasile”,”Mihai”);
printf(“\n”);
printvar(5,”marti”,”joi”,”luni”,”vineri”,”duminica”);
printf(“\n”);
{ va_list pa;
int narg=0;
char *siruri[10];
va_start(pa,max);
siruri[narg]=va_arg(pa, char*);
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:
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>
void main(void) {
fmt1[]=”%s %s %s\n”;
printvar(fmt1,”Ion”,”Vasile”,”Mihai”);
va_list pa;
va_start(pa,fmt);
vprintf(fmt,pa);
va_end(pa);
afişează, sub controlul formatului, în fişierul fis, un număr variabil de argumente, accesate prin pointerul
pa
Exemplu:
#include <stdio.h>
#include <stdarg.h>
void main(void) {
FILE* f1;
fmt1[]=”%s %s %s\n”;
f1=fopen(NUMEFIS, “w”);
printvar(f1,fmt1,”Ion”,”Vasile”,”Mihai”);
fclose(f1);
va_list pa;
va_start(pa,fmt);
va_end(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>
void main(void) {
fmt1[]=”%s %s %s\n”;
char s[100];
printf(“%s”,s);
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>
va_list pa;
int i;
double d;
va_start(pa,fmt);
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);
double sqrt(double);
long sqrt(long);
Tot polimorfism ad-hoc reprezintă conversia implicită între tipul pointer generic (pointer la void) şi
pointer cu tip.
Pointerii generici nu pot fi dereferenţiaţi, motiv pentru care îă convertim în pointeri la octet.
int rezcp;
OCTET *med;
if(rezcp < 0)
else
.. if(rezcp > 0)
.. 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 cpsir(void* ch, void* 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).
return x – y > 0? x : y;
// identic
// identic
T max(T x, T y){
return x - y > 0? x : y;
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.
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.
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)
T min(T a, T b){
şi
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.
T xm = x[0];
if(x[i]<xm)
.. xm = x[i];
return xm;
};
Instanţierea şablonului,, adică înlocuirea parametrilor şablonului cu parametrii efectivi are forma:
void main(){
n = sizeof(fx) / sizeof(float);
. . .
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.
Utilizarea unei clase generice presupune generarea de către compilator a fiecărei clase individuale,
corespunzător tipurilor care instanţiază clasa generică.
class Array{
public:
~Array(){delete a;}
private:
T *a;
int d;
};
Array<int> x;
Array<Complex> c;
Array<String> s;
Definirea unei clase şablon se face prin:
template <lista_parametri_sablon>
Exemplu
T a[d];
int dim;
public:
Buffer() : dim(d){}
};
şi instanţieri de forma:
cu:
Funcţiile generice pot fi supraîncărcate, cu condiţia ca instanţele să aibă semnături diferite. De exemplu:
int 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.
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.
while(prim!=ultim)
f(*prim++);
return f;
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_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.
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.
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.
equal() întoarce true/false dacă cele două secvenţe sunt egale sau dacă toate perechile satisfac
predicatul
FwIt search_n(FwIt prim, FwIt ultim,Size n, const T& val, BinPred p);
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.
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.
while(prim != ultim)
*rez++ = *prim++;
return rez;
while(prim != ultim)
*--rez = *--ultim;
return rez;
copy_if() copiază toate valorile din intervalul de iteratori care satisfac predicatul, la destinaţie.
if(p(*prim))
.. *rez++ = *prim;
prim++;
return rez;
replace() înlocuieşte toate apariţiile ale unei valori vechi valv din intervalul de iteratori cu o
valoare nouă valn
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ă.
OutIt replace_copy(InIt prim, InIt ultim, OutIt rez, const T& valv, const
T& valn);
OutIt replace_copy_if(InIt prim, InIt ultim, OutIt rez, Pred p, const T&
valn);
remove() şterge toate valorile din domeniul de iteratori egale cu o valoare dată
remove_if() şterge toate valorile din domeniul de iteratori care satisfac un predicat
template <class FwIt, class Pred>
OutIt remove_copy(InIt prim, InIt ultim, OutIt rez, const T& val);
şi variantele _copy:
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.
11. partition() împarte secvenţa în două intervale, astfel că elementele care satisfac un predicat vor
fi plasate în primul interval (înaintea celorlalte).
13. iota() umple un interval de iteratori cu valori crescătoare, pornind cu o valoare dată ca parametru
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.
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)
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).
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.
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)
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.
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.
9. interclasare
merge() combină două secvenţe sortate într-o secvenţă de asemeni sortată. Are complexitate
liniară.
OutIt merge(InIt1 prim1, InIt1 ultim1, InIt2 prim2, InIt2 ultim2, OutIt
rez);
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)
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.
bool includes (InIt1 prim1, InIt1 ultim1, InIt2 prim2, InIt2 ultim2,
Compare cp);
2. set_union() construieşte mulţimea reuniune M1M2 a mulţimilor date prin intervalele de iteratori
Algoritmi cu heapuri.
Un heap este un arbore binar complet parţial ordont, folosit pentru implementarea cozii prioritare.
3. make_heap() aranjează elementele din intervalul de iteratori, astfel încât acestea să reprezinte un
heap
1. min(), max()determină cel mai mic (respectiv cel mai mare) între două elemente
2. min_element(), max_element() furnizează un iterator la cel mai mic (cel mai mare) element
din intervalul de iteratori.
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ă.
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>
rez vinit xi yi .
template <class InIt1, class InIt2, class T, class BinOp1, class BinOp2>
3. partial_sum() calculează în containerul rezultat valorile acumulate din prima poziţie, primele două,
primele trei, etc.
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{};
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.
struct iterator_traits{
};
Î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:
struct iterator_traits<T*>{
typedef T value_type;
typedef T* pointer;
};
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;
};
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:
...................... input_iterator_tag){
while(i1++ != i2)
d++;
return d;
În cazul iteratorilor cu acces direct, distanţa se calculează prin simpla diferenţă a iteratorilor:
......................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ă:
};
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:
class grupa{
gr_stud g;
public:
// alte metode
#include <cstddef>
class carray {
private:
public:
// definiri de tipuri
// dimensiunea e constanta
T* as_array() { return v; }
};
#include <algorithm>
#include <functional>
#include "carray.hpp"
#include "print.hpp"
int main()
{
carray<int,10> a;
.. a[i] = i+1;
afisare(a);
reverse(a.begin(),a.end());
afisare(a);
transform(a.begin(),a.end(), // sursa
afisare(a);
Pentru clasele vector şi string iteratorii sunt chiar pointeri, aşa că delegarea iteratorilor este mai simplă:
class Itor{
public:
// constructori, destructor
T* operator->() const;
private:
//asocierea cu containerul
};
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);
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).
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:
Pentru a defini un iterator pentru un obiect ostream folosim una din formele:
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:
copy(v.begin(),v.end(),OutIt); // echivalent cu
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>
ifstream s(av[1]);
ofstream d(av[2]);
copy(istream_iterator<char>(in),istream_iterator<char>(),
.. ostream_iterator<char>(d));
în care T reprezintă tipul elementelor citite din flux. Declaraţia de mai sus construieşte un iterator care
accesează un obiect istream.
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;
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>
void main(){
vector<string> vs;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(vs));
}
//Nicolai M. Josuttis "The C++ Standard Library - A Tutorial and
Reference"
#include <iostream>
#include <iterator>
int main()
istream_iterator<int> intReader(cin);
istream_iterator<int> intReaderEOF;
.. ++intReader;
Iteratorii de avans combină posibilităţile iteratorilor de intrare şi de ieşire, permiţând citirea din şi
scrierea în container.
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.;
s+=*i;
return s;
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:
double s=0.;
while(start!=stop)
s+= *start++;
return s;
double x[]={1.5,2.,3.5,6.2};
Î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:
struct binary_function{
};
return x==y;
};
Astfel pentru a sorta un tablou cu funcţia de comparaţie mai mic în valoare absolută, vom defini:
#include <iostream>
struct absLess{
};
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>.
#include <iostream>
#include <functional>
#include <string>
#include <algorithm>
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:
return x % 2 != 0;
};
};
. . .
int a;
cin >> a;
if(not1(impar()(a)))
cout << a << ” este par\n”;
. . .
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.
find_if(v.begin(),v.end(),bind1st(less<int>(),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>
void main(){
double x=3.1416;
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:
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)
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.
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).
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:
1.Exemple de utilizare.
//test palindrom
string t;
t = s;
reverse(t.begin(), t.end());
return s==t;
};
string mic(s);
transform(s.begin(),s.end(),mici.begin(),tolower);
return pal1(mici);
};
dest++ =f(*start++);
};
string t=remove_all(s,punct);
return pal2(t);
};
string rez;
int ltx=txt.length();
int lsp=sep.length();
string car=txt.substr(i,1);
int poz=sep.find(car,0);
if(poz<0 || poz>lsp)
.. rez+=car;
};
return rez;
};
int ltx=txt.length();
int start=txt.find_first_not_of(sep,0);
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);
};
};
void main(){
string mic=”mijloc”;
string mare=”mijloc”;
list<string> cuv;
list<string>::iterator crt;
list<string>::iterator stop=cuv.end();
if(*crt<mic)
.. mic=*crt;
if(*crt>mare)
.. mare=*crt;
};
cout<<mic<<endl;
cout<<mare<<endl;
};
2. Operaţii cu şiruri.
s2[0]=’B’;
string nume;
FILE* f=fopen(nume.c_str(),”r”);
3.Lungime şir
4.Atribuire si concatenare
s+=s1; //adauga s1 la s
5.Iteratori
s.begin();....//iterator de pornire
7.Comparaţii
8.Operaţii de căutare
s.find(s1);.... //cauta in s unde incepe s1
9.Intrări / ieşiri
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.
Prin indexare se asigură acces la un caracter din string fără verificarea domeniului; accesul cu verificare
este asigurat de funcţia at().
class string{
public:
//constructori
~string();.. //destructor
//iteratori
iterator begin();
iterator end();
//functii generale
int empty();
int length();
//secventa
string& replace(int p, int n,const string& str, int pos, int n0);
string& replace(iterator pr, iterator ul, const char *s, int n);
char& operator[](int);
void operator=(string&);
void operator+=(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
//[p1,u1) si [p2,u2)
OutIt transform(InIt1 p1, InIt1 u1, InIt2 p2, OutIt d, Binop bop);
//intervalul [p,u)
# include <string.h>
# include <assert.h>
class string{
public:
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
friend int operator < (string & st, string & dr);
private:
int lgbuf;
char* buf;
};
string::string (){
buf = 0;
buf = 0;
buf = 0;
// supraincarcare atribuire
if (buf == 0)
.. lgbuf = 0;
.. buf[lgnoua] = '\0';
.. int i;
.. bufnou[i] = buf[i];
.. bufnou[i] = fil;
.. bufnou[i] = '\0';
.. if (buf != 0)
.. delete [ ] buf;
.. buf = bufnou;
.. lgbuf = lgnoua;
// lungime string
.. if (buf[i] == '\0')
.. return i;
return lgbuf;
return buf[index];
// formare subsir
return sub;
// muta caractere
.. buf[start++] = buf[stop++];
buf[start] = '\0';
}
// inserare sirnou, incepand din pos
resize(lgnoua, '\0');
.. buf[pos + i] = sirnou[i];
# endif
.. if (text == dest)
.. return i;
return lgbuf;
TDA Vector.
#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).
1.constructori
........//valoare initiala
2.acces la elemente
3.inserări
4.ştergeri
5. dimensionări
6. iteratori
2. Algoritmi generici.
template<class FwdIt>
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
OutIt merge(InIt1 p1, InIt1 u1, InIt2 p2, InIt2 u2, OutIt d, Pred pr);
template<class BidIt>
protected:
public:
typedef T* iterator;
~vector(){delete[]data;};
void reserve(int);
T front(){return *data;};
void insert(iterator,T);
void swap(vector&);
void pop_back(){ne--;};
data=0;
resize(n);
data=0;
resize(n);
fill(begin(),end(),vi);
data=0;
resize(v.size());
copy(v.begin(),v.end(),begin());
ne=0;
dim=0;
};
assert(datan);
dim = c;
delete [] data;
data = datan;
reserve(dim+INC);
data[ne++] = val;
while(++dupa!=end()){
*crt++ = *dupa;
};
while(start!=stop)
erase(start);
};
//algoritmi generici
template <class ItrT, class T>
while(p!=u)
*p++ = val;
};
while(p!=u)
*dest++ = *p++;
};
int d = u - p;
vector<T> temp(d);
merge(p,m,m,u,temp.begin());
copy(temp.begin(),temp.end(),p);
};
void merge(InIt1 p1, InIt1 u1, InIt2 p2, InIt2 u2, OutIt dest){
.. *dest++ = *p1++;
else
.. *dest++ = *p2++;
while(p1!=u1)
*dest++ = *p1++;
while(p2!=u2)
*dest++ = *p2++;
};