Sunteți pe pagina 1din 222

1.

Introducere

Versiunea originală a limbajului de programare C a fost creată de


Dennis Ritchie, la “AT&T Bell Laboratories”, în 1972. Acest limbaj
utilizează şi unele din principiile limbajului ”B”, inventat de Ken Thompson,
în 1970, pentru primul sistem de operare UNIX, pentru DEC-PDP7.
Limbajul “C” a fost creat ca un instrument de lucru pentru
programatori; scopul său principal este de a fi un limbaj folositor
programatorilor. Cele mai multe limbaje de programare sunt, bineînţeles,
folositoare, dar ele au diferite scopuri. De exemplu, unul dintre scopurile
principale ale limbajului PASCAL este de a furniza o bază solidă pentru
învăţarea principiilor de programare, fiind, în general, unul dintre primele
limbaje care se studiază. BASIC-ul, pe de altă parte, a fost dezvoltat pentru a
semăna cu limbajul natural, limba Engleză, astfel încât să poată fi învăţat
uşor de cei nefamiliarizaţi cu calculatoarele.
Aceste sunt criterii importante, dar nu sunt întotdeauna compatibile
cu utilizarea pragmatică. Din acest punct de vedere ”C”-ul este un limbaj
pragmatic, orientat către programator. De exemplu să considerăm interfaţa
între programe şi hardware. Limbajul PASCAL accentuează principiile de
programare generale şi încearcă să izoleze programatorul de consideraţiile
hardware. Din acest motiv au fost adăugate multe extensii, nestandard,
acestui limbaj, cum ar fi posibilitatea de a specifica adrese hardware. Aceste
caracteristici sunt adesea, în paralel, cu cele deja găsite în C. În plus, C-ul
oferă multe facilităţi de programare care reduc sarcina de lucru.
În special, după 1980, C-ul a devenit unul dintre cele mai importante
şi răspândite limbaje de programare. Iniţial creşterea în popularitate a
acestui limbaj s-a realizat odată cu creşterea în popularitate a sistemului de
operare UNIX, care a fost dezvoltat, de asemenea, la “Bell Laboratories”.
De fapt mai mult de 90% din acest sistem de operare a fost scris în limbajul
C.
Ulterior, după succesul calculatoarelor IBM PC, bazate pe sistemul
de operare MS-DOS, s-au realizat şi compilatoare C pentru acestea. Aceste
versiuni s-au bazat pe versiunea C, descrisă în cartea “The C Programming
Language”, de Brian Keringham şi Dennis Ritchie (Prentice Hall, 1978).
Din păcate cartea nu furniza o definiţie completă şi fără ambiguităţi a
limbajului C, astfel încât mulţi producători au interpretat unele aspecte ale
limbajului în moduri diferite.
La începutul anilor ’80 a apărut nevoia de a standardiza definirea
limbajului C. Organizaţia care se ocupă cu standardizarea este Institutul
2 Programare C/C++
American de Standarde Naţionale (ANSI - American National Standards
Institute); în 1983 un comitet ANSI (denumit X3J11) a realizat
standardizarea limbajului C, astfel încât orice compilator de C are
încorporate toate caracteristicile limbajului, specificate de acest standard.
Exemplele prezentate, în continuare, sunt scrise în ANSI C, astfel
încât să poată fi rulate cu succes, după compilare şi editarea legăturilor, de
diferite limbaje de programare C, cum ar fi Microsoft C, Turbo C++,
Borland C++, etc.

1.1. Caracteristicile limbajului C

C-ul este u limbaj universal, care permite scrierea de programe bine


structurate (datorită structurilor de control: decizia, selecţia şi ciclul),
utilizează un bogat set de operatori şi permite definirea de structuri de date
şi lucrul cu pointeri (adrese).
El conţine multe caracteristici din teoria şi practica ştiinţei
calculatoarelor. Conţine structuri moderne de control, trei forme de cicluri
pentru operaţiile repetitive şi trei structuri pentru alegerea căilor alternative
de acţiune. De asemenea, poate reprezenta un domeniu larg de informaţie
prin diferite tipuri de variabile, tablouri şi alte structuri de date.
Limbajul permite descompunerea programului în module, denumite
funcţii; permite documentarea programului prin includerea de comentarii
explicative. Aceste caracteristici permit utilizarea de tehnici de programare,
cum ar fi descompunerea de sus în jos (top-down), programare structurată şi
programare modulară.
C-ul este un limbaj portabil, ceea ce înseamnă că programele scrise
pe un sistem pot fi rulate pe un alt sistem, fără modificări, sau cu mici
modificări. Dacă sunt necesare schimbări, ele pot fi făcute, adesea, numai
prin modificarea câtorva intrări într-un fişier antet (header), care însoţeşte
programul principal.
C-ul este un limbaj puternic şi flexibil. De exemplu, cel mai puternic
şi flexibil sistem de operare, UNIX, este scris în C. Nu este un limbaj de
“nivel foarte înalt”, este unul de nivel mediu, şi nu este specializat pentru un
anumit domeniu de aplicaţii. Absenţa restricţiilor şi generalitatea sa îl fac un
limbaj mai convenabil şi mai eficient, decât alte limbaje.
Nu posedă operaţii care să prelucreze direct: şiruri de caractere,
mulţimi, liste sau tablouri, considerate fiecare ca o entitate. Nu are facilităţi
de intrare / ieşire (precum Read / Write, în Pascal) şi nici metode de acces
direct la fişiere. Aceste mecanisme, de nivel înalt, se realizează prin funcţii
Programare C/C++ 3
explicite. Deci este un limbaj de nivel relativ scăzut (sau înalt), expresiv,
elastic, ce permite o gamă largă de programe.
Limbajul poate fi utilizat pentru probleme inginereşti, dar şi pentru
secvenţe de animaţie. El realizează secvenţe de control delicate, asociate, de
obicei, cu limbajul de asamblare, deci permite accesul la hardware. Permite
manipularea de biţi individuali din memorie, şi este mai puţin restrictiv
decât limbajul Pascal. Această libertate are avantajul că permite mult mai
multe operaţii, cum ar fi convertirea datelor care este mult mai simplă în C,
dar are şi dezavantajul că permite utilizatorului să facă unele greşeli care în
alte limbaje sunt imposibile. Posedă o mare bibliotecă de funcţii utile.
Compilatorul de C furnizează programe compacte şi eficiente.
În concluzie limbajul C este, în prezent, unul dintre cele mai
importante limbaje de programare, şi va continua să fie şi în continuare.

1.2. Noţiuni fundamentale

În acest paragraf sunt definiţi câţiva termeni fundamentali, care


trebuie înţeleşi, înainte de a învăţa cum se programează în C.

Programare

Calculatoarele execută numai ceea ce li se spune (comandă) să


execute. Ele realizează anumite operaţii de bază, aritmetice, logice, etc., care
alcătuiesc setul de instrucţiuni al calculatorului. Pentru a rezolva o problemă
cu ajutorul calculatorului, trebuie exprimată soluţia problemei în termenii
instrucţiunilor, pentru calculatorul respectiv.
Deci un program de calculator este, de fapt, tocmai o listă de
instrucţiuni necesare pentru a rezolva o anumită problemă. Concepţia sau
metoda utilizată pentru a rezolva problema este denumită algoritm. De
exemplu, dacă vrem să determinăm dacă un număr este par sau nu, putem
utiliza următorul algoritm: se divide numărul cu 2; dacă restul împărţirii
este 0, atunci numărul este par, altfel numărul este impar.
După determinarea algoritmului putem trece la scrierea
instrucţiunilor necesare pentru a implementa algoritmul pe un anumit
calculator (sau într-un anumit limbaj). Aceste instrucţiuni pot fi exprimate în
declaraţiile unui anumit limbaj, cum ar fi : PASCAL, BASIC, sau C.

3
4 Programare C/C++

Limbaje de nivel înalt


La începutul evoluţiei lor, calculatoarele puteau fi programate numai
în termenii numerelor binare, care corespundeau direct la anumite
instrucţiuni şi locaţii din memoria calculatorului. Acest limbaj este denumit
limbaj maşină sau cod maşină, iar instrucţiunile respective sunt denumite
instrucţiuni maşină.
Următorul pas tehnologic, în evoluţia softului (programării), a fost
apariţia limbajelor de asamblare, care permiteau programatorului să lucreze
cu calculatorul la un nivel uşor ridicat. În locul secvenţelor de numere
binare, care realizau anumite task-uri (sarcini), limbajul de asamblare
permite programatorului să utilizeze nume simbolice pentru a realiza
anumite operaţii şi pentru a face referire la anumite locaţii de memorie. Un
program special, denumit asamblor, translatează (traduce) programul scris
în limbaj de asamblare, de la formatul său simbolic, la instrucţiunile maşină,
specifice calculatorului, de altfel, singurele pe care acesta le poate executa.
Datorită corespondenţei de unu la unu, existentă între declaraţiile
limbajului de asamblare şi o anumită instrucţiune maşină, limbajele de
asamblare sunt denumite, sau privite, ca limbaje de nivel coborât (sau
scăzut). Programatorul trebuie să cunoască setul de instrucţiuni ale
calculatorului respectiv, pentru a scrie un program în limbaj de asamblare,
iar programul obţinut nu este portabil; deci programul nu va putea fi rulat
(executat) pe un alt tip de calculator, fără a fi rescris, deoarece fiecare tip de
calculator are setul său de instrucţiuni specifice, dependent de maşină.
Ulterior, au apărut aşa numitele limbaje de nivel înalt, unul dintre
primele fiind FORTRAN-ul. O instrucţiune sau declaraţie într-un limbaj de
nivel înalt este translatată în mai multe instrucţiuni maşină, spre deosebire
de corespondenţa unu la unu existentă între declaraţiile limbajului de
asamblare şi instrucţiunile maşină. Standardizarea limbajelor de nivel înalt
înseamnă că un program poate fi scris într-un limbaj, fără a fi dependent de
instrucţiunile maşină; deci programul poate fi rulat pe orice maşină ce
suportă limbajul respectiv, adică posedă un compilator pentru limbajul
respectiv.
Pentru a permite execuţia unui program scris într-un limbaj de nivel
înalt s-au dezvoltat programe speciale care translatează declaraţiile
dezvoltate în limbajul de nivel înalt, într-o formă pe care calculatorul
respectiv o poate înţelege, adică în instrucţiuni specifice calculatorului. Un
astfel de program este denumit compilator. Acesta are ca intrare programul
sursă, adică programul scris într-un limbaj de programare, şi furnizează, ca
ieşire, programul obiect sau programul executabil, care poate fi, ulterior
executat. Mai există un tip de program care realizează translaţia de la
programul sursă la cel executabil, denumit interpretor, dar acesta realizează
Programare C/C++ 5
translaţia pe măsura interpretării liniilor sursă ale programului, deci el nu
furnizează un program executabil, ci realizează translaţia şi execuţia fiecărei
linii sursă, pe măsura citirii declaraţiilor din programul sursă. Diferenţa
dintre cele două programe de translaţie constă, în primul rând, în timpul de
execuţie, care este net defavorabil interpretoarelor, care realizează, de
fiecare dată, traducerea şi execuţia fiecărei instrucţiuni în cod maşină. Cele
două tipuri de translatoare de cod, compilatoare şi interpretoare, precum şi
asambloarele, mai sunt denumite, generic, translatoare de cod.

Sisteme de operare
Un sistem de operare este un program care controlează întreaga
operare asupra sistemului de calcul. Toate operaţiile de intrare / ieşire, pe
care le realizează un calculator sunt efectuate prin intermediul sistemului de
operare. El administrează, de asemenea, resursele calculatorului şi trebuie să
manipuleze execuţia programelor.
Unul dintre cele mai răspândite sisteme de operare este UNIX, care
se poate găsi pe multe tipuri de calculatoare. Iniţial sistemele de operare
erau, de obicei, asociate cu un tip de calculator; deoarece UNIX este scris, în
primul rând, în C, şi face puţine presupuneri asupra arhitecturii
calculatorului, el este portabil, cu succes, pe diferite calculatoare, cu
minimum de modificări.
Alte sisteme de operare, larg răspândite, cum ar fi MS-DOS,
Windows’95, au fost dezvoltate, în primul rând, pentru calculatoarele IBM
PC.

Compilatoare
Un compilator analizează un program dezvoltat într-un anumit
limbaj de programare şi apoi îl translatează într-o formă adecvată pentru
execuţie, pe un anumit calculator.
Pentru rezolvarea unei anumite probleme, cu ajutorul calculatorului,
trebuie stabilit ce trebuie să realizeze programul respectiv, adică ce
informaţii va include programul, ce calcule trebuie să realizeze şi ce
informaţii trebuie să furnizeze utilizatorului. La acest nivel de planificare,
trebuie gândit în termeni general, nu în termenii unui anumit limbaj de
programare.
Odată definită diagrama conceptuală a programului, se va decide
asupra limbajului de implementare, în funcţie de modul de reprezentare a
datelor şi metodelor de prelucrare, a interfeţei cu utilizatorul, a modului de
organizare a programului. Evident, noi vom alege pentru implementare
limbajul C, care reprezintă scopul acestui curs.
În figura următoare sunt prezentate etapele necesare pentru
introducerea, compilarea şi execuţia unui program dezvoltat în limbajul C.

5
6 Programare C/C++
Programul ce va fi compilat este, mai întâi, editat într-un fişier, denumit, în
general, printr-un nume sugestiv, urmat de extensia c sau cpp, pentru C++,
(deci: nume_fişier.c, sau nume_fişier.cpp), care specifică, că acel fişier
conţine un program C.
Pentru editarea lui se poate utiliza orice editor:
- editorul inclus în mediul de integrat de dezvoltare de programe C,
Turbo C, Borland C++, sau pentru UNIX unul dintre editoarele: vi,
edit, sau emacs. În cazul utilizării unui astfel de editor, extensia
fişierului, dacă nu este specificată de utilizator, va fi, în mod
automat, pusă de către editor.
- indiferent de editorul utilizat, trebuie creat un fişier simplu, care să
nu conţină decât text (de exemplu în WS se utilizează modul
”NonDocument”, iar în WP “Text In/Out”, pentru a salva fişierul în
format DOS).
Acest program, salvat în fişierul specificat, este denumit program sursă,
deoarece reprezintă forma originală a programului, exprimată în limbajul C.
După editarea programului urmează compilarea sa, care se realizează
prin apelarea compilatorului printr-o comandă specifică; sub UNIX această
comandă este:
cc nume_fişier.c
În primul pas al procesului de compilare, acesta verifică fiecare
declaraţie, dacă este conformă cu sintaxa şi semantica limbajului. Dacă sunt
descoperite erori, în această fază, ele sunt raportate utilizatorului şi
compilarea ia sfârşit în această fază. Erorile sunt, apoi, corectate în
programul sursă şi se reia procesul de compilare.
Programare C/C++ 7

Figura 1.1 - Etapele realizării unui program

După eliminarea tuturor erorilor de sintaxă şi semantică,


compilatorul va lua fiecare declaraţie a programului şi o translatează în
declaraţii echivalente în limbaj de asamblare, care realizează un task identic.
Următorul pas, după translatarea în limbaj de asamblare, în procesul de
compilare, este translatarea declaraţiilor din limbaj de asamblare în limbaj
(instrucţiuni) maşină. De cele mai multe ori asamblorul este executat în mod
automat, ca parte a procesului de compilare. Asamblorul ia fiecare
declaraţie, din programul în limbaj de asamblare, şi o converteşte într-un
format binar, denumit “cod maşină” sau “cod obiect”, care este depus într-
un alt fişier, care va avea acelaşi nume cu fişierul sursă, dar cu extensia
“obj” (sau “o”, pentru UNIX).

7
8 Programare C/C++
În final se realizează “editarea de legături” (link). De obicei acest
pas este realizat automat (sub UNIX, când se utilizează comanda cc). Alte
sisteme de operare pot cere emiterea unei comenzi specifice, pentru a realiza
acest task. Scopul acestei etape este de “editare de legături” este de a
furniza programul în forma finală, executabilă pe calculator. Dacă
programul curent utilizează alte programe, care au fost procesate anterior de
compilator, atunci în cadrul acestei etape programele sunt ”legate”
împreună. De asemenea sunt căutate şi legate împreună (adică editate
legăturile) cu programul obiect, şi programele din biblioteca de programe a
sistemului.
Fişierul obiect, cel cu extensia .o, sub UNIX, este şters de editorul de
legături, după furnizarea programului executabil, dacă nu se fac referiri la
alte fişiere (programe). Totuşi, dacă programul original utilizează mai multe
fişiere sursă, programe multifişiere, fişierele ce conţin cod obiect sunt
salvate. Spre deosebire de compilatorul de C de sub UNIX, cel de sub DOS
(Windows) nu şterge fişierul obiect, când se obţine fişierul executabil.
Fişierul final, rezultat după această etapă, este în format obiect
executabil, şi el este memorat într-un alt fişier, gata de a fi executat (rulat).
Sub DOS, el va fi denumit cu acelaşi nume, dar cu extensia .exe
(nume_fişier.exe). Sub UNIX, el este denumit, în mod implicit a.out. Pentru
execuţie, comanda constă în tastarea numelui fişierului executabil, care are
ca efect “încărcarea” programului cu numele respectiv, în memoria
calculatorului şi începerea execuţiei sale. Pentru a vizualiza, sub UNIX,
aceste fişiere, se poate utiliza comanda ls, care afişează fişierele din
directorul curent. Dacă dorim să păstrăm fişierul executabil, a.out, trebuie
să-l renumim (cu un nume nou), altfel acest fişier va fi înlocuit printr-un nou
fişier a.out, la următoarea compilare a unui alt program.
Când se execută programul, fiecare declaraţie a sa este executată
secvenţial. Dacă programul solicită date de la utilizator, denumite intrări
(input), programul este temporar suspendat pentru introducerea datelor.
Rezultatele afişate de program, denumite ieşiri (output), vor fi afişate la
terminalul de la care a fost executat programul.
Dacă programul nu furnizează rezultatele dorite, va fi necesară
reanalizarea logicii programului; această etapă este denumită “etapa de
depanare”, în care se elimină toate erorile logice. De obicei, acestea sunt
eliminate prin modificări făcute în programul sursă, după care se reia
întregul proces de compilare, editare de legături şi execuţie, până se obţin
rezultatele dorite (corecte).
Programare C/C++ 9

2. Structura unui program C++

Un program constă dintr-o colecţie de una sau mai multe funcţii,


dintre care una, şi numai una, trebuie denumită main(). Descrierea unei
funcţii constă dintr-un antet şi un corp, ca în exemplul, extrem de simplu,
următor:

/* Exemplificare pentru structura unui program C */

#include <stdio.h> /* declaraţii preprocesor C */


main() /* nume funcţii (argumente) */

{
int numar; /* declară variabila */
numar=1; /* atribuire valoare */
printf("%d este primul numar \n",numar); /* apel funcţie */
}

Antetul conţine declaraţiile preprocesor, precum #include, şi numele


funcţiei. Numele unei funcţii poate fi recunoscut prin parantezele rotunde,
ce urmează, şi care pot fi şi goale (adică fără parametrii între ele). Corpul
funcţiei este inclus între acolade, şi constă dintr-o serie de declaraţii, fiecare
terminată prin ;. După execuţia acestui program se va afişa:
1 este primul numar
Să considerăm un alt exemplu, la fel de simplu, pentru a evidenţia
câteva caracteristici de bază:

/* exemplul 1b */
#include <stdio.h>
main() /* un program simplu */
{
int num; /* se defineşte variabila num */
num = 1; /* se iniţializează variabila num cu valoarea 1 */
printf(”Este un simplu”); /* utilizarea funcţiei printf() */
printf(“exemplu.\n”);
printf(“Numarul favorit este %d, deoarece”);
printf(“este primul.\n”, num);
}

9
10 Programare C/C++
După execuţia acestui program se va afişa următorul mesaj:
Este un simplu exemplu.
Numarul favorit este 1, deoarece este primul.
Vom exemplifica, tot printr-un exemplu simplu, care tipăreşte un mesaj şi o
valoare numerică, folosirea şi a celor două proceduri des utilizate în C++
cin şi cout.

#include <iostream.h>
#include <conio.h>
/*
se afiseaza: Un exemplu de program simplu C++
1 este primul numar
*/
void main(void)
{
int numar;
numar = 1;
cout << "Un exemplu de program simplu C++ " << endl;
cout << numar << " este primul numar" << endl;
getch();
}

Prima linie a programului specifică calculatorului să includă


informaţia ce se găseşte în fişierul stdio.h, iostream.h sau conio.h care sunt
fişiere din pachetul de programe al compilatorului C. Un program constă
dintr-una sau mai multe funcţii, care sunt module de bază în C. Parantezele (
) identifică numele pe care-l urmează, ca numele unei funcţii, în exemplul
anterior main(); aceasta este prima funcţie, întotdeauna, dintr-un program C.
Parantezele care urmează după numele unei funcţii conţin între ele, în
general, informaţii ce se transmit funcţiei. În cazul de faţă nu se transmite
nimic ( ), dar parantezele trebuie, totuşi, specificate.
Comentariile, explicative, care însoţesc un program, sunt incluse
între simbolurile /* şi */, care sunt ignorate de compilator. Acolada la stânga
{ marchează începutul declaraţiilor care alcătuiesc funcţia. Definiţia funcţiei
se termină cu acoladă dreapta }. Cele două acolade marchează corpul unei
funcţii.
Declaraţia: int num;
specifică utilizarea unei variabile, denumită num, care este de tip întreg
(integer); declaraţia este utilizată şi pentru a rezerva spaţiul necesar în
memorie. Toate variabilele trebuie să fie declarate, ceea ce înseamnă că
Programare C/C++ 11
trebuie listate toate variabilele care se utilizează într-un program, şi pentru
fiecare variabilă trebuie specificat tipul. Există mai multe tipuri de date, care
vor fi descrise ulterior, dar deocamdată putem preciza câteva tipuri de date:
întregi, reale, caracter, etc.

11
12 Programare C/C++
Structura generală a unui program C este următoarea:
- #include

- main()
|--------- declaraţii

- function a()

|--------- declaraţii

- function b()

|---------declaraţii:
declaraţii - cuvinte cheie,
cele 5 tipuri de asignări
declaraţii, în funcţie - date,
limbajul C: control
null - operatori

Următoarea declaraţie num = 1 asociază (atribuie) variabilei num


valoarea 1. În continuare se afişează propoziţiile dintre ghilimele “Este un
simplu” “exemplu.”; simbolul \n utilizat în cea de-a doua funcţie,
printf(“exemplu.\n”), specifică calculatorului să se treacă, la afişare, pe o
linie nouă.
Următoarele linii afişează valoarea variabilei num (1), care este
inclusă în propoziţia dintre ghilimele. Simbolul %d specifică calculatorului,
unde şi în ce format să se tipărească valoarea lui num.
Ultima acoladă, dreapta, }, specifică sfârşitul programului. Simbolul
; utilizat la sfârşitul unei linii identifică linia ca o declaraţie sau instrucţiune
C.
O declaraţie poate fi scrisă pe mai multe linii, şi, de asemenea, pe o
linie pot fi scrise mai multe declaraţii, separate prin ;. Dar, pentru claritatea
programului, se recomandă scrierea unei singure declaraţii pe câte o linie;
doar dacă declaraţia este mai lungă, şi nu încape pe o linie, ea poate fi
continuată pe liniile următoare. Separatorul ; va preciza compilatorului unde
se sfârşeşte o declaraţie şi unde începe următoarea.
Programare C/C++ 13
2.1. Modularizarea programelor

Directiva #include indică faptul că într-un fişier poate fi inclus


conţinutul altui fişier. În mod normal, un fişier C poate conţine mai multe
funcţii, iar un program este format din ansamblul fişierelor de program care
grupează toate funcţiile utilizate pentru rezolvarea problemei. Un astfel de
fişier de program este denumit modul de program, sau, pe scurt, modul.
În general, o problemă se descompune în subprobleme, care la
rândul lor, se pot descompune în alte subprobleme, şi acest proces de
descompunere succesivă poate continua până se obţin subprobleme suficient
de simple, rezolvate cu ajutorul uneia sau câtorva funcţii simple.
Funcţiile care cooperează pentru rezolvarea unei subprobleme sunt
grupate în cadrul aceluiaşi modul. De obicei, un modul conţine o funcţie
reprezentativă, de bază, principală, care este apelată pentru a declanşa
rezolvarea subproblemei corespunzătoare modulului de program.
Generalizând, va exista un modul de program şi pentru întreaga problemă,
care conţine şi el, la rândul său, o funcţie principală, care este apelată atunci
când se lansează în execuţie programul, de către sistemul de operare al
calculatorului. În C această funcţie trebuie să se numească main (), pentru a
fi recunoscută de compilator, care generează un cod special, pentru a putea
fi apelată de către sistemul de operare.
Deci, în concluzie, structura unui program este modulară, fiecare
modul corespunzând unei subprobleme. Aceste module alcătuiesc o ierarhie,
în vârful căreia se află modulul principal, care conţine funcţia principală a
programului, main.
Limbajul C are o mare varietate de funcţii, ce alcătuiesc o bibliotecă,
pentru prelucrarea tipurilor de date predefinite în limbaj: numerice, şiruri de
caractere, tablouri, structuri, pointeri, fişiere.
Pe lângă modulele program, care conţin funcţii, există şi module
antet, ce au un rol declarativ, fiind asociate cu modulele program. Funcţiile
de bibliotecă sunt grupate în astfel de module antet. În C există o convenţie
în ceea ce priveşte numele fişierelor sursă ce corespund modulelor program
şi celor antet. Modulele de program sunt de forma nume_fişier.c (sau
nume_fişier.cpp ), în timp ce pentru modulele antet sunt nume_fişier.h .
Pentru a include textual un modul sursă C în alt modul sursă C se
utilizează directiva #include, care poate fi utilizată în două forme:
#include <nume_fişier>
#include “nume_fişier”
Prima formă caută fişierul <nume_fişier> printre fişierele mediului de
programare C, în timp ce cea de-a doua caută în zona de lucru a
utilizatorului.

13
14 Programare C/C++
În exemplul anterior fişierul stdio.h, care este furnizat ca parte a
compilatorului C, conţine informaţii despre funcţiile de intrare / ieşire
utilizate de compilator; numele său provine de la standard input / output
header (adică modul antet standard de intrare / ieşire).
Utilizatorii C fac referire la o colecţie de informaţii care se află la
începutul unui fişier, denumit fişier antet (header); implementările tipice
încep cu mai multe module antet. Efectul declaraţiei #include este de a
include acest fişier, adică acelaşi cu copierea întregului conţinut al fişierului
stdio.h în fişierul nostru, la începutul acestuia. În acest modul se află, de
exemplu, funcţia printf. Compilatorul va include doar informaţiile necesare
programului. Această directivă, #include, este adresată preprocesorului C.
Orice informaţie pe care compilatorul nu o utilizează nu devine parte a
programului. Simbolul # identifică această linie ca pe o directivă, nu ca pe o
declaraţie propriu-zisă, şi care este rezolvată de preprocesorul C;
preprocesorul manipulează anumite task-uri înainte de prelucrarea lor de
către compilator.
Programare C/C++ 15

2.2. Unităţile lexicale ale limbajului C

Ca în orice limbaj există următoarele tipuri de unităţi lexicale:


- identificatori (inclusiv cuvintele cheie);
- constante (numerice, caracter, simbolice, enumerare);
- şiruri de caractere;
- operatori, şi
- separatori (sau delimitatori);
Înainte de a descrie aceste unităţi lexicale, care alcătuiesc liniile unui
program C, sursă, trebuie precizat alfabetul limbajului , adică mulţimea
semnelor (simbolurilor) pe baza cărora se construiesc aceste entităţi; acesta
conţine :
- cele 26 de litere mari şi mici;
- cele 10 cifre (caractere numerice);
- caracterul subliniere _, spaţiul ;
- alte caractere speciale: +, -, *, /, :, :, etc., care se află pe tastatura
calculatorului.

2.2.1. Identificatori

Un identificator este o succesiune de litere, cifre şi caracterul


subliniere, şi care trebuie să înceapă cu o literă sau cu caracterul subliniere.
Trebuie menţionat că C-ul face deosebire între literele mari şi cele mici,
adică, de exemplu Numar şi numar, reprezintă identificatori diferiţi.
Aceşti identificatori pot fi împărţiţi în două categorii:
- identificatori predefiniţi, sau cuvinte cheie, rezervaţi limbajului;
- identificatori definiţi de utilizator.
Identificatorii definiţi de utilizator pot reprezenta nume de variabile,
constante, tipuri, funcţii, câmp al unei structuri, sau o macrodefiniţie. Aceste
nume trebuie alese cât mai sugestive. Numărul de caractere permise pentru
un identificator variază între diferite implementări, dar limita inferioară este
de cel puţin 8 caractere. ANSI C permite utilizarea până la 32 de caractere,
cu excepţia identificatorilor externi, pentru care sunt recunoscute doar 8
caractere.
Caracterele utilizate pot fi litere, mari şi mici, cifre, precum şi
caracterul subliniere _ , care este considerat ca oricare altul, iar primul
caracter trebuie să fie o literă. Dacă, de exemplu, compilatorul nu recunoaşte
decât 8 caractere, se pot utiliza mai multe, dar compilatorul nu le va lua în
considerare pe cele în exces.

15
16 Programare C/C++
Astfel pe un sistem cu limita de 8 caractere, identificatorii:
variabila1 şi variabila2
vor fi consideraţi ca una şi aceeaşi variabilă: variabil (adică vor fi citite
numai primele 8 caractere). Multe dintre rutinele de bibliotecă utilizează, de
obicei, nume ce încep cu simbolul subliniere, deoarece se presupune că
utilizatorul nu va alege, de obicei, nume ce încep cu acest simbol, şi deci va
utiliza aceste nume cu alte semnificaţii.
Aceşti identificatori trebuie să fie diferiţi de următoarele cuvinte
cheie (rezervate) ale limbajului:

Char register break


Short const case
Int volatile continue
Long auto default
Signed extern do
Unsigned static for
Float struct goto
Double union if
Return while else
Main enum switch
Void sizeof typedef

Aceste cuvinte au o semnificaţie bine determinată, predefinită, şi nu pot fi


utilizate decât astfel cum permite sintaxa limbajului. Cuvintele cheie se
scriu, obligatoriu, cu litere mici.

2.2.2. Constante

Constantele pot fi numerice, caracter, simbolice, sau enumerare.


Constantele numerice pot fi întregi sau reale.

Constante întregi
O constantă întreagă este o secvenţă de cifre, care poate fi precedată,
sau nu, de semnul + sau - (operatorii binari + sau -, cei care precizează
semnul unei expresii).
Dacă constanta începe cu cifra 0, ea este considerată o constantă
octală (deci, exprimată în baza 8), în care caz toate cifrele care urmează
trebuie să fie între 0 şi 7.
Dacă constanta este precedată de secvenţa 0x sau 0X, ea este o
constantă hexazecimală, şi cifrele care urmează pot fi în domeniul 0-9, sau
a-f (sau A-F); aceste litere (mici, a-f, sau mari, A-F) sunt utilizate pentru a
Programare C/C++ 17
codifica valorile de la 10 la 15 (10=a,…,15=f), care trebuie exprimate, în
hexazecimal, şi care trebuie să ocupe, ca în orice sistem de numeraţie
poziţional, o singură poziţie.
În orice alt caz, constanta întreagă este considerată o constantă
zecimală. O constantă întreagă este reprezentată, în general, pe 2 octeţi.
Dacă constanta zecimală depăşeşte tipul întreg, de bază, se consideră de tip
long int, sau dacă nu este suficient, va fi de tip unsigned long int, şi în acest
caz este reprezentată pe 4 octeţi.
O constantă octală sau hexazecimală care depăşeşte cel mai mare
întreg fără semn de tipul int se consideră, de asemenea, de tip unsigned int,
iar dacă nu este suficient acest format va fi de tip long int, şi dacă şi acest
format nu este suficient, va fi de tip unsigned long int.
Dacă constanta este urmată de litera l sau L, atunci ea este de tip
long int. Dacă valoarea nu poate fi reprezentată de acest tip, ea este tratată
ca unsigned long int.
Dacă constanta este urmată de litera u sau U, atunci ea va fi tratată
ca unsigned int, sau unsigned long int, dacă nu poate fi reprezentată de
primul tip.
Dacă se utilizează ambele sufixe (lu sau LU), atunci constanta
întreagă va fi de tip unsigned long int.

Constante reale
Constantele reale pot fi reprezentate în două moduri:
- în forma normală, adică prin partea întreagă, ce poate fi precedată
de semnul plus sau minus, un punct zecimal şi partea fracţionară.
De exemplu: -12.456
- în forma cu exponent, adică forma precedentă, urmată de litera e
sau E, şi, opţional, de un exponent, care este un întreg, cu semn.
De exemplu:
-1245.6e-2 (sau -1245.6E-2)
şi care reprezintă, de fapt, valoarea:
-1245.6 * 102 , adică -12.456.
În ambele reprezentări partea întreagă şi cea fracţionară sunt constituite
dintr-o succesiune de cifre. La o constantă reală, partea întreagă sau cea
fracţionară pot lipsi, dar nu amândouă simultan; de asemenea, poate lipsi
punctul zecimal, sau litera e (E) şi exponentul.
Constantele reale sunt tratate ca valori cu precizie dublă, adică de tip
double. Dacă dorim ca o valoare de tip real să fie considerată de tipul float,
atunci ea trebuie urmată de litera f sau F. Dacă sunt urmate de litera l sau L,
vor fi considerate de tip long double.

Constante caracter

17
18 Programare C/C++
O constantă de tip caracter este reprezentată de un caracter inclus
între apostrofuri, cum ar fi de exemplu: ’x’. Valoarea unei constante de tip
caracter este valoarea numerică a codului caracterului, din codificarea
ASCII. De exemplu, iată câteva valori asociate unor caractere:
‘0’ - 48 (30H)
....
‘9’ - 57 (39H)
‘A’ - 65 (41H)
‘B’ - 66 (42H)
....
‘a’ - 97 (61H)
‘b’ - 98 (62H)
....
Deoarece codurile asociate sunt succesive, şi valorile ce vor fi asociate sunt
succesive (pentru cifre, litere mari şi litere mici, respectiv).
Constantele caracter pot participa la operaţiile aritmetice, ca şi
oricare alte numere.

Secvenţe de evitare (escape)


Anumite caractere negrafice şi caracterele apostrof (‘) şi backslash
(\) pot fi reprezentate ca şi constante caracter, cu ajutorul, aşa-numitelor,
secvenţe de evitare (escape). Aceste secvenţe, de evitare, permit şi
reprezentarea caracterelor mai greu de reprezentat (introdus), precum şi a
oricăror configuraţii de biţi. Aceste secvenţe sunt următoarele:
\a - semnal sonor (beep);
\b - spaţiu înapoi (backspace);
\f - linie nouă, dar pe coloana următoare celei curente
(form feed);
\n - linie nouă, dar pe prima coloană (new line);
\r - revenire la începutul liniei curente (carriage return);
\t - tabulare orizontală (horizontal tab);
\v - tabulare verticală (vertical tab);
\\ - backslash;
\” - ghilimele;
\’ - apostrof;
\0 - null;
\nnn - valoare caracter, în octal;
\xnn - valoare caracter, în hexazecimal;

Când caracterul care urmează după backslash (\) nu este unul dintre
cele specificate, atunci backslash-ul este ignorat. Toate caracterele setului
ASCII (American Standard Code for Information Interchange) sunt pozitive,
Programare C/C++ 19
sau mai exact sunt fără semn, dar o constantă caracter specificată printr-o
secvenţă de editare poate fi şi negativă (de exemplu ‘\377’ are valoarea -1).
Unele compilatoare de C permit reprezentarea de caractere, ale căror
coduri ocupă 2 sau mai mulţi octeţi. Reprezentarea unor astfel de caractere
începe cu litera L. De exemplu:
L ‘\101\102\103\104’ (ABCD)
este un caracter multi-octet, al cărui cod este memorat pe 4 octeţi. Tipul
acestor constante este wchar_t, cum este definit în fişierul antet standard
<stddef.h>, şi permite exprimarea unui caracter, dintr-un set de caractere, ce
nu pot fi reprezentate cu tipul normal char.

Constante simbolice
O constantă simbolică este un identificator cu valoare de constantă;
valoarea constantei poate fi orice şir de caractere, introdus prin construcţia
(declaraţia) #define, ca de exemplu:
#defineMAX 100
Compilatorul va înlocui toate apariţiile constantei simbolice MAX, cu
valoarea 100. Numele constantelor simbolice se scriu, de obicei, cu litere
mari, fără însă a fi obligatoriu, spre a le deosebi de alţi identificatori.

Constante enumerare
Un identificator care a fost declarat ca o valoare pentru un tip
enumerare este considerat o constantă de un tip particular şi este, de altfel,
tratat ca fiind de tipul int, de către compilator.

2.2.3. Şiruri de caractere

Un şir de caractere este o succesiune de caractere scrise între


ghilimele; de exemplu:
“ABCD”
Ghilimelele delimitează şirul. Dacă dorim să includem şi caracterul
ghilimele în şirul de caractere, pentru acesta se va utiliza secvenţa de
evitare: \”. Pentru a continua un şir de caractere pe mai multe linii se poate
folosi caracterul \, la sfârşitul liniei respective, caz în care acest caracter \ va
fi ignorat.
Pentru un şir de caractere se mai utilizează denumirea constantă (de
tip) şir. Sfârşitul unui şir de caractere este marcat, automat, de către
compilator, prin plasarea, la sfârşitul şirului, a caracterului NULL (‘\0’), care
este un caracter netipăribil. Această reprezentare înseamnă că, teoretic, nu

19
20 Programare C/C++
există o limită a lungimii unui şir; programele trebuie să parcurgă şirul,
analizându-l pentru a-i determina lungimea.
La alocare memoria fizică necesară este cu un octet mai mult decât
numărul de caractere, scris între ghilimele, datorită adăugării automate a
caracterului ‘\0’, la sfârşitul fiecărui şir.
În mod normal, compilatorul furnizează un pointer (referinţă, adică
adresa de început a şirului) către primul caracter din şir, şi tipul său este
“pointer la char”(char *). Constantele de tip şir de caractere nu pot fi
modificate de program.
Preprocesorul concatenează împreună, automat, constantele de tip şir
de caractere, adiacente. Şirurile pot fi separate sau nu prin blancuri (spaţii
libere):
“un ” “sir ” “de ” “caractere”
Cele trei şiruri sunt echivalente cu un singur şir:
“un sir de caractere”
Iată un exemplu de program simplu, ce utilizează şiruri de caractere:

/* exemplu de utilizare a unui sir de caractere*/


#include <stdio.h>
#define SIR “daca asta ti-e numele”
main ( )
{
char nume[50];
printf(“Cum te cheama ?\n”);
scanf(“%s”, nume); /* descriptorul %s citeste o data de tip sir
de caractere*/
printf(“Salut, %s ; %s !\n”, nume, SIR);
}

Observaţii:
1) Funcţia scanf citeşte caracterele până întâlneşte unul dintre
caracterele: spaţiu liber, tab, sau linie nouă (newline). Pentru a citi
mai multe cuvinte (propoziţii) ca un şir, se poate folosi o altă funcţie
de citire gets(), pentru a manipula şiruri, în general.
2) Şirul “x” nu este acelaşi cu caracterul ‘x’. O primă diferenţă este că
‘x’ este un tip de bază (char), iar “x” este un tip derivat, un tablou de
char. O a doua diferenţă este că ‘x’ este un singur caracter, un singur
octet, în care se află codul ASCII al literei x, în timp ce “x” este un
şir de caractere, mai exact doi octeţi: primul este ‘x’, iar cel de-al
doilea este caracterul NULL (‘\0’).
Programare C/C++ 21
Pentru a determina numărul efectiv de caractere dintr-un şir se poate utiliza
funcţia strlen().
În exemplul următor, vom utiliza această funcţie, dar şi operatorul
sizeof, pentru a vedea care este diferenţa dintre aceştia:

/* alt exemplu de utilizare a unui sir de caractere, si a unor functii*/


#include <stdio.h>
#include <string.h>
#define SIR “Am retinut numai numele”
main ( )
{
char nume[50];
printf(“Cum te cheama ?\n”);
scanf(“%s”, nume);
printf(“Salut, %s . %s \n”, nume, SIR);
printf(“Numele are %d litere, si ocupa %d octeti \n”, strlen(nume),
sizeof nume);
printf(“Propozitia pentru SIR are %d litere, si ocupa %d octeti\n”,
strlen(SIR), sizeof SIR);
}

Funcţia strlen(), returnează lungimea şirului de caractere s, fără


caracterul de sfârşit:

int strlen (char s[])


{
int i;
i = 0;
while (s[i] != ‘\0’)
++i;
return (i);
}

La prima utilizare funcţia strlen(nume) returnează numărul de litere


efective din variabila nume, până la întâlnirea caracterului de sfârşit de şir
de caractere (‘\0’). Tot la prima utilizare funcţia sizeof nume returnează
numărul de octeţi pe care-i ocupă tabloul (şirul) nume.
La cea de-a doua utilizare, funcţia strlen(SIR) returnează numărul de
litere din SIR (23), iar funcţia sizeof SIR va returna numărul de litere din
SIR, la care se adaugă şi caracterul NULL (‘\0’), deci în total 24 de
caractere.

21
22 Programare C/C++
Dacă un comentariu apare în interiorul unei constante de tip caracter
sau şir de caractere, el nu este tratat ca separator.
Se pot utiliza şi şiruri de caractere reprezentate pe mai mulţi octeţi,
nu doar pe unul, ca în cazul obişnuit. În acest caz şirul va fi precedat de
litera L :
L “ABCD” este echivalent cu vectorul (şirul):
{L ‘A’, L ‘B’, L ‘C’, L ‘D’, ‘\0’}, care conţine 5 valori de
tipul wchar_t.

2.2.4. Operatori

Limbajul C prezintă un mare număr de operatori (sau semne de


punctuaţie), care pot fi clasificaţi după diverse criterii:
- după numărul de operanzi: unari (un operand), binari (doi
operanzi), ternari (trei);
- după tipul operaţiei: aritmetici, logici, la nivel de bit, de
adresare, etc.
În capitolele următoare vom prezenta clasele de operatori, care
corespund la anumite nivele de prioritate.

2.2.5. Separatori

Un separator este un caracter sau un şir de caractere, care separă


unităţile lexicale, dintr-un program C. Cel mai frecvent separator este aşa-
numitul “spaţiu alb”, care poate fi:
- unul sau mai multe blancuri (spaţii libere);
- tab-uri orizontale sau verticale;
- linie nouă (new-line), sau comentariile.
Aceste construcţii sunt eliminate de către preprocesor, care furnizează
această “unitate de compilare”, compilatorului. Compilatorul verifică
sintaxa şi generează cod, adică furnizează modulul obiect. Acest modul este
completat cu celelalte module obiect ale programului şi cu cele de
bibliotecă, folosind un editor de legături; după această fază, de editare de
legături, se obţine programul executabil.
Separatorii admişi de limbajul C, sunt următorii:
- separatorii limbajului: + - / = < > <= >= == . ; , etc.;
- ( ) utilizate în expresii, sau încadrează lista de argumente a unei
funcţii;
Programare C/C++ 23
- { } acoladele încadrează instrucţiunile compuse, sau blocurile,
care constituie corpul unor instrucţiuni, sau corpul unor
funcţii;
- [ ] delimitează dimensiunile de tablou, sau indicii acestuia;
- “ “ delimitează un şir de caractere;
- ‘ ‘ delimitează un caracter sau o secvenţă de evitare;
- ; termină o instrucţiune;
- /* */ sau // delimitează, sau precede un comentariu;

23
24 Programare C/C++

2.3. Operaţii de intrare / ieşire

Funcţiile printf () şi scanf ()


Aceste două funcţii permit comunicarea cu un program, fiind
denumite funcţii de intrare/ieşire. Nu sunt singurele, dar sunt cele mai des
utilizate.
Parantezele ce urmează după printf/scanf specifică, că avem de-a
face cu o funcţie. Informaţia dintre paranteze este denumită argumentul
funcţiei. Când execuţia funcţiei este terminată, controlul este returnat
programului apelant. Această funcţie va tipări textul cuprins între ghilimele.
Pentru a transmite diferite caractere de control, la tipărire, se utilizează
secvenţele de evitare, descrise anterior.
Cu ajutorul funcţiei printf() putem tipări text, dar şi valori de
diferite tipuri (numerice: întregi sau reale, sau caractere) utilizând anumite
notaţii. Aceste notaţii sunt denumite specificatori de conversie sau
descriptori de conversie (format), pentru că specifică cum datele sunt
convertite într-o formă ce poate fi afişată:
Specificator Semnificaţie (tipărire)
%c Un singur caracter
%d Număr întreg zecimal cu semn
%e Număr zecimal, în virgulă mobilă, cu exponent
%E Număr zecimal, în virgulă mobilă, cu exponent
%f Număr zecimal, în virgulă mobilă, formă normală
%g Se utilizează %f sau %e, care din ele este mai scurt
%G Se utilizează %f sau %E, care este mai scurt
%i Întreg zecimal cu semn
%o Întreg octal fără semn
%p Pointer
%s Şir de caractere
%u Întreg, zecimal, fără semn
%x Întreg hexazecimal, fără semn
%X Întreg hexazecimal, fără semn

Se pot modifica specificatorii de conversie de bază prin inserarea


unor modificatori între simbolul % şi caracterul care defineşte conversia. În
tabela următoare sunt prezentate aceste simboluri; dacă se utilizează mai
mulţi modificatori ei trebuie specificaţi în ordinea în care apar în această
tabelă. Nu chiar toate combinaţiile sunt posibile.
Programare C/C++ 25

Modificatori Semnificaţie
- Este tipărit la începutul (stânga) câmpului respectiv.
Exemplu: %-20s
+ Valorile cu semn sunt afişate cu semnul + sau -, după
cum sunt pozitive sau negative. Exemplu: %+6.2f
Spaţiu Afişează un spaţiu pentru valorile pozitive când nu se
utilizează semnul +.
Exemplu: printf(“$ %6.2f”, 10) va tipări $ 10.00
# Foloseşte o formă alternativă pentru specificarea
conversiei. Furnizează un 0 iniţial pentru forma o, un
0x iniţial sau 0X pentru formele x şi X. Pentru toate
formele în virgulă mobilă, garantează tipărirea
caracterului punct (.), chiar dacă nu sunt cifre după
punct (pentru forma g sau G previne ca zerourile ce
urmează să fie şterse(înlocuite)). Exemple: %#o,
%#8.0f, %#10.3E
0 Pentru formele numerice umple câmpul cu zerouri, la
început, în loc de spaţii. Acest indicator este ignorat
dacă este prezent -, sau dacă pentru un întreg se
specifică o precizie. Exemple: %010d, %08.3f
dim_câmp O valoare ce reprezintă dimensiunea minimă a
câmpului în care se afişează. Dacă valoarea este mai
mare se extinde la numărul de caractere necesare
pentru a tipări valoarea. Exemplu: %4d
.precizie Reprezintă numărul de zecimale cu care se afişează o
valoare reală (virgulă mobilă) pentru specificatorii e,
E şi f. Pentru f şi G reprezintă numărul maxim de
cifre semnificative. Pentru s reprezintă numărul
maxim de caractere de tipărit. Pentru conversiile
întregi reprezintă numărul minim de cifre; vor fi
utilizate zerouri în faţa numărului pentru a satisface
acest număr minim de cifre. Utilizarea uneori a
punctului (.) este echivalentă cu .0. Exemplu: %5.2f-
afişează real cu două zecimale într-un câmp de cinci
caractere.
h Utilizat cu un specificator întreg pentru a indica o
valoare de tip short int sau unsigned short int.
Exemplu: %hu, %hx, %6.4hd
l Utilizat cu o conversie întreagă pentru a indica o

25
26 Programare C/C++
valoare long int sau unsigned long int.
Exemple: %ld , %8lu
L Utilizat cu o conversie de număr real (virgulă mobilă)
pentru a indica o valoare long double. Exemple: %Lf,
% 10.4Le

Pentru cei doi modificatori, dim_câmp şi precizie, se poate utiliza şi


simbolul * , ceea ce înseamnă că valorile celor doi modificatori vor fi
furnizate de o expresie (variabilă), din lista de argumente a funcţiei.
Simbolul * poate înlocui numai un modificator sau pe amândoi.
Trebuie remarcat că nu există specificatori pentru tipul float; motivul
este că aceste valori sunt convertite automat la tipul double, înainte de a fi
utilizate în expresii sau ca argumente. Astfel şi la tipărire valorile de tip
float sunt convertite la tipul double.
Funcţia printf(), pe lângă afişarea caracterelor dintre ghilimele şi a
valorilor specificate, va returna şi o valoare întreagă reprezentând numărul
de octeţi afişaţi (sau de coduri ASCII transmise la display); această valoare
este asociată numelui funcţiei.

Funcţia scanf ()

Această funcţie este una din cele mai folosite, în C, pentru citirea
unei varietăţi de date. Orice tip de valoare tastată de la tastatură reprezintă
un text format din caractere. Dacă vrem să memorăm aceste caractere ca o
valoare numerică programul trebuie să convertească şirul de caractere la o
valoare numerică. Această operaţie este realizată de funcţia scanf() care
converteşte în diferite forme: întregi, numere în virgulă mobilă (reale),
caractere şi şiruri. Într-un fel este inversul funcţiei printf() .
În mod asemănător cu printf(), funcţia scanf() utilizează un şir de
caractere de control urmate de o listă de argumente. Caracterele de control
(sau specificatori de conversie) indică în ce formate este convertită intrarea.
Dar spre deosebire de printf(), care utilizează nume de variabile,
constante şi expresii, scanf() utilizează pointeri la variabile.
Pentru aceasta numele variabilei căreia i se atribuie unul din tipurile
de bază, trebuie precedat de semnul &. Excepţia de la această regulă o
constituie citirea unei valori pentru o variabilă de tip şir de caractere:
char sir[30];
---------------
scanf(“%s”, sir);
Pentru a separa câmpurile, la introducerea datelor, se utilizează spaţii
albe (blancuri, taburi şi linie nouă).
Programare C/C++ 27
Se realizează separat conversia acestor câmpuri succesive, sărindu-se
spaţiile albe ce le despart. Valorile citite sunt asociate, în ordinea citirii,
variabilelor din lista de variabile asociată lui scanf(). Un câmp se poate
întinde pe mai multe linii până la întâlnirea unui spaţiu alb. Singura excepţie
de la această regulă este utilizarea specificatorului %c, cu care se citeşte
caracterul următor, chiar dacă el este spaţiu alb.

Specificatorii de conversie utilizaţi de această funcţie sunt prezentaţi


în tabela următoare:

Specificator de Semnificaţie (interpretarea )


conversie
%c Un caracter
%d Un număr întreg zecimal cu semn
%e, %f, %g Un număr real (virgulă mobilă)
%E, %G Un număr real (virgulă mobilă)
%i Număr întreg zecimal cu semn
%o Întreg octal cu semn
%p Pointer
%s Şir de caractere; şirul începe cu primul caracter
diferit de spaţiu alb şi continuă până la primul
spaţiu alb
%u Întreg zecimal fără semn
%x, %X Întreg hexazecimal cu semn

Observaţie: dacă se utilizează specificatorul %c, şi este precedat de


un spaţiu, atunci scanf() va sări la primul caracter diferit de spaţiu alb, deci
va omite spaţiile albe. Astfel:
scanf(“%c”, &car) - se va citi primul caracter din intrare
scanf(“ %c “, &car) - citeşte primul caracter diferit de spaţiu alb, din
intrare.
Pe lângă această funcţie mai există şi alte funcţii de intrare cum ar fi
getchar() şi gets() care sunt mai adecvate pentru anumite probleme, pentru
citirea de caractere, unul câte unul, sau şiruri de caractere ce conţin şi spaţii.

27
28 Programare C/C++
În mod asemănător funcţiei printf(), specificatorii de conversie pot fi
precedaţi de modificatori, care sunt prezentaţi, împreună cu semnificaţia lor
în tabela următoare.
Funcţia scanf() returnează numărul de elemente citite cu succes.
Dacă nu se citeşte nici un element, de exemplu se introduce un şir de
caractere nenumerice, când funcţia aşteaptă un număr, funcţia scanf()
returnează valoarea 0. Se returnează EOF, dacă detectează condiţia
cunoscută ca ”End of File” (sfârşit de fişier). EOF este o valoare specială
definită în fişierul stdio.h, având asociată valoarea -1 (#define EOF-1).
Deci pentru citirea corectă a unei valori numerice, de exemplu, se
poate utiliza un ciclu de forma:
while (scanf(“%d”, &numar) == 0);
care se va executa cât timp valoarea returnată de scanf(), adică
numărul de elemente citite, este 0.
Programare C/C++ 29

Modificatori Semnificaţie
* Suprimă atribuirea pentru funcţia scanf(); se utilizează
în printf() în locul specificatorilor de dimensiune de
câmp, deşi se va specifica dimensiune câmpului, dar
printr-un argument
Exemplu: printf(“numarul real:%*.*f\n”, lung_camp,
precizie, numar);
printf(“intreg:%*d\n”, lung_camp, valoare);
Când se utilizează în scanf() determină ca funcţia să
sară peste intrarea respectivă.
Exemplu: scanf(“%*d %*d %d, &n);
va avea ca efect omiterea primilor 2 întregi şi copierea
celui de-al 3-lea în n.
Valoare Reprezintă dimensiunea maximă a câmpului. Citirea se
întreagă opreşte când se atinge dimensiunea maximă sau când
(pentru şir de se întâlneşte primul caracter spaţiu alb.
caractere) Exemplu: %10s
h, l, L %hd, %hi valoarea va fi automat de tip short int
%ho, %hx, %hu - unsigned short int
%ld, %li - long int
%lo, %lx, %lu - unsigned long int
%le, %lf, %lg - double
Utilizând L în loc de l cu e, f, g va fi de tip long double.
În absenţa acestor modificatori: d, i, o şi x - vor
memora valoarea de tip int;
e, f, g - indică tipul float

Dacă specificatorii de conversie cu care se tipăresc valorile


variabilelor din lista de argumente a funcţiei printf() nu corespund tipului
variabilelor respective nu se va semnala nici o eroare, dar valorile tipărite nu
vor fi cele corecte.
Dacă pentru separarea valorilor ce se citesc cu funcţia scanf() nu se
utilizează spaţii albe ci alţi separatori, aceştia vor fi intercalaţi între
specificatorii de conversie utilizaţi pentru citirea valorilor variabilelor
respective.

2.3.1. Utilizarea procedurilor cin şi cout

29
30 Programare C/C++

Pe lângă funcţiile prezentate în paragraful anterior, scanf şi printf,


mai pot fi folosite tot pentru operaţiile de intrare/ieşire şi procedurile cin şi
cout care sunt şi ele găsite în C++.
Pentru aceasta trebuie să includeţi în program fişierul antet în care
sunt definite aceste proceduri, respectiv fişierul iostream.h. În rest utilizarea
lor nu diferă mult de utilizarea funcţiilor descrise anterior. O deosebire
esenţială constă în transmiterea comenzii de trecere pe o linie nouă (new
line); în cazul funcţiei printf() trecerea pe o linie nouă se realizează prin
transmiterea, în cadrul şirului de caractere de tipărit, a caracterului ‘\n’, în
timp ce pentru cout aceasta se realizează prin procedura endl.

#include <iostream.h>
void valori(int sir[], int numar_de_elemente)
/* aceasta functie citeste un sir de “numar_de_elemente” valori de
tip interg */
{
int i;

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


{
cout << "Introduce valoarea " << i << ": ";
cin >> sir[i];
}
}

void main(void)
{
int numar[3];
valori(numar, 3); /* se citeste un vector de 3 valori intregi */
cout << "Valorile sirului sunt" << endl;
for (int i = 0; i < 3; i++) /* dupa care se tiparesc */
cout << numar[i] << endl;
}
Programare C/C++ 31
3.Variabile şi tipuri de date.

Pentru a rezolva diverse probleme, cu ajutorul calculatorului; pe


baza unui program, acesta trebuie să lucreze cu date de diferite tipuri, cum
ar fi numere sau caractere, pentru a reprezenta informaţiile necesare
programului.
Unele date sunt prezente chiar “înainte” de utilizarea programului
şi păstrează aceste valori nemodificate. Astfel de date sunt denumite
constante.
Alte date se pot modifica, fie la aceeaşi execuţie a programului fie la
execuţii ulterioare, sau li se asociază valori, pe durata execuţiei programului.
Aceste date sunt denumite variabile.
Limbajele de nivel înalt permit utilizarea de nume
simbolice(variabile) pentru memorarea datelor, calculelor sau rezultatelor.
Să considerăm exemplul următor: programul determină vârsta,
calculată în numărul total de luni, pentru un utilizator care specifică vârsta
în ani şi luni.

#include < stdio.h >


main ( )
{
int ani, luni, ani_l ; / * se definesc aceste variabile * /
printf ( ”cati ani aveti ( ani , luni ) ? ” ) ; /* se solicita varsta in ani si
luni */
scanf (“%d %d”, & ani, &luni); /* citeste varsta */
ani_l =ani*12+luni; /* calculeaza varsta in luni */
printf (“varsta in luni:%d\n”,ani_l);
}

În acest exemplu variabilelor ani şi luni li se asociază diferite valori,


la fiecare execuţie a programului; variabilei ani_l i se asociază o valoare pe
durata execuţiei programului.
În acest exemplu se utilizează, pe lângă funcţia printf( ), pentru
tipărire, şi funcţia scanf(), utilizată pentru a citi valori de la tastatură.
Simbolul %d este specificator de conversie, utilizat pentru a citi un întreg de
la tastatură.
Pentru a citi valoarea, pentru unul din tipurile de bază, numerice,
trebuie precedat numele variabilei, căreia i se asociază valoarea, de simbolul

31
32 Programare C/C++
&. Dacă nu apare acest simbol, în faţa variabilei, atunci se va citi o valoare
reprezentând şirul de caractere introdus (bineînţeles, în acest caz, variabila
trebuie declarată de tip caracter).

3.1. Tipuri de date


Unele date pot fi numerice, altele litere, sau în general, caractere.
Calculatorul trebuie să identifice şi să utilizeze în mod adecvat aceste date.
În C sunt definite tipurile de date numerice (întregi sau reale) şi caracter.

3.1.1. Tipul întreg

Un întreg este o secvenţă de cifre precedate sau nu de semn. Nu sunt


permise spaţiile libere (blancuri) între cifre (nici, eventual, în loc de
zerouri). Aceste spaţii libere sunt considerate delimitatori (separatori) de
valori.
Pentru declararea unei variabile de tip întreg se pot folosi următoarele
abrevieri (sunt specificate, în plus, pentru fiecare tip, numărul de biţi şi
domeniul de valori pentru fiecare declaraţie):

Abreviere tip Număr de biţi pentru Domeniul de valori


reprezentarea internă
int , 16 -32768 / 32767
signed int
unsigned int 16 0 / 65535
short int , 8 -128 / 127
signed short int
unsigned short int 8 0 / 255
long int, 32 -2147483648 /
signed long int 2147483647
unsigned log int 32 0 / 4294967296

În funcţie de domeniul de valori întregi pe care le presupune că îl va


lua o variabilă, ea va fi asignată în mod corespunzător, la un anumit tip
întreg. Variabile întregi pot fi specificate şi în alte două baze, pe lângă cea
zecimală, utilizată de obicei, în octal sau hexazecimal .
Dacă prima cifră a valorii întregi este un zero, atunci întregul este
considerat ca fiind exprimat în baza opt (octal) .În acest caz, celelalte cifre
Programare C/C++ 33
ale valorii trebuie să fie valide pentru baza opt, adică trebuie să fie cifre în
intervalul 0 - 7 .
Pentru afişarea unei valori în octal, de către funcţia printf(),se
utilizează caracterele de formatare %o; afişarea se va face fără zero la
început (care este necesar doar la introducere). Pentru a afişa în acelaşi
format, adică cu un zero la început, se utilizează pentru acest format
simbolurile %#o .
Pentru a introduce date în hexazecimal valoarea respectivă trebuie
precedată de un zero şi litera x (mare sau mică); imediat după x se află
cifrele valorii în hexazecimal, care constau în cifrele de la 0 la 9 şi literele
de la A la F.
Pentru a afişa o valoare în hexazecimal se utilizează caracterele %x,
care vor afişa valoarea fără a fi precedată de 0x (ca la introducere). Pentru a
afişa valoarea în acelaşi format ca la introducere, adică precedate de 0x,
trebuie utilizate în formatul de tipărire caracterele %#x.

3.1.2.Tipul real

Acest tip corespunde numerelor reale din matematică; deci spre


deosebire de numerele întregi acestea conţin punctul zecimal .Aceste
numere pot fi exprimate în două forme: normală şi cu exponent. În prima
formă, cea normală, valorile sunt specificate prin partea întreagă, precedate
sau nu de semn, şi cea subunitară separate prin punct, de exemplu : -
175.357 .
În cea de-a doua formă numărul este reprezentat printr-o mantisă şi un
exponent, separate prin litera e (sau E). Mantisa poate fi un număr întreg sau
zecimal, precedat opţional de semn. Exponentul, precedat de litera e, şi care
este un număr întreg, cu sau fără semn, reprezintă puterea lui zece, cu care
se înmulţeşte mantisa.
Să considerăm aceeaşi valoare, reprezentată în acest mod :
-1.75357e + 2 (care înseamnă : -1.75357*102 = - 175.357)
sau -1753.57 e –1 (adică –1753.57 * 10-1 = - 175.357 )
Spre deosebire de numerele întregi a căror reprezentare internă, în
calculator, este exactă, reprezentarea internă a numerelor reale este
aproximativă; aproximarea este cu atât mai bună cu cât numărul de biţi pe
care se reprezintă mantisa acestuia este mai mare .
De exemplu, în cazul tipului float valorile sunt reprezentate intern pe
7-8 cifre, cele mai semnificative (primele 7-8 cifre) , în domeniul de valori:
[- 3.4e + 38 , -3.4e –38 ] U {0} U [ 3.4e –38 , 3.4e + 38 ]
Pentru a afişa o valoare reală, în forma cu exponent, de către funcţia
printf ( ), se utilizează caracterele %e. Dacă se utilizează caracterele %g,

33
34 Programare C/C++
funcţia printf ( ) va decide dacă numărul este afişat în forma normală, sau
cea cu exponent. Decizia se bazează pe valoarea exponentului: dacă acesta e
mai mic decât –4, sau mai mare decât 5, se utilizează formatul cu exponent
(%e), altfel se utilizează formatul normal (%f).
În afara formatului float, pentru reprezentarea numerelor reale se
mai pot utiliza tipurile double şi long double. Diferenţele între aceste tipuri
constau în numărul de biţi pe care se face reprezentarea internă, a mantisei şi
exponentului, şi vor corespunde la domenii diferite de valori, ca în tabelul
următor :
Reprezentarea internă (nr.
Abreviere biţi), nr. biţi pentru Domeniul de valori
mantisă / exponent şi
precizia
float 32 ( 23 / 8 ), 8 cifre exacte 3.4e –38 /3.4e+38
double 64 (52 / 11), 15 cifre exacte 1.7e-308/1.7e+308
long double 80 (64 / 15 ), 19 cifre exacte 3.4e-4932 / 1.1e+4932

Tipurile double şi long double sunt similare tipului float şi sunt


utilizate ori de câte ori precizia furnizată de acestea nu este suficientă.
Precizia furnizată de tipul float este de aproximativ 7, de tipul double de 15
iar de tipul long double de 19 cifre zecimale.
Constantele utilizate într-un program sunt, în mod implicit, asociate
tipului double, de către compilatorul C. Pentru a asigna o constantă tipului
float aceasta trebuie făcută în mod explicit, prin însoţirea numărului de litere
f sau F astfel :
12 .5f sau 12.5F
Pentru afişarea, de către funcţia printf ( ), a unei valori de tip double
se utilizează aceleaşi caractere ca pentru tipul float precedat de
modificatorul l, adică %lf, %le sau %lg, cu aceeaşi semnificaţie (se vor afişa
primele cifre semnificative ale numărului). În cazul formatului long double
se utilizează următoarele formate de tipărire (specificatori de conversie):
%Lf, %Le şi %Lg; primele două afişează valoarea de acest tip în format
normal şi respectiv, cu exponent, în timp ce cel de-al treilea lasă la
latitudinea funcţiei printf ( ) modalitatea de tipărire, normală sau cu
exponent, în funcţie de valoarea exponentului .
Pentru unele operaţii aritmetice, adunare sau scădere, de exemplu,
între un număr foarte mare şi unul mult mai mic, rezultatul suferă din punct
de vedere al preciziei, datorită numărului limitat de cifre pentru
reprezentarea mantisei. De exemplu pentru tipul float dacă diferenţa dintre
exponenţii celor două numere este mai mare de 8, rezultatul va fi egal cu cel
mai mare număr dintre cei doi operanzi:
Programare C/C++ 35
/ * demonstrarea erorii de rotunjire a rezultatului * /
# include < stdio.h >
main ( )
{
float a , b ;
a = 5.0e+8 + 1.0 ;
b = 5.0e+8 – 1.0 ;
printf ( “ a = %f \ n “ , a ) ;
printf ( “ b = %f \ n “ , b ) ;
}

Valorile tipărite, în acest exemplu, vor fi a = 5.0 e + 8 şi respectiv b


= 5.0 e + 8, deoarece pentru tipul float se reţin doar primele 7- 8 cifre
semnificative.
Pentru adunarea / scăderea a două numere, calculatorul egalează
exponenţi, la valoarea celui mai mare, prin deplasarea (diminuarea)
corespunzătoare a mantisei numărului mai mic, astfel ca valoarea acesteia
să nu fie afectată.
În acest caz prin egalizarea exponenţilor, unitatea care se adună /
scade se va scrie 0.00000001e+8, şi deci cifra 1 va ajunge în afara
numărului de cifre ce se reţin (reprezintă intern) pentru un număr, adică
primele 8 cifre, şi deci cifra 1 nu se adună / scade efectiv la numărul a
respectiv numărul b.
În astfel de situaţii se utilizează fie alt tip de real: double sau long
double, sau dacă nici aceste tipuri nu sunt suficiente, se utilizează algoritmi
speciali pentru aceste operaţii.

3.1.3.Tipul caracter (char)

O variabilă de acest tip (char) poate fi utilizată pentru a memora un


singur caracter. O constantă de tip caracter se reprezintă prin includerea
caracterului între apostrofuri.
De exemplu: ‘a’ ‘;’ ‘0’ sunt constante de tip
caracter.
Să nu se confunde o constantă de tip caracter, care este un simplu
caracter, scris între apostrofuri, cu un şir de caractere, care este reprezentat
de un număr oarecare de caractere, incluse între ghilimele.
Constanta caracter ’\n’- adică caracterul pentru linie nouă – este o
constantă validă, chiar dacă nu respectă regula anterioară. Motivul îl
constituie caracterul \ (backslash), care este un caracter special în C şi nu

35
36 Programare C/C++
este numărat ca un caracter. Nu este singurul este exemplu de acest tip;
acestea au fost descrise anterior şi sunt denumite secvenţe de evitare.
Caracterele sunt memorate, în calculator, sub forma unor coduri
numerice (binare); pentru reprezentarea internă a caracterelor se utilizează
codul ASCII (American Standard Code for Information Interchange),
reprezentat pe un octet (8 biţi), din domeniul de valori întregi 0-255. În acest
cod, de exemplu:
 litera A este reprezentată prin valoarea întreagă 65,
B- 66, ş.a.m.d.;
 litera a este reprezentată prin valoarea 97, b- 98,
ş.a.m.d, sau
 cifrele 0-9 prin valori întregi în intervalul 48-57.
Setul standard de caractere ASCII (litere mari şi mici sau cifre şi
caractere speciale) este reprezentat în intervalul de valori întregi 0-127;
domeniul de valori întregi 128-255 reprezintă setul extins de caractere
ASCII.
Pentru a afişa un caracter, asociat unei variabile de tip char, utilizând
funcţia printf ( ) se vor utiliza caracterele %c. Acest format de tipărire, %c,
specifică funcţiei printf ( ) să convertească întregul, corespunzător
variabilei, la caracterul corespunzător.
De exemplu, următorul program, afişează codul zecimal
corespunzător unei taste:
/* codtasta.c- afiseaza codul numeric ASCII pentru un caracter */
#include <stdio.h >
main ( )
{
char car ;
printf (“Apasati o tasta : \n “ ) ;
scanf (“%c, &car ) ; /* citeste codul tastei (caracterului) */
printf (“Codul tastei (caracterului ) \“%c\“ este %d (zecimal), %x”
“ (hexa) %o (octal)\n“, car, car , car, car);
}

În acest exemplu, variabila car este tipărită, mai întâi, ca un caracter


(utilizând %c) iar apoi ca un întreg zecimal, hexazecimal şi octal (utilizând
%d, %x, %o). Aceşti specificatori de conversie, %c, %d, %x şi respectiv %o
determină cum este afişată data, nu cum este ea memorată.
Pentru transmiterea unor caractere ASCII, ce nu pot fi tipărite ca în
cazul caracterelor obişnuite: litere, cifre, semne speciale, prin specificarea
caracterului între apostrofuri, se pot utiliza trei metode; prima constă în
utilizarea, aşa numitelor, secvenţe de evitare, descrise anterior:
Programare C/C++ 37
‘\a’ - semnal sonor (sonerie);
‘\b’ - spaţiu înapoi , etc.
O a doua metodă constă în utilizarea, întocmai, a codului ASCII cum
ar fi, de exemplu, pentru o avertizare sonoră (sonerie - beep) :
char beep = 7 ;
Cea de-a treia metodă constă în specificarea într-o formă specială a codului
ASCII:
- codul ASCII, reprezentat în octal, este precedat de \ şi inclus între
apostrofuri :
beep = ‘\007 ‘ ;
- se poate specifica codul ASCII şi în baza hexazecimal, în acelaşi
mod, dar semnul \ trebuie urmat de x sau X pentru a specifica că
valoarea este reprezentată în hexazecimal:
CR = ‘\x0A’ ; /* pentru linie nouă */
Iată un exemplu de utilizare a unora dintre aceste caractere
netipăribile:
/* escape.c - utilizare de caractere de tip 'escape '*/
# include < stdio.h >
main( )
{
float salariu ;
printf ("Specificati salariul lunar : ");
printf (" $ \b\b\b\b\b\b\b\b") ;
scanf ("%f", &salariu) ;
printf ( "\ n\t\t $%.2f pe luna=$%.2f pe an", salariu, salariu*12.0);
printf ( "\r Oho ! \n") ;
printf ( "\t\t\"\\\\\\\\\\\\\\\\\\\\\\\t\\\\\\\\\"\n" );
}

Pentru tipărirea diferitelor tipuri de date se utilizează, în funcţia


printf ( ), următorii specificatori de conversie (următoarele grupuri de
caractere):
Tip date Exemple Specificatori conversie
char ‘a’ , ‘\ n ‘ %c
short int -25 , 0xFF, 075 %hi, %hx, %ho
unsigned short int 15u , 0x7Fu , 077 u %hu, %hx, %ho
int -97, 0xFFE0, 0177 %i, %x, %o
unsigned int 12u, 100u, 0xFFu %u, %x, %o
long int 122L , 0xfffffL , %li, %lx, %lo
unsigned long int 073757 L %lu, %lx, %lo

37
38 Programare C/C++
122Lu , 0xfffffLu ,
07537Lu
float 12.33f, 3.1e–5F %f, %e, %g
double 12.34 , 3.1e - 5 %f, %e, %g (sau %lf,%le,
%lg)
long double 12.35e , 3.3e - 52 %Lf , %Le, %Lg

3.1.4. Tipul enumerare

Mai există şi un alt tip de constantă denumită constantă enumerare.


Tipul acesta permite să definim nume simbolice pentru a reprezenta
constante întregi. El se utilizează pentru a reprezenta mai sugestiv obiectele
din cadrul problemei ale căror valori sunt identificate printr-un număr finit
de nume simbolice, deci un număr finit de valori. De exemplu dacă în cadrul
unei probleme trebuie să ne referim la lunile unui an, în loc de a utiliza
codificarea numerică prin constante numerice din intervalul 1-12 putem
utiliza chiar numele acestor luni, sau prescurtări ale acestora.
Pentru a crea acest tip se utilizează cuvântul cheie enum, urmat de
numele asociat tipului enumerare şi lista de nume constante separate prin
virgule şi incluse între acolade:
enum nume_tip {lista_nume_constante }
Pentru o astfel de declaraţie compilatorul asociază fiecărei constante
enumerate o valoare întreagă începând de la 0.
Exemple :
enum luni {ian, feb, mar, apr, mai, iun, iul, aug, sept, oct, nov, dec }
/* valori asociate : ian =0 ,…. , dec = 11 */
enum spectru {alb , rosu , verde , galben , albastru }
/* valori asociate implicit : alb =0 ,…, albastru =4*/
În cazul în care dorim o anumită codificare numerică a constantelor
enumerate, diferită de cea implicită stabilită de compilator începând de la 0,
atunci se specifică valoarea respectivă ca în exemplul de enumerare
evitare.
enum evitare {bell=’\a‘, backspace =‘\b‘, tab=‘\t‘,
newline=‘\n‘,vtab=‘\v’,return=‘\r‘}
/* valorile asociate sunt cele specificate dupa semnul = */
Alte exemple:
enum nivele {scazut =100, mediu= 500, ridicat=1000};
Dacă dorim să asociem valori succesive, începând de la o anumită valoare,
vom proceda astfel:
Programare C/C++ 39
enum directie {sus, jos, stanga =10, dreapta};
/* valori asociate: sus=0 , jos=1 - implicit, stanga =10, dreapta=11 */
Identificatorii unei enumerări pot avea şi valori identice; de exemplu:
enum boolean {nu=0, fals=0, da=1, adevarat=1};
Identificatorii (numele) din enumerări diferite trebuie să fie distincţi;
valorile asociate pot să nu fie distincte.
Enumerările furnizează un mod convenabil de a asocia valori la nume,
o alternativă pentru #define, cu avantajul că valorile pot fi generate pentru
program.
Tipul enumerare este pentru uz intern; dacă dorim să citim o valoare
pentru rosu, de exemplu din enumerarea spectru, trebuie citită valoarea 1,
nu cuvântul rosu. Se poate totuşi citi şirul de caracter ”rosu”, iar programul
să o convertească la valoarea rosu. Întrucât constantelor enumerare li se
asociază valori întregi ele pot fi tipărite, de fapt valorile asociate lor;
de exemplu instrucţiunea:
printf (“alb=%d, verde=%d”,alb,verde);
va afişa : alb=0, verde=2
Dacă un obiect al unui anumit tip enumerare este asignat la altceva
decât una din constantele sale, sau la o expresie de alt tip, compilatorul
transmite un mesaj de atenţionare.
Tipurile caracter, întreg de orice dimensiune cu sau fără semn, şi
enumerarea sunt denumite, global, tipuri integrale. Tipurile float, double şi
long double sunt denumite tipuri flotante (în virgulă mobilă).

39
40 Programare C/C++

4. Operatorii limbajului C++

Operatorii îşi datorează numele faptului că ei realizează operaţii


asupra unor valori. În C, la fel ca în majoritatea limbajelor semnele +, -, *
şi / sunt utilizate pentru adunarea, scăderea, înmulţirea şi respectiv
împărţirea a două valori. Aceşti operatori, şi nu numai aceştia, sunt doar
câteva exemple evidente, şi sunt cunoscuţi ca operatori binari, deoarece ei
operează asupra celor două valori, sau doi termeni.
Din acest punct de vedere, adică după numărul de operanzi
prelucraţi, operatorii se împart în operatori: unari, binari şi ternari . Ordinea
relativă în care se succed operatorii şi operanzii lor determină împărţirea
operatorilor în operatori : prefixaţi , infixaţi şi postfixaţi.
O expresie este o formulă cu ajutorul căruia se calculează o valoare
prin aplicarea unor operatori asupra unor operanzi. Evaluarea unei expresii
se face într-o anumită ordine, determinată de prioritatea operatorilor.
În ceea ce priveşte expresiile acestea pot conţine : nume de
variabile, de funcţii, de tablouri, componentă a unei structuri, constante,
apeluri de funcţii, referinţe de tablouri sau structuri, sau combinaţii între
acestea utilizând diferiţi operatori. De asemenea în construcţia unei expresii
se pot utiliza parantezele, expresiile cuprinse între acestea mai fiind
denumite şi subexpresii, cu ajutorul cărora se pot construi expresii
complexe.

4.1. Prioritatea şi asociativitatea operatorilor

Fiecare operator are asociată o anumită prioritate, sau precedenţă.


Această prioritate (precedenţă) este utilizată pentru a determina cum este
evaluată o expresie, care conţine mai mulţi operatori. Operatorul cu
prioritatea cea mai mare este evaluat primul. Expresiile conţinând operatori
de aceeaşi prioritate sunt evaluate fie de la stânga la dreapta, fie de la
dreapta la stânga, în funcţie de operatorul respectiv. Această proprietate este
cunoscută sub numele de asociativitatea operatorului (sau asociere).
O expresie (un simbol), de orice tip, altul decât void (care înseamnă
“nimic” şi la care ne vom referi ulterior) care identifică un obiect este
denumită valoare-stânga. Un obiect este un termen general pentru o zonă de
Programare C/C++ 41
memorare de date care poate fi utilizată pentru a reprezenta valori. De
exemplu, memoria utilizată pentru a reţine o variabilă sau un tablou (vector)
este un obiect. Deci numele unei variabile sau unui vector, de exemplu, este
o valoare-stânga. Deci obiectul se referă la locaţia de memorie curentă
(conţinutul său) şi valoare-stânga este o etichetă utilizată pentru a identifica
acea locaţie de memorie .
Orice obiect care-şi poate modifica valoarea este denumit valoare-
stânga modificabilă sau valoare-dreapta deoarece este valoarea efectivă
desemnată de o variabilă, în timp ce adresa ei (a zonei de memorie asociată
variabilei) este o valoare-stânga. În C pot fi prelucrate ambele valori .
În concluzie o valoare-dreapta specifică o valoare proprie (efectivă)
a unui tip fundamental (int, float, double, char etc dar nu poate fi un tablou
sau funcţie), iar o valoare stânga fiind adresa (de memorie) a unei valori de
orice tip. Pe lângă aceste tipuri mai există tipul valoare-F, care reprezintă
referinţa către o funcţie.
În cazuri particulare, în C, o expresie poate să nu producă nici o
valoare, ca în cazul apelului unei funcţii fără rezultat (de tip void) .
În tabela următoare sunt prezentaţi operatorii utilizaţi în C, listaţi în
ordinea descrescătoare a precedenţei. Operatorii grupaţi împreună au
aceeaşi precedenţă, iar în coloana din dreapta este precizată asociativitatea
operatorilor:
Operatori Descriere Asociativitate
() Apel funcţie.
[] Referinţă la elementul unui vector. S -> D
-> selecţie Referinţă la adresa câmpului unei
indirectă structuri/uniuni.
. selecţie Referinţă la câmpul unei structuri
directă /uniuni
+,- Operatori unari ( de semn ) .
++, -- Operatori de incrementare /
! decrementare.
~ Negaţie logică . D -> S
* Complement faţă de 1 .
& Referinţă (adresare indirectă);
sizeof dereferenţiere.
( tip ) Determină adresa (referinţa unui
operand)
Dimensiunea unui obiect .
Conversie de tip (cast) .
*,/,% Înmulţire, împărţire, modulo (operatori S -> D
binari )

41
42 Programare C/C++
+,- Adunare, scădere (operatori binari ) S -> D
<< , >> Deplasare stânga , dreapta S -> D
< Mai mic . S -> D
<= Mai mic sau egal .
> Mai mare .
>= Mai mare sau egal .
== Egalitate S -> D
!= Inegalitate (diferit)
& “ŞI“ bit cu bit S -> D
^ “SAU exclusiv “ , bit cu bit S -> D
| “SAU“ , bit cu bit S -> D
&& ŞI logic S -> D
|| SAU logic S -> D
?: Operator condiţional D -> S
=
*= , /= ,
%= Operatori de atribuire cu variantele . D -> S
+= , -=
&= , ^= ,
|=
<<= , >>=
, Operator secvenţiere ( , ) S -> D

Operatorii aritmetici
Operaţiile uzuale de adunare, scădere, înmulţire şi împărţire se
realizează cu simbolurile folosite de toate limbajele pentru aceste operaţii :
+ , - , * şi /.
Primele două simboluri pot fi folosite şi ca operatori unari (adică cu
un singur operand) pentru a stabili semnul unui operand.
Operatorul / este utilizat atât pentru împărţire de valori reale cât şi
pentru valori întregi, în care caz câtul este trunchiat la un întreg, iar restul
împărţirii a doi întregi e furnizat de operatorul : % - operator
modulo.
Tot în această categorie intră şi operatorii de incrementare, respectiv
decrementare :
++ (increment)
-- (decrement)
care realizează incrementarea / decrementarea valorii operandului cu 1.
Operatorii pot fi utilizaţi în două moduri, diferenţiate după locul plasării
operatorului şi momentul incrementării, astfel:
Programare C/C++ 43
- modul prefixat, în care operatorul e plasat în faţa variabilei afectate; în
acest caz variabila e modificată înainte de utilizarea ei în expresie;
- modul postfixat, în care operatorul e plasat după variabila respectivă,
în acest caz variabila este folosită cu valoarea ei în expresia respectivă şi
apoi este modificată (incrementată /decrementată).
De exemplu, să considerăm o expresie în forma prefixată:
b =2*++a; // ordinea operaţiilor este următoarea:
1) se incrementează a; 2) se înmulţeşte a cu 2; 3) valoarea astfel obţinută
se atribuie lui b;
Dacă aceeaşi expresie o scriem în forma post fixată :
b = 2*a++; //ordinea operaţiilor va fi următoarea:
1) a*2 ; 2) b=a*2 ; 3) a=a+1;
Operaţiile de incrementare / decrementare pot fi realizate în mod
explicit sub forma:
a = a+1 pentru incrementare şi
a = a-1 respectiv pentru decrementare
Alte exemple:
y=2;
n=3;
s = ( y+ n-- )*6 ;
după această instrucţiune s = ( 2 + 3 ) * 6 = 30, iar n = 2.
Alte exemple: y=++x; adică: x=x+1; y=x;
y=x++; adică: y=x; x=x+1;
Nu este recomandabil să se încerce efectuarea de mai multe operaţii
simultan cu incrementarea / decrementarea; de exemplu funcţia:
printf (“%10d , %10d”num, num*num++)
poate afişa: 5, 25 sau 6, 25, în cazul num =5.
Compilatorul de C are libertatea în alegerea ordinii de evaluare a
argumentelor unei funcţii: care sunt primele evaluate. Această libertate
creşte eficienţa compilatorului dar poate produce necazuri dacă se
utilizează ca argument un operator de incrementare.
O altă sursă de erori este o declaraţie de forma :
a= n/2 +5 *(1+n++) ;
în care problema este că, compilatorul nu realizează operaţiile în aceeaşi
ordine pe care o aveţi în minte; adică ordinea de scriere (stânga ->
dreapta) ci în ordinea asociată operatorilor, adică depinde de compilator.
Aici se face în această ordine, dacă se mai pune o paranteză:
a=n/2 +(5* (1+n++) ), se va începe cu (1+n++) iar n-ul
utilizat în operaţia n/2 va fi acesta.
O altă sursă de erori este cazul următor :
n=3 ;
a=n++ +n++;

43
44 Programare C/C++
Valoarea lui n după această instrucţiune va fi sigur 5, dar valoarea pentru
a nu e sigură: se poate utiliza valoarea curentă a lui n în calculul lui a şi
apoi să-l incrementeze de două ori (a=6, n=5), sau se poate utiliza n-ul
curent, se incrementează n, se utilizează pentru al doilea n în expresie şi
apoi se incrementează n a doua oară (deci a=7 , n=5), sau orice altă
alegere.
Pentru a evita astfel de ambiguităţi trebuie evitate aceste situaţii :
1. Să nu se utilizeze operatorii de incrementare / decrementare pentru o
variabilă care apare de mai multe ori într-o expresie.
2. Să nu se utilizeze operatorii de incrementare / decrementare pentru o
variabilă care este parte de mai mult de un argument al unei funcţii;
(care apare în mai multe argumente).

Operatorii de atribuire
Există mai mulţi operatori de atribuire, care se grupează (asociază)
toţi de la dreapta la stânga. Operandul stâng este o valoare –stânga, iar
operandul drept este o expresie. Tipul rezultatului expresiei de atribuire
este tipul operandului (identificatorului) stânga. Rezultatul este valoarea
memorată în operandul stâng, după ce a avut loc atribuirea (adică după
evaluarea membrului drept).
Evaluarea unei expresii poate avea şi efecte laterale, concretizate
prin modificarea valorilor unei variabile care intră în cadrul expresiei ce se
evaluează şi este atribuită operandului stânga. De obicei, în celelalte
limbaje de programare, se vorbeşte despre efecte laterale dacă unul dintre
operanzi este un apel de funcţie, cu efecte laterale.
În C, atribuirea, care poate fi completată şi cu alţi operatori cu efect
de atribuire, oferă posibilitatea de a descrie mult mai compact, printr-o
singură expresie, secvenţe de prelucrări ce ar necesita mai multe
instrucţiuni de atribuire.
Acest avantaj, prezintă însă pericolul ca printr-un abuz de
compactare, facilitate oferită de C, să se scrie programe greu de înţeles şi
cu eventuale erori datorită conversiilor automate sau precedenţei şi
asociativităţi operatorilor.
Pentru a realiza această prelucrare trebuie specificat: adresa de
memorie afectată (valoare–stânga), şi expresia ce se evaluează (valoare-
dreapta) a cărei valoare se va memora la adresa specificată.
Pe lângă operatorul de atribuire, de bază, de forma
valoare_stânga = expresie
el mai poate fi completat şi cu alţi operatori, cu efect de atribuire, de
formele următoare:
valoare_stânga += expresie (adunare)
Programare C/C++ 45
valoare_stânga -= expresie (scădere)
valoare_stânga *= expresie (înmulţire)
valoare_stânga /= expresie (împărţire)
valoare_stânga %= expresie (modulo, rest împărţire întreagă)
valoare_stânga >>= expresie (deplasare dreapta)
valoare_stânga <<= expresie (deplasare stânga)
valoare_stânga &= expresie (ŞI logic, la nivel de bit)
valoare_stânga ^= expresie (SAU exclusiv, la nivel de bit)
valoare_stânga |= expresie (SAU logic, la nivel de bit)
O expresie de forma
valoare_stânga Op= expresie
unde Op este unul dintre operatori +, - , * , / ,% , >> ,<< , & , ^ , |, se
interpretează ca fiind echivalentă cu expresia:
valoare_stânga = valoare_stânga [Op] expresie
în care valoare_stânga este evaluată o singură dată.
De exemplu expresia:
x *= y+1 este echivalentă cu x = x * (y+1)
Pentru operatorii += şi -=, operandul stâng poate fi şi un pointer, în care caz
operandul din dreapta este convertit la întreg. Toţi operanzii din dreapta şi
cei din stânga, care nu sunt pointeri, trebuie să fie de tip aritmetic.
Se pot realiza şi atribuiri multiple:
var1=var2 =expresie
Ordinea de evaluare : expresie
var2 <- expresie
var1 <- var2

Operatorul de conversie (cast)


Când un operator are operanzi de diferite tipuri, ei sunt convertiţi la un tip
comun în concordanta cu anumite reguli. În general, conversiile automate
sunt acelea ce convertesc un operand “mai limitat” într-unul “mai larg
(mare)”, fără pierderea informaţiei, cum ar fi convertirea unui întreg la real.
Conversiile de tip respectă următoarele reguli:
1. Dacă într-o expresie apar tipurile char şi short, atât cu semn
cât şi fără (signed, unsigned) vor fi convertite automat la tipul int
sau dacă este necesar la tipul unsigned int (dacă tipul short are
aceeaşi dimensiune ca int, atunci unsigned short este mai mare ca
int şi în acest caz, unsigned short este convertit la unsigned int).
Deoarece acestea sunt conversii la tipuri mai mari, ele sunt numite
promovări.
2. În orice operaţie ce implică două tipuri, ambele valori sunt
convertite la tipul cu rangul “cel mai mare “ dintre cele două tipuri.

45
46 Programare C/C++
3. Rangurile (ordinea) tipurilor, de la cel mai mare la cel mai
mic sunt:
long double, double, float, unsigned long int, long int, unsigned int,
int.
O singură excepţie poate să apară, dacă tipurile long int şi int au
aceeaşi dimensiune; în acest caz tipul unsigned int este înaintea
tipului long int, în lista anterioară. Tipurile short şi char, care nu
apar în această listă, au fost deja promovate la tipul int (sau
unsigned int).
4. Într-o declaraţie de atribuire, rezultatul final al calculelor este
convertit la tipul variabilei căreia i se atribuie valoarea. Această
operaţie poate reprezenta o promovare, descrisa anterior, sau
retrogradare, adică valoarea este convertită la un tip de “rang mai
mic”. O conversie la un “rang mai mic” poate duce la depăşiri sau
pierderi de precizie. De exemplu, atribuirea unei valori de tip real
unei variabile de tip întreg, conduce la trunchierea părţii subunitare,
sau chiar la depăşire dacă partea întreagă a valorii reale este mai
mare decât domeniul valorilor tipului întreg.
5. Pe lângă aceste conversii automate (implicite), programatorul
poate să precizeze tipul conversiei dorite, în mod explicit. Această
metodă de utilizare explicită a unui operator de conversie este
denumită cast (a mula) şi constă în precedarea expresiei cu numele
tipului dorit, între paranteze, astfel: (tip) expresie.
De exemplu, dacă dorim să împărţim două numere întregi a şi b, iar
rezultatul real să nu fie trunchiat, atunci cel puţin unul din cei doi
operanzi, trebuie convertit explicit la tipul real ca în expresia:
(float) a/b.
Acest operator poate fi utilizat şi în operaţiile de atribuire, pentru a
evita depăşirile sau trunchierile menţionate anterior.

Operatori relaţionali
Aceşti operatori permit verificarea unor condiţii, care reflectă relaţia
dintre două valori ce sunt comparate. Rezultatul unei comparaţii este o
valoare de tip logic: adevărat sau fals. În C, însă, o valoare întreagă 0 este
interpretată ca având valoarea logică fals, în timp ce orice valoare întreagă
diferită de 0 este interpretată ca adevărat.
Cei 6 operatori relaţionali sunt :
< (mai mic)
< = (mai mic sau egal)
> ( mai mare)
> = (mai mare sau egal)
= = (egalitate)
Programare C/C++ 47
!= (inegalitate, diferit de ).
Observaţie: să nu se confunde folosirea operatorului de atribuire = în locul
celui de egalitate (sau invers). Regula de asociativitate pentru aceşti
operatori (adică evaluarea expresiilor în care apar) este de la stânga la
dreapta.

Operatori logici
Operatorii logici au ca operanzi, în mod normal, expresii relaţionale,
care pot avea una din cele două valori logice: false (0) sau true (o valoare
diferită de 0). Dat fiind, însă, codificarea valorilor logice prin valori întregi,
operatorii logici se pot aplica şi expresiilor de tip întreg, pe lângă cele
relaţionale.
Operatorii sunt:
&& ŞI logic
|| SAU logic
! Negaţie
Expresiile logice sunt de forma:
expr1 && expr2 –rezultatul este adevărat dacă ambele
expresii sunt adevărate (adică dacă ambele sunt diferite de 0);
expr1 || expr2 – rezultatul este adevărat dacă cel puţin una
din expresii este adevărată (adică cel puţin o expresie diferită de 0);
! expr – dacă expresia este adevărată rezultatul este fals, şi
respectiv invers (adică 0->1, şi o expresie diferită de zero va conduce la un
rezultat 0).
Operanzii nu trebuie să aibă în mod obligatoriu acelaşi tip, dar
fiecare trebuie să aibă unul din tipurile fundamentale sau pointer. Rezultatul
este întotdeauna de tip “int”. Evaluarea expresiilor de acest tip se face,
pentru aceşti operatori, de la stânga la dreapta şi este suspendată dacă
valoarea expresiei este cunoscută la un moment dat, după evaluarea doar a
unor termeni ai expresiei. Astfel dacă primul operand este 0 pentru
operatorul ŞI logic sau diferit de 0 pentru operatorul SAU logic al doilea
operand nu mai este evaluat deoarece rezultatul e deja cunoscut, 0 în primul
caz şi respectiv 1 în cel de-al doilea.
Deci evaluarea expresiilor logice, în C, este întreruptă în momentul
în care valoarea rezultatului devine certă, adică unul sau mai mulţi operanzi
nu mai sunt evaluaţi. Din acest motiv operanzii din expresiile logice trebuie
corect folosiţi, dacă au efecte laterale.
Deci dacă într-o astfel de expresie apare şi o atribuire, aceasta
trebuie făcută înaintea evaluării expresiei, pentru a putea fi siguri că se
efectuează. De exemplu condiţia ca “pătratul de latură l1“ cu colţul stânga –
sus (x1, y1) să acopere un “pătrat de latură l2“, cu colţul stânga – sus (x2,
y2)este:

47
48 Programare C/C++
( x1 < x2 & & y1 < y2 & & (d = l1- l2 ) > 0,
poate să nu actualizeze calculul lui d (dacă x1 > = x2 sau y1 > = y2 ), care
poate fi, însă, necesar, ulterior.

Operatorul condiţional
Este un operator cu trei operanzi (ternar ) şi se utilizează astfel:
expr1 ? expr2 : expr3
Se evaluează prima expresie (expr1) şi dacă ea este adevărată (sau
diferită de 0) rezultatul este valoarea expresiei expr2, altfel rezultatul este
expr3, adică:
dacă expr1! = 0
atunci rezultat = expr2
altfel rezultat = expr3
El se utilizează în situaţiile în care există două variante de obţinere
a unui rezultat, din care se selectează una singură. De exemplu, expresia:
z = ( a >b) ? a : b ;
calculează maximul dintre a şi b şi–l atribuie lui z. Întotdeauna numai una
dintre expresiile a doua şi a treia este evaluată.
Determinarea a valorii absolute a unui număr:
x = (y < 0) ? – y : y ;
Alt exemplu: tipărirea intervalului întreg în care se află un număr real:

#include <stdio.h>
main ()
{
float num;
int limita1, limita2
printf ( ”Introduceti un numar : \n “ )
scanf ( “%f “ , &num ) ;
limita1 = num ;
limita2 = (limita1 > = 0 ) ? limita1+1 : limita1 - 1;
(limita1>=0)? printf (“numarul este in intervalul %d - %d\n“, limita1,
limita2) :
printf (“numarul este in intervalul %d - %d\n“, limita2,
limita1);
}

Operatori la nivel de bit


C – ul oferă operatori logici şi de deplasare la nivel de bit. Spre
deosebire de celelalte operaţii logice, operaţiile logice la nivel de bit se
Programare C/C++ 49
aplică asupra fiecărui bit, individual, din reprezentarea operanzilor,
indiferent de valorile celorlalţi biţi. Operaţiile logice la nivel de bit care
admit numai operanzi de tip întreg sunt următoarele:
~ negaţie (complement faţă de 1 )
& ŞI logic, bit cu bit;
| SAU logic, bit cu bit;
^ SAU exclusiv, bit cu bit;
Operaţiile logice la nivel de bit: negaţie, ŞI logic, SAU logic(~ & |)
se realizează asemănător cu cele anterioare, dar la nivelul fiecărui bit, de pe
aceleaşi poziţii din cei doi operanzi. Pentru ultima operaţie, sau exclusiv (^),
fiecare bit al rezultatului are valoarea 1, dacă biţii corespunzători ai
operanzilor sunt diferiţi, altfel, dacă cei doi biţi sunt identici, rezultatul este
0.
Operatorul ŞI logic este utilizat, de obicei, în conjuncţie cu o mască
de biţi, care este o constantă binară cu anumiţi biţi 1, iar ceilalţi 0; în urma
acestei operaţii se selectează în variabila respectivă doar acei biţi pentru care
biţii corespunzători din mască sunt 1, toţi ceilalţi fiind puşi pe 0.
Tot în categoria operaţiilor la nivel de bit intră şi operaţiile de
deplasare, care realizează deplasarea logică la nivel de bit:
>> - deplasare la dreapta
<< - deplasare la stânga,
cu un număr de poziţii specificat de cel de-al doilea operand, primul
operand fiind cel ai cărui biţi sunt deplasaţi.
La deplasarea la stânga cu un bit, bitul cel mai semnificativ, deplasat
în afara operandului, se pierde, iar cel mai puţin semnificativ, devenit liber
prin deplasare, se completează cu 0. Rezultatul deplasării la stânga cu n biţi,
a unui număr întreg, este echivalent cu o înmulţire cu 2 n, dacă nu se
produce depăşire (adică la schimbarea semnului).
La deplasarea la dreapta bitul cel mai semnificativ se deplasează la
dreapta, dar se păstrează aceeaşi valoare pe poziţia sa (operaţie denumită şi
extensie de semn), iar cel mai puţin semnificativ, deplasat în afara
operandului, se pierde, deci deplasarea la dreapta este echivalentă cu
împărţirea la doi.
Exemplu: programul citeşte o valoare întreagă de la tastatură şi realizează
conversia de la întreg zecimal la binar, reprezentat ca un şir de caractere.

/*Converteste si afiseaza in binar o valoare intreaga*/


#include<stdio.h>
main()
{
char *int_la_str_bin(int,char[]);

49
50 Programare C/C++
char strbin[1+8*sizeof(int)];
int numar;
printf ("Programul o valoare intreaga, din zecimal in binar\n");
printf ("Conversia ia sfarsit cu o valoare nenumerica\n");
printf ("numar = ");
while (scanf ("%d", &numar) == 1)
printf ("Valoarea binara a numarului %d este %s\n numar = ",
numar, int_la_str_bin(numar,strbin));
}
char *int_la_str_bin ( n, ps )
int n;
char* ps;
{
int i;
static int dim = 8*sizeof ( int );
for ( i = dim-1 ; i >= 0 ; i--, n >>= 1 )
ps[i] = ( 1&n ) + '0';
ps[dim] = '\0';
return ( ps );
}

Alte exemple:
/* invers.c - functie pentru inversarea ultimilor biti dintr-un numar */
int inver_ultim (int numar , int n )
{
int masca = 0;
int val_bit = 1;
while (n -- > 0 )
{
masca |= val_bit;
val_bit <<= 1;
}
return ( numar ^ masca );
}
void main ( void )
{
int numar, nr_bit;
printf ( "Numarul caruia i se inverseaza ultimii 'n' biti:");
scanf ( "%d", &numar );
printf("Numarul de biti de inversat:");
scanf("%d", &nr_bit);
Programare C/C++ 51
printf("numarul cu ultimii %d biti inversati este: %d\n", nr_bit,
inver_ultim (numar, nr_bit ) );
}

/* programul care realizeaza conversia binar -> zecimal intreg */


#include <stdio.h>
main()
{
char car;
int val = 0;
printf (“Programul realizeaza conversia din binar la intreg
zecimal\n”);
printf (“Introduceti un numar binar:”);
scanf (“%c”, &car);
while (car == '0' || car == '1' )
{
val = val*2 + (car&1);
scanf (“%c”, &car);
}
printf (“valoarea zecimala (intreaga) este: %d\n”, val);
}

Operatorul sizeof
Acest operator returnează o valoare întreagă, ce reprezintă
dimensiunea în octeţi a operandului. Operandul poate fi o dată obiect
specificată, cum ar fi numele unei variabile, sau poate fi un tip. Dacă este un
tip operandul trebuie închis între paranteze, altfel nu este nevoie: sizeof
(tip); sizeof expresie sau sizeof variabilă.
Operandul lui sizeof nu este evaluat, în cazul expresiilor, deoarece
tipul operanzilor este cunoscut în etapa de compilare, când se determină
efectul acestui operator.

Operatorul de secvenţiere (virgula)


Mai multe expresii separate prin virgule vor fi examinate şi evaluate
de la stânga la dreapta, tipul rezultatului şi valoarea lui vor fi cel al ultimei
expresii. Operatorul se utilizează când prelucrarea presupune evaluarea a
două sau mai multe expresii. Deoarece toţi operatorii din C furnizează o
valoare, valoarea operatorului de secvenţiere, virgula, este aceea a expresiei
cea mai din dreapta. De exemplu următoarea declaraţie:
x = ( y = 3, ( z = ++y + 2 ) + 5 );

51
52 Programare C/C++
are următorul efect: atribuie lui y valoarea 3, apoi îl incrementează pe y la
valoarea 4 îi adună 2 şi atribuie valoarea 6 lui z; apoi adună 5 la z şi
rezultatul 11 îi este atribuit lui x.
De asemenea se mai utilizează acest operator pentru a include mai
multe informaţii într-o expresie de control a unei bucle for :
for (pas=2,f=0 ; f<100 ; pas*=2 ) f+=pas;
în acest caz se iniţializează pas şi f cu valorile 2 respectiv 0. Alt exemplu:
for ( i=0 , j=0 ; i!=10 ;++i, j-=10)
iniţializează i şi j cu 0 şi respectiv 100, înainte de a începe bucla şi
incrementează valoarea lui i cu 1 şi scade 10 din j, la fiecare execuţie a
ciclului (buclei).
Virgula este utilizată de asemenea în funcţie de context şi ca
separator nu numai ca operator; de exemplu folosirea virgulei în separarea
argumentelor unei funcţii:
printf (“%d%d\n”,f,s);
sau pentru a separa variabile de acelaşi tip.
Dacă virgula apare ca secvenţiator într-o listă de argumente ale unei
funcţii, atunci ea poate să apară numai între paranteze. De exemplu funcţia:
f( a,( b = 2 , b+3 ), c),
are trei argumente, dintre care al doilea argument este 5, iar variabila b va
avea valoarea 2, cea atribuită la apelul lui f(a,5,c).
Virgula utilizată pentru a separa argumentele într-un apel de funcţie,
sau nume de variabile într-o listă de declaraţii, este un separator sintactic de
entităţi şi nu este un exemplu de utilizare a operatorului virgulă.
Deoarece ca toţi operatorii din C furnizează o valoare, valoarea
operatorului virgula este aceea a expresiei cea mai din dreapta. Un alt
exemplu de utilizare a operatorului este:
while (I <100 ) sum+=data[i],++ i;
valoarea lui data[i] se adună la sum, şi apoi i este incrementat. Nu sunt
necesare acolade deoarece este numai o declaraţie ce urmează după while,
ce constă din două expresii separate de operatorul virgulă.

Operatorii de adresare
Aceşti operatori se utilizează pentru a obţine adresa unei entităţi,
deci o valoare-stânga. Operatorul unar * este operatorul de dereferenţiere;
expresia ce-l urmează trebuie să fie un pointer (adică o variabilă ce
memorează adresa), iar rezultatul este o valoare-stânga care se referă la
obiectul pe care-l referă expresia.
Acest operator tratează operandul său ca o adresă, face acces la ea şi
îi obţine conţinutul. De exemplu: y=*px;
Programare C/C++ 53
deci se atribuie lui y conţinutul de la adresa la care face referire px. El poate
fi folosit pentru obţinerea valorii entităţii pentru a o utiliza ca operand în
altă operaţie.
Operatorul unar & este operatorul ce furnizează adresa unui obiect
sau a unui pointer la obiectul respectiv. Operandul este o valoare-stânga, iar
rezultatul este un pointer la obiectul referit de valoarea-stânga.
Exemplu: int x, y;
int *px;/* declarare pointer la o valoarea de tip int */
şi să considerăm pentru aceste declaraţii secvenţa următoare:
px = &x; /* px = adresa (pointer) lui x */
y= *px; /* y = valoarea întreagă referită de px */
care este echivalentă cu: y=x;
Observaţie: dacă pointerul nu referă date de acelaşi tip cu variabila
căreia i se atribuie valoarea rezultă valori eronate, întrucât în funcţie de tipul
la care este asociat pointerul el va referi 1, 2, 4 etc. octeţi în funcţie de tipul
la care este asociat.
Acest operator, &, poate fi aplicat numai la variabile (ce nu pot fi de
tip register), dar nu admite ca operand expresii sau constante. El poate fi
utilizat pentru transmiterea argumentelor de tip adresă (referinţă), în funcţii.
Rezultatul unei operaţii de adresare (referinţă) este interpretat, în
funcţie de context, ca valoare-stânga (dacă este primul operand dintr-o
atribuire) sau ca valoare-dreapta. În declaraţia px = &x; operatorul &
returnează o valoare-stânga respectiv adresa lui x, dar pentru px reprezintă o
valoare-dreapta. Pointerii sunt utilizaţi deoarece, de obicei, conduc la un
cod mult mai compact şi mai eficient decât s-ar obţine altfel.
O declaraţie de forma: pointer ptr; nu este modul normal de a
declara un pointer, deoarece trebuie specificat, pentru referirea de variabile,
tipul de variabilă ce este referit de pointer. Motivul este că diferite tipuri de
variabile ocupă diferite dimensiuni de memorie (număr diferit de octeţi), iar
operaţiile cu pointeri necesită cunoaşterea dimensiunii de memorie, adică ce
tip de date sunt memorate la adresele respective. Chiar şi pentru date ce
ocupă aceeaşi dimensiune, de exemplu float şi long int (32 de biţi),
reprezentarea internă a valorilor este diferită şi deci şi interpretarea trebuie
să fie diferită.
Să considerăm următorul exemplu:

int x = 1, y = 2, z[10] = { 10, 20 };


int *ip, *iq;
……………
ip = &x; /* ip referă acum x */
y = *ip; /* y este acum 1 */

53
54 Programare C/C++
*ip = 0; /* x este acum 0 */
ip = &z[0]; /* ip referă acum z[0] */
*ip = *ip +10;/* se adună 10 la elementul z[0], deci z[0]=10 +10*/
y = *ip +1;/* y ia valoarea referită de ip (z[0]), adică y=20 + 1 */
*ip+ = 1; /* este echivalentă cu ++*ip sau (*ip) ++, adică
valoarea referită de ip este incrementată cu 1, deci ip va
referi z[1]*/

Parantezele (*ip) sunt necesare deoarece, fără ele, expresia


incrementează ip, în loc să incrementeze valoarea referită de ip, deoarece
operatorii unari * şi ++ se asociază de la dreapta la stânga. Se pot face
atribuiri între pointeri: iq = ip;
Alt exemplu:

#include <stdio.h>
main()
{
int i1, i2 ;
int *p1, *p2;
i1=5;
p1 = &i1; /* pointer la i1, la valoarea intreaga 5 */
i2 = *p1/2 + 10; /* 5/2 + 10 = 12, deoarece *p1 este de tip int*/
p2 = p1; /* pointerului p2 i se atribuie aceeasi valoare cu p1
*/
printf("i1 = %i, i2 = %i, *p1 = %i , *p2 = %i \n", i1, i2, *p1, *p2 );
printf ("p1 = %p, p2 = %p\n", p1, p2 );
/* tiparire continut variabile pointer p1 si p2 */
printf ("p1 = %x, p2 = %x\n", p1, p2 ); /* tipareste aceleasi adrese */
}

Pe lângă cei doi operatori, descrişi anterior, mai există şi următorii operatori
de adresare:
 indexare, reprezentat de [ ], şi care se utilizează pentru a referi un
element, cu indicele specificat, dintr-un tablou furnizat ca prim
operand:
tablou [expresie_indice],
unde tablou este expresie de tip “pointer la tablou”, iar
expresie_indice este o expresie de tip întreg.
 selecţia directă reprezentată de punct . şi care se utilizează pentru a
adresa un câmp (componentă) dintr-o structură sau uniune sub forma:
nume_structură . selector. Această operaţie furnizează câmpul cu
Programare C/C++ 55
numele “selector” din structura obţinută în urma evaluării primului
operand.
 selecţia indirectă reprezentată de ->, şi care permite accesul la un
câmp dintr-o structură sau uniune, a cărei adresă este furnizată de un
pointer: pointer -> selector.
Această expresie este echivalentă cu:
(* pointer). selector.
Pentru operatorul de indexare s-a prezentat un exemplu, iar pentru
cel de selecţie se vor prezenta în capitolul referitor la structuri.

55
56 Programare C/C++

5. Instrucţiuni

Utilizând declaraţii, definiţii şi expresii se pot construi instrucţiuni.


Într-un program C instrucţiunile se execută secvenţial, în afară de situaţiile
în care se indică altfel.
O instrucţiune poate fi scrisă, continuată pe mai multe linii, iar pe o
linie pot fi scrise mai multe instrucţiuni; de obicei, însă, pe fiecare line se
scrie o singură instrucţiune, pentru claritatea programului şi pentru a permite
urmărirea şi modificarea, cu uşurinţă a acestora. Pentru terminarea unei
instrucţiuni se utilizează simbolul ";".
De asemenea, instrucţiunile incluse în alte structuri sunt scrise
decalat spre dreapta, faţă de liniile anterioare. Această modalitate de scriere,
realizată prin introducerea unui număr de spaţii libere (se utilizează tasta
TAB), se numeşte indentare a programului. Această modalitate valorifică
libertatea permisă de C, în ceea ce priveşte plasarea textului pe linii. La
terminarea unei structuri, pentru revenirea la aliniamentul anterior se
utilizează tasta "BACKSPACE".
Indentarea pentru scrierea programului se utilizează pentru a mari
claritatea, şi pentru a pune în evidenţă, grafic, structurile sintactice şi logice
ale programului. Includerea unităţilor sintactice unele în altele, sugerată de
deplasarea spre dreapta a unităţilor sintactice incluse, faţă de cele
înconjurătoare corespunde şi structurii logice a nivelurilor de abstractizare
în cadrul prelucrării.
Pentru a asigura o bună documentare a programelor, necesară atât
pentru înţelegerea lor cât şi pentru depanare sau modificare, este necesar ca
acestea să fie însoţite de comentarii.
De asemenea, în cazul ciclurilor incluse, pentru cele cu multe instrucţiuni,
pentru a nu pierde şirul înlănţuirilor, se recomandă marcarea ciclurilor, adică
scrierea de comentarii la începutul şi sfârşitul fiecărui ciclu. Pentru funcţii
se recomandă ca la începutul şi sfârşitul lor să se scrie comentarii privind
prelucrările efectuate de acestea precum şi pentru a preciza rolul fiecărui
parametru formal.
Se recomandă utilizarea de spaţii libere între operatori şi operanzi .
În C există următoarele instrucţiuni:
- instrucţiunea vidă;
- instrucţiunea expresie;
- instrucţiunea compusă (blocul);
- instrucţiuni condiţionale (decizie - "if"; selecţie - "switch");
- instrucţiuni de ciclare (while, for, do);
- instrucţiuni de întrerupere a execuţiei ciclice (break);
Programare C/C++ 57
- instrucţiunea continue ;
- instrucţiune de salt (goto);

5.1. Instrucţiunea vidă

Instrucţiunea vidă este ";" şi se utilizează nu numai pentru a separa


instrucţiunile, dar şi în cazul instrucţiunilor care nu necesită nici o
prelucrare (de exemplu: else;) în instrucţiunile compuse, sau pentru a
introduce un corp nul într-o instrucţiune de ciclare, care cere un corp, cum
ar fi while sau for.
Exemplu:
for ( nc = 0 ; getchar( ) != E0F ; ++nc ) ;
această instrucţiune numără caracterele unui text introdus de la tastatură;
corpul lui for este vid deoarece totul se face în partea de test şi actualizare;
dar sintaxa sa cere un corp. Alte exemple de instrucţiuni vide:
if (expr) ; sau if ( ! expr)
else instrucţiune;
instrucţiune; else;

5.2. Instrucţiunea expresie

Cele mai multe instrucţiuni din cadrul unui program C sunt instrucţiuni
expresie. O expresie devine instrucţiune dacă expresia este urmată de punct
şi virgula:
expresie;
unde expresia evaluată poate avea efect lateral, deci este reprezentată, de
regulă, de o atribuire sau un apel de funcţie. De exemplu:
x = 0;
i++;
printf (….);
scanf (….);
sau o atribuire cu efect lateral:
y = x++;
Pentru a marca sfârşitul unei instrucţiuni se utilizează ";".

5.3. Instrucţiunea compusă (blocul)

57
58 Programare C/C++

Sintaxa limbajului impune, în numeroase situaţii, prezenţa unei


singure instrucţiuni. Dacă însă prelucrările ce trebuie efectuate cer utilizarea
mai multor instrucţiuni, pentru a rezolva o astfel de situaţie se utilizează o
instrucţiune compusă (bloc).
Instrucţiunea compusă (blocul) este o grupare de declaraţii şi
instrucţiuni incluse între acolade, de forma :
{
declaraţii şi definiţii
instrucţiuni
}
Dacă sunt prezente şi declaraţii şi definiţii de variabile, acesta sunt
locale acestui bloc, cum se întâmplă în cazul funcţiilor.
Dacă anumiţi identificatori din lista de declaraţii au fost declaraţi
anterior, atunci declaraţia exterioară este salvată pe durata blocului, după
care îşi reia sensul său.
Cel mai simplu program C conţine cel puţin un bloc, reprezentând
corpul funcţiei main().
Orice iniţializare pentru variabile auto şi register se efectuează la
fiecare intrare în bloc. Iniţializările pentru variabilele static se execută o
singură dată la începutul programului. În interiorul unui bloc, declaraţiile
externe nu rezervă memorie, deci nu pot fi iniţializate.
Observaţie: după acolada dreapta, de sfârşit a blocului poate lipsi caracterul
punct şi virgulă .

5.4. Instrucţiuni condiţionale

O instrucţiune condiţională permite alegerea pentru execuţie a unei


prelucrări dintre două sau mai multe prelucrări posibile.
În cazul în care alegerea se face între două sau mai multe alternative,
instrucţiunea se numeşte de decizie, iar dacă alegerea se face între mai multe
alternative, instrucţiunea se numeşte de selecţie.

5.4.1. Instrucţiunea de decizie (if)


Programare C/C++ 59
Instrucţiunea face selecţia între cele două alternative pe baza
evaluării unei expresii logice, numită şi condiţie (sau predicat). Forma
generala a unei instrucţiuni este :
if (expresie)
instrucţiunea_1;
else
instrucţiunea_2;
Execuţia acestei instrucţiuni începe prin evaluarea expresiei, dacă ea
este adevărată, deci diferita de zero se execută instrucţiunea_1, iar dacă
expresia este falsă, adică are valoarea zero, se execută instrucţiunea_2, dacă
există clauza else.
Reamintim că limbajul C nu operează cu tipul logic folosind, însă, în
acest scop codificările numerice:
0 - pentru fals, şi
0 - pentru adevărat.
Instrucţiunea poate avea şi forma simplificată, în care nu apare
clauza else, dacă pentru aceasta nu se execută nici o prelucrare:
if (expresie)
instrucţiune;
Dacă pentru vreuna din alternative se realizează mai multe
prelucrări (instrucţiuni), întrucât sintaxa nu permite decât o instrucţiune se
va utiliza, în aceste situaţii, instrucţiunea compusă (blocul), delimitată de
acolade :
if (expresie)
{
listă de instrucţiuni
}
else
{
listă de instrucţiuni
}
Uneori pot să apară şi formulări ambigue ale instrucţiunii if, de exemplu:
if (expresie_1)
if (expresie_2)
instrucţiune_2;
else
instrucţiune_1;
La care dintre instrucţiunile if se va referi la clauza else ? Din alinierea
textului se poate presupune că utilizatorul dorea ca instrucţiune_1 să se
execute la primul if. Sintaxa limbajului rezolvă, însă, această ambiguitate
asociind clauza else ultimei instrucţiuni if întâlnite. Deci pentru a realiza

59
60 Programare C/C++
prelucrarea presupusă de utilizator se poate proceda în una din următoarele
forme:
a) if (expresie_1) b) if (expresie_1)
if (expresie_2) {
instrucţiune_2; if (expresie_2)
else ;
instrucţiune_2;
else }
instrucţiune_1; else
instrucţiune_1;
Pentru a selecta mai mult de două alternative posibile se poate
adăuga declaraţia if la clauza else conform sintaxei:
if (expresie_1) echivalent cu : if (expresie_1)
instrucţiune_1; instrucţiune_1;
else if (expresie _2) else
instrucţiune_2; if (expresie_2)
else if (expresie_3) instrucţiune_2;
……… else
else if (expresie_n) if (expresie_3)
instrucţiune_n; ……………
else else
instrucţiune_n+1; if (expresie_n)
instrucţiune_n;
else
instrucţiune_n+1;
Prima formă, construită diferit faţă de cea de-a doua, utilizată
frecvent, este referită ca o construcţie (instrucţiune) else if .
Expresiile sunt evaluate în ordinea în care apar; dacă este întâlnită o
expresie "adevărată", atunci se execută instrucţiunea asociata ei şi astfel se
termină întregul lanţ else if .
Observaţie : deoarece if testează valoarea numerică a expresiei, faţă de zero,
este suficientă forma :
if ( expresie ) sau if (! expresie )
în loc de formele:
if ( expresie != 0 ) sau if ( expresie == 0 )
La cea de-a doua formă trebuie inversate instrucţiunile ce se execută
pentru cele două cazuri, pentru a fi echivalentă cu forma clasică.
Exemple:
/* programul determina daca un numar este par sau nu */
#include <stdio.h>
main()
Programare C/C++ 61
{
int numar, rest;
printf ("Introduceti numarul:");
scanf ("%d", &numar);
rest = numar % 2;
if (rest )
printf ("Este numar impar\n");
else
printf ("Este numar par\n");
}

/* determinarea valorii unei functii, definite pe intervale intr-un punct


dat, x */
#include <stdio.h>
#include <math.h>
main()
{
float x, f;
printf ("Se calculeaza valoarea functiei f(x), definita astfel:\n");
printf ("f(x) = x^2 - 2*x + 5 , pt. x<= 1\n");
printf (" = x*sin(x) - 2 , pt. 1<x<=5\n");
printf (" = ln(2*x^2 + 1) - 5 , pt. x>5\n");
printf ("Introduceti valoarea lui x:");
scanf ("%f", &x);
if ( x <= 1 )
f = pow(x,2) - 2*x +5;
else if ( x <= 5 )
f = x*sin(x) -2;
else
f = log(2*pow(x,2) + 1 ) - 5;
printf ("f(%.2f)=%.3f", x, f);
}

/* Program de rezolvare a ecuatiei de gradul II */


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

61
62 Programare C/C++
double a, b, c, delta, real, imag;
printf ("Introduceti coeficientii ecuatiei de gradul al doilea:\ "
"\n\t\t ( ax^2+bx+c=0 )\n a=");
scanf ("%lf", &a);
printf ("b=");
scanf ("%lf", &b);
printf (" c=");
scanf ("%lf", &c);
if (a)
{
delta = pow (b,2) - 4*a*c;
real = -b/2/a;
imag = sqrt(fabs(delta))/2/a;
if (delta >= 0)
printf ("X1=%5.2lf , X2=%5.2lf\n", real-imag,
real+imag);
else
printf ("X1=%5.2lf-i*(%5.2lf), X2=
%5.2lf+i*(%5.2lf)\n",
real, imag, real, imag);
}
else
if(b)
printf ("Ecuatia de grad I cu solutia X=%5.2lf\n"
, -c/b);
else
printf ("Ecuatie degenerata\n");
}

Observaţie: în cazul în care b2 >> 4ac valoarea lui delta este


aproximativ egală cu b2, care poate conduce la obţinerea unei rădăcini zero,
în locul uneia cu o valoare foarte mică. În această situaţie se poate ajunge
deoarece numai primele n cifre semnificative sunt reţinute (n-depinde de
reprezentarea internă: 7-8 cifre pentru formatul float, 15 cifre pentru double,
19 cifre pentru long double); efectuând o scădere între două numere care au
primele n cifre identice (în reprezentarea internă) se va obţine zero.
Pentru a evita această situaţie este preferabil să se calculeze rădăcina
cea mai mare (o vom nota X1), iar cea mai mică se poate obţine astfel:
X2=c/(a*X1)
O altă observaţie se referă la cazul în care unei variabile vrem să-i
atribuim o valoare logică adevărat sau fals (bineînţeles codificată numeric:
Programare C/C++ 63
0 sau =0) după cum o anumită condiţie este îndeplinita sau nu. În locul
utilizării instrucţiunii de decizie if:
if (condiţie)
var_logica =1;
else
var_logica=0;
se poate utiliza o atribuire de forma:
var_logica = condiţie;

5.4.2. Instrucţiunea de selecţie (switch)

Instrucţiunea de selecţie switch este o generalizare a celei de decizie,


if; ea permite selecţia uneia sau mai multor alternative, dintre mai multe
posibile, în funcţie de valoarea unei expresii de tip întreg (sau caracter),
denumită şi selector. Formatul general este:
switch (expresie)
{
case eticheta_1: prelucrare_1; break;
case eticheta_2: prelucrare_2; break;
……………………………..
case ericheta_n: prelucrare_n; break;
default: prelucrare_d; break
}
Etichetele eticheta_i (i = 1,…, n) trebuie să fie constante sau
expresii cu valoare constantă, de tip întreg sau caracter, şi trebuie să fie
distincte între ele.
Eticheta default şi prelucrarea asociată ei sunt opţionale; în cazul în
care este utilizată corespunde mulţimii etichetelor posibile, diferite de cele
menţionate explicit în instrucţiunea de selecţie.
Prelucrările reprezintă secvenţe de instrucţiuni, dar pot fi şi vide.
Dacă sunt mai multe
instrucţiuni asociate unei etichete case, ele nu trebuie incluse între acolade.
Execuţia acestei instrucţiuni începe cu evaluarea expresiei dintre paranteze a
cărei valoare este apoi succesiv comparată cu valorile etichetelor eticheta_i.
În cazul în care este găsită o etichetă eticheta_i egală cu valoarea expresiei,
atunci se execută prelucrare_i, ce este asociată acestei etichete.
Declaraţia break semnalează sfârşitul unui anumit caz (case) şi
determină terminarea instrucţiunii de selecţie. Deci în continuare se va
executa următoarea instrucţiune ce urmează după cea de selecţie. Declaraţia
break este opţională; dacă ea lipseşte se va continua execuţia programului cu

63
64 Programare C/C++
următorul caz (case) şi aşa mai departe până la întâlnirea primei declaraţii
break sau dacă nu există până la sfârşitul instrucţiunii de selecţie.
Dacă nici o etichetă case nu este egală cu valoarea expresiei şi dacă
există prefixul default, atunci se execută prelucrare_d asociată acestuia,
altfel nu se execută nici o instrucţiune din switch.
De fapt forma generală a instrucţiunii de selecţie poate fi exprimată
cu instrucţiunea de decizie if în mod echivalent, astfel (echivalentă structurii
instrucţiunii case cu instrucţiunea break):
if (expresie == eticheta_1)
{
prelucrare_1
}
else if (expresie == eticheta_2)
{
prelucrare_2
}
…………………………….
else if (expresie == eticheta_n)
{
prelucrare_n
}
else
{
prelucrare_d /* echivalentă cu default */
}
În mod asemănător se poate exprima şi forma fără declaraţia break, a
instrucţiunii de selecţie, dar se obţin notaţii mult mai lungi:
if (expresie == eticheta_1)
{
prelucrare_1
}
if (expresie == eticheta_1 || expresie==eticheta_2)
{
prelucrare_2
………………………….
if (expresie == eticheta_1||……||expresie == eticheta_n)
{
prelucrare_n
}
prelucrare_d;
Exemple :
Programare C/C++ 65
/* Numara vocalele dintr-un text introdus de la tastatura */
#include <stdio.h>
void main()
{
int nr_a, nr_e, nr_i, nr_o, nr_u; /* contoare pentru numarul de vocale
*/
char car;
nr_a = nr_e = nr_i = nr_o = nr_u = 0;
printf ("Introduceti un text care se termina cu #\n");
while ( ( car = getchar() ) != '#' )
{
switch (car)
{
case 'A':
case 'a': nr_a++;
break;
case 'E':
case 'e': nr_e++;
break;
case 'I':
case 'i': nr_i++;
break;
case 'O':
case 'o': nr_o++;
break;
case 'U':
case 'u': nr_u++;
}
}
printf ("Numarul de vocale\n A, E, I, O, U este:\n"
"%5d %5d %5d %5d %5d\n",nr_a, nr_e, nr_i, nr_o,nr_u);
}

Observaţie: dacă dorim să determinăm numărul de linii care încep cu


o vocală va trebui să citim doar primul caracter din linie, iar restul liniei să-l
ignorăm:
while ( ( car = getchar () ) != ‘#’ ) /* se va lua primul caracter */
{
/* urmează aceeaşi secvenţă ca la exemplul anterior */
while ( getchar () != ‘\n’ ); /* se ignoră restul liniei */
/* această buclă citeşte caractere până la, inclusiv, caracterul ‘linie nouă’,

65
66 Programare C/C++
generat de tasta ENTER sau RETURN */
}
/* caracterul de sfârşit ‘#’ se va introduce la începutul unei linii */

/* Program pentru calculul datei zilei urmatoare pe baza datei curente


data este data prin: an, luna, zi;daca data zilei precedente nu este
ultima zi din luna se incrementeaza doar ziua si se obtine data
curenta.; daca este ultima zi din luna se actualizeaza ziua cu 1 si se
incrementeaza luna, daca nu este ultima din an;
daca este ultima din an se initializeaza si luna cu 1 si se
incrementeaza anul. Ultima zi dintr-o luna depinde de tipul
(numarul) lunii, iar daca este luna a 2-a depinde de tipul
anului ( bisect sau nu ) */
#include <stdio.h>
void main()
{
int zi, luna , an, ultima_zi;
/*citeste data zilei precedente si pune in ultima_zi data ultimei zile din
luna:*/
printf("\tIntroduceti data curenta (zi/luna/an):\n");
scanf("%d %d %d”, &zi, &luna, &an);
switch (luna)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12: ultima_zi=31; break;
case 4:
case 6:
case 9:
case 11: ultima_zi=30; break;
case 2: if((an % 4 == 0 && an % 100 != 0) || (an % 400 == 0))
ultima_zi=29;
else
ultima_zi=28;
}
/*calculeaza data zilei urmatoare*/
if (zi == ultima_zi)
Programare C/C++ 67
{
zi=1;
if(luna == 12)
{
luna=1;
an++;
}
else
luna++;
}
else
zi++;
/*scrie rezultatul obtinut*/
printf("Data curenta(zi/luna/an) este: %2d-%2d-%4d\n",zi,luna,an);
}

Programul poate fi simplificat (redus) dacă se elimină cazurile (case)


1, 3, 5, 7, 8 , 10 şi 12 şi se introduce clauza default: ultima_zi = 31;

5.5. Instrucţiuni de ciclare


Pentru obţinerea repetată (ciclică) sau iterativă a unor prelucrări,
care apar frecvent în algoritmi, se utilizează instrucţiunile ciclice.
Prelucrările ciclice ce trebuie efectuate constituie corpul ciclului. Un ciclu
se termină după un număr finit de repetări ca urmare a neîndeplinirii unei
condiţii (test). După momentul în care are loc testarea acestei condiţii, faţă
de corpul ciclului, instrucţiunile de ciclare se împart în două clase :
- cicluri cu test la început (test iniţial);
- cicluri cu test la sfârşit (test final).
Încheierea execuţiei unui ciclu, denumită şi ieşirea din ciclu, deci a
prelucrărilor descrise în corpul acestora, se poate face în două moduri:
- ieşire normală, care se datorează modificării condiţiei testate de ciclu în
corpul acestuia în sensul neîndeplinirii ei;
- ieşire forţată prin utilizarea în corpul ciclului a unei instrucţiuni speciale,
care întrerupe execuţia ciclului.
Iniţializările datelor din corpul ciclului sunt realizate înainte de a
începe instrucţiunea de ciclare. Există trei instrucţiuni de ciclare dintre care
două cu test la început (while şi for) şi una cu test la sfârşit (do-while).

67
68 Programare C/C++
Condiţia de testare, pentru oricare dintre cicluri, este reprezentată de o
expresie cu valoare de tip întreg interpretată ca valoare logică astfel :
- adevărat, adică condiţie îndeplinită, dacă valoarea expresiei este diferită de
zero; în acest caz se execută instrucţiunile din corpul ciclului, după care se
evaluează din nou expresia de testat;
- fals, adică condiţie neîndeplinită, dacă valoarea expresiei este zero, şi care
are ca efect încetarea (normală) a execuţiei instrucţiunii de ciclare.
O diferenţă majoră intre cele două tipuri de cicluri este aceea că ciclurile cu
test iniţial pot să nu se execute nici o dată, dacă condiţia de testat nu este
îndeplinită încă de la început, în timp ce ciclul cu test la sfârşit se execută
cel puţin o dată, întrucât se execută corpul ciclului şi apoi se testează
condiţia de sfârşit de ciclu.

5.5.1. Instrucţiunea while

Sintaxa acestei instrucţiuni este:


while (expresie) instrucţiune;
Efectul acestei instrucţiuni constă în evaluarea repetată e expresiei şi
execuţia repetată a instrucţiunii cât timp (while) valoarea expresiei este
diferită de zero (deci adevărată). Execuţia ia sfârşit în momentul în care
valoarea expresiei este zero (fals); dacă expresia are valoarea fals de la
prima evaluare a expresiei, atunci instrucţiunea nu se execută deloc.
Sintaxa instrucţiunii while prevede execuţia repetată a unei singure
instrucţiuni; dacă dorim repetarea mai multor instrucţiuni, acestea vor fi
descrise ca o instrucţiune compusă (bloc), utilizând în acest scop includerea
lor între paranteze.
Pentru a evita repetarea infinită a unui ciclu condiţia de testat trebuie
aleasă cu grijă; să considerăm, de exemplu, secvenţa care elimină multiplii
lui 2 dintr-un număr întreg :
while ( numar % 2 == 0 )
numar = numar/2; /* sau: numar /= 2; */
în acest exemplu expresia se modifică în sensul terminării ciclului, cu
excepţia cazului în care iniţial numar=0, situaţie în care ciclul se va repeta
la infinit. Pentru a evita această situaţie vom modifică condiţia astfel:
while ( numar != 0 && numar %2 == 0 )
numar = numar/2; /* numar/=2; */

Exemple:

#include <iostream.h>
Programare C/C++ 69
/* programul citeste un caracter si in functie de acesta se termina
sau se executa ciclic;
daca caracterul este ‘d’, ‘D’, ‘n’ sau‘N’ se executa ciclic pana se
introduce un
caracter diferit de acestea; le terminarea programului se emite
un sunet
*/

void main(void)
{
int gata = 0;
char litera;

while (! gata)
{
cout << "\nTasteaza D sau N si apoi Enter pentru a continua:
";
cin >> litera;
if ((litera == 'D') || (litera == 'd') || (litera == 'N') || (litera ==
'n'))
gata = 1;
else
cout << '\a'; //Sunet pentru altfel de caracter
}
cout << "Litera tastata a fost " << litera << endl;
}

/*Programul determina cel mai mare divizor comun pentru doua


numere a,b pozitive */
#include<stdio.h>
main()
{
int a, b, rest;
printf ("Introduceti doua numere intregi:\n");
scanf ("%d %d”, &a, &b);
while (b)
{
rest = a % b;
a = b;
b = rest;
}

69
70 Programare C/C++
printf ("Cel mai mare divizor este: %d\n", a);
}

/*Media valorilor pozitive dintr-un sir de valori ce se termina cu 0 */


#include<stdio.h>
main()
{
double val, suma = 0;
int nv = 0;
printf ("Programul determina media numerelor pozitive\n");
printf ("Introduceti numere (0 = sfarsit), numar=");
scanf ("%lf", &val);
while (val != 0)
{
if (val > 0)
{
nv++;
suma += val;
}
printf ("numar = ");
scanf ("%lf", &val);
} /* while */
if(nv>0)
printf ("Media valorilor pozitive este %5.2f", suma/nv);
else
printf ("Nu s-au introdus valori pozitive");
}

/*Program ce citeste un numar (si cu parte fractionara) intr-o baza


oarecare,
cuprinsa intre 2 si 10 facandu-i apoi conversia in zecimal*/
#include <stdio.h>
void main()
{
const char blanc = ' ', punct = '.', plus = '+', minus = '-';
char semn, car;
double valoare;
int p_subunitar, baza;
Programare C/C++ 71
printf ("\nBaza=");
scanf ("%d", &baza); while ( getchar() != '\n' );
/* ultima instructiune are rolul de a descarca caracterul ‘\n’ din
intrare, altfel prima instructiune de citire va citi acest caracter *\
printf ("\nIntroduceti numarul\n");
car = blanc;
while (car == blanc)
scanf("%c", &car);
semn = '+';
if (car == plus || car == minus)
{
semn = car;
scanf ("%c", &car);
};
valoare = 0;
while(car >= '0' && car < ('0'+baza))
{
valoare = valoare*baza + car - '0';
scanf("%c", &car);
}
if (car == punct)
{
p_subunitar = 0;
scanf("%c", &car);
while(car >= '0' && car < ('0'+baza))
{
valoare = valoare*baza + car - '0';
scanf("%c", &car);
p_subunitar++;
}
while (p_subunitar > 0)
{
valoare /= baza;
p_subunitar--;
}
}
if (semn == minus)
valoare = -valoare;
printf("Valoarea zecimala este:%f\n", valoare);
}

71
72 Programare C/C++

5.5.2. Instrucţiunea do

Sintaxa acestei instrucţiuni este:


do
instrucţiune;
while (expresie);

Instrucţiunea din corpul ciclului poate fi simplă sau compusă (bloc).


Efectul acestei instrucţiuni constă în execuţia repetată a instrucţiunii din
corpul ciclului (dintre do şi while) şi în evaluarea repetată a expresiei, cât
timp (while) valoarea acesteia este adevărat (adică diferită de zero); execuţia
instrucţiuni se termina când valoarea devine falsă adică egală cu zero.

Exemple:
/*Tipareste un numar intreg (de maxim 9-10 cifre) in ordie inversa*/
#include<stdio.h>
main()
{
long int numar, rest;
printf ("Introduceti un numar intreg:");
scanf ("%li",&numar);
printf ("Numarul scris in ordine inversa:");
do
{
rest=numar%10;
printf("%d", rest);
numar/=10;
}
while (numar);
printf ("\n");
}

/*Calculeaza pana la o limita specificata suma seriei:


1 + 1/2 + 1/3 + 1/4 + . . . 1/n > limita
programul va afisa fiecare suma si in final numarul de termeni
insumati*/
#include <stdio.h>
#define MAX_TERMENI 100
long int n = 0;
Programare C/C++ 73
long double suma = 0, limita;
main()
{
printf ("Specificati limita: ");
scanf ("%Lf", &limita);
do
{
n ++;
if ( n % 10 == 0 ) /* se afiseaza fiecare a 10-a iteratie
*/
printf ("Pasul=%li\tSuma=%Lf\n", n, suma);
suma += (long double) 1/n;
}
while (suma < limita && n < MAX_TERMENI);
printf("Suma finala este : %Lf\n", suma );
printf("Numarul de termeni insumati este: %li\n", n);
}

/*Calculeaza radacina patrata a unui numar real, folosind sirul


definit prin relatia de recurenta Xn=(Xn-1+a/Xn-1)/2, Xo=a/2
*/
#include <stdio.h>
#include <math.h>
#define MAX_TERMENI 100
main()
{
double radacina, a, precizia, modul;
int n=0;
printf ("Calculeaza radacina patrata a unui numar real\n");
printf ("Introduceti numarul: ");
scanf ("%lf", &a);
if (a < 0)
printf ("Nu se poate calcula radical dintr-un numar”
“ negativ\n");
else
if (a == 0)
printf ("Radacina este 0\n");
else
{
radacina = a/2; /* initializare */
precizia = a/1E+15; /*valoarea preciziei*/

73
74 Programare C/C++
do
{
radacina = (radacina + a/radacina)/2;
modul = a - radacina*radacina;
if (modul < 0)
modul = -modul;
n++;
}
while ( modul > precizia && n <
MAX_TERMENI);
printf ("Radacina patrata din %lg este %lf”
“ obtinuta dupa %d iteratii\n",
a, radacina, n);
printf ("Valoarea returnata de functia standard”
“ sqrt este : %lf\n", sqrt(a));
printf ("Diferenta fata de functia standard este”
“ %lg\n", sqrt(a)-radacina);
}
}

5.5.3. Instrucţiunea for

Această instrucţiune are următoarea sintaxa:


for ( expr_iniţializare ; condiţie_ciclu ; expr_actualizare )
instrucţiune;
Efectul acestei instrucţiuni este următorul:
- se evaluează expr_iniţializare o singură dată, la începutul execuţiei
ciclului for; expr_iniţializare poate fi o singură atribuire sau o listă de
expresii de atribuire separate prin virgule;
- apoi se evaluează condiţie_ciclu; dacă valoarea sa este adevărat,
deci diferită de zero, se execută instrucţiunea ce constituie corpul
ciclului; după aceasta se evaluează expr_actualizare;
- această secvenţă: execuţie instrucţiune ciclu şi apoi evaluare
expr_actualizare continuă cât timp valoarea condiţie_ciclu este
diferită de zero (adevărat); această expresie (condiţie_ciclu) este
evaluată de fiecare dată, înainte de execuţia instrucţiunii ce constituie
corpul ciclului;
- execuţia ciclului ia sfârşit când valoarea expresiei condiţie_ciclu
devine zero (fals).
Programare C/C++ 75
Trebuie menţionat că la fel ca la while, ciclul folosit poate să nu se execute
de loc, dacă de la prima evaluare expresia condiţie_ciclu are valoarea fals.
Aceasta instrucţiune poate fi descrisă si cu ajutorul instrucţiunii while:

expr_iniţializare;
while (condiţie_ciclu)
{
instrucţiune;
expr_actualizare;
}

Oricare dintre expresiile instrucţiunii for sau chiar toate pot lipsi.
Dacă lipseşte conditie_ciclu, aceasta implică faptul că, clauza while
(condiţie_ciclu) este echivalentă cu while (1),ceea ce înseamnă o condiţie
permanent adevărată.
Expr_actualizare poate conţine una sau mai multe expresii ce sunt
actualizate la fiecare ciclu; acestea sunt separate prin virgule.

Exemple :
/*Program ce afiseaza (genereaza) numerele prime in intervalul [3,n]
intervalul este parcurs cu un pas egal cu 2, deoarece numerele prime
sunt numai printre numerele impare
*/
#include<stdio.h>
void main()
{
long int n, i, div, este_prim;
printf (" Afiseaza numerele prime pana la n=");
scanf ("%ld", &n);
printf ("\n%9d,%9d", 2, 3);
for( i=5 ; i <= n ; i += 2 )
{
div=3;
do
{
este_prim = i % div;
div += 2;
}
while(este_prim && div*div <= i);
if (este_prim)
printf(",%9ld", i);

75
76 Programare C/C++
}
printf(".\n\n");
}

Calculul următoarei sume de factoriale:


n
suma factoriale= ∑ (−1)k∗k !
k=1

/* Calculeaza suma factorialelor pentru un n dat:


suma = -1! +2! -3! +4! -5! +6! -7! +8! - . . . . . .
folosind direct formula "suma de (-1)^k * k */
#include<stdio.h>
void main()
{
long int suma = 0, i, k, n, fact;
printf ("n="); scanf ("%ld", &n);
for ( k = 1 ; k <= n ; ++k )
{
fact = 1;
for ( i = 1 ; i <= k ; i++ )
fact = fact * i;
if ( k % 2 )
fact = - fact;
suma += fact;
}
printf("pentru n =%li, suma factorialelor este: %li\n", n,
suma);
}

/* Calculeaza suma factorialelor pentru un n dat:


suma = -1! +2! -3! +4! -5! +6! -7! +8! - . . . . . .
tinand cont ca , k! = (k-1)! * k , unde (k-1)! a fost calculat la pasul
anterior si schimband semnul produsului (factorialului) la fiecare
pas, in loc de a calcula, la fiecare pas, factorialul k! pornind de la
inceput (k=1) */
#include<stdio.h>
void main()
{
long int suma = 0, k, n, fact;
Programare C/C++ 77
printf ("n=");
scanf ("%ld", &n);
fact = 1;
for ( k = 1 ; k <= n ; k++ )
{
fact = - fact*k;
suma += fact;
}
printf("Suma factorialelor este: %li\n", suma);
}

/*Conversia unui numar intreg intr-o baza oarecare - 2..36 */


#include <stdio.h>
#define LUNG_MAX 31
void main ()
{
char cifra[LUNG_MAX];
int numar, baza, indice;
printf ("Numar de convertit: "); scanf ("%d", &numar);
printf(" In baza: "); scanf ("%d", &baza);
indice = 0;
if ( numar < 0 )
{
printf("-");
numar = - numar;
}
do
{
if ( numar % baza < 10 )
cifra[indice] = '0' + numar % baza;
else
cifra[indice] = 'A' + numar % baza - 10;
numar /= baza;
indice++;
} while(numar>0);
for ( indice-- ; indice >= 0 ; indice-- )
printf("%c", cifra[indice]);
printf("\n");
}

77
78 Programare C/C++

Să se calculeze xn pentru x real şi n întreg date, folosind un număr


cât mai mic de înmulţiri de numere reale. Iată pe un exemplu cum se poate
rezolva eficient problema:

y = x77 corecţie
x77 = x38 * x38 * x1
x38 = x19 * x19 * x0
x19 = x9 * x9 * x1
x9 = x4 * x4 * x1
x4 = x2 * x2 * x0
x2 = x1 * x1 * x0
x1 = 1 * 1 * x1

Puterile lui x, utilizat pentru corecţie, reprezintă (citite de jos în sus)


reprezentarea numărului n = 77 în baza 2 (1001101). De fapt în acest mod
se obţine reprezentarea unui număr în baza 2: se împart succesiv la 2,
numărul iniţial şi câturile obţinute în urma împărţirii, până se obţine câtul 0.
Resturile obţinute, citite în ordine inversă, reprezintă numărul scris în noua
bază.
Deoarece înmulţirea utilizează pentru corecţie cifrele reprezentării în
baza 2, începând cu cele mai semnificative, trebuie utilizat un alt algoritm
de obţinere a cifrelor numărului n în baza 2, decât cel prezentat, deoarece
acesta furnizează cifrele reprezentării în baza 2 începând cu cele mai puţin
semnificative.
Ţinând cont de acestea, se poate rezolva problema prin scăderi de
puteri ale lui 2, deoarece în baza 2 împărţirea se poate reduce la scădere
astfel:
Se scade din numărul iniţial n un număr putere a lui 2 (de ex. 2m), cel
mai mare posibil, aceasta însemnând că obţinem prima cifră a reprezentării
în baza 2. Din restul obţinut se încearcă scăderea numărului 2 m-1 ; dacă se
poate scădea, cifra corespunzătoare din reprezentarea numărului este 1
(trebuie făcută corecţie) altfel este zero şi se continuă algoritmul până la m =
0 (20 = 1); deci algoritmul este urmtorul:
1. se calculează cea mai mică putere a lui 2 > n, notată cu p;
2. y 1
3. p p/2
4. y y2
5. dacă n >p atunci { se face corectarea deoarece cifra reprezentată
în baza 2 este 1 }
Programare C/C++ 79
y y*x
n n - p)
6. dacă p >1 atunci execută pasul 3
7. stop

/* putere.c - calculeaza puterea intreaga a unui numar real, x^n,


utilizand un numar minim de inmultiri de numere reale */
#include <stdio.h>
#include <math.h>
main()
{
int n, p=1, nr_inmult=0; /* p = cea mai mica putere a lui 2 mai mare ca
n*/
int semn; /* memoreaza semnul lui n */
double x, y=1; /* y = x^n, nr_inmult = numarul de inmultiri efectuate
*/
printf ("Se calculeaza x^n.\n"); printf ("x= "); scanf ("%lf", &x);
printf ("n= "); scanf ("%d", &n);
semn = n < 0;
n = abs ( n );
while ( p <= n )
p = p * 2;
do
{
p = p / 2;
y = y * y;
nr_inmult++;
if ( n >= p )
{
n = n - p;
y=y*x;
nr_inmult++;
}
} while ( p > 1 );
if ( semn )
y=1/y;
printf ("x^n = % 5.2e, utilizand %d inmultiri .\n", y, nr_inmult);
}

79
80 Programare C/C++

5.6 Instrucţiunea break, de întrerupere a execuţiei ciclice

Instrucţiunea break a fost deja întâlnită ca o componentă a


instrucţiunii switch. Ea poate fi folosită nu numai la instrucţiunea de
selecţie, pentru a termina prelucrările selectate, ci şi cu oricare din cele trei
structuri de control de tip ciclu.
Există situaţi în care, la execuţia unui ciclu, se doreşte părăsirea
acestuia de îndată ce este îndeplinită o anumită condiţie; în acest scop poate
fi folosită instrucţiunea break (deci cu acelaşi efect ca la instrucţiunea de
selecţie). Execuţia acestei instrucţiuni determină ieşirea imediată din ciclul
ce se executa fie că este while, do sau for. Declaraţiile următoare lui break
din cadrul ciclului sunt sărite şi ciclul este terminat, execuţia programului va
continua cu declaraţiile ce urmează ciclul.
Dacă sunt cicluri incluse unele în altele sau instrucţiuni de selecţie,
execuţia unei instrucţiuni break va termina ciclul ce o conţine, adică cea mai
interioară dintre instrucţiunile while, do, for sau switch, care o conţine;
controlul este cedat instrucţiunii imediat următoare ciclului, deci ciclului
exterior celui în care s-a întâlnit instrucţiunea break.
Exemplu:
/* programul tipăreşte, în ecou, caracterele citite până */
/* se întâlneşte unul dintre caracterele : "linie noua" sau "tab" */
while ( ( car = getchar( ) ) != '\n' )
{
if (car == '\t' )
break;
putchar ( car );
}
dar care poate fi pus şi sub forma echivalentă:
while (( car = getchar( ) ) != '\n' && car !=' \t' )
putchar ( car );
De multe ori utilizarea lui break ca parte a instrucţiunii if poate fi
eliminată prin exprimarea diferită a condiţiei (ca în exemplul precedent). De
cele mai multe ori, însă, această instrucţiune este utilizată pentru a termina
în mod "forţat" o instrucţiune de ciclare.
Programare C/C++ 81
5.7. Instrucţiunea continue
Această instrucţiune poate fi utilizată în cele trei forme de ciclu, dar
nu în instrucţiunea de selecţie switch. În mod asemănător cu break,
instrucţiunea continue întrerupe ciclul curent, dar nu-l termină. În loc să
termine întregul ciclu, instrucţiunea continue determină ca restul
instrucţiunilor iteraţiei curente să fie sărit, dar este pornită următoarea
iteraţie .
Exemple :
1)
/* programul afişează cu ecou şi numără caracterele diferite de blanc */
/* dintr-un text citit de la tastatură, până la sfârşit de fişier */
contor = 0;
while ((car=getchar( ))!=EOF)
{
if (car == ' ' )
continue;
putchar(car);
contor++;
}

2)
/* afişare cu ecou a caracterelor, mai puţin tab-urile şi blancuri, */
/* până la sfârşitul liniei curente */
while ((car = getcar( ))!=' \n ' ) /* care poate fi scrisă mai
economic, însă fără */
{ /*continue: */
if (car=='\t || car==' ') /*while ((car = getchar ( )) != '\n') */
continue; /* if (car !=' \t' && car! = ' ') */
putchar(car); /* putchar (car); */
}
De multe ori inversând condiţia pentru un if se elimină utilizarea
instrucţiunii continue.

5.8. Instrucţiunea de salt goto

81
82 Programare C/C++
Această instrucţiune întrerupe parcurgerea secvenţială, normală, a
programului şi realizează un salt necondiţionat la o instrucţiune, etichetată,
în cadrul funcţiei (blocului) curente. Spre deosebire de break, care are un
efect asemănător, dar nu are efect decât la un singur nivel (se limitează la
terminarea unei singure instrucţiuni), instrucţiunea goto poate întrerupe şi o
succesiune de prelucrări (cicluri) incluse unele în altele.
Sintaxa sa este:
goto etichetă
si are ca efect execuţia instrucţiunii precedată de etichetă.
O astfel de instrucţiune etichetată poate fi plasată oriunde în cadrul
funcţiilor. Domeniul de valabilitate al etichetei, care este supusă aceloraşi
reguli ca numele de variabile, este blocul reprezentat de corpul funcţiei în
care apare, excluzând orice sub-bloc în care acelaşi nume a fost redeclarat.
Utilizarea frecventă a acestei instrucţiuni va face programul mai
greu de urmărit şi de înţeles. Din acest motiv această instrucţiune este
utilizată foarte rar, ea nefiind considerată ca parte a unui (bun) stil de
programare.
De exemplu:
a) citirea şi prelucrarea unui şir de numere, ce se termină cu o valoare nulă
(0):
cit_numar: scanf ("%d, & numar);
if ( numar == 0 )
goto etapa2;
. . . . . . . . . /* alte declaraţii */
goto cit_numar; /* se reia citirea numerelor */
etapa2:
. . . . . /* alte prelucrări */

b) pentru acelaşi exemplu, dacă se utilizează while secvenţa va fi :


scanf("%d”, &numar);
while (numar)
{
. . . . . . /* alte declaraţii; */
scanf ("%d", & numar);
}
. . . . . /* alte prelucrări; */
În schimb utilizarea acceptată pentru goto este ieşirea forţată, la îndeplinirea
unei condiţii, dintr-o succesiune de cicluri incluse, unele în altele:
while (valoare > 0 )
{
for ( i=1 ; i <= i_max ; i++ )
{
Programare C/C++ 83
for ( j=1 ; j <= j_max ; j++)
{
/* diverse prelucrări; */
if (condiţie_terminare_forţată)
goto gata;
/* prelucrări; */
}
/* prelucrări; */
}
/* prelucrări; */
}
/* alte declaraţii; */

6. Tipuri de date structurate

Au fost prezentate în capitolul 3 şi s-au utilizat în exemplele


prezentate tipurile de date predefinite (sau standard). Pe lângă aceste tipuri
limbajul C, ca şi alte limbaje de nivel înalt, oferă şi alte mecanisme pentru
reprezentarea valorilor unor obiecte, de altă natură decât cele direct
reprezentabile cu ajutorul tipurilor (predefinite) standard.
Principalele tipuri de date predefinite, simple , sunt tipurile : întreg,
real şi caracter. Pentru reprezentarea valorilor logice s-a recurs la codificarea
întreagă a acestora conform convenţiei : fals - codificat prin valoarea
întreagă 0 şi adevărat – codificat printr-o valoare întreagă diferită de 0.
O altă codificare întreagă a valorilor unui tip , o reprezintă tipul
enumerare .
Pe lângă aceste tipuri simple, limbajul C permite definirea unor
tipuri structurate (derivate), pe baza celor fundamentale, cum ar fi: tablouri,
şiruri de caractere, structuri, uniuni. De asemenea mai există alte două tipuri
: funcţia şi pointerul .

6.1 Tablouri

Un tablou este o structură omogenă, formată dintr-un număr finit de


componente, ordonate, de acelaşi tip, denumit tip de bază al tabloului;
elementele tabloului sunt memorate într-o zonă continuă şi compactă de
memorie, adică elementele sunt memorate succesiv.
Componentele tabloului sunt acelaşi tip de bază ce poate fi un tip
simplu (întreg, real, caracter) sau un tip structurat (derivat ).

83
84 Programare C/C++
Sintaxa de bază pentru declararea unui tablou este :
tip_de_bază nume_tablou [dim] ={val_iniţială , val_iniţială ,
…} ;
unde dim este o expresei constantă şi reprezintă numărul de elemente;
De obicei dimensiunea se specifică printr-un identificator (constantă
simbolică), introdus cu ajutorul directivei #define (a preprocesorului).
Această modalitate uşurează munca programatorului în cazul în care se
modifică dimensiunea tabloului, întrucât se face o singură modificare în
program, şi anume a directivei #define.
Numele utilizate pentru a identifica elementele vectorului sunt
denumite indici. Valorile indicilor trebuie să fie valori întregi şi valorile lor
încep de la 0. Deci, dacă un tablou a fost declarat de dimensiune n,
elementele sale vor fi referite prin indici în intervalul 0 ÷ n-1.
Valoarea iniţială, val_iniţială, este o expresie constantă de acelaşi tip
cu tipul se bază şi reprezintă valoarea iniţială asociată elementului respectiv
din tablou; valorile iniţiale sunt asociate în ordine elementelor tabloului. Pot
fi specificate mai puţine valori, în această listă, decât numărul de elemente
ale tabloului, dar nu mai multe . Dacă sunt specificate mai puţine valori, vor
fi iniţializate cu aceste valori, în ordine, atâtea elemente câte valori există în
listă, începând cu primul element. Elementele rămase vor fi iniţializate cu
0.
Dimensiunea unui tablou poate fi omisă în următoarele situaţii :
- se declară un tablou extern şi atunci pentru acesta s-a rezervat spaţiu
în alt fişier sursă ;
- se declară un tablou ca parametru formal al unei funcţii şi în acest
caz declaraţia este echivalentă cu cea a unui pointer ;
- declaraţia conţine o listă de iniţializare şi în această situaţie
dimensiunea tabloului este determinată, implicit, de numărul de
expresii constante din lista de iniţializare ;
Tabloul este o structură cu acces direct deoarece timpul de acces la o
componentă nu depinde de valoarea indicelui (adică de poziţia componentei
în cadru tabloului).

Exemple de declaraţii :
1) int tab_i [20] ;
defineşte un tablou denumit tab_i, cu 20 de elemente de tip întreg;
acestui tablou îi vor fi alocaţi 20* sizeof (int ) = 20*2 = 40 octeţi.
Elementele sale vor fi referite astfel:
tab_i[0] , tab_i[1] , … , tab_i[19] .
2) float tab _f [10] ;
defineşte tabloul tab_f cu 10 elemente de tip float; acestui tablou îi
vor fi alocaţi:
Programare C/C++ 85
10*sizeof (float) = 10*4 =40 octeţi
3) double tab_d [30] ;
un tablou de tip double, cu 30 elemente, căruia îi vor fi alocaţi :
30 *sizeof (double) =30*8 =240 octeţi
4) char tab_c [10] ;
un tablou de caractere, cu 10 elemente, ce va ocupa 10*sizeof =10*1
=10 octeţi
5) int tab_ii [ ] = {1 , 2 , 3 , 4 , 5 };
defineşte un tablou de numere întregi , de 5 elemente , iniţializate cu
valorile
tab_ii [0] = 1 , tab_ii [1] = 2 ;…, tab_ii [4] = 5 ;
declaraţia este echivalentă cu :
int tab [5] = {1 ,2 , 3 , 4 ,5 };
Exemple de programe utilizând tablouri :

1) Generarea literelor alfabetului latin şi tipărirea lor. Tipărirea lor se va


termina cu caracterul NULL.

#include <iostream.h>

void main(void)
{
char alfabet[27];
// 26 litere plus NULL
char litera;
int index;
// initializare litera si index-ul tabloului alfabet; actualizare litera si
// index
for (litera = 'A', index = 0; litera <= 'Z'; litera++, index++)
alfabet[index] = litera;
alfabet[index] = NULL;
// ultimul caracter tiparibil
cout << "Literele sunt: " << alfabet << endl;
// tiparirea se poate face si astfel :
cout << "Literele sunt :" << endl ;
for ( index = 0; index < 26 ; index++)
// in acest caz nu mai este necesar caracterul NULL
cout << alfabet[index] ;
cout << endl ;
}

85
86 Programare C/C++

2) Generarea numerelor lui Fibonacci; se vor genera cel mult 100 de numere
din acest şir, ce se construieşte cu relaţia:
fib (n) = fib (n-1) + fib (n-2) ; pentru n >= 2 ;
cu fib (0) = 0; fib (1) = 1;

/* fib.c – genereaza cel mult 100 numere Fibonacci */


# include <stdio.h>
main ( )
{
int fib[100] , i , n ;
fib [0] = 0 ; fib [1] = 1 ;/* initializarea primelor doua numere */
printf (“Specificati dimensiunea sirului Fibonacci (<= 100) : “) ;
scanf (“%d “, &n ) ;
for (i =2 ; i < n ; i++)
fib [i] = fib [i-1] +fib [i-2] ;
for (i = 0 ; i < n ; i++)
printf ( “ %i / “ , fib [i] ) ;
printf("\n");
}

3) Determinarea numerelor prime într-un interval [2 , n] şi depunerea lor


într-un tablou.

/* prime . c - numere prime in intervalul [2 , n] */


#include <stdio.h>
#include <math.h>
main ( )
{
int i, n, p, este_prim, prim [1000], index_prim;
prim [0] = 2 ;
prim [1] = 3 ;
printf (“Se determina numerele prime in intervalul [2 , n], n = ”);
scanf (“%d “ , &n) ;
index_prim = 1;
for (p = 5 ; p <= n ; p = p + 2)
{
este_prim = 1;
for (i = 1 ; este_prim && i <= index_prim ; i++)
este_prim = p % prim [i];
if (este_prim)
Programare C/C++ 87
{
index_prim ++;
prim [ index_prim ] = p ;
}
}
for (i = 0 ; i <= index_prim ; i++)
printf ( “%i / “ , prim [i] );
printf (“ \n “ ) ;
}

La acest algoritm, pentru un număr, p, se determină dacă este prim


sau nu prin încercarea de a găsi un divizor al său printre numerele prime
determinate până la el.

4) Aceeaşi problemă, dar utilizând alt algoritm: sita lui Eratostene.


Algoritmul constă din eliminări succesive ale multiplilor numerelor prime,
în ordine crescătoare. Se asociază intervalului un tablou “boolean“ iniţializat
cu valorile “adevărat” (diferit de zero) pentru toate componentele; indicii
tabloului reprezintă numerele pentru care se determină dacă sunt prime sau
nu (începând, bineînţeles, de la 2). Numărul prim, pentru care se elimină
multipli săi este primul indice, mai mare decât, căruia îi corespunde în
tabloul asociat valoarea ”adevărat” (diferită de 0). ”Eliminarea” constă , de
fapt, în modificarea valorii asociate indicelui din “adevărat “ în “fals“. Se
contorizează numerele prime şi cele “eliminate“. Algoritmul i-a sfârşit când
contorul devine egal cu n-1.

/* prime2.c – sita lui Eratostene */


# include <stdio.h >
#define MAXIM 100
main ( )
{
int prim [ MAXIM ], n , index , contor , multiplu ;
printf (“ Determina numerele prime in intervalul 2-n ,n =’ ) ;
scanf (“ %d” , &n ) ;
for (index = 2, index <n+1 ; ++index )
prim [index] = 1 ; {primele 2 elemente nu sunt folosite }
contor = 0 ;
index = 1;
do
{
index ++;

87
88 Programare C/C++
if (prim [index] )
{
printf ( “ %d …/” , index );
for (multiplu = 1 ; multiplu <= n / index ;++
multiplu )
if ( prim[ index * multiplu ] )
{
prim [index * multiplu ] = 0 ;
contor ++;
}
}
} while (contor <n-1) ;
}

5) Ordonarea unui şir utilizând algoritmul Shell (Donald Shell): se


accelerează ordonarea unui şir, prin parcurgerea lui cu un pas variabil,
pentru a reduce dezordinea şirului .
Fiecare etapă realizează inversiuni (în sensul ordonării), între
elementele aflate la o anumită “distanţă“ (pas). Pasul iniţial este egal cu
jumătate din numărul de elemente; la fiecare parcurgere cu un anumit pas,
se iniţializează o variabilă “logică”, care e modificată doar dacă apar
inversiuni, cel puţin între două elemente .
Dacă la sfârşitul unei etape variabila logică a fost modificată, deci au
existat inversiuni, se reia parcurgerea cu acelaşi pas, până când nu se mai
realizează inversiuni, deci variabila “logică” nu mai este modificată. Astfel
după fiecare etapă numerele mai mici se apropie de începutul şirului şi cele
mari de sfârşitul său.
După fiecare parcurgere, când nu au mai apărut inversiuni se reia
parcurgerea cu un pas înjumătăţit. Algoritmul i-a sfârşit cu etapa în care
pasul, prin înjumătăţiri succesive, devine 1, ceea ce asigură şi convergenţa
algoritmului.

/* ordonare .c – ordonarea unui sir cu metoda Shell */


#include < stdio.h >
#define N_MAX 100
main ( )
{
double sir [N_MAX], a;
int n, i, j , inv ;
printf (“ numărul de elemente ale şirului (: <= 100) : “ ) ;
scanf (“ %d “ , &n ) ;
Programare C/C++ 89
for ( i = 0 ; i < n ; ++i )
{
printf ( “sir [ % d ] = “ , i+1 );
scanf ( “%d“, &sir [ i ] ) ;
}
pas = n;
while (pas >1 )
{
pas = pas / 2 ;
do
{
inv = 0 ; /* fals */
for (i = 0 ; i < n – pas ; ++i)
{
j = i+pas ;
if ( sir [i] > sir [j] ) /* sau if (sir[i] >
sir[i+pas] )*/
{
a: = sir [j]; sir [j] = sir [i]; sir[i] = a; inv =
1;
}
}
} while (inv);
}
for ( i = 0 ; i < n ; ++i )
printf (“ inv [ %d ] = %f / “ , i+1 , sir [i] ) ;
}

6.1.1. Tablouri multidimensionale

Pot fi definite tablouri nu numai de o singură dimensiune ci cu


oricâte dimensiuni. Un astfel de tablou multidimensional poate fi considerat
un tablou ale cărui elemente sunt tablouri. Declaraţia este asemănătoare cu
cea a tabloului unidimensional; în acest caz parantezele pătrate sunt asociate
de la stânga la dreapta. De exemplu o matrice cu 4 linii şi 5 coloane cu
valori de tip real se declară astfel : double MAT [4] [5] ;
Referirea la un element al matricei din linia l, coloana c se face sub
forma:
MAT [l] [ c]

89
90 Programare C/C++
matricea fiind memorată pe linii .
Pentru tablouri multidimensionale, deci cu mai mulţi indici, primul
indice este cel care variază cel mai lent, iar ultimul indice variază cel mai
repede.
Exemple :
1) Înmulţirea a două matrice A şi B are ca rezultat matricea C (prodmat.c):
A(la , ca) * B(lb , cb) = C(la , cb ) , cu condiţia ca = lb.
Termenul general al matricei C este:
lb
c (i, j)= ∑ a(i, k )∗b (k , j)
k =1
cu: 1<= i<= la, 1 <= j <= cb

/* Calculeaza produsul a doua matrice, daca se pot inmulti (adica


numarul de coloane de la prima este egal cu numarul de linii de la
a
2-a)
*/
#include <stdio.h>
#define DIM 10
void main()
{
double a[DIM][DIM], b[DIM][DIM], c[DIM][DIM];
double x;
int la, ca, lb, cb, i, j, k;
printf ("Dimensiunea matricei A (linii,coloane):");
scanf ("%d%d", &la, &ca);
printf ("Dimensiunea matricei B (linii,coloane):");
scanf ("%d%d", &lb, &cb);
if (ca != lb)
printf ("Nu se pot inmulti\n");
else
{
printf ("Matricea A:");
for ( i = 0 ; i < la ; i++)
for ( j = 0; j < ca ;j++)
{
printf (" A(%d,%d)=",i+1, j+1);
scanf ("%lf", &a[i][j]);
}
printf("Matricea B:");
Programare C/C++ 91
for ( i = 0 ; i < lb ; i++)
for ( j = 0 ; j < cb ; j++)
{
printf (" B(%d,%d)=", i+1, j+1);
scanf ("%lf", &b[i][j]);
}
for ( i = 0 ; i <la ; i++)
for ( j = 0 ; j < cb ; j++)
{
c[i][j] = 0;
for ( k = 0 ; k < ca ; k++)
c[i][j] += a[i][k] * b[k][j];
printf (" C(%d,%d)=%lf\n",
i+1,j+1,c[i][j]);
}
}
}

2) Calculul valorii unui polinom p(x) de grad n într-un punct dat x. Se va


utiliza dezvoltarea polinomului sub forma de înmulţire prin împachetare:
p(x) = an* xn + an-1* xn-1 + an-2* xn-2 + . . . + a1* x1 + a0* x0 =
= ( . . . ( ( ( 0 + an) * x + an-1) * x + an-2 ) * x + . . . + a1) * x + a0

#include <stdio.h>
#include <conio.h>
void main(void)
{
int grad, i, val, x;
int coef[10];
clrscr();
printf ("Programul calculeaza valoarea unui polinom de grad n"
"intr-un punct x dat\n\n");
printf ("Introduceti gradul polinomului:");
scanf ("%d", &grad);
for ( i = grad ; i >= 0 ; i--)
{
printf ("Introduceti coeficientul lui x la puterea %d:", i);
scanf ("%d", &coef[i]);
}
printf ("Introduceti x-ul in care se calculeaza valoarea polinomului:");
scanf ("%d", &x);

91
92 Programare C/C++
val = 0;
for ( i = grad ; i >= 0 ; i--)
val = val * x + coef[i];
printf ("Valoarea este: %d", val);
}

Tablourile multidimensionale pot fi iniţializate într-o manieră


asemănătoare cu cele cu o singură dimensiune, cu precizarea că elementele,
după cum am arătat, trebuie specificate pe linii . Pentru separarea liniilor
între ele se utilizează acolade. De exemplu :
int M [4][5] = {
{1, 2, 3, 4, 5 },
{ 2, 5, 17, 82, 16 },
{ 3, 9, 29, 89, 243 },
{ 4, 8, 16, 32, 64 },
};
Ţinând cont de precizarea anterioară matricea poate fi iniţializată şi astfel :
int M [4][5]={1, 2, 3, 4, 5, 2, 5, 17, 82, 16, 3, 9, 27, 89, 24, 4, 8, 16,
32, 64}
Dacă sunt prezentate mai puţine valori, restul vor fi iniţializate cu 0 ca în
exemplul:
int M[4][5] = {
{1 , 2, 3},
{2 , 4, 8 },
{3 ,6 ,12 },
};
Se vor iniţializa primele trei valori din fiecare linie cu valorile specificate,
ultimele două fiind 0. De asemenea ultima linie va fi iniţializată cu 0.
Utilizarea acoladelor incluse va forţa iniţializarea dorită. Fără aceste
acolade incluse, dacă se utilizează forma precedentă, se vor iniţializa
primele 9 elemente: deci prima linie (cinci elemente) şi patru elemente din
linia a 2-a, restul fiind 0.

6.2. Şiruri de caractere

Un şir de caractere este o serie de unul sau mai multe caractere, de


exemplu :
Programare C/C++ 93
“Un sir de caractere”
Ghilimelele nu fac parte din şir, ci ele sunt utilizate pentru a delimita
şirul de caractere, în mod asemănător cu apostrofurile care marchează un
caracter ('a').
În C nu există un tip de date special pentru şirurile de caractere (cum
este în Pascal) .
Şirurile de caractere sunt memorate în tablouri de tip caracter (char),
întrucât un tablou este o secvenţă ordonată de elemente de acelaşi tip. Pentru
a marca sfârşitul unui şir de caractere, de lungime variabilă, se utilizează
caracterul NULL, adică pe ultima poziţie se află caracterul ‘\0‘ (nu este
codul ASCII al cifrei 0, ci valoarea numerică 0 şi este un caracter, cod,
netipăribil) .
Pentru un şir care conţine n caractere, compilatorul va rezerva n+1
locaţii de memorie: în primele n sunt depuse caracterele din şir, iar ultima
conţine terminatorul (caracterul null ‘\0‘). Exemplu de iniţializare şi tipărire
a unui tablou de caractere:

# include < stdio.h >


main ( )
{
char salut [ ] = { ‘S‘, ‘a’ , ‘l’ , ‘u’ , ‘t’ , ‘!‘ } ;
int i ;
for ( i = 0 ; i < 6 ; ++i )
printf (“%c“ , salut [ i ] ) ;
printf ( “ \ n “ ) ;
/* in continuare este transformat tabloul de caractere in sir */
salut[5] = ‘\0’;
printf(“%s!\n”, salut);
}
Acest exemplu defineşte şi tipăreşte şirul de caractere “Salut!”.
Pentru tipărirea tabloului caracter cu caracter se utilizează, în funcţia
printf(), specificatorul %c (tipărirea unui caracter).
Pentru a simplifica citirea / tipărirea şirurilor de caractere se poate
utiliza specificatorul %s , ca în exemplul următor :

/* nume .c – citeste si tipareste numele utilizatorului */


# include < stdio.h >
# include <string.h>
# define INTREB “Cum te cheama ?”
# define SALUT “Salut ! “
main ( )

93
94 Programare C/C++
{
char nume [50] ;
printf (INTREB);
scanf (“%s“, nume);
printf (SALUT, “%s”, nume );
printf (“Numele tau are %d litere, dar s-au rezervat %d locatii

“de memorie\n”, strlen (nume), sizeof nume);
printf (“Propozitia \“%s\“ , are %d litere “,
INTREB, strlen (INTREB)) ;
printf (“si ocupa %d octeti de memorie \n “ , sizeof INTREB)
}

La citirea unui şir de caractere, funcţia scanf ( ) va pune caracterul


NULL (‘\0‘), de sfârşit de şir de caractere, când citeşte şirul; citirea i-a
sfârşit la întâlnirea primului spaţiu alb (blanc, tab sau linie nouă). Pentru a
citi numele şi prenumele sau, în general o frază, nu numai un singur cuvânt,
limbajul C oferă o altă funcţie de citire şiruri şi anume gets (s), care citeşte
până la sfârşit de linie, care nu e memorat în buffer, ci se pune caracterul de
sfârşit ‘\0‘; iar pentru tipărire există puts (s).
În acest exemplu se utilizează funcţia strlen( ), care furnizează
lungimea unui şir de caractere (numărul efectiv de caractere din şir). Această
funcţie se află în fişierul header <string.h>. Spre deosebire de această
funcţie, operatorul sizeof furnizează numărul de octeţi rezervaţi pentru un tip
sau o variabilă. Dacă operatorul are drept operand un tip acesta se pune între
paranteze: sizeof (int), iar dacă este o variabilă, ca în exemplul precedent,
nu se pune între paranteze .
Observaţie: şirul “a” nu este acelaşi cu caracterul ‘a’. O primă diferenţă,
esenţială, este că ‘a’ este un tip de bază, char, iar “a“ este un tip derivat
(structurat), un tablou de tip char. O a doua diferenţă este că şirul “a”
constă din două caractere: caracterul ‘a’ şi caracterul ‘\0’.

Exemple:
1)
/*
cnvbazas.c - programul realizeaza conversia unui numar intreg intr-o
alta baza
*/
#include <stdio.h>
#define LUNG_MAX 32
main ()
Programare C/C++ 95
{
long int numar, baza, ind1, ind2;
char cifra [ LUNG_MAX ], car_aux;
printf ("numarul de convertit = ");
scanf ("%ld", &numar);
printf ("conversie in baza :");
scanf ("%ld%", &baza);
if ( numar < 0 )
{ printf ("-");
numar = - numar;
}
ind1 = -1;
do
{ ind1 ++;
if ( numar % baza < 10 )
cifra [ ind1 ] = '0' + numar % baza;
else
cifra [ ind1 ] = 'A' + numar % baza - 10;
numar /= baza;
} while ( numar > 0 );
for ( ind2 = 0; ind2 < ind1/2 ; ind2 ++ )
{
car_aux = cifra [ ind2 ];
cifra [ ind2 ] = cifra [ ind1 – ind2 – 1 ];
cifra [ ind1 – ind2 – 1 ] = car_aux;
}
cifra [ ind1 ] = ‘\0’;
printf ("%s\n", cifra);
}

2)
/*
Programul concateaza doua siruri de caractere, utilizand o
functie, si determina lungimea sirului obtinut
*/
#include <stdio.h>
void main()
{
void concat ( char rezultat[], char s1[], char s2[]);
int lungime ( char s[]);

95
96 Programare C/C++
char sir1[]={"Test pentru"};
char sir2[]={" concatenare siruri de caractere"};
char sir[60];
concat (sir, sir1, sir2);
printf ("Sirul rezultat este:\n%s\n", sir);
printf ("Lungimea sirului este: %d\n", lungime(sir));
}
void concat (char rezultat[], char s1[], char s2[])
{
int i, j;
for ( i = 0 ; s1[i] != '\0' ; i++ )
rezultat[i] = s1[i];
for ( j = 0 ; s2[j] != '\0' ; j++)
rezultat[i+j] = s2[j];
rezultat[i+j] = '\0';
}
int lungime (char s[])
{
int contor;
for ( contor = 0 ; s[contor] != '\0' ; contor++);
return (contor);
}

Specificatorul de conversie %s este utilizat pentru a tipări, cu funcţia


printf(), un şir de caractere ce se termină cu caracterul NULL. Implicit,
specificatorul %s presupune că tabloul de caractere se termină cu acest
caracter. Dacă se utilizează %s la citirea unui şir de caractere, cu funcţia
scanf(), atunci citirea va avea loc până la întâlnirea spaţiului alb (blanc, tab
sau linie nouă); dacă se introduc mai multe caractere decât este dimensiunea
tabloului, în care se depun, se pot întâmpla lucruri neprevăzute; din
nefericire funcţia scanf() nu posedă un mecanism de determinare a
dimensiunii tabloului pentru a suspenda citirea caracterelor în exces; ea
citeşte caractere până la întâlnirea spaţiului alb şi le depune în memorie la
locaţii succesive. Pentru a limita numărul (maxim) de caractere citite se
poate utiliza un modificator, reprezentând numărul maxim de caractere
admise, în faţa specificatorului %s, astfel:
scanf (“%60s”, sir);
care va citi cel mult 60 de caractere, caracterele în exces fiind ignorate.

Testarea egalităţii a două şiruri


Programare C/C++ 97
Nu se poate testa direct, dacă două şiruri sunt egale, printr-o
declaraţie de forma :
if (sir 1== sir 2 )
deoarece operatorul de egalitate poate fi aplicat doar unor variabile de tip
simplu (întregi, reale sau caracter), nu asupra unor tipuri mai sofisticate
(cum ar fi tablourile sau structurile).
Pentru a determina dacă două şiruri de caractere sunt egale, trebuie
deci comparate explicit cele două şiruri, caracter cu caracter . Dacă se
ajunge simultan la sfârşitul ambelor şiruri de caractere, şi toate caracterele
până în acel punct sunt identice, atunci cele două şiruri sunt egale, altfel nu
sunt .
Deoarece ne interesează să determinăm dacă cele două şiruri de
caractere sunt egale sau nu, vom poziţiona o variabilă pe 1 (adevărat) dacă
sunt identice şi pe 0 (fals) dacă nu sunt identice:

int i = 0 , egale = 0 ; /* presupunem că nu sunt egale */


while ( sir1[i] == sir2[i] && sir1[i] != ’\0’ && sir2[i] == ’\0 ‘ )
++i ;
if ( sir1[i] == ‘\0‘ && sir2[i] == ‘\0 ‘ )
egale =1 ;
sau
int i = 0 , egale = 1 ; /* presupunem că sunt egale */
while ( egale )
{
egale = (sir1[i] == sir2[i] && sir1[i] != ’\0’ && sir2[i] == ’\0 ‘);
++i;
}
if ( egale )
egale = (sir1[i] == ‘\0‘ && sir2[i] == ‘\0 ‘);

Pentru a declara o constantă şir de caractere, mai lungă, care nu


încape pe o linie, se va utiliza caracterul de continuare de linie \; altfel
compilatorul va genera un mesaj de eroare. În cazul continuării şirului de
caractere pe o linie nouă trebuie ca această continuare să se facă de la
începutul liniei următoare, altfel spaţiile libere (blancurile) de la începutul
liniei până la continuarea şirului vor fi incluse şi, deci, memorate în acest şir
.
Exemplu :
char litere [ ] = { “abcdefghijklmnoprstuvwxyz \
ABCDEFGHIJKLMNOPRSTUVWXYZ “};

97
98 Programare C/C++
Pentru a simplifica o astfel de declaraţie, a unor şiruri lungi, se poate
împărţi şirul iniţial în două sau mai multe subşiruri adiacente astfel :
char litere [ ] ={
“abcdefghijklmnoprstuvwxyz”
“ABCDEFGHIJKLMNOPRSTUVWXYZ”};
Exemple:
1)
/*
Numara cuvintele dintr-un text, citit de la consola;
textul se termina cu caracterul ‘#’, la inceputul unei linii noi
*/
#include <stdio.h>
int litera (char c)
{
if(( c> = 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ))
return (1); /* daca este o litera */
else
return (0); /* daca nu este o litera */
}
int numara_cuvinte ( char linie[] )
{
int contor_cuv = 0, cuvant = 1, i = 0;/* cuvant =1, s-a terminat un
cuvant */
while ( linie[i] != '\0' )
{
if( litera ( linie[i] ) )
{
if (cuvant)
{
contor_cuv ++;
cuvant = 0; /* a inceput un nou cuvant */
}
}
else
cuvant = 1;
i++;
}
return ( contor_cuv );
}

void citeste_linie ( char l_text[] )


Programare C/C++ 99
{
int i = 0;
char c;
do /* sau in loc de do{ . . .} */
{ /* while (( l_text[i++] = getchar()) != ’\n’); */
c = getchar(); /* l_text [i-1] = ‘\0’; */
if (c != '\n' && i < 80)
{
l_text[i] = c;
i++;
}
} while (c != '\n');
l_text[i] = '\0'; /* pune marcajul de sfarsit de sir de caractere */
}

void main()
{
int sf_text = 0, total_cuv = 0;
char text[81];
printf ("Introduceti un text terminat prin '#' la inceputul unei
linii.\n");
while ( !sf_text )
{
citeste_linie ( text );
if ( text[0] == '#' )
sf_text = 1;
else
total_cuv += numara_cuvinte (text);
}
printf ("\nNumarul de cuvinte este %d\n", total_cuv);
}

2)
/*
programul elimina spatiile (blancurile) dintr-un sir de caractere
*/
#include <stdio.h>
#include <conio.h>
#include <string.h>
#define DIM 100
void main(void)

99
100 Programare C/C++
{
int i, j;
char sir[DIM], sir1[DIM];
clrscr();
printf ("Porgramul elimina spatiile dintr-un sir\n\n");
printf ("Introduceti sirul:");
gets (sir1);
j = 0; /* contorul de caractere pentru sirul nou, fara spatii */
for( i = 0 ; i < (strlen(sir1)) ; i++ )
{
if (sir1[i] != ' ')
{
sir[j] = sir1[i];
j++;
}
}
sir[j] = ’\0’;
printf("Sirul fara spatii:%s\n",sir);
}

Se poate rezolva problema utilizând numai şirul iniţial, prin


deplasarea caracterelor următoare unui blanc astfel:

for (i = 0 ; i < strlen (sir1) ; i++)


if (sir1 [i] = ' ')
for (j = i ; j < strlen (sir1) ; j++)
sir1[j] = sir1[j+1];
/* se muta si caracterul de sfarsit de sir ‘\0’ */

3)
// tiparirea a doua siruri de carcatere unul numai cu litere mari, iar
// celalalt cu litere mici
#include <iostream.h>
#include <string.h> // Contine functiile prototip de transformare:
// strupr si strlwr, prima
// in litere mari, iar cea de-a doua in litere mici.
void main(void)
{
char titlu[] = "Introducere in C++";
Programare C/C++ 101
char tema[] = "Descrierea sirului de caractere";
cout << "Litere mari: " << strupr(titlu) << endl;
cout << "Litere mici: " << strlwr(tema) << endl;
}

4)
/*
invcar.c - inversarea tipului de caractere dintr-un text:
de la litera mare la litera mica si invers
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LIMITA 30
#define DIMENS 81
main()
{
void transforma ( char linie[] );
char text [LIMITA] [DIMENS];
int nr_linie =0 , i;
printf ("Introduceti cel mult %d linii pentru transformare\n",
LIMITA);
printf ("Introducerea se termina cu ENTER, pe o linie noua\n");
while ( nr_linie < LIMITA && gets(text[nr_linie]) != NULL &&
strcmp ( text[nr_linie], "") !=0 )
nr_linie ++;
for ( i = 0 ; i < nr_linie ; i++)
{
transforma ( text[i] );
puts ( text[i] );
}
}
void transforma ( char *ptr_car )
{
while ( *ptr_car != '\0' )
{
if ( isupper ( *ptr_car ) )
*ptr_car = tolower ( *ptr_car );
else if ( islower ( *ptr_car ) )
*ptr_car = toupper ( *ptr_car );
ptr_car++;

101
102 Programare C/C++
}
}
Programare C/C++ 103

6.3 Structuri

În paragraful 6.1 s-a definit tabloul, care permite gruparea unor


elemente, de acelaşi tip, într-o singură entitate. Limbajul C furnizează şi un
alt instrument pentru a grupa elementele împreună, pentru a reprezenta date
complexe. Această formă de date, denumită structură, nu numai că este
suficient de flexibilă, în forma ei de bază, pentru a reprezenta o diversitate
de date, dar permite utilizatorului să definească noi tipuri (forme).
O structură este o colecţie de valori, ce pot fi de tipuri diferite,
memorată într-o zonă continuă de memorie. Componentele unei structuri,
sunt denumite câmpuri, şi pot fi de orice tip simplu sau derivat (cu excepţia
tipului void şi funcţie). Câmpurile unei structuri sunt identificate prin nume
simbolice (identificatori), denumite selectori. În plus, o componentă a unei
structuri poate fi un câmp de biţi.
Sintaxa generală pentru declararea unei structuri este :
struct nume_structură sau (în C++) class nume_structură {
{ { public :
declaraţii_câmpuri declaraţii_câmpuri

} listă _variabile; } listă _variabile

Fiecare declaraţie_câmp constă dintr-un specificator de tip urmat de


o listă de unul sau mai multe nume de câmpuri .
Variabilele pot fi declarate în acelaşi timp cu declararea structurii
prin listarea lor (ca în forma anterioară) şi în acest caz numele structurii
poate lipsi şi avem definită o structură anonimă (fără nume); variabilele pot
fi declarate şi după definirea structurii utilizând sintaxa:
struct nume_structură listă _variabile;
În această situaţie poate lipsi listă_variabile din definirea structurii.
E clar că, fie într-un caz, fie în celălalt nume_structură şi listă_variabile nu
pot lipsi simultan la definirea unei structuri.
Dacă o componentă a structurii este un câmp de biţi, ea se declară
sub forma :
tip nume_câmp : n
şi defineşte un câmp al structurii care are n biţi (n este întreg). De obicei
câmpul acesta, de n biţi, este aliniat la dreapta. Dacă nume_câmp este omis,
numărul de biţi specificaţi, este rezervat, dar nu pot fi referiţi. Tipul poate fi
int, signed int sau unsigned int. Dacă nume_câmp este omis şi n = 0, atunci
câmpul care urmează este aliniat la următoarea unitate de memorie, unitatea
fiind definită de implementare. Nu pot fi definite tablouri de biţi şi nu se
poate aplica unui câmp de biţi operatorul de adresare (&) .

103
104 Programare C/C++
O declaraţie de structură care nu este urmată de o listă de variabile
nu alocă memorie; ea descrie numai un şablon, o formă de structură. Dacă
structurii i se asociază un nume, atunci acesta poate fi utilizat ulterior pentru
definirea unor variabile de tip structură cu acelaşi format cu structura
respectivă.
Formatul de iniţializare pentru o variabilă de tip structură este
similar cu acela pentru tablouri. Câmpurile sale pot fi iniţializate prin
includerea listei de valori iniţiale între acolade. Fiecare valoare din listă
trebuie să fie o expresie constantă.
Pentru a selecta (referi) un câmp, o componentă, dintr-o structură se
utilizează operatorul de selecţie ( . ) între numele variabilei de tip structură
şi numele unui câmp al structurii:
variabilă_structură . nume_câmp
În acest caz numele câmpului mai este denumit şi selector datorită
faptului că el selectează componenta din cadrul structurii. Câmpul selectat
se comportă ca o variabilă şi i se pot aplica toate operaţiile ce se pot aplica
variabilelor (datelor) de tipul respectiv.
Iată un exemplu de declarare şi iniţializare a unei structuri pentru
reprezentarea unor cărţi:
struct s_carte
{
char titlu [ 50 ] ;
char autor [ 25 ] ;
float pret;
int nr_pag;
} volum [ 100 ] =
{ { " Programare C", "Vasile Lungu", 2.50, 209 } };
În acest exemplu este iniţializată doar prima "carte" din acest tablou cu 100
de structuri de tip "carte".
În plus, cu o variabilă de tip structură, ca o entitate, se mai pot
efectua operaţiile:
 poate fi transmisă ca parametru , pentru funcţii ;
 o funcţie poate returna o valoare de tip structură ;
 i se poate atribui valoarea altei variabile structură , de acelaşi tip, ca
în exemplul următor:
struct s_planeta {
char nume [20] ;
double raza, orbita;
int vizibila;
};
struct s_planeta planeta [20] ;
Programare C/C++ 105
planeta [ i ] = planeta [ j ] ;
această ultimă declaraţie este echivalentă cu următoarele:
planeta [ i ].nume = planeta [ j ].nume ;
planeta [ i ].raza = planeta [ j ].raza ;
planeta [ i ].orbita = planeta [ j ].orbita ;
planeta [ i ].vizibila = planeta [ j ].vizibila ;
Pentru a afla spaţiul de memorie alocat (numărul de octeţi) unei
variabile de tip structură se poate utiliza operatorul sizeof :
sizeof ( struct nume_structura )
Exemple:
1)
// se initializeaza cateva date pentru un angajat (nume, ID, salariu,
// telefon)
#include <iostream.h>
#include <string.h>
#define DIM_NUME 30
#define NR_TELEF 12
void main(void)
{
struct st_angajat {
char nume[DIM_NUME];
long id;
float salariu;
char telefon[NR_TELEF];
} angajat;

// Copieaza un nume pentru un angajat


strcpy(angajat.nume, "Popescu Dan");
// si initializeaza si celelalte campuri: ID, salariu, telefon
angajat.id = 12345;
angajat.salariu = 25000.00;
strcpy(angajat.telefon, "2/118777");
// Tiparirea datelor angajatului:
cout << "angajat id: " << angajat.id << endl;
cout << "Angajat: " << angajat.nume << endl;
cout << "Salariu: " << angajat.salariu << endl;
cout << "Telefon: " << angajat.telefon << endl;
}

2)

105
106 Programare C/C++
/* carti.c - programul realizeaza inventarul cartilor dintr-o
biblioteca */
#include <stdio.h>
#include <string.h>
/* numarul maxim de caractere pentru : titluri, autori,
si numarul maxim de carti */
#define MAXTIT 150
#define MAXAUT 50
#define MAXCARTI 100
/* STOP = sirul vid de caractere, care va termina introducerea
datelor */
#define STOP ""
struct s_carte
{
char titlu [ MAXTIT ];
char autor [ MAXAUT ];
double pret;
};

main()
{
struct s_carte carte [ MAXCARTI ]; /* tablou cu structura de carte */
int index , contor = 0;
double val;
printf ("Introduceti titlurile cartilor.\n");
printf ("Pentru oprire se va tasta RETURN, la inceputul unei
linii.\n");
printf ("Titlu carte:");
while ( contor < MAXCARTI && strcmp ( gets ( carte [contor].titlu ),
STOP ) != 0 )
{
printf ("Nume autor:");
gets (carte[contor].autor);
printf ("Pretul cartii:");
scanf ("%lf", &val);
while ( getchar() != '\n'); /* sterge linia curenta */
carte[contor++].pret = val;
if ( contor < MAXCARTI )
printf ("Titlu carte:");
}
printf ("Inventarul cartilor introduse :\n");
Programare C/C++ 107
for ( index = 0 ; index < contor ; index++ )
printf ("%s de %s : $ %lf\n", carte[index].titlu,
carte[index].autor, carte[index].pret );
}

3)
/* programul listeaza studentii, dintr-o grupa, in ordinea
descrescatoare a mediilor; se citesc, pentru fiecare student:
numele, notele si se calculeaza media.
Se citesc datele respective pentru studentii dintr-un an
Pentru listare nu se sorteaza vectorul studentilor dupa medii
ci se parcurge vectorul studenti, se determina studentul ce are
cea mai mare medie si nu a fost inca listat; pentru fiecare
student listat se actualizeaza campul “afisat”; aceste campuri au
fost initializate la introducerea datelor, pentru fiecare student.
Ciclul de afisare va lua sfarsit dupa ce s-au listat toti studentii
din grupa respectiva
*/
#include <stdio.h>
#include <string.h>
#define MAXGR 5
#define MAXSTUD 25
#define MAXNOTE 12
#define MAXNUME 15
struct s_student
{
char nume [MAXNUME];
char prenume [MAXNUME];
int nota [MAXNOTE];
double media;
int afisat; /* acest camp precizeaza daca studentul respectiv
a fost listat */
};
struct s_grupa
{
char numeg [MAXNUME];
int nr_stud;
struct s_student stud [MAXSTUD];
};
struct s_an

107
108 Programare C/C++
{
char numean [MAXNUME];
int nr_grupe;
int nr_note;
struct s_grupa grupa [MAXGR];
};
main()
{
struct s_an an;
int ig, is, im, imax, contor;
double medie, medmax;
char numegr [MAXNUME];
printf ("Nume an:");
gets (an.numean);
printf("Numarul de note(materii):");
scanf("%d", &an.nr_note);
while ( getchar() !='\n'); /* sterge restul liniei */
printf ("Numar grupe:");
scanf ("%d", &an.nr_grupe); /* numarul de grupe din an */
while ( getchar() != '\n');
for ( ig = 0 ; ig < an.nr_grupe ; ig++)
{
printf ("Nume grupa:"); /* numele grupei */
gets ( an.grupa[ig].numeg );
printf ("Numar studenti:"); /* numarul de studenti */
scanf ("%d", &an.grupa[ig].nr_stud);
while ( getchar() != '\n');
for ( is = 0 ; is < an.grupa[ig].nr_stud ; is++)
{ /* citesc datele pentru fiecare sudent */
printf ("Nume student (%d):", is+1);
gets ( an.grupa[ig].stud[is].nume );
printf ("Prenume student (%d):", is+1);
gets ( an.grupa[ig].stud[is].prenume );
medie = 0;
for ( im = 0 ; im < an.nr_note ; im++)
{
printf ("Materia (%d):", im+1);
scanf ("%d", &an.grupa[ig].stud[is].nota[im]);
medie += an.grupa[ig].stud[is].nota[im];
}
while ( getchar() != '\n');
Programare C/C++ 109
an.grupa[ig].stud[is].media = medie/an.nr_note;
an.grupa[ig].stud[is].afisat =0; /* initializare camp
‘afisat’ */
}
}
printf ("Grupa de afisat:"); /* numele grupei de afisat */
gets ( numegr );
ig = 0; /* se determina, pe baza numelui, numarul grupei de afisat */
while ( ig < an.nr_grupe && strcmp(an.grupa[ig].numeg,numegr) !=
0)
ig++;
if ( ig >= an.nr_grupe)
printf ("Eroare nume grupa \n"); /* nu avem ce afisa */
else
{
contor = 0; /* contorul numarului de studenti listati */
while ( contor < an.grupa[ig].nr_stud )
{
imax = 0; /* se determina indexul studentului cu media
maxima */
while ( an.grupa[ig].stud[imax].afisat ) /* care nu a fost
listat */
imax++;
medmax = an.grupa[ig].stud[imax].media; /* si
media acestuia */
for ( is = imax + 1 ; is < an.grupa[ig].nr_stud; is++)
if (( !an.grupa[ig].stud[is].afisat) && (medmax <
an.grupa[ig].stud[is].media))
{
imax = is;
medmax = an.grupa[ig].stud[is].media;
}
an.grupa[ig].stud[imax].afisat = 1; /* la afisare se
actualizeaza */
printf ("%-*s %-*s %5.2lf\n", MAXNUME,
an.grupa[ig].stud[imax].nume,
MAXNUME,
an.grupa[ig].stud[imax].prenume,
an.grupa[ig].stud[imax].media);
contor++;
}

109
110 Programare C/C++
}
}

4)
#include <iostream.h>
#include <string.h>

class angajat { // definirea clasei angajat


public:
int atribuie_valori(char *, long, float);
void tip_angajat(void);
int schimba_salariu(float);
long get_id(void);
private:
char nume[50];
long angajat_id;
float salariu;
};

int angajat::atribuie_valori(char *ang_nume, long ang_id,


float ang_salariu)
{ // initializeaza numele angajatului si salariul
strcpy(nume, ang_nume);
angajat_id = ang_id;

if (ang_salariu < 50000.0)


{
salariu = ang_salariu;
return(0); // Corect
}
else
return(-1); // Salariu incorect
}

void angajat::tip_angajat(void)
{ // tiparire date angajat: nume, ID, salariu
cout << "Angajat: " << nume << endl;
cout << "Id: " << angajat_id << endl;
cout << "Salariu: " << salariu << endl;
}
Programare C/C++ 111

int angajat::schimba_salariu(float salariu_nou)


{ // modifica salariul angajatului
if (salariu_nou < 50000.0)
{
salariu = salariu_nou;
return(0); // Corect
}
else
return(-1); // Salariu incorect
}

long angajat::get_id(void)
{
return(angajat_id);
}

void main(void)
{
angajat lucrator;

if (lucrator.atribuie_valori("Ionescu Ion", 101, 10101.0) == 0)


{ // daca valorile atribuite angajatului sunt corecte se tiparesc
cout << " Valorile atribuite angajatului:" << endl;
lucrator.tip_angajat();
if (lucrator.schimba_salariu(35000.00) == 0)
{
cout << " Salariu nou atribuit" << endl;
lucrator.tip_angajat();
}
}
else
cout << "Salariu specificat este invalid" << endl;
}

6.4 Uniuni

O uniune (sau reuniune) este o construcţie ce seamănă cu structura,


care permite memorarea diferitelor tipuri de date, în aceeaşi zonă de

111
112 Programare C/C++
memorie, dar nu simultan. Deci în cazul unei uniuni, aceeaşi zonă de
memorie poate fi interpretată în diferite moduri, în funcţie de tipul de date
memorat.
Spre deosebire de structură, în cazul căreia pentru fiecare câmp i se
alocă spaţiul necesar de memorie şi deci pentru structură se alocă un spaţiu
de memorie total egal cu suma spaţiilor de memorie necesare fiecărui câmp
al structurii, în cazul uniunii se alocă atâta spaţiu de memorie cât este
necesar pentru memorarea câmpului de dimensiune maximă.
O uniune mai poate fi definită ca o structură cu un singur câmp, de
un anumit tip care poate fi considerat reuniunea unor câmpuri (componente)
de diferite tipuri, dintre care este activ, la un moment dat, doar unul dintre
acestea .
De exemplu se poate defini o variabilă ce poate lua valori de diferite
tipuri: întreg, real, complex sau caracter, astfel :
struct complex
{
double real , imag ; /* memorie necesară: 16 octeţi */
};
union var_mixta
{
struct complex cmpx ; /* memorie necesară: 16 octeţi */
int n; /* memorie necesară: 2 octeţi */
double r ; /* memorie necesară: 8 octeţi */
char c ; /* memorie necesară: 1 octet */
};
Pentru a determina spaţiul de memorie necesar unei variabile, sau tip
de date, se poate utiliza operatorul sizeof :
sizeof ( struct complex ) =16 octeţi;
întrucât sunt definite două câmpuri (real , imag) fiecare de tip double, deci
ocupă fiecare 8 octeţi.
În schimb, în cazul uniunii, vom avea :
sizeof (union var_mixta) = 16 octeţi,
care reprezintă spaţiul de memorie maxim cerut de componenta cea mai
lungă (mare).
Uniunile sunt iniţializate în mod asemănător cu structurile, fie
utilizând un şablon de uniune fie o variabilă uniune, dar spre deosebire de
structuri, se va iniţializa doar un singur câmp. Accesul la un câmp al unei
uniuni se face cu acelaşi operator de selecţie (.), ca şi în cazul structurilor.
Atribuirea unei valori pentru un câmp al unei uniuni va afecta toate
câmpurile uniunii. Numele de uniuni împart acelaşi spaţiu de nume cu
numele de structuri şi de enumerări, dar separat de numele de variabile; deci
putem avea o variabilă sau o uniune cu acelaşi nume, fără a se confunda .
Programare C/C++ 113
Numele câmpurilor unei uniuni sunt locale acesteia, deci putem avea
câmpuri cu acelaşi nume în uniuni diferite, fără a se face confuzie.
Exemple de iniţializări :
union var_mixta variabila = {5};
/* iniţializare număr întreg */
union var_mixta variabila = {‘A’};
/* iniţializare caracter */
union var_mixta variabila = {{ 1.5 , 3.5 }};
/* iniţializare număr complex */
union var_mixta tablou [20] = {{41.0 , 2.0 }};
/* iniţializare primul element din tablou cu un număr complex */

Să considerăm informaţiile referitoare la personalul unei firme care să


conţină: nume, prenume, vârstă, sex şi studii. Câmpul studii poate fi de una
dintre următoarele forme :
- fără pregătire, deci fără nici o informaţie ;
- cu şcoală generală, şi se vor preciza numărul de clase
absolvite;
- cu liceu, şi se vor preciza media şi anul absolvirii;
- cu învăţământ superior, şi se vor preciza universitatea şi
facultatea, media şi anul absolvirii.
Deci se va utiliza o structură de forma:
enum pregatire { fara, scgen , liceu , inv_superior};
union u_st
{
struct {
char univ[30] , facult[30] ;
double medief;
int anf;
} stud_super;
struct {
double mediel;
int anl;
} stud_liceu
int nr_clase;
};
struct
{
char nume [20] , prenume [20] ;
int varsta , sex;
enum pregatire preg;
union u_st studii;

113
114 Programare C/C++
} persoana [10] = { {"Ionescu", "Ion", 30, 1, inv_superior,
"Universitatea Poli Buc.", "Electrotehnica", 9.50, 1995 } } ;
Câmpurile uniunii corespunzătore acestei structuri vor fi referite astfel:
persoana [ i ].preg, pentru câmpul pregătire,
persoana [ i ].studii.stud_super.univ, pentru câmpul universitate,
persoana [ i ].studii.stud_super.facult, pentru câmpul facultate,
persoana [ i ].studii.stud_super.medief, pentru câmpul medie
facultate,
persoana [ i ].studii.stud_super.anf, pentru câmpul an absolvire
facultate etc.

Conform acestei definiri putem avea patru structuri diferite :

nume: nume nume nume nume


prenume: prenume prenume prenume prenume
varsta: varsta varsta varsta varsta
sex: sex sex sex sex
studii: fara scgen liceu inv_superior
nr_clase mediel univ
anl facult
medief

Din punct de vedere al implementării utilizarea tipului uniune nu


conduce la economisirea memoriei pentru alocarea variabilelor de acest tip.
Zona alocată este dată de numărul de celule de memorie necesare memorării
celei mai mari componente.
Pentru componentele mai scurte o parte din acest spaţiu de memorie
rămâne nefolosit, spaţiul alocat întregii uniuni fiind acelaşi, indiferent de
componenta activă la un moment dat şi de mărimea acesteia. Pentru a
specifica, care componentă este activă, la un moment dat, se utilizează un
anumit câmp, denumit discriminant, care pentru exemplul anterior este preg.
De exemplu angajat[i].preg = liceu determină selectarea acestei
componente şi abandonarea variantei precedente. Câmpurile anterioare
devin inaccesibile, o referire la angajat[i].studii.stud_super.univ este o
eroare (adică referirea se poate face, dar valoarea returnată este eronată,
Programare C/C++ 115
întrucât sunt active, efective, alte câmpuri) iar cele actuale sunt accesibile
dar trebuie definite.
Pentru evitarea unor erori, ce pot să apară la utilizarea uniunilor,
înaintea prelucrării câmpurilor unei uniuni se verifică dacă câmpul
discriminator conţine valoarea corespunzătoare componentei respective .
Pentru a prelucra câmpurile unei uniuni trebuie, în primul rând,
determinată componenta activă (deci cea care conţine informaţie); pentru
aceasta trebuie utilizată o variabilă (sau componentă) care să precizeze
componenta activă din uniune. Pentru determinarea valorii ei se va utiliza
instrucţiunea switch.
Este responsabilitatea programatorului de a asigura că valoarea
recuperată de la o uniune este consistentă (corespunzătoare) cu ultima
valoare ce a fost memorată în uniune.
Dacă tipurile componentelor unei uniuni sunt distincte, atunci o
variabilă de tip uniune poate fi iniţializată pentru oricare dintre aceste
câmpuri; dacă nu sunt de tipuri diferite, se poate iniţializa doar primul câmp.
Primul membru al unei uniuni poate fi iniţializat prin includerea
valorii iniţiale, care trebuie să fie o expresie constantă, între acolade:
union partaj
{ int w [2] ;
long int l ;
} schimb = {1,2} ;
Aceasta declară variabile uniune schimb şi pune componenta
w = {1,2}.
Dacă pentru aceeaşi declaraţie avem următoarea iniţializare:
union partaj
{ int w [2] ;
long int l ;
} schimb = {0xffff} ;
se va iniţializa componenta w = {0xffff , 0}.
Dacă se inversează declaraţia:
union partaj
{
long int l ;
int w [2] ;
} schimb = {0xffffL} ;
va iniţializa componenta l cu 0xffffL.
De asemenea o variabilă uniune automatică poate fi iniţializată cu o
uniune de acelaşi tip, astfel :
union partaj schimb2 = schimb ;
Exemplu:

115
116 Programare C/C++
/*programul listeaza angajatii, unei firme, dupa tipul pregatirii:
- fara pregatire;
- cu scoala generala, pentru care se specifica numarul de clase;
- cu liceu : anul absolvirii liceului si media;
- cu facultate: universitatea, facultatea, anul abs. si media
*/
#include <stdio.h>
#define MAXNUME 20
#define MAXPERS 50
main()
{
enum pregatire { fara , scgen , liceu , facultate };
union u_studii
{
struct /* studii: facultate */
{
char nume_univ[MAXNUME];
char nume_facult[MAXNUME];
int anf;
double mediaf;
} st_univ;
struct /* studii: liceu */
{
int anl;
double medial;
} st_liceu;
int nr_clase; /* studii: scoala generala */
};
struct
{
char nume[MAXNUME];
char prenume[MAXNUME];
int varsta, sex;
enum pregatire preg;
union u_studii studii;
} angajat[MAXPERS];
enum pregatire sel_preg;
int i, n;
char tip_pregatire[MAXNUME];
double val;
printf ("Numarul de angajati ai firmei este:");
Programare C/C++ 117
scanf ("%d", &n);while(getchar() != '\n');
for ( i = 0 ; i < n ; i++ )
{
printf ("Nume angajat:");
gets ( angajat[i].nume );
printf ("Prenume angajat:");
gets ( angajat[i].prenume );
printf ("Varsta : ");
scanf ("%d", &angajat[i].varsta);
printf ("Sex(0-femeie,1-barbat) : ");
scanf("%d", &angajat[i].sex); while( getchar() != '\n');
printf("Studii(fara, scoala generala, liceu, universitate):");
gets (tip_pregatire);
switch ( tip_pregatire[0] ) /*primul caracter din tip pregatire */
{
case 'f':
case 'F': angajat[i].preg = fara; break;
case 's':
case 'S': angajat[i].preg = scgen;
printf("Numarul de clase:");
scanf ("%d", angajat[i].studii.nr_clase);
while(getchar()!='\n');
break;
case 'l':
case 'L': angajat[i].preg = liceu;
printf ("Anul absolvirii liceului:");
scanf ("%d", &angajat[i].studii.st_liceu.anl);
printf ("Media de absolvire a liceului:");
scanf ("%lf", &val);
angajat[i].studii.st_liceu.medial = val;
while ( getchar() != '\n');
break;
case 'u':
case 'U': angajat[i].preg = facultate;
printf ("Universiatatea:");
gets (angajat[i].studii.st_univ.nume_univ);
printf ("Facultatea:");
gets (angajat[i].studii.st_univ.nume_facult);
printf ("Anul absolvirii facultatii:");
scanf("%d", &angajat[i].studii.st_univ.anf);
printf ("Media de absolviria a facultatii:");

117
118 Programare C/C++
scanf("%lf", &val);
angajat[i].studii.st_univ.mediaf = val;
while( getchar() != '\n');
break;
};
}
printf ("Angajatii cu ce fel de pregatire doriti sa fie listati?\n ");
gets (tip_pregatire); /* tipul de angajati ce se doreste a fi listati
switch ( tip_pregatire[0] )
{ /* tiparire antet lista */
case 'f':
case 'F': sel_preg = fara;
printf("Lista angajatilor fara pregatire:\n");
break;
case 's':
case 'S': sel_preg = scgen;
printf("Angajatilor cu studii scoala generala”
“(nr.clase):\n");
break;
case 'l':
case 'L': sel_preg = liceu;
printf("Lista angajatilor cu studii liceul:\n");
break;
case 'u':
case 'U': sel_preg = facultate;
printf("Lista angajatilor cu studii”
“ universitare:\n");
break;
};
for ( i = 0 ; i < n ; ++i )
if ( angajat[i].preg == sel_preg )
{
printf ("%-*s %-*s, varsta: %2d, sex:", MAXNUME,
angajat[i].nume, MAXNUME, angajat[i].prenume,
angajat[i].varsta);
switch ( angajat[i].sex ) /* tiparire sex */
{
case 0 :printf("femeie\n");
break;
case 1 :printf("barbat\n");
break;
Programare C/C++ 119
}
}
}

119
120 Programare C/C++

6.5. Pointeri

În acest paragraf vom descrie una dintre cele mai importante


(sofisticate) caracteristici ale limbajului de programare C: pointerii. De fapt
puterea şi flexibilitatea pe care C le furnizează în lucrul cu pointeri,
reprezintă o caracteristică aparte faţă de celelalte limbaje, cum ar fi Pascal-
ul.
Pointerii permit: reprezentarea efectivă de structuri de date
complexe, să se modifice valori transmise ca argumente la funcţii, să se
lucreze cu memorie ce a fost alocată ‘dinamic’, precum şi operarea, mult
mai concisă şi eficientă, cu tablouri.
Pentru a înţelege modul în care operează pointerii (sau referinţe, cum
mai sunt denumiţi), este necesar, în primul rând, să înţelegem conceptul de
indirectare. Un pointer furnizează un mod indirect de acces la valoarea unei
anumite date. Să presupunem că definim o variabilă astfel:
int numar = 10 ;
Putem defini o altă variabilă, denumită int_ptr, care va permite
accesul indirect la valoarea variabilei numar, prin declaraţia :
int *int_ptr ;
Asteriscul defineşte, în C, că variabila int_ptr este de tipul pointer
(referinţă) către o valoare de tip int; ceea ce înseamnă că int_ptr va fi
utilizată în program pentru a accesa, indirect, valoarea uneia sau mai multor
variabile întregi .
Am văzut în exemplele anterioare cum a fost utilizat operatorul &
în funcţia scanf(). Acest operator unar numit operator de adresare, este
utilizat pentru a crea (defini) un pointer către un obiect. Astfel, dacă x este o
variabilă de un anumit tip, atunci expresia &x este un pointer (adresă)
către acea variabilă.
De aceea, pentru declaraţiile anterioare, putem scrie o declaraţie,
astfel :
int_ptr = &numar ;
pentru a iniţializa o referinţă indirectă între int_ptr şi numar.
Operatorul de adresare are ca efect asignarea la variabila int_ptr, nu
a valorii variabilei numar ci un pointer (referinţă) la variabila numar (adică
&numar reprezintă adresa variabilei numar). Legătura dintre int_ptr şi
numar poate fi reprezentată astfel:

int_ptr: ------ -----> numar: 10


Programare C/C++ 121
Linia dintre int_ptr şi numar ilustrează idea că int_ptr nu conţine valoarea
variabilei numar, ci un pointer (adresa ) către variabila numar.
Pentru a referi conţinutul variabilei numar prin variabila pointer
int_ptr, se utilizează operatorul de indirectare care este asteriscul * . Deci
dacă definim o variabilă x de tipul int, atunci declaraţia:
x = *int_ptr ;
va atribui valoarea, care este referită indirect prin int_ptr, variabilei x.
Deoarece int_ptr a fost, anterior, încărcat cu referinţa la variabila numar,
această declaraţie are ca efect asignarea valorii conţinută în variabila
numar, adică valoarea 10, variabilei x.
De fapt un pointer, întrucât conţine adresa zonei de memorie asociată
variabilei, este echivalent cu valoarea-stângă a variabilei:
&x – adresa lui x ;
*x – dereferenţiere x ;
*( &x) este echivalentă cu x;
Exemple :
1)
/* pointer.c – program pentru exemplificare pointeri */
#include <stdio.h >
main ( )
{
int numar =10 , x;
int * int_ptr;
int_ptr = &numar;
x = * int_ptr ; /* x ia valoarea referita de int_ptr, adica 10
(numar) */
printf (“ numar = %i , x =% i/n “ , numar , x );
}

2)
// programul exemplifica utilizarea unui pointer fara tip (void) la doua
// variabile de tipuri
// diferite, motiv pentru care se face conversia explicita la tipul
necesar
// de pointer
#include<iostream.h>
int x;
float r;
void *p = &x; /* p pointer la variabila x */
int main () {
*(int *) p = 2;

121
122 Programare C/C++
p = &r; /* p pointer la variabila r */
*(float *)p = 8.8;
cout<<r<<endl; // se tiparesc valorile atribuite celor doua variabile
cout<<x<<endl; // prin intermediul variabilei pointer p
}

Forma de bază pentru declararea unei variabile pointer este


următoarea :
tip * nume_variabilă ;
Identificatorul nume_variabilă este declarat ca fiind tipul “pointer la
tip“; tip-poate fi un tip de date de bază sau un tip derivat (structură, tablou);
iată câteva, astfel, de declaraţii:
double * real; /* pointer la un real dublă precizie */
struct persoana * p ; /* pointer la o structură de tip persoana */
struct persoana (*fu_ptr) (int) ; /* fu_ptr este un pointer la o funcţie
care returnează o structură de tip persoana şi care are un singur argument
de tip int */
int (*tab) [100] ; /* se defineşte un pointer la un tablou de 100
întregi */
char *tcar[ 100]; /* un tablou de 100 de pointeri la tipul
caracter */

Alt exemplu simplu :

#include <stdio.h>
main ( )
{
char car = ‘c’ ;
char *ch_ptr = &car ; /* ch_ptr e iniţializat cu adresa variabilei car */
printf (“%c %c\n“, car, *ch_ptr);
car = ‘?’ ;
printf (“%c %c\n“, car, *ch_ptr);
*ch_ptr = ‘+‘;
printf (“%c %c\n“, car, *ch_ptr) ;
}

Ieşirea acestui program va fi


c c
? ?
+ +
Programare C/C++ 123
Dacă variabila car e declarată după pointerul ch_ptr se va semnala o
eroare deoarece o variabilă trebuie declarată înainte ca valoarea să fie
referită într-o expresie.
În programul precedent, deoarece ch_ptr referă variabila car,
afişarea valorii lui *ch_ptr va coincide cu afişarea valorii variabilei car.
Pointerii ne furnizează un mod simbolic de utilizare a adreselor,
folosite de instrucţiunile calculatorului; deci ei ne permit să exprimăm
adresele într-un mod care este apropiat de modul în care însuşi calculatorul
le utilizează.
În continuare vom prezenta un exemplu de utilizare a unei variabile
pointer pentru afişarea, în hexazecimal, a conţinutului memoriei începând de
la o adresă specificată până la apăsarea tastei CTRL-Z (sfârşit de fişier).

/* afisarea continutului memoriei , incepand de la o adresa


specificata de utilizator; afisarea ia sfarsit cand se introduce de la
tastatura sfarsit de fisier (CTRL-Z)
*/
#include <stdio.h>
main()
{
unsigned int start, adr_seg = _DS;
int n, e = 1, afis_adr_pag =1;
char *p;
printf ("Adresa de start a zonei de memorie afisata:");
scanf ("%x", &start);
while ( getchar() !='\n');
p = ( char *) start; /* valoarea este transformata in pointer la
tipul char*/
for (n = 1 ; ; n++, p++)
{if ( afis_adr_pag ) /* s-a afisat adresa de inceput a paginii ? */
{printf("Adresa de inceput a paginii afisateeste:”
“%p:%p\n", adr_seg, p );
afis_adr_pag = 0;
};
printf ("%02X ", *p ); /* afisarea se face in hexazecimal */
if ( ! ( n % 16 ) )
{
e++; /* variabila e contorizeaza numarul de linii pe ecran
*/
printf("\n") ; /* si se trece pe o linie noua */
};

123
124 Programare C/C++
if ( ! ( e % 24 ) && ! ( n % 8 ) )
{e=1; /* dupa afisarea unui ecran se va tipari din nou
adresa */
afis_adr_pag = 1; /* curenta a zonei de memorie afisata
*/
if ( getchar() == EOF ) /* se testeaza daca s-a introdus
caracterul */
break; /* de sfarsit listare memorie */
}
}
}

6.5.1. Pointeri şi tablouri

Numele unui tablou este echivalent cu un pointer constant (adică


adresa) către primul element al tabloului. Deci pentru un tablou oarecare,
vector, de exemplu, expresiile:
vector &vector &vector[0]
sunt echivalente şi reprezintă adresa de memorie a primului element din
tabloul vector. Toate acestea sunt constante pointer deoarece ele rămân fixe
pe durata execuţiei programului. Totuşi, ele pot fi atribuite la o variabilă
pointer pe care o putem modifica; deci un pointer poate fi folosit ca şi cum
ar fi numele unui tablou.
Unul dintre cele mai obişnuite utilizări ale pointerilor, în C, sunt ca
pointeri către tablouri; motivele principale pentru utilizarea pointerilor la
tablouri sunt uşurinţa notaţiei şi eficienţa programului. Utilizarea pointerilor
la tablouri are ca efect reducerea spaţiului de memorie şi execuţia mai rapidă
a codului generat .

Operaţii cu pointeri
Pentru a exemplifica operaţiile cu pointeri (cinci operaţii) să
urmărim efectul acestora, afişat, după fiecare operaţie, de următorul
program:

/* oper _ptr.c - operatii cu pointeri */


#include <stdio.h>
main ( )
{
static int vect [3] = { 100, 200, 300 };/* vector este initializat cu aceste
valori */
Programare C/C++ 125
int * ptr1 , * ptr2 ; /* se vor utiliza doi pointeri pentru exemplificare */
ptr1 = vect ;
/* sau ptr1=&vect[0], ptr1=&vect, atribuirea unei adrese de la
pointer*/
ptr2 = &vect [2] ; /* sau ptr2 = vect+2 */
printf ("ptr1 = %u, *ptr1 = %d, &ptr1 = %u \n", ptr1, *ptr1, &ptr1);
/* valorile continute : ptr1 , referită de ptr1 si adresa lui ptr1 */
ptr1++ ; /* incrementare pointer */
printf ("ptr1 = %u, *ptr1 = %d, &ptr1 = %u\n", ptr1, *ptr1, &ptr1);
printf ("ptr2 = %u, *ptr2 = %d, &ptr2 = %u\n", ptr2, *ptr2, &ptr2);
++ptr2 ; /* avansat după tabloul definit , vect*/
printf ("ptr2 = %u, *ptr2 = %d, &ptr2 = %u\n", ptr2, *ptr2, &ptr2);
printf ("ptr2-ptr1 = %u\n" , ptr2-ptr1);
/* scadere intre doi pointeri , de acelasi tip*/
}

Deci, putem realiza următoarele operaţii cu un pointer:


1. Atribuire. Putem atribui o adresă unui pointer, utilizând un nume de
tablou sau utilizând operatorul de adresare (&).
2. Dereferenţiere (valoarea referită). Operatorul de dereferenţiere *
furnizează valoarea memorată în locaţia referită .
3. Adresa unui pointer. La fel ca toate variabilele, variabilele pointer au
o adresă şi o valoare. Operatorul de adresare & precizează unde este
memorat însuşi pointerul.
4. Incrementare / decrementare pointer. Prin incrementarea /
decrementarea unui pointer acesta va referi următorul / anteriorul
element. Valoarea pointerului este mărită / micşorată, efectiv, cu numărul
de octeţi ai unui element referit de acesta.
5. Diferenţa între doi pointeri se poate face doar dacă sunt de acelaşi
tip, şi referă, de obicei, un tablou; rezultatul reprezintă numărul de
componente dintre cei doi pointeri şi nu numărul de octeţi; rezultatul nu
este definit dacă valorile pointerilor nu reprezintă adrese de elemente din
acelaşi tablou. Pe baza scăderii se definesc operatorii relaţionali (>, < ,
>=, <=, ==, !=).
Dacă avem un tablou de 100 valori întregi, denumit valori, putem
defini un pointer, denumit val_ ptr, care poate fi utilizat pentru a referi
valorile întregi conţinute în acest tablou, utilizând declaraţia:
int * val_ptr ;
Când definim un pointer care va fi utilizat pentru a servi elementele
unui tablou, noi nu îl definim ca fiind de tipul “pointer la tablou“, ci pointer

125
126 Programare C/C++
la tipul elementelor care sunt conţinute în tablou (adică tipul int). Deci se
declară ce tip de obiect referă variabila pointer.
Dacă definim un tablou de caractere denumit text, putem defini în
mod asemănător un pointer care va referi elemente din tabloul text, cu
declaraţia:
char * text_ptr ;
Acest mod de declarare este necesar pentru a specifica calculatorului
câţi octeţi sunt folosiţi pentru a memora un obiect (tipul int utilizează 2
octeţi, tipul char 1 octet, tipul float 4 octeţi, ş.a.m.d.). Astfel, dacă se
“adună 1 la un pointer“, de fapt, se adună o unitate de memorare pentru a
referi următorul element. Pentru tablouri aceasta înseamnă că adresa este
crescută la adresa ce referă următorul element, şi nu la următorul octet din
memorie.
Deci declaraţia unui pointer specifică tipul de date referite : ”pointer
la orice tip–de-date–obiect“.
Pentru a referi primul element al tabloului valori vom scrie:
val_ptr = &valori ; sau val_ptr = valori;
Nu trebuie utilizat operatorul de adresare (&) în cazul tablourilor,
deoarece compilatorul tratează numele unui tablou, fără indice, ca un pointer
la tablou. Un mod echivalent de iniţializare este aplicarea operatorului de
adresare primului element al tabloului:
val_ptr = &valori [0];
În continuare expresia:
* val_ptr
poate fi utilizată pentru a adresa primul element din tabloul valori, adică
valori[0]. Pentru a referi al doilea element se pot utiliza oricare dintre
expresiile:
val_ptr = &valori[1] sau val_ptr += 1;
Pentru a avea acces la elementul i, din acest tablou, se va utiliza
expresia:
* ( val_ptr + i )
Următoarele două expresii, de exemplu, sunt echivalente:
valori [ 10 ] = 100;
*( val_ptr + 10 ) = 100;
Deci, în general, dacă t este un tablou de elemente de tip x, iar px este de
tipul "pointer la x" şi i , n sunt variabile de tip întreg, atunci declaraţia:
px = t;
va pune pointerul px să refere primul element din t; expresia:
* ( px + i );
va referi valoarea din t[i]; iar declaraţia:
px + = n;
Programare C/C++ 127
va pune px să refere, mai departe, la al n-lea element din tablou, indiferent
de tipul elementului ce este conţinut de tablou.
Operatorii de incrementare şi decrementare asupra unui pointer au
acelaşi efect cu adunarea / scăderea unei unităţi din pointer, deci pointerul
va referi elementul următor / anterior dintr-un tablou. Cei doi operatori pot
fi utilizaţi şi în cazul pointerilor în cele două forme: prefixată şi postfixată.
De exemplu, expresiile :
*( ++ p x ) ; /* incrementează pointerul px, şi apoi referă elementul
din tabloul t */
*( px ++ ) ; /* referă, mai întâi, elementul din tablou, apoi
incrementează pointerul */
Alte operaţii care se pot efectua cu pointeri sunt:
- comparaţia, pentru a vedea dacă doi pointeri sunt egali sau
nu, sau care dintre ei este mai mare;
- adunarea / scăderea unei valori întregi n la un pointer
furnizează adresa obiectului respectiv situat cu n poziţii după /
înaintea celui curent;
- scăderea, care este permisă doar între doi pointeri ce
reprezintă adrese din acelaşi tablou; rezultatul scăderii celor doi
pointeri reprezintă numărul de elemente conţinute, în tablou, între
cei doi pointeri.
Alt exemplu :

/* programul insumeaza elementele unui vector */


#include <stdio.h>
main ( )
{
int valori [10] = { 3,7, -9, 5, 18, 35, -11, 100, -225, 77 };
int sum = 0 , * ptr ;
int * ptr_sfarsit = valori + 10 ;
for ( ptr = valori ; ptr < ptr_sfarsit ; ++ ptr )
sum += *ptr ;
printf ("suma valorilor este %i\n" , sum ) ;
}

o formă echivalentă cu ciclul anterior poate fi următoarea:


for ( i = 0 ; i < 10 ; i ++ )
sum + = * ( ptr + i );
Observaţii.
1. În ciclul for s-a utilizat condiţia de sfârşit de ciclu :
ptr < ptr_sfarsit ;

127
128 Programare C/C++
în loc de: ptr < valori + 10;
din motive de optimizare a tipului de execuţie:
- în primul caz variabila ptr_sfarsit, calculată o singură dată la
începutul programului, nu îşi modifică valoarea, deci nu e nevoie să
fie reevaluată ;
- în cel de-al doilea caz, expresia valori +10, va fi evaluată, la
fiecare execuţie, cu toate că valoarea sa nu este modificată niciodată
în cadrul buclei; deci pentru această buclă expresia ar fi calculată, în
mod inutil, de 10 ori.
2. În general, procesul de indexare a unui tablou ia mai mult timp de
execuţie decât necesită accesul la conţinutul unui pointer. De fapt aceasta
este unul dintre motivele principale pentru care se utilizează pointerii
pentru a accesa elementele unui vector, întrucât codul generat este mult
mai eficient. Dacă accesul nu se face secvenţial atunci expresiile :
* ( ptr + j ) şi tablou [ j ]
vor necesita acelaşi tip de execuţie .
Dacă, însă, accesul se face secvenţial :
ptr ++ ; /* trece la elementul următor mai rapid, decât
oricare dintre */
i ++ ; /* expresiile următoare , care vor */
tablou [ i ] ; /* lua mai mult timp */
sau tablou[++i]; /* echivalentă cu precedentele două
instrucţiuni */

Încă un exemplu de utilizare a unui pointer pentru a tipări valorile unui


tablou.

#include <iostream.h>
void afiseaza(float *sir, int numar_elemente)
{ // functia afiseaza elementele de tip float referite de pointerul sir
int i;
for (i = 0; i < numar_de_elemente; i++)
cout << *sir++ << endl;
}

void main(void)
{
float valori[5] = {1.1, 2.2, 3.3, 4.4, 5.5};// initializarea componentelor
tabloului
afiseaza(valori, 5);
}
Programare C/C++ 129

6.5.1.1. Pointeri la tablouri multidimensionale

Sa consideram următoarea declaraţie:


int vect [4] [2]; /* un tablou de tablouri de întregi * /
Operaţiile cu aceşti pointeri la tablouri multidimensionale sunt aceleaşi cu
cele de la pointeri pentru tablouri unidimensionale.
a) Valoarea unui pointer este adresa obiectului referit de acesta:
vect == & vect [0] , un vector de 2 întregi;
vect [0] == & vect [0] [0] , adresa primului element de tip int;
b) Adunarea unui pointer furnizează o valoare mai mare cu
dimensiunea obiectului referit:
vect +1 , va referi a doua pereche de întregi;
vect [0] +1 , va referi un singur obiect de tip int;
c) Dereferenţierea unui pointer furnizează valoarea reprezentată de
obiectul referit:
-vect[0] refera primul element, care este vect[0][0] şi deci
*(vect[0]) reprezintă valoarea lui vect [0] [0], o valoare int.
-*vect reprezintă valoarea primului element vect[0], dar acesta
este, el însuşi, un pointer la int, adică este adresa &vect[0]
[0].
-deoarece *vect==vect [0], iar *( vect [0] ) este echivalentul
cu vect [0][0], rezultă că ** vect == vect[0][0], care este de
tip int; deci vect este un pointer la un pointer şi trebuie
dereferenţiat de două ori pentru a furniza o valoare obişnuită.
Un pointer la un pointer este un exemplu de dubla indirectare.

Deci, in final, putem scrie:


vect [i] == &vect [i][0] = *(vect+i ) şi
*vect [i] == vect [i] [0] = *(*( vect+i) ) sau în general:
vect[n][m] == * ( * ( vect + n ) +m )

Pentru exemplificare se poate rula următorul program.

/* vect_ptr.c- exemple cu vector bidimensional si adrese */


#include <stdio.h>
main( )
{
int vect [4][2] = {{1,2}, {3,4},{5,6},{7,8}};
printf("Moduri de referire la adresele elementelor unui tablou:\n");

129
130 Programare C/C++
printf("\tAdresa primului element:\n");
printf("vect[0]=%u, &vect[0][0]=%u, *vect=%u, vect=%u\n",
vect[0], &vect[0][0], *vect, vect);
printf("\tAdresa celui de-al doilea element:\n");
printf("vect[0]+1=%u, &vect[0][0]+1=%u, *vect+1=%u,”
“&vect[0][1]=%u\n",
vect[0]+1, &vect[0][0]+1, *vect+1, &vect[0][1]);
printf("\tAdresa celui de-al treilea element:\n");
printf("vect[0]+2=%u, *(vect+1)=%u, &vect[1][0]=%u\n",
vect[0]+2, *(vect+1), &vect[1][0]);
printf("\tAdresa elementului din linia 3 coloana 2:\n");
printf("vect[0]+2*2+1=%u, *(vect+2)+1=%u, &vect[2][1]=%u\n",
vect[0]+2*2+1, *(vect+2)+1, &vect[2][1]);
printf("Adresa elementului din linia m, coloana n este:\n\t\t\t"
" *(vect + (m-1)*dimensiune_coloana) + n-1\n");
printf("\n");
printf("Moduri de referire la valorile elementelor unui tablou:\n");
printf("**vect = %u, *vect[0] = %u, vect[0][0]= %u\n",
**vect, *vect[0], vect[0][0]);
printf("**(vect+1) = %u, *(*vect+1) = %u\n", **(vect+1), *(*vect+1));
printf("vect[1][0] = %u, vect[0][1] = %u\n", vect[1][0], vect[0][1]);
printf("\tElementului din linia 3 coloana 2:\n");
printf("*(vect[0]+2*2+1)=%u, *(*(vect+2)+1)=%u, vect[2][1]=%u\n",
*(vect[0]+2*2+1),*(*(vect+2)+1), vect[2][1]);
printf("\n");
}

Pentru a declara variabila pointer compatibilă cu vect, va trebui să definim


un pointer la un tablou de 2 întregi (dimensiune 2): int (*pv)[2];
Trebuie precizate parantezele ( ) deoarece parantezele [ ] au prioritate
(precedenţă) mai mare decât *. Folosind parantezele vom aplica mai întâi
operatorul * creând un pointer la un vector de doi întregi. Dacă s-ar fi
utilizat declaraţia:
int *pve [2];
s-ar crea un vector de doi pointeri, *pve[2], iar precizând tipul int, rezultă
un vector de doi pointeri la tipul int.
Să considerăm următorul exemplu.

/* programul calculeaza suma elementelor de pe diagonala


principala a unei matrici, utilizand pentru adresarea
elementelor matricii un pointer,
Programare C/C++ 131
initializat pentru fiecare linie, cu adresa de inceput a liniei
respective
*/
#include <stdio.h>
#include <conio.h>
main()
{
int vect[10][10];
int suma = 0, *ptr, n, i, j;
clrscr();
printf ("Programul calculeaza suma elementelor de pe”
“ diagonala principala\n"
"utilizand pentru adresare pointeri \n");
printf ("Dimensiune matrice(patrata):");
scanf ("%d", &n);
for ( i = 0 ; i < n ; i++)
for ( j = 0 ; j < n ; j++)
{
printf ("vect[%d][%d]=", i+1, j+1);
scanf ("%d", &vect[i][j]);
}
for ( i = 0 ; i < n ; i++)
{
ptr = vect[i];
suma += *(ptr+i);
}
printf ("Suma= %d \n", suma);
}

6.5.2. Pointeri la şiruri de caractere

Una din aplicaţiile cele mai utilizate ale pointerilor la tablouri o


reprezintă pointerii la şiruri de caractere. Un pointer la un şir de caractere se
declară, de exemplu, astfel:
char *text_ptr;
iar declaraţia:
text_ptr = “Un numar de caractere “;
va asigna pointerul text_ptr la o constantă şir de caractere. Sau se poate
iniţializa de la declarare pointerul cu acest şir de caractere:
char *text_ptr= “Un sir de caractere “;

131
132 Programare C/C++
Nu trebuie confundat un pointer la caractere cu un tablou de
caractere. De exemplu pentru o declaraţie:
char text[80];
nu putem scrie o declaraţie de forma :
text = {“Aceasta initializare este o eroare “};
Singurul loc în care se poate face o astfel de atribuire la un tablou de
caractere este când acesta se iniţializează:
char text[80] = {“Aceasta initializare este corecta“};
Se pot defini şi tablouri care să conţină pointeri la şiruri de caractere; în
exemplul următor tabloul zile conţine 7 pointeri la cele 7 şiruri de caractere
ce conţin numele zilelor săptămânii.
main ( )
{int i;
char *zile[]= {“Luni“, ”Marti”, ”Miercuri”, ”Joi”, ”Vineri”, ”Sambata”,
”Duminica “};
/* tiparim zilele saptamanii */
for ( i = 0 ; i < 7 ; i++ )
printf(“ %s\n”, zile[i]);
}

Exemplul 2: copierea unui şir de caractere într–alt şir .


main ( )
{
char sir_sursa []= {“acesta este sirul de copiat”};
char sir_dest[50];
char *de_la , *catre ;
de_la = sir_sursa;
catre = sir_dest;
while ( *de_la )/* sau for ( ; *de_la !=’\0’ ; ++de_la, +
+catre) */
* catre ++ = *de_la++; /* *catre = *de_la; */
* catre = ‘\0 ‘; /* *catre =’\0’; */
printf (“sirul destinatie este : %s\n “, sir_dest”);
}

Pentru a determina lungimea unui şir de caractere pentru o variabilă


pointer la un şir de caractere putem utiliza secvenţa:
char * sir = “lungimea unui sir de caractere”;
char * cptr = sir ;
while (*cptr)
++ cptr;
Programare C/C++ 133
printf (“lungimea sirului este :%i \n “, cptr - sir);

Exemplul 3:
/*afisarea sirului incepand de dupa primul spatiu */
main ( )
{
char s[80];
char *ptrc = s;
int i;
printf (“introduceti un sir de caractere”);
gets(s); /* citeste un sir de caractere pana cand se tasteaza ‘\n’ */
/* se determina primul spatiu sau sfarsit de sir */
if ( s[0] != ‘\0’ )
{
for ( i = 0 ; s[i] && s[i] ! = ‘ ‘; i++);
/* se elimina primul spatiu intalnit */
if (s[i] == ‘ ‘)
i++;
ptrc = &s[i];
printf (ptrc); /* sau puts (ptrc), tipareste sirul de dupa
primul blanc */
}
}

sau în locul ciclului for se poate utiliza următoarea secvenţă cu acelaşi efect:
if ( *ptrc )
{
while ( *ptrc != ‘ ‘ && *ptrc )
ptrc++;
if ( *ptrc == ‘ ‘ )
ptrc++;
puts (ptrc);
}

6.5.3. Pointeri la structuri

În mod asemănător definirii de pointeri la tablouri pot fi definiţi şi


pointeri la structuri. Aceştia sunt utilizaţi din cel puţin trei motive:
- este mai uşor de manipulat un pointer la o structură decât
manipularea structurii;

133
134 Programare C/C++
- în unele implementări mai vechi o structură nu poate fi
transmisă ca argument al unei funcţii, în timp ce un pointer poate
fi transmis; de altfel este mai economic, din punct de vedere al
memoriei, să se transmită un pointer decât întreaga structură;
- multe reprezentări de date sunt, de fapt, structuri ce conţin
pointeri la alte structuri .
Să considerăm declaraţia unei structuri:
struct data
{int zi ;
int luna ;
int an;
};
şi declararea unei variabile şi a unui pointer la acest tip:
struct data astazi;
struct data *data_ptr;
Putem iniţializa acest pointer să refere variabila astazi, astfel:
data_ptr = &astazi ;
Odată asignat pointerul putem avea acces, indirect, la orice componentă
(membru) astfel:
(* data_ptr).zi=28;
În cazul pointerilor la structuri pe lângă operatorul de selecţie (.), utilizat
anterior, se poate utiliza pentru selecţia unui câmp şi operatorul pointer la
structură -> (linie urmată de semnul mai mare), care permite o exprimare
mult mai clară:
data_ptr -> zi =28;
Exemplul 1:
Să reprezentăm informaţiile referitoare la o persoană (nume, prenume,
vârsta, ocupaţie, venit) utilizând o structură şi să accesăm structura utilizând
un pointer:

/*persoana.c – utilizare de pointer la o structura */


#include <stdio.h>
#define LUNG 20
struct s_nume {
char nume [LUNG];
char prenume [LUNG];
};
struct tip {
struct s_nume apel;
int varsta ;
char ocupatie [LUNG] ;
Programare C/C++ 135
float venit ;
};
main ( )
{
static struct tip persoana [2]={
{{“Ionescu “, “Ilie”}, 30, ”inginer”, 150.0},
{{“Georgescu “, “ George “}, 35 , “profesor”, 130.0}
};
struct tip *el ; /* pointer la structura */
printf (“adresa #1:%u, #2:%u\n”, &persoana[0], &persoana[1]);
el = &persoana [0];/* sau el = persoana, initializam pointerul */
printf(“%s %s, %d ani, ocupatie :%s, venit: % 5.2f\n”,
el -> apel.nume, el -> apel.prenume,
el -> virsta, el -> ocupatie, el->venit);
el ++;
printf (“%s %s, %d ani, ocupatie :%s, venit:%5.2f\n”,
el -> apel.nume, el -> apel.prenume,
el -> virsta, el -> ocupatie, el -> venit);
}

O structură poate conţine drept componente chiar pointeri. Să


consideram următorul exemplu.
Exemplul 2:
/*strptr.c – structura ce contine pointeri */
#include <stdio.h>
main ( )
{
struct int_ptr
{
int *p1 ; /* se defineste o structura ce contine */
int *p2 ; /* doi pointeri */
};
struct int_ptr ptr ;
int i1 = 1000, i2;
ptr.p1 = &i1;
ptr.p2 = &i2;
*ptr.p2 = i1-100;
printf (“i1 = %i = *ptr.p1 = %i\n “, i1, *ptr.p1);
printf (“i2 = %i = *ptr.p2 = %i\n “, i2, *ptr.p2);}

ptr .p1 ------- -------> 1000 i1

135
136 Programare C/C++

.p2 ------- -------> 900 i2

Conceptele de pointeri la structuri şi structuri ce conţin pointeri sunt


foarte puternice şi sunt utilizate pentru a crea structuri de date sofisticate
cum sunt: listele, liste dublu înlănţuite şi arbori.
Să considerăm structura următoare :
struct intrare
{
int valoare ;
struct intrare *urmator ;
};
Această structură, denumită intrare, are două componente: prima
este valoare, de tip întreg, iar cea de-a doua, denumită urmator, este definită
ca pointer la o structură de acelaşi tip, intrare.
Dacă definim trei variabile de acest tip:
struct intrare n1, n2, n3;
putem construi o structură de lista, prin legarea (conectarea) celor trei
variabile astfel:
n1.urmator = &n2; /* iniţializarea legăturii în listă
*/
n2.urmator = &n3;

Deci se obţine următoarea listă:

n1 -------> n2 --------> n3
valoare valoare valoare
urmator urmator urmator

Pentru a actualiza câmpul valoare se utilizează atribuiri:


n1.valoare = 100;
n2.valoare = 200;
cea de-a doua declaraţie e echivalentă cu:
( n1.urmator ) -> valoare = 200;
sau, întrucât cei doi operatori, de selecţie (.) şi pointer la structură (->) au
aceeaşi precedenţă şi sunt evaluaţi de la stânga la dreapta:
n1.urmator -> valoare = 200;
Operaţiile obişnuite cu liste sunt: căutarea, eliminarea şi inserarea. Pentru
lista anterioară eliminarea elementului n2, se poate face, simplu, prin
actualizarea câmpului urmator al lui n1, să refere ceea ce referea n2:
n1.urmator = n2.urmator; /* eliminare n2 din lista */
Programare C/C++ 137
sau
n1.urmator = &n3;
echivalentă cu prima, dar trebuie sa ştim ca n2 referea n3.
Pentru inserare să considerăm că vrem să inserăm între n2 şi n3 o nouă
componentă: n2_3. În acest caz noile referinţe între aceste elemente trebuie
modificate astfel:
n2_3.urmator = n2.urmator;
n2.urmator = &n2_3;
Cele două declaraţii trebuie executate în această ordine, deoarece
altfel s-ar pierde referinţa la n3, conţinută iniţial, în n2.urmator.
În memorie n2_3 nu va fi plasat între n2 şi n3; n2_3 poate fi oriunde
în memorie şi deci nu are o adresă fizică între n2 şi n3. Acesta, de fapt, este
şi unul dintre motivele principale pentru a utiliza metoda listelor înlănţuite
pentru a memora informaţie: intrările unei liste nu trebuie să fie memorate
secvenţial în memorie, ca în cazul elementelor unui tablou.
Pentru a defini corespunzător o listă mai trebuie specificate două
lucruri. Primul se referă la modalitatea de a cunoaşte unde se află începutul
listei, deci trebuie definit un pointer, de acelaşi tip, care se iniţializează cu
primul element din listă:
struct intrare *lista_ptr = &n1;
În al doilea rând trebuie specificat, într-un mod, ultimul element din
listă. Prin convenţie o valoare constantă 0 este utilizată în acest scop, ca
legătură a ultimului element, şi este cunoscută sub numele de pointerul
NULL.
De exemplu, în cazul nostru, dacă n3 este ultimul element din listă
vom marca aceasta prin memorarea pointerului NULL în referinţa
n3.urmator:
n3.urmator = 0; /* sau n3.urmator = NULL */
Întrucât dimensiunea unei liste nu este dinainte cunoscută şi nici nu
putem defini, de la început, toate elementele listei, înseamnă că trebuie
alocată memorie pentru fiecare element al listei, pe măsură ce aceasta este
construită. Această metodă de alocare de memorie, nu de la început, ci pe
durata execuţiei programului este denumită alocare dinamică de memorie.
Până acum alocarea de memorie necesară unui program era făcută
static, la declararea variabilelor, ca în exemplul următor:
int vector[100]; /* aloca 100 de locaţii de tip int, adică 200 de octeţi
*/
char text[ ]={“Programare C”}; /*se alocă memoria necesară,
adică 13 octeţi */
Deci pentru aceste declaraţii se realizează în mod automat alocarea
de memorie (statică). Pentru a aloca memorie, pe durata execuţiei unui
program, se utilizează două funcţii malloc( ) şi calloc( ):

137
138 Programare C/C++
- malloc( dimensiune), alocă un spaţiu continuu de ‘dimensiune’
octeţi, returnând un pointer la începutul blocului alocat, dacă s-a
realizat alocarea cu succes, sau pointerul NULL dacă nu se poate
aloca memorie;
- calloc (n , dim), alocă spaţiu continuu pentru n elemente de date,
fiecare necesitând ‘dim’ octeţi. Spaţiul alocat este iniţializat cu zero.
Dacă s-a alocat, returnează un pointer la începutul spaţiului alocat,
altfel returnează pointerul NULL.
Când se termină operarea cu memoria ce a fost alocată dinamic, cu funcţiile
malloc() sau calloc(), spaţiul de memorie poate fi eliberat (cedat sistemului)
prin aplicarea funcţiei free():
- free (pointer), eliberează blocul de memorie referit de pointer, ce a
fost alocat de una dintre funcţiile calloc( ) sau malloc( ).
Aceste funcţii se află în fişierul antet standard <stdlib.h>.
În C++ pentru operaţii de alocare / eliberare de memorie se
utilizează funcţiile (operatorii) new şi delete.
Funcţia new încearcă să creeze un obiect de tip “nume” prin
alocarea, dacă e posibil, de sizeof ( nume ) octeţi în memoria disponibilă
(denumită “heap”). Durata de alocare e până la eliberare, cu operatorul
delete sau până la sfârşitul programului.
Dacă alocarea se face cu succes, new returnează un pointer către
această zonă, altfel un pointer nul. Trebuie testat pointerul returnat, la fel ca
la funcţia malloc. Dar, spre deosebire de aceasta, new calculează
dimensiunea spaţiului de memorie necesar (sizeof(nume)) fără a fi nevoie de
utilizarea explicită a operatorului. De asemenea pointerul returnat este de
tipul corect, “pointer la nume”, fără să fie necesară prezenţa operatorului de
conversie: nume malloc( sizeof( nume)). Sintaxa sa de utilizare este:
ptr_nume = new nume;
respectiv
delete ptr_nume;
Dacă nume este un tablou pointerul returnat de “new” referă primul
element al tabloului. Pentru tablouri multidimensionale, create cu new
trebuie specificate toate dimensiunile:
mat_ptr = new int[3][10][12];
Un alt avantaj al lui new faţă de malloc este iniţializarea opţională.
În absenţa inţializărilor explicite obiectul creat cu new va conţine date
nedefinite. Obiectele, altele decât tablouri, pot fi iniţializate cu o expresie
adecvată între paranteze:
int_ptr = new int(3);
Pentru exemplificarea acestor funcţii să considerăm următorul
exemplu. Să se aloce, dinamic, memorie pentru 40 de valori întregi, care
Programare C/C++ 139
apoi vor fi iniţializate şi tipărite, după care se eliberează memoria înapoi
sistemului.

/* exemplu de alocare dinamica de memorie */


#include <stdio.h>
#include <stdlib.h> /* sau alloc.h */
main( )
{ int *ptr, val;
ptr = (int*) malloc (40*sizeof (int) );
if (!ptr) /*testam daca s-a alocat memorie */
printf (“Memorie plina! Eroare de alocare! \n”);
else
{
for ( val = 0 ; val < 40 ;++val )
*(ptr + val) = val;
for ( val = 0 ; val < 40 ; ++val)
printf (“%d ”, *(ptr + val) );
free ( ptr);
}
}

Observaţie: dacă memoria este plină, funcţia malloc( ) va returna un pointer


NULL, care nu este un pointer valid.
Dacă se utilizează, din greşeală, înainte de iniţializarea pointerului, o
atribuire de forma:
*ptr = malloc (100); /* în loc de ptr = malloc (100) */
se va pierde controlul asupra calculatorului, deoarece se va scrie (aloca)
peste tabela de vectori de întrerupere (la adresa 0). Motivul este că acest cod
nu atribuie lui ptr adresa pe care o returnează malloc( ), ci atribuie adresa la
locaţia de memorie pe care o referă ptr, care este 0 (deoarece nu a fost
iniţializat).
De fapt acelaşi lucru se întâmplă şi pentru următoarele declaraţii:
int *x;
*x = 100; /* atribuire la un pointer neiniţializat */
Pointerul x este iniţializat cu 0, sau altă valoare, iar atribuirea valorii
100 se va face la această adresă necunoscută, care va distruge, probabil, cod
sau date din program.
Revenind la liste, să considerăm următoarea problemă: să se
construiască o listă de tip coadă, ce conţine valori întregi, care apoi să fie

139
140 Programare C/C++
tipărită, bineînţeles în ordinea construită. Construirea listei va lua sfârşit
când se introduce de la consolă valoarea zero.

/*
coada.c - programul construieste, apoi tipareste, o lista de valori
intregi, de tip coada
*/
#include <stdio.h>
#include <stdlib.h>
main()
{
struct intrare
{
int valoare;
struct intrare *urmator;
};
struct intrare *start, *ptr_lista;
int valoare;
start = NULL;
printf ("Introduceti un sir de valori, terminat cu 0.\n");
printf ("Valoare:");
scanf ("%d", &valoare);
while ( valoare )
{
if ( start == NULL )
{
start = ( struct intrare * ) malloc( sizeof (struct intrare ));
/* se poate utiliza si functia new in loc de malloc */
start-> valoare = valoare;
start-> urmator = NULL;
ptr_lista = start;
}
else
{
ptr_lista ->urmator=(struct intrare*)
malloc(sizeof(struct intrare));
if ( ptr_lista -> urmator != NULL ) /* se adauga la lista*/
{
ptr_lista = ptr_lista -> urmator;
ptr_lista -> valoare = valoare;
ptr_lista -> urmator = NULL;
Programare C/C++ 141
}
else
exit (0);
}
printf ("Valoare:");
scanf ("%d", &valoare);
}
/* afisare lista */
ptr_lista = start; /* initializare adresa de inceput a listei */
if ( ptr_lista != NULL )
{
do
{
printf ("%d -> ", ptr_lista -> valoare);
ptr_lista = ptr_lista -> urmator;
} while ( ptr_lista != NULL );
printf ("NULL\n");
}
else
printf ("Lista vida ( NULL )\n");
}

6.5.4. Pointeri la pointeri

Un tablou de pointeri este acelaşi lucru cu pointeri la pointeri. Un


pointer la un pointer este o formă de indirectare multiplă, sau un lanţ de
pointeri, de forma:
Pointer Pointer Variabilă
adresă --------> Adresă --------> valoare

Această indirectare se poate extinde oricât dorim, dar sunt puţine


cazuri în care este necesar mai mult de un pointer la pointer; indirectarea
excesivă este dificil de urmărit şi poate fi uşor de greşit. Nu trebuie
confundată indirectarea multiplă cu listele înlănţuite, descrise anterior, şi
care sunt utilizate în aplicaţii de baze de date.
Pentru a declara o astfel de variabilă, pointer la pointer, se plasează
un asterisc suplimentar în faţa numelui variabilei. De exemplu:
float **ptr_ptr;
Deci declaraţia aceasta are semnificaţie de “pointer la un pointer de
tip float”, şi nu de pointer la un număr de tip float.

141
142 Programare C/C++
Accesul la valoarea referită, pe care un pointer la un pointer o referă
indirect necesită, de asemenea, aplicarea de două ori a operatorului asterisc:

#include <stdio.h>
main( )
{
int x, *p, **q;
x = 10;
p = &x;
q = &p;
printf (“%d”, **q); /*tipareste valoarea lui x referita indirect de q*/
}

6.6. Tipuri definite de utilizator (typedef)

În C, asemănător cu alte limbaje, programatorul are posibilitatea de a


asocia un nume alternativ la un tip de date sau de a defini un nume propriu
pentru un tip. Pentru aceasta se utilizează declaraţia typedef.
De exemplu, declaraţia :
typedef int CONTOR ;
defineşte numele CONTOR echivalent cu tipul de date int. În continuare se
pot declara variabile de acest "nou" tip :
CONTOR i , j ; /* variabile de tip int */
În multe privinţe o declaraţie typedef poate fi substituită, echivalent,
cu o declaraţie #define, corespunzătoare. În cazul anterior putem utiliza
declaraţia:
#define CONTOR int
pentru a avea acelaşi efect.
Cu toate aceste asemănări există următoarele trei diferenţe :
1) Spre deosebire de #define, typedef este limitat în a da nume
simbolice numai pentru tipuri de date ;
2) Declaraţia typedef este realizată (interpretată) de compilator
şi nu de către preprocesor (ca în cazul lui #define);
3) Declaraţia typedef oferă mai multă flexibilitate, decât
#define, când se asociază nume la tipuri de date derivate .

Alt exemplu :
typedef float REAL ; /* redefinim tipul float ca REAL */
REAL x , y [10] , z ;
Programare C/C++ 143
Se utilizează, de obicei, litere mari în definiţia typedef pentru a reaminti
utilizatorului că numele tipului este, de fapt, o abreviere simbolică.
Scopul acestei definiţii depinde de plasarea declaraţiei typedef :
- dacă este în interiorul unei funcţii, are scop local, limitat la
aceea funcţie;
- dacă este în exteriorul unei funcţii, atunci scopul este global.
Alte exemple de utilizare:
a) typedef char STRING [81] ;
defineşte un tip denumit STRING, care este un tablou de 81 de
caractere. În consecinţă se pot declara variabile de acest tip astfel:
STRING text , linie ;
Această declaraţie este echivalentă cu următoarea:
char text [81] , linie [81] ;
De remarcat, în acest caz, STRING nu poate fi definită echivalent cu o
declaraţie preprocesor #define.
b) typedef char *STRING_PTR;
defineşte un pointer de tip char. Variabilele de acest tip vor fi
declarate astfel:
STRING_PTR buffer;
c)se poate folosi această declaraţie şi pentru structuri, astfel:
typedef struct
{
double real ;
double imag ;
} COMPLEX ;
COMPLEX numar = { 1.0 , - 1.0 } , var_compx ;
/* se declară două variabile de tip complex, prima iniţializată, cea de-a
doua nu */
d) typedef struct
{
int ziua ;
int luna ;
int anul ;
} DATA ;
DATA zile_nastere [100];
/* se declară vectorul zile_nastere, care conţine 100 de componente de
tip DATA */
Pentru programele ce conţin mai multe module, se recomandă de a plasa
definiţiile typedef utilizate de acestea, într-un fişier separat care poate fi
inclus în fiecare fişier sursă (modul) cu o declaraţie #include.
Trebuie reţinut că utilizarea declaraţiei typedef nu defineşte, de fapt, un
nou tip, ci numai asociază un nume nou tipului respectiv.

143
144 Programare C/C++
Exemplu: să se calculeze distanţa dintre două puncte specificate prin
coordonatele lor în plan.
#include <stdio.h>
#include <math.h>
main ( )
{
typedef struct
{
double x ;
double y;
}PUNCT ;
PUNCT P1, P2 ; double difx , dify;
printf (“Coordonatele punctului P1 ( x , y) : \n “) ;
scanf (“%lf%lf “, &P1.x, &P1.y);
printf (“Coordonatele punctului P2 (x , y ) : \n“);
scanf (“%lf %lf “ , &P2.x , &P2.y ) ;
difx = P1.x – P2.x ;
dify = P1.y – P2.y ;
printf (“Distanţa dintre cele două puncte este %5.2lf\n “ ,
sqrt ( pow (difx , 2 ) + pow (dify , 2 ) ) );
}
Programare C/C++ 145

7. Funcţii

În general, un program este alcătuit dintr-un ansamblu de module,


fiecare modul corespunzând unei subprobleme a problemei date. La rândul
lor, aceste subprobleme pot fi descompuse în alte subprobleme, acest proces
de descompunere progresivă putând continua până se obţin probleme
simple, alcătuite doar din câteva instrucţiuni. Identificarea acestor
subprobleme, care vor fi mai simplu tratat, presupune determinarea unor
prelucrări similare efectuate asupra unor informaţii diferite, sau împărţirea
problemei în subprobleme, mai simple, dar având semnificaţii clare.
Descrierea în limbajul C a unei astfel de subprobleme constituie o funcţie.
Deci o funcţie este o unitate de sine stătătoare de cod (instrucţiuni)
proiectată pentru a realiza (rezolva) o anumită problemă (task). O funcţie C
joacă acelaşi rol pe care îl au funcţiile, subrutinele şi procedurile în alte
limbaje de programare, deşi detaliile pot fi diferite.
Aceste funcţii sunt organizate, din punct de vedere funcţional, într-o
structură ierarhică; în vârful acestei ierarhii se află funcţia principală (main),
sau modulul principal, care apelează modulele de la nivelurile inferioare. O
funcţie poate fi apelată nu numai de către funcţia (modulul) principal, dar şi
de alte funcţii.
Avantajele utilizării funcţiilor constau în micşorarea dimensiunilor
programelor, modularizarea acestora, urmărirea mai uşoară a programelor,
posibilitatea testării şi a depanării separate a funcţiilor, ceea ce permite
localizarea mai simplă a unor erori, etc.
De fapt, în spatele unui program, bine scris în C, se află aceste
elemente fundamentale: funcţiile.
Folosirea funcţiilor presupune existenţa unor mecanisme de definire
şi apelare a acestora. Pentru identificarea unei funcţii, acesteia i se asociază
un nume, distinct de numele altor funcţii. Pentru a specifica informaţiile pe
care funcţia le schimbă cu o altă funcţie, sau modulul principal, de care va fi
apelată, funcţiei i se asociază o listă de parametri. În cadrul definirii unor
funcţii, aceşti parametri sunt denumiţi parametri formali (întrucât au rol
descriptiv; descriu operaţiile efectuate de funcţie). În cadrul apelului unei
funcţii, parametrii se numesc actuali sau efectivi, şi aceştia îi vor înlocui pe
cei formali, în momentul apelului.
Şi în exemplele utilizate anterior s-au folosit funcţii, de exemplu
pentru citirea, respectiv scrierea datelor s-au utilizat funcţiile: scanf() şi
printf ().
Pe lângă aceste funcţii de bibliotecă, un program C poate conţine o
singură funcţie, main(), ca în exemplele prezentate până acum.

145
146 Programare C/C++
Spre deosebire de Pascal, o funcţie C nu poate conţine alte funcţii. Funcţiile
definite de utilizator pot fi incluse într-unul sau mai multe fişiere – antet,
care vor fi compilate separat.
În general, o funcţie poate realiza atât acţiuni cât şi furniza rezultate.

7.1. Definirea funcţiei şi transferul parametrilor

Formatul general pentru definirea unei funcţii este:


tip_returnat nume_funcţie (tip1 param1, tip2 param2, … )
{
declaraţii_variabile_locale
/* corpul funcţiei:
prelucrările efectuate de aceasta */
...........
return (expresie);
}
Se defineşte funcţia “nume_funcţie”, care returnează o valoare de un
anumit tip – “tip_returnat” (tipul expresiei) – şi are parametrii formali
param1, param2, … , declaraţi de tipurile: tip1, tip2, … .
Dacă funcţia nu returnează o valoare, “tip_returnat” nu este
specificat sau este void (adică nimic). Dacă funcţia returnează o valoare de
tip int, “tip_returnat” poate fi omis, deşi specificarea tipului int, ca valoare
returnată, este o mai bună practică de programare (şi se recomandă, pentru a
pune în evidenţă valoarea returnată). Dacă între paranteze se specifică
numai void, funcţia nu are argumente.
Dacă se utilizează puncte (…) ca ultimul (sau singurul) parametru în
lista de parametri, funcţia va lua un număr variabil de argumente, ca în
declaraţia:
int printf (char*format, …)
{
....
}
Declaraţiile pentru argumente de tip tablou cu o singură dimensiune
nu trebuie să specifice numărul de elemente din tablou. Pentru tablouri
multidimensionale, mărimea fiecărei dimensiuni, mai puţin prima, trebuie
specificată.
Dacă funcţia returnează o singură valoare, tipul acesteia precede
numele funcţiei, iar valoarea returnată este cea indicată de declaraţia return
(expresie);. Parantezele ce includ expresia sunt opţionale, dar ele sunt
folosite de mulţi programatori.
Programare C/C++ 147
Spre deosebire de Pascal sau Fortran în care sunt diferite proceduri
(subrutine) ce returnează oricâte valori, inclusiv nici una, şi funcţii care
returnează o singură valoare, limbajul C nu face deosebire între acestea. În
C sunt doar funcţii, care pot, opţional, returna o singură valoare, în modul
descris anterior.
O funcţie care returnează o valoare poate fi utilizată în expresii, în
timp ce o funcţie de tip void nu are o valoare şi nu poate fi utilizată într-o
expresie.
Formatul general al apelului unei funcţii este:
nume_funcţie (arg1, arg2, …);
Valorile arg1, arg2, … sunt transmise ca argumente efective pentru
funcţie. Dacă funcţia nu are argumente, sunt specificate, obligatoriu, cele
două paranteze ( ).
Dacă se apelează o funcţie care este descrisă (definită), după apelul
său, sau definită în alt fişier trebuie inclusă o declaraţie a prototipului
funcţiei, ce are formatul general:
tip_returnat nume_funcţie (tip1 param1, tip2 param2, … );
Această declaraţie precizează compilatorului tipul valorii returnate
de funcţie, numărul de argumente pe care le ia şi tipul fiecărui argument. De
exemplu:
long double power (double x, int n);
Numele argumentelor dintre paranteze pot fi omise, întrucât
important este tipul argumentelor şi nu numele lor:
long double power (double, int);
deoarece numele sunt nesemnificative în acest moment.
Dacă compilatorul a întâlnit anterior definiţia funcţiei sau o
declaraţie de prototip pentru funcţie, fiecare argument va fi automat
convertit pentru a se potrivi cu tipul aşteptat de funcţie, când aceasta este
apelată.
Dacă nu a fost întâlnită nici definiţia funcţiei, nici o declaraţie de
prototip, compilatorul va presupune că funcţia returnează o valoare de tip
int, şi va converti automat toate argumentele de tip float la tipul double, iar
cele de tip char sau short int la tipul int, înainte de a le transmite funcţiei.
Celelalte tipuri de argumente ale funcţiei vor fi transmise fără conversie.
Toate argumentele pentru o funcţie pot fi transmise prin valoare;
adică valorile transmise, prin variabile sau expresii, sunt utilizate pentru a
iniţializa argumentele formale ale funcţiei, şi deci valorile argumentelor
efective nu pot fi modificate de funcţie. Funcţia nu are acces niciodată la
argumentele actuale (efective) ale apelului, dacă sunt transmise prin valoare.
Valorile pe care le manipulează funcţia sunt propriile sale copii, locale, care
sunt memorate în stivă. Odată ce funcţia se termină stiva este descărcată,
deci aceste valori locale se pierd. Din acest motiv argumentele actuale nu

147
148 Programare C/C++
sunt modificate, ceea ce înseamnă că programatorul nu trebuie să salveze şi
să refacă valorile argumentelor, la apelul unei funcţii.
Transferul parametrilor prin valoare nu este potrivit întotdeauna:
- când se transferă un obiect mare: necesită timp şi spaţiu
pentru a aloca şi copia obiectul în stivă, care poate fi prea mare
pentru aplicaţii în timp real;
- când valorile argumentelor trebuie să fie modificate.
Dacă un pointer (referinţă) este transmis unei funcţii, funcţia poate
modifica valorile referite de pointer, dar nu poate modifica valoarea acestui
pointer.
Utilizarea referinţei (pointerului) unui argument pentru transmiterea
acestuia se impune când funcţia trebuie să returneze rezultate. De asemenea
poate fi utilizată în situaţiile când se transmite un obiect de dimensiuni mari,
când se transmite doar adresa obiectului; în locul copierii întregului obiect,
dacă se transmite argumentul prin valoare.
Pentru a evita modificarea, din greşeală, a pointerului (referinţei), o
bună practică este aceea de a declara argumentul de tip const. Aceasta
permite compilatorului să prevină modificarea neintenţionată a acestuia, mai
ales într-un lanţ de apeluri de funcţii, la care argumentul este transmis.
Pentru început iată câteva exemple simple.
1)
#include <iostream.h>
// functie care tipareste un mesaj fix, deci nu primeste nici un
// parametru
void scrie_mesaj(void)
{
cout<<"Mesaj scris din functie"<<endl;
}

void main(void)
{
cout<<"Despre chemarea functiei"<<endl;
scrie_mesaj();
cout<<"Mesaj scris din program"<<endl;
}

2)
// functie care tipareste, de aceasta data, un sir de caractere transmis
ca
// parametru
#include <iostream.h>
Programare C/C++ 149

void afis_sir(char sir[])


{
cout << sir << endl;
}

void main(void)
{
afis_sir("Salut, C++!");
afis_sir("Mesaje afisate de o functie C++");
}

3)
// functia determina lungimea unui sir de caractere transmis ca
// parametru
#include <iostream.h>

int lung_sir(char sir[])


{
int i;

for (i = 0; sir[i] != '\0'; i++); // Salt la urmatorul caracter caracter


return(i); // returneaza lungimea sirului
}

void main(void)
{
char titlu[] = " Introducere in C++";
char tema[] = "Despre siruri de caractere";

cout << "Sirul " << titlu << " contine " << lung_sir(titlu) <<
" caractere" << endl;
cout << "Sirul " << tema << " contine " << lung_sir(tema) <<
" caractere" << endl;
}

4)
// functie care calculeaza suma a doua numere, deci returneaza o
singura valoare
// prin numele functiei
#include<iostream.h>

149
150 Programare C/C++
int aduna(int x,int y)
{
return(x+y);
}
void main(void)
{
cout<<"300+200="<< aduna(300,200)<<endl;
cout<<"10-1="<< aduna(10,-1)<<endl;
}

Alte exemple:
1) Se va scrie un program care citeşte perechi de numere întregi şi
afişează cel mai mare divizor comun pentru fiecare pereche. Se va
utiliza o funcţie pentru determinarea celui mai mare divizor comun.
Programul se va executa ciclic până se citeşte o valoare nulă.
Transmiterea parametrilor se va face prin valoare, întrucât funcţia
primeşte doi întregi şi returnează unul singur - cel mai mare divizor
comun.
/* programul determina cel mai mare divizor comun pentru
perechi de numere intregi, introduse de la tastatura, pana se
introduce o valoare egala cu zero
*/
#include <stdio.h>
int cmmdc (int a, int b)
{
int rest;
while ( b != 0 )
{
rest = a % b;
a = b;
b = rest;
}
return ( a );
}
main ()
{
int cmmdc (int a, int b);
int x, y;
printf ("Numerele :");
scanf ("%d%d", &x, &y);
while ( ( x*y ) != 0 )
Programare C/C++ 151
{
printf ("Cel mai mare divizor comun este :%d\n",
cmmdc (x,y));
printf ("Numerele : ");
scanf ("%d%d", &x, &y);
}
}

2) Tipărirea unui număr întreg în toate bazele de la 2 la 16 (transmitere


tot prin valoare)
/* Programul afiseaza valorile unui numar intreg in toate bazele de la
2 la 16, utilizand pentru conversia intr-o baza data ca parametru o
functie (conversie).
*/
#include <stdio.h>
#define LUNG_MAX 32
const int baza_max = 16;
main()
{
long int numar;
int domeniu;
void conversie (long int valoare, int baza);
printf ("Programul afiseaza valoarea unui numar intreg in \n"
"toate bazele de la 2 la 16 \n");
printf ("Valoarea de convertit:");
scanf ("%li", &numar);
for (domeniu=2 ; domeniu <= baza_max ; ++domeniu)
{
conversie (numar, domeniu);
printf (" /baza: %d \n", domeniu);
}
}
void conversie (long int valoare, int baza)
{
char cifra[LUNG_MAX];
int index = 0;
if (valoare < 0)
{
printf("-");
valoare = -valoare;
}

151
152 Programare C/C++
do
{
if (valoare % baza < 10)
cifra[index] = '0' + valoare % baza;
else
cifra [index] = 'A' + valoare % baza-10;
index ++;
valoare /= baza;
} while (valoare > 0);
for (--index ; index >= 0; --index)
printf ("%c", cifra[index]);
}

3) Să se scrie un program care calculează şi afişează sumele parţiale ale


seriei armonice:
S(n) = 1 + 1/2 + 1/3 + … + 1/n ;
pentru diferite valori ale lui n. Rezultatele vor fi afişate sub forma unor
numere raţionale de tip fracţie ireductibilă a/b, unde a şi b sunt întregi.
Pentru a rezolva această problemă putem folosi două funcţii:
- o funcţie pentru simplificarea unei fracţii prin cel mai mare
divizor comun (utilizând algoritmul lui Euclid); spre deosebire de
funcţia din primul exemplu, aceasta primeşte fracţia şi o
returnează simplificată;
- o funcţie care adună două fracţii:
a1/b1 + a2/b2 = (a1*b2 + b1*a2) / (b1*b2) ;
şi va returna fracţia simplificată în prima din cele două fracţii.
Cele două funcţii vor utiliza transmiterea parametrilor prin
referinţă, deoarece returnează câte două valori: numărătorul şi
numitorul fracţiei.
/* programul calculeaza si afiseaza sumele partiale ale seriei:
1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + . . . + 1/n
afisarea se face sub forma unor numere rationale, de tip fractii
ireductibile a/b.
Se utilizeaza doua functii:
- prima aduna doua fractii, si returneaza fractia rezultata;
- a doua simplifica o fractie, si o returneaza;
Transferul parametrilor se face prin referinta ( pointeri ).
*/
#include <stdio.h>
main()
{
Programare C/C++ 153
int nr_linii = 0;
long int numarator, numitor, i, n;
void aduna_fractii (long int *pa1, long int *pb1,
long int pa2, long int pb2);
void simplifica (long int *pa, long int *);
printf("\n Introduceti nr.");
scanf("%li", &n);
numarator = 1;
numitor = 1;
printf("\n");
printf("%5li/%-5li ", numarator, numitor);
for ( i = 2 ; i <= n ; ++i)
{
aduna_fractii ( &numarator, &numitor, 1, i);
simplifica ( &numarator, &numitor);
printf("%5li/%-5li ", numarator, numitor);
nr_linii++; /* numarul de fractii afisate pe o linie */
if (nr_linii % 5 == 0)
printf("\n");
}
}
void aduna_fractii ( long int *pa1, long int *pb1,
long int a2, long int b2)
{
*pa1 = (*pa1)*b2+(*pb1)*a2;
*pb1 = (*pb1)*b2;
}
void simplifica (long int *pa, long int *pb)
{
long int ca, cb, rest;
ca = *pa;
cb = *pb;
while ( cb != 0 )
{
rest = ca % cb;
ca = cb;
cb = rest;
}
*pa /= ca;
*pb /= ca;
}

153
154 Programare C/C++

Să se modifice programul, înlocuind funcţia “simplificare” cu


funcţia c.m.m.d.c., definită anterior;

4) Să se scrie un program care desenează un număr n de pătrate


(dreptunghiuri) incluse unele în altele.

#include <graphics.h>
#include <stdio.h>
int initializare( int *pxmax, int *pymax )
/* se initializeaza modul grafic, prin */
{
int gdriver = DETECT, gmode, errorcode;
/* initializare variabile grafice si locale */
initgraph( &gdriver, &gmode, "");
/* citeste rezultatul initializarii */
errorcode = graphresult();
/* se testeaza daca a aparut o eroare, la initializare, si se
tipareste un mesaj de eroare impreuna cu codul de eroare */
if (errorcode != grOk)
{
printf("Eroare de initializare a modului grafic: %s\n",
grapherrormsg( errorcode ) );
printf("Apasati o tasata pentru a termina programul.");
getchar();
return(1);
}
/* se seteaza culoarea cu care se deseneaza la valoarea maxima */
setcolor(getmaxcolor());
/* se determina dimensiunile maxime ale ecranului */
*pxmax = getmaxx();
*pymax = getmaxy();
return 0;
}
void desencub ( int xss, int yss , int xdj , int ydj )
{
line ( xss , yss , xdj , yss );
line ( xss , yss , xss , ydj );
line ( xss , ydj , xdj , ydj );
line ( xdj , yss , xdj , ydj );
}
Programare C/C++ 155
main()
{
int xmax, ymax, midx , midy , i;
char mesaj [25];
if ( initializare( &xmax, &ymax) == 0 )
{
for ( i = 0; i <= 20 ; i += 5)
desencub(11 *i, 6 *i, xmax - 11*i , ymax - 6*i);
/* se tiparesc dimensiunile ecranului, in modul grafic in centrul
acestuia*/
midx = xmax/2;
midy = ymax /2;
moveto ( midx-80 , midy );
sprintf (mesaj,"dim ecran = %d * %d",xmax,ymax);
outtext (mesaj);
/* se deseneaza o linie pe diagonala principala a ecranului */
line(0, 0, xmax, ymax);
/* se inchide modul grafic */
getchar();
closegraph();
}
}

7.2. Variabile locale şi globale

Funcţiile pot comunica (transmite parametri) şi prin intermediul


variabilelor globale. O variabilă globală este o variabilă a cărei valoare
poate fi accesată de orice funcţie, din cadrul unui program. Variabilele
globale, pentru a putea fi accesate de orice funcţie, sunt declarate în
exteriorul acestor funcţii, astfel încât nu “aparţin” unei anumite funcţii.
În programul următor, prezentat ca exemplu, toate funcţiile utilizate
nu au nici un parametru; ele utilizează pentru efectuarea prelucrărilor şi
transmiterea valorilor variabilele globale: numar_convertit[ ], numar şi
baza.
Variabilele globale sunt definite primele în program. Utilizarea lor
poate fi acceptată în programele în care multe funcţii trebuie să aibă acces la
valorile aceleiaşi variabile. În acest mod se reduce numărul de argumente
necesare a fi transmise unei funcţii.
Dezavantajul utilizării variabilelor globale constă în reducerea
generalităţii funcţiei, deoarece se referă la o anumită variabilă globală; de

155
156 Programare C/C++
asemenea, în unele cazuri, aceasta diminuează şi claritatea programului.
Funcţiile ce utilizează variabilele globale nu mai pot fi utilizate în cadrul
altor module, deoarece ele depind de context: nume variabile globale, tipul
lor, etc.
Variabilele declarate în cadrul unei funcţii (sau bloc) sunt variabile
locale ale funcţiei (sau blocului). Ele sunt definite numai în domeniul
funcţiei (blocului) reprezentat de corpul funcţiei sau blocului respectiv,
inclusiv blocurile incluse în acestea, exceptând blocurile interioare acestora
în care variabilele au fost redeclarate.
Variabilele definite în cadrul unei funcţii sunt denumite şi variabile
locale automatice, deoarece ele sunt “automat” create la apelul funcţiei şi
valorile lor sunt locale funcţiei.
Variabilele locale, declarate într-o funcţie (bloc) există numai pe
durata execuţiei acelei funcţii (bloc), fiind create la intrarea în funcţie, prin
alocare de memorie, şi distruse la ieşirea din funcţie, prin eliberarea
memoriei ocupate.
Variabilele declarate în exteriorul unui bloc pot fi referite din cadrul
blocului; pentru acest bloc, variabilele acestea sunt nelocale (sau globale).
Într-un bloc (al unei funcţii) sunt accesibili toţi identificatorii definiţi
în acel bloc, precum şi cei din blocurile în care acesta este inclus. Pentru
oricare din aceşti identificatori, definiţia valabilă în blocul considerat este
cea aflată în blocul cel mai apropiat.
Parametrii formali declaraţi în prototipul funcţiei pot fi folosiţi
pentru a referi argumentele funcţiei oriunde în cadrul corpului funcţiei.
Exemplu de utilizare a variabilelor globale: programul converteşte un număr
întreg într-o altă bază.

/*
Programul afiseaza un numar intreg intr-o baza ,folosind o
functie pentru conversia in baza specificata;
Se utilizeaza pentru transferul parametrilor intre functii:
VARIABILE GLOBALE
*/
#include <stdio.h>
int numar_convertit[16];
int numar, baza;
main()
{
void citeste_numar_baza(void);
void conversie (void);
citeste_numar_baza();
Programare C/C++ 157
if (baza < 2 || baza > 16)
{
printf ("Baza eronata ! Implicit baza = 10 \n");
baza = 10;
}
conversie();
}
void citeste_numar_baza(void)
{
printf ("Numar de convertit:");
scanf ("%d", &numar);
printf ("In baza:");
scanf ("%d", &baza);
}
void conversie (void)
{
char
cifra_baza[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
int index=0;
if (numar < 0)
{
printf("-");
numar = -numar;
}
do
{
numar_convertit[index] = numar % baza;
numar /= baza;
index ++;
} while (numar != 0);
for ( --index ; index >= 0 ; --index )
printf ("%c", cifra_baza[numar_convertit[index]]);
printf ("\n");
}

În acest exemplu, funcţiile definite nu au argumente, motiv pentru care la


declararea funcţiilor s-a utilizat tipul void pentru argumente; de asemenea
funcţiile nu returnează nici o valoare. Prelucrările efectuate de aceste funcţii
se fac, de fapt, asupra variabilelor globale, definite în afara funcţiilor.

Efecte laterale

157
158 Programare C/C++
Orice funcţie, fie că nu este de tip void şi calculează o valoare pe
care o întoarce ca rezultat al apelului său, fie că este de tip void, poate
modifica şi variabilele globale (sau nelocale) ale programului, pe lângă
valorile transmise ca parametrii, prin referinţă (pointeri).
Acestea constituie efecte laterale ale funcţiei şi, de obicei, fac
înţelegerea programelor mai dificilă. Din acest motiv se evită modificarea
variabilelor globale în cadrul funcţiilor.
De asemenea, dacă dorim ca funcţia să nu modifice valorile
parametrilor, chiar dacă sunt transmişi prin referinţă, parametrii respectivi
trebuie declaraţi de tip const în antetul funcţiei. În acest fel se evită unele
erori de programare, dar şi prelucrările realizate de funcţie devin mai clare.

7.3. Funcţii şi tablouri

În mod asemănător transmiterii ca argumente, pentru o funcţie, de


valori sau variabile simple se poate transmite valoarea unui element al unui
tablou sau chiar întregul tablou. În mod asemănător unei variabile simple,
valoarea unui element al unui tablou va fi copiată în valoarea parametrului
formal corespunzător, când este apelată funcţia.
Pentru a transmite un întreg tablou către o funcţie este suficient să se
specifice numele tabloului, fără indicii săi, la apelul funcţiei.
Trebuie, însă, reţinută o diferenţă esenţială când avem argumente de
tip tablou: dacă funcţia modifică valoarea unui element al tabloului, atunci
modificarea va fi făcută în tabloul original, care a fost transmis funcţiei.
Când se apelează o funcţie, valorile care sunt transmise ca
argumente funcţiei sunt copiate în parametri formali corespunzători. În cazul
tablourilor, întregul conţinut al tabloului nu este copiat în parametrul formal
tablou; pentru tablouri se transmite adresa unde se află locatat (plasat în
memorie) acesta. În acest fel, orice modificare realizată de funcţie asupra
parametrului formal tablou se realizează, de fapt, asupra tabloului original
transmis funcţiei şi nu asupra unei copii a tabloului.
Deci, numele unui tablou înseamnă, de fapt, un pointer la primul
element al tabloului; declararea unui parametru formal de tip tablou este de
fapt un pointer. Funcţia poate modifica elementele tablourilor prin folosirea
indexării faţă de primul element.
Din acest motiv, la declararea funcţiei, putem omite specificarea
numărului de elemente conţinute în parametrul formal de tip tablou.
Exemple:

/*
Programare C/C++ 159
programul determina valoarea minima dintr-un sir de valori
utilizand pentru determinarea valorii minime o functie
*/
#include<stdio.h>
double minim (double vector[], int nr)
{
int i;
double val_min;
val_min = vector[0];
for ( i = 1 ; i < nr ; i++)
if ( vector[i] < val_min)
val_min = vector[i];
return ( val_min );
}
main()
{
double sir[100];
int ne, i;
printf ("Numar elemente:");
scanf ("%d",&ne);
for ( i = 0 ; i < ne ; i++)
{
printf ("sir(%d)=", i+1);
scanf ("%lf", &sir[i]);
}
printf ("Valoarea minima este:%lf\n", minim(sir,ne));
}

/* Program de ordonare crescatoare sir, utilizand metoda de selectie


directa, adica: din sirul initial se pune valoarea minima pe prima
pozitie, apoi pentru subsirul ramas [2,n] se pune valoarea minima
din
el pe pozitia 2, s.a.m.d
*/
#include<stdio.h>
void sort (double sir[], int nrelem)
{
int i,j;
double aux;
for( i = 0 ; i < nrelem-1; ++i )

159
160 Programare C/C++
for( j = i+1;j < nrelem ; ++j)
if ( sir[i] > sir[j] )
{
aux = sir[i];
sir[i] = sir[j];
sir[j] = aux;
}
}

main()
{
double sir[1000];
int ne, i;
printf ("Numarul de elemente: ");
scanf ("%d",&ne);
for ( i = 0 ; i < ne ; ++i)
{
printf (" sir(%d)=", i+1);
scanf ("%lf", &sir[i]);
}
sort(sir,ne);
for( i = 0 ; i < ne ; ++i )
printf (" sir(%d)=%lf ", i+1, sir[i]);
}

Tablouri multidimensionale
Un element al unui tablou multidimensional poate fi transmis unei
funcţii la fel ca o variabilă obişnuită sau ca un element al unui tablou
unidimensional:
nume_funcţie (matrice [i][j]);
În acest exemplu se transmite ca argument valoarea elementului matrice[i]
[j].
Pentru a transmite un întreg tablou multidimensional se procedează
la fel ca la tablourile unidimensionale, adică se specifică numele tabloului:
multiplica_scalar (matrice, constanta);
Aceasta implică că, însăşi, funcţia poate modifica valorile conţinute
în tabloul matrice, întrucât se transmite adresa tabloului.
În declararea ca parametru formal al unei funcţii, a unui tablou
unidimensional nu este necesar să se precizeze dimensiunea tabloului, ci se
puteau specifica doar parantezele [] pentru a informa compilatorul de C că
parametrul este, de fapt, un tablou.
Programare C/C++ 161
În schimb, pentru un tablou bidimensional, de exemplu, se poate
omite prima dimensiune (numărul de linii), dar declaraţia trebuie să conţină
numărul de coloane din tablou. Motivul se datorează modului de adresare,
de exemplu, a unui element din linia l, coloana c:
matrice [l][c] = *(*(matrice+l)+c) sau *( matrice + maxc*l + c )
Deci pentru a determina corect adresa elementului trebuie să se
cunoască valoarea maximă a lui c (maxc) – numărul maxim de elemente
dintr-o linie (ştiut fiind că elementele sunt dispuse, în memorie, pe linii).
În general, pentru un tablou multidimensional, se poate omite prima
dimensiune, dar celelalte sunt obligatorii. Astfel, declaraţii de forma:
double matrice [10][10];
sau
double matrice [][10];
sunt corecte pentru un parametru formal de tip matrice (10*10); pe când
declaraţiile:
double matrice [10][];
sau
double matrice [][];
reprezintă erori, deoarece nu se specifică numărul de coloane.

Exemplul 1: înmulţirea unei matrice cu un scalar:

/* Inmultirea unei matrici cu un scalar, utilizand functii */


#include <stdio.h>
void citire_matrice( double matc [][8], int *pnl, int *pnc )
{
int i,j;
printf ("Dimensiunile matricii (linii,coloane):");
scanf ("%i%i", pnl, pnc);
for ( i = 0 ; i < *pnl ; i++)
for ( j = 0 ; j < *pnc ; j++)
{
printf("mat(%i,%i)=", i+1, j+1);
scanf("%lf", &matc[i][j]);
}
}
void multiplica_scalar ( double matm [][8], int nlinii,
int ncoloane,double valoare )
{
int i,j;
for ( i = 0 ; i < nlinii ;i++)

161
162 Programare C/C++
for ( j = 0 ; j < ncoloane ; j++)
matm[i][j] *= valoare;
}
void afisare_matrice ( double mata[][8], int linii, int coloane )
{
int i, j, nr_elem_pe_linie = 0;
for ( i = 0 ; i < linii ; i++)
for ( j = 0; j < coloane ; j++, nr_elem_pe_linie++ )
{
printf (" mat(%i,%i)=%6.2f ", i+1, j+1, mata[i][j]);
if ( nr_elem_pe_linie % 4 == 0 )
printf("\n"); /* se afiseaza cate 4 valori/linie */
}
}
void main()
{
double matrice [8][8], scalar;
int l, c;
printf ("Introduceti matricea :\n");
citire_matrice ( matrice, &l, &c );
printf ("Scalarul cu care se inmultesc elementele matricii:\n");
scanf ("%lf", &scalar );
multiplica_scalar ( matrice, l, c, scalar );
printf ("Matricea multiplicata cu scalarul %f este :\n", scalar );
afisare_matrice( matrice, l, c );
}

Exemplul 2: limbajul C, spre deosebire de alte limbaje, permite o mare


libertate. Astfel, putem utiliza o funcţie, definită pentru un tablou
unidimensional, la prelucrarea unui tablou bidimensional, ca în acest
exemplu.

/* inmultirea cu un scalar a unei matrice, utilizand o


functie care inmulteste un vector cu un scalar */
#include <stdio.h>
void main ()
{
static int mat [3][4] = {
{ 1, 2, 3, 4},
{ 5, 7, 9, 11},
Programare C/C++ 163
{ 16, 25, 36, 49}
};
int i, j;
void mult_scalar ( int vect [ ] , int dim, int scalar );
mult_scalar ( mat, 3*4, 2 ); /* se inmultesc cele 3*4 = 12 elemente cu 2
*/
for ( i = 0 ; i < 3 ; i++ )
{
for ( j = 0 ; j < 4 ; j++ )
printf (“m(%d,%d)=%d “, i+1, j+1, mat [i][j] );
putchar ( ‘\n’ );
}
}
void mult_scalar ( int vect [ ] , int dim , int scalar )
{
int i ;
for ( i = 0 ; i < dim ; i++ )
vect [i] *= scalar;
}

7.4. Funcţii şi pointeri

Aşa cum putem transmite un pointer, ca argument, pentru o funcţie,


putem avea, de asemenea, o funcţie care returnează ca rezultat un pointer.
Parametrul formal pointer poate fi utilizat, în acelaşi mod, ca o
variabilă pointer normală. Trebuie reţinut, în acest caz, când se transmite ca
argument un pointer, că valoarea pointerului este copiată în parametrul
formal, la apelul funcţiei. Din acest motiv, orice modificare a parametrului
formal (de fapt a valorii acestuia) nu trebuie să afecteze (modifice)
pointerul; deci funcţia poate modifica numai elementele referite de pointer,
dar nu valoarea acestuia.
Pentru a exemplifica vom considera un exemplu simplu: utilizarea
de pointeri pentru a schimba două valori între ele.

/* schimba.c - se schimba 2 valori intre ele, utilizand pointeri */


#include <stdio.h>
void interschimba (int *, int *);
void main ()
{

163
164 Programare C/C++
int x = 5 , y = 10;
printf ("Valori initiale: x = %d, y = %d\n", x, y);
interschimba (&x, &y ); /* se transmit adresele variabilelor la functie
*/
printf ("Noile valori: x = %d, y = %d\n", x, y);
}
void interschimba ( int *u , int *v )/* u, v sunt pointeri */
{
int temp;
temp = *u;
*u = *v;
*v = temp;
}

Dacă nu se utilizează pointeri (deci în prototipul funcţiei


interschimba înlocuiţi *u şi *v cu u şi v, iar la apelul ei transmiteţi u şi v, în
locul adreselor &u şi &v), interschimbarea valorilor nu se vede în afara
funcţiei, adică se interschimbă u cu v, dar x şi y rămân la valorile avute
anterior apelului funcţiei.
În mod asemănător se întâmplă lucrurile când se apelează funcţia
standard scanf(): dacă vrem să citim o valoare pentru o variabilă numar,
utilizăm ca parametru adresa variabilei:
scanf("%d", &numar);
Funcţia va citi o valoare, apoi returnează adresa pe care o primeşte
ca argument, când memorează valoarea. De fapt, această funcţie este
definită în fişierul antet <stdio.h> astfel:
int scanf ( format, arg1, arg2, . . . , argn);
Funcţia citeşte date din fişierul stdin (tastatura) în concordanţă cu
formatul specificat de şirul de caractere “format”. Valorile citite sunt
memorate în argumentele “argi”, ce urmează după “format”, fiecare
argument fiind un pointer.
Numărul de valori citite cu succes este asignat şi returnat de funcţie.
Dacă se întâlneşte sfârşit de fişier se returnează valoarea EOF, indiferent de
numărul de elemente convertite (conform formatului).
Următorul exemplu va arăta cum o funcţie poate returna un pointer.
Programul defineşte o funcţie “afla_element”, al cărui scop este de a
determina o anumită valoare într-o listă înlănţuită de valori întregi. Când se
întâlneşte valoarea căutată programul returnează un pointer către acel
element din listă; dacă valoarea nu este găsită, funcţia va returna pointerul
NULL. De asemenea, programul va utiliza o funcţie ce returnează tot un
pointer, pentru a construi lista (de tip coadă).
Programare C/C++ 165

/* programul construieste o lista - utilizand o functie : constructor


si determina daca un anumit element exista in lista, folosind functia
afla_element, care returneaza un pointer la elementul respectiv
*/
#include <stdio.h>
struct s_lista
{
int valoare;
struct s_lista *urmator;
};
struct s_lista *constructor ( void )
{
struct s_lista *pelem, * pstart;
int numar;
pstart = NULL;
printf ("Valorile intregi de introdus in lista:\n");
printf ("Valoare = ");
scanf ("%d", &numar);
while ( numar )
{
if ( pstart == NULL )
{
pstart = ( struct s_lista *) malloc ( sizeof ( struct
s_lista ));
pstart -> valoare = numar;
pstart -> urmator = NULL;
pelem = pstart;
}
else
{
pelem -> urmator =( struct s_lista * )
malloc (sizeof(struct s_lista));
if ( pelem -> urmator != NULL ) /* daca se aloca se
adauga la lista */
{
pelem = pelem -> urmator;
pelem-> valoare = numar;
pelem-> urmator = NULL;
}
else

165
166 Programare C/C++
exit (0);
}
printf ("Valoare = ");
scanf ("%d", &numar);
}
return ( pstart );
}
void display_lista ( struct s_lista *start )
{
struct s_lista *ptrel = start;
int nr_val_linie = 0;
while ( ptrel != NULL )
{
printf ("%d -> ", ptrel->valoare);
ptrel = ptrel -> urmator;
nr_val_linie++; /* contor pentru numarul de valori afisate pe
liinie */
if ( nr_val_linie % 10 == 0 )
printf ("\n");
}
printf ("\n");
}
struct s_lista *afla_element ( struct s_lista *ptr_lista,
int numar_cautat, int *p_pozitie )
{
struct s_lista *p_lista = ptr_lista;
while ( p_lista != NULL )
{
(*p_pozitie)++;
if ( p_lista ->valoare == numar_cautat )
return ( p_lista );
else
p_lista = p_lista -> urmator;
}
return (NULL);
}
void main()
{
struct s_lista *constructor();
void display_lista ( struct s_lista *start );
struct s_lista *afla_element (struct s_lista *ptr_lista, int numar_cautat,
Programare C/C++ 167
int *p_pozitie );
struct s_lista *start_lista, * ptr_valoare;
int *pozitie, val_cautata;
start_lista = constructor();
display_lista ( start_lista );
if ( start_lista == NULL )
printf ("Lista vida de valori\n");
else
{
printf ("Valoarea de cautat in lista :");
scanf ("%d", &val_cautata);
*pozitie = 0;
ptr_valoare = afla_element(start_lista, val_cautata, pozitie );
if ( ptr_valoare != NULL )
printf ("Valoarea: %d, este in lista pe pozitia %d\n",
ptr_valoare -> valoare, *pozitie);
else
printf ("Valoarea %d nu este in lista \n", val_cautata);
}
}

Probleme propuse:
- construirea unei liste ordonate de numere;
- construirea unei liste de tip stivă ce conţine numere;
- organizarea unui dicţionar de cuvinte, ca o listă înlănţuită.
Deoarece utilizarea pointerilor la tablouri are avantajele unei notaţii
mai uşoare şi o eficienţă a programului (rezultând cod mai scurt, şi deci un
timp de execuţie mai mic) aceştia sunt utilizaţi şi pentru transmiterea de
tablouri, ca parametrii la funcţie.
Să considerăm, de exemplu, o funcţie care furnizează suma
elementelor unui tablou, utilizând ambele metode de transmitere a
parametrilor:

/* Sumtab1.c - realizeaza suma elementelor unui tablou, utilizand


o functie de parametru formal tablou */
# include <stdio.h>
# define DIM 100
double suma(double *, int);
void main()
{
double tablou[DIM], suma_elemente;

167
168 Programare C/C++
int nr_elem, i;
printf ("Numarul de elemente din vector:");
scanf ("%d", &nr_elem);
for ( i = 0 ; i < nr_elem ; i++)
{
printf ("numar(%d)=", i+1);
scanf ("%lf", &tablou[i]);
}
suma_elemente = suma(tablou, nr_elem);
printf ("Suma elementelor vectorului este;%lf\n", suma_elemente);
}
double suma ( double tab[], int n )
{
int i;
double sum = 0;
for ( i = 0 ; i < n ; i++)
sum += tab[i];
return sum;
}

/* Sumtab2.c, realizeaza suma elementelor din tablou utilizand


functie cu parametru formal pointer la tablou */
#include <stdio.h>
#define DIM 100
double suma (double *ptrtab, int n);
void main()
{
double tablou[DIM], suma_elemente;
int nr_elem, i;
printf ("Numarul de elemente din vector:");
scanf ("%d", &nr_elem);
for ( i = 0 ; i < nr_elem ; i++)
{
printf ("Numar(%d)=", i+1);
scanf ("%lf", &tablou[i]);
}
suma_elemente = suma (tablou, nr_elem);
printf ("Suma elementelor vectorului:%lf\n", suma_elemente);
}
double suma ( double *ptab, int n)
Programare C/C++ 169
{
int i;
double sum = 0;
for ( i = 0 ; i < n ; i++)
sum += *(ptab + i);
return sum;
}

Referirea la elementele unui tablou se poate face utilizând şi pointeri. De


exemplu să consideram o funcţie, şi programul, care determină suma
elementelor unei matrice:

#include <stdio.h>
#define MAXL 10
#define MAXC 10
double suma ( double **pmat , int nrl, int nrc )
{
double s = 0;
int i, j ;
for ( i = 0 ; i < nrl ; i++ )
for ( j = 0 ; j < nrc ; j++ )
s += (( double *) pmat ) [ i * MAXC + j ];
return ( s );
}
void main (void)
{
double m [ ][ 3 ] = {{1, 2, 3 }, { 2, 4, 6 }, { 3, 6, 9 }};
printf ("Suma elementelor matricii : %lf\n", suma ( m , 3 , 3 ) );
}

Funcţia aceasta lucrează corect şi pentru o matrice cu un număr de


linii şi / sau de coloane mai mic decât MAXC, deoarece se utilizează
indexul i*MAXC+ j pentru referirea elementelor. În schimb, dacă se
utilizează pointerul pentru referirea elementelor, funcţia va returna un
rezultat corect doar dacă sunt definite toate elementele. În acest caz, funcţia
pentru însumarea elementelor matricei va fi de forma:
for ( i = 0 ; i < nrl ; i++ )
for ( j = 0 ; j < nrc ; j++ )
s += ( double **) pmat ++;

169
170 Programare C/C++
Este posibil să se declare pointeri la funcţii. De obicei, un pointer la
funcţie este utilizat ca un argument pentru altă funcţie, pentru a specifica
acesteia ce funcţie să utilizeze.
Pentru a exemplifica utilizarea pointerilor la funcţii, în exemplul
următor vom utiliza pointeri la funcţii pentru a tipări un argument din linia
de comandă în ordine normală şi inversă:

/* ptr_func.c utilizeaza pointeri la functii */


#include <stdio.h>
#include <stdlib.h>
int rev_puts (char *str); /* functie pt. inversare sir de caractere */
void display ( int (*fp)(), char *str ); /* functia pentru afisare */
main ( int argc, char *argv[] ){
if (argc < 2) /*daca numarul de argumente < 2, se termina programul
*/
exit(1);
display ( puts, argv[1] ); /* afiseaza direct sirul, utilizand puts */
display ( rev_puts, argv[1] ); /* afisare in ordine inversa,
utilizand functia rev_puts */
}
int rev_puts ( char *str ){
char *start = str;
while ( *str != '\0' )
str++; /*merge la sfarsitul sirului*/
while ( str != start )
putchar ( *--str );/*tipareste invers sirul de caractere*/
return putchar ( '\n' );/*returneaza valoarea caracterului tiparit
sau EOF daca nu s-a tiparit*/
}
void display (int(*fp)(), char *str) /* functie ce apeleaza o alta functie
*/
{ /* pentru afisarea efectiva, fp(), transmisa ca paramatru */
(*fp)(str); /*se apeleaza functia referita si i se transmite arg. str */
}

Dacă după numele programului se furnizează (tastează) şi alte


informaţii, se spune că s-au furnizat programului argumente în linia de
comandă (cea în care se specifică numele programului, deci când se
lansează în execuţie).
Când se apelează funcţia main se transmit funcţiei două argumente.
Primul dintre ele, denumit argc (de la argument count) este o valoare
Programare C/C++ 171
întreagă care specifică numărul de argumente tastate în linia de comandă.
Cel de-al doilea argument este un tablou de pointeri la caracter, denumit
argv (argument vector); acest tablou va conţine argc+1 pointeri la caracter,
unde argc >= 0. Prima intrare în acest vector este un pointer la numele
programului ce se execută sau este un pointer la un şir nul de caractere, dacă
numele programului nu există. Următoarele intrări în vector referă valorile
care au fost în linia de comandă a programului ce se execută. Ultimul
pointer din vectorul argv, argv [argc], este definit ca fiind null. Pentru a
asocia unui argument mai multe caractere, inclusiv spaţii albe, acesta va fi
scris între ghilimele. Pentru a avea acces la argumentele din linia de
comandă, funcţia main este declarată cu aceste două argumente.
Alt exemplu: să se calculeze integrala unei funcţii definite pe intervale.
b m b

∫ f ( x )dx=∫ f 1 ( x )dx+∫ f 2 ( x )dx


a a m
utilizând pentru calculul integralei metoda trapezelor:
v n−1
∫ f ( x )dx=( f (u )+ 2∗∑ f (u+k∗pas )+ f ( v ))∗pas/2
u k=1
unde n este numărul de subintervale în care se împarte intervalul [ u , v ], iar
pas = (v-u)/n.

#include <stdio.h>
#include <math.h>
double fct1 ( double x )
{
double val_funct;
val_funct = sqrt ( pow ( x , 2 ) + log ( pow ( x , 2 ) + 2 ) - 1 );
return ( val_funct );
}
double fct2 ( double y )
{
double val_funct;
val_funct = y + y * log ( pow ( y , 2 ) + 2 ) - 1;
return ( val_funct );
}
double integrala ( double (* fp ) (), double li, double ls, int n )
{
double pas, suma = 0 , arg ;
pas = ( ls - li ) / n;

171
172 Programare C/C++
arg = li + pas;
while ( arg < ls )
{
suma += ( *fp ) (arg);
arg += pas;
}
suma *= 2;
suma += ( *fp ) (li) + ( *fp ) (ls);
suma = suma * pas / 2;
return suma;
}
void main ()
{
double a = 1, m = 3, b = 5;
int n =100;
printf ("valoarea integralei este: %lf\n", integrala (fct1, a, m, n)
+ integrala(fct2, m, b, n);
}

Un alt exemplu utilizează algoritmul de sortare “quick sort” (definit


de Hoare, în 1962) care utilizează o metodă recursivă pentru partiţia
vectorului în dimensiuni mai mici, până se ajunge la nivel de element. La
început, vectorul este împărţit în două, cu valori într-o partiţie, mai mici
decât cele din cealaltă partiţie. Procesul continuă asupra partiţiilor obţinute,
ş.a.m.d., până se ordonează vectorul.
Funcţia are următorul prototip:
void qsort (vector, n, dim, comp_fu)
unde: vector = vectorul de sortat
n = numărul de elemente din vector
dim = dimensiunea în octeţi a unui element
comp_fu = pointer la o funcţie care returnează int, şi are doi pointeri void ca
argumente; funcţia qsort apelează această funcţie când e nevoie să compare
două elemente din vector, transmiţându-i pointerii către elementele de
comparat. Funcţia, care e furnizată de utilizator, compară două elemente şi
returnează o valoare negativă, nulă sau pozitivă dacă primul element este
respectiv mai mic, egal sau mai mare decât cel de-al doilea.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>
Programare C/C++ 173
#define NUM 40
void umple_siruri ( char (*tab)[LEN], int n);
void display_sir ( char (*tab)[LEN], int n);
int compara ( const void *str1, const void *str2);
char litere [NUM][LEN];
void main()
{
umple_siruri ( litere, NUM ); /* se genereaza cuvinte */
display_sir ( litere,NUM ); /* se afiseaza in forma initiala */
putchar ( '\n' );
qsort ( (void*)litere, NUM, LEN, compara );/*sunt ordonate alfabetic
*/
display_sir ( litere, NUM); /* sunt afisate cuvintele astfel ordonate */
}
void umple_siruri ( char (*tab)[LEN], int n)
{ /* genereaza cuvinte aleatoare */
int cuvant, lit;
for ( cuvant = 0 ; cuvant < n ; cuvant++)
{
for ( lit = 0 ; lit < LEN-1;lit++)
tab[cuvant][lit] = 'a'+rand() % 26;
tab[cuvant][lit-1] = '\0';
}
}
void display_sir ( char (*tab)[LEN], int n)
{
int cuv;
for ( cuv = 0 ; cuv < n ; cuv++)
{
if ( cuv % 8 == 7 ) /* sunt afisate cate 8 cuvinte pe o linie de
ecran */
putchar ( '\n' );
printf ("%s ", tab[cuv]);
}
putchar ('\n');
}
int compara ( const void *str1, const void *str2 )
{ /* compara doua siruri de caractere */
return strcmp ( str1, str2 );
}

173
174 Programare C/C++
Acest exemplu mai utilizează funcţiile:
int rand ( void );
care returneză un număr aleator în intervalul [ 0 , RAND_MAX], unde
RAND_MAX este definit în <stdlib.h>, şi are valoarea minimă 32767;
int strcmp( s1 , s2 );
definită în <stdio.h> şi care compară două şiruri de caractere s1, s2 şi
returnează o valoare negativă, zero sau pozitivă, după cum s1<s2, s1=s2 sau
s1>s2. (size_t este tipul întreg furnizat de operatorul sizeof).
Alte două exemple simple în care funcţia main() are argumente.

// programul citeste parametrii din linia de comanda (cei ce sunt


tastati
// dupa numele programului), dupa care tipareste numarul lor (argc)
#include <iostream.h>
void main(int argc, char *argv[])
{ cout << "Numar parametri in linia de comanda " << argc <<
endl;
}

#include <iostream.h>
void main(int argc, char *argv[])
// programul citeste parametrii din linia de comanda
{ // si ii afiseaza, fiecare pe cate o linie
int i;
for (i = 0; i < argc; i++)
cout << "argv[" << i << "] contine " << argv[i] << endl;
}
Programare C/C++ 175

7.5. Funcţii pentru şiruri de caractere

Există funcţii predefinite pentru prelucrarea caracterelor sau a


şirurilor de caractere. Aceste funcţii se află în următoarele fişiere antet:
<stdio.h> - funcţiile de intrare / ieşire de caractere sau şiruri de caractere;
<string.h> - funcţii de operare pe şiruri: copiere, concatenare, căutare etc.
<stdlib.h> - funcţii pentru transformarea şirurilor de caractere;
<ctype.h> - funcţii care analizează tipul caracterului sau pentru
transformarea unei litere în literă mare sau mică;
În continuare vor fi prezentate funcţiile mai des utilizate din aceste fişiere
antet.

Funcţiile din <stdio.h>


in getchar ( ) - citeşte şi returnează următorul caracter din fişierul standard
de intrare stin (adică tastatura); dacă apare o eroare de citire
sau este sfârşit de fişier se va returna EOF;
char *gets( buffer ) - citeşte caractere din stdin în buffer până se întâlneşte
caracterul de sfârşit de linie; acesta nu este depus în buffer,
iar şirul de caractere se va termia cu caracterul NULL; dacă
apare o eroare de citire sau nu sunt citite caractere se va
returna un pointer nul, altfel este returnat buffer.
int putchar ( c ) - scrie valoarea lui c ca un unsigned char la stdout (adică
la display), returnând c dacă operaţia a reuşit, altfel
returnează EOF;
int puts ( buffer ) - scrie caracterele conţinute în buffer la stdout până se
întâlneşte caracterul nul. În mod automat, ca ultim caracter
este înscris şi caracterul linie nouă; dacă apare o eroare se
returnează EOF.
Pe lângă aceste funcţii mai pot fi folosite pentru citirea sau scrierea de
caractere sau şiruri de caractere funcţiile printf () şi scanf () , care au fost
descrise anterior şi utilizate în exemplele de până acum.

Funcţiile din fişierul <string.h>


Următoarele funcţii realizează operaţii pe şiruri (tablouri) de caractere. În
descrierea lor s, s1 şi s2 reprezintă pointeri la tablouri de şiruri de caractere
(terminate cu caracterul nul); c este un întreg (int) iar n reprezintă un întreg
de tip size_t. Pentru funcţiile strn__ , s1 şi s2 pot referi tablouri de caractere
care se termină cu caracterul nul.
char *strcat (char *s1, const char *s2) - concatenează şirul de caractere s2
la sfârşitul lui s1, plasând caracterul nul la sfârşitul şirului
rezultat; funcţia returnează şirul rezultat în şirul s1;

175
176 Programare C/C++
char *strchr ( const char *s, int c ) - se va căuta prima apariţie a caracterului
c în şirul s; dacă este găsit va returna pointerul către acesta,
altfel returnează pointerul nul;
int strcmp ( const char *s1, const char *s2 ) - compară s1 cu s2 şi
returnează o valoare negativă dacă s1<s2, returnează 0 dacă
s1 = s2, şi returnează o valoare pozitivă dacă s1<s2;
char *strcpy ( char *s1, const char *s2) - copiază şirul s2 în s1 şi returnează
s1;
size_t strlen ( const char *s) - returnează numărul de caractere din s ,
exclusiv caracterul nul;
char *strncat (char *s1, const char *s2, size_t n) - concatenează şirul de
caractere s2 la sfârşitul lui s1, până când se întâlneşte
caracterul nul, dar nu copiază mai mult de n caractere;
funcţia returnează şirul rezultat în şirul s1;
int strncmp ( const char *s1, const char *s2, size_t n ) - realizează aceeaşi
funcţie cu strcmp cu deosebirea că se compară cel mult n
caractere;
char *strncpy ( char *s1, const char *s2, size_t n) - copiază şirul s2 în s1
până la caracterul nul, dar nu mai mult de n caractere şi
returnează s1;
char *strrchr ( const char *s, int c) - caută în şirul s ultima apariţie a
caracterului c; dacă este găsit returnează un pointer la acest
caracter, altfel returnează caracterul nul;
char *strstr ( const char *s1, const char *s2 ) - caută în s1 prima apariţie a
şirului s2; dacă este găsit va returna un pointer la începutul
şirului s2, din s1, altfel returnează un pointer nul.

Următoarele funcţii sunt destinate pentru căutare şi copiere eficientă


a unor date (şiruri de caractere) dintr-un bloc de memorie într-altul, pentru
care se cunosc adresele de început. În aceste funcţii s1 şi s2 sunt de tipul
void * , iar c este de tip int.

void *memchr ( const void *s1 , int c, size_t n ) - caută prima apariţie a lui c
în s1, de n octeţi; returnează adresa lui c sau dacă nu este
găsit returnează NULL;
int memcmp ( const void *s1, const *void *s2, size_t n ) - compară blocurile
de octeţi s1 şi s2; rezultatul este negativ, zero sau pozitiv
după cum, lexicografic, s1<s2 , s1 = s2 sau s1>s2;
void *memcpy ( void *s1, const void *s2, size_t n) - copiază un bloc de n
octeţi din s2 în s1 şi returnează s1; blocurile nu se
suprapun;
Programare C/C++ 177
void *memmove ( void *s1, const void *s2, size_t n ) - copiază un bloc de n
octeţi din s2 în s1 şi returnează s1, chiar dacă blocurile se
suprapun;
void *memset ( void *s1, int c, size_t n ) - memorează în cei n octeţi ai lui s1
valoarea c;

Funcţiile din fişierul <stdlib.h>


Aceste funcţii convertesc şiruri de caractere la valori numerice. În
descrierile ce urmează s este un pointer la un şir de caractere, ptr_sf este un
pointer la un caracter, iar baza în care se face conversia este de tip int. Toate
aceste funcţii trec peste spaţiile albe ce preced caracterele din şir şi se
opreşte scanarea (citirea) când se întâlneşte un caracter care nu este valid
pentru tipul valorii de convertit.
double atof ( const char *s ) - converteşte şirul referit de s la o valoare reală
(double) şi returnează rezultatul;
int atoi ( const char *s ) - converteşte s la un întreg (int);
long atol ( const char *s ) - converteşte s la un întreg de tipul long int;
double strtod ( const char *s, char *ptr_sf ) - converteşte s la real de tipul
double şi returnează rezultatul. În pointerul caracter referit
de ptr_sf se memorează pointerul către caracterul care a
încheiat citirea, deci un pointer la partea netransformată (nu
este un pointer nul). De exemplu secvenţa:
char buffer [ ] = " 123.456abc" , *sf;
double valoare;
.......
valoare = strtod ( buffer , sf );
va memora în valoare 123.456, iar variabila pointer la
caracter, sf, va fi poziţionată pentru a referi primul caracter
nenumerică ('a');
long int strtol ( const char *s, char *ptr_sf, int baza ) - converteşte s la tipul
long int; întregul este interpretat în baza specificată, care
poate fi în intervalul 2-36. Dacă baza este 0, valoarea poate
fi exprimată fie în baza 10, fie în baza octal (dacă este
precedat de 0), fie în hexazecimal (dacă este precedat de 0x
sau 0X). Dacă baza este 16 valoarea poate fi precedată,
opţional, de 0x sau 0X.
unsigned long int strtoul ( const char *s, char *ptr_sf, int baza ) -
converteşte s la un întreg fără semn, returnând rezultatul.
Argumentele sunt interpretate la fel ca la funcţia strtol.

Funcţiile din fişierul <ctype.h>

177
178 Programare C/C++
Funcţiile din acest fişier operează asupra unui singur caracter; aceste
funcţii au ca argument un întreg (c) şi returnează valoarea "adevărat" (diferit
de 0) dacă testul este satisfăcut sau "fals" (=0), dacă testul nu este satisfăcut.
isalnum ( c) - este c un caracter alfanumeric ?
isalpha ( c) - este c un caracter alfabetic ?
iscntrl ( c) - este c un caracter de control (0-1FH sau 7F-"del") ?
isdigit ( c) - este c un caracter numeric (cifră) ?
isgraph ( c) - este c un caracter grafic (21H-7EH)?
islower ( c) - este c o literă mică ?
isprint ( c) - este c un caracter tipăribil (20H-7EH) ?
ispunct ( c) - este c un caracter de punctuaţie ? (nu este spaţiu
sau alt caracter alfanumeric)
isspace ( c) - este c un spaţiu alb (spaţiu, linie nouă, tab, return)?
isupper ( c) - este c o literă mare ?
isxdigit ( c) - este c o cifră hexazecimală?

Pe lângă acestea mai există două funcţii care realizează translaţie de


caracter:
int tolower ( c ) - dacă c este literă mare returnează litera mică
respectivă, altfel
este returnat acelaşi caracter;
int toupper ( c ) - dacă c este literă mică returnează litera mare
respectivă, altfel
este returnat acelaşi caracter;

Exemple:

1) Citirea unor şiruri de caractere şi sortarea (ordonarea) lor alfabetică,


utilizând algoritmul de sortare prin selecţie; se va utiliza funcţia strcmp(),
care poate determina ordinea a două şiruri de caractere.
Algoritmul de selecţie: se compară fiecare element (şir de caractere) cu
primul şi dacă cazul (adică nu sunt ordonate) le interschimbă; după prima
parcurgere a şirului primul element va conţine primul şir, în ordine
alfabetică, din şirurile de caractere. Se reia apoi parcurgerea comparând
al doilea element cu restul elementelor din şir ş.a.m.d., până la ultima
parcurgere care va compara ultimele două şiruri; deci în total vor fi n-1
parcurgeri. Programul nu sortează vectorul (lista) de şiruri de caractere
intrare, ci vectorul cu pointeri ptr_str, ce referă aceste şiruri de caractere;
în acest mod vectorul de şiruri de caractere va rămâne nemodificat.
Această metodă este mai simplă decât utilizarea, după comparaţie, a
funcţiei strcpy() pentru a inetrschimba conţinutul a două şiruri de
caractere, folosind pentru interschimb un al treilea şir, temporar.
Programare C/C++ 179

/*sort_str.c - citirea de siruri de caractere si sortarea (ordonarea) lor*/


#include <stdio.h>
#include <string.h>
#define DIMENS 81
#define LIMITA 30
#define STOP ""
void main()
{
void sort_sir ( char *sirc[], int lung );
static char intrare[LIMITA][DIMENS];/* vector ce memorează
intrările */
char *ptr_str [LIMITA]; /* vector de variabile pointer */
int contor = 0, cont_tip;
printf ("Introduceti cel mult %d linii,pentru sortare\n", LIMITA);
printf ("Introducerea se termina cu ENTER , pe o linie noua\n");
while ( contor < LIMITA && gets(intrare[contor]) != NULL &&
strcmp(intrare[contor], STOP) !=0 )
{/*se depune in vectorul de pointeri adresa curenta din
intrare*/
ptr_str[contor] = intrare[contor]; /* a sirului de caractere,
curent */
contor++;
}
sort_sir( ptr_str, contor );/*ordonarea vectorului de siruri de car.*/
puts("\n Lista ordonata :\n");
for ( cont_tip = 0 ; cont_tip < contor ; cont_tip++)
puts ( ptr_str[cont_tip] );
}
void sort_sir ( char *sirc[], int lung)
{
char *temp;
int i,j;
for ( i = 0 ; i < lung-1 ; i++)
for ( j = i+1 ; j < lung ; j++)
if ( strcmp( sirc[i], sirc[j] ) > 0 )
{
temp = sirc[i];
sirc[i] = sirc[j];
sirc[j] = temp;
}

179
180 Programare C/C++
}

2) Inversarea tipului de caractere dintr-un text, de la literă mică la literă


mare şi invers.
/* invcar.c - inversarea tipului de caractere dintr-un text:
de la litera mare la litera mica si invers */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LIMITA 30
#define DIMENS 81
void main()
{
void transforma ( char linie[] );
char text [LIMITA] [DIMENS];
int nr_linie = 0, i;
printf("Introduceti cel mult %d linii pentru transformare\n",
LIMITA);
printf("Introducerea se termina cu ENTER, pe o linie noua\n");
while ( nr_linie < LIMITA && gets( text[nr_linie] ) != NULL &&
strcmp (text[nr_linie],"") != 0 )
nr_linie++;/* numarul liniei curente din vectorul de siruri de
caractere */
for ( i = 0 ; i < nr_linie ; i++)
{
transforma ( text[i] );
puts ( text[i] );
}
}
void transforma ( char *ptr_car )
{
while ( *ptr_car != '\0')
{
if ( isupper ( *ptr_car ) )
*ptr_car = tolower ( *ptr_car );
else if ( islower ( *ptr_car ) )
*ptr_car = toupper ( *ptr_car );
ptr_car++;
}
}
Programare C/C++ 181

3) Valorile numerice sunt necesare pentru diverse operaţii aritmetice,


inclusiv comparaţii; în schimb pentru afişarea acestor valori numerice, pe
display, este necesară o formă de tip şir de caractere pentru cifrele valorii
respective. În mod asemănător la introducerea valorilor numerice este
citit un şir de caractere numerice care, apoi, sunt convertite la valoarea
numerică corespunzătoare. Aceste operaţii sunt realizate şi de funcţiile de
intrare / ieşire printf() / scanf().
Să considerăm, de exemplu, un program care citeşte un argument, din
linia de comandă, ce reprezintă o valoare numerică şi realizează
conversia de şirul de caractere citit la valoarea numerică corespunzătoare.
Argumentele din linia de comandă sunt citite ca şiruri de caractere, astfel
că pentru a utiliza valoarea numerică trebuie realizată conversia
respectivă, de la şir de caractere la valoarea numerică.

/* convstr.c - conversia unui sir de caractere, din linia de comanda,


la o valoare numerica */
#include <stdio.h>
#include <stdlib.h>
void main ( int argc, char *argv[ ] )
{
const int baza = 10;
char *ptr_sf;
long int val_int;
double val_reala;
int i;
if ( argc < 2 )
printf ("Utilizare: %d numere\n", argv[0]);
else
for ( i = 1 ; i < argc ; i++ )
{
val_int = strtol ( argv [ i ] , ptr_sf, baza );
if ( (*ptr_sf) == '\0' )
printf ("Argumentul %d este un numar intreg: %li\n",
i, val_int);
else if ( ispunct ( *ptr_sf ) )
{
val_reala = strtod ( argv[ i ], ptr_sf, baza );
if ( (*ptr_sf) == '\0' )
printf ( "Argumentul %d este un numar”
“ real: %lf\n, i, val_reala );

181
182 Programare C/C++
else
printf ("Argumentul %d nu este o valoare”
“ numerica\n",i);
}
}
Programare C/C++ 183

7.6. Funcţii pentru prelucrarea fişierelor

Programele prezentate până acum preiau datele de intrare de la


consolă, utilizând funcţia scanf(), şi furnizează rezultatele pe display, prin
folosirea funcţiei printf(); de asemenea, pentru citirea / scrierea unor
caractere sau şiruri de caractere s-au folosit funcţiile: getchar()/putchar()
respectiv gets()/puts(). Ele fac transferul datelor între calculator şi
echipamentele standard de intrare/ieşire, tastatura, respectiv display-ul.
Există, însă, numeroase situaţii în care o mare parte a informaţiilor
prelucrate la execuţia unui program sunt necesare la o execuţie ulterioară a
acesteia sau sunt folosite de un alt program. La terminarea execuţiei unui
program, însă, datele acestea se pierd din memoria calculatorului. Pentru a
putea fi recuperate aceste date, ca date de intrare pentru un alt program, ele
trebuie salvate pe un suport extern. O soluţie poate fi listarea lor la
imprimantă, iar la lansarea programului, datele să fie reintroduse de un
operator. Această soluţie are însă două dezavantaje:
- pentru un volum mare de date se va consuma un timp
suficient de mare pentru introducerea lor de către operator;
- probabilitatea de a introduce date eronate creşte cu volumul
de date de introdus.
O soluţie mult mai eficientă, din ambele puncte de vedere, este înregistrarea
datelor de intrare necesare pe un suport magnetic (de exemplu disc), de pe
care pot fi citite direct din program.
Pe un suport magnetic (disc, bandă, CD ) poate fi reprezentat un
volum foarte mare de informaţii, cu semnificaţii diverse: programe sursă,
executabile, date, texte, organizate în fişiere.
O formă de comunicare program – fişier este redirectarea fişier,
astfel încât un fişier poate fi sursa sau destinaţia datelor unui program.
Pentru aceasta se utilizează operatorii de redirectare (> sau <) în linia de
comandă, astfel :
nume_program < fişier_intrare
în care caz datele vor fi citite din “ fişier_intrare ”, sau
nume_program > fişier_ieşire
în care caz, datele vor fi scrise în fişierul “ fişier_ieşire “.
Operatorii nu pot fi utilizaţi pentru a conecta două fişiere sau două
programe. Această formă mai are şi dezavantajul că nu permite comunicarea
cu mai multe fişiere, fie de intrare, fie de ieşire.
Pe lângă această modalitate C-ul oferă diferite funcţii standard de
intrare/ieşire care transferă date între calculator şi fişiere păstrate pe disc
magnetic. Aceste funcţii sunt incluse în fişierul antet <stdio.h> , în care se
află şi definiţiile pentru EOF, NULL, stdin, stdout, stderr şi FILE.

183
184 Programare C/C++
Prelucrarea unui fişier presupune, mai întâi, deschiderea sa, care face
asocierea acestuia cu un flux (stream) sau canal de intrare/ieşire. Biblioteca
funcţiilor de intrare/ieşire standard păstrează starea fiecărui flux într-o
entitate de tip FILE. Aceasta conţine informaţii referitoare la adresa şi
lungimea zonei tampon pentru date, modul de utilizare a fişierului (citire,
scriere, actualizare) precum şi :
- un indicator de eroare care e setat la o valoare nenulă, de o
funcţie care întâlneşte o eroare de citire sau scriere;
- un indicator de sfârşit de fişier, care e setat la o valoare
nenulă, dacă se determină sfârşitul fişierului, la citire;
- un indicator de poziţie al fişierului, care precizează
următorul octet din flux, de citit sau scris, fiind avansat după
fiecare operaţie de citire sau scriere, sau este actualizat explicit prin
operaţii de poziţionare.
Tipul FILE este definit în fişierul antet <stdio.h> şi este un tip utilizator
derivat, o structură, pe baza tipurilor fundamentale de următoarea formă :
typedef struct
{ short level; /* nivel buffer : plin/gol */
unsigned flags; /* indicatori stare fişier */
char fd; /* descriptor fişier */
unsigned char hold; /* nu este buffer */
short bsize; /* dimensiune buffer */
unsigned char * buffer; /* buffer transfer date */
unsigned char *curp; /* pointerul curent */
unsigned istemp; /* indicator de fişier temporar */
unsigned token; /* folosit pentru verificări de validitate */
} FILE;
Programele C, la lansare, prin apelul funcţiei main, iniţializează variabilele
statice şi deschid, în mod automat, trei fişiere text predefinite :
- stdin, fişier standard de intrare date, tastatură;
- stdout, fişier standard de ieşire date, display-ul;
- stderr, fişier standard de ieşire pentru erori, display-ul; la apariţia
unei erori pe durata execuţiei programului vi se afişează un mesaj de
eroare pe acest display.
Celelalte fişiere utilizate de program trebuie deschise explicit
utilizând funcţia fopen.

Funcţia fopen
Această funcţie specifică numele fişierului (un pointer la un şir de
caractere ce conţine numele) şi modul de acces la conţinutul său (citire,
scriere sau actualizare):
FILE * fopen (const char *nume_fişier, const char * mod );
Programare C/C++ 185
Se va deschide fişierul “nume_fişier“ şi i se asociază un canal
identificat printr-un pointer, folosit în operaţiile ulterioare asupra fişierului:
FILE * fişier_intrare;
fişier_intrare = fopen ( “date “, “ r “ );
Deschiderea este însoţită de stocarea întregii structuri, descrisă
anterior deci inclusiv a buffer-ului utilizat pentru transfer. Cel de-al doilea
parametru, un şir de caractere, specifică modul în care e deschis fişierul :
“ r “ – deschide un fişier pentru citire;
“ w “ – deschide un fişier pentru scriere;
“ a “ – deschide pentru scriere, adăugând la sfârşitul fişierului
sau creează fişierul dacă nu există încă;
“ r+ “ – deschide pentru actualizare, atât pentru citire cât şi pentru scriere;
“ w+ “ – deschide pentru actualizare (citire şi scriere) prin trunchierea
fişierului la lungime zero, dacă există, sau creează fişier dacă nu
există;
“ a+ “ – deschide pentru actualizare (citire şi scriere) adăugând la sfârşitul
fişierului existent, sau îl creează, dacă nu există; poate fi citit tot
fişierul, dar scrierea se poate face doar prin adăugare.

Fişierul poate fi prelucrat în mod text, utilizând comenzile


anterioare sau adăugând sufixul t la mod :“rt“, “wt“, “at“, “r+t“, “w+t“,
“a+t“
sau “rt+“, “wt+“, “at+“
sau poate fi prelucrat în mod binar, adăugând sufixul b:
“rb“, “wb“, “ab“, “a+b“, “w+b“, “a+b“
sau “rb+“, “wb+“, “ab+“
Dacă nu se specifică sufixul, t sau b, modul se stabileşte în funcţie de
valoarea variabilei globale _fmode (O_BINARY sau O_TEXT, valori
constante definite în <fnctl.h>).
Un fişier (flux) text este o succesiune de caractere organizate pe linii,
separate prin caractere NL (New Line - linie nouă).
Un fişier binar este o succesiune oarecare de octeţi.
Funcţia fopen returnează pointerul NULL dacă nu poate deschide
fişierul (disc plin, nume ilegal, etc.), altfel returnează un pointer către
structura asociată.

Funcţia fclose
După realizarea prelucrărilor asupra fişierului (scriere, citire,
actualizare) acesta trebuie închis; pentru închiderea unui fişier se utilizează
funcţia: int fclose ( FILE * ptr_fişier )
care returnează valoarea 0, dacă fişierul a fost închis cu succes, sau
returnează EOF dacă a apărut o eroare.

185
186 Programare C/C++
Dacă un program se termină în mod normal, sistemul de operare va
închide (prin apelarea funcţiei fclose), în mod automat, toate fişierele
deschise.
Această operaţie de închidere a fişierelor trebuie făcută din două
motive :
- numărul de fişiere deschise simultan este limitat de sistemul
de operare;
- se eliberează bufferul de memorie asociat fişierului prin
transferarea tuturor datelor acumulate în acesta, dar încă
netransmise, prin scrierea lor în fişier.
Alte funcţii utilizate în prelucrarea fişierelor sunt prezentate în continuare,
grupate pe categorii.

Funcţii generale
int feof ( FILE * ptr_fişier ) - determină dacă, la ultima operaţie de citire, s-a
ajuns la sfârşitul fişierului; dacă s-a ajuns la sfârşit de fişier
returnează o valoare nenulă, altfel returnează valoarea 0;
int ferror ( FILE * ptr_fişer ) - verifică producerea unei erori şi returnează 0,
dacă a apărut o eroare, altfel returnează o valoare nenulă;
void clearerr ( FILE * ptr_fişier ) - şterge sfârşitul de fişier şi indicatorii de
erori asociaţi fişierului, identificat de ptr_fişier;
int fflush ( FILE * ptr_fişier ) - scrie (goleşte) datele din bufferul intern,
asociat fişierului, referit de ptr_fişier, şi returnează 0 dacă
operaţia a reuşit, iar dacă a apărut o eroare returnează EOF;
int remove ( FILE * ptr_fişier ) - şterge fişierul specificat şi returnează o
valoare nenulă dacă apare o eroare;
int rename ( const char * nume_vechi, const char * nume_nou ) -
redenumeşte fişierul nume_vechi cu nume_nou, şi returnează
o valoare nenulă dacă apare eroare;
int fgetpos (FILE * ptr_fişier, fpos_t * pos ) - memorează la locaţia
specificată de pointerul pos, poziţia curentă a indicatorului de
fişier asociat cu ptr_fişier; tipul fpos_t este definit în
<stdio.h>, ca typedef long fpos_t; funcţia returnează zero în
caz de succes, altfel returnează o valoare nenulă;
int fsetpos ( FILE * ptr_fişier, const fpos_t * pos ) - actualizează indicatorul
de fişier la valoarea referită de pos şi returnează 0 în caz de
succes sau o valoare nenulă în caz de eroare;
int fseek (FILE * ptr_fişier, long offset, int mod ); - poziţionează indicatorul
fişierului, referit de ptr_fişier, la valoarea, în octeţi,
specificată de offset; poziţionarea se face pentru fişierele
binare:
- faţă de începutul fişierului, dacă mod = SEEK_SET;
Programare C/C++ 187
- faţă de poziţia curentă, dacă mod = SEEK_CUR;
- faţă de sfârşitul fişierului, dacă mod = SEEK_END;
SEEK_SET, SEEK_CUR şi SEEK_END sunt definite în
<stdio.h>.
În cazul fişierului text fie offset trebuie să fie 0, fie trebuie să
fie valoarea returnată de un apel anterior al funcţiei ftell; în
cel de-al doilea caz, mod trebuie să fie SEEK_SET. Deci
pentru fişiere text avem situaţiile :
- indicatorul ia valoarea offsetului, care trebuie să fie 0
sau cea returnată de funcţia ftell, dacă mod =
SEEK_SET;
- indicatorul ia valoarea curentă (offset = 0 ), dacă mod
= SEEK_CUR;
- indicatorul ia valoarea de sfârşit de fişier (offset =0),
dacă mod = SEEK_END;
Dacă apare o eroare fseek returnează o valoare nenulă;
long int ftell ( FILE * ptr_fişier ); - furnizează poziţia relativă în octeţi, a
indicatorului fişierului sau – 1L dacă apare o eroare;
void rewind ( FILE * ptr_fişier ); - pune indicatorul fişierului la începutul
acestuia.
int setvbuf ( FILE * ptr_fişier, char * buffer, int mod, size_t dim ) - această
funcţie iniţializează un buffer alternativ pentru a fi utilizat de
funcţiile standard de I/O. Ea este apelată după deschiderea
fişierului şi înainte de prima operaţie asupra fişierului. Se va
utiliza zona de memorie referită de buffer, a cărei dimensiune
este precizată de “dim”; modul selectat poate fi :
_IOFBF – pentru utilizare buffer complet;
_IOLBF – pentru utilizare buffer pentru fiecare linie;
_IONBF – fără buffer-are.
Pointerul buffer nu este NULL; dacă totuşi este NULL, funcţia
va aloca totuşi un buffer. Funcţia va returna zero dacă a avut
succes, altfel va returna o valoare nenulă.
Dacă un program lucrează cu date obiect având, de exemplu,
o dimensiune de 1000 octeţi fiecare, se poate utiliza setvbuf
() pentru a crea un buffer a cărei dimensiune se potriveşte cu
data obiect.
FILE * tmpfile ( void ) - creează şi deschide un fişier binar, temporar, în
modul de actualizare / scriere (“r+b“), sau returnează
pointerul NULL, dacă apare o eroare. Fişierul temporar este
automat şters când se termină execuţia programului. O
funcţie tmpnam poate fi utilizată pentru asocierea de nume
fişierului temporar, unic.

187
188 Programare C/C++

Transferul caracterelor
int fgetc ( FILE * ptr_fişier ) - citeşte următorul caracter din fişier, sau
returnează EOF dacă întâlneşte sfârşit de fişier;
int fputc ( int c, FILE * ptr _fişier ) - scrie caracterul c (convertit la unsigned
char) în fişier şi returnează caracterul c, dacă a fost scris sau
valoarea EOF, dacă nu a fost scris;
int getc ( FILE * ptr_fişier ) - citeşte şi returnează următorul caracter din
fişier; returnează valoarea EOF dacă a întâlnit sfârşit de fişier
sau în caz de eroare;
int getchar ( ) - citeşte următorul caracter stdin; este echivalentă cu fgetc
(stdin);
int putc ( int c, FILE * ptr_fişier ) scrie caracterul c în fişier şi returnează
caracterul, dacă a fost scris, sau EOF, dacă nu.
int putchar ( int c ) echivalentă cu fputc (c, stdout);
int ungetc ( int c, FILE * ptr_fişier ) - pune înapoi în fişier un caracter.
Caracterul nu e scris în fişier ci e pus în bufferul asociat
fişierului. Următorul apel getc va returna acest caracter. Deci
pentru a “pune înapoi“ un caracter trebuie să se fi executat,
anterior, o operaţie de citire din fişier (getc). Funcţia
returnează c, dacă caracterul a fost “ pus înapoi “, altfel
returnează EOF.

Transferul şirurilor de caractere


char * fgets ( char * s, int n, FILE * ptr_fişier )- citeşte un şir de cel mult n-
1 caractere sau până întâlneşte caracterul '\n'; caracterele sunt
puse în tabloul oferit de s. Caracterul '\n' va fi dispus şi el în
tablou, dacă a fost citit (ca ultim caracter). Dacă apare o
eroare sau s-a întâlnit sfârşit de fişier este returnată valoarea
NULL, altfel returnează s.
char * gets ( char * s ); - citeşte şi returnează caractere din stdin, până
întâlneşte caracterul '\n'; caracterul '\n' nu e memorat în
tabloul s şi şirul este terminat cu un caracter nul. Dacă apare
o eroare sau nu se citesc caractere este returnat un pointer
nul, altfel este returnat s.
int fputs ( const char * s, FILE * ptr_fişier ); - scrie caracterele din tabloul
referit de s în fişier, până se întâlneşte caracterul de terminare
de şir, nul. Nu este scris, în mod automat, un caracter de
sfârşit de linie '\n', de către această funcţie. Dacă se
detectează o eroare este returnată valoarea EOF.
int puts ( const char * s); - scrie caracterele din tabloul referit de s în fişierul
stdout, până se întâlneşte caracterul de terminare de şir, nul.
Programare C/C++ 189
Spre deosebire de funcţia fputs(), funcţia puts() scrie, în mod
automat, ca ultim caracter, caracterul linie nouă '\n'. Dacă se
detectează o eroare este returnată valoarea EOF.

Transferul blocurilor de date


size_t fread ( const void * ptr, size_t dim, size_t n, FILE * ptr_fişier ) -
citeşte n elemente, fiecare cu lungimea dim, din fişier în
tabloul specificat de pointerul ptr, de exemplu, apelul:
valread = fread ( text, sizeof ( char ), 80, in_file ); citeşte 80
de caractere din fişierul “in_file“ şi le memorează într-un
tablou, referit de text. Funcţia va returna numărul de elemente
citite din fişier.
size_t fwrite ( const void * ptr, s ize_t dim, size_t n, FILE * ptr*fişier ); -
scrie n componente, fiecare cu lungimea dim, din blocul
specificat de pointerul ptr, în fişierul referit. Funcţia
returnează valoarea EOF dacă apare o eroare sau dacă este
sfârşit de fişier.
Dacă vrem să salvăm date numerice într-un fişier şi utilizăm fprintf (
), aceasta le va salva într-un şir de caractere. de exemplu secvenţa :
double numar = 1./3.;
fprintf ( pf, “ %f “, numar );
va salva numar ca un şir de opt caractere 0.333333; dacă utilizăm
specificatorul %.2f va salva caracterele 0.33, sau pentru %.12f va salva :
0.333333333333. Indiferent de specificator, odată salvat numar, nu se mai
poate reveni la precizia completă, când se citeşte fişierul.
În general, fprintf ( ) converteşte valorile numerice la şir de
caractere, cu o posibilă rotunjire a valorii (alterare). Pentru a păstra valoarea,
în aceeaşi formă, trebuie memorată exact în forma internă de reprezentare
(double, în acest caz). Această formă internă este denumită “formă binară“
de reprezentare. Deci cele două funcţii fread()/fwrite() realizează operaţiile
I/O în formă binară; nu se realizează nici o conversie de la forma numerică
la şir de caractere.
Un fişier poate fi deschis în cele două moduri: format text şi format
binar. Se poate deschide un fişier text în mod binar, şi se poate memora text
într-un fişier binar (se poate folosi funcţia getc() pentru a copia fişiere
binare).Totuşi, pentru a nu se crea confuzii, trebuie utilizat modul binar,
pentru a memora date în format binar, şi respectiv modul text, pentru date în
format text.

Transfer cu conversie de format


int scanf ( const char *format, arg_1, arg_2, . . . , arg_n ) - citeşte date din
stdin, corespunzător cu formatul specificat de şirul “format“.

189
190 Programare C/C++
Toate componentele arg_i, sunt pointeri. Funcţia returnează
numărul de elemente citite şi asignate cu succes, sau poate
returna valoarea EOF dacă s-a întâlnit sfârşit de fişier înainte
de conversia vreunui element.
int fscanf ( FILE * ptr_fişier, const char *format, arg_1, arg_2, …, arg_n ) -
citeşte date la fel ca funcţia scanf(), cu deosebirea că în loc să
citească stdin citeşte date din fişierul transmis ca prim
parametru.
int sscanf ( const char *s, const char *format, arg_1, arg_2, …, arg_n ) -
preia date, conform format-ului, din şirul s; este
asemănătoare cu funcţiile anterioare cu deosebirea că în loc
să preia elementele dintr-un fişier le preia dintr-un şir de
caractere s.
De exemplu secvenţa :
char data [ ] = “ 15 iunie, 1997 “, luna [ 10 ];
int an, zi;
-----------
sscanf ( data, “%d %s, %d “, &zi, &luna, &an );
va memora : luna = “ iunie “; zi = 16; an = 1997.
Alt exemplu :
if ( sscanf ( arg [ 1 ], “%lf “, &val ) != 1 )
{ fprintf ( stderr, “ numar gresit : %s\n “, argv [1] );
exit ( 1 );
}
va converti primul argument din linia de comandă (referit
argv[1]) la o valoare reală (double) şi verifică dacă a fost
memorată cu succes în s (deci dacă s-a făcut conversia); dacă
nu va tipări mesajul de eroare.
int printf ( const char * format, arg_1, arg_2. . . , arg_n ); - scrie
argumentele specificate în stdout, conform formatului
specificat de şirul de caractere “format“. Returnează numărul
de caractere scrise.
int fprintf ( FILE * ptr_fisier, const char * format, arg_1, arg_n . . . ,
arg_n ); scrie date, la fel ca funcţia anterioară, dar nu în
stdout ci în fişierul referit de primul argument. Dacă apare o
eroare returnează o valoare negativă, altfel returnează
numărul de caractere scrise.
int sprintf ( char * s, const char * format, arg_1, arg_2,. . . , arg_n ); -
argumentele specificate, arg_i, sunt convertite conform
formatului specificat, format, şi sunt depuse în tabloul de
caractere referit de s. Este depus în mod automat, caracterul
Programare C/C++ 191
nul la sfârşitul şirului s. Funcţia returnează numărul de
caractere depuse în s, mai puţin caracterul nul.
De exemplu :
int var = 2;
char nume [ 120 ];
----------------
sprintf ( nume, “ / usr / data % i / 1997 “, var );
va avea efectul ca şirul de caractere :
“ / usr / data2 / 1997 “
să fie memorat în nume.

191
192 Programare C/C++

Exemple de programe pentru prelucrarea fişierelor:

1. Programul copiază un fişier sursă într-un fişier destinaţie, simultan


cu afişarea pe ecran a conţinutului şi determină şi numărul de caractere
din fişierul sursă.

/* copierea unui fisier sursa intr-unul destinatie programul se va


lansa astfel:
cop_fis.exe fis_sursa.txt fis_dest.txt
*/
#include <stdio.h>
#include <stdlib.h>
int main ( int argc, char*argv[])
{
FILE *in, *out; /* se utilizeaza doua fisiere: sursa si destinatia */
int c;
long int contor = 0;
if ( argc != 3 )
{ /* daca nu sunt doua fisiere -> eroare !! */
fprintf ( stderr,"Trebuie doua nume de fisier\n");
return(EXIT_FAILURE);
}
if (( in = fopen ( argv[1], "r" )) == NULL)
{ /* nu se poate deschide primul fisier(citire)? -> eroare !! */
fprintf ( stderr, "Nu pot citi : %s.\n", argv[1] );
return ( EXIT_FAILURE );
}
if (( out = fopen ( argv[2], "w" )) == NULL)
{ * nu se poate deschide al doilea fisier(scriere)? -> eroare !! */
fprintf ( stderr, "Nu pot scrie in %s.\n", argv[2] );
return ( EXIT_FAILURE );
}
while ( ( c = getc ( in ) ) != EOF )
{
putc ( c, out );
putc ( c, stdout );
contor++;
}
fclose ( in );
fclose ( out );
Programare C/C++ 193
printf ("Fisierul a fost copiat.\n");
printf ( "\n Fisierul %s contine %ld caractere\n", argv[1], contor );
return (EXIT_SUCCESS);
}

2. Programul permite, la prima utilizare, crearea unui fişier


"cuvinte.txt", iar la utilizările ulterioare, adăugare de noi cuvinte la acest
fişier.

/* adaugcuv.c - creaza sau adauga cuvinte la fisierul "cuvinte.txt" */


#include <stdio.h>
#include <stdlib.h>
#define LUNG 20
void main()
{
FILE *pf;
char cuvant [ LUNG ];
if ( ( pf = fopen ( "cuvinte.txt", "a+") ) == NULL )
{
fprintf ( stderr , "Nu se poate deschide fisierul \"cuvinte.txt\"");
exit ( 1 );
}
puts ( "Introduceti cuvintele pentru adaugare la fisier,");
puts ( " Pentru sfarsit folositi tasta Enter la inceputul unei linii noi");
while ( gets ( cuvant ) != NULL && cuvant [0 ] != '\0' )
fprintf ( pf, "%s\n", cuvant );
/* sau se poate folosi functia fputs(char*, FILE*),
care nici ea nu trece automat la o linie noua, dar se va folosi
astfel:
fputs ( cuvant, pf );
fputs ( "\n", pf );
*/
puts ( "Fisierul contine urmatoarele cuvinte: ");
rewind ( pf ); /* pozitionare la inceputul fisierului */
while ( fscanf ( pf , "%s", cuvant ) == 1 )
puts ( cuvant );
fclose ( pf );
}

Pentru a determina sfârşitul citirii de cuvinte şi a depunerii lor în fişier se


compară cuvant[0] cu '\0', deoarece funcţia gets () descarcă caracterul '\n'.

193
194 Programare C/C++
Pentru a copia un text din intrarea standard stdin în fişierul referit de pf se va
utiliza următoarea secvenţă:

char c;
while (( c = getchar()) != EOF)
putc (c, pf);
fclose (pf);

3. Programul va afişa un fişier în ordinea inversă.

/* invers.c-afisare fisier in ordinea inversa */


#include <stdio.h>
#include <stdlib.h>
#define CTRL_Z '\032'
/* marcaj sfarsit fisier EOF */
void main ( int argc, char *argv[ ] )
{
char car;
FILE *pf;
long int contor, ultim;
if (argc != 2)
{
printf ( "Lipseste numele fisierului din linia de comanda\n" );
exit ( 1 );
}
if( ( pf = fopen ( argv[1], "r+b" ) ) == NULL )
{
printf ( "Nu se poate deschide fisierul:%s\n", argv[1] );
exit ( 1 ); /* deschis in: binar,citeste binar */
}
fseek ( pf, 0L, SEEK_END); /* pozitionare la sfarsit de fisier */
ultim = ftell ( pf ); /* ultima pozitie */
for ( contor = 1L ; contor <= ultim ; contor++ )
{
fseek ( pf, -contor, SEEK_END );
car = getc ( pf );
if ( car != CTRL_Z && car != '\r' )
putchar(car);
}
fclose(pf);
}
Programare C/C++ 195

Secvenţa de poziţionare în fişier şi tipărire în ordine inversă poate fi


scrisă şi sub forma:

fseek (pf, -1L, SEEK_END);


car = getc (pf);
putchar (car);
for ( contor = 1L ; contor < ultim ; contor++ )
{
fseek (pf, -2L, SEEK_CUR);/*după citirea şi scrierea caracterului
curent*/
car = getc (pf);/* se face o deplasare (căutare) cu 2 poziţii */
putchar (car); /* înapoi, una pentru cea curent tipărită */
}/* şi cea de-a doua pentru a ajunge pe poziţia anterioară */

4. Programul adaugă conţinuturile unei liste de fişiere la un anumit fişier;


parametrii sunt transmişi prin linie de comandă:
adauga fis_sursa1, fis_sursa2, fis_dest
care va adăuga fis_sursa1 şi fis_sursa2 la fis_dest.

/* adauga.c-adauga fisiere sursa la un fisier destinatie */


#include <stdio.h>
#define DIMBUF 1024
char buffer[DIMBUF];
void adauga ( FILE *sursa, FILE *dest);
void main ( int argc, char*argv[] )
{
FILE *pfa, *pfr;
int nrfisier;
/*Daca sunt mai putin de doua argumente in linia de comanda ,
programul se termina*/
if ( argc < 3 )
{
printf ("Utilizare:%s fisiere_sursa
fisiere_destinatie/u",argv[0]);
exit(1);
}
/*daca nu se poate deschide fisierul destinatie:EROARE si STOP*/
if ( ( pfa = fopen ( argv[argc-1], "a" ) ) == NULL )
{
printf ( "Nu se poate deschide fisierul:%s\n", argv[argc-1] );

195
196 Programare C/C++
exit(2);
}
/*Se initializeaza un buffer de 1024 octeti pt acest fisier*/
if ( setvbuf ( pfa, NULL, _IOFBF, DIMBUF ) != 0 )
{
fputs ( "Nu se poate crea bufferul de iesire\n", stderr );
exit(3);
}
/* pentru fiecare fisier din linia de comanda:
- daca are acelasi nume cu destinatia -> trece la fisierul urmator;
- daca nu poate fi deschis pentru citire -> urmatorul fisier;
- adauga continutul fisierului la fisierul destinatie */
for ( nrfisier = 1 ; nrfisier < argc-1 ; nrfisier++ )
{
if ( strcmp ( argv[argc-1], argv[nrfisier] ) == 0 )
printf ( "Nu se poate adauga fisierul la el insusi\n" );
else if ( ( pfr = fopen ( argv[nrfisier], "r" ) ) == NULL )
printf ( "Nu se poate deschide fisierul: %s\n",
argv[nrfisier] );
else
{
if ( setvbuf ( pfr, NULL, _IOFBF, DIMBUF ) != 0 )
{
printf ( "Nu se poate crea buffer de iesire\n" );
continue;
}
adauga ( pfr, pfa );
fclose ( pfr );
}
}
fclose(pfa);
}

void adauga ( FILE *sursa, FILE *dest )


{
size_t octeti;
extern char buffer[];
while ( ( octeti = fread ( buffer, sizeof (char), DIMBUF, sursa ) ) > 0 )
fwrite ( buffer, sizeof(char), octeti, dest );
}
Programare C/C++ 197
5. a) Programul creează un fişier, numit PRODUSE.DTA, pentru care
iniţializează 100 de componente cu structură struct_produs:

/* programul initializeza un fisier, care va contine date pentru


un numar de produse (MAX_PROD); */
#include <stdlib.h>
#include <stdio.h>
#define MAX_PROD 100
void main()
{
struct struct_produs{
char nume [20];
int nr_prod;
double stoc;
float pret;
char moneda [10];
};
struct struct_produs *prod;
FILE *ptr_fis;
int i;
if ( ( ptr_fis = fopen ( "PRODUSE.DTA", "wb" ) ) == NULL )
{
printf ( "Fisierul:PRODUSE.DTA nu poate fi deschis pentru”
“ scriere\n");
exit(1);
}
else
{
prod -> nume[0] = ' '; prod -> nume[1] = '\0';
prod -> stoc = 0; prod -> pret = 0;
prod -> moneda[0] =' '; prod -> moneda[1] = '\0';
for ( i = 1 ; i <= MAX_PROD ; i++ )
{
prod->nr_prod = i;
fwrite ( prod, sizeof ( struct struct_produs), 1, ptr_fis);
}
fclose(ptr_fis);
}
}

197
198 Programare C/C++
5. b) Actualizarea fişierului creat anterior, pentru un produs, al cărui număr
(cod) se citeşte (introduce) de la tastatură.

/* programul actualizeaza un fisier de produse, cu datele curente


*/
#include <stdlib.h>
#include <stdio.h>
#define MAX_PROD 100
void main()
{
struct struct_produs{
char nume [20];
int nr_prod;
double stoc;
float pret;
char moneda [10];
};
struct struct_produs *prod;
FILE *ptr_fis;
int pnr;
double stoc;
float pret;
char optiune[10];
if ( ( ptr_fis = fopen ( "produse.dta", "r+b") ) == NULL )
{
printf ("Fis. PRODUSE.DTA nu poate fi deschis pentru”
“ citire/scriere \n");
exit ( 1 );
}
else
{
printf ( "Numarul produsului de actualizat (0=stop):");
scanf ( "%d", &pnr ); while ( getchar() != '\n');
while ( pnr >= 1 && pnr <= MAX_PROD )
{
fseek ( ptr_fis, (pnr-1)*sizeof( struct struct_produs),
SEEK_SET);
fread ( prod, sizeof(struct struct_produs), 1, ptr_fis);
printf ( "%s, val: %d,stoc: %lf, pret: %f,moneda:”
“ %s \n",prod->nume,prod->nr_prod,
prod->stoc, prod->pret,prod->moneda);
Programare C/C++ 199
printf ( "Actualizare: nume/stoc/pret/moneda:");
gets (optiune );
while (optiune [0] != '\0' )
{
switch (optiune[0] )
{
case 'N':
case 'n': printf ( "Nume nou :" );
gets ( prod -> nume );
break;
case 's':
case 'S': printf ( "Stoc actualizat:" );
scanf ( "%lf", &stoc);
prod -> stoc = stoc;
while (getchar()!='\n');
break;
case 'p':
case 'P': printf ( "Pret nou:" );
scanf ( "%f", &pret );
prod -> pret = pret;
while ( getchar() != '\n' );
break;
case 'm':
case 'M': printf ( "Moneda:" );
gets ( prod -> moneda );
}
printf ( "Actualizare:Nume/Stoc/Pret/Moneda:" );
gets ( optiune );
}
fseek ( ptr_fis, (pnr-1)*sizeof(struct struct_produs),
SEEK_SET);
/*sau: fseek ( ptr_fis, -sizeof(struct struct_produs),
SEEK_CUR);*/
fwrite ( prod, sizeof(struct struct_produs), 1, ptr_fis);
printf ( "Numar produs de actualizat (0=stop):" );
scanf ( "%d", &pnr ); while ( getchar()!= '\n' );
}
fclose(ptr_fis);
}
}

199
200 Programare C/C++

5. c) Programul afişează pe display fişierul astfel creat şi actualizat. Vom


prezenta numai secvenţa de afişare, deoarece restul programului este
acelaşi (ca la prob. 4a şi 4b).

if ( ( ptr_fis = fopen ( "PRODUSE>DTA", "rb" ) == NULL )


{
printf("Fisierul PRODUSE.DTA,nu poate fi deschis pentru”
“ citire\n");
exit(1);
}
else
{
while ( fread ( prod, sizeof ( struct struct_produs ), 1,
ptr_fis ) == 1 )
if ( prod -> nume[0] != ‘\0’) /* sau: !strcmp(prod->nume,””) */
printf("%s, cod:%d, stoc:%lf, pret:%f, moneda:%s\n",
prod -> nume, prod -> nrprod, prod -> stoc,
prod -> pret, prod -> moneda);
close(ptr_fis);
}

Alte probleme propuse:


6. Construirea unui nou fişier, care conţine numai anumite produse, după o
anumită cheie (nume / cod sau stoc);
7. Sortarea alfabetică a fişierului de produse într-un nou fişier. Acelaşi lucru
pentru fişierele cu studenţii cu structura de tip an.

În general pentru prelucrarea şi modificarea componentelor unui fişier


binar (cu o anumita structură a componentelor) inclusiv în cazul eliminării,
sau inserării, unor componente, se va utiliza un fişier temporar (auxiliar); în
acest fişier se vor depune noile componente ale fişierului prelucrat, iar după
terminare se va schimba numele la cel al fişierului sursă iniţial. Algoritmul
pentru aceste operaţii este schiţat în continuare.
Programare C/C++ 201
FILE *fis_sursa , *fis_temp;
if ( fis_sursa = fopen ( "nume_fis_sursa", "rb" ) == NULL )
{/* ca in exemplele anterioare: mesaj eroare si exit program */}
if ( fis_temp = fopen ( "fis_tem.tmp", "wb" ) == NULL )
{/* ca in exemplele anterioare: mesaj eroare si exit program */}
while ( fread ( elem , sizeof ( struct s_elem ), 1, fis_sursa ) == 1 )
{/* prelucrarile asupra componentei "elem" de tip struct s_elem
*/
if ( !conditie_eliminare )
fwrite ( elem , sizeof (struct s_elem ), 1 , fis_temp );
}
fclose ( fis_sursa );
fclose ( fis_temp );
if ( remove ( fis_sursa ) )
printf ( "Eroare ! Nu se poate sterge fisierul sursa !\n" );
else if ( rename ("fis_temp.tmp", "nume_fis_sursa") )
printf ("Eroare !Nu s-au putut schimba numele fis_temp.tmp");

201
202 Programare C/C++

7.7. Funcţii matematice

Principalele funcţii matematice (algebrice şi trigonometrice)


frecvent utilizate în calcule sunt declarate în fişierul antet <math.h>:

double acos (double x) - calculează(x) în radiani,


în domeniul [0, ], cu x [-1,1];
double asin (double x) -calculează arcsin(x) în radiani,
în domeniul [-/2, /2] cu x [-1,1];
double atan (double x) - calculează arctg(x) în radiani,
în domeniul [-/2, /2];
double atan2 (double y, double x) - calculează arctg (y/x);
double ceil (double x) - returnează cel mai mic întreg >= x;
valoarea este de tip double;
double cos (double x) - calculează cos(x);
double cosh (double x) - calculează cosinus hiperbolic ch (x);
double exp (double x) - calculează ex ;
double fabs (double x) - returnează |x|;
double floor (double x) - returnează cel mai mare întreg <= x,
ca valoare reală (realizează trunchierea);
double fmod (double x, double y) - returnează restul împărţirii x/y
(x modulo y); semnul rezultatului este semnul lui x;
double frexp (double x, int*exp) - împarte un real de tip double în mantisă
(valoare returnată de funcţie) şi exponent-memorat ca întreg
referit de exp); mantisa este în domeniul [1/2,1); daca x=0,
ambele valori sunt zero.
double ldexp (double x, int exp) - calculează x*2exp ;
double log (double x) - calculează ln (x);
double log10 (double x) - calculează log10(x);
double modf (double x, double *ipart) - separă partea fracţionara şi întreagă
a unor valori reale (double) şi memorează partea întreagă la
pointerul ipart, iar partea fracţionară este returnată de
funcţie;
double pow (double x, double y) - returnează xy ;
dacă x<0, y trebuie să fie întreg,
dacă x=0, y trebuie să fie > 0;
double sin (double x) - calculează sin(x);
double sinh (double x) - calculează sinus hiperbolic, sh(x);
double sqrt (double x) - calculează x1/2 , x>0;
double tan (double x) - calculează tg(x);
Programare C/C++ 203
double tanh (double x) - calculează th(x);

Pe lângă acestea mai sunt câteva funcţii matematice şi în fişierul


antet <stdlib.h>:
int abs (int n) - returnează modul de n; de tip int;
long int abs (int n) - returnează ˝ n ˝ de tipul long int;
int rand (void) - returnează un număr aleator în domeniul
[ 0, RAND_MAX ], unde RAND_MAX e definit
în <stdlib.h> şi are valoarea (minimă) 32767;
void srand (unsigned int n) - iniţializează generatorul de numere aleatoare
la valoarea n.
Pe lângă aceste funcţii în fişierul antet <stdlib.h> mai sunt definite
şi alte funcţii cu caracter general.

Funcţii generale
void exit (int n) - termină execuţia programului, închizând orice fişier
deschis şi returnând starea ieşirii specificată de argumentul
n. EXIT_SUCCES şi EXIT_FAILURE, definite în <stdlib.h>
pot fi utilizate pentru a returna o stare de ieşire cu succes sau
cu eroare, respectiv. Mai pot fi utilizate rutinele abort sau
atexit.
char *getenv (char*s) - returnează un pointer la variabila referita de s sau
un pointer nul, dacă variabila nu există. Funcţia operează
dependent de sistem. De exemplu secvenţa:
char *homedir;
............
homedir = getenv("HOME");
poate fi utilizată pentru a lua valoarea variabilei utilizator
HOME, memorând un pointer la ea în "homedir".
void qsort (const void *tab, size_t n, size_t din , int funct_comp()); -
această funcţie sortează (ordonează) un tablou de date referit
de tab, care are n componente, fiecare de dimensiune dim
octeţi. Cel de-al patrulea argument este de tip "pointer la
funcţia ce returnează int şi ce ia două argumente pointer
void". Funcţia qsort apelează această funcţie când trebuie sa
compare două elemente din vector transmiţându-i pointeri la
elementele de comparat. Această funcţie e furnizată de
utilizator şi compară două elemente; ea va returna o valoare
mai mică ca zero, egală cu zero sau mai mare ca zero după
cum relaţia între cele 2 elemente e1 respectiv e2 este: e1 <
e2 , e1 = e2 sau e1 > e2.
de exemplu:

203
204 Programare C/C++
int data[1000], comp_int ( void *, void * );
......
qsort ( data, 1000, sizeof ( int ) , comp_int);
......
int comp_int ( void *pe1, void *pe2 )
{
int e1 = *( int *) pe1;
int e2 = *( int *) pe2;
return ( e1-e2 );
}
Funcţia qsort() sortează tabloul utilizând algoritmul
quicksort(); se poate utiliza şi funcţia bserach(), care are
aceleaşi argumente cu qsort(), dar realizează sortarea unui
tablou utilizând metoda de selecţie binară.
int system (char *s); - furnizează sistemului pentru execuţie comanda
conţinută în şirul de caractere referit de s; funcţia returnează
o valoare definită de sistem; dacă s este pointerul nul, funcţia
returnează o valoare nenulă dacă un procesor de control
(comandă) este capabil să execute comanda utilizatorului. De
exemplu, o comandă pentru crearea unui director:
system ( "md /usr/tmp/data");
Exemple de utilizare a funcţiilor matematice au fost prezentate pe
parcurs, în capitolele anterioare. Vom mai prezenta, totuşi, câteva simple
exemple.

/* programul calculeaza puterea unui numar real, la o putere reala


x^n, folosind metoda logaritmica: y = x^n;
-> ln(y) = n*ln(x);
-> e^ln(y) = e^(n*ln(x));
-> y = e^(n*ln(x)); */
# include <stdio.h>
# include <math.h>
void main()
{
double x,y,n;
printf ("\nProgram de ridicare la putere (metoda logaritmica).\n");
printf ("\nIntroduceti numarul=");
scanf ("%lf", &x);
printf ("Introduceti puterea=");
scanf ("%lf", &n);
y = exp ( n * log ( x ) );
Programare C/C++ 205
printf ("(%.2lf)^(%.2lf) = %.2lf\n\n", x, n, y);
}

/* calculul lui e^x, utilizand dezvoltarea in serie Taylor:


1 + x/1! + x^2/2! + x^3/3! + . . . + x^n/n! , cu o anumita precizie
si se compara cu valoarea furnizata de functia standard exp(x)
*/
#include <stdio.h>
#include <math.h>
void main ()
{
double ex, x, precizie, termen;
int n = 0;
printf ("Programul calculeaza exp(x); introduceti x=");
scanf ("%lf", &x);
printf ("Cu precizia:");
scanf ("%lf", &precizie);
ex = 1; termen = 1;
do
{
n++;
termen = termen * x / n;
ex = ex + termen;
} while ( termen > precizie );
printf ("seria Taylor exp(%f)=%f\n", x, ex);
printf ("Functia C exp(%f)=%f\n\n", x, exp(x));
}

/* programul determina o radacina a unei functii f(x)-


definita de utilizator, separata intr-un interval (a,b),
utilizand metoda injumatatirii intervalului*/
# include <stdio.h>
/* definirea unei functii de o variabila */
/* f(x) = x^2 - 4x -5, care are solutiile x1 = -1, x2 = 5 */
double f ( double x )
{
double valf;
valf = x*x -4*x -5;

205
206 Programare C/C++
return ( valf );
}
void main()
{
double a, b, mijloc, precizia, modul;
printf ("Introduceti intervalul a,b: ");
scanf ("%lf", &a);
scanf ("%lf", &b);
printf ("introduceti precizia de localizare a radacinii:");
scanf ("%lf", &precizia);
do
{
mijloc = (a+b)/2;
if ( f (a) * f (mijloc) < 0 )
b = mijloc;
else if ( f ( mijloc ) * f(b) < 0 )
a=mijloc;
else goto gata;
modul =a-b;
if ( modul < 0 )
modul = -modul;
} while( modul > precizia );
gata : printf ("Solutia : %lf\n\n", ( a+ b)/2 );
}
Programare C/C++ 207

7.8. Funcţii pentru alocarea dinamică a memoriei

Sunt două metode primare prin care un program C poate memora


informaţia în memoria principala a calculatorului. Prima metodă utilizează
variabilele globale şi cele locale, definite în program. În cazul variabilelor
globale memorarea acestora este fixă pe durata execuţiei programului, deci
este o alocare statică. Pentru variabilele locale, programul alocă dinamic
memorie din spaţiul de stivă al acestuia, dar trebuie cunoscut dinainte
spaţiul de memorie necesar pentru acestea.
A doua metodă, pe care programul o poate utiliza pentru a memora
informaţie, este prin intermediul funcţiilor de alocare dinamică malloc() şi
free (). Cu această metodă un program alocă memorie, pentru informaţie,
din spaţiul de memorie disponibil, denumit heap, care se întinde de la
sfârşitul programului (deci după codul şi datele programului) până la stivă.

Adrese mici Program

Variabile globale

Memorie
disponibilă pentru
alocare

Adrese mari Stiva

Stiva este tot o zonă de memorie dar este parcursă invers, de la


adrese mari către adrese mici, iar dimensiunea ei este determinată de
program. Ea este utilizată pentru a păstra variabilele locale, dar şi pentru
implementarea mecanismului de apel, respectiv revenire din apel de funcţie.
În cazul unui apel de funcţie, dintr-un program, se salvează în stivă
adresa instrucţiunii imediat următoare apelului; astfel, la terminarea
execuţiei funcţiei, adresa următoarei instrucţiuni de executat se va lua din
stivă şi va permite continuarea programului cu instrucţiunea următoare celei
de apel a funcţiei.
De asemenea, un program cu multe funcţii recursive va necesita mult
mai multă memorie decât unul ce nu utilizează funcţii recursive, deoarece
variabilele locale şi adresele de revenire, pentru apelurile recursive sunt
memorate în stivă.

207
208 Programare C/C++
Funcţiile pentru alocarea - eliberarea memoriei dinamice se afla în
fişierul <stdlib.h>.
Pentru a aloca dinamic memorie, adică în timpul execuţiei programului, se
utilizează funcţia malloc(), care alocă memorie în spaţiul de după variabilele
globale şi până la stivă. Funcţia malloc() este declarată astfel:
void * malloc (size_t număr_octeţi);
Funcţia returnează un pointer de tipul void, care înseamnă că trebuie utilizat
un operator explicit de conversie de tip (cast), când pointerul returnat de
această funcţie se asociază un pointer de un anumit tip .Dacă alocarea s-a
putut face funcţia returnează un pointer valid, iar dacă nu, va returna
pointer nul (0).
Deci după fiecare utilizare a acestei funcţii trebuie testat pointerul returnat
de malloc() pentru a fi siguri că alocarea s-a făcut cu succes; altfel se va
utiliza un pointer cu adresa 0 care va scrie peste tabela vectorilor de
întrerupere şi va conduce la blocarea calculatorului; zona alocată are
dimensiunea număr_octeţi.
Cealaltă funcţie free() returnează (eliberează) sistemului blocul de
memorie alocat anterior de funcţia malloc(). Bineînţeles că după eliberarea
memoriei se poate utiliza din nou funcţia malloc(). Această funcţie este
declarată astfel: free ( void * ptr)
Ori de câte ori este apelată această funcţie, ea trebuie apelată cu un
argument (pointer) valid, altfel se poate pierde controlul asupra
calculatorului. Aceste funcţii de alocare dinamică precum şi altele (calloc,
realloc) se află în fişierul antet standard <stdlib.h>. De asemenea mai sunt
disponibile funcţiile :
void * calloc ( size_t n, size_t dim ); - aloca un spaţiu continuu (bloc) de n
componente, unde fiecare componentă ocupă dim octeţi.
Spaţiul alocat este iniţializat cu 0. Funcţia returnează un
pointer la blocul de memorie alocat sau un pointer NULL
dacă nu există suficientă memorie pentru alocare.
void * realloc ( void *ptr, size_t dim ); - modifică (realocă) dimensiunea
unui bloc alocat anterior la "dim" octeţi, returnând un
pointer la noul bloc (care poate fi mutat în altă zonă de
memorie şi atunci se copiază vechiul conţinut) sau un pointer
NULL dacă apare o eroare.

// un exemplu simplu de alocare de memorie pentru un tablou si apoi


// eliberarea ei
#include <iostream.h>
Programare C/C++ 209
void main(void)
{
int *int_sir = new int[100];
float *float_sir;
int i;

if (int_sir != NULL) // daca s-a putut aloca memorie


{
for (i = 0; i < 100; i++) // se creaza tabloul de valori
int_sir[i] = i + 1;

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


cout << int_sir[i] << ' '; // se afiseaza tabloul
delete int_sir; // se elibereaza memoria ocupata de acesta
}

float_sir = new float[200]; // aceleasi operatii pentru un tablou de


valori reale

if (float_sir != NULL)
{
for (i = 0; i < 200; i++)
float_sir[i] = (i + 1) * 1.0;

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


cout << float_sir[i] << ' ';
delete float_sir;
}
}

Un alt exemplu de utilizare a funcţiilor pentru gestiunea memoriei a mai fost


prezentat anterior (construirea unei liste de tip coadă, conţinând numere, la
capitolul despre pointeri la structuri).
Vom mai considera un exemplu asemănător dar vom utiliza o funcţie
şi anume : o funcţie care inserează (adauga_coada) un nou număr într-o
listă de tip coadă; dacă elementul există deja, va actualiza numărul de
apariţii al valorii respective, iar dacă nu există îl va adăuga la coada de
numere. Se va utiliza şi o funcţie care afişează această lista de tip coadă. La
sfârşit spaţiul de memorie ocupat de listă este eliberat.

/* coada.c - programul construieste o coada utilizand o functie

209
210 Programare C/C++
adauga_coada, si apoi tipareste lista de valori intregi,
de tip coada
*/
#include <stdio.h>
#include <stdlib.h>
struct s_coada
{
int valoare;
int nr_aparitii; /* numar aparitii ale numarului in coada */
struct s_coada *urmator;
};
int adauga_coada ( struct s_coada *primul, int val )
{
struct s_coada *ptr_coada;
if ( primul == NULL )
{
primul = (struct s_coada *) malloc (sizeof(struct s_coada));
if ( primul == NULL )
{
printf ("Memorie plina\n");
return ( 0 );
}
else
{
primul -> valoare = val;
primul -> nr_aparitii = 1;
primul -> urmator = NULL;
return ( 1 );
}
}
else
{
ptr_coada = primul ;
while ( ptr_coada -> valoare != val
&& ptr_coada -> urmator != NULL )
ptr_coada = ptr_coada -> urmator;
if ( ptr_coada -> valoare == val )
ptr_coada -> nr_aparitii ++;
else
{
ptr_coada -> urmator =
Programare C/C++ 211
( struct s_coada *) malloc(sizeof (struct s_coada));
ptr_coada = ptr_coada -> urmator;
if ( ptr_coada == NULL )
{
printf ("Memorie plina!\n");
return ( 0 );
}
else
{
ptr_coada -> valoare = val;
ptr_coada -> nr_aparitii = 1;
ptr_coada -> urmator = NULL;
return ( 1 );
}
}
}
}
void afisare_coada ( struct s_coada * primul )
{
struct s_coada *ptr;
ptr = primul;
if (ptr == NULL)
printf("Coada goala!!\n");
while ( ptr != NULL )
{
printf ( "Numar: %d, numar de aparitii: %d\n",
ptr -> valoare, ptr -> nr_aparitii );
ptr = ptr -> urmator;
}
}
void main()
{
struct s_coada *prim = (struct s_coada *)
malloc (sizeof(struct s_coada));
/* initializarea adresei variabilei 'prim'; altfel daca prim = NULL
functia 'adauga_coada' nu primeste o adresa valida la care incepe
lista si la care se face actualizarea (adaugare element sau daca
acesta exista se incrementeaza campul 'nr_aparitii'
*/
int numar;
prim -> valoare = 0;

211
212 Programare C/C++
prim -> nr_aparitii = 0;
prim -> urmator = NULL;
printf ("Programul construieste si afiseaza o coada (lista) de”
“ numere\n");
printf ("Introduceti un numar:");
while ( scanf ("%d", &numar ) != EOF )
{
if (adauga_coada (prim , numar) == 0)
break;
printf ( "Introduceti numar:");
}
afisare_coada ( prim );
free ( prim );
}
Programare C/C++ 213

7.9. Funcţii recursive

Recursivitatea constă în definirea unui obiect în funcţie de el însuşi.


Ea este utilizată în definiţiile matematice (şiruri, funcţii, etc.). De exemplu
funcţia factorial este definită astfel:
- dacă n = 0, f ( 0 ) = 1;
- dacă n > 0 , f ( n ) = n * f ( n-1 );
După cum se poate observa cu ajutorul recursivităţii se poate defini
o mulţime infinită printr-o declaraţie finită; deci un număr infinit de
calcule se poate descrie printr-un program recursiv finit.
Algoritmii recursivi se utilizează în situaţiile în care fie problema de
rezolvat, fie structura datelor de prelucrat se definesc în termeni recursivi. În
rezolvarea algoritmilor recursivi se utilizează funcţiile, întrucât ele asociază
un nume unei acţiuni care poate fi activată (apelată) prin invocarea
numelui respectiv.
O funcţie F poate fi direct recursivă, dacă conţine o referire explicită
la ea însăşi sau poate fi indirect recursivă, dacă ea apelează o altă funcţie, iar
aceasta apelează (direct sau indirect) funcţia F. Rezultă, deci, că o funcţie
recursivă nu este vizibilă imediat din textul programului, ci trebuie
urmărite toate apelurile de funcţii.
Vom descrie algoritmul lui Euclid, utilizând o funcţie recursivă
pentru determinarea celui mai mare divizor comun, pentru două numere:

int cmmdc ( int a, int b )


{
int rest;
if ( b == 0 )
rest = a;
else
rest = cmmdc ( b , a % b );
return ( rest );
}

Ca şi în cazul instrucţiunilor repetitive cu un număr necunoscut de


paşi funcţiile recursive trebuie condiţionate în privinţa apelului recursiv de o
condiţie, care la un moment dat devine falsă şi astfel se termină prelucrarea
descrisă de procedura recursivă.
Variabilele, constantele şi tipurile definite local într-o funcţie
recursivă nu au nici o semnificaţie în afara ei. De fiecare dată când o
asemenea funcţie este apelată recursiv, un nou set de variabile locale este

213
214 Programare C/C++
creat. Deşi noile variabile au aceleaşi nume cu cele existente înainte de
apelul recursiv al funcţiei, valorile lor sunt distincte deoarece au domenii de
valabilitate diferite: identificatorii se referă întotdeauna la ultimul set de
variabile creat. Aceleaşi reguli sunt valabile şi pentru parametrii funcţiei,
care sunt înglobaţi prin definiţie în procedură.
Să considerăm un alt exemplu: să tipărim în ordine inversă un şir de
numere strict pozitive, de lungime arbitrară, care se termină cu o valoare
nulă sau negativă. Varianta recursivă a acestei funcţii este următoarea:

void invers (void)


{ /* tipareste in ordine inversa numerele pozitive introduse */
int numar;
printf ( "numar:" ) ;
scanf ( "%d", &numar );
if ( numar > 0 )
invers ();
printf ("%d", numar );
}

Pentru un şir de n numere se vor genera n apeluri recursive cu n


copii diferite pentru variabila locală numar. Pentru a clarifica mecanismul
unui apel recursiv, precum şi domeniul de valabilitate al variabilelor pentru
apeluri recursive, vom considera, pentru exemplul anterior, că se citesc 3
numere (cel de-al patrulea introdus va fi 0). Reamintim că la terminarea
execuţiei unei funcţii programul se continuă cu execuţia instrucţiunii
imediat următoare apelului funcţiei. Vom nota apelurile recursive, succesive,
ale funcţiei invers cu inv(i), pentru a le diferenţia. În mod asemănător vom
nota cu n(i) domeniul de valabilitate al variabilei numar, corespunzătoare
apelului i. Schematic apelurile recursive se desfăşoară astfel :

/* funcţia main ( ) */
.........
invers ( ); { apelul funcţiei invers ( ) }
inv ( 1 ) ; - primul apel al funcţiei
citeşte n(1) , n(1) > 0 ;
apel invers ;
inv ( 2 ) ;
citeşte n ( 2 ) , n( 2 ) > 0 ;
apel invers ;
inv ( 3 ) ;
citeşte n ( 3 ) , n( 3 ) > 0 ;
Programare C/C++ 215
apel invers ;
inv (4 ) ;
citeşte n (4 ) , n( 4 ) <=0;
scrie n ( 4 );
revenire din apel;
scrie n ( 3 ) ;
revenire din apel;
scrie n ( 2 ) ;
revenire din apel;
scrie n ( 1 ) ;
revenire din apel;
/* se continuă funcţia main ( ) */
.......

De fiecare dată când se termină execuţia unei funcţii inv( i ) se va


continua execuţia programului cu instrucţiunea imediat următoare apelului
(adresa acelei instrucţiuni este, în mod automat, salvată în stivă), adică cu
instrucţiunea printf ( "%d%", numar ), sau corespunzător apelului scrie (n(i-
1)); la terminarea primului apel al funcţiei (inv ( 1 )) se va reveni în
programul principal.
Funcţia utilizată va tipări numerele în ordine inversă, inclusiv ultima
valoare, care este zero sau negativă. Dacă dorim să nu tipărim şi această
valoare, care de fapt reprezintă sfârşitul şirului şi nu o valoare din şir,
trebuie modificată numai secvenţa de tipărire a numerelor din şir, astfel :

void invers (void)


{ /* tipareste in ordine inversa numerele pozitive introduse */
int numar;
printf ( "numar:" ) ;
scanf ( "%d", &numar );
if ( numar > 0 )
invers ();
if ( numar > 0 )
printf ("%d", numar );}
Alte exemple simple :

/* exemplu de functie recursiva : functia factorial */


#include <stdio.h>
long int factorial ( long int n )
{
long int rezultat;

215
216 Programare C/C++
if ( n <= 0 )
rezultat = 1;
else
rezultat = n * factorial ( n-1 );
return ( rezultat );
}
void main()
{
long int i;
printf ( "Valoarea pentru care se calculeaza factorial, i = " );
scanf ( "%li", &i );
printf ("\nFactorial ( %li ) = %li\n", i , factorial (i) );
}

Aceeaşi funcţie se poate implementa şi iterativ astfel:

long int factorial ( long int n )


{ long int j, rezultat = 1;
for ( j = 1 ; j <= n ; ++j )
rezultat *= j;
return ( rezultat );}

/*Calculul puterii intregi a unui numar real cu o functie recursiva */


double putere ( double x, int n )
{double rezultat;
if ( n == 0 )
rezultat = 1;
else if ( n > 0 )
rezultat = x * putere ( x , n - 1 );
else
rezultat = putere ( x , n + 1 ) / x;
return ( rezultat );}

void main ( void )


{double numar;
int exponent;
printf("Programul calculeaza puterea intreaga a unui numar real.\n");
printf("Numar = ");
scanf("%lf", &numar);
printf("Putere = ");
scanf("%d", &exponent);
Programare C/C++ 217
printf("(%.2lf)^(%d) = %.5le\n", numar, exponent, putere(numar,
exponent));}

/* scriere unui cuvant in oglinda (invers) */


#include <stdio.h>
void invers ( void )
{const char punct = '.';
char c;
c = getchar ( );
if ( c != punct )
invers();
if ( c != punct )
putchar ( c );
}
main ( )
{invers();
printf("\n");
}

Vom prezenta, în continuare, o funcţie recursivă folosită pentru


rezolvarea unei probleme clasice apărută la sfârşitul secolului al XIX-lea, în
Europa, cunoscută sub numele de "Turnurile din Hanoi". În esenţă
problema este următoarea: se dau trei tije, denumite stânga (sursa), mijloc
(aux) şi dreapta (dest) şi 64 de discuri, de dimensiuni (diametre) diferite,
aşezate unul peste altul în ordine descrescătoare (a diametrelor), pe tija din
stânga, ce formează un "turn". Se cere să se deplaseze turnul format din cele
n discuri (n=64) din poziţia stânga în poziţia dreapta, prin intermediul tijei
mijloc, respectând următoarele reguli:
- se mută câte un singur disc;
- nu se poate aşeza un disc mai mare, ca diametru, peste unul
mai mic.

Sursa Dest. Aux Sursa Dest. Aux


| | | | | |
1 === | | ==> | === |
2 ===== | | | ===== |
3 ======= | | ======= |
----------------------------- ---------------------------------------

Problema mutării celor n discuri poate fi descrisă recursiv astfel:

217
218 Programare C/C++
- dacă n=1 , se mută un singur disc;
- dacă n >1 se mută primele n-1 discuri în poziţia mijloc, prin
intermediul poziţiei dreapta; se mută apoi discul rămas în poziţia
dreapta, după care se mută cele n-1 discuri din poziţia mijloc în
poziţia dreapta, prin intermediul poziţiei stânga.
De exemplu deplasarea celor n discuri din poziţia stânga în poziţia dreapta,
muta (n, stanga, dreapta) este echivalentă cu următoarea secvenţă de sub-
probleme :
muta ( n-1 , stanga , mijloc );
deplaseză un disc din poziţia "stânga" în "dreapta";
muta ( n-1 , mijloc , dreapta );
Am redus problema la a deplasa n-1 discuri, iar acest procedeu poate fi
repetat pentru a rezolva problema. De exemplu operaţia muta ( n-1 , stanga ,
mijloc ) poate fi exprimată astfel:
muta ( n-2 , stanga , dreapta );
deplasează un disc din poziţia stânga în mijloc;
muta ( n-2 , dreapta , mijloc );
Algoritmul acesta de descompunere a problemei mutării a k discuri
în subprobleme de mutare a k-1 discuri poate continua până când se ajunge
la k-1=1, adică deplasarea unui singur disc. În algoritmul general de
rezolvare a acestei probleme va trebui să specificăm, pentru mutarea
discurilor de la poziţia sursă la cea destinaţie, şi poziţia intermediară care
este utilizată pentru a realiza un turn provizoriu. Vom nota această funcţie
astfel :
muta ( n , sursa , mijloc , destinatie );
care înseamnă că se mută n discuri de la poziţia "sursă" în poziţia
"destinaţie", utilizând poziţia intermediară "mijloc" pentru un turn
provizoriu.
Această mutare poate fi rezolvată prin descompunerea în
subprobleme astfel:
muta ( n - 1 , stanga , dreapta , mijloc );
deplasează un disc de la stânga la dreapta;
muta ( n - 1 , mijloc , stanga , dreapta );
Acest algoritm este valabil cât timp n>=1. Se observă că această
funcţie este recursivă, apelându-se pe ea însăşi cât timp n>=1. Întregul
program pentru rezolvarea problemei "Turnurilor din Hanoi" este
următorul:

/* Problema 'Turnurilor din Hanoi', rezolvata recursiv */


#include <stdio.h>
enum pozitie { stanga , mijloc , dreapta};
Programare C/C++ 219
void tipareste_pozitie ( enum pozitie poz )
{
switch ( poz )
{
case 0 : printf ( "stanga" );/* se poate case stanga */
break;
case 1 : printf ( "mijloc" );
break;
case 2 : printf ( "dreapta" );
}
}
void deplasare( enum pozitie sursa , enum pozitie dest )
{
printf ( "muta un disc din " );
tipareste_pozitie ( sursa );
printf ( " in " );
tipareste_pozitie ( dest );
printf ( "\n" );
}
void muta ( int n, enum pozitie sursa, enum pozitie inter,
enum pozitie dest )
{
if ( n > 0 )
{
muta ( n - 1 , sursa , dest , inter );
deplasare ( sursa , dest );
muta ( n - 1 , inter, sursa , dest );
}
}
void main ()
{
int nd;
printf ( "Nr. de discuri :" );
scanf ("%d", &nd);
printf ("Mutarile pt. deplasarea unui turn cu %d discuri sunt :\n",
nd);
muta ( nd , stanga , mijloc , dreapta );
printf("\n\n");
}

Recursivitate şi iteraţie

219
220 Programare C/C++

Pentru a transforma un algoritm într-unul recursiv trebuie să


descompunem problema în subprobleme a căror rezolvare să fie similară cu
rezolvarea problemei iniţiale; descompunerea aceasta poate continua până la
îndeplinirea unei condiţii. De exemplu în cazul problemei "Turnurilor din
Hanoi" această operaţie a constat din transformarea problemei mutării celor
n discuri într-o subproblemă de mutare a n-1 discuri; această descompunere
se repetă până când se obţine o problemă foarte simplă, în exemplul anterior
mutarea unui singur disc.
Recursivitatea poate fi, întotdeauna, transformată în iteraţie. În
general forma iterativă este mai eficientă decât cea recursivă, în ceea ce
priveşte timpul de execuţie şi memoria ocupată; în schimb are dezavantajul
unei elaborări mai dificile a soluţiei, decât forma recursivă care, de obicei,
este mai simplă.
Să consideră ca exemplu problema generării numerelor lui
Fibonacci:
0, 1, 1, 2, 3, 5, 8, 13, 21, 44, 65, 109, 174, . . . . .
care se generează pe baza relaţiei:
Fib ( n ) = 1, pentru n = 1,
Fib ( n ) = Fib ( n - 1 ) + Fib ( n - 2 ), pentru n > 2.
Descrierea recursivă a acestei funcţii este următoarea:

/* Determinarea sirului Fibonacci utilizand o functie recursiva: */


#include <stdio.h>
int fib (int n)
{
int rezultat;
if (n <= 2 )
rezultat = 1;
else
rezultat = fib ( n-1 ) + fib ( n-2 );
return ( rezultat );
}
void main ()
{
int index;
printf ("Pozitia pentru care se determina un numar din sirul”
“ Fibonacci, poz=");
scanf ("%d", &index );
printf ("Numarul al %d-lea din sir este: %d\n", index, fib(index));
}
Programare C/C++ 221

Această funcţie este, însă, ineficientă, deoarece pentru, de exemplu,


Fib ( 5 ) se vor apela şi calcula Fib (3) şi Fib(1) de câte două ori, iar Fib (2)
de trei ori. De fapt pentru determinarea termenului n din acest şir se va
ocupa o zonă de memorie proporţională cu n pentru memorarea termenilor
şirului Fibonacci. Un termen din şirul lui Fibonacci va fi determinat, mai
simplu, cu ajutorul unei funcţii iterative, astfel:

int fibi ( int n )


{
int fn1 = 1, fn2 = 1, i , aux;
for ( i = 3 ; i <= n ; ++i )
{
aux = fn1;
fn1 = fn1 + fn2;
fn2 = aux;
}
return ( fn1 );
}

Această funcţie nu calculează fiecare termen decât o singură dată şi


nu rezervă, pentru memorarea rezultatelor intermediare, decât trei variabile:
fn1, fn2 şi aux. Alegerea între soluţia iterativă şi cea recursivă se face în
funcţie de spaţiul de memorie ocupat, eficienţa în execuţie, claritatea
programului, uşurinţa testării şi întreţinerii programului. Varianta iterativă
este preferată pentru probleme de complexitate redusă, pentru a obţine
eficienţă maximă. Varianta recursivă este preferată în situaţiile în care
iteraţia ar cere tehnici deosebite de programare, algoritmul pierzându-şi din
claritatea exprimării, ca în cazul problemei "Turnurilor din Hanoi" la care
soluţia recursivă este mult mai simplă decât cea iterativă.
Toate funcţiile, în C, sunt egale spre deosebire de procedurile din
Pascal sau din Modula-2 care pot fi încuibate, unele în altele; în acest caz
procedurile incluse într-o procedură sunt ignorate (necunoscute)
procedurilor din altă includere.
În C doar funcţia main() este un pic mai specială, deoarece execuţia
unui program, oricâte funcţii ar avea, începe cu prima declaraţie din
funcţia main.
Dar şi funcţia main(), ca orice altă funcţie, poate fi apelată de alte
funcţii:

#include <stdio.h>

221
222 Programare C/C++
void main ( )
{
void apel ( );
char car;
printf ( "Introduceti un caracter (Q-pentru sfarsit):\n");
car = getchar ( );
if ( car != 'Q' )
apel ( );
}
void apel ( )
{
main ( );
}

De fapt aici avem un apel recursiv, indirect, al funcţiei main() prin


intermediul funcţiei apel(), până la tastarea caracterului 'Q'.

S-ar putea să vă placă și