Limbajul Cc++

Lecția 1

Cuvinte importante:

– elemente de bază ale limbajului C/C++: identificatori, separatori, comentarii;

– structura unui program C/C++: funcția main (), definirea și apelul unei funcții;

– preprocesare: directiva #include și fișierul antet (header), directiva #define;

– utilizarea funcțiilor din bibliotecile standard: prototipul unei funcții; apelul funcției dintr-o bibliotecă standard;

– etapele dezvoltării unui program C/C++: compilare, link-editare, execuție;

– variabile: tipuri de variabile predefinite, declararea variabilelor, variabile locale și globale;

– citirea datelor de la tastatură și afișarea datelor pe ecran în limbajul C/C++: funcția scanf () și funcția printf (), fluxul I/O cin și fluxul I/O cout.

1.1. Noțiuni introductive

Istoric C/C++

1972 – Brian Kerninghan și Dennis Ritchie au conceput un limbaj cu destinație universală, denumit C;

1980 – Bjorne Stroustrup publică specificațiile limbajului C++, o extensie a limbajului C destinată programării orientate pe obiecte.

Setul de caractere folosit:

Setul de caractere utilizat pentru scriere programelor C/C++ este setul de caractere al codului ASCII.

1.2. Elemente de bază ale limbajului C/C++

Identificatori

Identificatorii, întâlniți și sub denumirea de nume simbolice, au rolul de a denumi elemente ale programului C/C++: constante, variabile, funcții etc.

Din punct de vedere sintactic, un identificator este constituit dintr-o succesiune de litere, cifre sau caracterul ‘_’ (liniuță de subliniere), primul caracter fiind obligatoriu litera sau liniuța de subliniere. Lungimea maximă a unui identificator este de 31 de caractere.

Limbajul C/C++ este “case-sensitive”, adică face diferența între literele mici și literele mari. De aceea, identificatorii sunt diferiți în funcție dacă sunt scriși cu litere mici și mari.

Cuvintele-cheie (keywords) sunt identificatori speciali, cu înțeles predefinit, care pot fi utilizați numai în construcții sintactice în care sunt specificați. De exemplu: if, while etc. În limbajul C/C++, toate cuvintele-cheie se scriu numai cu litere mici.

Separatori

Separatorii au rolul de a separa unitățile sintactice:

– Ca separatori “generali” se utilizează caracterele albe: spațiu (‘ ‘), TAB (‘\t’), sfârșit de linie (‘\n’) și comentariile.

– Separatorii specifici sunt folosiți, de exemplu, pentru a separa unele construcții sintactice: variabilele sunt separate prin caracterul virgula (‘,’).

– Delimitatorii sunt folosiți, de exemplu, pentru:

– a delimita sfârșitul unei instrucțiuni sau al unei declarații – caracterul punct și virgulă (‘;’);

– a delimita o constantă de tip caracter – caracterul apostrof (‘);

– a delimita constantele șir de caractere (ghilimelele).

Comentarii

Comentariile sunt texte care vor fi ignorate de compilator, dar au rolul de a explicita și de a face mai vizibil pentru programator anumite secvențe de program.

Din punct de vedere sintactic, un comentariu este:

– o succesiune de caractere încadrată între /* și */ ; aceste comentarii pot fi formate din mai multe linii;

– o succesiune de caractere care începe cu // și se termină la sfârșitul unei singure linii.

1.3. Structura unui program C/C++

Un program C/C++ este format dintr-o succesiune de module, denumite funcții. Una dintre aceste funcții este funcția principală denumită main (). Această funcție delimitează instrucțiunile din programul principal pe care le execută mai întâi calculatorul. Astfel, funcția main () este o funcție specială, care trebuie să apară obligatoriu o singură dată în orice program C/C++.

Cel mai simplu program C/C++ (care nu face nimic) arată astfel:

void main (void)

{ }

Programul de mai sus nu execută nimic, el fiind constituit numai din funcția main ().

În general, o funcție este o colecție de instrucțiuni care execută o anumită sarcină. O funcție se definește prin:

– antetul funcției;

– corpul funcției.

Antetul funcției conține numele funcției, tipul rezultatului pe care îl calculează și îl returnează funcția și o listă de parametri, fiecare având un tip precizat, prin care funcția comunică cu exteriorul ei, încadrată între paranteze rotunde:

Corpul funcției este constituit din declarații și instrucțiuni care execută diferite sarcini. Toate declarațiile și instrucțiunile din corpul funcției sunt încadrate între paranteze acolade.

Sintaxa definirii unei funcții este:

<tip_rezultat> nume_functie (<lista_parametrii>)

{ <corpul_functiei> }

Notă: În cazul funcției main () prezentată mai sus, cuvântul-cheie void, care apare între paranteze, spune compilatorului de C că funcția nu utilizează nici un parametru. Cuvântul-cheie void, care apare înaintea funcției main () specifică faptul că funcția nu întoarce nici un rezultat (este de tip void).

În mod obișnuit funcțiile folosesc instrucțiunea return pentru încheierea execuției lor și pentru a returna rezultatul calculelor către funcția care le-a apelat.

Sintaxa instrucțiunii return este:

return <expresie>;

unde:

– <expresie> – specifică valoarea returnată de funcție și este, de fapt valoarea funcției.

În cazul funcției main (), dacă este specificat cuvântul-cheie void înaintea funcției, atunci nu se folosește instrucțiunea return pentru returnarea rezultatului funcției. Dacă funcția main () este precedată de un alt tip de dată (de exemplu int) atunci este necesară folosirea instrucțiunii return. În acest caz, rezultatul returnat de funcția main () este preluat de sistemul de operare și oferă indicații despre modul de funcționare a programului. Când execuția unui program se termină cu succes, în mod uzual programul returnează valoarea 0.

De exemplu:

int main (void)

{ return 0; }

Acest program nu face nimic, dar se termină cu succes.

În caz că apar erori pe parcursul execuției programului, funcția main () returnează o valoare diferită de 0, care, de obicei, codul specific erorii care a apărut pe parcursul execuției programului.

1.4. Preprocesare

Preprocesorul este un program lansat în execuție automat înainte de compilare. El execută toate directivele preprocesor incluse în program, efectuând substituții de texte. Toate directivele incluse în program încep cu caracterul # și sunt puse înaintea oricăror declarații de variabile sau funcții (deci, sunt plasate la începutul programului).

De exemplu, #include, #define, #if, #undef etc.

Pentru elaborarea programelor trebuie să înțelegem două dintre aceste directive, și anume, #include și #define

Directiva #include este utilizată pentru a include într-un program un fișier antet (header) standard sau creat de programator.

Un fișier antet (header) conține declarațiile funcțiilor, constantelor, variabilelor și tipurilor definite într-o bibliotecă standard sau creată de programator. Fișierul antet este specific bibliotecii folosite și are întotdeauna extensia .h .

Pentru a include într-un program un fișier antet (existent în biblioteca standard a limbajului) se folosește sintaxa:

#include <<nume_fisier_antet>.h>

De exemplu:

#include <math.h> – include fișierul antet al bibliotecii de funcții matematice;

#include <iostream.h> – include un fișier antet al bibliotecii limbajului C++ cu funcții de intrare/ieșire;

#include <stdio.h> – include un fișier antet al bibliotecii limbajului C cu funcții de intrare/ieșire.

Directiva #define permite definirea unei constante simbolice. Sintaxa folosită este:

#define <identificator_constanta> valoare

Ca efect, preprocesorul va substitui în program orice apariție a identificatorului de constantă cu valoarea acesteia (<valoare>).

De exemplu:

#define PI 3.1415

#define NrMaxPersoane 35

Notă: Se recomandă utilizarea constantelor simbolice atunci când se dorește să se asocieze o denumire mai sugestivă la o valoare. De asemenea, prin utilizarea constantelor simbolice programul devine mai ușor de modificat.

Observație: Fișierele antet conțin și definiții de constante simbolice. De exemplu:

– în fișierul antet values.h sunt definite constante simbolice cum ar fi: MAXINT (cu valoarea 32767) și MAXLONG (cu valoarea 2147483647) etc.;

– în fișierul antet conio.h sunt definite constantele simbolice pentru culori (de exemplu: BLACK cu valoarea 0, BLUE cu valoarea 1, GREEN cu valoarea 2, RED cu valoarea 4 etc.).

1.5. Utilizarea funcțiilor din bibliotecile standard

Pot exista unele operații care sunt frecvent utilizate în cadrul unui program C/C++ (cum ar fi: citirea datelor, scrierea datelor, ștergerea ecranului, extragerea radicalului etc.) pentru care nu există instrucțiuni specifice ale limbajului C/C++.

Din acest motiv, s-au constituit biblioteci de funcții (elaborate de firma proiectantă a mediul de programare folosit) care conțin colecții de funcții de utilitate generală grupate pe categorii. Pentru a putea fi utilizate, se include la începutul programului (cu ajutorul directivei #include) fișierul antet al bibliotecii și se apelează funcția care ne este necesară.

Pentru a apela o funcție trebuie să se cunoască numele și formatul (prototipul) acesteia. În biblioteci există sute și sute de prototipuri de funcții care nu pot fi toate învățate, ci vor fi reținute cele mai des folosite. Însă, în Help vom găsi prototipul tuturor funcțiilor din bibliotecile standard și explicații despre modul de funcționare a acestora.

Prototipul unei funcții este compus din: numele funcției, tipul valorii calculate de funcție, parametrii funcției. Cu alte cuvinte, prototipul unei funcții are structura unui antet de funcție.

Apelul unei funcții dintr-o bibliotecă standard se face folosind sintaxa:

nume_functie (<lista_valori_parametrii>);

Valorile parametrilor de la apelul funcției trebuie să corespundă ca număr, ordine și tip cu parametrii specificați în prototipul funcției.

De exemplu:

1. În fișierul antet math.h care conține funcții matematice este declarată funcția de extragere a radicalului sqrt (). Prototipul funcției sqrt () este:

double sqrt (double x);

Funcția calculează radicalul valorii x transmise ca parametru.

2. În fișierul antet conio.h este declarată funcția clrscr () care are ca efect ștergerea conținutului ferestrei curente (adică fereastra care coincide, implicit, cu întreg ecranul care are implicit culoarea negru). Prototipul acestei funcții este:

void clrscr (void);

3. Dacă dorim să schimbam culoarea de fundal a ecranului vom folosi funcția textbackground (), declarată tot în fișierul antet conio.h. Prototipul funcției este:

void textbackground (int culoare);

Pentru a schimba culoarea ecranului în verde, se va folosi funcția textbackground () pentru a stabili culoarea de fundal, și apoi funcția clrscr () pentru a colora ecranul în culoarea de fundal stabilită:

textbackground (GREEN);

clrscr ();

1.6. Etapele dezvoltării unui program C/C++

1. Editarea instrucțiunilor programului. Pentru această operație este necesar un editor de texte (tip ASCII). Rezultatul este un fișier sursă, care are extensia .cpp (pentru C++) sau .c (pentru C).

2. Compilarea programului. Pentru această operație se lansează în execuție un program special, denumit compilator. Compilatorul analizează textul sursă al programului din punct de vedere sintactic, semnalând eventualele erori. Dacă programul nu conține erori sintactice, compilatorul traduce acest program în limbajul-mașină. Rezultatul compilării este un fișier denumit fișier obiect, care are extensia .obj (sub Windows/DOS) sau extensia .o (sub Linux).

3. Editarea de legături. Această operație este realizată de un program special denumit editor de legături (sau link-editor). Editorul de legături asamblează toate modulele obiect din care este alcătuită aplicația (inclusiv, combină instrucțiunile programului din fișierul obiect cu funcțiile apelate din bibliotecile standard). Rezultatul link-editării este programul executabil, care are extensia .exe.

Procesul de compilare a unui program și de editare a legăturilor

1.7. Variabile

Variabila este o zonă temporară de stocare, rezidentă în memoria RAM, care are un nume simbolic (identificator) și stochează un anumit tip de date. Ea poate fi modificată pe parcursul execuției programului.

Pentru utilizarea unei variabile într-un program C/C++ trebuie ca aceasta să fie declarată. La declararea variabilei trebuie specificat numele simbolic al variabilei, tipul acesteia și, eventual, o valoare inițială care se atribuie variabilei.

Tipurile de date predefinite

Un tip de date definește mulțimea de valori pe care variabila poate să le stocheze, modul de reprezentare a acestora în memorie, ca și setul de operații pe care programul poate să le realizeze cu aceste date.

Limbajul C/C++ acceptă următoarele tipuri de bază: int, char, float, double, void.

Observație:

În funcție de compilatorul utilizat și de sistemul hardware folosit, domeniul valorilor și reprezentarea internă în memorie pentru tipurile de date, enumerate mai sus, pot fi diferite.

Tipul de date int

Valorile datelor de tip int sunt numere întregi, cuprinse, de obicei, în intervalul [-32768, 32767], și reprezentate în memorie pe 2 bytes, un bit fiind alocat pentru semn.

Tipul de date int suportă modificatori de tip unsigned și long.

Notă: Un modificator de tip schimbă domeniul valorilor pe care o variabilă le poate păstra sau modul în care compilatorul stochează o variabilă. Pentru a modifica un tip, se plasează modificatorul în fața numelui tipului de bază.

Folosind și modificatorii de tip, se obțin următoarele tipurile de date întregi.

Notă: Atunci când un tip de date nu este precizat, implicit este considerat int. Astfel, se poate specifica numai modificatorul long sau unsigned sau unsigned long, tipul fiind implicit int.

Constantele întregi sunt numere întregi din intervalul corespunzător tipului.

Ele pot fi specificate în:

– baza 10 – folosind notația uzuală;

– baza 8 – constantă este precedată de un 0 (zero) nesemnificativ;

– baza 16 – constantă este precedată de 0x sau 0X.

De exemplu:

25 – constanta zecimală de tip int;

28231567 – constantă de tip long int;

01230 – constantă octală;

0x1a3 – constantă hexazecimală.

Tipul constantei este determinat implicit de valoarea ei.

Tipul de date char

Tipul char este folosit pentru a păstra valori de tip caracter. În memorie, tipul char este reprezentat pe un byte, cu semn. O variabilă de tip char poate păstra numere întregi cu valori cuprinse între -128 și 127. Acest tip suportă un singur modificator, unsigned. Se obțin astfel următoarele tipuri de date char :

Se poate atribui o valoare unei variabile de tip char într-una din modalitățile următoare:

– atribuirea codului ASCII corespunzător caracterului; de exemplu litera A are codul ASCII 65:

char litera = 65;

– folosirea unei constante de tip caracter care apare între două apostrofuri:

char litera = ‘A’;

Notă: Variabilele de tip char nu pot păstra decât un caracter la un moment dat. Pentru a păstra un șir de caractere trebuie declarată o variabilă indexată de tip char.

Constantele de tip char (unsigned char) pot fi numere întregi din intervalul specificat sau caracterele care au codurile ASCII în intervalul specificat. Rezultă că datele de tip char par a avea o natură duală – caractere ASCII și numere întregi. Dar compilatorul interpretează valorile de tip char ca fiind caractere ASCII în funcție de valorile numerice asociate (adică codurile ASCII asociate). De exemplu, caracterul ‘A’ și constanta 65 au pentru calculator aceeași semnificație.

Caracterele grafice (tipăribile) (cu coduri ASCII de la 32 la 127) se pot specifica încadrând caracterul respectiv între apostrofuri. De exemplu: ‘a’, ‘9’, ‘ ‘, ‘*’.

Caracterele speciale (netipăribile) se pot specifica încadrând între apostrofuri o secvență escape. Secvența escape este formată din caracterul \ (backslash) și o literă sugestivă sau o valoare ASCII în octal sau în hexazecimal. Exemple de reprezentare a caracterelor speciale prin secvențe escape:

Constantele șir de caractere sunt formate dintr-o succesiune de caractere încadrate între ghilimele.

Observație: Limbajul C++ nu utilizează secvența escape \’ pentru caracterul apostrof din cadrul șirului de caractere la ieșire.

De exemplu:

“Totul despre C/C++”

“ Prima linie afișată pe ecran \n A doua linie afișată pe ecran”

Tipul de date float și double

Tipul de date float este folosit pentru a păstra valori reale în virgula mobilă (numere negative și pozitive care conțin părți fracționare). În memorie, tipul float este reprezentat, de obicei, pe 4 bytes. O variabilă de tip float poate păstra numere reale în intervalul [3.4*10-38, 3.4*10+38] [-3.4*10+38, -3.4*10-38].

Tipul de date double este folosit pentru a păstra valori reale în virgula mobilă (numere negative și pozitive care conțin parți fracționare). În memorie, tipul double este reprezentat pe 8 bytes. Tipul de date double acceptă și modificatorul de tip long.

Folosind și modificatorul de tip long, se obțin următoarele tipuri de date reale de tip double:

Constantele reale care se pot reprezenta în memoria calculatorului sunt numere raționale din intervalele specificate. Constantele reale pot fi specificate în notația obișnuită sau în format științific.

Formatul științific (sau exponențial) prezintă numărul real utilizând o singură cifră în stânga virgulei, un număr oarecare de cifre în dreapta virgulei și un exponent (precedat de litera e sau E) reprezentând o putere a lui 10. Valoarea numărului se obține înmulțind numărul cu 10 la puterea specificată.

De exemplu:

-17.6 – în format obișnuit;

-12.56 – în format obișnuit;

– 3.1415967E+7 (în format științific) = 3.1415967*107 = 31415967 (în format obișnuit).

Tipul void

Tipul void este un tip special, pentru care mulțimea valorilor este vidă. Acest tip se utilizează când este necesar să se specifice absența oricărei valori. De exemplu pentru tipului funcțiilor care nu întorc nici un rezultat, cum a fost cazul funcției main ().

Observație: În limbajul C/C++ nu există un tip de date special pentru valori logice (true și false). Valoarea 0 este asociată valorii false, orice valoare diferită de 0 este asociata valorii true.

Sintaxa folosită pentru declararea de variabile este:

<tip> <nume_v1> [= <expresie>] [, <nume_v2> [= <expresie2>] … ];

unde:

– <tip> – specifică tipul de data al variabilelor;

– <nume_v1>, <nume_v2>, … – specifică numele simbolic al variabilelor care se declară (adică, identificatorii);

– <expresie1>, <expresie2>, … – specifică o expresie de inițializare; expresia trebuie să fie evaluabilă în momentul declarării; sunt opționale și sunt folosite pentru a atribui unor variabile anumite valori inițiale.

Notă: Se pot declara simultan mai multe variabile de același tip, separând numele lor prin virgulă.

Instrucțiunea de declarare a unei variabile poate fi plasată în interiorul unei funcții (în cazul nostru, al funcției main ()) sau în exteriorul oricărei funcții (în cazul nostru în exteriorul funcției main ()). Dar declarația unei variabile trebuie sa preceadă orice referire la variabila respectivă.

Observație: În limbajul C, declarațiile de variabile trebuie să fie plasate la începutul corpului funcției (în cazul nostru, al funcției main ()), înaintea oricărei instrucțiuni. În limbajul C++, declarațiile de variabile pot fi plasate oriunde în corpul funcției, dar trebuie să preceadă orice referire la ele.

Variabile locale și globale

O variabilă locală este recunoscută numai la nivelul unei funcții. Declararea unei variabile locale se face prin plasarea instrucțiunii de declarare în interiorul corpului funcției. O variabilă locală nu este automat inițializată cu valoarea 0.

O variabilă globală este recunoscută la nivelul tuturor funcțiilor care compun programul. Declararea unei variabile globale se face prin plasarea instrucțiunii de declarare în exteriorul oricărei funcții. O variabilă globală este automat inițializată cu valoarea 0.

De exemplu:

#include <stdio.h>

void main ()

{

int varsta;

int greutate;

int inaltime;

printf ("Totul despre C/C++");

………………

}

#include <stdio.h>

int main ()

{

int varsta = 41, greutate = 75, inaltime = 180;

printf (" varsta=%d\n greutate=%d\n inaltime=%d\n", varsta, greutate, inaltime);

return 0;}

#include <stdio.h>

void main ()

{

int varsta, greutate, inaltime;

printf ("Totul despre C/C++");

…………….

}

Alte exemple de declarații de variabile ce pot fi folosite într-un program:

int a, b=3, c=8;

char h;

float x=b*5.6, y;

1.8. Citirea datelor de la tastatură și afișarea datelor pe ecran

În limbajul C/C++ nu există instrucțiuni specializate pentru citirea/scrierea datelor. Aceste operații se realizează prin intermediul unor funcții existente în bibliotecile standard ale limbajului. Operațiile de intrare/ieșire diferă în limbajul C++ față de limbajul C.

Citirea datelor de la tastatură și afișarea datelor pe ecran în limbajul C

Citirea și scrierea datelor se realizează prin intermediul unor funcții specifice.

Citirea datelor cu format specificat

Funcția scanf () permite citirea datelor cu un format specificat. Această funcție este declarată în fișierul antet stdio.h și are prototipul:

int scanf (<format>, <adresa_v1>, <adresa_v2>, …, <adresa_vn>); unde:

– <format> – este un șir de caractere care poate conține specificatori de format, „caractere albe” (spațiu, tab sau newline) și alte caractere; caracterele albe prezente la intrare vor fi ignorate; celelalte caractere (care nu fac parte dintr-un specificator de format) trebuie să fie prezente la intrare în pozițiile corespunzătoare;

– <adresa_v1>, <adresa_v2>, …, <adresa_vn>) – adresele de memorie (pointeri) care specifică variabilele în care se vor memora valorile citite de la tastatură; adresa unei variabile se obține cu ajutorul operatorului de referențiere & (de exemplu: &v1 este adresa de memorie a variabilei v1).

Efectul apelului funcției scanf () este următorul: se parcurge succesiunea de caractere introdusă de la tastatură și se extrag valorile care trebuie citite conform formatului dat; valorile citite sunt memorate în ordine în variabilele specificate prin adresele lor în lista de parametri ai funcției.

Funcția scanf () returnează numărul de valori citite corect. În cazul unei erori, citirea se întrerupe în poziția în care a fost întâlnită eroarea.

Specificatorii de format au următoarea sintaxă:

% [*] [lg] [l|L] <litera_tip>

unde:

– % – indică începutul unui specificator de format;

– <litera_tip> – specifică tipul valorii care se citește.

Litera ce indică tipul valorii citite poate fi:

Opțional, unele litere pot fi precedate de litera l sau de litera L.

Literele d, o și x pot fi precedate de litera l, caz în care valoarea citită va fi convertită la tipul long int. Litera u poate fi precedată de litera L, caz în care valoarea citită va fi convertită la tipul unsigned long int.

Literele f, e, g pot fi precedate de litera l, caz în care valoarea citită este convertită la tipul double. Literele f, e, g pot fi precedate de litera L, caz în care valoarea citită este convertită la tipul long double.

Parametrul opțional lg din specificatorul de format reprezintă numărul maxim de caractere ce vor fi citite până la întâlnirea unui caracter alb sau a unui caracter neconvertibil în tipul specificat de <litera_tip>.

Caracterul opțional * specifică faptul că la intrare este prezentă o dată de tipul specificat de specificatorul de format, dar ea nu va fi atribuită nici uneia dintre variabilele specificate în lista de parametri ai funcției scanf ().

De exemplu:

1. Fie declarațiile de variabile:

int a; unsigned long b; double c;

iar de la tastatură se introduc valorile: 650 -4.8 123000. Apelând funcția:

scanf (“%d %lf %lu”, &a, &c, &b);

variabilei a i se atribuie valoarea 650, variabilei c i se atribuie valoarea -4,8 si variabilei b i se atribuie valoarea 123000.

2. Fie declarația de variabile:

char a1, a2, a3;

iar de la tastatură se introduc caracterele: 245. Apelând funcția:

scanf (“%c %c %c”, &a1, &a2, &a3);

variabilei a1 i se atribuie caracterul ‘2’, variabilei a2 i se atribuie caracterul ‘4’ si variabilei a3 i se atribuie caracterul ‘5’.

3. Fie declarația de variabile:

int a, b, c;

iar de la tastatura se introduce: 5=2+3 . Apelând funcția:

scanf (“%d=%d+%d”, &a, &b, &c);

variabilei a i se atribuie valoarea 5, variabilei b i se atribuie valoarea 2 și variabilei c i se atribuie valoarea 3.

Apelând funcția:

scanf (“%d=%*d+%d”, &a, &b);

variabilei a i se atribuie valoarea 5, variabilei b i se atribuie valoarea 3. Valoarea 2 a fost citită, dar nu a fost atribuită nici unei variabile.

4. Fie declarația de variabile:

int zi, luna, an;

iar de la tastatură se introduce o dată în formatul zz-ll-aa. La apelul funcției:

scanf (“%d-%d-%d”, &zi, &luna, &an);

se citește ziua, luna și anul.

Pentru citirea caracterelor au fost elaborate unele funcții speciale cum ar fi funcțiile: getch () și getche ().

Funcția getch () este declarată în fișierul antet conio.h. Prototipul funcției este:

int getch (void);

La apelul funcției se citește de la tastatură un caracter. Funcția returnează codul ASCII al caracterului citit. Caracterul tastat nu este afișat pe ecran.

Funcția getche () este declarată în fisierul antet conio.h. Prototipul funcției este:

int getche (void);

La apelul funcției se citește de la tastatură un caracter. Funcția returnează codul ASCII al caracterului citit. Caracterul tastat este afișat pe ecran.

Afișarea datelor cu format specificat

Afișarea datelor cu format specificat se face cu funcția printf (), care este declarată în fișierul antet stdio.h. Prototipul funcției este:

int printf (<format>, <expresie1>, <expresie2>, …<expresie n>

unde:

– <format> – este un șir de caractere care poate conține specificatori de format și, eventual, alte caractere; în parametrul <format> trebuie să existe câte un specificator de format pentru fiecare expresie din lista de parametrii (specificatorul 1 corespunde la <expresie1>, specificatorul 2 corespunde la <expresie2> etc.); celelalte caractere din parametrul <format> vor fi afișate pe ecran în pozițiile corespunzătoare.

– <expresie 1>, <expresie 2>, …<expresie n> – reprezintă expresii sau variabile care sunt afișate pe ecran în formatul corespunzător specificatorilor de format aleși.

Efectul apelului funcției printf () este următorul: se evaluează expresiile (sau variabilele) și se scriu în ordine valorile acestora, în forma specificată de parametrul <format>.

Funcția printf () returnează numărul de caractere afișate.

Specificatorii de format au următoarea sintaxă:

% [ind] [lg] [.prec] [l|L] <litera_tip>

unde:

– % – indică începutul unui specificator de format;

– <litera_tip> – specifică tipul expresiei (variabilei) corespunzătoare care se afișează.

Litera ce indică tipul valorii afișată poate fi:

Opțional, unele litere_tip pot fi precedate de litera l sau de litera L.

Literele d, o, x, X sau u pot fi precedate de litera l, caz în care valoarea afișată va fi convertită la tipul long int (respectiv unsigned long int).

Dacă litera l apare înainte de f, e, E sau g, conversia se realizează în tipul double. Literele f, e, E sau g pot fi precedate de litera L, caz în care valoarea afișată va fi convertită la tipul long double.

Parametrul opțional lg din specificatorul de format reprezintă numărul minim de caractere ce vor fi afișate.

Dacă numărul de caractere necesare pentru a afișa valoarea expresiei depășește lg, se va realiza trunchierea valorii din expresie.

Dacă numărul de caractere necesare pentru a afișa valoarea expresiei este mai mic decât lg, se vor afișa cele lg caractere (restul zonei completându-se cu spații).

Modul de aliniere implicit este la dreapta (completarea cu spații se realizează la stânga). Dacă se specifică indicatorul (parametrul ind) minus (-) atunci alinierea se va face la stânga (și completarea cu spații se va realiza la dreapta).

Parametru .prec stabilește precizia pentru expresiile de tip real. Acesta indică numărul de cifre de la partea zecimală care vor fi afișate (implicit 6 zecimale). Dacă precizia este mai mică decât numărul de zecimale al numărului real afișat, atunci valoarea afișată va fi rotunjită.

Pentru șiruri de caractere, precizia indică numărul de caractere din șir care se afișează (primele prec).

De exemplu:

1. Citirea și afișarea datei calendaristice în formatul zz-ll-aa se poate face cu următoarea secvență de program (numit citeste_data_ca.c):

#include <stdio.h>

void main ()

{ int zi, luna, an;

printf ("Introduceti o data calendaristica in formatul zz-ll-aa\n");

scanf ("%d-%d-%d", &zi, &luna, &an);

printf ("Data introdusa de dvs.\n%d-%d-%d", zi, luna, an);

}

2. Citirea unui caracter de la tastatură care nu este afișat atunci când este tastat și afișarea la ecran a caracterului introdus se poate face cu următoarea secvență de program (numit citeste_cadASCII.c):

#include <stdio.h>

#include <conio.h>

void main ()

{ int caracter;

printf ("Introduceti un caracter:");

caracter=getch ();

printf ("\nCaracterul introdus de dvs. este: %c", caracter);

}

3. Citirea unui caracter de la tastatură care este afișat atunci când este tastat și afișarea la ecran a caracterului introdus se poate face cu următoarea secvență de program (numit citeste_codASCII.c):

#include <stdio.h>

#include <conio.h>

void main ()

{ int caracter;

printf ("Introduceti un caracter:");

caracter=getche ();

printf ("\n\nAti tastat caracterul: %c care are codul ASCII %d", caracter, caracter);

}

4. Fie declarația de variabilă:

float x=-243.192;

la apelul funcției:

printf (“x=%f sau\n x=%e sau\n x=%g\n”, x, x, x);

se afișează următoarele:

x=-243.192 sau

x=-2.43192e+02 sau

x=-243.192

5. Fie declarația de variabilă:

int d=0xf01a;

la apelul funcției:

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

se afișează următoarele:

d=-4070 sau d=f01a

6. Fie declarația de variabilă:

float x=-238.147

la apelul funcției:

printf (“x=%.2f \n”, x);

se afișează următoarele:

X=-238.15

Citirea datelor de la tastatură și afișarea datelor pe ecran în limbajul C++

Conceptul fundamental în operațiile de intrare/ieșire în limbajul C++ este fluxul de intrare/ieșire (stream). Dacă stream-ul este de intrare, succesiunea de caractere “curge” dinspre exterior (în acest caz, de la tastatură) către memoria calculatorului. Dacă stream-ul este de ieșire, secvența de caractere “curge” dinspre memoria calculatorului către exterior (în acest caz, către ecran). În limbajul C++, fișierul antet (header) iostream.h definește biblioteca de clase pentru fluxurile de intrare/ieșire pentru ecran și tastatură.

În fișierul antet iostream.h sunt declarate două fluxuri standard:

– un flux de intrare de la tastatură, denumit cin (console input);

– un flux de ieșire către ecran, denumit cout (console output).

Observație: De fapt, cin și cout sunt instanțe ale unei clase I/O din biblioteca de clase C++.

Fluxurile de intrare/ieșire cin și cout sunt utilizate în programe pentru a executa operații de I/O cu fișierele standard (indicator-fișier) stdin (tastatură) și stdout (ecranul sau consola). Când se efectuează operații cu fluxurile I/O cin și cout, complilatorul furnizează informații despre fiecare tip de dată, astfel că specificatorii de format nu mai sunt obligatorii ca în cazul funcțiilor limbajului C, scanf și printf.

Citirea datelor de la tastatură folosind fluxul cin

Fluxul I/O cin este folosit pentru a primi date de la stdin (tastatură).

Datele citite de la tastatură, vor fi extrase din fluxul de intrare, folosind operatorul (de clasă) de intrare (citire) ‘>>’ și sintaxa:

cin >> <nume_variabila>;

Efect: se va citi de la tastatură o succesiune de caractere, care va fi convertită într-o valoare de tipul variabilei <nume_variabila> și apoi atribuită variabilei <nume_variabila>.

Pentru citirea datelor ce vor fi stocate succesiv în mai multe variabile se folosește sintaxa:

cin >> <nume_v1> >> <nume_v2> >> … >> <nume_vn>;

Cum selectează cin câmpurile de date?

1. cin utilizează caractere albe (spațiu, tab sau linie nouă) pentru a delimita câmpurile de date

2. Caracterele albe sunt ignorate de cin.

De exemplu (programele citire_cin.cpp și citire_cin1.cpp) pentru citirea a trei numere întregi:

Varianta 1:

# include <iostream.h>

# include <stdio.h>

void main()

{int x, y, z;

printf ("introduceti numerele:\n");

cin >> x;

cin >> y;

cin >> z;

printf ("Numerele introduse de dvs. sunt: %d %d %d\n", x, y, z);

}

Varianta 2:

# include <iostream.h>

# include <stdio.h>

void main()

{int x, y, z;

printf ("introduceti numerele:\n");

cin >> x >> y >> z;

printf ("Numerele introduse de dvs. sunt: %d %d %d\n", x, y, z);

}

Afișarea datelor pe ecran folosind fluxul cout

Fluxul I/O cout este folosit pentru a trimite date către stdout (ecranul sau consola calculatorului).

Pentru scrierea datelor pe ecran se folosește operatorul (de clasa) de ieșire (scriere) ‘<<’. Acest operator specifică fluxul la care programul transmite datele.

Sintaxa folosită este:

cout << <expresie>; unde:

– <expresie> – specifică un nume de variabilă sau o expresie.

Efect: Se evaluează <expresie> și apoi valoarea ei este convertită într-o succesiune de caractere care va fi afișată pe ecran.

Când se dorește scrierea la ecran a mai multor date de același tip sau de tipuri diferite într-o singură instrucțiune se folosește sintaxa:

cout << <expresie1> << <expresie2> << … << <expresie n>;

Notă: Secvența escape \n (newline) este folosită pentru trecerea pe o linie nouă. Trecerea la linie nouă se poate face și folosind simbolul endl (endline).

Fluxul de I/O cout permite programelor, în mod implicit, afișarea valorilor numerice în baza 10.

De exemplu, programele prezentate la fluxul de intrare cin (în care s-a folosit funcția printf () pentru afișarea datelor) pot fi modificate pentru a folosi fluxul de ieșire cout (programele scriere_cout.cpp și scriere_cout1.cpp) astfel:

Varianta 1:

# include <iostream.h>

void main()

{int x, y, z;

cout<< "introduceti numerele:" << '\n';

cin >> x;

cin >> y;

cin >> z;

cout << "Numerele introduse de dvs. sunt: ";

cout << x;

cout << ‘ ‘;

cout << y;

cout << ‘ ‘;

cout << z;

}

Varianta 2:

# include <iostream.h

void main()

{int x, y, z;

cout << "introduceti numerele: " << endl;

cin >> x >> y >> z;

cout << "Numerele introduse de dvs. sunt: " << x << ‘ ‘ << y << ‘ ‘ << z << '\n';

}

Notă: Se pot utiliza modificatorii dec, oct, hex pentru a afișa valorile în zecimal, octal și hexazecimal. Iată un exemplu de program care face conversii (cu numerele scriere_cout2.cpp) care face conversii ale unor numere în octal și hexazecimal:

# include <iostream.h>

void main()

{int x, y, z;

cout << "introduceti numerele: " << endl;

cin >> x >> y >> z;

cout << "Numerele introduse de dvs. in baza 10 sunt: " << x << ' ' << y

<< ' ' << z << '\n';

cout << "Numerele introduse de dvs. in baza 8 sunt: " << oct << x << ' '

<< oct << y << ' ' << oct << z << '\n';

cout << "Numerele introduse de dvs. in baza 16 sunt: " << hex << x << ' '

<< hex << y << ' ' << hex << z << '\n';

}

Lecția 2

Cuvinte importante:

– expresii și operatori: operatori aritmetici, operatori de incrementare/decrementare, operatori relaționali, operatori de egalitate, operatori logici globali; operatori logici pe biți; operatorul sizeof, operatori de atribuire; operatorul condițional; operatorul de referențiere (adresa); operatorul virgula; evaluarea expresiilor;

– instrucțiuni simple și instrucțiuni compuse;

– structuri fundamentale de control – structuri alternative (de decizie): instrucțiunea if; instrucțiunea switch; instrucțiunea break;

– structuri fundamentale de control – structuri repetitive: instrucțiunea while; instrucțiunea do-while.

2.1. Expresii și operatori

O expresie este compusă dintr-o succesiune de operanzi, legați prin operatori.

Un operand poate fi o constantă, o variabilă, un apel de funcție, o expresie încadrată între paranteze rotunde.

Operatorii desemnează operațiile care se execută asupra operanzilor și pot fi grupați pe categorii, în funcție de tipul operațiilor realizate.

Din punct de vedere al priorității, operatorii pot fi grupați în 16 clase de prioritate numerotate de la 1 la 16, 1 fiind prioritatea maximă.

Operatorii limbajului C/C++ sunt unari (se aplică unui singur operand) sau sunt binari (se aplică asupra a doi operanzi). Toți operatorii unari au clasa de prioritate 2.

A. Operatorii aritmetici

Operatorii aritmetici sunt: ‘*’ – înmulțirea; ‘/’ – împărțirea; ‘%’ – restul împărțirii întregi; ‘+’ – adunarea; ‘-’ – scăderea.

Înmulțirea, împărțirea și restul împărțirii întregi au clasa de prioritate 4. Adunarea și scăderea au clasa de prioritate 5.

Notă:

1. Operatorul ‘%’ nu poate fi aplicat decât operanzilor întregi.

2. Operatorul ‘/’ poate fi aplicat atât operanzilor întregi, cât și operanzilor reali, dar funcționează diferit pentru operanzii întregi, față de operanzii reali. Dacă cei doi operanzi sunt numere întregi, operatorul ‘/’ are ca rezultat câtul împărțirii întregi (fără parte fracționară). Dacă cel puțin unul dintre cei doi operanzi este un număr real, operandul ‘/’ furnizează rezultatul împărțirii reale (cu parte fracționară).

De exemplu:

1. Fie declarațiile de variabile:

int a=5, b=7

float x=3.5

2. Programul următor (denumit math.c) ilustrează utilizarea operatorilor aritmetici de bază din C/C++:

#include <stdio.h>

void main()

{

int secunde_pe_ora;

float media;

secunde_pe_ora = 60 * 60;

media = (5 + 10 + 15 + 20) / 4;

printf("Numarul de secunde intr-o ora este %d\n", secunde_pe_ora);

printf("Media numerelor 5, 10, 15 si 20 este %3.2f\n", media);

printf("Numarul de secunde in 48 de minute este %d\n", secunde_pe_ora – 12

* 60);

}

B. Operatorii de incrementare/decrementare

Operatorul de incrementare este ‘++’. Operatorul de decrementare este ‘–’. Acești operatori sunt unari și au ca efect mărirea (respectiv micșorarea) valorii operandului cu 1. Limbajul C/C++ permite două forme pentru operatorii de incrementare / decrementare: forma prefixată (înaintea operandului) și forma postfixată (după operand). Grupa de prioritate a operatorilor de incrementare/decrementare este 2.

Instrucțiunile care urmează incrementează variabila total cu 1:

total++;

++total;

Instrucțiunile care urmează decrementează variabila total cu 1:

total–;

–total;

În cazul când se folosește operatorul de incrementare / decrementare în forma prefixată (înaintea operandului), limbajul C/C++ va incrementa / decrementa mai întâi valoarea variabilei și apoi va utiliza variabila.

În cazul când se folosește operatorul de incrementare / decrementare în forma postfixată (după operand), limbajul C/C++ va utiliza mai întâi valoarea variabilei și apoi va efectua operația de incrementare /decrementare.

De exemplu, programul următor (Prepost.c) evidențiază forma prefixată și postfixată a operatorilor de incrementare / decrementare:

#include <stdio.h>

void main()

{

int value = 1;

printf("Utilizare postfix %d\n", value++);

printf("Valoarea dupa incrementare %d\n", value);

value = 1;

printf("Utilizare prefix %d\n", ++value);

printf("Valoarea dupa incrementare %d\n", value);

}

În urma execuției programului va fi afișată următoarea ieșire:

Utilizare postfix 1

Valoarea dupa incrementare 2

Utilizare prefix 2

Valoarea dupa incrementare 2

C. Operatori relaționali

Operatorii relaționali sunt operatori binari și desemnează relația de ordine în care se găsesc cei doi operanzi: <, >, <=, >=.

Rezultatul aplicării unui operator relațional este 1 dacă cei doi operanzi sunt în relația indicată de operator, și 0, altfel.

Grupa de prioritate a operatorilor relaționali este 7.

Observație: Când se lucrează cu structuri de control condiționale și iterative limbajul C/C++ interpretează orice valoare diferită de 0 ca adevăr, iar valoarea 0 ca fals.

De exemplu, expresiile logice:

4 > 6 are ca rezultat valoarea 0 (fals),

8 <= 3+13 are ca rezultat valoarea 1 (adevăr).

D. Operatori de egalitate

Operatorii de egalitate sunt folosiți pentru testarea unei egalități sau inegalități. Sunt operatori binari și arată relația de egalitate (==) sau inegalitate (!=).

Rezultatul aplicării unui operator de egalitate este 1, dacă cei doi operanzi sunt în relația indicată de operator și 0 altfel.

Grupa de prioritate a operatorilor de egalitate este 8.

De exemplu, expresiile logice:

5 == 2+3 are ca rezultat valoarea 1,

5 != 2+3 are ca rezultat valoarea 0.

E. Operatori logici globali

Există trei operatori logici globali:

– negația logică (not) reprezentată cu semnul !, grupa de prioritate este 2;

– conjuncție logică (și) reprezentată cu semnul &&, grupa de prioritate este 12;

– disjuncție logică (sau) reprezentată cu semnul | |, grupa de prioritate este 13.

În limbajul C/C++, valoarea logică fals este asociată cu valoarea 0, și valoarea logică adevăr este asociată cu o valoare diferită de 0(0) și de aceea rezultatul aplicării operatorilor logici globali este:

De exemplu, expersiile logice:

(z >= c) && (z <= d) are ca rezultat valoarea 1 dacă z este în intervalul [c,d] și valoarea 0 dacă z este în afara intervalului [c,d];

(z < c) | | (z > d) are ca rezultat valoarea 1 dacă z este în afara intervalului [c,d] și valoarea 0 dacă z este în intervalul [c,d].

! (z%2) are ca rezultat 1 dacă z este un număr par și 0 dacă z este un număr impar

F. Operatori logici pe biți

Operatorii logici pe biți se aplică numai operanzilor întregi și au același rezultat ca și operațiile logice studiate (negative, conjuncție, disjuncție exclusivă) dar bit cu bit.

Operatorii logici pe biți sunt:

Rezultatul aplicării operatorilor de complementare pe biți, de disjuncție logică pe biți și de conjuncție logică pe biți este același cu cel prezentat la operatorii logici globali.

Rezultatul aplicării operatorului de disjuncție exclusivă pe biți este:

Operațiile care se efectuează pe biți (deci acționează direct asupra reprezentării interne a operanzilor) sunt foarte performante pentru că se execută foarte rapid.

Exemplu de folosire a operatorului de disjuncție logică (sau) pe biți:

3 00000011

4 00000100

––––

7 00000111

De exemplu, programul de mai jos (denumit bit_sau.cpp), ilustrează modul de utilizare a operatorului de disjuncție logică pe biți:

#include <stdio.h>

void main()

{

printf("0 | 0 este %d\n", 0 | 0);

printf("0 | 1 este %d\n", 0 | 1);

printf("1 | 1 este %d\n", 1 | 1);

printf("1 | 2 este %d\n", 1 | 2);

printf("128 | 127 este %d\n", 128 | 127);

}

În urma execuției programului va fi afișată următoarea ieșire:

0 | 0 este 0

0 | 1 este 1

1 | 1 este 1

1 | 2 este 3

128 | 127 este 255

Exemplu de folosire a operatorului de conjuncție logică (și) pe biți:

5 00000101

7 00000111

––––

5 00000101

De exemplu, programul de mai jos (bit_și.cpp), ilustrează modul de utilizare a operatorului de conjuncție logică pe biți:

#include <stdio.h>

void main()

{

printf("0 & 0 este %d\n", 0 & 0);

printf("0 & 1 este %d\n", 0 & 1);

printf("1 & 1 este %d\n", 1 & 1);

printf("1 & 2 este %d\n", 1 & 2);

printf("15 & 127 este %d\n", 15 & 127);

}

În urma execuției programului va fi afișată următoarea ieșire:

0 & 0 este 0

0 & 1 este 0

1 & 1 este 1

1 & 2 este 0

15 & 127 este 15

Exemplu de folosire a operatorului de disjuncție exclusivă (sau exclusiv) pe biți:

5 00000101

7 00000111

––––

2 00000010

De exemplu, programul de mai jos (denumit bit_xsau.cpp) ilustrează modul de utilizare a operatorului de disjuncție exclusivă pe biți:

#include <stdio.h>

void main()

{

printf("0 ^ 0 este %d\n", 0 ^ 0);

printf("0 ^ 1 este %d\n", 0 ^ 1);

printf("1 ^ 1 este %d\n", 1 ^ 1);

printf("1 ^ 2 este %d\n", 1 ^ 2);

printf("15 ^ 127 este %d\n", 15 ^ 127);

}

În urma execuției programului va fi afișată următoarea ieșire:

0 ^ 0 este 0

0 ^ 1 este 1

1 ^ 1 este 0

1 ^ 2 este 3

15 ^ 127 este 112

Exemplu de folosire a operatorului de complementare pe biți:

15 00001111

––––

240 11110000

De exemplu, programul de mai jos (denumit bit_inv.cpp), ilustrează modul de utilizare a operatorului de complementare pe biți:

#include <stdio.h>

void main()

{

int val;

val= 0xFF;

printf("%x complementat este %x\n", val, ~val);

}

În urma execuției programului va fi afișată următoarea ieșire:

FF complementat este FFFFFF00

Observație: Deoarece compilatorul Borlad C++ 5.0 utilizat (în acest curs) este pe 32 bits, variabilele de tip întreg sunt reprezentate în memorie pe 32 bits și de aceea, în exemplul prezentat FF complementat este FFFFFF00.

Operatorii de deplasare pe biți au ca efect deplasarea reprezentării interne binare a primului operand spre stânga (<<) sau spre dreapta (>>). Numărul de poziții care se deplasează este specificat de cel de-al doilea operand.

La deplasarea la stânga (<<), pozițiile rămase libere în dreapta se completează cu 0.

La deplasarea la dreapta (>>), pozițiile rămase libere în stânga se completează cu 0, bitul de semn rămânând neschimbat.

Observații:

1. În funcție de compilatorul utilizat (16 sau 32 biți), de sistemul hardware și de tipul de dată al variabilei, valorile rezultate prin deplasările la stânga și la dreapta pe biți pot fi diferite.

2. Expresia x << n are ca efect înmulțirea operandului x cu 2n. Expresia x >> n are ca efect împărțirea întreagă a operandului x cu 2n.

De exemplu, următoarea instrucțiune folosește operatorul de deplasare la stânga pentru a deplasa valorile variabilei var_deplas două poziții spre stânga:

var_deplas=var_deplas << 2

Să presupunem că în reprezentarea internă binară, valoarea 2 (în zecimal), stocată în variabila var_deplas este:

0000 0010

La deplasarea valorii două locuri spre stânga, rezultatul va fi 8 (în zecimal), așa cum este ilustrat mai jos:

0000 1000

De exemplu, programul de mai jos (depls.cpp), ilustrează modul de utilizare a operatorilor de deplasare pe biți:

#include <stdio.h>

void main()

{

unsigned int u_val = 1;

int value = -8;

printf("%u (unsigned) deplasat la stanga de doua ori este %u\n", u_val,u_val << 2);

printf("%u (unsigned) deplasat la dreapta de doua ori este %u\n", u_val,u_val >> 2);

u_val = 65535;

printf("%u (unsigned) deplasat la stanga de doua ori este %u\n", u_val,u_val << 2);

printf("%u (unsigned) deplasat la dreapta de doua ori este %u\n", u_val,u_val >> 2);

printf("%d deplasat la stanga de doua ori este %d\n", value, value << 2);

printf("%d deplasat la dreapta de doua ori este %d\n", value, value >> 2);

}

După execuția programului, cu compilatorul Borland C++ pe 32 de biți, va fi afișată următoarea ieșire:

1 (unsigned) deplasat la stanga de doua ori este 4

1 (unsigned) deplasat la dreapta de doua ori este 0

65535 (unsigned) deplasat la stanga de doua ori este 262140

65535 (unsigned) deplasat la dreapta de doua ori este 16383

-8 deplasat la stanga de doua ori este -32

-8 deplasat la dreapta de doua ori este -2

G. Operatorul sizeof de determinare a dimensiunii

Este un operator unar care determină dimensiunea, exprimată în număr de bytes, a zonei de memorie alocată de compilator pentru o variabilă (expresie) sau pentru un tip de dată:

sizeof (<expresie>) sau sizeof(<tip_de_data>).

Grupa de prioritate a operatorului sizeof este 2.

De exemplu, următorul program (sizeof.c), exemplifică folosirea operatorului sizeof:

#include <stdio.h>

void main()

{

long int valoare = 56;

printf("Variabila de tip int foloseste %d bytes\n", sizeof(int));

printf("Variabila de tip char foloseste %d bytes\n", sizeof(char));

printf("Variabila de tip float foloseste %d bytes\n", sizeof(float));

printf("Variabila de tip double foloseste %d bytes\n", sizeof(double));

printf("Variabila de tip unsigned int foloseste %d bytes\n",

sizeof(unsigned));

printf("Variabila de tip long int foloseste %d bytes\n", sizeof(long));

printf("Variabila de tip unsigned long int foloseste %d bytes\n",

sizeof(unsigned long));

printf("Variabila de tip unsigned char foloseste %d bytes\n",

sizeof(unsigned char));

printf("Variabila de tip long double foloseste %d bytes\n",

sizeof(long double));

printf ("Dimensiunea in bytes a valorii de tip long int %d alocata \

de compilator este %d\n", valoare, sizeof(valoare));

}

Observație: În funcție de compilatorul utilizat și de sistemul hardware, ieșirea rezultată prin aplicarea operatorului sizeof poate fi diferită

După execuția programului, cu compilatorul Borland C++ 5.0 pe 32 de biți, va fi afișată următoarea ieșire:

Variabila de tip int folosește 4 bytes

Variabila de tip char foloseste 1 bytes

Variabila de tip float foloseste 4 bytes

Variabila de tip double foloseste 8 bytes

Variabila de tip unsigned int foloseste 4 bytes

Variabila de tip long int foloseste 4 bytes

Variabila de tip unsigned long int foloseste 4 bytes

Variabila de tip unsigned char foloseste 1 bytes

Variabila de tip long double foloseste 10 bytes

Dimensiunea în bytes a valorii, de tip long int 56 alocată de compilator este 4

H. Operatori de atribuire

Operatorii de atribuire sunt operatori binari care permit modificarea valorii unei variabile.

Există un operator de atribuire simplu (=) și 10 operatori de atribuire compuși cu ajutorul operatorului ‘=‘ și al unui alt operator binar (aritmetic sau logic pe biți).

Grupa de prioritate a operatorilor de atribuire este 15.

O variantă de sintaxă folosită este:

<nume_variabila> = <expresie>

Efectul aplicării operatorului este: Se evaluează <expresie>, apoi se atribuie variabilei <nume_variabila> valoarea expresiei.

Notă: <expresie> poate fi la rândul ei o expresie de atribuire, caz în care se realizează o atribuire multiplă.

<nume_variabila1> = <nume_variabila2> = … = <nume_variabilan> = <expresie>;

Se folosește atunci când se dorește să se atribuie aceeași valoare mai multor variabile.

De exemplu:

total = 0;

suma = 0;

valoare = 0;

folosind atribuirea multiplă rezultă:

total = suma = valoare = 0;

Atunci când compilatorul întâlnește o operație de atribuire multiplă, el atribuie valorile de la dreapta la stânga.

De regulă, atribuirile multiple se folosesc numai pentru a inițializa variabile.

A doua variantă de sintaxă folosită este:

<nume_variabila> <operator_binar> = <expresie>;

unde:

– <operator_binar> – operator din mulțimea {*, /, %, +, -, <<, >>, &, |, ^}.

Efectul aplicării operatorilor de atribuire compuși este echivalent cu instrucțiunea:

<nume_variabila> = <nume_variabila> <operator_binar> <expresie>;

De exemplu instrucțiunile:

total = total + 100;

suma = suma – 5;

jumătate = jumătate/2;

b = a = a*2;

sunt echivalente cu:

total+ =100;

suma- = 5;

jumătate/ =2;

b = a* = 2;

I. Operatorul condițional ?:

Operatorul condițional examinează o condiție și returnează o valoare dacă este adevărată și alta dacă este falsă. Grupa de prioritate este 14.

Sintaxa operatorului condițional este:

(<conditie>) ? <rezultat_adevar> : <rezultat_fals>

unde:

– <conditie> – o expresie de evaluat ;

– <rezultat_adevar> – rezultatul returnat dacă condiția este adevărată (are o valoare nenulă);

– <rezultat_fals> – rezultatul returnat dacă condiția este falsă (are o valoarea 0).

De exemplu:

calificativ = (punctaj >= 60) ? ‘A’ : ‘R’;

Dacă valoarea din variabila punctaj este mai mare sau egală cu 60, instrucțiunea atribuie variabilei calificativ valoarea A. Dacă valoarea din variabila punctaj este mai mică decât 60, instrucțiunea atribuie variabilei calificativ valoarea R.

Instrucțiunea este similară unei instrucțiuni if-else.

J. Operatorul de referențiere (adresa)

Este un operator unar care permite determinarea adresei zonei de memorie în care este stocată o variabilă. Grupa de prioritate este 9.

Sintaxa folosită pentru acest operator este:

&<nume_variabila>

K. Operatorul virgulă

Operatorul virgulă (,) permite compunerea mai multor expresii, astfel încât să fie tratate din punct de vedere sintactic ca o singură expresie. Prioritatea operatorului virgulă este 16.

Sintaxa folosită este:

<expresie_1>, <expresie_2>, … ,<expresie_n>

Efect: Se evaluează în ordine de la stânga la dreapta cele n expresii, valoarea întregii expresii fiind egală cu valoarea <expresie_n>.

Utilizarea expresiilor compuse cu operatorul virgulă este necesară atunci când sintaxa permite evaluarea unei singure expresii (de exemplu, în instrucțiunea for), dar este necesar să fie evaluate mai multe expresii.

De exemplu, expresia compusă de mai jos:

int i,a,b;

i=0, b=i+2, a=b*2

are ca efect: variabila i primește valoarea 0, variabila b primește valoarea 2, apoi variabila a primește valoarea 4; valoarea întregii expresii compuse este 4.

Evaluarea expresiilor

Evaluarea unei expresii presupune calculul valorii expresiei, prin înlocuirea în expresie a fiecărei variabile cu valoarea ei și a fiecărei funcții cu valoarea returnată de funcția respectivă și efectuarea operațiilor specificate de operatori. În timpul evaluării expresiei se ține cont de existența parantezelor, de asociativitate și de prioritatea operatorilor astfel:

– se evaluează în primul rând expresiile din paranteze, începând cu parantezele cele mai interioare;

– în cadrul unei expresii fără paranteze, se efectuează operațiile în ordinea priorității operatorilor;

– dacă într-o expresie apare o succesiune de operatori cu priorități egale, se ține cont de asociativitatea operatorilor. În limbajul C/C++, operatorii se asociază de la stânga la dreapta, cu excepția operatorilor unari, condiționali și de atribuire, care se asociază de la dreapta la stânga.

Conversii implicite

Dacă toți operanzii care intervin într-o expresie au același tip, tipul expresiei coincide cu tipul operanzilor. În cazul în care operanzii nu au același tip, pe parcursul evaluării expresiei se realizează automat o serie de conversii implicite. Regula conversiilor implicite este: operandul care are un domeniu de valori mai restrâns este convertit la tipul operandului care are mulțimea valorilor mai amplă.

2.2 Instrucțiuni simple și instrucțiuni compuse

O instrucțiune simplă este o singură instrucțiune, cum ar fi aceea prin care se atribuie o valoare unei variabile sau se apelează funcția prinf.

O instrucțiune compusă este folosită în cadrul procesărilor iterative (while sau do-while) sau condiționale (if-else).

O instrucțiune compusă este alcătuită din două sau mai multe instrucțiuni incluse între acolade.

Sintaxa unei instrucțiuni compuse este:

{

<declaratii_de_variabile_locale>;

<instructiune_1>;

<instructiune_2>;

<instructiune_n>;

}

Notă: Declarațiile de variabile care apar într-o instrucțiune compusă sunt locale instrucțiunii; ele sunt valabile numai în corpul instrucțiunii compuse, din momentul declarării lor până la sfârșitul instrucțiunii.

2.3 Structuri fundamentale de control. Structuri alternative (de decizie)

Instrucțiunea if

Sintaxa instrucțiunii este:

if (<expresie>) <instructiune_1>;

[else <instructiune_2>];

unde:

– <expresie> – specifică expresia de evaluat;

– <instructiune_1>, <instructiune_2> – specifică instrucțiunile (simple sau compuse) de executat.

Semantica: se evaluează <expresie> și dacă valoarea expresiei este diferită de 0, se execută <instructiune_1>, altfel se execută <instructiune_2>.

Exemple:

1. Secvența de program (denumit if_simplu.cpp) folosește instrucțiunea if cu o singură ramură.

#include <stdio.h>

void main()

{

int varsta = 21;

int inaltime = 73;

if (varsta == 21)

printf("Varsta utilizatorului este 21\n");

if (varsta != 21)

printf("Varsta utilizatorului nu este 21\n");

if (inaltime == 73)

printf("Inaltimea utilizatorului este 73\n");

if (inaltime != 73)

printf("Inaltimea utilizatorului nu este 73\n");

}

2. Următorul program (denumit if_2ramuri.cpp) testează dacă un număr este par sau impar (restul împărțirii numărului la 2 este 0):

#include <iostream.h>

#include <stdio.h>

int main()

{

int x;

cin >> x;

if (x%2) printf("%d este impar\n", x);

else printf ("%d este par\n", x);

return 0;}

3. Programul următor (denumit if_compus.cpp) numără numerele pare dintr-un interval [a, b] și il afișează. Programul folosește o instrucțiune compusă în cadrul structurii alternative if.

#include <iostream.h>

#include <stdio.h>

int main()

{

int a, b, nr;

printf("Introduceti intervalul de numere:");

cin >> a >> b;

if (a<=b)

{

nr=(b-a+1)/2;

if (a%2==0 && b%2==0) nr++;

printf("Numarul de numere pare din intervalul [%d, %d] este %d\n", a, b, nr);

}

else printf ("Interval de numere introdus eronat\n");

return 0;}

Instrucțiunea switch

Sintaxa instrucțiunii este:

switch (<expresie>)

{ case <constanta_1> : <grup_de_instructiuni_1>;

case <constanta_2> : <grup_de_instructiuni_2>;

case <constanta_n> : <grup_de_instructiuni_n>;

[default: <grup_de_instructiuni_n+1>;]

}

unde:

– <expresie> – specifică variabila sau expresia de evaluat;

– <constanta_1>, <constanta_2>, …, <constanta_n> – specifică valorile constantelor cu care se

face compararea rezultatului evaluării expresiei;

– <grup_de_instructiuni_1>, … – o instrucțiune sau un grup de instrucțiuni care se execută în cazul în care o alternativă case se potrivește.

Semantica: se evaluează <expresie>; se compară succesiv valoarea expresiei cu valorile constantelor <constanta_1>, <constanta_2>, …, <constanta_n> din alternativele case:

– dacă se întâlnește o constantă din alternativa case cu valoarea expresiei, se execută secvența de instrucțiuni corespunzătoare și toate secvențele de instrucțiuni care urmează, până la întâlnirea instrucțiunii break sau până la întâlnirea acoladei închise (}) care marchează sfârșitul instrucțiunii switch;

– dacă nici una dintre valorile constantelor din alternativa case nu coincide cu valoarea expresiei, se execută secvența de instrucțiuni din alternativa default (alternativa implicită sau prestabilită).

Observații:

1. Spre deosebire de if-else, care permite selectarea unei alternative din maximum două posibile, switch permite selectarea unei alternative din maximum n+1 posibile.

2. În instrucțiunea if-else se execută instrucțiunea (instrucțiunile) corespunzătoare valorii expresiei și atât, în timp ce în instrucțiunea switch se execută și toate secvențele de instrucțiuni ale alternativelor case următoare.

Instrucțiunea break din switch

Sintaxa instrucțiunii este:

break;

Semantica: determină ieșirea necondiționată din instrucțiunea switch, adică oprește execuția secvențelor de instrucțiuni ale alternativelor case următoare.

Exemplu:

Programul următor (denumit switch_compus.cpp) utilizează instrucțiunea switch pentru a executa anumite calcule (verificarea dacă un număr este par, calculul numerelor pare dintr-un interval, ieșirea din program) prin selecția de către utilizator a unor opțiuni dintr-un meniu. A se observa și folosirea instrucțiunii break.

#include <iostream.h>

#include <stdio.h>

void main ()

{

char litera;

int x, a, b, nr;

cout<<"A Verifica daca un numar este par/impar"<<"\n";

cout<<"B Calcul numere pare intr-un interval [a, b]"<<"\n";

cout<<"E Iesire"<<"\n";

cout<<"Alegeti una din literele meniului:" ;

cin>>litera;

switch (litera)

{

case 'A': cout <<"Introduceti un numar: "; cin>>x;

if (x%2) printf("%d este impar\n", x);

else printf ("%d este par\n", x);

break;

case 'B' : printf("Introduceti intervalul de numere:"); cin >> a >> b;

if (a<=b)

{

nr=(b-a+1)/2;

if (a%2==0 && b%2==0) nr++;

printf("Numarul de numere pare din intervalul [%d, %d] este %d\

\n", a, b, nr);

}

else printf ("Interval de numere introdus eronat\n");

break;

case 'E' : cout<<"S-a iesit din program"<<"\n";

break;

default : cout<<"Nu s-a ales litera corespunzatoare meniului"<<"\n";

}

}

2.4 Structuri fundamentale de control. Structuri repetitive (iterative)

Instrucțiunea while

Sintaxa instrucțiunii este:

while (<expresie>)

<instructiune>;

unde:

– <expresie> – specifică expresia de testat;

– <instructiune> – specifică instrucțiunea simplă sau compusă de executat.

Semantica: se evaluează <expresie>:

– dacă valoarea expresiei este 0 (expresia testată este falsă) se iese din ciclul while;

– dacă valoarea expresiei este diferită de 0 (expresia testată este adevărată), se execută instrucțiunea atâta timp cât valoarea expresiei este nenulă.

Notă:

1. Pentru ca ciclul să nu fie infinit, este obligatoriu ca una din instrucțiunile care se execută în ciclul while să modifice cel puțin una dintre variabilele care intervin în <expresie>, astfel încât aceasta să poată lua valoarea 0 sau să conțină o operație de ieșire necondiționată din ciclu folosind instrucțiunea break.

2. Instrucțiunea while poate conține o secvență de instrucțiuni și atunci această secvență se grupează într-o singură instrucțiune compusă (încadrată între acolade).

Exemple:

1. Programul următor (denumit bucla_while.c) afișează numerele de la 1 la 100.

#include <stdio.h>

void main()

{

int contor = 1;

while (contor <= 100)

{

printf("%d \t", contor);

contor++;

}

}

2. Programul de mai jos (bucla_while1.cpp) calculează succesiv puterile unui număr, introdus de la tastatură, până la o putere oarecare, introdusă tot de la tastatură.

#include <iostream.h>

int main()

{

int n, x, y, i;

cout << "Introduceti numarul:";

cin>> x;

cout << "Introduceti puterea pana la care doriti ridicarea la putere\

a numarului: ";

cin>> n;

i=1;

y=1 ;

while (i<=n)

{

y*=x;

cout << "y=" << y << "\n";

i++;

}

return 0;}

2.5 Structuri fundamentale de control. Structuri repetitive.

Instructiunea do-while

Sintaxa instrucțiunii este:

do

<instructiune>;

while (<expresie>);

unde:

– <instructiune> – o instrucțiune simplă de executat;

– <expresie> – specifică expresia de testat (de evaluat);

Semantica: se execută instrucțiunea și apoi se evaluează expresia:

– dacă valoarea expresiei este 0 (expresia testată este falsă) se iese din ciclul do-while;

– daca valoarea expresiei este diferită de 0 (expresia testată este adevărată), se execută instrucțiunea (din ciclul do-while) atâta timp cât valoarea expresiei este nenulă.

Notă:

1. Spre deosebire de instrucțiunea while, instrucțiunea do-while execută instrucțiunea specificată în corpul ciclului cel puțin o dată, chiar dacă de la început valoarea expresiei este 0 (este falsă), deoarece evaluarea expresiei se face după execuția instrucțiunii.

2. Pentru ca ciclul să nu fie infinit, este obligatoriu ca una din instrucțiunile care se execută în ciclul do-while să modifice cel puțin una dintre variabilele care intervin în <expresie>, astfel încât aceasta să poată lua valoarea 0 sau să conțină o operație de ieșire necondiționată din ciclu folosind instrucțiunea break.

3. Instrucțiunea do-while poate conține o secvență de instrucțiuni și atunci această secvență se grupează într-o singură instrucțiune compusă (încadrată între acolade).

Instrucțiunile while și do-while sunt folosite în funcție de momentul la care dorim să testăm o condiție care determină efectuarea unor prelucrări repetate.

Când este necesar să testăm o condiție înainte de efectuarea unor prelucrări repetate atunci se folosește instrucțiunea while.

Când condiția depinde de la început de prelucrările repetate din ciclu (prin urmare, este necesar să fie testată după executarea prelucrărilor din ciclu) atunci se folosește instrucțiunea do-while.

Exemple:

1. Programul următor (bucla_do_while.cpp) numără cifrele unui număr natural stocat în variabila x:

#include <iostream.h>

int main()

{

unsigned nr =0;

long x, x_init;

cout << "Introduceti un numar natural:";

cin >> x;

x_init=x;

do

{

x/=10;

nr++;

}

while (x);

cout << "Numarul natural " << x_init << " are " << nr << " cifre.";

return 0;}

Dacă s-ar fi folosit instrucțiunea while atunci algoritmul nu ar fi funcționat și pentru numărul natural 0 (care are o cifră), deoarece testând condiția la început, nu s-ar fi executat niciodată instrucțiunile din ciclu.

Iată programul următor (bucla_while2.cpp) care rezolvă corect problema în cazul numărului 0, folosind instrucțiunea while și o instrucțiune suplimentară if:

#include <iostream.h>

int main()

{unsigned nr =0;

long x, x_init;

cout << "Introduceti un numar natural:"; cin >> x;

x_init=x;

if (x)

{while (x)

{

x/=10; nr++;

}

cout << "Numarul natural " << x_init << " are " << nr << " cifre.";}

else

cout << "Numarul natural 0 are o cifra.";

return 0;}

2. Programul următor (bucla_do_while1.cpp) ajută un copil să învețe tabla înmulțirii până la 10. Programul generează aleator două numere naturale mai mici decât 10, le va afișa pe ecran și îl va întreba pe copil cu cât este egal produsul lor. Dacă răspunsul este incorect, îl întreabă pe copil dacă dorește să mai încerce sau dacă dorește să afle răspunsul corect. Programul repetă învățarea până când copilul răspunde cu ‘N’ (nu).

Notă:

Pentru generarea aleatoare a două numere naturale mai mici decât 10 va trebui să se inițializeze generatorul de numere aleatoare, cu ajutorul funcției randomize() declarată în fișierul antet stdlib.h astfel:

void randomize(void);

ceea ce înseamnă că funcția nu are parametrii și nu returnează nici un rezultat.

După inițializarea generatorului de numere aleatoare, pentru a genera un număr aleator se va utiliza funcția random (), declarată în fișierul antet stdlib.h astfel:

int random (int x);

Funcția returnează o valoare aleatoare de tip int, cuprinsă între 0 si x-1.

#include <iostream.h>

#include <conio.h>

#include <stdlib.h>

void main()

{char litera;

int x, y, raspuns;

cout << "Sa invatam tabla inmultirii. Iti place? (D/N): ";

litera=getche(); cout<<'\n';

if (litera=='N' || litera=='n')

cout<<"\nHai totusi sa incercam macar odata.";

randomize();

do

{x=random(10);

y=random(10);

cout<<'\n'<<x<<'*'<<y<<"=?\b"; litera='D';

do

{cin>>raspuns;

if (raspuns==x*y) cout<<"\nRaspuns corect!";

else

{cout<<"\nNu este corect! Mai incercati alt raspuns? (D/N) : ";

litera=getche();

cout<<'\n'<<'\n'<<x<<'*'<<y<<"=?\b";}}

while(raspuns !=x*y && (litera=='D' || litera =='d'));

if (litera=='N' || litera=='n')

cout<<"\nRaspunsul corect era "<<x*y<<endl;

cout<<"\nContinuam cu inmultirea altor numere? (D/N) : ";

litera=getche(); cout<<'\n';}

while(litera=='D' || litera =='d');

cout<<"\nLa revedere! Dar mai incercati si altadata."<<endl;}

Lecția 3

Cuvinte importante:

– structuri fundamentale de control – structuri repetitive: instrucțiunea for;

– funcții matematice din bibliotecile standard ale limbajului C/C++;

– tablouri (variabile indexate) unidimensionale și bidimensionale: declarare, accesare, inițializare, ciclare printre elementele unui vector, ciclare printre elementele unei matrice;

– prelucrări elementare pe tablouri, exemple.

3.1 Instrucțiunea for

Este folosită pentru efectuarea unor prelucrări de un anumit număr de ori.

Sintaxa instrucțiunii este:

for (<valoare_initiala>; <conditie_sfarsit>; <valoare_increment>)

<instructiune>;

Instrucțiunea for folosește, de obicei, o variabilă denumită variabilă de control care indică de câte ori s-a executat instrucțiunea (<instructiune>) din corpul ciclului. Instrucțiunea for conține patru secțiuni:

– secțiunea <valoare_initiala> atribuie variabilei de control o valoare inițială, care, de cele mai multe ori, este 0 sau 1;

– secțiunea <conditie_sfarsit> testează valoarea variabilei de control pentru a stabili dacă programul a executat instrucțiunea de atâtea ori cât s-a dorit;

– secțiunea <valoare_increment> adaugă (scade), de obicei, valoarea 1 la variabila de control, de fiecare dată, după ce se execută instrucțiunea din corpul ciclului; valoarea de incrementare sau decrementare poate fi diferită de 1;

– secțiunea <instructiune> reprezintă instrucțiunea (sau instrucțiunile) care se dorește (doresc) a fi repetată (repetate).

Pentru înțelegerea efectului instrucțiunii for, să luăm de exemplu următoarea instrucțiune for, care va afișa pe ecran numerele de la 1 la 10:

for ( contor = 1; contor <= 10; contor++)

printf(“%d\n”, contor);

În acest exemplu, contor este variabila de control a ciclului for. Instrucțiunea for se execută astfel:

– pasul 1: se atribuie valoarea 1 variabilei contor;

– pasul2: se evaluează condiția (de sfârșit a ciclului) contor <= 10 :

– pasul 3: – dacă contor > 10 (valoarea condiției este 0) se iese din instrucțiunea

repetitivă for;

– dacă contor <= 10 (valoarea condiției este diferită de 0):

– se execută instrucțiunea imediat următoare care, în exemplul dat,

este printf ;

– se incrementează valoarea variabilei contor cu 1;

– se revine la pasul 2

Exemple:

1. Programul următor (for_test.c) numără crescător de la 1 la 5 și apoi de la 1 la 10 și afișează numerele respective folosind o instrucțiune for:

#include <stdio.h>

void main(void)

{int contor;

for (contor = 1; contor <= 5; contor++)

printf("%d ", contor);

printf("\nIncepe a doua bucla for\n");

for (contor = 1; contor <= 10; contor++)

printf("%d ", contor);

printf("\nIncepe a treia bucla for\n");

for (contor = 100; contor <= 5; contor++)

printf("%d ", contor);}

După executarea programului, pe ecran se afișează:

1 2 3 4 5

Începe a doua buclă for

1 2 3 4 5 6 7 8 9 10

Începe a treia buclă for

Se observă că a treia buclă for nu afișează nici o valoare deoarece valoarea inițială atribuită variabilei contor este 100, iar condiția de sfârșit este evaluată la 0 (este falsă) pentru că variabila contor este mai mare decât valoarea finală 5.

2. Programul următor (for_desc.c) numără descrescător numerele de la 5 la 1 și apoi de la 10 la 1 și afișează numerele respective folosind o instrucțiune for:

#include <stdio.h>

void main(void)

{int contor;

for (contor = 5; contor >= 1; contor–)

printf("%d ", contor);

printf("\nIncepe a doua bucla\n");

for (contor = 10; contor >= 1; contor–)

printf("%d ", contor);

printf("\nIncepe a treia bucla\n");

for (contor = 0; contor >= 1; contor–)

printf("%d ", contor);}

După executarea programului, pe ecran se afișează:

5 4 3 2 1

Începe a doua buclă

10 9 8 7 6 5 4 3 2 1

Începe a treia buclă

Se observă că a treia buclă for nu afișează nici o valoare deoarece valoarea inițială atribuită variabilei contor este 0, iar condiția de sfârșit este evaluată la 0 (este falsă) pentru că variabila contor este mai mică decât valoarea finală 1.

Notă:

1. Instrucțiunea for poate conține o secvență de instrucțiuni și atunci această secvență se grupează într-o singură instrucțiune compusă (încadrată între acolade).

2. Oricare dintre primele trei secțiuni care intervin în for poate să fie omisă. Dar și în acest caz, caracterul separator punct și virgulă (;) trebuie să apară.

3. Poate fi omisă și secțiunea de instrucțiuni din corpul ciclului (buclei) for, caz în care se spune că avem o buclă for vidă.

În exemplul următor instrucțiunea for citește în mod repetat un număr de la tastatură, până când numărul citit devine pozitiv (atunci se iese din for, datorită instrucțiunii break). Dacă condiția de ieșire din ciclu nu era specificată, ciclul for devenea infinit.

for ( ; ; )

{cin >> n;

if (n > 0) break;}

Observație: Bucla for din exemplul de mai sus va cicla la infinit dacă nu are în corpul ei o condiție de ieșire din ciclu.

Secvența de program următoare, care afișează numere naturale de la 0 la 9, arată cum poate fi omisă secțiunea de inițializare a variabilei de control a instrucțiunii for:

contor = 0;

….

for ( ; contor < 10; contor++)

printf (“ %d”, contor);

Exemplul următor de ciclu for omite secțiunile de inițializare și de incrementare ale variabilei de control:

contor = 0;

….

for ( ; contor < 10; )

printf (“ %d”, contor++);

Observație:

Ca regulă, dacă nu este nevoie să se utilizeze toate cele trei secțiuni ale instrucțiunii for, mai bine se va utiliza o altă structură repetitivă, cum ar fi instrucțiunea while,

Secvența de program următoare calculează factorialul unui număr n (stocat în variabila f) folosind instrucțiunea for în care secțiunea de instrucțiuni ale corpului ciclului este vidă:

for (f = i = 1; i <= n; f* = i++);

Utilizând instrucțiunea while secvența de program de mai sus este:

f = i =1;

while (i <= n)

{ f*=i;

i++; }

Alte exemple de folosire a instrucțiunii for.

Programul ce urmează (sir_fibonacci.cpp) ilustrează folosirea instrucțiunilor for pentru generarea și afișarea a n termeni din șirul Fibonacci și din șirul Lucas. De asemenea, calculează și afișează suma a n termeni din șirul Lucas.

Reamintim că șirul Fibonacci este de forma: 1, 1, 2, 3, 5, 8, 13, 21, 34, …, iar șirul Lucas este de forma: 1, 3, 4, 7, 11, 18, 29, 47, ….

#include <stdio.h>

#include <iostream.h>

int main()

{ int n, i, tr_f0=1, tr_f1=1, tr_f2, suma_sir;

cout<<"Introduceti numarul de termeni din sirul Fibonacci: ";

cin>>n;

printf ("Sirul Fibonacci este: %d %d ", tr_f0,tr_f1);

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

{ tr_f2=tr_f0+tr_f1; //calculeaza termenul curent

printf ("%d ", tr_f2);

tr_f0=tr_f1; //deplasare in sir

tr_f1=tr_f2; }

tr_f0=1; tr_f1=3;

suma_sir = tr_f0+tr_f1;

cout<<"\nIntroduceti numarul de termeni din sirul Lucas: ";

cin>>n;

printf ("Sirul Lucas este: %d %d ", tr_f0,tr_f1);

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

{ tr_f2=tr_f0+tr_f1; //calculeaza termenul curent

printf ("%d ", tr_f2);

suma_sir = suma_sir + tr_f2;

tr_f0=tr_f1; //deplasare in sir

tr_f1=tr_f2; }

printf("\nSuma primilor %d termeni din sirul Lucas este: %d", n, suma_sir);

return 0;}

Utilizarea instrucțiunii for cu valori de tip char și float

Instrucțiunea for poate utiliza valori de tip int (cum s-a văzut în exemplele prezentate deja) dar și valori de tip char și float pentru variabila de control a ciclului.

Următorul program (for_extl.cpp) ilustrează modul de folosire a caracterelor sau a valorilor reale în virgula mobilă într-un ciclu for (se afișează toate literele alfabetului cu majuscule și minuscule) și se afișează crescător numerele reale de la 0.0 la 0.9 cu un pas de 0.1:

#include <stdio.h>

#include <iostream.h>

void main()

{char litera;

float procent;

for (litera = 'A'; litera <= 'Z'; litera++)

cout<<litera<<" ";

cout<<'\n';

for (litera = 'z'; litera >= 'a'; litera–)

cout<<litera<<" ";

cout<<'\n';

for (procent = 0.0; procent < 1.0; procent += 0.1)

printf("%3.1f\n", procent);}

3.2 Funcții matematice din bibliotecile standard ale limbajului C/C++

1. Valoarea absolută precizează modulul, deci distanța valorii față de 0. Valoarea absolută este întotdeauna pozitivă.

Pentru determinarea valorii absolute de tip întreg, limbajul C/C++ pune la dispoziția programatorului funcția abs.

Funcția abs returnează valoarea absolută a unei expresii de tip întreg și este declarată astfel:

#include <stdlib.h>

int abs (int expresie);

De exemplu (programul functia_abs.cpp):

#include <stdio.h>

#include <stdlib.h>

void main()

{printf("Valoarea absoluta a lui %d este %d\n", 5, abs(5));

printf("Valoarea absoluta a lui %d este %d\n", 0, abs(0));

printf("Valoarea absoluta a lui %d este %d\n", -5, abs(-5));}

Pentru determinarea valorii absolute a unei expresii în virgulă mobilă (reală), limbajul C/C++ pune la dispoziția programatorului funcția fabs.

Funcția fabs returnează valoarea absolută a unei expresii de tip real în virgulă mobilă și este declarată astfel:

#include <math.h>

float fabs (float expresie);

De exemplu (programul functia_ fabs.cpp):

#include <stdio.h>

#include <math.h>

void main()

{

float val;

for (val = -1.0; val <= 1.0; val += 0.1)

printf("Valoare %f fabs %f\n", val, fabs(val));

}

2. Rotunjirea unei valori reale în virgulă mobilă, prin înlocuirea cu valoarea întreagă imediat următoare, se poate face prin folosirea funcției ceil.

Funcția ceil primește un parametru de tip double și returnează o valoare de tip double. Ea este declarată astfel:

#include <math.h>

double ceil (double valoare);

De exemplu (programul functia_ceil.cpp):

#include <stdio.h>

#include <math.h>

void main()

{printf("Valoarea %f ceil %f\n", 1.9, ceil(1.9));

printf("Valoarea %f ceil %f\n", 2.1, ceil(2.1));}

După execuția programului, pe ecran se va afișa:

Valoarea 1.900000 ceil 2.000000

Valoarea 2.100000 ceil 3.000000

3. Pentru a calcula cosinusul unui unghi se folosește funcția cos.

Funcția cos returnează o valoare de tip double ce reprezintă cosinusul unui unghi specificat în radiani. Ea este declarată astfel:

#include <math.h>

double cos (double expresie);

4. Pentru a calcula sinusul unui unghi se folosește funcția sin.

Funcția sin returnează o valoare de tip double ce reprezintă sinusul unui unghi specificat în radiani. Ea este declarată astfel:

#include <math.h>

double sin (double expresie);

5. Pentru a calcula tangenta unui unghi se folosește funcția tan.

Funcția tan returnează o valoare de tip double care reprezintă tangenta unui unghi specificat în radiani. Ea este declarată astfel:

#include <math.h>

double tan (double expresie);

Notă:

Numărul PI este exprimat în C/C++ prin constanta M_PI.

6. Pentru a calcula arccosinusul unui unghi se folosește funcția acos.

Funcția acos returnează o valoare de tip double ce reprezintă arccosinusul unui unghi specificat în radiani. Ea este declarată astfel:

#include <math.h>

double acos (double expresie);

7. Pentru a calcula arcsinusul unui unghi se folosește funcția asin.

Funcția asin returnează o valoare de tip double ce reprezintă arcsinusul unui unghi specificat în radiani. Ea este declarată astfel:

#include <math.h>

double asin (double expresie);

8. Pentru a calcula arctangenta unui unghi se folosește funcția atan.

Funcția atan returnează o valoare de tip double care reprezintă arctangenta unui unghi specificat în radiani. Ea este declarată astfel:

#include <math.h>

double atan (double expresie);

9. Pentru a calcula ex se utilizează funcția exp.

Funcția exp returnează o valoare de tip double și este declarată astfel:

#include <math.h>

double exp (double x);

Următorul program(functia_exp.cpp) ilustrează modul cum se utilizează funcția exp:

#include <stdio.h>

#include <math.h>

void main()

{

double val;

for (val = 0.0; val <= 1.0; val += 0.1)

printf("exp(%f) este %f\n", val, exp(val));

}

10. Pentru a calcula x*2exponent se utilizează funcția ldexp.

Funcția ldexp returnează o valoare de tip double și este declarată astfel:

#include <math.h>

double ldexp (double val, int exponent);

Următorul program (functia_ldxp.cpp) ilustrează modul cum se utilizează funcția ldexp:

#include <stdio.h>

#include <math.h>

void main()

{

printf("3 * (2 la puterea 4) este %f\n",

ldexp(3.0, 4));

}

După execuția programului, pe ecran se va afișa:

3 * (2 la puterea 4) este 48.000000

11. Pentru a calcula logaritmul natural al unui număr se utilizează funcția log.

Funcția log returnează o valoare de tip double și este declarată astfel:

#include <math.h>

double log (double val);

Funcția log lucrează numai cu valori pozitive pentru parametrul <val>, în caz contrar semnalând eroare.

Următorul program (functia_log.cpp) ilustrează modul cum se utilizează funcția log:

#include <stdio.h>

#include <math.h>

void main()

{

printf("Logaritmul natural de 256.0 este %f\n", log(256.0));

}

După execuția programului, pe ecran se va afișa:

Logaritmul natural de 256.0 este 5.545177

12. Pentru a calcula logaritmul în baza 10 al unui număr se utilizează funcția log10.

Funcția log10 returnează o valoare de tip double și este declarată astfel:

#include <math.h>

double log10 (double val);

Funcția log10 lucrează numai cu valori pozitive pentru parametrul <val>, în caz contrar semnalând eroare.

Următorul program (functia_log10.cpp) ilustrează modul cum se utilizează funcția log10:

#include <stdio.h>

#include <math.h>

void main()

{

printf("Logaritmul zecimal al lui 100 este %1.1f\n", log10(100.0));

printf("Logaritmul zecimal al lui 10000 este %1.1f\n", log10(10000.0));

}

După execuția programului, pe ecran se va afișa:

Logaritmul zecimal al lui 100 este 2.0

Logaritmul zecimal al lui 10000 este 4.0

13. Pentru ridicarea unui număr la o putere dată (Xn), limbajul C/C++ oferă funcția pow.

Funcția pow returnează o valoare de tip double și este declarată astfel:

#include <math.h>

double pow (double val, double putere);

Dacă rezultă o depășire din calculul ridicării la puterea dată, funcția pow semnalează o eroare.

Următorul program (functia_pow.cpp) ilustrează modul cum se utilizează funcția pow:

#include <stdio.h>

#include <math.h>

void main()

{

int putere;

for (putere = -2; putere <= 2; putere++)

printf("10 ridicat la %d este %f\n", putere, pow(10.0, putere));

}

14. Generarea unui număr aleator

Pentru generarea unui număr aleator sau mai multor numere aleatoare limbajul C/C++ pune la dispoziție două funcții, rand și random.

Funcțiile rand și random returnează, fiecare, un număr întreg aleator și sunt declarate astfel:

#include <stdlib.h>

int rand (void);

int random (int limita);

Funcția rand returnează un număr întreg aleator cuprins în intervalul de la 0 la RAND_MAX (valoare definită în stdlib.h și dependentă de compilatorul C folosit, 16 sau 32 bits).

Funcția random returnează un număr întreg aleator cuprins în intervalul de la 0 până la valoarea maximă dată de parametrul <limita> al funcției.

Următorul program (functia_random.cpp) ilustrează modul cum se utilizează ambele funcții generatoare de numere aleatoare:

#include <stdio.h>

#include <stdlib.h>

void main()

{int i;

printf("Numere aleatoare date de rand\n");

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

printf("%d \t", rand());

printf("Numere aleatoare date de random(10)\n");

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

printf("%d \t", random(10));}

După execuția programului, pe ecran se va afișa:

Numere aleatoare date de rand

346 130 10982 1090 11656 7117 17595 6415 22948 31126

9004 14558 3571 22879 18492 1360 5412 26721 22463 25047

27119 31441 7190 13985 31214 27509 30252 26571 14779 19816

21681 19651 17995 23593 3734 13310 3979 21995 15561 16092

18489 11288 28466 8664 5892 13863 22766 5364 17639 21151

Numere aleatoare date de random(10)

4 3 6 1 7 0 4 9 5 7

4 5 6 2 1 2 6 3 6 4

8 3 3 6 1 6 7 5 1 9

4 3 4 8 0 8 9 6 1 7

8 7 1 9 4 3 8 4 6 2

Pentru generarea de numere aleatoare într-un interval dat se pot folosi două procedee în funcție de tipul datelor dorite a fi generate, astfel:

– dacă se dorește generarea de valori aleatoare întregi pozitive, se va folosi funcția random cu parametrul <limita> care specifică limita maximă a intervalului de numere aleatoare generate (plecând de la 0); dacă se dorește generarea de valori aleatoare întregi negative se va folosi funcția random într-o expresie aritmetică prin care se scade corespunzător numărul aleator generat pentru a se încadra în intervalul dorit;

– dacă se dorește generarea de valori aleatoare în virgulă mobilă (cum ar fi cele din intervalul 0.0 până la 1.0), se împarte numărul aleator generat de funcția random cu o constantă; pentru a transforma o serie de numere întregi aleatoare într-o serie de numere în virgulă mobilă, pur și simplu se împarte numărul aleator la limita maximă (dată de parametrul <limita> al funcției random) a numărului aleator, ca în exemplul următor:

random(100)/100.0 – se va genera un număr aleator cuprins în intervalul de la 0.00 până la 1.00

Următorul program (interval_random.cpp) generează numere aleatoare în virgulă mobilă în intervalul de al 0.0 până la 1.0 și numere întregi în intervalul de la -5 până la +5.

#include <stdio.h>

#include <stdlib.h>

void main()

{int i;

printf("Numere aleatoare generate de random in intervalul de la 0.01 pana la \

1.0\n");

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

printf("%f\t", random(100)/100.0);

printf("\nNumere aleatoare generate de random in intervalul de la -5 pana la 5\n");

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

printf("%d\t", random(10)-5);}

După execuția programului, pe ecran se va afișa:

Numere aleatoare generate de random în intervalul de la 0.01 până la 1.0

0.400000 0.200000 0.120000 0.760000 0.880000

0.720000 0.920000 0.120000 0.130000 0.600000

Numere aleatoare generate de random în intervalul de la -5 până la 5

2 4 -5 -1 -5 2 2 -4 1 -2

Inițializarea generatorului de numere aleatoare este folosită pentru a controla sau nu seria de numere pe care le produce generatorul de numere aleatoare. Controlul seriei de numere aleatoare înseamnă, de fapt, că la fiecare execuție a programului dvs. va fi generat același set de numere aleatoare și nu altul.

Procesul de atribuire a unui număr de start (de “plecare”) pentru generatorul de numere aleatoare este denumit inițializare sau lansare (seeding).

În limbajul C/C++ sunt folosite două funcții pentru inițializarea generatorului de numere aleatoare: funcția randomize și funcția srand, care sunt declarate astfel:

#include <stdlib.h>

void randomize (void);

void srand (unsigned primul);

Funcția randomize utilizează ceasul calculatorului pentru a produce o inițializare aleatoare. Folosind această funcție se generează aleator o serie de date, de fiecare dată, alta decât cea care a fost generată la o execuție anterioară a programului (deci, seria nu poate fi controlată).

Funcția srand folosește o valoare de start a generatorului de numere aleatoare (precizată prin parametrul <primul>). Folosind această funcție se generează aleator o serie de date, de fiecare dată, aceeași cu cea care a fost generată la o execuție anterioară a programului (deci, seria poate fi controlată).

Următorul program (ini_random.cpp) ilustrează modul de folosire a funcțiilor srand și randomize:

#include <stdio.h>

#include <stdlib.h>

void main()

{int i;

srand(30);

printf("Numere aleatoare date de random\n");

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

printf("%d \t", random(50));

printf("\n5 numere identice\n");

srand(30);

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

printf("%d \t", random(50));

randomize();

printf("\n5 numere diferite\n");

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

printf("%d \t", random(50));}

După execuția programului, pe ecran se va afișa:

Numere aleatoare date de random

0 48 7 45 31

5 numere identice

0 48 7 45 31

5 numere diferite

26 48 48 8 33

15. Pentru a calcula rădăcina pătrată a unui număr se utilizează funcția sqrt.

Funcția sqrt returnează o valoare de tip double și este declarată astfel:

#include <math.h>

double sqrt (double val);

Funcția sqrt lucrează numai cu valori pozitive pentru parametrul <val>, în caz contrar semnalând eroare.

Observații referitoare la folosirea funcțiilor matematice:

1. Câteva din funcțiile matematice prezentate detectează erorile de interval și de depășire. În mod prestabilit, când apar astfel de erori, funcția matematică respectivă apelează o altă funcție specială care execută niște procese speciale pentru semnalarea numărului erorii.

2. Multe compilatoare de C furnizează funcțiile matematice prezentate (acelea care sunt de tip double) și pentru valori de tip long double.

3.2 Tipuri structurate de date: tablouri (variabile indexate)

Un tablou (o variabilă indexată) este o structură de date care poate să păstreze mai multe valori de același tip, memorate într-o zonă de memorie contiguă și reunite sub un nume simbolic comun (numele tabloului).

Un tablou poate fi:

– unidimensional sau vector;

– bidimensional sau matrice;

– multidimensional.

Declararea unui tablou unidimensional (vector) se face astfel:

<tip> <nume_tablou>[nr_elemente];

unde:

– <tip> – specifică tipul de dată al elementelor tabloului;

– <nume_tablou> – specifică numele simbolic al tabloului;

– <nr_elemente> – indică numărul de elemente din tablou și trebuie să fie obligatoriu o expresie constantă.

Notă: parantezele drepte sunt obligatorii, nu delimitează o construcție opțională.

Accesarea (identificarea) unui element al tabloului unidimensional

Deoarece elementele unui tablou sunt memorate în ordine, unul după altul, într-o zonă contiguă, pentru a accesa (sau a referi) un element al unui tablou se specifică numele tabloului din care face parte elementul și poziția sa în tablou, prin numărul său de ordine (numerotarea începe de la 0), astfel:

<nume_tablou>[<indice>]

în care:

– <indice> reprezintă numărul de ordine al elementului în tablou, cuprins între 0 și <nr_elemente> – 1; parantezele pătrate ([]) constituie operatorul de indexare și are prioritate maximă (mai mare decât a operatorilor unari).

Exemple:

int punctaje[5];

elementele tabloului sunt: punctaje[0], punctaje[1], punctaje[2], …. punctaje[4]

float salarii[50];

elementele tabloului sunt: salarii[0], salarii[2], …, salarii[49].

Calculul dimensiunii zonei de memorie necesară unui tablou unidimensional

La întâlnirea unei declarații de variabilă-tablou, compilatorul verifică dacă dimensiunea zonei de memorie necesară pentru memorarea tabloului nu depășește memoria disponibilă.

Dimensiunea zonei de memorie necesară unui tablou unidimensional se calculează astfel:

<nr_elemente> * sizeof (<tip>).

Inițializarea unui tablou unidimensional

Inițializarea unei variabile-tablou se poate face chiar de la declarare astfel:

<tip> <nume_tablou>[nr_elemente] = {val_1, val_2, ….val_k};

unde: <val_1>, … <val_k> – specifică valorile atribuite elementelor tabloului.

Ca urmare, se vor atribui în ordine elementelor tabloului valorile din lista de inițializare (k ≤ nr_elemente).

Exemple:

int punctaje[5] = {80, 70, 90, 85, 80}; sau

float salarii[50] = {25000.0, 32000.0, 44000.0, 23000.0};

Notă: Dacă tabloul este integral inițializat la declarare (ca în primul exemplu) nu este necesar să se mai specifice dimensiunea sa, aceasta fiind considerată egală cu numărul de valori din lista de inițializare.

Deci, primul exemplu de declarare și inițializare a unei variabile-tablou poate fi rescris astfel:

int punctaje[] = {80, 70, 90, 85, 80};

ceea ce înseamnă că s-a declarat un tablou unidimensional cu 5 elemente de tip int inițializate astfel:

În al doilea exemplu, se inițializează patru elemente ale tabloului salarii, restul rămânând neinițializate. Ca regulă, compilatorul nu inițializează celelalte elemente ale tabloului.

Declararea unui tablou bidimensional (matrice) se face astfel:

<tip> <nume_tablou>[nr_linii][nr_coloane];

unde:

– <tip> – specifică tipul de dată al valorilor tabloului;

– <nume_tablou> – specifică numele simbolic al tabloului;

– <nr_linii> – indică numărul de linii și trebuie să fie obligatoriu o expresie constantă;

– <nr_coloane> – indică numărul de coloane și trebuie să fie obligatoriu o expresie constantă.

Accesarea (identificarea) unui element al tabloului bidimensional (matrice)

Pentru a accesa (sau a referi) un element al unei matrice se specifică numele matricei din care face parte elementul și poziția sa în matrice, prin numărul liniei și numărul coloanei corespunzătoare (numerotarea începe de la linia 0 și coloana 0), astfel:

<nume_tablou>[<indice_linie>] [<indice_coloana]

Exemplu:

int a[4][3];

elementele tabloului sunt: a[0][0], a[0][1], a[0][2], a[1][1], a[1][2], …, a[3][2].

Inițializarea unui tablou bidimensional (matrice)

Să presupunem că, se inițializează la declarare o matrice cu numele simbolic b astfel:

int b[2][3]={{1, 2, 3}, {7, 8, 9}};

Compilatorul va inițializa elementele matricei ca în figura de mai jos:

Un alt exemplu de matrice mai mare:

int vanzari[4][5]={{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}, {16, 17, 18, 19, 20}};

Calculul dimensiunii zonei de memorie necesară unui tablou bidimensional

Pentru a determina memoria consumată de o matrice se efectuează același calcul ca la un vector:

<nr_linii> * <nr_coloane> * sizeof (<tip>).

Tablouri multidimensionale

Elementele unui tablou bidimensional pot fi de orice tip, inclusiv de tip tablou. Se obține astfel un tablou multidimensional.

De exemplu, un tablou tridimensional:

int a[10][5][40];

Ciclarea printre elementele unui vector și utilizarea constantelor pentru dimensionarea acestuia

Următorul program (vector_const.cpp), utilizează variabila i și bucla for pentru a afișa elementele unui vector cu numele val. De asemenea, programul declară vectorul val utilizând constanta DIM_VECT (declarată printr-o directivă #define), care permite modificarea, ulterioară, mai ușoară a dimensiunilor vectorului val.

#include <stdio.h>

#define DIM_VECT 5

void main()

{int val[DIM_VECT] = {80, 70, 90, 85, 80};

int i;

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

printf("valorile[%d] = %d\n", i, val[i]);}

După execuția programului, pe ecran se va afișa:

valorile[0] = 80

valorile[1] = 70

valorile[2] = 90

valorile[3] = 85

valorile[4] = 80

Ciclarea printre elementele unei matrice

Următorul program (ciclare_matrice.cpp) utilizează variabilele rând și coloană pentru a afișa valorile conținute în cadrul matricei tabel:

#include <stdio.h>

void main()

{int linie, coloana;

float tabel[3][5] = {{1.0, 2.0, 3.0, 4.0, 5.0},

{6.0, 7.0, 8.0, 9.0, 10.0},

{11.0, 12.0, 13.0, 14.0, 15.0}};

for (linie = 0; linie < 3; linie++)

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

printf("tabel[%d][%d] = %f\n", linie, coloana, tabel[linie][coloana]);}

După execuția programului, pe ecran se va afișa:

Programul următor (traversare_elem_matr.cpp) parcurge elementele dintr-o matrice pătratică, care este citită de la tastatură. Sunt afișate elementele de pe diagonala principală, elementele deasupra și de sub diagonala principală, elementele de pe diagonala secundară, elementele deasupra și de sub diagonala secundară.

#include <iostream.h>

#include <conio.h>

void main()

{int n,i,j,A[10][10];

cout << "Introduceti numarul de linii ";

cin >> n;

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

{cout <<"Introduceti linia " <<i<<endl;

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

cin >> A[i][j];}

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

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

cout<<"A["<<i<<"]["<<j<<"]="<<A[i][j]<<" ";

cout <<endl;}

cout<<"Elementele de pe diagonala principala: "<<endl;

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

cout<<A[i][i]<<endl;

cout<<"Elementele deasupra diagonalei principale: " <<endl;

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

for(j=i+1;j<n;j++)

cout<<A[i][j]<<endl;

cout<<"Elementele de sub diagonala principala: " <<endl;

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

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

cout<<A[i][j]<<endl;

cout<<"Elementele de pe diagonala secundara: " <<endl;

for(i=n;i>0;i–)

cout<<A[i-1][n-i]<<endl;

cout<<"Elementele deasupra diagonalei secundare: " <<endl;

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

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

cout<<A[i][j]<<endl;

cout<<"Elementele de sub diagonala secundara: " <<endl;

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

for(j=n-i;j<n;j++)

cout<<A[i][j]<<endl;

getch();

}

Prelucrări elementare pe tablouri

Programul următor (cautare_binara.cpp) verifică dacă un număr citit de la tastatură se găsește sau nu într-un vector citit și el de la tastatură.

S-a folosit algoritmul de căutare binară. Se ordonează crescător valorile din vector. Apoi, se lucrează prin înjumătățiri succesive. Mai întâi se compară numărul căutat cu elementul din mijloc. Dacă este egal cu elementul din mijloc, s-a găsit numărul și programul se termină. Dacă nu este egal, se verifică dacă numărul căutat este mai mare decât elementul din mijloc, caz în care căutăm mai departe numai în cea de-a două jumătate. Dacă numărul căutat este mai mic decât elementul din mijloc, căutăm mai departe numai în prima jumătate.

#include <iostream.h>

void main()

{int a[100], n, i, j, temp;

int limi,lims, gasit, x, mijloc;

cout << "n= ";cin>>n;

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

{cout<<"a["<<i<<"]="; cin>>a[i];}

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

for (j=i+1; j<n; j++)

if(a[i]>a[j])

{temp=a[i]; a[i]=a[j]; a[j]=temp;} //ordonez crescator

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

cout<<'\n'<<"a["<<i<<"]="<<a[i];

cout<<'\n'<<"Introduceti numarul de cautat:";

cin>>x;

for (limi=0, lims=n-1, gasit=0; !gasit && limi<=lims;)

{mijloc=(limi+lims)/2; //calulez mijloc

if(a[mijloc]==x) gasit = 1;

else

if (a[mijloc]<x) limi = mijloc+1; //caut in a doua jumatate

else

lim_s=mijloc-1;} //caut in prima jumatate

if(gasit) cout<<x<<" se gaseste pe pozitia "<<mijloc;

else cout<<x<<" nu se afla in vector ";}

Programul următor (min_elem_matr.cpp) calculează valoarea minimă a elementelor unei matrice, cu n linii și m coloane, care este citită de la tastatură.

#include <iostream.h>

#include <conio.h>

void main()

{int n,m,i,j,min,A[10][10];

cout << "Introduceti numarul de linii ";

cin >> n;

cout << "Introduceti numarul de coloane ";

cin >> m;

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

{cout <<"Introduceti linia " <<i<<endl;

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

cin >> A[i][j];}

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

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

cout<<"A["<<i<<"]["<<j<<"]="<<A[i][j]<<" ";

cout <<endl;}

min=A[0][0];//initializare minim

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

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

/*fiecare element al matricei este comparat cu minim*/

if (A[i][j]<min)

min=A[i][j];

cout<<"Minimul elementelor matrecei A este:"<<min;

getch();}

Programul următor (elem_imp_dprinc.cpp) verifică elementele impare de sub diagonala principală dintr-o matrice pătratică, care este citită de la tastatură.

#include <iostream.h>

#include <conio.h>

void main()

{int n,i,j,A[10][10];

int nr=0;

cout << "Introduceti numarul de linii "; cin >> n;

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

{cout <<"Introduceti linia " <<i<<endl;

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

cin >> A[i][j];}

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

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

cout<<"A["<<i<<"]["<<j<<"]="<<A[i][j]<<" ";

cout <<endl;}

cout<<"Elementele impare de sub diagonala principala sunt:"<<endl;

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

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

if(A[i][j]%2 != 0)

{nr++;

cout<<A[i][j]<<endl;}

cout<<"Nr. elementelor impare este "<<nr<<endl; getch();}

Programul următor (traversare_elem_matr.cpp) traversează elementele dintr-o matrice pătratică, care este citită de la tastatură.

#include <iostream.h>

#include <conio.h>

void main()

{

int n,i,j,A[10][10];

cout << "Introduceti numarul de linii ";

cin >> n;

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

{

cout <<"Introduceti linia " <<i<<endl;

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

cin >> A[i][j];

}

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

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

cout<<"A["<<i<<"]["<<j<<"]="<<A[i][j]<<" ";

cout <<endl;}

cout<<"Elementele de pe diagonala principala: "<<endl;

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

cout<<A[i][i]<<endl;

cout<<"Elementele deasupra diagonalei principale: " <<endl;

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

for(j=i+1;j<n;j++)

cout<<A[i][j]<<endl;

cout<<"Elementele sub diagonala principala: " <<endl;

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

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

cout<<A[i][j]<<endl;

cout<<"Elementele de pe diagonala secundara: " <<endl;

for(i=n;i>0;i–)

cout<<A[i-1][n-i]<<endl;

cout<<"Elementele deasupra diagonalei secundare: " <<endl;

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

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

cout<<A[i][j]<<endl;

cout<<"Elementele sub diagonala secundara: " <<endl;

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

for(j=n-i;j<n;j++)

cout<<A[i][j]<<endl;

getch();

}

Lecția 4

Cuvinte importante:

– tipul pointer;

– operații cu pointeri: operația de referențiere, operația de dereferențiere, incrementare/decrementare, adunarea/scăderea dintre un pointer și un număr întreg n, comparații;

– pointeri și tablouri unidimensionale (vectori);

– pointeri și tablouri bidimensionale (matrice);

– tipuri structurate de date: șiruri de caractere;

– pointeri și șiruri ca tablouri unidimensionale de caractere;

– pointeri const;

– citirea și afișarea șirurilor de caractere.

Tipuri structurate de date: tipul pointer

Știm că, memoria internă este alcătuită din octeți. Să presupunem că aceștia sunt numerotați de la 0 la n-1. O variabilă ocupă unul sau mai mulți octeți consecutivi. Adresa unei variabile este numărul de ordine al primului octet al ei (în realitate mecanismul de adresare a memoriei este mult mai complicat, dar simplificat este suficient acest model).

Un pointer este o dată care are ca valoare adresa de memorie la care se găsește o variabilă. Pointerii sunt utilizați, în principal, în următoarele situații: lucru cu tablouri, transferul parametrilor funcțiilor, accesul direct la memorie și alocarea dinamică a memoriei.

Declararea unui pointer de date

Declararea unui pointer de date se realizează cu sintaxa:

<tip> * <variabila_pointer>; unde:

– <tip> – reprezintă tipul de bază al pointerului, care specifică tipul valorilor memorate la adresa conținută în <variabila_pointer>; tipul de bază al pointerului poate fi orice tip al limbajului, inclusiv tipul void; un pointer cu tipul de bază void se numește pointer generic;

– <variabila_pointer> – specifică numele unei variabile-pointer în care este stocată o adresă a unei zone de memorie care conține valori cu tipul de dată precizat în <tip>.

Exemplu:

int * adr_nr;

adr_nr este o variabilă-pointer cu tipul de bază int, care va conține adresa unei zone de memorie la care este memorat un număr întreg (de tip int).

char * adr_car;

adr_car este o variabilă-pointer cu tipul de bază char, deci va conține adresa unei zone de memorie la care este memorat un caracter.

Notă:

1. Declararea unei variabile-pointer este asemănătoare cu declararea unei variabile obișnuite, singura diferență constă în prezența asteriscului (*) înaintea variabilei de tip pointer.

2. Există o constantă-pointer specială, denumită NULL. Valoarea acestei constante este 0 și semnificația ei este: “pointerul nu conține adresa nici unei zone de memorie”.

Observație:

Indicarea tipului de bază al pointerului este esențială. Tipul de bază al unui pointer precizează:

a) dimensiunea zonei de memorie a cărei adresă este stocată în pointer;

b) codul utilizat pentru reprezentarea datelor în acea zonă de memorie.

a) Adresa unei zone de memorie este, de fapt, adresa primului octet (byte) din zona respectivă (o valoare numerică). Pointerul stochează, de fapt, adresa primului octet din zona de memorie respectivă. Indicând tipul de bază al pointerului, se poate determina, dimensiunea zonei de memorie a cărei adresă este memorată în variabila-pointer (de exemplu: un octet pentru tipul de bază char, 4 octeți pentru long int etc.).

b) Pentru a afla valoarea memorată într-o anumită zonă de memorie trebuie să știm și codificarea utilizată pentru reprezentarea datelor în acea zonă. De exemplu, într-o zonă de 4 octeți s-ar putea afla un număr natural de tip unsigned long int (codificat prin reprezentarea lui binară) sau un număr întreg de tip long int (reprezentat în cod complementar) sau un număr real de tip float (reprezentat în virgulă mobilă simplă precizie) sau un șir de 4 caractere de tip char (reprezentat în cod ASCII). Tipul de bază al pointerului, ce stochează adresa de început a unei zone de memorie, oferă informația necesară pentru decodificarea secvenței de octeți stocată în acea zonă de memorie.

Operații cu pointeri

1. Operația de referențiere

Operația de referențiere este operația prin care se poate obține adresa de memorie a unei variabile. Rezultatul acestei operații este un pointer care are ca valoare adresa primului octet al zonei de memorie în care este stocată variabila. Această operație se mai numește și referențierea unei variabile.

Operatorul folosit în operația de referențiere a unei variabile este operatorul unar & (operator de referențiere sau operator de adresă).

Sintaxa folosită este:

<adr_variabila> = &<nume_variabila>

unde:

– <adr_variabila> – specifică o variabilă-pointer în care este stocată adresa de memorie a variabilei <nume_variabila> de un anumit tip;

– <nume_variabila> – specifică numele unei variabile ce conține valori, de un anumit tip, stocate într-o zonă de memorie.

2. Operația de dereferențiere

Operația de dereferențiere este operația prin care se poate afla valoarea stocată într-o zonă de memorie a cărei adresă de început este memorată într-un pointer. Aceasta operație se mai numește și dereferențierea unui pointer.

Operatorul folosit în operația de dereferențiere a unui pointer este operatorul unar * (operator de dereferențiere).

Sintaxa folosită este:

<nume_variabila> = *<adr_variabila>

unde:

– <adr_variabila> – specifică o variabilă-pointer în care este stocată adresa de memorie a variabilei <nume_variabila> de un anumit tip;

– <nume_variabila> – specifică numele unei variabile ce conține valori, de un anumit tip, stocate într-o zonă de memorie.

Exemplu:

int i = 20, j, *p;

p = &i; //referențiere – se atribuie variabilei-pointer p adresa variabilei i

j = *p; //dereferențiere – se atribuie variabilei j valoarea conținută în zona de memorie a cărei adresă este memorată în variabila-pointer p (adică valoarea 20).

3. Incrementare/decrementare

Asupra unui pointer se poate aplica operatorul de incrementare (++) și operatorul de decrementare (–).

Efectul aplicării operatorului de incrementare/decrementare este:

Se mărește/micșorează adresa memorată în variabila-pointer cu numărul de octeți necesari pentru a memora o valoare cu tipul de bază al pointerului (sizeof (<tip>)).

Exemple:

long int * p;

p++; //adresa memorată în variabila-pointer p se mărește cu sizeof (long int)

char * p;

p–; //adresa memorată în variabila-pointer p se micșorează cu sizeof (char)

4. Adunarea/scăderea dintre un pointer și un număr întreg n

Operația de adunare (+) a unei variabile-pointer cu un număr întreg n are ca efect mărirea adresei stocate în pointer cu n*sizeof(<tip>), unde <tip> este tipul de bază al pointerului.

De exemplu: p+n specifică adresa celui de-al n-lea element de tipul de bază al pointerului care urmează după adresa p.

Operația de scădere (-) a unei variabile-pointer cu un număr întreg n are ca efect micșorarea adresei stocate în pointer cu n*sizeof(<tip>), unde <tip> este tipul de bază al pointerului.

De exemplu: p-n specifică adresa celui de-al n-lea element de tipul de bază al pointerului care precede adresa p.

5. Scăderea dintre doi pointeri

Scăderea dintre doi pointeri se poate realiza numai dacă aceștia au același tip de bază. Rezultatul este o valoare întreagă care reprezintă diferența dintre adrese, raportată la sizeof(<tip>).

6. Comparații

Se pot folosi operatorii relaționali și de egalitate în lucru cu pointerii numai dacă aceștia au același tip de bază.

Afișarea unei variabile-pointer

În limbajul C se utilizează funcția printf() cu specificatorul de format %p.

De exemplu:

int b, *z=&b;

printf (“%p”, z); //se afișează adresa variabilei b în bază 16

– În limbajul C++ se folosește fluxul de ieșire cout.

De exemplu:

int b, *z=&b;

cout << z; //se afișează adresa variabilei b în bază 16

Pointeri și tablouri unidimensionale (vectori)

Numele unui tablou unidimensional este o constantă-pointer care are ca valoare adresa primului element din tablou.

Fie următoarea declarație:

long int vector[10];

Următoarele expresii sunt echivalente:

vector &vector[0] – specifică adresa primului element din tablou;

vector+i &vector[i] – specifică adresa elementului de pe poziția i din tablou;

*vector vector[0] – specifică valoarea stocată în primul element din tablou;

*(vector+i) vector[i] – specifică valoarea stocată în elementul de pe poziția i din tablou.

Observație:

Operațiile cu pointeri reprezintă o alternativă pentru adresarea elementelor unui tablou.

Următorul program (echi_pointer_vector.ccp) ilustrează legătura dintre pointer și tabloul unidimensional cu numele ”vector” care are trei elemente. De asemenea, arată și modul de folosire a operatorului de incrementare (++) aplicat pe un pointer cu numele ”p” .

#include <iostream.h>

#include <stdio.h>

void main()

{

long int vector[3];

int i;

long int *p;

vector[0] = 0;

cout << "adresa elementului vector[0] = " << vector << " ; " << &vector[0] << '\n';

cout << "valoarea elementului vector[0] = " << *vector << " ; " << vector[0] << '\n';

p = vector;

cout << "adresa elementului vector[0] stocata in pointerul \"p\" este " << p << '\n';

for (i=1; i < 3; i++)

{vector[i] = i;

cout << "adresa elementului vector[" << i << "] = " << (vector + i) << " ; “ << &vector[i] << '\n';

cout << "valoarea elementului vector[" << i << "] = " << *(vector + i) << " ; "

<< vector[i] << '\n';

p++ ;

cout << "adresa elementului vector[" << i << "] stocata in pointerul \"p\" este " << p << '\n';}

}

După execuția programului, pe ecran se afișează:

adresa elementului vector[0] = 0x0012ff40 ; 0x0012ff40

valoarea elementului vector[0] = 0 ; 0

adresa elementului vector[0] stocata in pointerul "p" este 0x0012ff40

adresa elementului vector[1] = 0x0012ff44 ; 0x0012ff44

valoarea elementului vector[1] = 1 ; 1

adresa elementului vector[1] stocata in pointerul "p" este 0x0012ff44

adresa elementului vector[2] = 0x0012ff48 ; 0x0012ff48

valoarea elementului vector[2] = 2 ; 2

adresa elementului vector[2] stocata in pointerul "p" este 0x0012ff48

Combinarea dereferențierii unui pointer cu incrementarea unui pointer (accesarea valorilor dintr-un tablou utilizând un pointer)

Următorul program (deref_increm_ptr.cpp) afișează valorile numerice conținute într-un vector de numere întregi, utilizând un pointer și operatorii de dereferențiere și de incrementare pe pointer.

Programul atribuie adresa primului element al vectorului de valori întregi, cu numele "valori", la pointerul, cu numele "iptr". Programul dereferențiază, mai întâi, pointerul (obținând valoarea de la adresa indicată de pointerul "iptr") și apoi incrementează pointerul (adresa) pentru a accesa următoarea valoare numerică întreagă din vectorul de valori întregi.

#include <stdio.h>

void main()

{

int valori[5] = {1, 2, 3, 4, 5};

int contor;

int *iptr;

iptr = valori;

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

printf("%d\n", *iptr++);

}

Pointeri și tablouri bidimensionale (matrice)

O matrice este un vector (liniile) de vectori (coloanele), deci numele matricei este o constantă-pointer care are ca valoare adresa primei linii din matrice.

Fie următoarea declarație:

long int matrice[3][3];

Următoarele expresii sunt echivalente:

matrice matrice[0] &matrice[0] – adresa primei linii din matrice;

matrice+i matrice[i] &matrice[I] – adresa liniei i din matrice;

*matrice matrice[0] &matrice[0][0] – adresa primului element de pe linia 0;

*(matrice+i) matrice[i] &matrice[I][0] – adresa primului element de pe linia i;

(*(matrice + i) + j) (matrice[i] + j) &matrice[i][j] – adresa elementului de pe linia i și coloana j;

*(*(matrice + i)) *matrice[i] matrice[i][0] – valoarea stocată în primul element de pe linia i;

*(*(matrice+i) + j) *(matrice[i] + j) matrice [i][j] – valoarea stocată în elementul de pe linia i și coloana j.

Următorul program (echi_pointer_matrice.ccp) ilustrează legătura dintre pointer și tabloul bidimensional cu numele "matrice" de 3 linii și trei coloane:

#include <iostream.h>

#include <stdio.h>

void main()

{long int matrice[3][3];

int i, j;

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

{

cout << "adresa liniei " << i << " din \"matrice\" este " << (matrice + i) << " ; “

<< matrice[i] << " ; " << &matrice[i] << '\n';

cout << "adresa primului element de pe linia " << i << " este " << *(matrice +i)

<< " ; " << matrice [i] << " ; " << &matrice[i][0] << '\n';

matrice [i][0] = 0;

cout << "valoarea elementului matrice[" << i << "][0] = " << *(*(matrice + i))

<< " ; " << *matrice[i] << " ; " << matrice[i][0] << '\n';

for (j = 1; j<3; j++)

{matrice[i][j] = j;

cout << "adresa elementului matrice[" << i << "][" << j << "] = " <<

(*(matrice + i) + j) << " ; " << (matrice[i] + j) << " ; "<< &matrice[i][j] << '\n';

cout << "valoarea elementului matrice[" << i << "][" << j << "] = " <<

*(*(matrice + i) + j) << " ; " << *(matrice[i] + j) << " ; " << matrice[i][j] << '\n';}

}

}

După execuția programului, pe ecran se afișează:

adresa liniei 0 din "matrice" este 0x0012ff14 ; 0x0012ff14 ; 0x0012ff14

adresa primului element de pe linia 0 este 0x0012ff14 ; 0x0012ff14 ; 0x0012ff14

valoarea elementului matrice[0][0] = 0 ; 0 ; 0

adresa elementului matrice[0][1] = 0x0012ff18 ; 0x0012ff18 ; 0x0012ff18

valoarea elementului matrice[0][1] = 1 ; 1 ; 1

adresa elementului matrice[0][2] = 0x0012ff1c ; 0x0012ff1c ; 0x0012ff1c

valoarea elementului matrice[0][2] = 2 ; 2 ; 2

adresa liniei 1 din "matrice" este 0x0012ff20 ; 0x0012ff20 ; 0x0012ff20

adresa primului element de pe linia 1 este 0x0012ff20 ; 0x0012ff20 ; 0x0012ff20

valoarea elementului matrice[1][0] = 0 ; 0 ; 0

adresa elementului matrice[1][1] = 0x0012ff24 ; 0x0012ff24 ; 0x0012ff24

valoarea elementului matrice[1][1] = 1 ; 1 ; 1

adresa elementului matrice[1][2] = 0x0012ff28 ; 0x0012ff28 ; 0x0012ff28

valoarea elementului matrice[1][2] = 2 ; 2 ; 2

adresa liniei 2 din "matrice" este 0x0012ff2c ; 0x0012ff2c ; 0x0012ff2c

adresa primului element de pe linia 2 este 0x0012ff2c ; 0x0012ff2c ; 0x0012ff2c

valoarea elementului matrice[2][0] = 0 ; 0 ; 0

adresa elementului matrice[2][1] = 0x0012ff30 ; 0x0012ff30 ; 0x0012ff30

valoarea elementului matrice[2][1] = 1 ; 1 ; 1

adresa elementului matrice[2][2] = 0x0012ff34 ; 0x0012ff34 ; 0x0012ff34

valoarea elementului matrice[2][2] = 2 ; 2 ; 2

Tipuri structurate de date: șiruri de caractere

Un șir de caractere este o succesiune de caractere terminată cu caracterul NULL.

În limbajul C/C++ nu există un tip de bază special pentru șirurile de caractere. Șirul de caractere este reprezentat ca tablou unidimensional (vector) de caractere terminat cu caracterul NULL.

Notă: caracterul NULL are codul ASCII 0 iar secvența escape corespunzătoare este \0.

Declararea unei variabile-șir de caractere se face astfel:

char <nume_sir>[nr_caractere];

unde:

– <nume_sir> – specifică numele simbolic al șirului de caractere;

– <nr_caractere> – indică numărul maxim de caractere din șir plus caracterul care reprezintă marcajul de sfârșit de șir (caracterul NULL) și trebuie să fie obligatoriu o expresie constantă.

Exemplu:

char sir[256];

Inițializarea unui șir de caractere la declarare

Inițializarea unui șir de caractere se poate face chiar de la declarare, cu o constantă-șir ca în exemple de mai jos:

char sir1[] = "Sir de caractere inițializat la declarare fără precizarea lungimii";

char sir2 [200] = "Sir de caractere inițializat la declarare cu precizarea lungimii";

În primul exemplu, dimensiunea șirului de caractere nu a fost specificată la declarare și atunci compilatorul de C determină automat dimensiunea șirului de caractere pe baza conținutului șirului declarat. Astfel, dimensiunea șirului este de 66 octeți (câte unul pentru fiecare caracter) plus un octet suplimentar pentru marcajul de sfârșit de șir, deci în total 67 de octeți (caractere).

În al doilea exemplu, dimensiunea șirului de caractere a fost specificată la declarare (200 de octeți). Compilatorul va atribui automat primilor 64 de octeți cele 64 caractere ale șirului "Șir de caractere inițializat la declarare cu precizarea lungimii” plus un octet suplimentar pentru marcajul de sfârșit de șir, deci în total 65 de octeți (caractere).

Crearea unui șir de caractere și controlul caracterului NULL la sfârșitul șirului

Pentru a crea un șir de caractere se declară un tablou unidimensional de caractere ca în exemplul de mai jos:

char sir[256];

Compilatorul de C/C++ va aloca o zonă de memorie contiguă de 256 octeți reprezentând un tablou unidimensional de caractere ale cărui elemente încep de la șir[0] până la șir[255].

Deoarece șirul poate conține mai puțin de 256 caractere este necesar să se plaseze caracterul NULL ca ultimul caracter al șirului. De obicei, limbajul C/C++ nu plasează automat caracterul NULL la sfârșitul șirului. De aceea, este responsabilitatea programului să asigure prezența caracterului NULL la sfârșitul unui șir care este creat și manevrat în program.

Observație:

Atunci când se lucrează cu șiruri de caractere, trebuie să ne asigurăm că am inclus corect caracterul NULL pentru a reprezenta sfârșitul șirului.

Programul următor (stocare_sir.cpp) definește un tablou de caractere de dimensiunea 256 octeți (caractere) și apoi atribuie primelor 52 de locații, literele mari și mici ale alfabetului. Apoi programul plasează caracterul NULL după litera z pentru a stabili sfârșitul șirului. Funcția printf va afișa apoi fiecare caracter al șirului până la caracterul NULL.

#include <stdio.h>

void main()

{char sir[256];

char sir1[] = "Sir de caractere initializat la declarare fara precizarea lungimii";

char sir2 [200] = "Sir de caractere initializat la declarare cu precizarea lungimii";

int i, j;

printf ("%s\n", sir1);

printf ("%s\n", sir2);

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

sir[i] = 'A' + i;

sir[i] = NULL;

printf ("Sirul de caractere contine %s\n", sir);

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

sir[i] = 'A' + i;

for (i = 26, j=0; i < 52 && j < 26; i++, j++)

sir[i] = 'a' + j;

sir[i] = NULL;

printf ("Sirul de caractere contine %s\n", sir);

printf ("Primele 30 de caractere ale sirului sunt %.30s\n", sir);}

Diferența între constanta ‘A’ și constanta “A”

Atunci când se lucrează cu caractere în limbajul C/C++, se poate utiliza codul ASCII al caracterului sau se poate plasa caracterul între apostrofuri, cum ar fi ‘A’.

Pe de altă parte, când se utilizează ghilimele, cum ar fi “A”, compilatorul de C/C++ crează un șir de caractere care conține litera specificată (sau literele) și termină șirul cu caracterul NULL. În figura de mai jos se ilustrează stocarea constantelor ‘A’ și “A”:

Prelucrarea șirurilor de caractere

Șirurile de caractere pot fi prelucrate în două moduri:

– la nivel de caracter – pot fi parcurse caracter cu caracter, ca un vector de caractere, după cum s-a prezentat în programul descris anterior;

– la nivel de structură – cu ajutorul funcțiilor existente în bibliotecile standard ale limbajului.

Pointeri și șiruri ca tablouri unidimensionale de caractere

Fiind un tablou unidimensional de caractere, numele șirului de caractere este o constantă-pointer care are ca valoare adresa primului caracter din șir.

Programul următor (lg_sir_pointer.cpp) calculează lungimea unui șir cu ajutorul unui pointer p, care inițial are ca valoare adresa de început a șirului. Lungimea șirului este diferența dintre valoarea pointerului p (care indică la sfârșit adresa caracterului NULL) și valoarea constantei-pointer sir (care indică adresa primului caracter din șir). Calculul lungimii șirului se face și în varianta parcurgerii obișnuite a elementelor tabloului de caractere care desemnează șirul și numărării acestora.

#include <stdio.h>

void main()

{char sir[100] = "Totul despre C/C++";

char *p;

int lg;

//determinarea lungimii sirului prin parcurgerea sirului pana la intalnirea marcajului

// de sfarsit de sir si numararea caracterele parcurse

for (lg = 0; sir[lg]; lg++);

printf("Lungimea sirului in prima variana este: %d\n", lg +1);

//determinarea lungimii sirului prin parcurgerea sirului cu ajutorul unui pointer p

// care initial are ca valoare adresa de inceput a sirului

for (p = sir; *p; p++);

lg = p – sir;

printf("Lungimea sirului in a doua varianata este: %d\n", lg+1);}

Inițializarea unui șir de caractere la declarare utilizând pointer

S-a arătat că, declararea unui șir de caractere și inițializarea lui la declarare se poate face ca în exemplul de mai jos:

char titlu[] = “Totul despre C/C++”;

Compilatorul de C/C++ atribuie variabilei titlu un pointer (o adresă) la primul caracter din șir.

Deoarece compilatorul alocă automat zona de memorie necesară și apoi lucrează cu pointerul către zona de memorie respectivă, programul poate utiliza un pointer cu tipul de bază char, și nu un tablou de caractere, după cum se observă mai jos:

char *titlu = “Totul despre C/C++”;

Tablouri (vectori) de șiruri de caractere

Tablourile de șiruri de caractere (vectori cu elemente șiruri de caractere) pot fi declarate ca tablouri de pointeri la șiruri de caractere.

Atunci când se crează un vector de șiruri de caractere, compilatorul păstrează pointeri la șirurile de caractere ale vectorului.

De exemplu, următoarea declarației:

char *litere[4] = {“AAA”, “BBB”, “CCC”, “DDD”};

Compilatorul stochează vectorul litere ca în figura de mai jos:

Compilatorul de C/C++ păstrează vectorul de șiruri de caractere litere ca pe un vector de pointeri către 4 șiruri de caractere.

Observație:

Când se declară un vector de șiruri de caractere, compilatorul nu adaugă caracterul NULL pentru a indica sfârșitul de vector de șiruri de caractere.

Ciclarea printr-un un vector de șiruri de caractere

Următorul program (zilele_sap_pointer.cpp) ciclează prin vectorul de șiruri de caracter săptămâna, care conține 7 pointeri la șirurile de caractere ce memorează numele zilelor săptămânii:

#include <stdio.h>

void main()

{char *saptamana[7] = {"Luni", "Marti", "Miercuri", "Joi",

"Vineri", "Sambata", "Duminica"};

int i;

// ciclare prin elementele vectorului de siruri de caractere

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

printf("saptamana[%d] este %s\n", i, saptamana[i]);}

Deci, vectorul șir de caractere poate fi declarat ca vector de pointeri.

Pentru a accesa vectorul de șiruri de caractere, declarat ca vector de pointeri, se poate utiliza un pointer la un pointer. Deci, pentru a accesa vectorul de pointeri, trebuie declarată o variabilă-pointer care indică adresa fiecărui șir din vectorul de șiruri de caractere (numită variabilă-pointer la pointer).

În exemplul următor se folosește declarația:

char **ptr_zi ;

în care ptr_zi reprezintă pointerul de referință la fiecare șir din vectorul de șir de caractere săptămâna (adică, acest pointer conține un pointer la fiecare șir de caractere din vector).

Asteriscul dublu (**), în acest caz, arată că ptr_zi este un pointer la un pointer la un șir de caractere (șir care este un element al vectorului de șiruri de caractere).

La începutul execuției programului, se atribuie pointerului ptr_zi adresa de început a vectorului de pointeri săptămâna (adresa șirului de caractere “luni”).

#include <stdio.h>

void main()

{char *saptamana[7] = {"Luni", "Marti", "Miercuri", "Joi",

"Vineri", "Sambata", "Duminica"};

char **ptr_zi;

int i;

ptr_zi = saptamana;

// ciclare prin pointer la pointer

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

printf("saptamana[%d] este %s\n", i, *ptr_zi++);}

Declararea constantelor și pointeri const

Când se declară o constantă, compilatorul va executa inițializarea constantei în același moment. După inițializare, de fiecare dată când se încearcă modificarea constantei, în cursul execuției programului, compilatorul va genera o eroare.

Sintaxa utilizată pentru declararea constantelor este:

const <tip> <nume_constanta> = <valoare>;

unde:

– <tip> – specifică tipul constantei;

– <nume_constanta> – numele simbolic al constantei;

– <valoare> – valoarea atribuită constantei.

Avantajul utilizării constantelor în locul unei macrodefiniții care se crează cu #define este acela că, se poate să se precizeze în mod explicit tipul valorii.

Exemple:

const int numar = 1001; const float pret = 39.95;

Următorul exemplu utilizează constanta dim_sir pentru a specifica dimensiunea unui șir de caractere:

const int dim_sir = 64;

char sir [dim_sir];

Pointeri const

Multe din prototipurile de funcții standard prezentate, deja, au parametrii de tip pointer în care apare și cuvântul cheie const.

Poziția în care se află cuvântul cheie const în declararea unui pointer are semnificație distinctă

.

De exemplu, declarațiile:

const int *p1;

int *const p2;

const int *const p3;

au următoarele semnificații:

– p1 este un pointer la o constantă de tip întreg; p1 păstrează adresa unei constante de tip întreg;

– p2 este un pointer constant la o valoare de tip întreg; p1 păstrează adresa unei variabile de tip întreg, dar această adresă primită inițial de pointer nu poate fi modificată;

– p3 este un pointer constant la o constantă de tip întreg; p3 păstrează adresa unei constante de tip întreg, dar aceasta adresă primită inițial de pointer nu poate fi modificată.

Citirea și afișarea șirurilor de caractere

Citirea unui șir de caractere în limbajul C se poate face cu ajutorul funcției scanf(), utilizând specificatorul de format %s.

De exemplu, următorul program (citeste_sir1.cpp) citește de la tastatură un șir de maxim 99 caractere (ultimul caracter fiind utilizat pentru caracterul NULL) exceptând caractere albe (spațiu, tab, newline):

#include <stdio.h>

void main()

{char sir[100];

scanf ("%s", sir);

printf ("sirul citit este: %s\n", sir);}

Notă:

1. Nu s-a transmis funcției scanf() adresa variabilei cu numele sir (&sir). Acest lucru se datorează faptului că numele oricărui vector (deci, implicit, și al unui șir de caractere) este adresa primului element din vector.

Observații:

1. Vor fi citite caracterele care încep cu primul caracter diferit de un caracter alb până la întâlnirea unui caracter alb. Deci, funcția scanf() nu permite citirea șirurilor de caractere care conțin caractere albe.

2. Dacă numărul de caractere citite este mai mare decât lungimea declarată a șirului, va rezulta eroare la execuția programului (care depinde de sistemul de operare și de compilatorul utilizat).

Citirea unui șir de caractere, care conține caractere albe, în limbajul C se poate face cu ajutorul funcției gets(), declarată în fisierul antet stdio.h.

Prototipul funcției gets() este:

char *gets(char *s);

Funcția gets() memorează în șirul s caracterele introduse de la tastatură, până la întâlnirea marcajului de sfârșit de linie (newline). Caracterul newline este citit și convertit automat în caracterul NULL.

De exemplu, pentru a citi un șir cu numele sir_car care, eventual, poate conține și caractere tab sau spații se apelează funcția gets() astfel:

gets (sir_car);

Citirea unui șir de caractere în limbajul C++

Citirea unui sir de caractere se poate face utilizând fluxul uzual de intrare cin >>. În acest caz, se vor citi în șir toate caracterele până la primul caracter <Enter>.

Citirea unui șir de caractere, care conține caractere albe, în limbajul C++ se poate face cu ajutorul funcțiilor cin.getline () și cin.get ().

Observație: Funcțiile getline() și get() sunt funcții membre ale unei clase speciale și pentru a putea fi utilizate trebuie să specificăm și fluxul de intrare (cin) din care citim datele.

Funcția getline () are următorul format:

getline (char *s, int n, char c=‘\n’);

unde:

– s – specifică șirul de caractere citit de la tastatură;

– n – numărul maxim de caractere care se pot citi;

– c – caracter delimitator; dacă nu apare, acest caracter este considerat implicit ca fiind ‘\n’).

Funcția getline () citește caractere de la tastatură până la întâlnirea caracterului delimitator sau până ce a citit exact n-1 caractere.

De exemplu, următorul program (getline.cpp) utilizează cin.getline() pentru a citi o linie de intrare de la tastatură:

#include <iostream.h>

void main()

{char sir[256];

cout << "Introduceti numele si prenumele si apasati Enter\n";

cin.getline(sir, sizeof(sir));

cout << sir;}

Funcția get (), utilizată pentru citirea șirurilor de caractere, se apelează la fel ca și funcția getline (). Efectul este asemănător. Diferența constă în faptul că getline () nu preia din fluxul de intrare caracterul delimitator, în timp ce get() preia din fluxul de intrare și caracterul delimitator.

Următorul program (functia_cin_get.cpp) utilizează cin.get() pentru a citi o linie de intrare de la tastatură:

#include <iostream.h>

void main()

{char sir[256];

cout << "Introduceti numele si prenumele si apasati Enter\n";

cin.get(sir, sizeof(sir));

cout << sir;}

Observație: La citirea unui șir de caractere prin modalitățile prezentate până acum, marcajul de sfârșit de șir (caracterul NULL) este automat plasat la sfârșitul șirului.

În cazul în care funcția get () se apelează fără nici un parametru, aceasta returnează un caracter citit de la intrare. Deci, funcția get () poate fi folosită pentru a citi caractere unul câte unul, astfel:

<caracter> = cin.get();

Următorul program (functia_cin_get1.cpp) atribuie șirului de caractere, cu numele str, caracterele citite la intrare până la, dar nu inclusiv, caracterul de linie nouă (newline). Se observă că în acest caz, marcajul de sfârșit de șir (caracterul NULL) trebuie plasat în cadrul programului.

#include <iostream.h>

#include <stdio.h>

void main(void)

{char str[256];

int i = 0;

while ((str[i] = cin.get()) != '\n')

i++;

str[i] = NULL;

cout << "Sirul citit este " << str;}

Afișarea unui șir de caractere în limbajul C se poate face cu ajutorul funcției printf(), utilizând specificatorul de format %s.

Funcția puts () declarată în fisierul antet stdio.h este folosită, de asemenea, pentru afișarea șirurilor de caractere.

Prototipul funcției puts () este:

int puts (const char *s);

Funcția puts () afișează caracterele șirului s până la întâlnirea caracterului NULL și apoi afișează un caracter newline. În caz de afișare cu succes, funcția returnează o valoare pozitivă; în caz contrar, returnează valoarea -1.

Deci, apelul:

puts ( sir );

este echivalent cu :

Printf ( “%s\n”, sir );

Afișarea unui șir de caractere în limbajul C++

Afișarea unui șir de caractere se poate face utilizând fluxul uzual de ieșire cout <<.

În acest caz, se vor afișa toate caracterele din șir până la întâlnirea primului marcaj de sfârșit de șir (\0).

De exemplu:

cout << “Se afiseaza un sir de caracter pana aici \0 de aici nu mai scrie”;

scrie pe ecran șirul până la întâlnirea caracterului \0.

Se poate utiliza, de asemenea, și funcția cout.put() pentru a scrie un caracter pe ecran.

Observație: Funcția put () este o funcție membră ale unei clase speciale și pentru a putea fi utilizată trebuie să specificăm și fluxul de ieșire (cout) în care scriem datele.

Deci, funcția put () poate fi folosită pentru a afișa caracterele dintr-un șir, unul câte unul, astfel:

cout.put (<caracter>);

Următorul program (functia_cout_put.cpp) utilizează funcția cout.put () pentru a afișa caracterele dintr-un șir, unul câte unul:

#include <iostream.h>

void main(void)

{

char *titlu = "Totul despre C/C++";

while (*titlu)

cout.put(*titlu++);

}

Lecția 5

Cuvinte importante:

– Funcții pentru lucru cu șirurile de caractere din bibliotecile standard ale limbajului C/C++

5.1. Funcții pentru lucru cu șirurile de caractere din bibliotecile standard ale limbajului C/C++

În fișierul antet string.h sunt declarate numeroase funcții de lucru cu șiruri de caractere.

1. Determinarea lungimii efective a unui șir de caractere se poate face prin folosirea funcției strlen(). Ea returnează numărul de caractere din șir. Funcția numără caracterele dintr-un șir până la caracterul NULL fără a-l număra și pe el.

Funcția strlen () este declarată astfel:

#include <string.h>

unsigned strlen ( const char *sir);

Următorul program (functia_strlen.cpp) ilustrează utilizarea funcției strlen ():

#include <stdio.h>

#include <string.h>

void main()

{char *titlu = "Totul despre C/C++";

printf("%s contine %d caractere\n", titlu, strlen(titlu));}

Următorul program (sir_palindrom.cpp) verifică dacă un șir de caractere este palindrom (un cuvânt poate fi citit în același fel în ambele direcții, ca de exemplu „madam” sau „123454321”). Pentru a face acest lucru, trebuie verificată simetria caracterelor fața de mijlocul șirului.

#include <stdio.h>

#include <conio.h>

#include <string.h>

void main(){

char sir[25];

int palindrom=1;

int i, n;

printf ("Introduceti sirul: ");

gets(sir);

n=strlen(sir);

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

if(sir[i] != sir[n-i-1]){

palindrom=0;

break;}

if(palindrom)

printf("Sirul este palindrom.");

else

printf("Sirul nu este palindrom. ");

getch();}

2. Copierea unui șir de caractere într-un alt șir de caractere se poate face cu ajutorul funcției strcpy(). Funcția copiază toate caracterele dintr-un șir (parametrul sursa) într-un alt șir (parametrul destintie), inclusiv caracterul NULL. Funcția returnează un pointer care conține adresa de început a șirului-destinație.

Funcția strcpy () este declarată astfel:

#include <string.h>

char *strcpy (char *destinatie, const char *sursa);

Următorul program (functia_strcpy.cpp) ilustrează utilizarea funcției strcpy ():

#include <stdio.h>

#include <string.h>

void main()

{char *titlu = "Totul despre C/C++";

char carte[128];

strcpy(carte, titlu);

printf("Numele cartii este: %s\n", carte);}

Observație: Spațiul de memorie alocat șirului destinatie trebuie să fie suficient de mare pentru a memora caracterele șirului sursa.

3. Transformarea unui șir de caractere în alt șir de caractere se poate face cu ajutorul funcției strncpy().

Funcția copiază primele maxim n caractere dintr-un șir (parametrul sursa) într-un alt șir (parametrul destinatie). Funcția returnează un pointer care conține adresa de început a șirului-destinație.

Funcția strncpy () este declarată astfel:

#include <string.h>

char *strncpy (char *destinatie, const char *sursa, unsigned n);

Observație:

1. Spațiul de memorie alocat șirului-destinație trebuie să fie suficient de mare pentru a memora toate caracterele șirului-sursă, dacă se dorește.

2. Dacă lungimea șirului-sursă este mai mică sau egală cu n, se copiază întreg șirul-sursă în șirul-destinație și la sfârșitul șirului-destinație se pune caracterul NULL.

Dacă lungimea șirului-sursă este mai mare decât n, atunci la sfârșitul șirului destinație nu va fi plasat caracterul NULL (deci, trebuie plasat prin programul propriu).

Următorul program (functia_strncpy.cpp) ilustrează utilizarea funcției strncpy ():

#include <stdio.h>

#include <iostream.h>

#include <string.h>

void main()

{char buffer[64];

char sir[256];

char sir1[256];

int i, nr;

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

buffer[i] = 'A' + i;

buffer[i] = NULL;

strncpy (sir, buffer, sizeof(buffer));

printf ("Sirul de caractere copiat este %s\nSirul sursa este %s\n", sir, buffer);

cout<<"Introduceti numarul de litere pe care doriti sa le copiati in noul sir ";

cin>>nr;

strncpy (sir1, buffer, nr);

sir1[nr] = NULL;

printf ("Sirul de caractere copiat este %s\nSirul sursa este %s\n", sir1, buffer);}

4. Adăugarea conținutului unui șir la alt șir de caractere (concatenarea a două șiruri de caractere) se poate face cu ajutorul funcției strcat().

Funcția copiază toate caracterele dintr-un șir (parametrul sursa) la sfârșitul altui șir (parametrul destinatie), incluzând caracterul NULL la sfârșitul șirului rezultat.

Funcția returnează un pointer care conține adresa de început a șirului obținut după concatenare (șirul-destinație).

Funcția strcat () este declarată astfel:

#include <string.h>

char *strcat (char *destinatie, const char *sursa);

Următorul program (functia_strcat.cpp) ilustrează utilizarea funcției strcat ():

#include <stdio.h>

#include <string.h>

void main()

{char sir1[128];

char sir2[128];

int i;

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

sir1[i] = 'A' + i;

sir1[i] = NULL;

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

sir2[i] = 'a' + i;

sir2[i] = NULL;

strcat (sir1, sir2);

printf ("Sirul de caractere concatenat contine %s\n", sir1);}

5. Adăugarea a n caractere dintr-un șir la un alt șir se poate face cu ajutorul funcției strncat().

Funcția adaugă primele n caractere dintr-un șir (parametrul sursa) la sfârșitul altui șir (parametrul destinatie), incluzând caracterul NULL la sfârșitul șirului rezultat.

Funcția returnează un pointer care conține adresa de început a șirului obținut după adăugarea celor n caractere (șirul-destinație).

Funcția strncat () este declarată astfel:

#include <string.h>

char *strncat (char *destinatie, const char *sursa, unsigned n);

Următorul program (functia_strncat.cpp) ilustrează utilizarea funcției strncat ():

#include <stdio.h>

#include <string.h>

void main()

{char calific[64] = "bine";

strncat(calific, " sau mai putin bine", 4);

printf("Studentii de la automatica invata %s?\n", calific);}

Observație: Dacă n precizează un număr de caractere mai mare decât numărul de caractere din șirul-sursă, funcția strncat() va copia toate caracterele până la sfârșitul șirului.

6. Compararea a două șiruri de caractere se poate face cu ajutorul funcției strcmp (). Funcția compară șirul de caractere sir1 cu șirul de caractere sir2 și returnează o valoare:

< 0 , dacă sir1 < sir2 (ordine alfabetică din dicționar)

= 0 , dacă sir1 == sir2 (sir1 și sir2 sunt egale)

> 0 , dacă sir1 > sir2 (ordine alfabetică din dicționar).

Funcția strcmp () este declarată astfel:

#include <string.h>

int strcmp (const char *sir1, const char *sir2);

Observație: La comparare se face distincție între literele mari și literele mici. Dacă se dorește o comparare care să nu facă distincție între majuscule și minuscule se poate utiliza funcția stricmp ().

Funcția stricmp () este declarată astfel:

#include <string.h>

int stricmp (const char *sir1, const char *sir2);

Următorul program (sortare_alfabetica.cpp) ilustrează utilizarea funcției strcmp () pentru ordonarea alfabetică a unui șir de nume de persoane a căror adrese de început sunt păstrat într-un vector de pointeri la șirurile de caractere denumit matr_sir :

#include <stdio.h>

#include <string.h>

#include <iostream.h>

void main()

{char *matr_sir[5] = {"Ionescu Florin ", "Andreescu Maria ",

"Popescu Virgil ", "Banciu Ion ",

"Florescu Teodor "} ;

char *temp_matr;

char **elem_matr;

int i, j;

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

printf("%s %p %p\n", matr_sir[i], &matr_sir[i], matr_sir[i]);

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

for (j=i+1; j<5; j++)

if (strcmp(matr_sir[i], matr_sir[j]) > 0)

{temp_matr = matr_sir[i];

matr_sir[i] = matr_sir[j];

matr_sir[j] = temp_matr;} //ordonez crescator sirurile de caractere

elem_matr = matr_sir ; //pointer la primul pointer din vectorul sir de caractere

cout << '\n' ;

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

printf("%s %p %p\n", *elem_matr++, elem_matr, *elem_matr);}

După execuția programului, pe ecran se afișează următorul rezultat:

Ionescu Florin 0012FF78 00409088

Andreescu Maria 0012FF7C 0040909D

Popescu Virgil 0012FF80 004090B2

Banciu Ion 0012FF84 004090C7

Florescu Teodor 0012FF88 004090DC

Andreescu Maria 0012FF78 0040909D

Banciu Ion 0012FF7C 004090C7

Florescu Teodor 0012FF80 004090DC

Ionescu Florin 0012FF84 00409088

Popescu Virgil 0012FF88 004090B2

Următorul program (verif_parola.cpp) ilustrează utilizarea funcției strcmp () pentru verificarea introducerii corecte de la tastatură a unei parole setată în program.

#include <stdio.h>

#include <conio.h>

#include <string.h>

void main(){

char parola[20]="Secret!";

char parola_u[20];

printf("Introduceti parola: \n");

gets(parola_u);

if(strcmp(parola, parola_u)==0)

printf("Este corecta");

else

printf("Parola incorecta");

getch();

}

7. Convertirea (transformarea) caracterelor unui șir în majuscule sau minuscule se poate face cu ajutorul funcțiilor strupr () și strlwr ().

Funcția strupr () transformă toate literele mici dintr-un șir în litere mari.

Funcția strupr () este declarată astfel:

#include <string.h>

char *strupr (char *sir);

Funcția strlwr () transformă toate literele mari dintr-un șir în litere mici.

Functia strlwr () este declarată astfel:

#include <string.h>

char *strlwr (char *sir);

Următorul program (functia_strncat.cpp) ilustrează utilizarea funcțiilor strupr () și strlwr ():

#include <iostream.h>

#include <string.h>

void main()

{

char *nume_u;

char *nume_l;

char nume[64];

cout <<"Introduceti un nume de persoana: ";

cin.getline (nume, sizeof(nume));

nume_u = strupr(nume);

cout << "Numele convertit in majuscule este: " << nume_u << '\n';

nume_l = strlwr (nume);

cout << "Numele convertit in minuscule este: " << nume_l << '\n';

}

8. Căutarea primei apariții a unui caracter într-un șir se poate face cu ajutorul funcției strchr ().

Funcția strchr () caută prima apariție a unui caracter (parametrul caracter) într-un șir (parametrul sir) și returnează un pointer care are ca valoare adresa primului caracter din șir egal cu parametrul caracter (dacă un astfel de caracter există) sau care are ca valoare NULL (dacă caracterul nu apare în șir) (pointerul nu conține adresa nici unei zone de memorie).

Funcția strchr () este declarată astfel:

#include <string.h>

chr *strchr (const char *sir, int caracter);

Următorul program (functia_strchr.cpp) ilustrează utilizarea funcției strchr (). Programul calculează poziția pe care o ocupă în șir prima apariție a caracterului căutat, dacă este găsit în șir sau afișează un mesaj, dacă acest caracter nu este găsit în șir.

#include <stdio.h>

#include <iostream.h>

#include <string.h>

void main()

{char sir[64];

char *ptr;

char litera;

int index;

cout << "Introduceti sirul de caractere "; cin.getline ( sir, sizeof(sir));

cout << "Introduceti caracterul de cautat "; cin >> litera;

ptr = strchr(sir, litera);

if (ptr)

{index = (ptr – sir) + 1;

printf("Prima aparitie a caracterului \"%c\" este la pozitia %d in sir", *ptr,

index);}

else

{printf ("%p\n", ptr);

printf("Caracter negasit");}}

Următorul program (functia_strchr1.cpp) ilustrează utilizarea funcției strchr (). Programul citește de la tastatură o frază și se dorește să se afle câte cuvinte are fraza.

#include<iostream.h>

#include<conio.h>

#include<string.h>

void main()

{

int i,k=0;

char v[120], s[]=";.!?, :";

cout<<"Introduceti sirul : ";

cin.getline(v,120); //citeste sirul

for(i=0;i<strlen(v);i++) //parcurege sirul

if(strchr(s,v[i])!=NULL) //cauta in v ce este in s

k++;

cout<<"Am gasit "<<(k+1)<<" cuvinte.";

getch();

}

9. Căutarea ultimei apariții a unui caracter într-un șir se poate face cu ajutorul funcției strrchr ().

Funcția strrchr () caută ultima apariție a unui caracter (parametrul caracter) într-un șir (parametrul sir) și returnează un pointer care are ca valoare adresa ultimului caracter din șir egal cu parametrul caracter (dacă un astfel de caracter există) sau care are ca valoare NULL (dacă caracterul nu apare în șir) (pointerul nu conține adresa nici unei zone de memorie).

Funcția strrchr () este declarată astfel:

#include <string.h>

chr *strrchr (const char *sir, int caracter);

Următorul program (functia_strrchr.cpp) ilustrează utilizarea funcției strrchr (). Programul calculează poziția pe care o ocupă în șir ultima apariție a caracterului căutat, dacă este găsit în șir sau afișează un mesaj, dacă acest caracter nu este găsit în șir.

#include <stdio.h>

#include <iostream.h>

#include <string.h>

void main(void)

{char sir[64];

char *ptr;

char litera;

long int index;

cout << "Introduceti sirul de caractere "; cin.getline ( sir, sizeof(sir));

cout << "Introduceti caracterul de cautat "; cin >> litera;

ptr = strrchr(sir, litera);

if (ptr)

{index = (ptr – sir) + 1;

printf("Ultima aparitie a caracterului \"%c\" este la pozitia %d in sir\n", *ptr,

index);}

else

{printf ("%p\n", ptr);

printf("Caracter negasit\n");}}

Nota: Pentru a număra aparițiile unui caracter într-un șir, se poate folosi secvența de program (nr_aparitii_caracter.cpp) următoare:

#include <stdio.h>

#include <iostream.h>

#include <string.h>

void main(void)

{char sir[64];

int nr_aparitii;

char litera;

char *p;

cout << "Introduceti sirul de caractere "; cin.getline (sir, sizeof(sir));

cout << "Introduceti caracterul de cautat "; cin >> litera;

nr_aparitii = 0;

p = sir;

while (*p)

{if (*p == litera)

nr_aparitii++;

p++;}

printf("Numarul de aparitii ale caracterului \"%c\" este %d in sir\n",

litera,nr_aparitii);}

10. Atribuirea unui caracter specificat unui șir întreg de caractere se poate face cu ajutorul funcției strset ().

Funcția strset () atribuie un caracter (parametrul caracter) specificat fiecărei locații a unui șir (parametrul sir) până când întâlnește caracterul NULL.

Funcția strset () este declarată astfel:

#include <string.h>

chr *strset (char *sir, int caracter);

Următorul program (functia_strset.cpp) ilustrează utilizarea funcției strset ():

#include <stdio.h>

#include <iostream.h>

#include <string.h>

void main(void)

{char sir[64] = "Sirul de umplut cu un caracter oarecare";

char litera;

cout << "Introduceti caracterul pentru umplerea sirului "; cin >> litera;

printf("Sirul umplut cu caracterul \"%c\" este %s \n", litera, strset(sir, litera));}

11. Localizarea unui subșir într-un șir de caractere se poate face cu ajutorul funcției strstr ().

Funcția strstr () determină prima apariție a unui subșir (parametrul subsir) într-un șir (parametrul sir) și returnează un pointer către începutul primei apariții a subșirului în șir sau care are valoarea NULL (dacă subșirul nu apare în șir).

Funcția strstr () este declarată astfel:

#include <string.h>

chr *strstr (const char *sir, const char *subsir);

Următorul program (functia_strstr.cpp) ilustrează utilizarea funcției strstr ():

#include <stdio.h>

#include <iostream.h>

#include <string.h>

void main()

{char sir[64];

char subsir [32];

char *ptr;

int index;

cout << "Introduceti sirul de caractere ";

cin.getline ( sir, sizeof(sir));

cout << "Introduceti subsirul de cautat ";

cin.getline (subsir, sizeof(subsir));

ptr = strstr(sir, subsir);

if (ptr)

{index = (ptr – sir) + 1;

printf("Prima aparitie a subsirului \"%s\" este la pozitia %d in sir\n", subsir, index);}

else

{printf ("%p\n", ptr);

printf("Subsir negasit\n");}

printf ("Subsirul %s %s in sirul %s " , subsir, (strstr (sir, subsir)) ? "apare " : "nu

apare", sir);}

12. Separarea subșirurilor delimitate de separatori (; – ! ? + – / * # etc.) dintr-un șir de caractere se poate face cu ajutorul funcției strtok ().

Funcția strtok () permite extragerea succesivă a subșirurilor, delimitate de separatori, dintr-un șir de caractere.

Funcția strtok () este declarată astfel:

#include <string.h>

chr *strtok (char *sir, const char *separatori);

unde:

– sir – specifică șirul de caractere din care se vor extrage separatorii;

– separatorii – depind de problema concretă și sunt memorați într-un șir de caractere.

Apelul funcției strtok () este diferit în cadrul aceluiași program astfel:

1. La primul apel, funcția se folosește sub forma:

strtok (sir, separatori);

iar funcția returnează adresa de început a primului subșir din șirul dat de parametrul sir. După acest prim apel, șirul este modificat în sensul că primul separator de după primul subșir este înlocuit de caracterul NULL. Dacă șirul este vid sau conține numai separatori, atunci funcția returnează un pointer cu valoarea NULL.

2. Se apelează din nou funcția strtok (), pentru extragerea succesivă a celorlalte subșiruri din șir, dar, în acest caz, primul parametru al funcției trebuie să fie valoarea NULL (altfel funcția returnează tot primul subșir din șir):

strtok (NULL, separatori);

și, astfel, funcția returnează adresa de început a următorului subșir din șir (sau valoarea NULL, dacă în șir nu mai există subșiruri delimitate de separatori). După fiecare apel, primul separator de după subșirul identificat este înlocuit de caracterul NULL.

Următorul program (functia_strtok.cpp) efectuează separarea unor subșiruri, delimitate de separatori, dintr-un șir de caractere, folosind apeluri repetate ale funcției strtok ():

#include <iostream.h>

#include <string.h>

void main()

{char sir[128];

char separatori [32];

char *p;

int i = 0;

cout << "Introduceti sirul de caractere ce contine separatori pentru delimitare\n";

cin.getline ( sir, sizeof(sir));

cout << "Introduceti separatorii\n";

cin.getline (separatori, sizeof(separatori));

p = strtok(sir, separatori);

while (p)

{i++;

cout << "Subsirul nr. " << i << " este ";

cout << "\"" <<p <<"\"" << endl;

p = strtok (NULL, separatori);

}}

După execuția programului pe ecran se afișează:

Introduceti sirul de caractere ce contine separatori pentru delimitare

Ionescu Florin #23# student# UTCB

Introduceti separatorii

#

Subsirul nr. 1 este "Ionescu Florin "

Subsirul nr. 2 este "23"

Subsirul nr. 3 este " student"

Subsirul nr. 4 este " UTCB“

13. Funcțiile de conversie a unui număr într-un șir de caractere

Funcțiile care realizează conversia unui număr întreg într-un șir de caractere sunt declarate în fișierul antet stdlib.h astfel:

char *itoa (int nr, char *sir, int baza); – convertește un număr de tip int;

char *ltoa (long nr, char *sir, int baza); – convertește un număr de tip long;

char *ultoa (unsigned nr, char *sir, int baza); – convertește un număr de tip unsigned long.

unde:

– nr – numărul reprezentat în baza de numerație dată de parametrul baza (2baza 36).

Următorul program (functie_itoa.cpp) convertește un număr reprezentat într-o bază oarecare într-un șir de caractere care va conține reprezentarea numărului în baza specificată la intrare:

#include <iostream.h>

#include <stdlib.h>

void main()

{char sir[256];

char *p;

int numar = 0;

int baza = 0;

cout << "Introduceti numarul de convertit in sir\n";

cin >> numar;

cout << "Introduceti baza de numeratie\n";

cin >> baza;

p = itoa (numar, sir, baza);

cout << "Numarul " << numar <<" convertit in sir este " << p << endl;}

După execuția programului, pe ecran se afișează:

Introduceti numarul de convertit in sir

197

Introduceti baza de numeratie

2

Numarul 197 convertit in sir este 11000101

14. Funcții de conversie a unui șir de caractere ce conține numere într-un număr de un tip predefinit

Funcțiile care realizează conversia unui șir de caractere ce conține numere într-un număr de un tip predefinit sunt declarate în fișierul antet stdlib.h astfel:

int atoi (char *sir); – convertește un șir la un număr de tip int;

long atol (char *sir); – convertește un șir la un număr de tip long;

double atof (char *sir); – convertește un șir la un număr de tip float;

double strtod (char *sir); – convertește un șir la un număr de tip double.

Observație: Dacă o funcție nu poate să convertească șirul de caractere într-o valoare numerică, funcția va returna valoarea 0.

Următorul program (functia_atoi.cpp) convertește un șir de caractere într-un număr de tip întreg:

#include <iostream.h>

#include <stdlib.h>

void main()

{char sir[256];

char *p;

int numar = 0;

int baza = 0;

cout << "Introduceti numarul de convertit in sir\n";

cin >> numar;

cout << "Introduceti baza de numeratie\n";

cin >> baza;

p = itoa (numar, sir, baza);

cout << "Numarul " << numar <<" convertit in sir este " << p << endl;

numar = atoi (sir);

if (numar)

cout <<"Sirul " << p << " convertit in numar este " << numar;

else

cout << "Sirul nu poate convertit intr-o valoare numerica";}

15. Testarea unui caracter pentru a stabili dacă este alfanumeric

Un caracter alfanumeric este ori o literă ori o cifră.

Fișierul antet ctype.h conține funcția numită isalnum () care permite testarea unui caracter pentru a stabili dacă este alfanumeric.

Funcția este declarată astfel:

int isalnum (int caracter);

Funcția examinează un caracter și returnează valoarea diferită de zero dacă este o literă sau o cifră și valoarea 0 dacă nu este.

Următorul program (functia_isalnum.cpp) testează dacă o literă sau o cifră este caracter alfanumeric:

#include <iostream.h>

#include <ctype.h>

void main()

{char car_alfa;

cout << “Introduceti un caracteralfa numeric\n";

cin >> car_alfa;

if (isalnum (car_alfa))

cout <<"Caracterul "<< car_alfa << " este litera sau cifra" ;

else

cout << "Caracterul "<< car_alfa << " nu este nici litera nici cifra";}

17. Testarea unui caracter pentru a stabili dacă este o literă

Fișierul antet ctype.h conține funcția numită isalpha () care permite testarea unui caracter pentru a stabili dacă este o literă.

Funcția este declarată astfel:

int isalpha (int caracter);

Funcția examinează un caracter și returnează valoarea diferită de zero dacă este o literă și valoarea 0 dacă nu este.

Următorul program (functia_isalpha.cpp) testează dacă un caracter este o literă sau nu:

#include <iostream.h>

#include <ctype.h>

void main()

{char car_alfa;

cout << "Introduceti un caracter alfanumeric\n";

cin >> car_alfa;

if (isalpha (car_alfa))

cout <<"Caracterul "<< car_alfa << " este litera" ;

else

cout << "Caracterul "<< car_alfa << " nu este litera";}

18. Testarea unui caracter pentru a stabili dacă este o cifră

Fișierul antet ctype.h conține funcția numită isdigit () care permite testarea unui caracter pentru a stabili dacă este o cifră.

Funcția este declarată astfel:

int isdigit (int caracter);

Funcția examinează un caracter și returnează valoarea diferită de zero dacă este un număr și valoarea 0 dacă nu este.

Următorul program (functia_isdigit.cpp) testează dacă un caracter este un număr sau nu:

#include <iostream.h>

#include <ctype.h>

void main()

{char car_alfa;

cout << "Introduceti un caracter alfanumeric\n";

cin >> car_alfa;

if (isdigit (car_alfa))

cout <<"Caracterul "<< car_alfa << " este numar" ;

else

cout << "Caracterul "<< car_alfa << " nu este numar";}

19. Testarea unui caracter pentru a stabili dacă este majusculă sau minusculă

Fișierul antet ctype.h conține funcțiile numite isupper () (pentru test majusculă) și islower () (pentru test minusculă) permit testarea unui caracter pentru a stabili dacă este majusculă sau minusculă.

Funcțiile sunt declarate astfel:

int isupper (int caracter);

int islower (int caracter);

Funcțiile examinează un caracter și returnează valoarea diferită de zero dacă este majusculă (isupper) sau minusculă (islower) și valoarea 0 dacă nu este.

20. Testarea unui caracter pentru a stabili dacă este semn de punctuație

În cărți, semnele de punctuație sunt virgula, punctul și virgula, punctul, semnul întrebării și așa mai departe. În schimb, în limbajul C/C++ se consideră ca semn de punctuație orice caracter grafic ASCII (tipăribil) care nu este alfanumeric.

Fișierul antet ctype.h conține funcția numită ispunct () care permite testarea unui caracter pentru a stabili dacă este un semn de punctuație.

Funcția este declarată astfel:

int ispunct (int caracter);

Funcția examinează un caracter și returnează valoarea diferită de zero dacă este semn de punctuație si valoarea 0 dacă nu este.

Următorul program (functia_ispunct.cpp) testează dacă un caracter este un semn de punctuație sau nu:

#include <iostream.h>

#include <ctype.h>

void main()

{

char car_alfa;

cout << "Introduceti un caracter\n";

cin >> car_alfa;

if (ispunct (car_alfa))

cout <<"Caracterul "<< car_alfa << " este semn de punctuatie" ;

else

cout << "Caracterul "<< car_alfa << " nu este semn de punctuatie";

}

21. Testarea unui caracter pentru a stabili dacă este spațiu alb

Termenul spatiu alb se referă la următoarele caractere: spațiu, tab, retur de car, linie nouă.

Fișierul antet ctype.h conține funcția numită isspace () permite testarea unui caracter pentru a stabili dacă este un caracter spatiu alb.

Funcția este declarată astfel:

int isspace (int caracter);

Funcția examinează un caracter și returnează valoarea diferită de zero dacă este un caracter spatiu alb și valoarea 0 dacă nu este.

22. Convertirea unui caracter în majusculă sau minusculă

Fișierul antet ctype.h conține funcțiile numite toupper () și tolower () care permit conversia unui caracter în majusculă, și, respectiv, în minusculă.

Funcțiile sunt declarate astfel:

int toupper (int caracter);

int tolower (int caracter);

Următorul program (functia_toupper.cpp) utilizează funcția toupper() pentru a transforma în litere mari opțiunile unui meniu (indiferent dacă literele opțiunilor meniului au fost introduse cu litere mari sau mici) care realizează:

– verificarea dacă un număr este par;

– calcularea numerelor pare dintr-un interval;

– ieșirea din program.

#include <iostream.h>

#include <ctype.h>

#include <stdio.h>

void main ()

{char litera;

int x, a, b, nr;

litera = ' ';

cout<<"A Verifica daca un numar este par/impar"<<"\n";

cout<<"B Calcul numere pare intr-un interval [a, b]"<<"\n";

cout<<"E Iesire"<<"\n";

cout<<"Alegeti una din literele meniului:" ;

cin >> litera;

switch (toupper(litera))

{case 'A': cout <<"Introduceti un numar: "; cin>>x;

if (x%2) printf("%d este impar\n", x);

else printf ("%d este par\n", x); break;

case 'B': printf("Introduceti intervalul de numere:"); cin >> a >> b;

if (a<=b)

{nr=(b-a+1)/2;

if (a%2==0 && b%2==0) nr++;

printf("Numarul de numere pare din intervalul [%d, %d] este %d\\n", a, b, nr);}

else printf ("Interval de numere introdus eronat\n"); break;

case 'E': cout<<"S-a iesit din program"<<"\n"; break;

default : cout<<"Nu s-a ales litera corespunzatoare meniului"<<"\n";}}

Lecția 6

Cuvinte importante:

– tipul de date struct: crearea tipului și declararea variabilelor de tip struct; inițializarea și accesarea unui membru al structurii;

– utilizarea unei structuri imbricate;

– citirea și afișarea variabilelor de tip struct;

– declararea unui tablou de structuri și accesarea unui membru al tabloului;

– Pointeri și structuri: pointeri la o structură, membrii de tip pointer la o structură;

– tipul de date union;

– tipul de date enum;

6.1. Tipul de date struct

Tipul de date struct definește o structură ce conține date de tipuri diferite reunite sub un nume comun. Acestă structură mai este cunoscută și sub denumirea de articol sau înregistrare.

Datele înglobate într-o structură sunt denumite membrii sau câmpuri. Fiecare membru are un nume distinct, care este utilizat pentru a identifica membrul respectiv.

Crearea unui tip de date struct – șablon pentru declarări de variabile de tipul struct

Declararea unui tip struct are următoarea sintaxă:

struct <nume_tip_structura>

{<declaratie_membru_1>;

<declaratie_membru_2>;

<declaratie_membru_n>;};

unde:

– <nume_tip_structura> – specifică numele dat de programator tipului de date struct;

– <declaratie_membru_1>, <declaratie_membru_2>,…, <declaratie_membru_n> – declarație, obișnuită, de tip pentru membrii structurii.

De exemplu, programul poate crea un tip de date struct, cum se arată în continuare:

struct Elev

{char nume_elev [40];

float nota_r, nota_m, nota_ig, media_gen;};

În acest exemplu, numele tipului de dată struct este Elev.

Programatorul de C/C++ se referă la numele tipului struct ca la un nume generic. Acest nume generic al structurii poate fi utilizat în program pentru a declara variabile de acest tip.

Declararea unei variabile de tip struct

Declararea unei variabile de tip struct se poate face în două moduri:

a) În declarația tipului struct, cu numele <nume_tip_structura>, astfel:

struct <nume_tip_structura>

{<declaratie_membru_1>;

<declaratie_membru_2>;

<declaratie_membru_n>;} <lista_variabile>; unde:

– <lista_variabile> – specifică numele variabilelor de tipul struct despărțite prin virgulă.

De exemplu, declarația:

struct Elev

{char nume_elev [40];

float nota_r, nota_m, nota_ig, media_gen;} elev_examen;

crează tipul de date struct, cu numele Elev, și declară variabila, de tip Elev, care are numele elev_examen.

b) După crearea tipului de date struct cu numele <nume_tip_structura>, într-o instrucțiune de declarare distinctă, astfel:

struct <nume_tip_structura> <lista_variabile>; // pentru limbajul C

sau,

<nume_tip_structura> <lista_variabile>;// pentru limbajul C++.

De exemplu, declarația:

struct Elev

{char nume_elev [40];

float nota_r, nota_m, nota_ig, media_gen;};

crează tipul de date struct, cu numele Elev.

Ulterior, într-o instrucțiune de declarare distinctă, se declară variabila de tip struct Elev, care are numele elev_examen.

struct Elev elev_examen; // pentru limbajul C

sau,

Elev elev_examen;

Notă: Atunci când se declară variabile de tip struct care urmează imediat după definirea structurii, nu este necesar să se specifice un nume generic. Dar, tipul fiind anonim, nu îl mai putem utiliza în declarații de variabile ulterioare.

De exemplu, următoarea declarație struct declară două variabile de tip structură fără specificarea unui nume generic pentru structură:

struct

{int tip_fig_geometrica;

float raza;

float aria;

float perimetru;} triunghi, cerc;

Referirea la un membru (accesarea unui membru) al unei structuri

Referirea la un membru al unei structuri se realizează specificând numele variabilei de tip struct și numele membrului respectiv, separate prin punct (“.”), astfel:

<variabila_structura>.membru;

De exemplu, pentru a accesa nume_elev ca membru al variabilei de tip struct Elev, cu numele elev_examen, se folosește notația: elev_examen.nume_elev

Sau

pentru a accesa membrul aria al variabilei de tip struct (anonim) cu numele triunghi, se folosește notația: triunghi.aria

Inițializarea unei structuri la declarare

La declararea unei variabile de tip struct, aceasta poate fi inițializată. La inițializare se precizează, în ordine, valorile ce vor fi atribuite membrilor structurii. Valorile sunt despărțite prin virgulă, corespund ca tip cu membrii structurii si sunt încadrate între paranteze acolade. Numărul de valori precizate la initializare poate fi egal cu numărul de membrii ai structurii sau mai mic (caz în care numai primele câmpuri vor fi inițializate).

De exemplu, următorul program (initstru.cpp) declară și inițializează o structură cu numele Schita:

#include <stdio.h>

void main(void)

{struct Schita

{int tip_fig_geometrica;

float raza;

float aria;

float perimetru;};

struct Schita cerc = {0, 5.0, 78.37, 31.42};

printf("cerc.tip_fig_geometrica %d\n", cerc.tip_fig_geometrica);

printf("cerc.raza %f\ncerc.aria %f\ncerc.perimetru %f\n",

cerc.raza, cerc.aria, cerc.perimetru);}

Utilizarea unei structuri imbricate

Într-o structură se pot include membrii de orice tip (int, float și așa mai departe), precum și membri care sunt ei înșiși structuri.

De exemplu, următoarea declarare de structura de tip Angajat include o structură de tip Data care conține data calendaristică a angajării:

struct Angajat

{

char nume[64];

int varsta;

struct Date

{

int zi;

int luna;

int an;

} date_angaj;

int categ_salar;

float salariu;

} noi_angajati;

Pentru a accesa membrii unei structuri imbricate, se utilizează semnul punct (“.”), mai întâi pentru a specifica structura imbricată și apoi pentru a specifica membrul dorit.

De exemplu:

noi_angajati.date_angaj.luna = 12

Un alt exemplu de structuri imbricate

Tipul-structură cu numele angjat conține un alt tip-structura pers așa după cum este prezentat mai jos.

struct pers {

char nume[20];

int varsta;

};

struct angajat {

struct pers p;

long salariu;

int vechime;

};

……….

Accesul la membrii acestei structuri imbricate este:

struct angajat a;

a.p.nume=“Ana Gheorghe”;

a.p.varsta=25;

a.salariu=1500;

a.vechime=3;

6.2 Asocierea unui nume pentru un tip de date

Tipurile de date predefinite ale limbajului (int, char, float etc) se identifică printr-un nume. De asemenea, unui tip struct i se poate asocia un nume.

Programatorul poate asocia unui tip de date un nume, indiferent dacă acesta este un tip predefinit sau definit de el. Pentru aceasta se folosește instructiunea typedef.

Sintaxa instrucțiunii este:

typedef <descriere_tip> <nume_tip>; unde:

– <descrire_tip> – specifică o descrire a tipului;

– <nume_tip> – specifică numele tipului de date atribuit descrierii tipului.

Această instrucțiune crează tipuri sinonime cu cele deja predefinite sau definite de utilizator și pot fi utilizate în declarații de variabile, mărind claritatea programului

Exemple:

1. Asocierea numelui REAL pentru tipul predefinit float:

typedef float REAL;

2. Asocierea numelui INTREG pentru tipul predefinit int:

typedef int INTREG;

3. Asocierea numelui TSchita pentru tipul struct Schita:

typedef struct Schita

{int tip_fig_geometrica;

float raza;

float aria;

float perimetru;} TSchita;

TSchita cerc = {0, 5.0, 78.37, 31.42};

4. Asocierea numelui complex unui tip struct (structura anonimă) care conține doi membri, partea reală și partea imaginară a numărului complex:

typedef struct

{double x, y;

} complex;

complex z;

z.x=5.67;

z.y=-7.55;

Citirea și afișarea variabilelor de tip struct

Când se efectuează operații de I/O, la ecran sau la tastatură, cu o variabilă de tip structură, trebuie să se efectueze operațiile de I/O membru cu membru, deci nu se poate lucra cu întreaga structură.

Operații cu structuri

Singura operație care se poate realiza la nivel de structură este atribuirea. Adică, unei variabile de tip struct i se poate atribui o alta variabilă de același tip struct.

De exemplu, se poate declara o variabilă de tip struct Elev cu numele elev_clasa și să o inițializăm cu valoarea variabilei elev_examen, astfel:

struct Elev elev_clasa;

elev_clasa = elev_examen;

Declararea unui vector de structuri

Limbajul C/C++ permite declararea unui vector de un tip specificat de structură.

De exemplu, următoarea declarare crează un tablou de tip struct angajat, denumit personal, care poate să păstreze informații despre 100 de angajați:

struct pers {

char nume[20];

int varsta;

};

struct angajat {

struct pers p;

long salariu;

int vechime;

};

……….

struct angajat personal[100];

Pentru a accesa membrul nume al primului element din vectorul personal, se va folosi notația:

personal[0].p.nume=“Ionescu Mioara”

Următorul program (examen.cpp) calculează pentru fiecare elev media generală obținută la examen pentru trei discipline (matematică, româna, istorie/geografie), afișează numele și prenumele elevilor în ordine alfabetică și mediile obținute de aceștia. De la tastatură se citesc numărul de elevi (n <=20) și pentru fiecare elev se citesc numele, prenumele și notele obținute la cele trei discipline. Programul ilustrează modul de folosire a tipului de dată struct și a funcției strcmp() pentru ordonarea alfabetică a elevilor.

#include <iostream.h>

#include <string.h>

void main()

{struct Elev

{ char nume_elev [40];

float nota_r, nota_m, nota_ig, media_gen;};

Elev elev_examen[20], elev_temp;

int n, i, j;

cout <<"Introduceti numarul de elevi "; cin >> n;

cout << "Introduceti informatiile despre elevi\n";

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

{ cout <<"Numele si prenumele: "; cin.get();

cin.getline (elev_examen[i].nume_elev, 40);

cout << "Limba romana: "; cin >> elev_examen[i].nota_r;

cout << "Matematica: "; cin >> elev_examen[i].nota_m;

cout << "Istorie/Geografie: "; cin >> elev_examen[i].nota_ig;}

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

elev_examen[i].media_gen = (elev_examen[i].nota_r + elev_examen[i].nota_m

+ elev_examen[i].nota_ig)/3;

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

for (j=i+1; j<n; j++)

if (strcmp(elev_examen[i].nume_elev, elev_examen[j].nume_elev) > 0)

{elev_temp= elev_examen[i];

elev_examen[i] = elev_examen[j];

elev_examen[j] = elev_temp;}

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

cout<<elev_examen[i].nume_elev<<"\t "<<elev_examen[i].media_gen <<endl;

}

6.3 Pointeri și structuri

A. Pointeri la structuri care nu au membrii pointeri

Pointerii simpli la structuri se declară la fel ca pointerii la tipurile de bază.

De exemplu:

struct persoana{

char nume[20];

int varsta;

int salariu;

};

struct persoana a = {“Ionescu Maria”,25,2000 };

struct persoana *p;

p=&a; //atribuie adresa variabilei a pentru pointerul p

Pentru accesul la membrii variabilei pointer de tip structură se folosesc două formate:

a) Formatul cu operatorul de dereferențiere asterisc (*), astfel:

(*pointer).membru = valoare; unde: pointer este o variabilă pointer de tip structură.

Compilatorul de C/C++ începe cu parantezele, obținând mai întâi locația structurii. Apoi, el adaugă, la adresa, deplasamentul membrului specificat.

b) Formatul ce folosește operatorul de selecție indirectă ->, astfel:

pointer -> membru = valoare;

De exemplu, folosind declarația din exemplul anterior:

struct persoana{

char nume[20];

int varsta;

int salariu;

};

struct persoana *p;

(*p).nume=“Ionescu Maria”;

(*p).varsta=20;

(*p).salariu=2000;

Următorul program (pointer_structuri.cpp) afișează la ecran informații despre angajații unei firme. Informațiile despre angajați se citesc de la tastatură. Programul folosește tipul struct persoana și pointerul p de tip persoana , pentru citirea datelor de la tastatură și stocarea membrilor structurii. Tabloul de structuri a de tip persoana este folosit pentru afișarea datelor la ecran.

#include <iostream.h>

#include <conio.h>

void main()

{struct persoana{

char nume[20];

int varsta;

int salariu;};

struct persoana a[10]; // tabloul de structuri de tip persoana

struct persoana *p; //pointer catre date de tip persoana

int i,nr_ang;

cout<<"Nr. angajati:"; cin>>nr_ang;

cout << "Introduceti informatiile despre angajati\n";

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

{ p=&a[i]; // atribuirea adresei elementului i din tabloul de structuri

cout<<"Nume angajat:"; cin.get();

cin.getline((*p).nume,20); // accesul la membrii structurii cu pointerul p

cout<<"Varsta angajat:"; cin>>(*p).varsta;

cout<<"Salariu angajat:"; cin>>(*p).salariu;

}

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

cout<<a[i].nume<<"\t"<<a[i].varsta<<"\t"<<a[i].salariu<<endl;

getch(); }

Următorul program (apart_cerc_struct.cpp) este un exemplu de folosire a structurilor imbricate.

Se citesc de la tastatura n cercuri date prin coordonatele centrului și al razei ca numere reale de tip float. De asemenea se citește de la tastatură coordonatele unui punct în plan. Programul stabilește la care dintre cercuri aparține punctul dat.

Reamintim că distanța dintre două puncte A și B din plan se calculează cu formula:

Un punct din plan aparține unui cerc dacă distanța dintre centrul cercului și punct este mai mică decât raza cercului.

#include <iostream.h>

#include <iostream.h>

#include <conio.h>

void main() {

struct Spunct {

float x;

float y;} ;

struct Scerc{

struct Spunct centru;

float raza;}; // structura imbricata

int i, n;

float distanta;

Scerc c[20]; //tablou de structuri de tip Scerc

Spunct p;

cout<<"Introduceti numarul de cercuri ";

cin>>n;

cout<<"Introduceti caracteristici cerc:"<<endl;

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

cout<<"Cercul "<<i+1<<":"<<endl;

cout<<"Centrul:";

cin>>c[i].centru.x>>c[i].centru.y;

cout<<"Raza:";

cin>>c[i].raza;

}

cout<<"Introduceti punctul pentru verificare apartenenta: ";

cin>>p.x>>p.y;

cout <<endl;

cout<<" Rezultat "<<endl;

cout<<"Punctul ("<<p.x<<", "<<p.y<<")"<<"apartine cercurilor:"<<endl;

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

distanta = sqrt(pow((c[i].centru.x-p.x), 2)+pow((c[i].centru.y-p.y), 2));

if ((distanta <c[i].raza)) {

cout<<i+1<<" C( "<<c[i].centru.x<<", ";

cout<<c[i].centru.y<<", "<<c[i].raza<<" )"<<endl;

}

}

getch();

}

După execuția programului pe ecran se afișează:

Introduceti numarul de cercuri 3

Introduceti caracteristici cerc:

Cercul 1:

Centrul:1.30 3.5

Raza:3.72

Cercul 2:

Centrul:-1.25 9.2

Raza:2.40

Cercul 3:

Centrul:0 1.5

Raza:2.39

Introduceti punctul pentru verificare apartenenta: 1.61 2.36

Rezultat

Punctul (1.61, 2.36)apartine cercurilor:

1 C( 1.3, 3.5, 3.72 )

3 C( 0, 1.5, 2.39 )

B. Structuri cu autoreferențiere

Sunt folosite pentru implementarea listelor, arborilor, etc.

O structură este cu autoreferențiere dacă are cel puțin un membru care este pointer către structura însăși.

De exemplu:

struct nod{

char nume[100];

struct nod *urmator;};

nod n1,n2; // in C++

……

n1.urmator=&n2; // este un pointer (are tipul nod*) si i se atribuie adresa lui n2

Legătura dintre două noduri este prezentată mai jos.

Următorul program (struct_autoref.cpp) afișează la ecran informații despre angajații unei firme. Informațiile despre angajați se citesc de la tastatură cu excepția membrului pointer de tip persoana, cu numele urmator, care pastrează adresa de început a elementului următor din tabloul de structuri. Programul folosește o structură cu autoreferențiere persoana care conține membrul pointer urmator tot de tip persoana, care referă structura insăși (adresa de început a elementului următor din tabloul de structuri de tip persoana). Pointerul p de tip persoana este folosit pentru citirea datelor de la tastatură .

#include <iostream.h>

#include <conio.h>

void main()

{struct persoana{ //structura cu autoreferentiere

char nume[20];

int varsta;

int salariu;

persoana * urmator;}; // membru pointer de tip persoana

persoana a[10];

persoana *p; //pointer catre date de tip persoana

int i,nr_ang;

cout<<"Nr. angajati:"; cin>>nr_ang;

cout << "Introduceti informatiile despre angajati\n";

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

{

p=&a[i]; // atribuirea adesei elementului i din tablou

cout<<"Nume angajat:"; cin.get();

cin.getline((*p).nume,20);

cout<<"Varsta angajat:"; cin>>(*p).varsta;

cout<<"Salariu angajat:"; cin>>(*p).salariu;

if (i==nr_ang-1)

/* atribuirea NULL la membrul urmator pentru

ultimul element din tabloul de structuri */

(*p).urmator = NULL;

else

(*p).urmator = &a[i+1];

cout<<a[i].nume<<endl;

cout<<a[i].varsta<<endl;

cout<<a[i].salariu<<endl;

cout<<a[i].urmator<<endl;

}

p=&a[0]; // atribuirea adresei primului element din tablou

while (p) {

cout<<(*p).nume<<"\t"<<(*p).varsta<<"\t";

cout<<(*p).salariu<<"\t"<<(*p).urmator <<endl;

p = (*p).urmator;

}

getch();

}

6.4 Tipul de date union

Aceeași zonă de memorie poate fi utilizată pentru păstrarea unor obiecte (date) de diferite tipuri, prin declararea uniunilor. O variabilă de tip union este o variabilă care poate, la momente diferite, să memoreze valori de tipuri diferite.

Spațiul de memorie alocat unei variabile de tip union este determinat de membrul cu ce mai mare lungime.

Avantajul uniunilor constă în posibilitatea ca tipuri diferite de date să gestioneze aceeași zonă de memorie.

Uniunile au sintaxa similară cu structurile. Spre deosebire de structuri, care alocă spațiu în memorie pentru fiecare membru, uniunile alocă doar spațiul necesar pentru cel mai mare membru din uniune (cu lungimea, în octeți, cea mai mare).

Declararea unui tip union are următoarea sintaxă:

union <nume_tip_union>

{<declaratie_membru_1>;

<declaratie_membru_2>;

… };

unde:

– <nume_tip_union> – specifică numele dat de programator tipului de date union;

– <declaratie_membru_1>, <declaratie_membru_2>,…,<declaratie_membru_n>

– declarație, obișnuită, de tip pentru membrii uniunii.

Declararea unei variabile de tip union se face, similar cu declararea unei variabile de tip struct, în două moduri:

a) În instrucțiunea de declarare a tipului union denumit <nume_tip_union>;

b) După crearea tipului de date union cu numele <nume_tip_union>, într-o instrucțiune de declarare distinctă:

union <nume_tip_union> <lista_variabile>;

Pentru a vedea diferența între tipul struc și union, să analizăm următorul exemplu:

Să presupunem că se crează un tip struct denumit AngajDate

struct AngajDate

{

int zile_lucrate;

struct ultima_data_lucru;

{int zi;

int luna;

int an;

} ultima_zi_lucru;

};

și programul stochează valori, fie pentru membrul zile_lucrate, fie pentru membrul ultima_zi_lucru.

În acest caz, zona de memorie alocată membrului care nu va conține o valoare rămâne neutilizată pentru anumiți angajați. În această situație se poate folosi tipul union, care alocă memoria cerută de cel mai lung (ca dimensiune) membru al ei (adică membrul cu numele ultima_zi_lucru).

Astfel:

union AngajDate

{

int zile_lucrate;

struct ultima_data_lucru;

{int zi;

int luna;

int an;

} ultima_zi_lucru;

};

Iată cum se alocă memoria pentru o structură și o uniune similare (tipul int reprezentat pe doi octeți și nu pe patru octeti):

Un alt exemplu:

union numeric{

int i;

float f;

double d;

} num;

num.i = 20;

num.f = 5.80;

cout<<sizeof(num)<<'\n'; //8

Modul de alocare a memoriei pentru variabila num (uniune) – 8 octeți

Pentru variabile num se rezervă 8 octeți de memorie, dimensiunea maximă a zonei de memorie alocată membrilor (pentru int s-ar fi rezervat 2 octeți, pentru float 4, iar pentru double 8). În exemplul anterior, în aceeași zonă de memorie se păstrează fie o valoare întreagă (num.i=20), fie o valoare reală, dubla precizie (num.f=5.80).

Notă:

Pentru a accesa membrii uniunii, se folosește operatorul punct (“.”) la fel ca în cazul structurii.

Dacă pentru definirea tipului numeric s-ar fi folosit o structură, modul de alocare a memoriei este cel prezentat mai jos.

struct numeric{

int i;

float f;

double d;

} num;

num.i = 20;

num.f = 5.80;

cout<<sizeof(num)<<'\n'; //14

Observație:

Nu există nicio modalitate de a determina tipul datei curente stocate într-o uniune. Este în sarcina programatorului să țină cont de ceea ce se afla într-o variabilă de tip union la un moment dat.

O modalitate folosită este de a declara o variabilă care să indice tipul curent de fiecare dată când se atribuie valoare unei uniuni. Vom analiza un exemplu în acest sens.

Dereferențierea funcționează și în cadrul uniunilor.

Următorul program (dim_union.cpp) utilizează operatorul sizeof pentru a afișa volumul de memorie pe care îl consumă diferite tipuri de date de tip union sau struct:

#include <stdio.h>

void main(void)

{ union AngajDate

{int zile_lucrate;

struct ultima_data_lucru

{ int zi;

int luna;

int an;} ultima_zi_lucru;

} angaj_info;

struct sAngajDate

{int zile_lucrate;

struct sultima_data_lucru

{int zi;

int luna;

int an;} ultima_zi_lucru;

} sangaj_info;

union Numere

{int a;

float b;

long c;

double d; // cel mai mare -cere 8 bytes};

union Numere val;

printf("Dimensiunea variabilei tip struct AngajDate %d bytes\n", sizeof(sangaj_info));

printf("Dimensiunea variabilei tip union AngajDate %d bytes\n", sizeof(angaj_info));

printf("Dimensiunea variabilei Numere %d bytes\n", sizeof(val));

val.a = -48;

printf ("Uniunea pastreaza numai valoarea variabilei val.a, ultima initializata %d\n", val.a);

val.b = 34.75;

printf ("Uniunea pastreaza numai valoarea variabilei val.b, ultima initializata %.2f\n", val.b);

val.c = 7689;

printf ("Uniunea pastreaza numai valoarea variabilei val.c, ultima initializata %ld\n", val.c);

val.d = 6345.967;

printf ("Uniunea pastreaza numai valoarea variabilei val.d, ultima initializata %.3lf\n", val.d);

angaj_info.zile_lucrate = 200;

printf ("Variabila angaj_info.zile_lucrate este %d\n", angaj_info.zile_lucrate);

angaj_info.ultima_zi_lucru.zi = 12;

angaj_info.ultima_zi_lucru.luna = 12;

angaj_info.ultima_zi_lucru.an = 2005;

printf ("Variabila angaj_info.ultima_zi_lucru este %d-%d-%d\n",

angaj_info.ultima_zi_lucru.zi, angaj_info.ultima_zi_lucru.luna,

angaj_info.ultima_zi_lucru.an); }

După execuția programului, pe ecran se afișează:

Dimensiunea variabilei tip struct AngajDate 16 bytes

Dimensiunea variabilei tip union AngajDate 12 bytes

Dimensiunea variabilei Numere 8 bytes

Uniunea păstrează numai valoarea variabilei val.a, ultima inițializată -48

Uniunea păstrează numai valoarea variabilei val.b, ultima inițializată 34.75

Uniunea păstrează numai valoarea variabilei val.c, ultima inițializată 7689

Uniunea păstrează numai valoarea variabilei val.d, ultima inițializată 6345.967

Variabila angaj_info.zile_lucrate este 200

Variabila angaj_info.ultima_zi_lucru este 12-12-2005

6.5 Tipul de date enum

Un tip de dată enum este o listă de elemente (constante) care are fiecare valoarea sa unică. O variabilă de tip enum ia valori din lista specificată în tipul enum.

Crearea unui tip de date enum

Declararea unui tip enum are următoarea sintaxă:

enum <nume_tip_enum>

{<nume_constanta_1>, <nume_constanta_2>, …};

unde:

– <nume_tip_enum> – specifică numele dat de programator tipului de date enum;

– <nume_constanta_1>, <nume_constanta_2>, … – specifică nume de constante care reprezintă membii tipului enum; fiecare membru are o valoare unică

.

Notă:

1. În mod implicit, compilatorul atribuie primului membru valoarea 0, celui de al doilea valoarea 1 și așa mai departe.

2. Programatorul se referă la numele tipului enum ca la un nume generic. Acest nume generic al enumerări poate fi utilizat în program pentru a declara variabile de acest tip.

De exemplu, se poate crea un tip de date enum, cum se arată în continuare:

enum zilele_saptam {luni, marti, miercuri, joi, vineri};

În acest exemplu, numele tipului de data enum este zilele_saptam. Valorile care corespund membrilor enumerării (luni, marți, miercuri, joi și vineri) sunt 0, 1, 2, 3 și 4.

Declararea unei variabile de tip enum

Declararea unei variabile de tip enum se poate face în două moduri:

a) În declarația tipului enum, cu numele <nume_tip_enum>, astfel:

enum <nume_tip_enum> {<nume_constanta_1>, <nume_constanta_2>, …} <lista_variabile>;

unde:

– <lista_variabile> – specifică numele variabilelor de tipul enum desparțite prin virgulă.

b) După crearea tipului de date enum, cu numele <nume_tip_structura>, într-o instrucțiune de declarare distinctă, astfel:

enum <nume_tip_enum> <lista_variabile>; // pentru limabajul C

sau

<nume_tip_enum> <lista_variabile>; // pentru limbajul C++

De exemplu:

enum zilele_saptam {luni, marti, miercuri, joi, vineri} zi_lucru; //pentru varianata a)

sau

enum zilele_saptam zi_lucru; //pentru varianata b) in C

sau

zile_saptam zi_lucru; //pentru varianata C++

Observație:

O variabilă de tip enum nu se poate inițializa la declarare. Valorile atribuite membrilor la definirea tipului de data enum nu se pot modifica.

Atribuirile de valori făcute membrilor, la definirea tipului enum, sunt echivalente cu atribuirile de valori pentru constante efectuate cu macrodefinitia #define.

Referirea la un membru (accesarea unui membru) al unei enumerări

Referirea la un membru al unei enumerări se realizează specificând numele membrului prin intermediul căruia se atribuie o valoare variabilei de tip enum.

De exemplu:

enum zilele_saptam {luni, marti, miercuri, joi, vineri} zi_lucru;

zi_lucru = marti;

în care, variabiei zi_lucru de tipul enum cu numele zile_saptam, i se atribuie valoarea membrului cu numele marti.

Atribuirea valorilor pentru tipul de date enum

După cum știm, compilatorul atribuie o valoare unică pentru fiecare membru al unui tip enumerare. În mod implicit, compilatorul atribuie primului membru valoarea 0, celui de al doilea valoarea 1 și așa mai departe. Dar, la definirea tipului enum se pot specifica alte valori pentru fiecare membru.

Următoarea declarare de tip, de exemplu, atribuie valorile 10, 20, 30, 40 și 50 pentru membrii tipului cu numele zilele_saptam:

enum zilele_saptam {luni = 10, marti = 20, miercuri = 30, joi = 40, vineri = 50};

De asemenea, se poate atribui o valoare unui anumit membru. Compilatorul va incrementa valoarea fiecăruia dintre ceilalți membri (care urmează dupa el) cu 1.

Următorul program (tip_enum.cpp) ilustrează modul de folosire a tipulu enum. La definirea tipului zilele_saptam se atribuie valoarea 10 numai pentru un membru al enumerării. Ceilalți membrii, care urmează dupa el, vor avea valorile 11, 12, 13 si 14.

#include <stdio.h>

void main(void)

{enum zile_saptam {luni = 10, marti, miercuri, joi, vineri};

enum zile_saptam zi_lucru;

for (zi_lucru = luni; zi_lucru <= vineri; zi_lucru++)

switch (zi_lucru)

{case luni : printf("Luni este neplacuta\n"); break;

case marti : printf("Marti este cu ghinion\n"); break;

case miercuri : printf("Miercuri este o zi buna de lucru\n"); break;

case joi : printf("Joi lucrez in continuare\n"); break;

default : printf("Vineri ma pregatesc de weekend\n"); break;}

zi_lucru = miercuri;

printf ("Valoarea variabilei de tip enum \"zi_lucru\" este %d" , zi_lucru);}

Programul de mai jos (enum.cpp) generează aleator trei numere întregi cu valorile 0, 1, 2. Aceste valori sunt comparate cu valorile membrilor tipului enum cu numele color. Apoi se afișează la ecran un mesaj care simulează comenzile realizate de un semafor.

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

#include <string.h>

void main(){

enum color{rosu,galben,verde};

char comanda[25];

int culoare;

randomize();

culoare=random(3); // generare de numere aleatoare;

switch(culoare) // comparare cu valorile membrilor tipului color

{

case rosu:strcpy(comanda,"Stop!");break;

case galben:strcpy(comanda,"Stop daca este posibil!");break;

case verde:strcpy(comanda,"Treci!");break;

}

printf("%s\n",comanda);

getch();}

La prezentarea tipului de date union s-a făcut precizarea că nu există nicio modalitate de a determina tipul datei curente stocate într-o uniune și că este în sarcina programatorului să țină cont de ceea ce se află într-o variabilă de tip union la un moment dat.

O modalitate folosită este de a declara o variabilă care să indice tipul curent al datei stocate de fiecare dată când se atribuie valoare unui membru al unei variabile de tip union.

Vom prezenta un exemplu de folosire a tipului union care păstrează în aceeași zonă de memorie componente, de diferite tipuri, dintr-o entitate cu mai mulți membri.

Să presupunem că se fac măsuratori pentru anumite obiecte (indicatori, parametrii etc.) și pentru fiecare obiect numai una este necesară.

Programul următor (enum_union.cpp) citește și afișează aceste valori măsurate, folosind tipul union. Valoarea măsurată pentru un obiect poate fi de tip int, double sau char. Pentru a cunoaște tipul de măsurătoare se folosește tipul enum cu numele TipMas, iar pentru măsurătoarea propriu-zisă se folosește tipul uniun cu numele ValoareMas. Întreaga măsurătoare este de tipul struct și este complet definită de tipul ei și de valoarea măsurătorii și are numele Măsurat .

#include <iostream.h>

#include <iostream.h>

#include <conio.h>

enum TipMas{

tipNECU,

tipINT,

tipDOUBLE,

tipCHAR}; //tip masuratoare

union ValoareMas

{

int intVal;

double dblVal;

char chrVal; // valoarea masurata stocata in zona de memorie a var. dblVal

};

struct Masurat {

TipMas tip;

ValoareMas valoare; // masuratoarea efectuata

};

void main(){

int n, tipM,i;

Masurat aM[10]; // variabila in care se stocheaza masuratorile

cout<<"Numarul de masuratori = "; cin>>n;

cout<<"Introduceti masuratorile:"<<endl;

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

cout<<"Introduceti tipul masuratorii(1=int, 2=double, 3=char): " ;

cin>> tipM;

switch (tipM) {

case tipINT: cout<<"Valoare INT=";

cin>>aM[i].valoare.intVal; // stocare valori masurate

aM[i].tip=tipINT; break; // stocare tip

case tipDOUBLE: cout<<"Valoare DOUBLE=";

cin>>aM[i].valoare.dblVal; // stocare valoare masurata

aM[i].tip=tipDOUBLE; break; // stocare tip

case tipCHAR: cout<<"Valoare CHAR=";

cin>>aM[i].valoare.chrVal; // stocare valoare masurata

aM[i].tip=tipCHAR; break; // stocare tip

default: cout<<"Tip de masuratoare necunoscut"<<endl;

aM[i].tip=tipNECU; // stocare tip necunoscut

}

}

cout<<"Masuratorile introduse:"<<endl;

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

switch(aM[i].tip){

case tipINT: cout<<"INT "<<aM[i].valoare.intVal<<endl; break;

case tipDOUBLE: cout<<"DOUBLE "<<aM[i].valoare.dblVal<<endl; break;

case tipCHAR: cout<<"CHAR "<<aM[i].valoare.chrVal<<endl; break;

default: cout<<"Tip de masuratoare necunoscut"<<endl;

}

}

getch();

}

După execuția programului, pe ecran se afisează:

Numarul de masuratori = 3

Introduceti masuratorile:

Introduceti tipul masuratorii(1=int, 2=double, 3=char): 1

Valoare INT=45

Introduceti tipul masuratorii(1=int, 2=double, 3=char): 2

Valoare DOUBLE=67.9

Introduceti tipul masuratorii(1=int, 2=double, 3=char): 3

Valoare CHAR=F

Masuratorile introduse:

INT 45

DOUBLE 67.9

CHAR F

Lecția 7

Cuvinte importante:

– mecanismul alocării dinamice a memoriei;

– funcții din bibliotecile standard ale limbajului C utilizate pentru alocarea dinamică a memoriei: maloc, calloc, free, realloc;

– alocarea dinamică a memoriei în C++: alocarea dinamică a memoriei folosind operatorul new; eliberarea (dealocarea) zonei de memorie folosind operatorul delete;

– alocarea dinamică a unui tablou unidimesional și bidimensional;

– alocarea dinamică a structurilor;

– alocarea dinamică a tablourilor de șiruri de caractere.

Mecanismul alocării dinamice a memoriei

Necesitatea alocării dinamice a memoriei

La declararea unui tablou, compilatorul alocă memorie pentru a păstra tabloul. Dacă se modifică cerințele programului și tabloul trebuie să se mărească sau să se micșoreze, este necesar să se modifice și să se recompileze programul. Pentru a reduce numărul de modificări ale programului datorate schimbării dimensiunilor tabloului, programul poate să-și aloce propria memorie pe durata execuției. Deci, se realizează o alocare dinamică a memoriei pentru păstrarea tablourilor.

Zona Heap

La execuția unui program, sistemul de operare alocă o zonă de memorie pentru program. Dar, sistemul de operare alocă memorie nu numai pentru instrucțiunile executabile, ci și pentru alte părți ale programului cum ar fi datele.

Deci, la execuția unui program se alocă mai multe zone de memorie, care în general (depinzând de sistemul de operare și de modelul de memorie), ar putea fi: zonă text, zonă stivă, zonă de date și zonă heap.

1. Zona text este acea zonă rezervată din memorie ce conține instrucțiunile executabile ale programului. Zona este protejată la scriere și se poate accesa prin pointeri la funcții. Are dimensiune constantă.

2. Zona de date este zona rezervată memorării variabilelor statice (interne sau externe), tablouri și structuri inițializate, constante șir și variabilelor globale din program. Zona conține atât variabile inițializate, cât și neinițializate.

3. Zona stivă conține datele (organizate în cadre stivă) necesare mecanismului prin care compilatorul implementează apelul de funcții și variabilele locale ale programului. Zona stivă este dinamică (poate crește sau descrește) și dimensiunea ei depinde de modul de execuție al programului.

4. Zona heap este partea din memorie unde se fac alocările dinamice (din timpul execuției programului). Zona heap este dinamică, și ca atare, dimensiunea ei este variabilă. Dimensiunea zonei heap depinde și de sistemul de operare și platforma hardware.

Zona heap este separată de zona stivă, dar este posibil ca ele să partajeze aceeași zonă de memorie.

Gestiunea zonei heap este în sarcina programatorului care, prin intermediul funcțiilor din bibliotecile standard, alocă sau eliberează intervale de memorie și detectează eventualele depășiri ale capacității zonei. Accesul la datele din zona heap se face prin pointeri.

Funcții din bibliotecile standard ale limbajului C utilizate pentru alocarea dinamica a memoriei

A. Pentru alocarea dinamică a memoriei, pe durata execuției unui program, se poate utiliza funcția malloc ().

Funcția malloc () este declarată în fișierul antet alloc.h astfel:

#include <alloc.h>

void *malloc (unsigned nr_octeti);

unde:

– nr_octeti – specifică numărul de octeți doriți a fi alocați pentru dimensiunea tabloului.

Funcția malloc() returnează un pointer void la începutul intervalului de octeți, dacă alocarea intervalului de octeți reușește. Dacă apare o eroare, funcția malloc() va returna un pointer cu valoarea NULL. Programul poate lucra apoi cu zona de memorie alocată dinamic, utilizând un format tablou sau pointer.

Observație: Reamintim că, pointerul de tipul void este un pointer care servește la memorarea adresei oricărui tip de variabilă. Acest tip de pointer nu se poate dereferenția (indirectă).

Notă: Când se utilizează funcția malloc(), programul trebuie să convertească rezultatul funcției (care este un pointer void) la un pointer de orice tip de bază se dorește. Pentru a schimba tipul pointerului returnat de funcția malloc() se folosește operatorul de conversie explicită de tip (numit și cast).

Reaminitim că, operatorul de conversie explicită de tip permite conversia explicită (forțată) a tipului unei expresii la un tip specificat, astfel:

(<tip>) <expresie>;

unde:

– <tip> – specifică tipul de date la care se face conversia expresiei;

– <expresie> – specifică expresia care are un anumit tip și care va fi convertită la tipul specificat prin <tip>.

Exemple:

1. Pentru a aloca o zonă de memorie care să păstreze 100 de valori de tip int se folosesc instrucțiunile:

int *val_int;

val_int = (int *) malloc (100*sizeof(int));

Expresia (int *) malloc (100*sizeof(int)) înseamnă că tipul void al pointerului returnat de funcția malloc() este convertit explicit la tipul de baza int.

2. Pentru a aloca o zonă de memorie care să păstreze 50 de valori în virgulă mobilă se folosesc instrucțiunile:

float *val_float;

val_float = (flot *) malloc (50*sizeof(float));

B. Funcția calloc () se poate utiliza pentru alocarea dinamică a memoriei, în timpul execuției programului.

Funcția calloc () este similară funcției malloc (). Diferența dintre cele două funcții este că funcția malloc () alocă spațiu pentru un număr de octeți specificat, în timp ce funcția calloc () alocă spațiu pentru un număr de elemente de o anumită dimensiune specificată. În plus, zona alocată de funcția calloc () e inițializată cu valoarea zero. Acesta este motivul pentru care funcția calloc () se execută mai încet decât funcția malloc ().

Funcția calloc () este declarată în fișierul antet alloc.h astfel:

#include <alloc.h>

void *calloc (unsigned nr_elemente, unsigned dim_elemente);

unde:

– nr_elemente – specifică numărul de elemente pentru care se alocă zona de memorie;

– dim_elemente – specifică dimensiunea fiecărui element, în octeți.

Funcția calloc() returnează un pointer void la începutul intervalului de memorie alocat, dacă alocarea intervalului de memorie reușește. Dacă apare o eroare, funcția calloc() va returna un pointer cu valoarea NULL.

C. Pentru eliberarea spațiului de memorie alocat, când acesta nu mai este necesar, se poate folosi funcția free ().

Când programul a terminat cu utilizarea spațiului de memorie alocat de funcția malloc () sau de funcția calloc () este necesar să se utilizeze funcția free () pentru eliberarea memoriei. În acest fel, programul poate să reutilizeze zona respectivă în alte scopuri.

Funcția free () este declarată în fișierul antet alloc.h astfel:

#include <alloc.h>

void free (void *ptr);

unde:

– ptr – este un pointer la începutul zonei de memorie care se dorește a fi eliberată.

Următorul program (functia_malloc.cpp) utilizează funcția malloc () pentru a aloca memorie unui șir de caractere, unui tablou unidimensional (vector) de valori întregi și unui tablou unidimensional (vector) de valori în virgulă mobilă. După utilizarea zonelor de memorie alocate se face dealocarea (eliberarea) zonelor respective folosind funcția free().

#include <stdio.h>

#include <alloc.h>

void main(void)

{char *sir;

int *val_int, i;

float *val_float, j;

if ((sir = (char *) malloc(26)) != NULL)

{printf("Alocarea reusita a 26 byte sir\n");

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

sir[i] = 'A' + i;

sir[i] = NULL;

printf ("%s\n", sir);

free (sir);

}

else

printf("Eroare la alocarea sir\n");

if ((val_int = (int *) malloc(20 * sizeof(int))) != NULL)

{printf("Alocarea reusita val_int[20]\n");

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

{val_int[i] = i;

printf ("%d ", val_int[i]);}

free (val_int);

}

else

printf("Eroare la alocarea val_int[100]\n");

if ((val_float = (float *) malloc(20 * sizeof(float))) != NULL)

{printf("\nAlocarea reusita val_float[20]\n");

i=0;

for (j=0.0; j<2.0; j+=0.1)

{val_float[i] = j;

printf ("%f ", val_float[i]);

i+=1;}

free(val_float);

}

else

printf("Eroare la alocarea val_float[20]\n");

}

Următorul program (aloc_dinamic_matr.cpp) utilizează funcțiile calloc () și free () pentru alocarea dinamică a unui tablou bidimensional (matrice), și respectiv eliberarea zonei de memorie alocată:

#include <stdio.h>

#include <alloc.h>

#include <conio.h>

void main()

{

int **a; // adresa tabloului de pointeri la linii

int aloc =1;

int lin, col;

int inum = 1;

int i, j;

printf("Introduceti numarul de linii : ");

scanf("%d", &lin);

printf("Introduceti numarul de coloane: ");

scanf("%d", &col);

a = (int**)calloc(lin, sizeof(int*)); //alocarea zonei pointerilor de linie

if (!a)

{

printf("Eroare la alocarea zonei pointerilor de linie\n");

aloc = 0;}

else

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

{a[i]=(int*)calloc(col,sizeof(int)); //alocarea zonei elementelor liniei

if (!a[i])

{printf("Eroare la alocarea zonei elementelor liniei\n");

aloc=0;

}

}

if (aloc)

{for (i=0; i<lin; i++)

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

a[i][j]=inum++;

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

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

printf("a[%d,%d]=%d\n",i,j,a[i][j]);

free(*a); //dealocarea zonei elementelor liniei

free(a);} //dealocarea zonei pointerilor de linie

getch();

}

D. Pentru modificarea dimensiunii unei zone de memorie alocate anterior se poate folosi funcția realloc ().

Funcția realloc () este declarată în fișierul antet stdlib.h astfel:

#include <stdlib.h>

void *realoc (void *bloc, unsigned nr_octeti);

unde:

– bloc – este un pointer la zona de memorie alocată anterior;

– nr_octeti – specifică numărul de octeți doriți a fi alocați pentru noua dimensiune a zonei.

Funcția realloc() returnează un pointer void la începutul zonei de memorie redimensionată, dacă realocarea reușește.

Pointerul returnat poate fi diferit de cel inițial. Acest lucru se întâmplă dacă nu există spațiu contiguu suficient la realocare. Deci, funcția realloc () poate muta întregul bloc de memorie la o altă adresă, pentru a găsi spațiu necesar (copiind datele, dacă este necesar).

Dacă apare o eroare, funcția realloc() va returna un pointer cu valoarea NULL.

Următorul program (functia_realloc.cpp) utilizează funcția realloc () pentru mărirea dimensiunii unei zone de memorie de la un număr de octeți citit de la tastatură (care păstrează date reale în simplă precizie) la un număr de octeți citit, de asemenea de la tastatură.

#include <stdio.h>

#include <alloc.h>

#include <stdlib.h>

#include<conio.h>

void main(void)

{int i;

unsigned n;

float *vector,j;

printf("Introduceti numarul de elemente ale vectorului: ");

scanf ("%u", &n);

if ((vector = (float *) malloc(n * sizeof(float))) != NULL)

{printf("Alocare reusita vector\n");

j= 0.0;

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

{vector[i] = j;

printf ("%.2f ", vector[i]);

j+=0.1;}

printf("\nIntroduceti un alt numarul de elemente pentru vector: ");

scanf("%u", &n);

vector = (float *) realloc(vector, n * sizeof(float));

if (vector != NULL)

{printf("Realocare reusita vector\n");

j= 0.0;

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

{vector[i] = j;

printf ("%.2f ", vector[i]);

j+=0.1;}}

else

printf("Eroare la realocare vector\n");}

else

printf("Eroare la alocarea vector\n");

getch();

}

Verificarea zonei Heap

Un program care utilizează funcțiile de alocare dinamică fără verificări suplimentare este greu de depanat.

Mesajele de eroare afișate de funcțiile malloc(), calloc() și realloc() sunt, de regulă, insuficiente pentru găsirea erorii. Un exemplu este distrugerea integrității memoriei heap, printr-un apel al funcției free() cu un parametru ce nu este o adresă rezultată dintr-un apel anterior al unei funcții de alocare. Alte erori pot apărea în programele care alocă și eliberează repetat blocuri din memoria heap.

Dacă se întâlnesc erori într-un program care alocă dinamic memoria și nu se poate localiza sursa erorii, se poate apela la verificarea zonei heap.

Pentru a facilita testarea stării zonei heap, multe compilatoare dispun de o serie de funcții de bibliotecă run-time, cum ar fi heapcheck().

Pentru testarea stării întregii zone heap se utilizează funcția heapcheck ().

Funcția heapcheck () parcurge toată zona heap și examinează fiecare bloc verificând adresa de început a fiecărei zone de memorie alocată dinamic și dimensiunea acesteia.

Funcția heapcheck () este declarată în fișierul antet alloc.h astfel:

#include <alloc.h>

int heapcheck (void);

Funcția returnează una din valorile reținute în constantele următoare:

_HEAPEMPTY – nu există zonă heap liberă;

_HEAPOK – zona heap este alocată corect;

_HEAPCORRUPT – una sau mai multe intrări (zone alocate) sunt alterate.

Următorul program (functia_heapcheck.cpp) utilizează funcția heapcheck() pentru testarea stării zonei heap. Atunci când programul alocă memorie pentru trei zone, funcția returnează valoarea de stare care este ok. După ce programul alocă valori la cele trei zone de memorie, funcția verifică din nou starea zonei heap.

#include <stdio.h>

#include <alloc.h>

void main(void)

{char *sir;

int *val_int, i;

float *val_float, j;

int stare;

if ((sir = (char *) malloc(26)) != NULL)

printf("Alocarea reusita a 26 byte sir\n");

else

printf("Eroare la alocarea sir\n");

if ((val_int = (int *) malloc(20 * sizeof(int))) != NULL)

printf("Alocarea reusita val_int[20]\n");

else

printf("Eroare la alocarea val_int[20]\n");

if ((val_float = (float *) malloc(20 * sizeof(float))) != NULL)

printf("Alocarea reusita val_float[20]\n");

else

printf("Eroare la alocarea val_float[20]\n");

stare = heapcheck();

if (stare == _HEAPOK)

printf("HEAP este ok\n");

else

if (stare == _HEAPCORRUPT)

printf("HEAP este alterat\n");

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

sir[i] = 'A' + i;

sir[i] = NULL;

printf ("%s\n", sir);

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

{val_int[i] = i;

printf ("%d ", val_int[i]);}

printf("\n");

i=0;

for (j=0.0; j<2.0; j+=0.1)

{val_float[i] = j;

printf ("%f ", val_float[i]);

i+=1;}

printf("\n");

free(sir);

free(val_int);

free(val_float);

stare = heapcheck();

if (stare == _HEAPOK)

printf("HEAP este ok\n");

else

if (stare == _HEAPCORRUPT)

printf("HEAP este alterat\n");}

Alocarea dinamică a memoriei în C++

În C++, pentru memoria heap se folosește termenul de spațiu liber. Pentru a aloca memorie din spațiul liber, programele C++ utilizează operatorii new și delete.

Alocarea dinamică a memoriei folosind operatorul new

Operatorul new, care se mai numește și operator de alocare, se poate utiliza în două variante de sintaxă:

a) pentru alocarea unei zone de memorie care nu este de tip tablou:

new <tip>;

unde:

– <tip> – tipul valorilor care vor fi memorate în spațiul liber;

b) pentru alocarea unei zone de memorie care este de tip tablou:

new <tip> [];

unde:

– [] – specifică numărul de locații de tipul <tip> alocate.

new returnează un pointer la începutul zonei alocate, dacă mai există spațiu disponibil sau memoria heap nu este fragmentată. În caz contrar, new returnează pointerul cu valoarea NULL.

De exemplu, dacă se dorește alocarea unei zone de memorie care nu este de tip tablou, secvența de instrucțiuni este:

float *ptr;

ptr = new float;

în care variabila ptr va reține adresa unei locații de memorie de 4 octeți.

Echivalent se poate scrie:

float *ptr = new float;

În exemplul următor se alocă o zonă de memorie care este de tip tablou:

float *vect;

vect = new float [20];

în care variabila vect va reține adresa unei zone de memorie de tip tablou de 80 de octeți.

Notă: Zona heap având capacitate limitată, există posibilitatea ca alocarea să nu se poată face când aceasta este plină sau fragmentată. De aceea, după alocarea zonei prin new trebuie să se verifice dacă aceasta a avut loc în mod real.

În secvența de instrucțiuni următoare se exemplifică această verificare după alocarea unei zone de memorie care va păstra o valoare întreagă:

int *ptr;

ptr = new int;

if (ptr)

{ *ptr = 17;

cout << *ptr;}

else

cout << "Heap-ul este plin";

În programul următor (operatorul_new.cpp) se alocă dinamic o zonă de memorie pentru un vector, cu numele ptr, cu n elemente de tip întreg, a cărei adresă de început se păstrează în pointerul ptr, și apoi se face verificarea alocării corecte a acestei zone:

#include <iostream.h>

void main ()

{

int *ptr, i, n;

cin >> n;

ptr = new int [n];

if (ptr)

{

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

{ptr [i] = i;

cout << ptr[i];}

}

else

cout << "Heap-ul este plin";

}

Eliberarea (dealocarea) zonei de memorie folosind operatorul delete

Memoria alocată dinamic se eliberează automat la terminarea execuției programului. Totuși, când programul a terminat cu utilizarea spațiului de memorie alocat de operatorul new, este necesar să se utilizeze operatorul delete pentru eliberarea memoriei alocate. În acest fel, programul poate să reutilizeze zona respectivă în alte scopuri.

Operatorul delete se poate utiliza în trei variante de sintaxă:

a) pentru eliberarea unei zone de memorie care nu este de tip tablou:

delete <nume_pointer>;

unde:

– <nume_pointer> – numele variabilei pointer care a preluat adresa unei locații alocate în heap prin operatorul new;

b) pentru eliberarea unei zone de memorie care este de tip tablou unidimensional (vector):

delete <nume_vector>;

unde:

– <nume_vector> – numele unui tablou unidimensiunal;

c) pentru eliberarea unei zone de memorie care are mai multe locații (tablouri unidimensionale sau multidimensionale, structuri etc.) – forma generalizată:

delete[] <nume_pointer>;

În secvența de instrucțiuni următoare se exemplifică eliberarea unei locații de memorie care a păstrat o valoare întreagă, după alocarea ei cu operatorul new:

int *ptr;

ptr = new int;

if (ptr)

{ *ptr = 17;

cout << *ptr;}

else

cout << "Alocare esuata";

delete ptr;

În programul următor (operatorul_new.cpp) se alocă dinamic o zonă de memorie pentru un vector, cu numele ptr, cu n elemente de tip întreg, a cărei adresă de început se păstrează în pointerul ptr, și apoi se realizează dealocarea acestei zone:

#include <iostream.h>

void main ()

{

int *ptr, i, n;

cin >> n;

ptr = new int [n];

if (ptr)

{

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

{ptr [i] = i;

cout << ptr[i];}

}

else

cout << "Alocare esuata";

delete ptr // sau se poate folosi delete [] ptr;

}

Următorul program (operatorii_new_delete.cpp) utilizează operatorul delete pentru a dealoca trei tablouri de caractere alocate dinamic în memorie:

#include <iostream.h>

void main(void)

{char *sir = new char[256];

char *sir1, *sir_destinatie;

int i;

sir1 = new char[256];

if (!sir1)

cout << "Alocare esuata" << endl;

else

{for (i = 0; i < 256; i++)

{sir[i] = 'A';

sir1[i] = 'B';}

delete sir;

sir_destinatie = new char[256];

if (!sir_destinatie)

cout << "Alocare esuata" << endl;

else

{for (i = 0; i < 256; i++)

{sir_destinatie[i] = sir1[i];

cout << sir_destinatie[i] << ' ';}

delete sir1;

delete sir_destinatie;}}}

Următorul program (aloc_dinamic_matr1.cpp) utilizează operatorii new și delete pentru alocarea dinamică a unui tablou bidimensional (cu numele a), și respectiv pentru eliberarea zonei de memorie alocată.

#include <iostream.h>

#include <alloc.h>

#include <conio.h>

void main()

{

int **a; // adresa pointerilor la linii

int lin, col, aloc;

int inum = 1;

int i, j, stare;

cout << "Introduceti numarul de linii "; cin >> lin;

cout << "Introduceti numarul de coloane: "; cin >> col;

a = new int *[lin]; // alocare memorie pentru pointerii de linie

if (!a)

{cout << "Alocare esuata" << endl;

aloc’=0;}

else

{for (i = 0; i < lin; i++)

{a[i] = new int[col]; // alocare memorie pentru elementele liniilor

if (!a[i])

{cout << "Alocare esuata" << endl;

aloc = 0;}

else

aloc = 1;}

if (aloc)

{

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

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

a[i][j]=inum++;

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

{

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

cout<<a[i][j] ("\t");

cout<<endl;

}

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

{delete[] a [i]; //dealocarea zonei elementelor liniilor

stare = heapchecknode();

switch (stare)

{

case _HEAPOK : cout<<"Zona alocata pentru \"a[i]\" este Ok! \n"; break;

case _HEAPCORRUPT : cout<<"Zona alocata pentru \"a\" este corupta\n"; break;}

delete[] a; //dealocarea zonei pointerilor de linie

stare = heapcheck();

switch (stare)

{

case _HEAPOK : cout<<"Zona alocata pentru \"a\" este Ok! \n"; break;

case _HEAPCORRUPT : cout<<"Zona alocata pentru \"a\" este corupta\n"); break;}

}

}

getch()

}

Alocarea dinamică a unui tablou de structuri

Următorul program (aloc_tablou_struct.cpp) este un exemplu de alocare dinamică a memoriei pentru un tablou de structuri.

Se citește de la tastatură un număr n de litere de căutat într-un text citit de la tastatură. Apoi se citesc literele de căutat și se stochează în variabila-structură l_numarate de tip tablou (membrul litera). Se parcurge textul și se număra aparițiile fiecărei litere de căutat în text. După parcurgerea textului numărul de apariții al literei în text se stochează în variabila l_numarate (membrul contor).

#include <iostream.h>

#include <conio.h>

#include <string.h>

#include <ctype.h>

void main(){

struct litere_numarate{

char litera;

int contor;};

int i, j, n;

char text[50];

litere_numarate* l_numarate;

cout<<"Introduceti numarul de litere de cautat: " ;

cin>>n;

l_numarate = new litere_numarate[n];

cout<<"Introduceti un text sau o propozitie cu max. 50 de caractere: "<<endl; cin.get();

cin.getline(text, sizeof(text));

cout<<"Introduceti literele de cautat pe o linie despartite prin spatiu: "<<endl;

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

cin>> l_numarate[i].litera;

}

cout<<endl;

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

for(j=0;j<strlen(text);j++)

if(tolower(text[j])==l_numarate[i].litera)

l_numarate[i].contor++;

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

cout<<l_numarate[i].litera<<" "<<l_numarate[i].contor<<endl;

}

delete l_numarate;

getch();

}

Alocarea dinamică pentru structuri cu membrii tablouri

Următorul program (struct_matr_pointer.cpp) utilizează o structură de tip Matrice și realizează alocarea dinamică a unuia dintre membri structurii, cu numele elem, care stochează elementele unei matrice cu n linii și m coloane .

Programul citește de la tastatură două matrice A și B și calculează produsul, iar matricea rezultat C este afișată la ecran.

#include <iostream.h>

#include <alloc.h>

#include <conio.h>

void main() {

struct Matrice

{int n,m;

int **elem;};

Matrice A,B,C;

int aloc1 = 1, aloc2 = 1, aloc3 =1, i, j, k;

cout<<"Introduceti matricea A" <<endl;

cout<<"Numar de linii: ";

cin>>A.n;

cout<<"Numarul de coloane: ";

cin>>A.m;

//A.elem=(int**)calloc(A.n,sizeof(int*));

A.elem=new int*[A.n]; alocarea zonei pointerilor de linie

if(!A.elem)

{cout<<"Eroare la alocarea zonei pointerilor de linie";

aloc1=0;}

else

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

{

//A.elem[i]=(int*)calloc(A.m,sizeof(int));

A.elem[i]=new int[A.m]; //alocarea zonei elementelor liniei

if(!A.elem[i])

{cout<<"Eroare la alocarea zonei elementelor liniei matricei";

aloc1=0;}

}

if(aloc1)

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

{

cout <<"Introduceti linia "<<i<<" cu elemente despartite prin spatiu" <<endl;

for (j=0;j<A.m;j++)

cin >> A.elem[i][j];

}

cout<<"Introduceti matricea B" <<endl;

cout<<"Numar de linii: "; cin>>B.n;

cout<<"Numarul de coloane: "; cin>>B.m;

//B.elem=(int**)calloc(B.n,sizeof(int*));

B.elem=new int*[B.n];

if(!B.elem)

{cout<<"Eroare la alocarea zonei pointerilor de linie";

aloc2=0;}

else

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

{

//B.elem[i]=(int*)calloc(B.m,sizeof(int));

B.elem[i]=new int[B.m];

if(!B.elem[i])

{cout<<"Eroare la alocarea zonei elementelor liniei matricei";

aloc2=0;}

}

if(aloc2)

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

{

cout <<"Introduceti linia " <<i<<" cu elemente despartite prin spatiu" <<endl;

for (j=0;j<B.m;j++)

cin >> B.elem[i][j];}

if(aloc1==1 && aloc2==1 && (A.m-B.n)==0)

{

C.n=A.n;

C.m=B.m;

//C.elem=(int**)calloc(C.n,sizeof(int*));

C.elem=new int*[C.n];

if(!C.elem)

{cout<<"Eroare la alocarea zonei pointerilor de linie";

aloc3=0;}

else

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

{

//C.elem[i]=(int*)calloc(C.m,sizeof(int));

C.elem[i]=new int[C.m];

if(!C.elem[i])

{cout<<"Eroare la alocarea zonei elementelor liniei matricei";

aloc3=0;}

}

if(aloc3) {

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

for(j=0;j<C.m;j++) {

C.elem[i][j]=0;

for(k=0;k<A.m;k++)

C.elem[i][j] += A.elem[i][k]*B.elem[k][j];}

cout<<"Matricea rezultat al produsului este: "<<endl;

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

{cout<<endl;

for(j=0;j<C.m;j++)

cout<<C.elem[i][j]<< " ";}

delete *A.elem; //dealocare zona elementelor de pe linii

delete A.elem; // dealocarea zonei pointerilor de linie

delete *B.elem;

delete B.elem;

delete *C.elem;

delete C.elem;

} }

}

else

cout<<"Alocare nereusita sau nr coloane de la A nu coincide cu nr linii de la B";

getch();}

Alocarea dinamică pentru structuri cu autoreferențiere

Programul următor (aloc_struct_autoref.cpp) citește de la tastatură informații despre angajații unei firme (descriși în program prin tipul de date struct cu autoreferențiere care are numele persoana) și alocă dinamic o listă simplu înlănțuită cu un număr de nr_angaj angajați (variabila care se citește de la tastatură).

Adăugarea unui nou angajat înseamnă alocarea memoriei pentru un element din listă, curent de tip persoana, stocarea valorilor pentru membrii variabilei curent (nume, vârstă, salariu) cu date citite de la tastatură. Acest element curent va fi adăugat ultimul în listă, deci succesorul său este NULL (membrul urmator din variabila curent are adresa NULL).

Inserarea efectivă a informațiilor despre un angajat în listă se execută astfel:

dacă lista este vidă, atunci se inițializează capul listei (variabila cap de tipul persoana) cu variabila curent și variabila man de tipul persoana devine variabila curent;

altfel, succesorul variabilei man devine variabila curent (membrul urmator din variabila man are adresa variabilei curent) și variabila man devine variabila curent (variabila man va reține mereu ultimul element din listă).

Cazul în care variabila cap nu este vidă se poate reprezenta grafic astfel:

#include <iostream.h>

#include <conio.h>

void main()

{struct persoana{

char nume[20];

int varsta;

int salariu;

persoana * urmator;};

persoana *cap, *man, *curent;

int nr_ang, i;

cap = NULL;

cout<<"Nr. angajati:"; cin>>nr_ang;

cout << "Introduceti informatiile despre angajati"<<endl;

for(i=0;i<nr_ang;i++) {

curent=new persoana;

cout<<"Nume angajat:"; cin.get();

cin.getline((*curent).nume,20);

cout<<"Varsta angajat:"; cin>>(*curent).varsta;

cout<<"Salariu angajat:"; cin>>(*curent).salariu;

(*curent).urmator=NULL;

if(cap==NULL) // lista vida

{cap=curent;

man=curent;}

else

{(*man).urmator=curent; // succesorul lui man devine curent

cout<<(*man).nume<<endl;

cout<<(*man).varsta<<endl;

cout<<(*man).salariu<<endl;

cout<<(*man).urmator<<endl;}

man=curent; // man devine curent (man retine mereu ultimul element din lista)

}

// afisare lista cu structuri autoreferentiate

while (cap) {

cout<<(*cap).nume<<"\t"<<(*cap).varsta<<"\t";

cout<<(*cap).salariu<<"\t"<<(*cap).urmator <<endl;

man = cap;

cap = (*cap).urmator;

delete man; //eliberarea zonei de memorie pentru elementul curent afisat din lista

}

getch();

}

După execuția programului pe ecran se afișează:

Nr. angajati:3

Introduceti informatiile despre angajati

Nume angajat:Maria Ionescu

Varsta angajat:30

Salariu angajat:3000

Nume angajat:Ion Popescu

Varsta angajat:45

Salariu angajat:3500

Maria Ionescu

30

3000

1e1e680 –––––––––– adresa structurii cu membrul Ion Popescu

Nume angajat:Ana Vlad

Varsta angajat:25

Salariu angajat:1800

Ion Popescu –––––––– adresa structurii cu membrulAna Vlad

45

3500

1e1e6a8

Maria Ionescu 30 3000 1e1e680

Ion Popescu 45 3500 1e1e6a8

Ana Vlad 25 1800 0 ––– adresa cu valoarea NULL (ultimul in lista)

Alocarea dinamică a unui tablou de șiruri de caractere

Următorul program (aloc_tablou_sir.cpp) este un exemplu de alocare dinamică a unui tablou de șiruri de caractere.

Se citește de la tastatură un text scris pe mai multe linii, format din propoziții care se termină cu ‘.’, alcătuite din maximum 500 de caractere. Sfârșitul textului este marcat de întâlnirea secvenței “T.” . Să se determine cea mai lungă propoziție din text, în cazul în care lungimea unei propoziții este exprimată în număr de cuvinte. Două cuvinte consecutive pot fi separate prin unul sau mai multe caractere din mulțimea {‘ ’, ‘;’ , ‘,’, ‘:’, ‘-’, ‘)’, ’(‘}.

#include <iostream.h>

#include <conio.h>

#include <string.h>

void main(){

char propozitie[500], copie[500], propozitiemax[500];

char* cuvant;

char sep[]=" ;,:-)(";

int lg, terminat=0, lgmax = 0;

char** t_cuvinte;

cout<<"Introduceti propozitiile scrise pe mai multe linii"<<endl;

do {

cin.getline(propozitie,500,'.'); cin.get();

if(strcmp(propozitie,"T")!=0)

{ lg=0;

strcpy(copie,propozitie);

cuvant = strtok(copie,sep);

//alocare zona pentru tabloul de siruri de caractere

t_cuvinte = new char*[20];

//stocare cuvinte in tabloul de siruri de caractere

t_cuvinte[lg]=cuvant; while(cuvant) { cout<<"t_cuvinte["<<lg<<"]="<<t_cuvinte[lg]<<endl;

lg++ ;

cuvant=strtok(NULL,sep) ;

//stocare cuvinte in tabloul de siruri de caractere

t_cuvinte[lg]=cuvant;

}

if(lg>lgmax) { // comparare lg. in cuvinte cu lg. maxima

lgmax=lg;

strcpy(propozitiemax,propozitie);

delete t_cuvinte;

}

}

else

terminat=1;

} while (!terminat);

cout<<"Numarul de cuvinte cel mai mare= "<<lgmax<<endl;

cout<<"Propozitia cea mai lunga: "<<propozitiemax<<endl;

getch();

}

Observații:

A fost necesară copierea propoziției și extragerea cuvintelor din copie, deoarece funcția strtok() distruge șirul din care extrage unitățile lexicale.

După ce se citește o propoziție, am apelat funcția cin.get() pentru a extrage caracterul “\n” tastat după introducerea valorilor precedente.

Lecția 8

Cuvinte importante:

– funcții în limbajul C/C++: definiția unei funcții; declarația unei funcții; apelul unei funcției;

– variabile globale și variabile locale; variabile statice;

– utilizarea stivei (stack) de către funcții;

– transferul parametrilor prin valoare (apelul prin valoare);

– transferul parametrilor prin referință (apelul prin referință): utilizarea pointerilor, utilizarea referințelor din C++.

– transmiterea parametrilor de tip tablou unidimensional;

– transmiterea parametrilor de tip tablou bidimensional;

– transmiterea parametrilor de tip structura către o funcție;

– crearea funcțiilor cu un număr variabil de parametrii cu tipuri multiple;

– parametrii funcției main ().

Funcții în limbajul C/C++

În rezolvarea problemelor apar frecvent proceduri care se repetă. În aceste situații ar trebui să scriem secvența de program corespunzătoare unei astfel de operații o singură dată și să o folosim ori de câte ori este nevoie de ea.

Realizarea unui program de complexitate mare impune ca o necesitate organizarea datelor și a prelucrărilor acestora în subprobleme și, corespunzător lor, în module separate, sub formă de subprograme. În acest fel se realizează modularizarea programelor de complexitate mare, care pot fi implementate de echipe de programatori.

În limbajul C/C++, subprogramele sunt denumite funcții.

O funcție este o colecție independentă de declarații și instrucțiuni, realizată în scopul rezolvării unei sarcini precise.

Orice program C/C++ este compus dintr-o succesiune de funcții (din biblioteci standard sau create de programator), dintre care una este funcția principală, denumită main(). La lansarea în execuție a unui program C/C++ este apelată funcția main().

În concluzie, într-un program C/C++ toate prelucrările sunt organizate ca o ierarhie de apeluri de funcții, baza acestei ierarhii fiind funcția main().

Definiția unei funcții este compusă din:

– antet care conține: numele funcției, tipul rezultatului returnat de funcție și lista parametrilor funcției;

– corpul funcției care conține setul de instrucțiuni cu ajutorul căruia se descrie prelucrările efectuate de funcție.

Sintaxa generală pentru definiția unei funcții (sau formatul unei funcții) este:

<tip> <nume_functie> (<lista_parametri_formali>)

{

<declaratii_variabile_locale>

< instructiuni>

}

– <tip> – specifică tipul rezultatului returnat de funcție;

– <nume_functie> – specifică numele funcției;

– < lista_parametri_formali> – specifică una sau mai multe declarații de parametrii formali, separate prin virgulă; lista parametrilor formali poate să fie vidă, dar și în acest caz parantezele rotunde trebuie să apară.

Notă: O declarație de parametru formal specifică tipul și numele parametrului, sub forma:

<tip> <nume_parametru_formal>.

Parametrii unei funcții reprezintă o interfață prin intermediul căreia funcția comunică cu exteriorul ei. Parametrii desemnează valorile primite de funcție din exteriorul ei.

Parametrii formali ai funcției sunt numele parametrilor care apar în definiția funcției. Ei sunt folosiți pentru a descrie în mod formal (în definiția funcției) operațiile la care vor fi supuse datele ce vor fi transmise de programul apelant spre subprogram.

Observații:

1. Un program C/C++ poate conține o singură definiție pentru o funcție.

2. În standardul ANSI al limbajului C, în cazul în care lista parametrilor formali este vidă, trebuie să se specifice explicit aceasta prin cuvântul-cheie void.

3. Dacă tipul rezultatului returnat de funcție nu este specificat, implicit este considerat int. Dacă funcția nu trebuie să returneze nici un rezultat, se specifică tipul void ca tip al rezultatului returnat.

4. Funcțiile nu pot returna șiruri, structuri, uniuni sau funcții, dar ele pot returna pointeri la astfel de obiecte.

5. Nu există structură sau uniune care să conțină o funcție, dar ea poate să conțină un pointer la funcție.

Instrucțiunea return

Instrucțiunea return este folosită, de regulă, dacă funcția trebuie să returneze un rezultat. Ea se află în corpul funcției.

Sintaxa instrucțiunii este:

return <expresie>;

unde:

– <expresie> – specifică o expresie care este evaluată și transformată într-o valoare; în cazul în care expresia este vidă funcția nu returnează nici o valoare.

Semantica instrucțiunii return este: se evaluează <expresie> și se încheie execuția funcției, returnând în funcția apelantă valoarea expresiei.

În concluzie, revenirea dintr-o funcție se poate face în două modalități:

a) după executarea ultimei instrucțiuni din corpul funcției – caz în care funcția nu returnează nici o valoare;

b) la întâlnirea instrucțiunii return.

Observație: Pot exista mai multe instrucțiuni return în cadrul corpului unei funcții. Acest lucru se întâmplă, de regulă în instrucțiuni de decizie (if) care permit returnarea unei valori în anumite condiții.

Declarația unei funcții, specifică: numele funcției, tipul rezultatului returnat de funcție și lista parametrilor funcției.

Declarația unei funcții informează compilatorul despre existența funcției și formatul (definiția) acesteia. Declarația unei funcții precede întotdeauna definirea ei.

Declarația unei funcții se mai numește și prototip.

Necesitatea declarațiilor de funcții este determinată de faptul că, nu întotdeauna definiția unei funcții se află înaintea apelului unei funcții sau chiar poate să nu fie în același fișier-sursă sau este o funcție standard dintr-o bibliotecă a limbajului.

Sintaxa folosită pentru declarația unei funcții este:

<tip> <nume_functie> (<lista_parametri>);

Notă: Declarația unei funcții reprezintă, de fapt, antetul din definiția funcției. Spre deosebire de definiție, în declarație lista parametrilor nu conține în mod obligatoriu și numele parametrilor, ci este permisă doar specificarea tipurilor parametrilor.

Observații:

1. Este necesar ca înaintea oricărui apel de funcție să apară fie definiția funcției, fie declarația (prototipul) acesteia.

2. Un program C/C++ poate conține oricâte declarații pentru o funcție.

Exemple:

1. void func1 (int, int); – funcția are doi parametrii de tip int și nu returnează nici un rezultat.

2. long func2 (int x, int y); – funcția are doi parametri de tip int și returnează o valoare de tip long int.

3. double sqrt (double); – are un parametru de tip double și returnează radicalul valorii primite ca parametru; este declarată în fișierul antet math.h.

Biblioteci de funcții și fișiere antet

Bibliotecile de funcții sunt dezvoltate de firme specializate pentru a ușura activitatea de programarea a unor operații care se execută frecvent. În biblioteci, funcțiile sunt grupate pe categorii și sunt memorate în formă compilată.

Pentru a utiliza funcțiile dintr-o bibliotecă, într-un program, acestea trebuie declarate. Din acest motiv programatorii crează pentru bibliotecile lor fișiere speciale, denumite fișiere antet sau fișiere header, care conțin declarații de funcții și, eventual, declarații de variabile externe (globale).

În concluzie, pentru a utiliza funcțiile dintr-o bibliotecă, se include în program fișierul header corespunzător, printr-o directivă #include.

Apelul unei funcții, se poate realiza în două moduri:

– printr-o instrucțiune de apel;

– ca operand într-o expresie.

A) Instrucțiunile de apel se folosesc când funcția apelată nu returnează nici o valoare sau când nu se utilizează valoarea returnată de funcție, ci interesează doar efectuarea prelucrărilor descrise de funcție.

Instrucțiunea de apel a unei funcții are următoarea sintaxă:

<nume_functie> (<lista_parametrii_reali>);

unde:

– <lista_parametrii_reali> – lista parametrilor reali, care este o listă de variabile și expresii separate prin virgulă.

Parametrii reali ai unei funcții reprezintă valorile transmise de funcția apelantă către funcția respectivă (considerată apelată). Aceste valori pot fi constante sau variabile.

Observație: Numele variabilelor folosite pentru stocarea parametrilor reali nu are nici o legătură cu numele parametrilor formali.

B) Apelul funcției ca operand într-o expresie, se folosește în cazul în care interesează valoarea returnată de funcție ca operand într-o expresie.

Apelul funcției în cadrul expresiei are sintaxa:

<nume_functie> (<lista_parametrii_reali>)

Se observă că, lipsește caracterul “;” care marchează sfârșitul instrucțiunii de apel a funcției.

Regula de bază: Parametrii reali trebuie să corespundă cu parametrii formali ca număr, ordine și tip.

Rezultă că, la apelul unei funcții, valorile stocate în parametrii reali sunt atribuite, în ordine, parametrilor formali corespunzători.

Variabile globale și variabile locale

Variabilele globale au următoarele caracteristici:

– sunt declarate în exteriorul tuturor funcțiilor programului;

– sunt stocate în zona de date a programului, memoria alocată fiind automat inițializată cu 0; memoria rămâne alocată, pentru variabilele globale, până la sfârșitul execuției programului;

– sunt utilizabile (vizibile), din momentul declarării, în toate funcțiile definite ale programului (fișierului-sursă), chiar și în alte fișiere-sursă care compun un proiect.

Variabilele locale au următoarele caracteristici:

– sunt declarate în corpul (blocul) unei funcții;

– sunt stocate în stivă, memoria alocată nefiind inițializată automat; memoria rămâne alocată până la sfârșitul execuției funcției sau blocului de instrucțiuni în care au fost declarate variabilele locale;

– sunt utilizabile (vizibile) din momentul declarării numai în funcția sau în blocul în care sunt declarate.

Următorul program (variabile_globale.cpp) ilustrează folosirea a trei variabile globale, denumite a, b și c:

#include <stdio.h>

int a = 1, b = 2, c = 3; // variabile globale

float calcul_globale(void)

{

float z; // variabila locala

a = 9;

z = (float)(a + b + c)/5;

printf("a = %d b = %d c = %d z = %.2f\n", a, b, c, z);

return z;

}

void main(void)

{

printf("a = %d b = %d c = %d z = %.2f\n", a, b, c, calcul_globale());

}

După execuția programului pe ecran se afișează:

a = 9 b = 2 c = 3 z = 2.80

Evitarea utilizării variabilelor globale

La o prima privire, folosirea variabilelor globale pare a simplifica programarea, deoarece elimină necesitatea folosirii parametrilor în funcții și, mai important, necesitatea înțelegerii apelului prin valoare și a apelului prin referință.

Din păcate, în loc să reducă numărul de erori, variabilele globale adesea îl sporesc. Deoarece codul-sursă poate modifica valoarea unei variabile globale în oricare loc din program, este foarte dificil pentru un alt programator care citește programul să găsească fiecare loc din program în care variabila globală se modifică. De aceea, alți programatori ar putea să modifice programul fără să înțeleagă pe deplin efectul pe care modificarea îl are asupra variabilei globale.

Ca regulă, funcțiile trebuie să modifice doar acele variabile care le sunt transmise ca parametri. În acest fel, programatorii pot să studieze prototipurile funcțiilor pentru a determina rapid care variabilă este modificată de o funcție.

Dacă programul folosește variabile globale, ar fi bine să se reconsidere concepția programului. Obiectivul unui programator trebuie să fie eliminarea (sau cel puțin minimizarea) folosirii variabilelor globale.

Notă:

1. În interiorul funcțiilor (blocurilor de instrucțiuni) în care sunt declarate, variabilele locale au prioritate față de variabilele globale cu același nume. Prin urmare, în cazul în care în interiorul unei funcții se declară o variabilă locală cu același nume ca al unei variabile globale, numai variabila locală este utilizabilă și vizibilă pe parcursul execuției funcției și nu variabila globală cu același nume.

2. Dacă numele unui parametru formal al unei funcții coincide cu al unei variabile globale, pe durata execuției funcției respective, parametrul formal are prioritate față de variabila globală cu același nume.

Limbajul C++ permite accesarea și utilizarea, în cadrul aceluiași bloc de instrucțiunii, atât a variabilei locale cât și a variabilei globale cu același nume. Pentru a face utilizabilă și vizibilă și variabila globală ce are același nume cu variabila locală se folosește operatorul unar denumit operator de rezoluție globală C++.

Sintaxa folosită pentru utilizarea operatorului de rezoluție este:

::<nume_variabila_globala>

Următorul program (operator_rezolutie.cpp) ilustrează modul de utilizare a operatorului de rezoluție globală C++:

#include <iostream.h>

int y = 1001; //variabila globala

void main(void)

{

int y = 1; //variabila locala

cout << "Valoarea variabilei locale " << y << '\n';

cout << "Valoarea variabilei globale " << ::y << '\n';

}

După execuția programului, pe ecran se afișează următoarele:

Valoarea variabilei locale 1

Valoarea variabilei globale 1001

Variabile locale statice ;i specificatorul static indică faptul că variabila nu este memorată pe stivă, ci în zona de date a programului.

Clasa de memorare static oferă o cale de a reține valoarea unei variabile în timpul execuției programului.

În cazul unei variabile locale, specificatorul static indică faptul că variabila este inițializată automat cu 0 și are zona de memorie alocată de la prima execuție a blocului de instrucțiuni, în care a fost declarată, până la sfârșitul programului (aceasta însemnând că își păstrează valoarea între două apeluri consecutive ale funcției).

Domeniul de utilizare (vizibilitate) nu se modifică, adică variabila rămâne vizibilă numai în interiorul blocului în care a fost declarată.

Avantajele utilizării variabilelor locale statice sunt:

– variabilele sunt alocate o singură dată, nu la fiecare apel al funcției;

– își păstrează valorile între două apeluri consecutive ale funcției;

– domeniul de vizibilitate fiind local, nu există riscul ca valoarea variabilei să fie alterată de alte funcții ale programului, ca în cazul variabilelor globale;

– este o soluție de alocare a unor variabile locale, dacă spațiul disponibil pe stivă devine o problemă.

Următorul program (variabile_statice.cpp) ilustrează utilizarea unei variabile locale statice în cadrul unei funcții. Funcția numara() conține variabila locală statică y.

La primul apel al funcției numara(), variabila y este inițializată cu valoarea 100, apoi valoarea variabilei este afișată și incrementată cu 1.

La al doilea și al treilea apel al funcției numara(), variabila y, fiind statică, nu mai este nici alocată, nici inițializată, ci își păstrează valoarea de la apelul precedent (adică valoarea 101). Această valoare va fi afișată și apoi incrementată. Dacă nu s-ar fi declarat variabila y utilizând specificatorul static, funcția numara () ar fi afișat întotdeauna valoarea 100 și ar fi actualizat valoarea variabilei y numai la 101.

#include <iostream.h>

void numara()

{ static int y = 100;

cout << "Se afiseaza numarul " << y << endl;

y++ ;}

void main ()

{ numara ();

numara ();

numara ();}

Utilizarea stivei (stack) de către funcții

Funcțiile utilizează o zonă de memorie specială numită stivă. Denumirea este dată de modul de funcționare a acestei zone de memorie și anume LIFO (ultimele informații stocate sunt primele informații extrase). La apelarea unei funcții, compilatorul alocă spațiu de memorie pe stivă și plasează în stivă:

– adresa instrucțiunii care urmează apelării funcției (denumită adresă de revenire);

– valorile parametrilor reali ai funcției, de la dreapta la stânga.

În final, dacă funcția declară variabile locale, compilatorul alocă în stivă un spațiu pe care funcția îl utilizează pentru a stoca valorile variabilelor locale respective.

Mai jos, se arată modul în care compilatorul utilizează stiva în cazul unui apel simplu de funcție:

La încheierea execuției funcției, zona de memorie alocată pe stivă pentru apel este eliberată. Ca urmare a acestui mod de funcționare a stivei, chiar dacă în corpul funcției sunt modificate valorile parametrilor reali, la ieșirea din funcție, valorile modificate se pierd.

Transferul parametrilor prin valoare (apelul prin valoare)

În mod implicit, când se transmite un parametru real unei funcții, compilatorul folosește tehnica cunoscută sub numele de transferul parametrilor prin valoare. Prin această tehnică, orice modificare a unui parametru transmis în funcția apelată va exista numai în interiorul funcției apelate. La sfârșitul execuției funcției, valorile parametrilor, care i-au fost transmiși, rămân nemodificate în funcția apelantă.

De exemplu, următorul program (transfer_prin_valoare.cpp) transmite trei parametri (variabilele a, b, c) funcției afis_si_modif, care va afișa valorile, le va adăuga valoarea 100 și apoi va afișa rezultatul. După execuția funcției, programul va afișa valorile parametrilor. Deoarece se folosește tehnica de transfer a parametrilor prin valoare, funcția nu modifică valorile parametrilor în cadrul funcției apelante (functia main()).

#include <stdio.h>

void afis_si_modif (int prima, int a_doua, int a_treia)

{

printf("Valorile originale ale functiei: %d %d %d\n", prima, a_doua, a_treia);

prima += 100;

a_doua += 100;

a_treia += 100;

printf("Valorile finale ale functiei: %d %d %d\n",

prima, a_doua, a_treia);

}

void main (void)

{

int a = 1, b = 2, c = 3;

afis_si_modif (a, b, c);

printf("Valorile finale in main(): %d %d %d\n", a, b ,c);

}

După execuția programului, la consolă sunt afișate următoarele:

Valorile originale ale funcției: 1 2 3

Valorile finale ale funcției: 101 102 103

Valorile finale în main(): 1 2 3

Explicație: După cum se știe, fiecare variabilă are două atribute importante: valoarea sa curentă și adresa de memorie. În cazul programului prezentat mai sus, variabilele a, b, c ar putea utiliza adresele de memorie 1000, 1002 și 1004 (în cazul stocării lor pe doi octeți) astfel:

La transmiterea parametrilor către o funcție, compilatorul plasează valorile corespunzătoare în stivă. În cazul variabilelor a, b, c, stiva conține valorile 1, 2, 3.

Când funcția schimbă valorile unui parametru, modifică, de fapt, valorile din stivă, așa cum se arată în figura:

Când se încheie execuția funcției, compilatorul descarcă valorile din stivă și nu mai sunt luate în considerare modificările parametrilor pe care funcția le-a efectuat în locațiile stivei.

Transferul parametrilor prin referință (apelul prin referință)

Folosind transferul prin valoare, funcțiile nu pot modifica valorile parametrilor transmiși.

Totuși apare frecvent necesitatea de a modifica valorile parametrilor funcției și de a utiliza în afara funcției valorile modificate. În acest caz este necesar să se transmită funcției adresele de memorie ale parametrilor, adică să se realizeze transferul prin referință.

Pentru a transmite parametrii prin referință pot fi folosite două modalități:

a) cu ajutorul pointerilor (adreselor de memorie) – soluție utilizabilă atât în limbajul C cât și în C++;

b) cu ajutorul referințelor – soluție utilizabilă numai în limbajul C++.

A. Utilizarea pointerilor ca parametrii ai funcțiilor

Pentru a modifica valoarea parametrilor în cadrul unei funcții, se poate folosi apelul prin referință cu ajutorul pointerilor, adică utilizând adresele de memorie ale parametrilor funcției. Deci, în cadrul funcției trebuie să se folosească pointeri.

Următorul program (transfer_prin_pointeri.cpp) folosește transferul parametrilor prin referință cu ajutorul pointerilor pentru a afișa și apoi pentru a modifica parametrii transmiși funcției cu numele „afis_si_modif”:

#include <stdio.h>

void afis_si_modif (int *prima, int *a_doua, int *a_treia)

{

printf("Valorile originale ale functiei: %d %d %d\n", *prima, *a_doua, *a_treia);

*prima += 100;

*a_doua += 100;

*a_treia += 100;

printf("Valorile finale ale functiei: %d %d %d\n", *prima, *a_doua, *a_treia);

}

void main (void)

{int a = 1, b = 2, c = 3;

afis_si_modif (&a, &b, &c);

printf("Valorile finale in main(): %d %d %d\n", a, b ,c);}

După execuția programului, pe ecran se afișează următoarele:

Valorile originale ale funcției: 1 2 3

Valorile finale ale funcției: 101 102 103

Valorile finale în main(): 101 102 103

Folosirea combinată a apelului prin valoare și a apelului prin referință cu ajutorul pointerilor

În anumite cazuri, funcția trebuie să modifice valorile unor parametrii, dar lăsând nemodificate valorile altor parametrii transmiși.

De exemplu, următorul program (schimb_prim.cpp) folosește funcția „modif_x” pentru a modifica un parametru al funcției și pentru a lăsa neschimbat alt parametru:

#include <stdio.h>

void modif_x (int *x, int y)

{

*x = y; // Atribuire lui x valoarea lui y

}

void main (void)

{int a = 5, b = 10;

modif_x (&a, b);

printf("Valoarea lui \"a\" este %d valoarea lui \"b\" este %d\n", a, b);}

Utilizarea stivei la apelul prin referință cu ajutorul pointerilor

Compilatorul folosește stiva pentru a păstra parametrii, fie că se folosește transferul prin valoare sau prin referință cu ajutorul pointerilor.

Când se transmite un parametru prin valoare, compilatorul plasează valoarea parametrului în stivă.

Când se transmite parametrul prin referință, compilatorul plasează adresa parametrului în stivă. De aceea, o eventuală modificare a unui parametru în cadrul funcție, se face la adresa originală, modificarea rămânând și după terminarea execuției funcției.

De exemplu, atunci când programul prezentat anterior (schimb_prim.cpp) apelează funcția „modif_x”, compilatorul plasează adresa variabilei a și valoarea variabilei b în stivă, după cum se vede în figura de mai jos:

B. Utilizarea referințelor ca parametrii ai funcțiilor

Referință la o variabilă este utilizată numai în limbajul C++.

O referință la o variabilă reprezintă un alt nume pentru această variabilă (un alias, un sinonim)

Declararea unei variabile referință

Sintaxa utilizată pentru declararea unei variabile referință este:

<tip> & <variabila_referinta> = <variabila_referentiata>;

unde:

– <tip> – specifică tipul variabilei referențiate; poate fi orice tip;

– <variabila_referinta> – numele nou sau aliasul variabilei referențiate;

– <variabila_referentiata> – numele variabilei pentru care se crează un alias.

Efect: Declarația crează un alt nume (<variabila_referinta>) pentru variabila <variabila_referentiata> având tipul <tip>. Ulterior în program se poate utiliza atât variabila, cât și aliasul (referința) ei.

Variabilele referință și referită se găsesc, în memorie, la aceeași adresă și sunt variabile sinonime.

De exemplu:

int a = 100;

int &a_alias = a;

Dacă dorim să afișăm valoarea variabilei a putem scrie:

cout << a;

sau

cout << a_alias;

Valoarea afișată este 100.

Observații:

Nu se poate defini o variabilă referință în sine, care nu pornește de la o anumită variabilă.

Având în vedere semnificația unei referințe, putem interpreta un parametru formal de tip referință, ca un alt nume pentru parametrul real (efectiv). Deci în funcție se operează asupra aceleiași variabile, rezultând efectul de modificare în suprogram (funcție) a conținutului variabilei.

Deși referința seamănă cu o variabilă obișnuită, ea nu este însăși variabilă, ci doar o referire la variabila respectivă.

Să nu se facă confuzie între pointer și referință. Pointer-ul este o adresă de memorie către o anumită variabilă de un anumit tip. Pornind de la această adresă se poate accesa conținutul variabilei respective. Referința este un alt nume pentru o variabilă. Pornind de la referință se poate accesa conținutul variabilei.

Nu se pot declara pointeri către referințe.

Reguli pentru lucru cu referințe

1. Tipul referinței și tipul variabilei trebuie să fie aceleași;

2. Referințele trebuie să fie inițializate la declarație (spre deosebire de pointeri care pot fi inițializați în orice moment);

3. După inițializare, referința nu poate fi modificată pentru a referi o altă zonă de memorie (pointerii pot fi modificați pentru a referi altă zonă);

4. Într-un program C++ valid nu există referințe nule;

5. Prin compararea valorilor a două referințe, se compară, de fapt, valorile variabilelor referite;

5. Operațiile de incrementare, decrementare aplicate asupra variabilei-referință se vor aplica, de fapt, asupra valorii variabilei referite.

Programul următor (Variabile_referinta.cpp) creează două aliasuri și le utilizează pentru a afișa valorile și adresele variabilelor specificate.

#include <iostream.h>

#include <conio.h>

void main()

{

int a=10;

int& a_alias=a;

float b=42.76;

float& b_alias=b;

cout<<"Valoarea lui a e "<<a<<" aliasul e "<<a_alias<<endl;

cout<<"Adresa lui a e "<<&a<<" , iar a aliasului e "<<&a_alias;

cout<<"\nValoarea lui b e "<<b<<" aliasul e "<<b_alias;

a_alias++;

cout<<"\nValoarea lui a e "<<a<<" aliasul e "<<a_alias;

getch();

}

După execuția programului pe ecran se afișează:

Valoarea lui a e 10 aliasul e 10

Adresa lui a e 18ff50 , iar a aliasului e 18ff50

Valoarea lui b e 42.76 aliasul e 42.76

Valoarea lui a e 11 aliasul e 11

Transmiterea unei referințe către o funcție

Avantajul utilizării referințelor ca parametrii transferați către o funcție este acela că se beneficiază de toate avantajele pointerilor (posibilitatea de a modifica valorile parametrilor) fără complicații de sintaxă.

Următorul program (transfer_prin_referinte.cpp) folosește transferul parametrilor prin referință cu ajutorul referințelor pentru a afișa și apoi pentru a modifica parametrii transmiși funcției “afis_si_modif”:

#include <iostream.h>

#include<conio.h>

void afis_si_modif (int &prima, int &a_doua, int &a_treia)

{

cout<<"Valorile originale ale functiei:"<<prima<<a_doua<<a_treia<<endl;

prima += 100;

a_doua += 100;

a_treia += 100;

cout<<"Valorile finale ale functiei:"<<prima<<" "<<a_doua<<" "<<a_treia<<endl;

}

void main ()

{int a = 1, b = 2, c = 3;

afis_si_modif (a, b, c);

cout<<"Valorile finale in main(): "<<a<<" "<<b<<" "<<c;

getch();}

Observație:

După cum se observă, în definiția funcției afis_si_modif() s-au folosit ca parametrii formali, variabile-referință. La apelul funcției, parametrii reali, transmiși către funcție, sunt variabilele a, b, c. În funcția apelată se operează asupra acelorași trei variabile a, b, c, prin intermediul parametrilor de tip referință , prima, a_doua, a_treia, care sunt aliasuri ale acestora.

Utilizarea variabilelor-referință este de preferat chiar și când se face transmiterea unui parametru prin valoare. Acest lucru se întâmplă când variabila transmisă ca parametru este o structură de date voluminoasă.

Dacă se dorește să se evite modificarea valorii unui parametru transmis prin referință, atunci se poate utiliza cuvântul-cheie const care să preceadă declarația parametrului. Astfel, se inhibă modificarea parametrului respectiv. Orice tentativă de a modifica valoarea unui parametru transmis prin referință a cărui declarație este precedată de const va fi sancționată cu eroare la compilare.

De exemplu, dacă se modifică antetul funcției “afis_si_modif” din exemplul anterior astfel:

void afis_si_modif (const int &prima, int &a_doua, int &a_treia)

Nu se poate modifica valoarea parametrului prima (atribuirea: prima += 100 va genera eroare).

Cel mai indicat este, dacă nu se dorește să se modifice, în funcția apelată, valoarea unui parametru, să se facă transmisia prin valoare.

Următorul program (suma_referinte.cpp) calculează o expresie de forma:

1-2+3-4……+-n, unde n este un întreg dat de la tastatură.

Funcția va transmite rezultatul prin linia de parametri prin referințe.

#include <iostream.h>

#include <conio.h>

void suma(int &s,int n) {

s=0;

int semn=1;

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

{

s+=semn*i;

semn=-semn;

}

}

void main() {

int s,n;

cout<<"Dati n: ";

cin>>n;

suma(s,n);

cout<<"Suma este: "<<s<<'.'<<endl;

getch();}

Transmiterea parametrilor de tip tablou unidimensional

La definirea/declararea unei funcții, se poate utiliza pentru declararea parametrului formal de tip tablou una din construcțiile echivalente:

<tip> * <nume_variabila> sau

<tip> <nume_variabila> [] sau

<tip> <nume_variabila> [<max>]

unde:

– <tip> – specifică tipul elementelor tabloului unidimensional;

– <nume_variabila> – specifică numele parametrului formal de tip tablou unidimensional;

– <max> – specifică numărul maxim de elemente din tabloul unidimensional.

Notă: Aceste construcții sunt echivalente deoarece numele unui tablou este un pointer constant către primul element al tabloului.

La apelul unei funcții, care are parametrii de tip tablou unidimensional, trebuie să se transmită doar numele tabloului, deoarece este el însuși o adresă. Compilatorul plasează în stivă adresa de început a tabloului și nu elementele tabloului.

Transmiterea parametrilor de tip tablou bidimensional

La definirea/declararea unei funcții, se poate utiliza pentru declararea parametrului formal de tip tablou una din construcțiile echivalente:

<tip> * <nume_variabila> sau

<tip> * <nume_variabila> [] sau

<tip> <nume_variabila> [] [<max2>] sau

<tip> <nume_variabila> [<max1>] [<max2>]

unde:

– <tip> – specifică tipul elementelor tabloului bidimensional;

– <nume_variabila> – specifică numele parametrului formal de tip tablou bidimensional;

– <max1> – specifică numărul maxim de linii din tabloul bidimensional;

– <max2> – specifică numărul maxim de coloane din tabloul bidimensional.

La apelul unei funcții, care are parametrii de tip tablou bidimensional, trebuie să se transmită doar numele tabloului, deoarece este el însuși o adresă.

Următorul program (funcții_tablouri.cpp) ilustrează modul de definire și de apel pentru două funcții, ce utilizează parametrii-tablouri, care efectuează următoarele:

– generează toate numerele prime mai mici decât n;

– calculează produsul elementelor de deasupra diagonalei unei matrice pătratice cu n linii și n coloane.

Parametrul care reprezintă matricea (matr) este transmis în funcția prod_sus_dig () sub formă de tablou de pointeri:

Notă:

Generarea tuturor numerelor prime mai mici decât n s-a realizat folosind metoda ciurul lui Eratostene.

Metoda constă în “a pune în ciur” toate numerele mai mici decât n, apoi de “a cerne” aceste numere până rămân în ciur numai numerele prime. Astfel, mai întâi “cernem” (eliminăm din ciur) toți multipli lui 2, apoi cernem multipli lui 3 ș.a.m.d.

Ciurul este reprezentat ca un vector cu n componente care pot fi 0 sau 1, cu semnificația:

ciur[i] = 1, dacă numărul i este în ciur (este prim) și

ciur[i] = 0 dacă numărul i nu este în ciur (nu este prim).

Vectorul ciur se parcurge de la 2 (cel mai mic număr prim) până la .

Transmiterea parametrilor prin pointeri

#include <iostream.h>

#include<conio.h>

void gen_nr_prim (int *, int );

double prod_sus_dig (int **, int );

void main ()

{int *a, nr;

int **matr; // adresa vectorului de pointeri la linii

int lin, col, aloc = 0;

int inum = 1;

int i, j;

double produs;

cout << "Introduceti numarul n pana la care se vor genera numere prime: ";

cin >> nr;

a = new int [nr]; //alocare dinamica a tabloului unidimensional

if (a)

{

gen_nr_prim (a, nr);

delete a;

}

else

cout << "Alocare esuata";

cout << "Introduceti numarul de linii ale matricei patratice "; cin >> lin;

col = lin;

matr = new int *[lin]; // alocare zona pentru pointerii de linie

if (!matr)

{

cout << "Alocare esuata\n";

aloc = 1;

}

else

{

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

{

matr[i] = new int[col]; //alocare zona pentru elementele de pe linie

if (!matr[i])

{

cout << "Alocare esuata\n";

aloc = 1;

}

else

aloc = 0;

}

}

if (!aloc)

{

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

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

matr[i][j]=inum++;

produs = prod_sus_dig (matr, lin);

cout << "Produsul elementelor de deasupra diagonalei principale este " << produs;

delete *matr; //dealocarea zonei elementelor liniei

delete matr; //dealocarea zonei pointerilor de linie

}

getch();

}

double prod_sus_dig (int **a, int n)

{int i, j;

double p = 1;

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

for (j=i+1; j<n; j++)

p = p*a[i][j];

return p;}

void gen_nr_prim (int* ciur, int n)

{

int i, j;

for (i=2; i<n; i++) //initial toate numerele sunt in ciur, presupuse prime

ciur [i] = 1;

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

if (ciur[i]) //i este prim

for (j=2; j*i<n; j++) // se elimina toti multiplii lui i

ciur[i*j] = 0;

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

if (ciur[i])

cout << "Numarul prim generat este " << i << endl;

}

Transmiterea parametrilor prin referințe la tablouri unidimensionale și tablouri de pointeri unidimensionali pentru matrice

Următorul program (functii_tablouri_referinte1.cpp) este o variantă a primului program, dar parametrul care reprezintă vectorul (a) este transmis în funcția gen_nr_prim () ca referință la un tablou unidimensional (în C++ tablourile unidimensionale sunt implicit de tip referință), iar parametrul care reprezintă matricea (matr) este transmis în funcția prod_sus_dig () ca un tablou de pointeri:

#include <iostream.h>

#include<conio.h>

void gen_nr_prim (int [], int );

double prod_sus_dig (int *[], int );

void main (void)

{int *a, nr;

int **matr; // adresa vectorului de pointeri la linii

int lin, col, aloc = 0;

int inum = 1;

int i, j;

double produs;

cout << "Introduceti numarul n pana la care se vor genera numere prime: ";

cin >> nr;

a = new int [nr];

if (a)

{

gen_nr_prim (a, nr);

delete a;}

else

cout << "Alocare esuata";

cout << "Introduceti numarul de linii ale matricei patratice "; cin >> lin;

col = lin;

matr = new int *[lin]; // alocare zona pentru pointerii de linie

if (!matr)

{cout << "Alocare esuata\n";

aloc = 1;}

else

{

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

{matr[i] = new int[col]; // alocare zona pentru elementele de pe linie

if (!matr[i])

{cout << "Alocare esuata\n";

aloc = 1;}

else

aloc = 0;

}

}

if (!aloc)

{

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

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

matr[i][j]=inum++;

produs = prod_sus_dig (matr, lin);

cout << "Produsul elementelor de deasupra diagonalei principale este " << produs;

delete *matr; //dealocarea zonei elementelor liniei

delete matr; //dealocarea zonei pointerilor de linie

}

getch();

}

void gen_nr_prim (int ciur[], int n)

{int i, j;

for (i=2; i<n; i++) //initial toate numerele sunt in ciur, presupuse prime

ciur [i] = 1;

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

if (ciur[i]) //i este prim

for (j=2; j*i<n; j++) // se cern toti multiplii lui i

ciur[i*j] = 0;

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

if (ciur[i])

cout << "Numarul prim generat este " << i << endl;

}

double prod_sus_dig (int * a[], int n)

{int i, j;

double p = 1;

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

for (j=i+1; j<n; j++)

p = p*a[i][j];

return p;

}

Transmiterea parametrilor prin referințe la tablouri unidimensionale și bidimensionale

Următorul program (functii_tablouri_referinte2.cpp) este o variantă a primului program, dar parametrul care reprezintă vectorul (a) este transmis în funcția gen_nr_prim() ca referință la un tablou unidimensional (în C++ tablourile unidimensionale sunt implicit transmise ca referințe) iar parametrul care reprezintă matricea (matr) este transmis în funcția prod_sus_dig () ca un tablou unidimensional cu elemente care sunt tablouri unidimensionale dar care au dimensiune fixată.

#include <iostream.h>

#include <conio.h>

void gen_nr_prim (int [], int );

double prod_sus_dig (int [][10], int );

void main ()

{int *a, nr;

int matr [10][10];

int lin, col;

int inum = 1;

int i, j;

double produs;

cout << "Introduceti numarul n pana la care se vor genera nr. prime: "; cin >> nr;

a = new int [nr];

if (a)

{

gen_nr_prim (a, nr);

delete a;

}

else

cout << "Alocare esuata";

cout << "Introduceti numarul de linii ale matricei patratice "; cin >> lin;

col = lin;

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

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

matr[i][j]=inum++;

produs = prod_sus_dig (matr, lin);

cout << "Produsul elementelor de deasupra diagonalei principale este " << produs;

getch();

}

void gen_nr_prim (int ciur[], int n)

{int i, j;

for (i=2; i<n; i++) //initial toate numerele sunt in ciur, presupuse prime

ciur [i] = 1;

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

if (ciur[i]) //i este prim

for (j=2; j*i<n; j++) // se cern toti multiplii lui i

ciur[i*j] = 0;

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

if (ciur[i])

cout << "Numarul prim generat este " << i << endl;

}

double prod_sus_dig (int a[][10], int n)

{int i, j;

double p = 1;

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

for (j=i+1; j<n; j++)

p = p*a[i][j];

return p;

}

Observație:

Atunci când se transmit tablouri unidimensionale ca parametri de funcții, indexul nu trebuie precizat. În cazul tablourilor multidimensionale, se ignoră doar valoarea primului index, următorii trebuind să aibă valori. Compilatorul folosește aceste dimensiuni pentru a determina poziția în memorie a elementelor tabloului multidimensional.

Transmiterea parametrilor de tip structură către o funcție prin pointeri

La fel ca orice variabilă, compilatorul de C/C++ permite transmiterea unei structuri către o funcție.

Reamintim că, pentru a modifica membrii structurii în cadrul unei funcții, trebuie să se transmită către funcție un pointer la structură.

Pentru dereferențierea membrilor pointerului la structură se folosește sintaxa:

(*pointer).membru = valoare; unde: pointer este un pointer de tip structură.

Compilatorul de C/C++ începe cu parantezele, obținând mai întâi locația structurii. Apoi, el adaugă la adresa deplasamentul membrului specificat.

Următorul program (functii_structuri.cpp), apelează funcția modific_structura() care modifică valorile membrilor dintr-o structură de tip Schita și afișează la ecran membrii structurii )în funcția main()):

#include <stdio.h>

#include<math.h>

struct Schita

{

int tip;

int culoare;

float raza;

float aria;

float perimetru;

};

void modific_structura(struct Schita *schita)

{

(*schita).tip = 0;

(*schita).culoare = 1;

(*schita).raza = 5.0;

(*schita).aria = M_PI * (*schita).raza * (*schita).raza;

(*schita).perimetru = 2*M_PI* (*schita).raza;

}

void main()

{

struct Schita cerc;

modific_structura(&cerc);

printf("cerc.tip %d\n", cerc.tip);

printf("cerc.culoare %d\n", cerc.culoare);

printf("cerc.raza %f\ncerc.aria %f\ncerc.perimetru %f\n",

cerc.raza, cerc.aria, cerc.perimetru);

}

Notă:

1. Se observă că tipul structură Schita este creată în afara funcției main () deoarece ea trebuie să fie utilizabilă atât de funcția main () cât și de funcția modific_structura().

2. Pentru a modifica membrii structurii în cadrul unei funcții trebuie să se transmită structura prin adresă (la fel ca și la transmiterea unei variabile obișnuite care se dorește a fi modificată). În cazul de față, parametrul de tip structură transmis funcției modific_structura() este de forma &cerc.

Transmiterea parametrilor de tip structură către o funcție prin referință la o variabilă în C++

Următorul program (functii_structuri_ref.cpp), realizează aceleași operații ca programul precedent dar folosește referința la variabila de tip Schita.

#include <iostream.h>

#include<conio.h>

#include<math.h>

struct Schita

{

int tip;

int culoare;

float raza;

float aria;

float perimetru;

};

void modific_structura(struct Schita &schita)

{

schita.tip = 0;

schita.culoare = 1;

schita.raza = 5.0;

schita.aria = M_PI * schita.raza * schita.raza;

schita.perimetru = 2 * M_PI * schita.raza;}

void main()

{

struct Schita cerc;

modific_structura(cerc);

cout<<"cerc.tip "<< cerc.tip<<endl;

cout<<"cerc.culoare "<< cerc.culoare<<endl;

cout<<"cerc.raza "<< cerc.raza<<endl;

cout<<"cerc.aria "<<cerc.aria<<endl;

cout<<"cerc.perimetru "<<cerc.perimetru<<endl;

getch();

}

Crearea funcțiilor cu un număr variabil de parametrii cu tipuri multiple

Pentru a crea funcții care acceptă un număr variabil de parametrii se pot utiliza macroinstrucțiunile va_start, va_arg și va_end, definite în fișierul antet stdarg.h.

Cum funcționează macroinstrucțiunea va_start ?

Se știe că, atunci când programul apelează o funcție, compilatorul plasează parametrii în stivă de la dreapta la stânga. În cadrul unei funcții cu un număr variabil de parametrii, macroinstrucțiunea va_start inițializează lista de parametrii ai funcției la primul parametru fixat din lista variabilă (atribuie unui pointer adresa primului parametru fixat din lista variabilă).

Macroinstrucțiunea este definită în fișierul antet stdarg.h astfel:

void va_start(va_list ap, lastfix);

unde:

– ap – specifică pointerul care memorează adresa primului parametru fixat al funcției; acest pointer este declarat cu tipul va_list specificat în fișierul antet stdarg.h;

– lastfix – specifică primul parametru fixat din lista variabilă de parametrii ai funcției.

Cum funcționează macroinstrucțiunea va_arg ?

Macroinstrucțiunea va_arg returnează valoarea indicată de pointerul fiecărui parametru din lista variabilă a funcției. Pentru a determina valoarea, macroinstrucțiunea trebuie să cunoască tipul de dată al parametrului. După regăsirea valorii, macroinstrucțiunea va incrementa pointerul, astfel încât să indice următorul parametru din stivă. Pentru a determina numărul de octeți adăugați pointerului, macroinstrucțiunea va folosi din nou tipul de dată al parametrului.

Macroinstrucțiunea este definită în fișierul antet stdarg.h astfel:

tip va_start(va_list ap, tip)

unde:

– tip – specifică tipul de dată al parametrului funcției.

Cum funcționează macroinstrucțiunea va_end ?

După ce macroinstrucțiunea va_arg găsește ultimul parametru al funcției, macroinstrucțiunea va_end șterge lista de parametrii din stivă.

Macroinstrucțiunea este definită în fișierul antet stdarg.h astfel:

void va_end(va_list ap);

Observații:

1. În antetul funcției care are un număr variabil de parametrii se trec puncte de suspensie (…) pentru a indica un număr variabil de parametrii.

De exemplu:

double ad_valori(char *sir, …)

2. În corpul funcției care are un număr variabil de parametrii se declară o variabilă-pointer de tip va_list (tip specificat în fișierul antet sdtarg.h). Această variabilă-pointer este folosită la apelul macroinstrucțiunilor va_start, va_arg și va_end.

Următorul program (functie_param_variabili.cpp) folosește un număr variabil de parametrii (care acceptă valori de toate tipurile) pentru a aduna toate valorile transmise funcției ad_valori() de către funcția apelantă. Funcția returnează o valoare de tip double. Pentru a se putea determina tipul parametrilor, se transmite funcției un specificator de format similar cu cel din printf.

De exemplu, pentru a aduna valori întregi și în virgulă mobilă, se folosește următorul model de apel:

sum = ad_valori("%f %d %f %d", 1.1, 1, 2.2, 3);

Antetul funcției ad_valori() este de forma:

double ad_valori(char *sir, …)

#include <stdio.h>

#include <stdarg.h>

double ad_valori(char *sir, …)

{va_list marcator;

double suma = 0.0;

va_start(marcator, sir); // marcheaza primul parametru transmis

while (*sir) // examineaza fiecare caracter din sir

{

if (*sir == '%') // daca nu este un specificator de format,%_, sarim peste

{

switch (*(++sir))

{

case 'd': suma += va_arg(marcator, int);

break; // marcheaza urmatorul parametru transmis

case 'f': suma += va_arg(marcator, double);

break; // marcheaza urmatorul parametru transmis

}

}

sir++;

}

va_end(marcator); // anuleaza valoarea pointerului parametrului

return(suma);}

void main(void)

{double sum;

sum = ad_valori("%f", 3.3);

printf("Rezultatul este %f\n", sum );

sum = ad_valori("%f %f", 1.1, 2.2);

printf("Rezultatul este %f\n",sum );

sum = ad_valori("%f %d %f", 1.1, 1, 2.2);

printf("Rezultatul este %f\n", sum);

sum = ad_valori("%f %d %f %d", 1.1, 1, 2.2, 3);

printf("Rezultatul este %f\n", sum);}

Parametrii funcției main ()

Funcția main () este apelată automat la lansarea în execuție a programului. Funcția main () acceptă și ea parametrii. Parametrii funcției main () sunt transmiși la lansarea în execuție a programului din sistemul de operare în mod linie de comandă. Argumentele din linia de comandă sunt succesiuni de caractere separate prin spații sau caractere tab. Ele pot varia funcție de necesitățile programului.

Formatul general al unei linii de comandă în sistemul de operare DOS este:

c:\> nume_program argument1 argument2 …

Pentru funcția main () se pot utiliza trei parametrii:

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

unde:

– argc – specifică numărul de argumente din linia de comandă (argc >0);

– argv – este un vector de pointeri de tip caracter la cele argc argumente din linia de comandă (argv[0] este numele programului);

– env – este un vector de pointeri de tip caracter la informații de mediu ale sistemului de operare (de exemplu, prompterul sistemului, căi implicite de căutare), ultimul element al vectorului fiind NULL, pentru a marca sfârșitul listei.

De exemplu, programul următor (arg_main.cpp) permite trei opțiuni în linia de comandă și anume /l sau /t sau /s. Pentru linia de comandă:

>arg_main /l

parametrul argc are valoarea 2, iar pointerii argv[0] și argv[1] referă șirurile “arg_main” și respectiv “/l”.

#include <stdio.h>

main ( int argc, char *argv[])

{

if (argc!=2)

{

printf ("Utilizare program: %s /l sau /t sau /s\n", argv[0]);

return 1;

}

if (argv [1][0] == '/')

switch (argv[1][1])

{

case 'l' : printf("optiunea /l"); break;

case 't' : printf("optiunea /t"); break;

case 's' : printf("optiunea /s"); break;

default : printf ("Optiune ilegala pentru %s\n", argv[0]);

return 1;

}

else

{

printf("Optiune nespecificata pentru %s\n", argv[0]);

return 1;

}

}

Execuția programului pentru diferite linii de comandă afișează:

C:\mariana\Limbaj C\programe>arg_main

Utilizare program: C:\mariana\Limbaj C\programe\arg_main.exe /l sau /t sau /s

C:\mariana\Limbaj C\programe>arg_main /l

optiunea /l

C:\mariana\Limbaj C\programe>arg_main /t

optiunea /t

C:\mariana\Limbaj C\programe>arg_main /s

optiunea /s

C:\mariana\Limbaj C\programe>arg_main /

Optiune ilegala pentru C:\mariana\Limbaj C\programe\arg_main.exe

C:\mariana\Limbaj C\programe>arg_main l

Optiune nespecificata pentru C:\mariana\Limbaj C\programe\arg_main.exe

Lecția 9

Cuvinte importante:

– recursivitate;

– recursivitate și iterativitate;

– funcții recursive în C/C++; mecanismul prin care funcțiile se

pot autoapela;

– recursivitate directă;

– recursivitate indirectă;

– Ce este mai indicat de utilizat: recursivitate sau iterativitate?

9.1 Recursivitate

Un obiect sau un fenomen se definește în mod recursiv dacă în definiția sa există o referire la el însuși.

Utilitatea practică a recursivității: posibilitatea de a defini un set infinit de obiecte printr-o singură relație sau printr-un set finit de relații.

Recursivitatea s-a impus în programarea calculatoarelor odată cu apariția unor limbaje de nivel înalt, ce permit scrierea de subprograme ce se autoapelează:

PASCAL, LISP, ADA, ALGOL, C – limbaje recursive

FORTRAN,BASIC,COBOL – limbaje nerecursive

Cum gândim în termeni de recursivitate?

Un exemplu ar fi definirea conceptului de strămoș al unei persoane:

• Un părinte este stramoșul copilului. ("Baza"')

• Parinții unui stramoș sunt și ei stramoși ("Pasul de recursie").

O gândire recursivă exprimă concentrat o anumită stare, care se repetă la infinit.

Cunoaștem că, un algoritm este o succesiune de pași care transformă mulțimea datelor de intrare în date de ieșire (rezultatele dorite).

Plecând de la această definiție, să enumerăm câteva proprietăți ale unui algoritm:

să posede date de intrare;

să posede date de ieșire;

determinismul, adică la executarea oricărui pas, trebuie să cunoaștem succesorul acestuia;

corectitudinea datelor de ieșire (a rezultatelor) în raport cu datele de intrare;

finitudinea, adică pentru orice set de date de intrare posibile ale problemei, soluția este furnizată într-un număr finit de pași.

Un algoritm este implementat recursiv atunci când în interiorul său există subprobleme care apelează la același algoritm. Rezultă că recursivitatea este un mecanism general de elaborare a unui algoritm implementat recursiv.

Rezultă că, o gândire recursivă se aplică în elaborarea algoritmilor implementați recursiv cu o modificare esențială: adăugarea condiției de terminare. În absența acestei condiții nu se poate vorbi despre un algoritm deoarece acesta posedă proprietatea de finitudine.

Din acest punct de vedere, exemplul din slide-ul precedent nu este corect.

9.2 Recursivitate si iterativitate

Iterativitatea înseamna execuția repetată a unei porțiuni de program, până la îndeplinirea unei condiții (folosind instrucțiuni ca: while, do-while , for din C/C++). Deci, iterativitatea înseamnă folosirea structurilor de control repetitive “execută un set de instrucțiuni până când”.

Recursivitatea înseamnă:

– execuția repetată a unui întreg subprogram, funcție sau metodă;

– în cursul execuției lui se verifică o condiție (if din C/C++) ;

– nesatisfacerea condiției implică reluarea execuției subprogramului de la început, fără ca execuția curentă a acestuia să se fi terminat,

– în momentul satisfacerii condiției se revine în ordine inversă în lanțul de apeluri, reluându-se și încheindu-se apelurile suspendate.

În concluzie, recursivitatea înlocuiește structurile de control repetitive.

O precizare importantă:

Algoritmii implementați iterativ se pot transforma în algoritmi implementați recursiv (pentru o structură repetitivă se crează un suprogram recursiv) și invers (se poate folosi structura de date stiva care simulează apelurile recursive).

9.3 Funcții recursive în C/C++

Recursivitatea se exprimă cu ajutorul funcțiilor deoarece ele au un nume care este specificat în apeluri.

O funcție se numește recursivă dacă ea se autoapelează.

După tipul apelului, o funcție recursivă se autoapelează:

fie direct (în definiția ei, se face apel la ea însăși),

fie indirect (adică funcția X apelează funcția Y, care apelează funcția X).

Orice funcție recursivă poate fi scrisă și în forma nerecursivă (folosind structurile de control repetitive).

9.4 Mecanismul prin care funcțiile se pot autoapela

Se știe că la apelul fiecărei funcții, se generează un nou nivel în segmentul de stivă, corespunzător acelui apel. Pe acel nivel se memorează :

–         valorile parametrilor transmiși prin valoare;

–         adresele parametrilor transmiși prin referință;

–         variabilele locale;

–         adresa de revenire din funcție.

De fiecare dată când o funcție se autoapelează, se creează un nou nivel în segmentul de stivă care se suprapune peste vechiul nivel.

9.5 Recursivitate directă

Există mai multe feluri de funcții recursive directe:

– funcții cu un singur apel recursiv (în definiție); de exemplu: calculul factorialului, suma cifrelor unui număr etc;

– funcții cu două sau mai multe apeluri recursive (în definiție); de exemplu numerele Fibonacci, turnurile din Hanoi etc.

9.6 Funcții cu un singur apel recursiv (în definiție)

De exemplu: să se calculeze n!

Se observă că n !=(n-1) !*n si se știe că 0 !=1.

Definiția recursivă a lui n ! este :

Funcția recursivă se va scrie astfel:

long factorial(int n)

      {if(n==0) return 1;

       else return factorial(n-1)*n;}

void main()

      {cout<<factorial(5) ;}

Se observă că funcția factorial se autoapelează. Autoapelul se realizează prin instrucțiunea return factorial(n-1)*n.

Funcția nerecursivă se va scrie astfel:

int fact_it (int n) {

int i, f;

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

f *= i;

return f;

}

În figura de mai jos este prezentat fiecare nivel de apel în segmentul de stivă pentru funcția recursivă factorial(n), unde n=5.

Observație:

Fiecare apel de funcție lucrează cu datele aflate pe nivelul corespunzător acelui apel din segmentul de stivă. La ieșirea din apelul unei funcții, nivelul respectiv se eliberează și datele aflate acolo se pierd.

Un alt exemplu:

Să se scrie o funcție recursivă care calculează și returnează suma cifrelor unui număr natural care este primit ca parametru (suma_cifre.cpp). Formula folosită pentru recursie este: suma_cifre(n) = suma_cifre(n/10)+n%10.

#include<iostream.h>

#include<conio.h>

int sumacif(long n)

{int static i=0;

if(n==0) return 0;

else

{cout<<"n="<<n<<" "<<" nr. apel ="<<++i<<endl;

return sumacif(n/10)+n%10;}}

void main()

{int nr;

cin>>nr;

cout<<"Suma cifrelor nr. ="<<sumacif(nr);

getch();}

După execuția programului, pe ecran se afișează:

introduceti numarul:23562

n=23562 nr. apel =1

n=2356 nr. apel =2

n=235 nr. apel =3

n=23 nr. apel =4

n=2 nr. apel =5

Suma cifrelor nr. =18

În figura de mai jos este prezentat fiecare nivel de apel în segmentul de stivă pentru funcția recursivă sumacif(nr) , unde nr=23562.

Dacă rezolvăm aceeași problemă în varianta nerecursivă (folosind instrucțiuni repetitive), programul arată astfel:

#include<iostream.h>

#include<conio.h>

void main(){

int nr;

int suma = 0;

cout<<"Introduceti numarul:" ;

cin>>nr;

do {

suma=suma+nr%10; //aflare ultima cifra a numarului si adaugare la suma

nr/=10; // eliminare ultima cifra

cout<<nr<<endl;

} while (nr);

cout<<"Suma cifrelor nr. ="<< suma;

getch();

}

Un alt exemplu (sir_invers.cpp) de funcție recursivă este inversarea unui șir de caractere. Dându-se un șir de caractere citit de la tastatură, funcția recursivă creez_invers () va crea un nou șir care este inversul șirului citit și apoi va afișa pe ecran șirul de caractere creat în ordine inversă. Funcția recursivă este de tip void.

#include <iostream.h>

void creez_invers(char *sir, char *sir1)

{static int i =0;

cout << "Sunt in functia creez_invers cu "<< sir << endl;

if (*sir)

{

creez_invers(sir+1, sir1);

sir1 [i] = *sir;

i++;

sir1 [i] = '\0';

cout << "Sfarsit apel recursiv cu " << sir1 << endl; }

}

void main()

{char sir_sursa[64];

char sir_destinatie[64];

cout << "Introduceti sirul de inversat:\n";

cin.getline (sir_sursa, sizeof(sir_sursa));

creez_invers(sir_sursa, sir_destinatie);

cout << sir_destinatie;}

După execuția programului, pe ecran se afișează:

Introduceti sirul de inversat:

adriana

Sunt in functia creez_invers cu adriana

Sunt in functia creez_invers cu driana

Sunt in functia creez_invers cu riana

Sunt in functia creez_invers cu iana

Sunt in functia creez_invers cu ana

Sunt in functia creez_invers cu na

Sunt in functia creez_invers cu a

Sunt in functia creez_invers cu // ultimul apel al functiei recursive

Sfarsit apel recursiv cu a

Sfarsit apel recursiv cu an

Sfarsit apel recursiv cu ana

Sfarsit apel recursiv cu anai

Sfarsit apel recursiv cu anair

Sfarsit apel recursiv cu anaird

Sfarsit apel recursiv cu anairda

anairda

În figura de mai jos este prezentat fiecare nivel de apel în segmentul de stivă pentru funcția recursivă creez_invers(sir_sursa, sir_destinatie) , unde sir_sursa = “adriana” iar conținutul variabilei sir_destinatie se formează din revenirile din apelurile recursive ale funcției.

Dacă rezolvăm aceeași problemă în varianta nerecursivă (folosind instrucțiuni repetitive), programul arată astfel:

#include<iostream.h>

#include<conio.h>

void main(){

int i, j ;

char sir_sursa [64];

char sir_destinatie[64];

cout<<"Introduceti sirul de inversat"<<endl;

cin.getline(sir_sursa,sizeof(sir_sursa));

j = strlen(sir_sursa)-1;

for (i = 0; i < strlen(sir_sursa); i++) {

sir_destinatie[i]=sir_sursa[j];

j–;

}

sir_destinatie[i]='\0';

cout<<sir_destinatie;

getch();

}

Exemplu de funcții ce calculează produsul primelor n numere naturale :

Funcția in varianta nerecursivă

#include<iostream.h>

int f(int n)

{int i=1, P=1;

while (i<=n)

{P=P*i ;

i++ ;}

return P ;}

void main()

{int n=5;

cout<<f(n);}

Funcția recursivă 1

#include<iostream.h>

int n;

int f(int i)

{if (i<=n)

return i*f(i+1);

else return 1 ;}

void main()

{n=5;

cout<<f(1)<<endl;

Funcția recursivă 2

#include<iostream.h>

int f(int i)

{if (i>1)

return i*f(i-1);

else return 1 ;}

void main()

{int n=5;

cout<<f(n);}

Se observă că la primele două subprograme condiția de continuare (a instrucțiunii repetitive respectiv a autoapelului) este aceeași.

Recomandare : înainte de elaborarea funcțiilor recusive este de preferat să se creeze mai întâi funcția nerecursivă apoi să se realizeze funcția recursivă.

Programul următor (nr_patru_recursiv.cpp) construiește pentru un număr natural, citit de la tastatură, succesiunea de numere care duce la numărul 4. Succesiunea de numere se afișează la ecran. Funcția recursivă se numește numar_patru().

De exemplu, pentru numarul 12, succesiunea de numere naturale afișate la ecran până la numărul 4 este: 4->2->24->12 sau

4->2->1->10->5->50->504->252->2524

#include<iostream.h>

#include<conio.h>

void numar_patru(int);

void main() {

int n;

cout<<"Introduceti numarul: ";

cin>>n;

cout<<"4" ;

numar_patru(n);

getch(); }

void numar_patru(int nr){

if(nr !=4){

switch (nr%10) {

case 0: numar_patru(nr/10); break;

case 4: numar_patru(nr/10); break;

default: numar_patru(nr*2); }

cout<<"->"<<nr; }

}

Programul următor (recursie_pozitive_matrice.cpp) calculează suma elementelor pozitive dintr-o matrice folosind varianta recursivă.

Funcția recursivă se numește calcul_pozitive() și returnează din fiecare apel suma elementelor de pe linia i la care se adaugă suma returnată din ultimile apeluri ale funcției până se ajunge la linia n-1.

#include<iostream.h>

#include<conio.h>

int calcul_pozitive(int [][10], int,int);

void main(){

int i, j, suma, n, m;

int a[10][10];

cin>>n;

m=n;

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

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

cin>>a[i][j];

}

suma = calcul_pozitive(a,n-1, m);

cout<<suma;

getch();

}

int calcul_pozitive(int matr[][10],int i,int m) {

int j=0;

int s=0;

if(i>=0) {

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

if(matr[i][j]>=0)

s=s+matr[i][j]; //calcul suma pe fiecare linie

cout<<s<<endl;

return s+calcul_pozitive(matr,i-1,m) ;

}

else

return 0;

}

Observații:

Orice funcție recursivă trebuie să conțină (cel puțin) o instrucțiune if (de obicei chiar la început), prin care se verifică dacă (mai) este necesar un apel recursiv sau se iese din funcție;

Absența instrucțiunii if conduce la o recursivitate infinită (la autoapeluri fără condiție de terminare) și segmentul de stivă se ocupă total; în acest caz programul se va termina cu eroarea «Stack overflow»;

Apelul funcției recursive este de obicei condiționat astfel:

void f_recursiva( ){

if (condiție) f_recursiva(); // apel finit conditionat

}

În funcțiile recursive repetarea operațiilor este obținută prin apelul recursiv;

Pe parcursul unui apel, sunt accesibile doar variabilele locale și parametrii pentru apelul respectiv, nu și cele pentru apelurile anterioare, chiar dacă acestea poartă același nume;

Atenție! Pentru fiecare apel recursiv al unei funcții se crează copii locale ale parametrilor valoare și ale variabilelor locale, ceea ce poate duce la risipă de memorie!

9.7 Funcții cu două sau mai multe apeluri recursive în definiție

Fie șirul lui Fibonacci, definit astfel:

fibo(n) = 0 , dacă n=0

fibo(n) = 1 ,daca n=1

fibo(n) = fib(n-1) + fib(n-2) , dacă n>1

Adică: 0, 1, 1, 2, 3, 5, 8, 13, ….

Fie n un număr natural citit de la tastatură. Să se calculeze folosind o funcție nerecursivă și o funcție recursivă cel de-al n-lea termen din șirul Fibonacci.

#include<stdio.h>

#include<conio.h>

int fibo_it (int); //functia nerecursiva

int fibo(int); //functia recursiva

int main (){

int n;

printf("Introduceti n=");

scanf ("%d", &n);

printf ("recursiv=%d nerecursiv=%d", fibo(n), fibo_it(n));

getch ();

return 0;

}

int fibo_it (int n) // varianta nerecursiva

{

int f1=0,f2=1,fn=1, i;

if (n==0)return 0;

if (n==1) return 1;

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

{

fn=f1+f2;

f1=f2;

f2=fn;

}

return fn;

}

int fibo (int n) // varianta recursiva

{int static i=1;

printf("Apel %d ",i++);

if ( n==0) {printf("n=%d \n",n);return 0;}

if ((n==1)){printf("n=%d \n",n);return 1; }

printf("n=%d \n",n);

return fibo(n-1)+ fibo(n-2) ;

}

În figura de mai jos este prezentat fiecare nivel de apel în segmentul de stivă pentru funcția recursivă fibo(n).

După execuția programului, pe ecran se afișează:

Introduceti n=5

Apel 1 n=5

Apel 2 n=4

Apel 3 n=3

Apel 4 n=2

Apel 5 n=1

Apel 6 n=0

Apel 7 n=1

Apel 8 n=2

Apel 9 n=1

Apel 10 n=0

Apel 11 n=3

Apel 12 n=2

Apel 13 n=1

Apel 14 n=0

Apel 15 n=1

recursiv=5 nerecursiv=5

9.8 Exemplu de algoritm de divizare – "divide et impera“ care implementează funcții cu două apeluri recursive în definiție

Algoritmul de divizare general constă în:

– descompunerea unei probleme complexe în mod recursiv în mai multe subprobleme a căror rezolvare e mai simplă;

– rezolvă subproblemele;

– combină soluțiile subproblemelor pentru obținerea soluției problemei inițiale.

Astfel:

void rezolva (problema x) {

if (x poate fi descompus în subprobleme) {

//divide pe x în parti x1,…,xk

rezolva(x1);

//…

rezolva(xk);

//combina solutiile partiale intr-o solutie}

else

//rezolva pe x direct}

Exemple: determinarea minimului și maximului valorilor elementelor unui tablou; căutarea binară; turnurile din Hanoi.

Programul următor (hanoi_recursiv.cpp) ilustrează modul de rezolvare prin recursivitate a problemei turnurilor din Hanoi.

Problema turnurilor din Hanoi este un joc vechi de origine asiatică, în care un anumit număr de discuri este plasat pe trei tije A, B, C. Discurile au diametre diferite și sunt găurite la mijloc, astfel încât să poată intra pe cele trei tije.

Inițial, toate discurile sunt situate pe tija A (sursa) .

Obiectivul jocului este de a transfera toate discurile de pe tija A (sursa) pe tija C (destinația).

Restricțiile jocului sunt:

a) la un moment dat, se poate deplasa numai un singur disc;

b) nici un disc nu poate fi așezat deasupra unui disc mai mic decât el.

Algoritmul recursiv de rezolvare a problemei turnurilor din Hanoi.

Dorim să deplasăm toate discurile de pe un turn sursă (notat cu S) pe unul destinație (notat cu D). Dispunem de un turn intermediar (notat cu I).

Presupunem ca avem n discuri pe turnul S, așezate de jos în sus în ordinea mărimii discurilor (discul cel mai mare – n este la bază și discul cel mai mic – 1 este in vârf).

Algoritmul de rezolvare este:

1. Primele n-1 discuri se deplasează de pe S pe I;

2. Discul rămas (cel mai mare) se deplasează de pe S pe D.

3. Primele n-1 discuri se deplasează de pe I pe D.

Inițial , turnul sursă este A, turnul intermediar este B, iar cel de destinație este C. Să presupunem că pe turnul inițial se găsesc 3 discuri.

Soluția se construiește pornind de la următoarea observație: pentru a muta n discuri de pe turnul A pe turnul C cu ajutorul turnului B, se mută mai întâi n-1 discuri de pe turnul A pe turnul B prin intermediul turnului C, după care se execută mutarea discului cel mai mare (de la bază) de pe discul A pe discul C, urmată de mutarea a n-1 discuri de pe turnul B pe turnul C prin intermediul turnului A.

Astfel se definește funcția recursivă hanoi:

În programul prezentat în slide-ul următor parametrii de apel ai funcției hanoi sunt apelați în ordinea : A este turnul sursa, B este turnul intermediar și C este turnul destinație.

Funcția hanoi() este un exemplu de funcție recursivă de tip void cu două apeluri în definiția funcției.

#include <iostream.h>

void hanoi (int, char, char, char);

void main()

{int nr;

cout << "Introduceti numarul de discuri: " ;

cin >> nr;

hanoi (nr, 'A', 'B', 'C');}

void hanoi (int n, char sursa, char inter, char desti) {

cout << "In functia hanoi cu n = " << n << " " <<endl;

cout << sursa << inter << desti << endl;

if (n==1)

cout << "Discul 1 de pe " << sursa << " pe " << desti << endl;

else

{hanoi (n-1, sursa, desti, inter); //muta discurile de pe sursa pe turnul intermediar

cout <<"Discul "<<n<<" de pe "<< sursa<<" pe "<<desti<<endl; //muta discul de la baza pe destinatie

hanoi (n-1, inter, sursa, desti); //muta discurile de pe turnul intermediar la destinatie

}

getch();

}

După execuția programului pe ecran se afișează:

Introduceti numarul de discuri: 3

In functia hanoi cu n = 3

ABC

In functia hanoi cu n = 2

ACB

In functia hanoi cu n = 1

ABC

Discul 1 de pe A pe C

Discul 2 de pe A pe B

In functia hanoi cu n = 1

CAB

Discul 1 de pe C pe B

Discul 3 de pe A pe C

In functia hanoi cu n = 2

BAC

In functia hanoi cu n = 1

BCA

Discul 1 de pe B pe A

Discul 2 de pe B pe C

In functia hanoi cu n = 1

ABC

Discul 1 de pe A pe C

Niveluri de apel din segmentul de stivă pentru funcția recursivă hanoi()

9.9 Recursivitate indirectă

Recursivitatea se poate realiza și în mod indirect, atunci când în cadrul “corpului” unei funcții apare apelul unei alte funcții care la rândul său apelează direct sau indirect funcția respectivă.
De exemplu, să presupunem că în funcția f1() intervine un apel al funcției f2(). În funcția f2() intervine un apel al funcției f1(). Se observă ca funcția f1() se autoapelează, prin intermediul funcției f2(). Prin urmare funcția f1() este indirect recursivă.

Observație:

Funcțiile apelate trebuie declarate, deoarece nu se pot evita declarațiile prin ordinea în care sunt definite funcțiile.

Exemplu:

void f1 ( )

{

void f2 ( ); // functia apelata de f1 trebuie declarata

f2( ); // apel f2

}

void f2 ( )

{

void f1 ( ); // functia apelata de f2 trebuie declarata

f1( ); // apel f1

}

void main() {

………

}

Altă variantă de scriere mai elegantă:

// declaratii functii

void f1 ( );

void f2 ( );

void main() {

………

}

// definitii functii

void f1 ( ) {

f2( ); // apel f2

}

void f2 ( ) {

f1( ); // apel f1

}

9.10 Exemplu de recursivitate indirectă

Fie șirul: a0, b0, a1, b1, a2, b2….., an, bn, unde: an > 0 și bn > 0

Următorul program (recursie_indirecta1.cpp) calculează an , bn și n termeni din șir, pentru a, b, n citite de la tastatură.

Pentru rezolvarea problemei se folosesc două funcții fa() si fb(). Fiecare dintre ele se autoapelează, dar o apelează și pe cealaltă.

#include<iostream.h>

#include<conio.h>

double a0,b0;

double fa(int n);

double fb(int n);

void main()

{

int n;

cout<<"n=";

cin>>n;

cout<<"a0=";

cin>>a0;

cout<<"b0=";

cin>>b0;

cout<<endl<<"a["<<n<<"]="<<fa(n)<<endl;

cout<<"b["<<n<<"]="<<fb(n)<<endl;

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

cout<<"a["<<i<<"]="<<fa(i)<<endl;

cout<<"b["<<i<<"]="<<fb(i)<<endl;}

getch();

}

double fa(int n)

{

static j = 1;

cout<<"Sunt in functia fa "<<"Apelul " << j++<<" n= "<< n<<endl;

if (n==0)

return a0;

else

return (fa(n-1)+fb(n-1))/2;

}

double fb(int n)

{

static k = 1;

cout<<"Sunt in functia fb "<<"Apelul " << k++<<" n= "<< n<<endl;

if (n==0)

return b0;

else

return fa(n-1)*fb(n-1);

}

După execuția programului pe ecran se afișează

n=2

a0=2

b0=4

Sunt in functia fa Apelul 1 n= 2

Sunt in functia fa Apelul 2 n= 1

Sunt in functia fa Apelul 3 n= 0

Sunt in functia fb Apelul 1 n= 0

Sunt in functia fb Apelul 2 n= 1

Sunt in functia fa Apelul 4 n= 0

Sunt in functia fb Apelul 3 n= 0

a[2]=5.5

Sunt in functia fb Apelul 4 n= 2

Sunt in functia fa Apelul 5 n= 1

Sunt in functia fa Apelul 6 n= 0

Sunt in functia fb Apelul 5 n= 0

Sunt in functia fb Apelul 6 n= 1

Sunt in functia fa Apelul 7 n= 0

Sunt in functia fb Apelul 7 n= 0

b[2]=24

Apelurile din main() din ciclul for pentru a[i], b[i] unde i=0, 1, 2

Dacă rezolvăm aceeași problemă în varianta nerecursivă (folosind instrucțiuni repetitive), programul arată astfel:

#include<iostream.h>

#include<conio.h>

void main()

{double a0,b0;

double an, bn;

int n;

cout<<"n=";

cin>>n;

cout<<"a[0]=";

cin>>a0;

cout<<"b[0]=";

cin>>b0;

for(int i=1;i<=n;i++){

an=(a0+b0)/2;

bn=a0*b0;

a0=an;

b0=bn;

cout<<"a["<<i<<"]="<<an<<endl;

cout<<"b["<<i<<"]="<<bn<<endl;}

getch();}

9.11 Ce este mai indicat de utilizat: recursivitatea sau iterativitatea?

Recursivitatea este potrivită pentru:

– probleme care utilizează relații de recurență;

– prelucrarea structurilor de date definite recursiv (liste, arbori);

Recursivitatea permite crearea de cod mai simplu de înțeles și verificat;

Uneori însă este mai greu de dedus relația de recurență.

Iterativitatea este preferată (uneori) din cauza:

– vitezei mai mari de execuție;

– memoriei mai reduse.

Este de preferat ca atunci când programul recursiv poate fi transformat cu usurință într-unul nerecursiv, să se facă apel la cel din urmă.

De exemplu, așa cum am arătat, varianta recursivă a funcției Fibonacci duce la 15 apeluri pentru n=5, în timp ce varianta nerecursivă (cu utilizarea structurii repetitive for) este mult mai performantă.

De aceea, la implementarea unei soluții, poate fi necesară transformarea algoritmului recursiv în unul nerecursiv, în situații precum:

– soluția problemei trebuie scrisă într-un limbaj nerecursiv; un caz particular sunt compilatoarele ce "traduc" un program recursiv dintr-un limbaj de nivel înalt în cod mașină (nerecursiv);

– varianta recursivă ar duce la o viteză de execuție și spațiu de memorie prea mari, transformarea în una nerecursivă eliminând dezavantajele.

În scrierea unei variante nerecursive, trebuie parcurși toți pașii implicați în varianta recursivă, prin tehnici nerecursive.

Funcțiile recursive cu mai multe apeluri sau cu un apel care nu este ultima instrucțiune pot fi rescrise iterativ prin folosirea structurii de date stiva (implementată utilizând un vector și adăugând și extrăgând de la sfârșitul vectorului).

Funcțiile recursive sunt lente pentru că la fiecare apel se introduce în program o suprasarcina de apel a funcției (overhead).

Suprasarcina de apel reprezintă intervalul de timp necesar calculatorului pentru a încărca și a descărca informațiile din stivă. Deși, calculatorul poate executa rapid aceste operații de încîrcare și descărcare, totuși ele consumă timp.

Apelul funcției recursive de un număr prea mare de ori va determina creșterea considerabilă a duratei de execuție a programului.

De asemenea, utilizarea apelului recursiv necesită atenție la necesarul de stivă. Dacă numărul de apeluri recursive ale funcției este prea mare, s-ar putea depași capacitatea stivei (stack overflow).

Problemă de rezolvat:

Să se deducă valoarea funcției f pe care o afișează programul de mai jos. Să se descrie valorilor stocate pe niveluri de apel în stivă:

#include<iostream.h>

#include<conio.h>

int f(int n) {

if((n==0) || (n==1)) return n;

else return f(n-1)+2*f(n-2);

}

void main() {

cout<<f(5);

getch();

}

Lecția 10

Cuvinte importante:

– crearea unei funcții care returnează un pointer;

– pointeri la funcții, tablouri de pointeri la funcții;

– facilitați oferite de limbajul C++ la utilizarea funcțiilor: funcții inline; funcții cu parametrii impliciți; supraîncărcarea (overloading) funcțiilor;

– funcții de informare privind data calendaristică și ora, din bibliotecile standard ale limbajului C/C++.

10.1 Crearea unei funcții care returnează un pointer

În lecțiile 5 (despre funcții cu șiruri de caractere) și 7 (alocarea dinamică a memoriei) am învățat că multe dintre funcțiile bibliotecii run-time C/C++ returnează pointeri.

Se pot crea funcții proprii care returnează pointeri la un anumit tip de dată.

Funcții proprii care returnează pointeri la tablouri unidimensionale

De exemplu următorul program (funcție_citire_vect.cpp) crează o funcție numită citeste_vect care alocă zonă dinamică pentru un vector X de n componente, îl citește de la tastatură și apoi returnează un pointer la vectorul creat și citit.

#include <iostream.h>

#include<conio.h>

int *citeste_vect (int &);

void afis_vect (const int [], int);

void main ()

{int *A;

int nr;

A = citeste_vect (nr);

if (A)

{afis_vect (A, nr);

delete A;}

else

cout << "Alocare dinamica esuata";

getch();}

int *citeste_vect (int &n)

{int i;

int *X;

cout << "Introduceti numarul de elemente ale vectorului: "; cin >> n;

X = new int [n];

if (X)

{

cout << "Elementele vectorului: " << endl;

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

{cout << "Element[" << i << "] "; cin >> X[i];}

}

else

cout << "Alocare dinamica esuata";

return X;

}

void afis_vect (const int X[], int n)

{ int i;

cout << "Vectorul X" << endl;

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

cout << "X[" << i << "] " << X[i] << endl;}

Notă:

1.După cum se observă din programul precedent, pentru a crea o funcție care returnează un pointer, se plasează un asterisc (*) înaintea numelui funcției :

int *citeste_vect (int &n)

2. Pentru a înhiba o eventuală modificare a tabloului unidimensional din programul de mai sus funcția void afis_vect (const int X[], int n) are un parametr formal precedat de cuvântul const ceeace ne spune că valorile din zona de memorie alocată tabloului nu pot fi modificate în funcție.

Următorul program (str_majuscule.cpp) crează o funcție numită sir_majuscule care convertește toate caracterele unui șir în majuscule și apoi returnează un pointer la șir:

#include <iostream.h>

#include <ctype.h>

char *sir_majuscule(char *sir_s)

{char *adresa_start;

adresa_start = sir_s;

while (*sir_s)

{*sir_s = toupper(*sir_s);

sir_s++;}

return(adresa_start);

}

void main()

{char sir_m [256];

char *sir;

cin.getline (sir_m, 256);

sir = sir_majuscule(sir_m);

cout << sir << endl;

}

Funcții proprii care returnează pointeri la tablouri bidimensionale

Programul următor (functii_pointer_matrice.cpp) determină punctele sa dintr-o matrice cu n linii și m coloane citită de la tastatură.

Reamintim că, un punct sa este un element al matricei care are valoarea minimă pe linia lui și valoarea maximă pe coloana lui.

Programul este structurat din patru funcții:

Funcția citeste_matr() care citește de la tastatură o matrice cu n linii și m coloane și returnează un pointer la matricea citită;

Funcția afis_matr() care afișează matricea citită;

Funcția puncte_sa() care determină punctele sa din matrice și crează un tablou de șiruri de caractere (denumit tablou_rezultat) care are un număr de elemente egal cu numărul de puncte sa găsite;

Funcția main() care apelează funcțiile de mai sus și afișează punctele sa determinate.

#include <iostream.h>

#include<conio.h>

#include<string.h>

int ** citeste_matr(int&,int&);

void afis_matr(int *[],int, int);

char ** puncte_sa(int *[], int, int, int&);

void main()

{

int **a; // adresa pointerilor la linii

char ** t_rezultat; // adresa pointerilor la sirurile de caractere

int lin, col;

int i, j;

int nr_puncte_sa;

a=citeste_matr(lin,col);

if(a){

afis_matr(a, lin, col);

t_rezultat=puncte_sa(a,lin, col, nr_puncte_sa);

if(t_rezultat){

cout<<"punctele sa sunt:"<<endl;

i=0;

while(i<nr_puncte_sa ){

cout<<t_rezultat[i]<<endl;

i++ ;}

cout<<"Numarul de puncte sa = "<<i;

delete *t_rezultat; // dealocarea zonei elementelor sir

delete t_rezultat; // dealocarea zonei pointerilor sirurilor

}

else

cout<<"alocare eronata";

delete *a; //dealocarea zonei elementelor linilor

delete a; } //dealocarea zonei pointerilor de linie

else

cout<<"Alocare esuata";

getch();

}

int ** citeste_matr(int &n, int &m) {

int **matr;

int aloc, i, j;

cout << "Introduceti numarul de linii "; cin >> n;

cout << "Introduceti numarul de coloane: "; cin >> m;

matr = new int *[n];

if (!matr)

{cout<<"Alocare esuata"<<endl;

aloc=0;}

else

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

{matr[i] = new int[m];

if (!matr[i])

{cout<<"Alocare esuata"<<endl;;

aloc = 0;}

else

aloc = 1;} }

if (aloc)

{cout<<"Introduceti elementele de pe linie"<<endl;

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

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

{cout<<"matr["<<i<<","<<j<<"]=" ;

cin>>matr[i][j];} }

return matr;}

void afis_matr(int *matr[], int n, int m){

int i,j;

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

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

cout<<matr[i][j]<<" ";

cout<<endl;}

}

char ** puncte_sa(int *matr[], int n, int m, int &nr_sa ) {

int i, j, k;

int aloc=1;

int puncte_sa;

nr_sa=0;

char *p;

char sir[10];

char ** tablou_rezultat;

tablou_rezultat = new char*[50];

if (tablou_rezultat)

for (i = 0; i < 50; i++){

tablou_rezultat[i]=new char[50];

if (tablou_rezultat[i])

aloc=1;

else

aloc=0;

}

else

{cout<<"alocare esuata";

aloc = 0;}

if(aloc) {

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

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

puncte_sa = 1;

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

if(matr[i][k]<matr[i][j]) // comparare cu elementele de pe i

puncte_sa=0;

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

if(matr[k][j]>matr[i][j]) // comparare cu elementele de pe j

puncte_sa=0;

if(puncte_sa){

//creaza elementele tabloului de siruri de caractere

strcpy(tablou_rezultat[nr_sa],"a[");

itoa(i,sir,10);

strcat(tablou_rezultat[nr_sa],sir);

strcat(tablou_rezultat[nr_sa]," ");

strcat(tablou_rezultat[nr_sa],",");

itoa(j,sir,10);

strcat(tablou_rezultat[nr_sa],sir);

strcat(tablou_rezultat[nr_sa],"]= ");

itoa(matr[i][j], sir, 10);

strcat(tablou_rezultat[nr_sa],sir);

nr_sa++; // numara punctele sa

} } }

return tablou_rezultat;}

10.2 Funcții proprii care returnează pointeri la structuri

Programul următor (functii_pointer_structuri.cpp) rezolvă următoarea problemă:

Se dă o mulțime de intervale definită pe reuniunea de intervale închise:

A = [a0, b0][a1,b1]…[an-1, bn-1] unde n este un număr natural iar a0,b0,…, an-1, bn-1 reprezintă capetele intervalelor închise. Să se determine dacă un număr real x citit de la tastatură aparține mulțimii A.

Programul este format din trei funcții:

– Funcția citeste_intervale(), care citește capetele intervalelor reuniunii de intervale și le stochează într-o variabilă – tablou de tip struct;

– Funcția apart_reuniune_interval() care calculează apartenența unui număr citit de la tastatură la reuniunea de intervale;

– Funcția main() care apelează funcțiile de mai sus și afișează rezultatul.

#include<iostream.h>

#include<conio.h>

struct interval{

float a;

float b;};

interval *citeste_intervale(int &);

interval apart_reuniune_interval(interval *, int, float &);

void main(){

int i, nr;

float nr_cautat;

interval *tablou_intervale;

interval interval_gasit;

tablou_intervale=citeste_intervale(nr);

cout<<"Introduceti numarul de cautat: "; cin>>nr_cautat;

if (tablou_intervale) {

interval_gasit=apart_reuniune_interval(tablou_intervale,nr, nr_cautat);

if (interval_gasit.a !=0 && interval_gasit.b !=0)

cout<<"Se gaseste in reuniunea de intervale"<<endl;

else

cout<<"Nu se gaseste in reuniunea de intervale" ;

delete tablou_intervale; }

else

cout<<"Alocare esuata";

getch();}

interval *citeste_intervale(int &n){

interval *t_intervale ;

int i;

cout<<"Numarul de intervale:"; cin>>n;

t_intervale=new interval[n];

if(t_intervale)

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

cout<<"Intervalul "<<i+1<<":"<<endl;

cout<<"a = "; cin>>t_intervale[i].a;

cout<<"b = "; cin>>t_intervale[i].b;

}

else

cout<<"Alocare esuata";

return t_intervale;

}

interval apart_reuniune_interval(interval *t_intervale,int n, float &x){

int i,gasit=0;

int index_gasit;

for (i = 0; !gasit && i < n; i++)

if(x>=t_intervale[i].a && x<=t_intervale[i].b)

{gasit = 1;

index_gasit=i;}

if(gasit)

return t_intervale[index_gasit];

else

{

t_intervale[0].a=0;

t_intervale[0].b=0;

return t_intervale[0];}

}

10.3 Pointeri la funcții

După cum știm, instrucțiunile programului C/C++ se află în memoria RAM în așa-numită zonă text. Programul nu poate accesa zona text decât utilizând pointeri la funcții.

Un pointer la o funcție conține o adresă a unei funcții din zona text.

Declararea unui pointer la o funcție se realizează folosind sintaxa:

<tip> (*<variabila_pointer>) (<tip_param1>, <tip_param2>, …);

unde:

– <tip> – tipul de data returnat de funcție;

– <variabila_pointer> – numele variabilei-pointer care memorează adresa funcției;

– <tip_param1>, <tip_param2>, …- tipul parametrilor funcției.

De exemplu:

void (*ptr_combin)(int *, int *, int); sau

int (*ptr_min_max)(int, int);

Notă: Se observă utilizarea parantezelor în care se scrie numele variabilei-pointer. Dacă s-ar înlătură parantezele, declararea de pointeri la funcții ar servi drept prototipuri de funcții pentru funcții care returnează pointeri de un anumit tip, ca mai jos:

void *ptr_combin (int *, int *, int);

sau

int *ptr_min_max (int, int);

Inițializarea unui pointer la o funcție

Un pointer la o funcție trebuie inițializat cu adresa funcției. Acest lucru se face în două etape:

a) se specifică compilatorului numele funcției, tipul funcției și tipul parametrilor funcției, adică se precizează prototipul sau declarația funcției;

b) se atribuie pointerului adresa funcției.

De exemplu:

void combin_v (int *, int *, int);

ptr_combin = combin_v;

A doua linie din exemplu, inițializează pointerul ptr_combin cu adresa funcției combin_v. Numele funcției nu este urmat de paranteze, deoarece nu este un apel de funcție. Compilatorul interpretează numele funcției ca o adresa, deci operatorul & nu este necesar.

Apelul unei funcții folosind pointerul la funcție se face astfel:

<variabila_pointer> (<param1>, <param2>, …);

Sau

<valoare> = <variabila_pointer> (<param1>, <param2>, …);

unde:

– <variabila_pointer> – numele variabilei-pointer care memorează adresa funcției;

– <param1>, <param2>, … – parametrii reali ai funcției;

– <valoare> – variabilă în care este memorată valoarea returnată din apelul funcției.

De exemplu:

ptr_combin (A, B, nr);

sau

rezultat=ptr_min_max (7, 2);

Utilizarea pointerilor la funcții

Pointerii la funcții sunt cel mai des utilizați pentru transmiterea unei funcții, ca parametru, la o alta funcție sau pentru a păstra într-un tablou, valori ale unor diverse funcții.

Următorul program (pointer_la_functie.cpp) transmite, ca parametru, la funcția min_max fie funcția min (care calculează valoarea minimă a două numere), fie funcția max (care calculează valoarea maximă a două numere). Valoarea returnată de funcția min_max va diferi în raport de funcția transmisă de program.

#include <stdio.h>

int max(int a, int b)

{printf("In functia max\n");

return((a > b) ? a: b);}

int min(int a, int b)

{printf("In functia min\n");

return((a < b) ? a: b);}

int min_max(int a, int b, int (*ptr_min_max)(int,int))

{printf("In functia min_max\n");

printf("Adresa functiei este: %p\n", ptr_min_max);

return(ptr_min_max(a,b)); // Apeleaza functia transmisa}

void main(void)

{int rezultat;

rezultat = min_max(7, 2, max);

printf("Maximul dintre 7 si 2 este %d\n", rezultat);

rezultat = min_max(7, 2, min);

printf("Minimul dintre 7 si 2 este %d\n", rezultat);}

După execuția programului, pe ecran se afișează următoarele:

In functia min_max

Adresa functiei este: 0040107C

In functia max

Maximul dintre 7 si 2 este 7

In functia min_max

Adresa functiei este: 004010A0

In functia min

Minimul lui 7 si 2 este 2

Următorul program (pointer_la_functie1.cpp) transmite, ca parametru, la funcția apel_functii fie funcția prod_sus_dig (care calculează produsul elementelor de deasupra diagonalei principale), fie funcția prod_sub_dig (care calculează produsul elementelor de sub diagonala principală). Valoarea returnată de funcția apel_functii va diferi în raport de funcția transmisă de program.

De asemenea, programul crează și folosește un pointer la funcția combin_v pentru a combina doi vectori A și B de n elemente, conform relațiilor următoare:

A[i] = A[i] + B[i]

B[i] = A[i] *B[i], pentru orce i = 0, 1, 2, …, n-1

#include <iostream.h>

double prod_sus_dig (int *[], int );

double prod_sub_dig (int *[], int );

double apel_functii (int *[], int, double(*)(int *[], int));

void combin_v (int *, int *, int);

void main (void)

{int **matr; // adresa de inceput a vectorului de pointeri la linii

int lin, col, aloc = 0;

int inum = 1; int i, j;

double produs;

int *A, *B, nr;

void (*ptr_combin)(int *, int *, int);

cout << "Introduceti numarul de linii ale matricei patratice "; cin >> lin;

col = lin;

matr = new int *[lin]; // alocare zona de memorie pentru pointerii de linie

if (!matr)

{cout << “Alocare esuata\n";

aloc = 1;}

else

{for (i = 0; i < lin; i++)

{matr[i] = new int[col]; // alocare zona de memorie pentru elementele liniei

if (!matr[i])

{cout << “Alocare esuata\n";

aloc = 1;}

else

aloc = 0;}}

if (!aloc)

{

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

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

matr[i][j]=inum++;

produs = apel_functii (matr, lin, prod_sus_dig);

cout << "Produsul elementelor de deasupra diagonalei principale este " << produs << endl;

produs = apel_functii (matr, lin, prod_sub_dig);

cout << "Produsul elementelor de sub diagonala principala este " << produs << endl;

delete *matr; //dealocarea zonei liniilor matricei

delete matr; //dealocarea zonei pointerilor de linie

}

cout << "Introduceti numarul de elemente ale vectorilor: "; cin >> nr;

ptr_combin = combin_v;

A = new int [nr];

B = new int [nr];

if (A && B)

{

cout << "Elementele vectorului A: " << endl;

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

{

cout << "A[" << i << "] "; cin >> A[i];

}

cout << "Elementele vectorului B: " << endl;

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

{

cout << "B[" << i << "] "; cin >> B[i];

}

ptr_combin (A, B, nr);

cout << "Vectorul A" << endl;

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

cout << "A[" << i << "] " << A[i] << endl;

cout << "Vectorul B" << endl;

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

cout << "B[" << i << "] " << B[i] << endl;

delete A;

delete B; }

else

cout << “Alocare esuata"; }

double prod_sus_dig (int *a[], int n)

{int i, j;

double p = 1;

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

for (j=i+1; j<n; j++)

p = p*a[i][j];

return p;}

double prod_sub_dig (int *a[], int n)

{int i, j;

double p = 1;

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

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

p = p*a[i][j];

return p;}

double apel_functii (int *a[], int n, double(*ptr_functii)(int *[], int ))

{double rezultat;

rezultat = ptr_functii(a,n);

return rezultat;}

void combin_v (int X[], int Y[], int n)

{int i, aux;

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

{aux=X[i];

X[i] = X[i]+Y[i];

Y[i] = aux*Y[i];}

10.4 Tablouri de pointeri la funcții

Următorul program (tablou_pointeri__functie.cpp) calculează valorile a patru funcții trigonometrice (sin, cos, tan și cotan) unde x [0.01,π], pasul de calcul a lui x este 0.1 Valorile funcțiilor trigonometrice sunt afișate la ecran. Programul folosește un tablou de pointeri la cele patru funcții cu numele func_trig[4] și apelează pointerul la funcție stocat într-un element al acestui tablou.

#include<iostream.h>

#include<conio.h>

#include<math.h>

double cotan(double);

void main() {

int i;

double x;

double (*func_trig[])(double)={sin, cos, tan, cotan};

for (x=0.01;x<=M_PI;x+=0.1)

{cout<<endl<<x<<””;

for(i=0;i,4;i++)

cout<<func_trig[i](x)<<””; }

getch();

}

double cotan(double z)

{

return (1/tan(Z)); }

10.5 Facilitați oferite de limbajul C++ la utilizarea funcțiilor

Funcții inline

După cum se știe, programele transmit parametrii către funcții utilizând stiva. Operațiile suplimentare de depunere și extragere din stiva a informațiilor de apel a funcției pot conduce la scăderea vitezei de execuție a programelor. De aceea, în limbajul C++ a fost introdusă o categorie specială de funcții, denumite functii inline. Functiile inline determină creșterea vitezei de execuție a programelor.

Când compilatorul întâlnește o funcție inline, nu generează cod (obiect) distinct pentru funcție, așa cum face pentru o funcție obișnuită, ci înlocuiește numai apelul funcției cu secvența de instrucțiuni care reprezintă corpul funcției. De exemplu, dacă programul apelează o funcție inline din cinci locații diferite, compilatorul va insera de cinci ori, în program, secvența de instrucțiunii din definiția funcției.

De obicei, funcțiile inline se folosesc pentru funcții “mici” (care realizează un număr redus de operații specifice). Astfel, utilizarea lor pentru funcții “mici” nu duce la creșterea dimensiunii codului executabil, dacă se ia în calcul și operațiile suplimentare pe stiva executate la apelul oricarei funcții obișnuite. În plus, dacă o funcție inline este definită dar nu este apelată niciodată, compilatorul nu generează cod (obiect) pentru ea, în timp ce pentru o funcție obișnuită, indiferent dacă este sau nu apelată, codul este generat.

Definiția unei funcții inline are același format general cu definiția unei funcții obișnuite, cu excepția faptului că este precedată de cuvântul rezervat inline.

De exemplu:

inline void interschimb (int *a, int *b)

{

int temp;

temp = *a;

*a = *b;

*b = temp;

}

Apelul funcției inline are același format cu cel al unei funcții obișnuite.

De exemplu:

interschimb (&a, &b);

Funcții cu parametrii impliciți

Funcțiile cu parametrii impliciți se folosesc atunci când funcțiile au un număr mare de parametrii și pentru unii parametrii se utilizează frecvent la apel aceeași valoare.

Limbajul C++ a introdus o facilitate suplimentară, prin care se pot declara valori implicite în antetul funcției.

Formatul general al antetului pentru o funcție cu parametrii impliciți este:

<tip> <nume_functie> ( <tip_param1> <nume_param1>, …,

<tip_param_imp1> <nume_param_imp1> = <valoare1>, …)

unde:

– <tip> și <nume_functie> – specifică tipul rezultatului returnat de funcție și, respectiv, numele funcției;

– < tip_param1> și <nume_param1> – specifică tipul parametrului formal fără valori implicite și, respectiv, numele parametrului formal fără valori implicite;

– < tip_param_imp1> și <nume_param_imp1> – specifică tipul parametrului formal cu valori implicite și, respectiv, numele parametrului formal cu valori implicite;

– <valoare1> – valoarea implicită atribuită parametrului formal.

De exemplu, secvența de program următoare (parametrii_impliciti.cpp), utilizează funcția afis_nr cu parametrii formali impliciți pentru a afișa trei parametrii:

void afis_nr (int x1 = 1, int x2 = 2, int x3 = 3)

{cout << x1 << ' ' << x2 << ' ' << x3 << '\n';}

Dacă se apelează funcția cu mai puțini de trei parametrii, programul va utiliza valorile implicite 1, 2, 3, ca în secvența de mai jos:

afis_nr(10, 20, 30);

afis_nr(100, 200);

afis_nr(1000);

afis_nr();

După execuția programului pe ecran se afișează:

10 20 30

100 200 3

1000 2 3

1 2 3

Notă: La apelul funcției, se pot omite parametrii reali corespunzători parametrilor formali cu valori implicite, în acest caz utilizându-se valorile implicite ale acestora.

Observație: Pentru ca la apel corespondența parametru real – parametru formal să se execute corect, întotdeauna parametrii cu valori implicite sunt ultimii declarați în lista de parametrii ai funcției.

De exemplu următorul program (parametrii_impliciti.cpp) utilizează funcția suma cu un parametru formal implicit (numit a) pentru a calcula suma a trei numere (citite de la tastatură) care apoi este inmulțită cu un coeficient (parametrul implicit a), care poate fi citit de la tastatură sau pentru care programul utilizează valoarea implicita 2.5. Se observă ca parametrul implicit este ultimul declarat în lista de parametrii formali ai funcției suma. De asemenea în program sunt incluse și secvențele de program discutate anterior pentru funcția afis_nr cu parametrii formali impliciți.

#include <iostream.h>

#include <stdlib.h>

void afis_nr(int x1 = 1, int x2 = 2, int x3 = 3)

{cout << x1 << ' ' << x2 << ' ' << x3 << '\n';}

float suma (float x1, float x2, float x3, float a = 2.5)

{float y;

y = a*(x1+x2+x3);

return y;}

void main(void)

{char sir[64];

float x, s;

float l, m, n;

afis_nr(10, 20, 30);

afis_nr(100, 200);

afis_nr(1000);

afis_nr();

cout << "Introduceti primul numar "; cin >> l;

cout << "Introduceti al doilea numar "; cin >> m;

cout << "Introduceti al treilea numar "; cin >> n;

cin.get();

cout << "Introduceti coeficientul pentru inmultire[implicit = 2.5] ";

cin.getline(sir, sizeof(sir));

x = atof(sir);

if (x)

s = suma (l, m, n, x);

else

s = suma (l, m, n);

cout << "Suma celor trei numere inmultite cu un coeficient este " << s;}

Plasarea valorilor implicite ale parametrilor în prototipul funcțiilor

În plus față de plasarea valorilor implicite ale parametrilor în antetul funcțiilor, programul C++ poate specifica valorile implicite și în cadrul prototipurilor funcțiilor.

De exemplu, prototipul funcției suma prezentată anterior se poate scrie astfel:

float suma (float x1, float x2, float x3, float a = 2.5)

void main(void)

{

//instructiuni

}

Dacă funcția pentru care se dorește specificarea parametrilor impliciți nu se află în fișierul sursă curent, atunci se folosește prototipul funcției cu specificarea valorilor implicite corecte, ca în exemplul de mai sus.

Supraîncărcarea (overloading) funcțiilor

Supraîncărcarea înseamnă definirea și apelul mai multor funcții cu același nume, dar care diferă prin numărul și/sau tipul parametrilor.

În timpul compilării, compilatorul determină care funcție trebuie apelată, bazându-se pe numărul și tipul parametrilor reali pe care instrucțiunea de apelare il transmite funcției. În primul rând, se caută o corespondență exactă (o funcție pentru care tipurile parametrilor formali să coincidă cu cele ale parametrilor reali ai apelului respectiv). Dacă nu este găsită o corespondență exactă, se fac conversii de tip implicite (de exemplu: tipul float al parametrului real este convertit la double, și tipurile char, unsigned char, unsigned int ale parametrilor reali sunt convertite la int).

Următorul program (supraincarcare_functi.cpp) crează două funcții cu același nume combin_v care combina doi vectori A și B de n elemente, conform relațiilor următoare:

A[i] = A[i] + B[i]

B[i] = A[i] *B[i], pentru orce i = 0, 1, 2, …, n-1

Prima funcție combin_v accepta vectori de tip int, în timp ce a doua funcție combin_v acceptă vectori de tip float.

#include <iostream.h>

#include <ctype.h>

void combin_v (int *, int *, int);

void combin_v (float *, float *, int);

int *citeste_vect1 (int &);

float *citeste_vect2 (int &);

void main (void)

{int i;

int *A, *B;

int nr1, nr2;

float *C, *D;

char litera;

cout << "Doriti citire de vectori reali [Y/N] ";

cin >> litera;

if (toupper(litera) == 'Y')

{

C = citeste_vect2 (nr1);

D = citeste_vect2 (nr2);

if (C && D)

{

if (nr1 == nr2)

{

combin_v (C, D, nr1);

cout << "Vectorul C" << endl;

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

cout << "C[" << i << "] " << C[i] << endl;

cout << "Vectorul D" << endl;

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

cout << "D[" << i << "] " << D[i] << endl;

}

else

cout << "Numarul de elemente ale celor doi vectori nu coincid, se abandoneaza";

delete C;

delete D;

}

else

cout << "Alocare dinamica esuata pentru vectori"; }

else

{A = citeste_vect1 (nr1);

B = citeste_vect1 (nr2);

if (A && B)

{

if (nr1 == nr2)

{

combin_v (A, B, nr1);

cout << "Vectorul A" << endl;

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

cout << "A[" << i << "] " << A[i] << endl;

cout << "Vectorul B" << endl;

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

cout << "B[" << i << "] " << B[i] << endl;}

else

cout << "Numarul de elemente ale celor doi vectori nu coincid, se abandoneaza ";

delete A;

delete B;

}

else

cout << "Alocare dinamica esuata pentru vectori";

}

}

void combin_v (int X[], int Y[], int n)

{

int i, aux;

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

{aux=X[i];

X[i] = X[i]+Y[i];

Y[i] = aux*Y[i];}

}

void combin_v (float X[], float Y[], int n)

{

int i;

float aux;

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

{aux=X[i];

X[i] = X[i]+Y[i];

Y[i] = aux*Y[i];}

}

int *citeste_vect1 (int &n)

{

int i;

int *X;

cout << "Introduceti numarul de elemente ale vectorului: "; cin >> n;

X = new int [n];

if (X)

{

cout << "Elementele vectorului: " << endl;

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

{

cout << "Element[" << i << "] "; cin >> X[i];

}

}

else

cout << “Alocare esuata";

return X;

}

float *citeste_vect2 (int &n)

{

int i;

float *X;

cout << "Introduceti numarul de elemente ale vectorului: "; cin >> n;

X = new float [n];

if (X)

{

cout << "Elementele vectorului: " << endl;

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

{

cout << "Element[" << i << "] "; cin >> X[i];

}

}

else

cout << “Alocare esuata";

return X;

}

Următorul program (supraincarcare_functi1.cpp) crează câte două funcții cu același nume:

– o funcție prod_sus_dig care accepă matrice de tip int și o funcție prod_sus_dig care acceptă matrice de tip float; ambele funcții realizează produsul elementelor de deasupra diagonalei principale

– o funcție prod_sub_dig care acceptă matrice de tip int și o funcție prod_sub_dig care acceptă matrice de tip float; ambele funcții realizează produsul elementelor de sub diagonala principală.

#include <iostream.h>

#include <ctype.h>

double prod_sus_dig (int *[], int );

double prod_sus_dig (float *[], int);

double prod_sub_dig (int *[], int );

double prod_sub_dig (float *[], int );

int **citeste_matr1 (int &, int &);

float **citeste_matr2 (int &, int &);

void main (void)

{int **matr; // adresa de inceput a vectorului de pointeri la linii

float **matr_f;

int lin;

int i;

double produs;

int alocare = 0;

char litera;

cout << "Doriti citire de matrice cu valori reale [Y/N] "; cin >> litera;

if (toupper(litera) != 'Y')

{matr = citeste_matr1(lin, alocare);

if (!alocare)

{produs = prod_sus_dig (matr, lin);

cout << "Produsul elementelor de deasupra diagonalei principale este " << produs <<

endl;

produs = prod_sub_dig (matr, lin);

cout << "Produsul elementelor de sub diagonala principala este " << produs << endl;

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

delete[] matr [i]; //dealocarea zonei elementelor matricei

delete[] matr; //dealocarea zonei pointerilor de linie}

else

cout << "Alocare dinamica esuata pentru matrice";}

else

{

matr_f = citeste_matr2(lin, alocare);

if (!alocare)

{produs = prod_sus_dig (matr_f, lin);

cout << "Produsul elementelor de deasupra diagonalei principale este " << produs <<

endl;

produs = prod_sub_dig (matr_f, lin);

cout << "Produsul elementelor de sub diagonala principala este " << produs << endl;

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

delete[] matr_f [i]; //dealocarea zonei elementelor matricei

delete[] matr_f; //dealocarea zonei pointerilor de linie}

else

cout << "Alocare dinamica esuata pentru matrice";

}

}

double prod_sus_dig (int *a[], int n)

{int i, j;

double p = 1;

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

for (j=i+1; j<n; j++)

p = p*a[i][j];

return p;}

double prod_sus_dig (float *a[], int n)

{int i, j;

double p = 1;

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

for (j=i+1; j<n; j++)

p = p*a[i][j];

return p;}

double prod_sub_dig (int *a[], int n)

{int i, j;

double p = 1;

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

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

p = p*a[i][j];

return p;}

double prod_sub_dig (float *a[], int n)

{int i, j;

double p = 1;

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

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

p = p*a[i][j];

return p;}

int **citeste_matr1 (int &n, int &aloc)

{int i, j;

int m;

int **a;

cout << "Introduceti numarul de linii ale matricei patratice "; cin >> n;

m = n;

a = new int *[n]; // alocare zona de memorie pentru pointerii de linie

if (!a)

{cout << “Alocare esuata\n"; aloc = 1;}

else

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

{a[i] = new int[m]; // alocare zona de memorie pentru elementele fiecariei linii

if (!a[i])

{cout << “Alocare esuata\n"; aloc = 1;}

else

aloc = 0;}}

if (!aloc)

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

{cout << "Elemente linie " << i << endl;

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

{cout << "Element[" << j << "] "; cin >> a[i][j];}}

}

return a;}

float **citeste_matr2 (int &n, int &aloc)

{int i, j;

int m;

float **a;

cout << "Introduceti numarul de linii ale matricei patratice "; cin >> n;

m = n;

a = new float *[n]; // alocare zona de memorie pentru pointerii de linie

if (!a)

{cout << “Alocare esuata\n"; aloc = 1;}

else

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

{a[i] = new float[m]; // alocare zona de memorie pentru elementele fiecariei linii

if (!a[i])

{cout << “Alocare esuata\n"; aloc = 1;}

else

aloc = 0;}}

if (!aloc)

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

{cout << "Elemente linie " << i << endl;

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

{cout << "Element[" << j << "] "; cin >> a[i][j];}}

}

return a;}

Următorul progam (supraincarcare_functi2.cpp) supraîncarcă funcția interschimb. Prima funcție interschimbă două valori, în timp ce a doua funcție interschimbă patru valori. Compilatorul utilizează numărul de parametrii pentru a determina ce funcție apelează.

#include <iostream.h>

void interschimb(int *a, int *b)

{int temp = *a;

*a = *b; *b = temp;}

void interschimb(int *a, int *b, int *c, int *d)

{int temp = *a;

*a = *b; *b = temp;

temp = *c;

*c = *d; *d = temp;}

void main(void)

{int a = 1, b = 2 , c = 3, d = 4;

cout << a << ' ' << b << endl;

interschimb(&a, &b);

cout << "S-a interschimbat a cu b " << a << ' ' << b << '\n';

cout << a << ' ' << b << ' ' <<c << ' ' << d << '\n';

interschimb(&a, &b, &c, &d);

cout << "S-au interschimbat patru valori " << a << ' ' << b << ' ' <<c << ' ' << d << '\n';}

10.4 Funcții de informare privind data calendaristică și ora, din bibliotecile standard ale limbajului C/C++

În fișierul antet time.h sunt declarate numeroase funcții pentru data calendaristică și oră.

1. Informarea asupra datei și orei curente se poate face cu ajutorul funcției time (). Funcția returnează data și ora curente ca secunde de la 00:00, 1 ianuarie 1900. Funcția returnează o valoare de tip time_t. Dacă mediul sistemului de operare nu reușește determinarea se returnează -1

Funcția time() este declarată astfel:

#include <time.h>

time_t time (time_t *data_ora);

în care, în parametrul <data_ora > se stochează data și ora ca durată în secunde.

Dacă nu se dorește să se transmită un parametru funcției time (), se poate apela funcția cu valoarea NULL, astfel:

timp_curent = time (NULL);

Următorul program (functia_time.cpp) utilizează funcția time () pentru a introduce o așteptare de 5 secunde până la terminarea programului:

#include <stdio.h>

#include <iostream.h>

#include <time.h>

void main(void)

{

time_t timp_curent;

time_t timp_start;

printf("Pe punctul de a face o asteptare (delay) de 5 secunde\n");

timp_start = time(NULL); // Timpul in secunde la start

cout << timp_start << endl;

do

timp_curent = time(NULL);

while ((timp_curent – timp_start) < 5);

cout << timp_curent << endl;

printf("S-a terminat\n");

}

2. Pentru a converti secundele care sunt returnate de funcția time () într-un șir de caractere de tip ASCII în formatul – “Fri Oct 31 11:30:00 1998\n”- se folosește functia ctime ().

Functia ctime() este declarată astfel:

#include <time.h>

char *ctime(const time_t *time);

în care parametrul time specifică valoarea în secunde returnată de funcția time ().

Următorul program (functia_ctime.cpp) ilustrează modul de folosire a funcției ctime():

#include <stdio.h>

#include <time.h>

void main(void)

{time_t timp_curent;

timp_curent = time(NULL); // Timpul in secunde;

printf("Data si ora curente: %s", ctime(&timp_curent));}

După execuția programului pe ecran se afișează:

Data și ora curente: Tue May 02 02:47:43 2006

3. Determinarea timpului de procesare al programului se poate face cu ajutorul funcției clock().

Această funcție este folosită pentru a determina care dintre secțiunile programului sunt mari consumatoare de timp.

Funcția clock () returnează durata, în numărul de unități de ceas (dat de ceasul intern al BIOS -ului care bate, de obicei, de 18,2 ori pe secundă), consumată pentru execuția programului. Pentru a converti durata în secunde, se împarte rezultatul returnat de funcție la constanta CLK_TCK care este definită în fișierul antet time.h.

Funcția clock() este declarată astfel:

#include <time.h>

clock_t clock(void);

Următorul program (functia_clock.cpp) utilizează funcția clock () pentru a afișa timpul de procesare al programului în secunde:

#include <time.h>

#include <stdio.h>

int factorial(int n)

{int rezultat;

if (n == 1)

return(1);

else

{rezultat = n * factorial(n-1);

return(rezultat);}}

int main(void)

{clock_t start, stop;

long int nr, fact;

printf("Introduceti un numar: ");

scanf("%d", &nr);

start = clock();

fact=factorial(nr);

stop = clock();

printf("Timpul de procesare consumat este: %f\n", (stop – start) / CLK_TCK);

printf("Factorialul lui %d este %d\n", nr, fact);

return 0;}

Următorul program (functia_clock1.cpp) utilizează două funcții similare numite interschimb, plasând una inline si apelând-o pe cealaltă. Programul afișează durata necesară pentru execuția de 300000de ori a fiecărei funcții.

#include <iostream.h>

#include <time.h>

inline void interschimb_inline(int *a, int *b, int *c, int *d)

{int temp;

temp = *a;

*a = *b;

*b = temp;

temp = *c;

*c = *d;

*d = temp;}

void interschimb_apel(int *a, int *b, int *c, int *d)

{int temp;

temp = *a;

*a = *b;

*b = temp;

temp = *c;

*c = *d;

*d = temp;}

void main(void)

{clock_t start, stop;

long int i;

int a = 1, b = 2, c = 3, d = 4;

start = clock();

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

interschimb_inline(&a, &b, &c, &d);

stop = clock();

cout << "Durata pentru executia functiei inline: " << (stop – start) / CLK_TCK;

start = clock();

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

interschimb_apel(&a, &b, &c, &d);

stop = clock();

cout << "\nDurata pentru executia functiei apelate: " << (stop – start) / CLK_TCK;}

4. Obținerea datei calendaristice sub forma unui șir de caractere se poate face cu ajutorul funcției _strdate (). Funcția returnează data sub formă mm/dd/yy.

Funcția este declarată astfel:

#include <time.h>

char * _strdate (char *buffer_data);

Următorul program (functia_strdate.cpp) utilizează funcția _strdate () pentru a afișa data curentă:

#include <stdio.h>

#include <time.h>

void main(void)

{

char data[9];

_strdate(data);

printf("Data curenta este %s\n", data);

}

5. Obținerea orei curente sub forma unui șir de caractere se poate face cu ajutorul funcției _strtime (). Funcția returnează ora sub forma hh/mm/ss.

Funcția este declarată astfel:

#include <time.h>

char * _strtime (char *buffer_timp);

Următorul program (functia_strtime.cpp) utilizează functia _strtime () pentru a afișa ora curentă:

#include <stdio.h>

#include <time.h>

void main(void)

{

char ora[9];

_strtime(ora);

printf("Ora curenta este %s\n", ora);

}

Lecția 11

Cuvinte importante:

– fișiere text și binare, identificarea unui fișier; deschiderea unui fișier; închiderea unui fișier deschis

– citirea și scrierea datelor în fișier caracter cu caracter;

– pointerul de pozitie al unui fișier; poziționarea pointerului de fișier pe baza locației sale curente;

– fluxuri; interpretarea fișierelor de tip text sau binar;

– citirea și scrierea liniilor de text;

– citirea și scrierea datelor formatate dintr-un fișier de tip text;

– citirea și scrierea datelor unei structuri ;

– determinarea modului în care un program poate accesa un fișier; modificarea drepturilor de acces la un fișier;

– ștergerea și redenumirea unui fișier.

Fișiere în limbajul C

Un fșier este o colecție omogenă de date, stocate pe suport extern, care are un anumit tip de acces și de prelucrare pentru datele conținute. Fișierele pot fi clasificate după conținutul lor astfel:

– fișiere text – conțin șiruri de caractere ASCII structurate pe linii;

– fișiere binare – conțin o secvență de octeți, fără o structură predefinită.

Deschiderea unui fișier

Deschiderea unui fișier trebuie făcută înainte de a citi sau scrie date în fișier.

Funcția fopen () permite deschiderea unui fișier. Funcția fopen() returnează un pointer (numit pointer de fisier) la o structură de tip FILE pe care o definește fișierul antet stdio.h. Dacă funcția fopen() nu poate deschide un anumit fișier, ea returnează valoarea NULL.

Formatul funcției fopen() este:

#include <stdio.h>

FILE *fopen (const char *numefisier, const char *mod);

Parametrul numefisier este un șir de caractere care conține numele căii (path) și numele fișierului dorit, cum ar fi: “c:\\test_date.txt”.

Observație: În cadrul numelui de cale (path) a fișierului specificat în funcția fopen() se folosește caracterul backslash dublu (\\). Aceasta deoarece se știe că limbajul C tratează caracterul care urmează după backslash (\) ca pe un simbol special, în cadrul unui șir de caractere.

Parametrul mod precizează modul de acces la fișier, și anume: pentru citire, pentru scriere sau pentru adăugare.

În tabelul următor se descriu valorile pe care le acceptă parametrul mod.

Declararea variabilei pointer de fișier în cadrul unui program C

În cadrul programului C trebuie să se declare o variabilă-pointer la o structură de tip FILE (variabilă-pointer de fișier) astfel:

FILE *<pointer_fisier>;

unde: <pointer_fisier> – specifică variabilă-pointer la o structură de tip FILE.

Dacă programul deschide un fișier pentru intrare și altul pentru ieșire, se vor declara două variabile-pointer de fișier, folosind sintaxa următoare:

FILE *<pointer_fisier_intrare>, *< pointer_fisier_iesire>;

Notă: Programul trebuie să testeze întotdeauna valoarea returnată de funcția fopen() pentru a se asigura că s-a realizat deschiderea cu succes a fișierului.

Închiderea unui fișier deschis

Închiderea unui fișier deschis cere sistemului de operare să golească toate bufferele discului asociate fișierului și să elibereze resursele de sistem consumate de fișier, cum ar fi datele pointerului de fișier.

Funcția fclose () închide fișierul asociat pointerului de fișier specificat. Dacă funcția fclose() se execută cu succes, returnează valoarea 0, dacă apare o eroare, funcția fclose() returnează constanta EOF.

Formatul funcției fclose() este:

#include <stdio.h>

int fclose (FILE *pointer_fisier);

Observație: Dacă nu se apelează funcția fclose(), fișierele deschise se închid automat la terminarea programului

Testarea sfârșitului de fișier

Pentru a determina dacă pointerul de fișier este la sfârșitul fișierului se poate utiliza funcția feof(). Dacă pointerul de fișier este la sfârșitul fișierului, funcția feof () returnează o valoare diferită de 0 (adevarat). Dacă pointerul de fișier nu a ajuns la sfârșitul fișierului funcția feof () returnează valoarea 0 (fals).

Funcția este declarată astfel:

#include <stdio.h>

int feof (FILE *pointer_fisier);

unde:

– parametrul pointer_fisier – specifică variabila-pointer la o structură de tip FILE.

Citirea și scrierea datelor în fișier caracter cu caracter

Pentru a citi sau scrie câte un caracter dintr-un/într-un fișier, se pot utiliza funcțiile fgetc () și fputc () care sunt declarate astfel:

#include <stdio.h>

int fgetc (FILE *pointer_fisier_intrare);

int fputc (int caracter, FILE *pointer_fisier_iesire);

Funcția fgetc() citește caracterul curent dintr-un fișier de intrare. Funcția returnează caracterul curent citit după convertirea lui la tipul int. Dacă pointerul de fișier a ajuns la sfârșitul fișierului sau citirea nu a avut succes, funcția fgetc() returnează constanta EOF.

Funcția fputc () scrie un caracter în locația curentă a fișierului de ieșire specificat. Dacă apare o eroare, funcția fputc() returnează constanta EOF.

Următorul program (fgetc_fputc.cpp) utilizează funcțiile fgetc() și fputc() pentru a copia conținutul fișierului de tip text cu numele “test_date.txt” (existent pe discul “c” în directorul “\mariana\Limbaj C\programe”) într-un fișier de tip text numit “test_datec.txt”:

#include <stdio.h>

void main()

{FILE *intrare, *iesire;

int litera;

if ((intrare = fopen("c:\\mariana\\Limbaj C\\programe\\test_date.txt", "r")) == NULL)

printf("Eroare la deschidere test_date.txt\n");

else

if ((iesire = fopen("c:\\mariana\\Limbaj C\\programe\\test_datec.txt", "w")) == NULL)

printf("Eroare la deschidere test_datec.txt\n");

else

{

// Citeste si scrie fiecare caracter din fisier

while ((litera = fgetc(intrare)) != EOF)

fputc(litera, iesire);

fclose(intrare); // Inchide fisier intrare

fclose(iesire); // Inchide fisier iesire

}

}

Pointerul de poziție al unui fișier

Pointerul de poziție al unui fișier indică locația curentă în interiorul fișierului.

Când se deschide prima oară un fișier pentru operații de citire sau scriere, sistemul de operare stabilește pointerul de poziție la începutul fișierului.

De fiecare dată când se citește sau se scrie un caracter (un octet) în fișier, pointerul de poziție avansează cu un caracter.

Dacă se citește o linie de text, pointerul de poziție avansează la începutul liniei următoare.

Tabelul următor precizează locația la care funcția fopen () plasează pointerul de poziție când se deschide fișierul pentru citire, scriere sau adăugare.

Determinarea valorii pointerului de poziție se poate face cu ajutorul funcției ftell(). Funcția ftell() returnează o valoare de tip long int care specifică poziția curentă a pointerul de poziție. Deplasamentul (offset-ul) este măsurat în octeți de la începutul fișierului. În caz de eroare returnează valoarea -1.

Funcția ftell() este declarată astfel:

#include <stdio.h>

long int ftell (FILE *pointer_fisier);

Următorul program (functia_ftell.cpp) utilizează funcția ftell() pentru a afișa valoarea pointerului de poziție. Programul începe cu deschiderea în modul citire a fișierului “test_date.txt” (existent pe discul “c” în directorul “\mariana\Limbaj C\programe”). Programul utilizează apoi funcția ftell () pentru a afișa poziția curentă. În continuare, programul citește fișierul și afișează conținutul fișierului pe ecran (la pointerul de fișier stdout). După ce a găsit sfârșitul fișierului, programul folosește din nou funcția ftell () pentru a afișa poziția curentă.

Notă:

S-a folosit pointerul de fișier standard stdout, deoarece la ecran se mai afișează, pe lângă conținutul fișierului și alte mesaje de informare.

#include <stdio.h>

void main(void)

{FILE *intrare;

int litera;

if ((intrare = fopen("c:\\mariana\\Limbaj C\\programe\\test_date.txt", "r")) == NULL)

printf("Eroare la deschidere test_date.txt\n");

else

{

printf("Pozitia curenta este octetul %d\n\n", ftell(intrare));

// Citeste si scrie fiecare caracter din fisier

while ((litera = fgetc(intrare)) != EOF)

fputc(litera, stdout);

printf("\nPozitia curenta este octetul %d\n", ftell(intrare));

fclose(intrare); // Inchide fisierul de intrare

}}

După execuția programului pe ecran se afișează:

Poziția curentă este octetul 0

Acest fișier este folosit pentru a învața cum se copiază un fișier text într-un alt fișier text.

Iata cateva numere care pot fi copiate:

12.3 14.9 22.3

29.1

66.23

Poziția curentă este octetul 166

Poziționarea pointerului de fișier pe baza locației sale curente

Pentru poziționarea pointerului de fișier pe baza locației curente se poate folosi funcția fseek().

Acestă funcție se utilizează dacă se cunoaște structura datelor din fișier. Dacă poziționarea pointerului de fișier reușește funcția fseek() returnează valoarea 0, iar dacă apare o eroare funcția returnează o valoare diferită de 0.

Funcția este declarată astfel:

#include <stdio.h>

int fseek (FILE *pointer_fisier, long deplasament, int relativ_la);

Unde:

– parametrul pointer_fisier – specifică variabila-pointer la o structură de tip FILE;

– parametrul deplasament – precizează numărul de octeți de deplasament (de salt);

– parametrul relativ_la – precizează locația (octetul) din fișier de la care trebuie să se aplice deplasamentul.

Tabelul următor prezintă valorile parametrului relativ_la:

De exemplu, pentru a poziționa pointerul de fișier imediat după primii 256 octeți se folosește secvența de cod următoare:

fseek (intrare, 256, SEEK_SET);

Notă: Funcția fseek() se folosește împreună cu funcția ftell().

Programul următor (functia_fseek.cpp) utilizează funcția ftell () pentru a afla poziția curentă în fișier. În continuare, programul citește fișierul și afișează conținutul fișierului pe ecran (la pointerul de fișier stdout) până la poziția 139 (inclusiv). Apoi se folosește funcția fseek() pentru a se schimba poziția curentă la octetul 155 (un salt de 16 octeți). Astfel, pe ecran nu se mai afișează linia din fișier care are următorul conținut:

“12.3 14.9 22.3”

#include <stdio.h>

#include <conio.h>

void main()

{FILE *intrare;

int litera;

if ((intrare = fopen("c:\\mariana\\Limbaj C\\programe\\test_date.txt", "r")) == NULL)

printf("Eroare la deschidere test_date.txt\n");

else

{

printf("Pozitia curenta este octetul %d\n\n", ftell(intrare));

// Citeste si scrie fiecare caracter cu un salt de la octetul 139 pana la octetul 155

litera = fgetc(intrare) ;

while (litera != EOF)

{fputc(litera, stdout);

litera = fgetc(intrare);

if(litera == '\n')

printf("\nPozitia curenta este octetul %d\n",ftell(intrare));

if (ftell(intrare)==139)

fseek(intrare,16,SEEK_CUR);

}

fclose(intrare); // Inchide fisierul de intrare}

getch(); }

După execuția programului la ecran se afișează:

Pozitia curentă este octetul 0

Acest fisier este folosit pentru a invata cum se copiaza un fisier text într-un alt fisier text.

Pozitia curentă este octetul 98

Iata cateva numere care pot fi copiate:

Pozitia curentă este octetul 139

29.1

Pozitia curenta este octetul 161

66.23

În timp ce conținutul fișierului este:

Acest fișier este folosit pentru a învața cum se copiază un fișier text într-un alt fișier text.

Iată câteva numere care pot fi copiate:

12.3 14.9 22.3

29.1

66.23

Fluxurile

Multe cărți consideră pointeri de fișier din limbajul C ca pointeri de fluxuri de fișiere sau pe scurt fluxuri de fișiere.

Spre deosebire de alte limbaje de programare, limbajul C nu ia în considerare modul în care sunt structurate datele într-un fișier. Limbajul C consideră că orice fișier este o colecție de octeți. Atunci când se citește un fișier, el este parcurs octet după octet, ca un flux de octeți. Interpretarea octeților este lăsată pe seama programelor sau funcțiilor standard folosite.

Interpretarea fișierelor de tip text sau binar

Unele dintre funcțiile C de bibliotecă run-time pentru manipularea fișierelor, cum ar fi fgetc() și fputc() pot interpreta fișierele ca fiind de două tipuri: text sau binar. Modul prestabilit al funcțiilor fgetc() și fputc() este text.

În modul text, funcțiile C care scriu date într-un fișier (cum ar fi fputc) convertesc caracterul avans de rând în combinația retur de car cu avans de rand.

În modul text, funcțiile C care citesc date dintr-un fișier (cum ar fi fgetc) convertesc combinația retur de car cu avans de rând într-un singur caracter avans de rand.

În modul binar funcțiile C din biblioteca run-time nu execută conversiile de caractere menționate mai sus.

Pentru a interpreta un fișier ca fiind de tip text sau binar, în funcția fopen() se poate plasa litera t sau b imediat după modul de acces dorit, ca în tabelul de mai jos.

Notă: Dacă se omite litera b sau t, interpretarea fișierelor se face după modul prestabilit al funcțiilor C din biblioteca run-time.

Următorul program (fgetc_fputc_bin.cpp) utilizează funcția fopen() pentru a deschide, în acces citire, fișierul de tip binar cu numele “test_date.doc” (creat cu procesorul Word) și pentru a deschide, în acces scriere, fișierul de tip binar numit “test_datec.doc” (care poate fi citit folosind procesorul Word). Apoi, se folosesc funcțiile fgetc() și fputc() pentru a copia primul fișier de tip binar în al doilea fișier de tip binar.

#include <stdio.h>

void main()

{FILE *intrare, *iesire;

int litera;

if ((intrare = fopen("c:\\mariana\\Limbaj C\\programe\\test_date.doc", "rb")) == NULL)

printf("Eroare la deschidere test_date.doc\n");

else

if ((iesire = fopen("c:\\mariana\\Limbaj C\\programe\\test_datec.doc", "wb"))== NULL)

printf("Eroare la deschidere test_datec.doc\n");

else

{

// Citeste si scrie fiecare caracter din fisier

while ((litera = fgetc(intrare)) != EOF)

fputc(litera, iesire);

fclose(intrare); // Inchide fisier intrare

fclose(iesire); // Inchide fisier iesire

}

}

Următorul program (fgetc_fputc_bin1.cpp) utilizează funcția fopen() pentru a deschide, în acces citire, fișierul de tip binar cu numele “funcția_ctime.exe” și pentru a deschide, în acces scriere, fișierul de tip binar numit “functia_ctime_nou.exe”. Apoi, se folosesc funcțiile fgetc() și fputc() pentru a copia primul fișier de tip binar în al doilea fișier de tip binar.

#include <stdio.h>

void main()

{FILE *intrare, *iesire;

int litera;

if ((intrare = fopen("c:\\mariana\\Limbaj C\\programe\\functia_ctime.exe", "rb")) == NULL)

printf("Eroare la deschidere functia_ctime.exe\n");

else

if ((iesire = fopen("c:\\mariana\\Limbaj C\\programe\\functia_ctime_nou.exe", "wb"))

== NULL)

printf("Eroare la deschidere functia_ctime_nou.exe\n");

else

{

// Citeste si scrie fiecare caracter din fisier

while ((litera = fgetc(intrare)) != EOF)

fputc(litera, iesire);

fclose(intrare); // Inchide fisier intrare

fclose(iesire); // Inchide fisier iesire

}}

Citirea liniilor de text

Pentru a citi o linie dintr-un fișier, de tip text, se poate utiliza funcția fgets().

Dacă funcția fgets() reușește să citească datele din fișier, ea returnează un pointer la șirul de caractere. Dacă apare o eroare sau dacă s-a ajuns la sfârșitul fișierului, funcția fgets() returnează valoarea NULL.

Funcția fgets() este declarată astfel:

#include <stdio.h>

char *fgets (char *sir, int limita, FILE *flux);

unde:

– parametrul sir – specifică bufferul de caractere în care se memorează datele citite din fișier; de regulă, programul va declara un șir de 128 sau 256 de octeți pentru a reține datele;

– parametrul limita – precizează numărul de caractere pe care le reține bufferul; fgets() va citi fie până la limita -1, fie până la primul caracter newline (\n), în funcție de ce se întâlnește mai întâi; de regulă, se utilizează funcția sizeof pentru a specifica dimensiunea bufferului;

– parametrul flux – precizează fluxul de fișier din care funcția fgets() trebuie să citească.

Scrierea liniilor de text

Pentru a scrie o linie (sub forma unui șir de caractere) într-un fișier, de tip text, se poate utiliza funcția fputs().

Dacă funcția fputs() reușește să scrie datele în fișier, ea returnează o valoare pozitivă. Dacă apare o eroare, funcția fputs() returnează constanta EOF.

Funcția fputs() este declarată astfel:

#include <stdio.h>

char fputs (const char *sir, FILE *flux);

unde:

– parametru sir – specifică șirul care se scrie în fișier, până la terminatorul de șir, NULL (‘\0’);

– parametrul flux – precizează fluxul de fișier în care funcția fputs() trebuie să scrie.

Următorul program (fgets_fputs.cpp) utilizează funcțiile fgets() și fputs() pentru a copia conținutul primului fișier, de tip text, specificat în linia de comandă în cel de-al doilea fișier, de tip text, specificat în linia de comandă:

#include <stdio.h>

void main(int argc, char **argv) {

FILE *sursa, *destinatie;

char sir[256];

if (argc!=3)

printf ("Utilizare program: %s [fisier_sursa] [fisier_destinatie]\n", argv[0]);

if ((sursa = fopen(argv[1], "r")) == NULL)

printf("Eroare la deschiderea %s\n", argv[1]);

else

if ((destinatie = fopen(argv[2], "w")) == NULL)

{

printf("Eroare la deschiderea %s\n", argv[2]);

fclose(sursa);}

else

{

while (fgets(sir, sizeof(sir), sursa))

fputs(sir, destinatie);

fclose(sursa);

fclose(destinatie);

}

}

Următorul program (functia_ftell_text.cpp) utilizează funcțiile fgets() și fputs() pentru a copia conținutul primului fișier, de tip text, specificat în linia de comandă în cel de-al doilea fișier, de tip text, specificat în linia de comandă și funcția ftell () pentru aflarea locației curente (octetul de început ) a fiecărei linii.

#include <stdio.h>

void main(int argc, char **argv)

{FILE *sursa, *destinatie; char sir[256];

if (argc!=3)

printf ("Utilizare program: %s [fisier_sursa] [fisier_destinatie]\n", argv[0]);

if ((sursa = fopen(argv[1], "r")) == NULL)

printf("Eroare la deschiderea %s\n", argv[1]);

else

if ((destinatie = fopen(argv[2], "w")) == NULL)

{printf("Eroare la deschiderea %s\n", argv[2]); fclose(sursa);}

else

{printf("Pozitia curenta este octetul %d\n", ftell(sursa));

while (fgets(sir, sizeof(sir), sursa))

{printf("Pozitia curenta in sursa este octetul %d\n", ftell(sursa));

fputs(sir, destinatie);}

fclose(sursa);

fclose(destinatie);}

}

După execuția programului pe ecran se afișează:

C:\mariana\Limbaj C\programe>functia_ftell_text test_date.txt test_datecc.txt

Pozitia curenta este octetul 0

Pozitia curenta in sursa este octetul 98

Pozitia curenta in sursa este octetul 139

Pozitia curenta in sursa este octetul 155

Pozitia curenta in sursa este octetul 161

Pozitia curenta in sursa este octetul 166

Fișierul din care se citește are conținutul:

Acest fișier este folosit pentru a învăța cum se copiază un fișier text într-un alt fișier text.

Iată câteva numere care pot fi copiate:

12.3 14.9 22.3

29.1

66.23

Programul de mai jos (concatenare_fis.cpp) concatenează conținutul a două fișiere cu numele introduse de la tastatura și scrie în primul fișier conținutul celui de al doilea fișier. Citirea se face din al doilea fișier (sursă) și scrierea se face în primul fișier (destinație).

#include<stdio.h>

#include<conio.h>

void concatenare (char [], char []);

void main(){

char n_sursa[20], n_destinatie[20];

printf("Numele fisierului destinatie: ");

scanf("%s",n_destinatie);

printf("Numele fisierului sursa : ");

scanf("%s", n_sursa);

concatenare(n_destinatie, n_sursa);

getch();

}

void concatenare(char nume_d[], char nume_s[]) {

FILE *sursa;

FILE *destinatie;

char* linie;

sursa = fopen(nume_s, "r");

if (sursa==NULL){

printf("Fisierul %s nu exista", sursa);

return;}

destinatie=fopen(nume_d, "a");

if (destinatie==NULL){

printf("Fisierul %s nu exista", destinatie);

return;}

while(!feof(sursa)){

fgets(linie, sizeof(linie), sursa);

fputs(linie, destinatie);}

fclose(sursa);

fclose(destinatie);

}

Citirea datelor formatate dintr-un fișier de tip text

Pentru citirea datelor formatate dintr-un fișier se poate folosi funcția fscanf().

Funcția fscanf() returnează numărul de câmpuri citite. Dacă se întâlnește sfârșitul de fișier, funcția returnează constanta EOF.

Funcția fscanf() este declarată astfel:

#include <stdio.h>

int fscanf (FILE *flux, const char *format, adr_var1, adr_var2, …..);

unde:

– parametrul flux – este un pointer la fișierul din care se dorește să se citească;

– parametrul format – specifică formatul datelor, utilizând aceeași specificatori de format ca funcția scanf();

– parametrii: adr_var1, adr_var2, …- specifică adresa variabilelor citite.

Scrierea datelor formatate într-un fișier de tip text

Pentru scrierea datelor formatate într-un fișier se poate folosi funcția fprintf().

Funcția fprintf() este declarată astfel:

#include <stdio.h>

int fprintf (FILE *flux, const char *format, expresie_1, expresie_2, …..);

unde:

– parametrul flux – este un pointer la fișierul în care se dorește să se scrie;

– parametrul format – specifică formatul datelor, utilizând aceeași specificatori de format ca funcția printf();

– parametrii: expresie_1, expresie_2, …- sunt expresii sau variabile a caror valori sunt scrise în fișier.

Următorul program (fscanf_fprintf.cpp) deschide fișierul “DATA.DAT” pentru scriere, scrie în fișier m numere de tip float, pe n linii, într-un format specificat în funcția fprintf(), închide fișierul, apoi îl redeschide pentru citire, citește conținutul fișierului cu ajutorul funcției fscanf() și afișează la ecran datele din fișier.

De fapt programul scrie o matrice într-un fișier linie cu linie și apoi afișează la ecran matricea stocată în fișier.

#include <stdio.h>

void main(void)

{FILE *fp;

float a;

int i,j, m, n;

n = 0;

m = 0;

if ((fp = fopen("DATA.DAT", "w")) == NULL)

printf("Eroare la deschidere DATA.DAT pentru scriere\n");

else

{

printf("Introduceti numarul de linii: ");

scanf("%d", &n);

printf ("Introduceti numarul de coloane: ");

scanf("%d", &m);

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

{printf("Elemente linie %d \n", i);

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

{ printf("Element[%d]=", j);

scanf("%f", &a);

fprintf(fp, "%.2f ", a);}

fprintf(fp, "\n");}

fclose(fp);

}

if ((fp = fopen("DATA.DAT", "r")) == NULL)

printf("Eroare la deschidere DATA.DAT pentru citire\n");

else

{printf ("Elementele matricei sunt:\n");

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

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

{fscanf(fp, "%f", &a);

printf("%.2f\t", a);}

printf("\n");}

fclose(fp);

}

}

Conținutul fișierului DATA.DAT creat este:

2.30 -4.50 5.20 6.80 20.30

-25.40 23.20 50.10 60.10 -100.23

3.50 7.90 23.20 60.10 -25.60

60.50 1.40 -23.10 70.40 45.34

Observație:

După cum se observă din program numerele scrise în fișier pe o linie sunt despărțite prin spațiu. În acest fel, la citirea din fișier chiar dacă pe o linie sunt mai multe numere ele se citesc număr cu număr deoarece se întâlnește spațiu și citirea se oprește la întălnirea lui.

Următorul program (fscanf_fprintf1.cpp) citește de la tastatură un număr natural n impar și generează o matrice pătratică avînd n linii și n coloane după modelul din exemplul de mai jos (pentru n=5):

2 2 0 1 1

2 2 0 1 1

0 0 0 0 0

3 3 0 4 4

3 3 0 4 4

Matricea generată este stocată într-un fișier și apoi este citită din fișier și afișată la ecran.

Programul este strucrurat în patru funcții:

– funcția gen_matrice1() care generează matricea după modelul de mai sus;

– funcția scrie_fisier() care stochează matricea generată într-un fișier de tip text folosind fprintf();

– funcția citeste_fisier() care citește fișierul creat și afișează datele la ecran;

– functia main() care apelează cele trei funcții de mai sus.

#include<stdio.h>

#include<alloc.h>

#include<conio.h>

FILE * scrie_fisier(int **, int, int);

void citeste_fisier(FILE *);

int ** gen_matrice1(int*);

void main(){

int nr, lin, col;

int ** a;

FILE* fis_matrice;

a=gen_matrice1(&nr);

if(a){

lin=nr;

col=nr;

fis_matrice=scrie_fisier(a,lin, col);

if(fis_matrice)

{citeste_fisier(fis_matrice);

fclose(fis_matrice);}

else

printf("Fisierul nu poate fi deschis");

delete *a;

delete a;}

getch();

}

int ** gen_matrice1(int* n){

int i, j;

int ** matr;

printf("Introduceti un numar natural impar:");

scanf("%d",n);

matr = (int**)calloc(*n, sizeof(int*));

if (matr==NULL)

{printf("Alocare esuata");

return matr;}

else

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

{matr[i]=(int*)calloc(*n, sizeof(int));

if(matr[i]==NULL)

{printf("Alocare esuata");

return matr;}

}

//se completeaza linia din mijloc cu 0

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

matr[*n/2][i]=0;

//se completeaza coloana din mijloc cu 0

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

matr[i][*n/2]=0;

// se completeaza cadranul I

for(i=0;i<*n/2;i++)

for(j=*n/2+1;j<*n;j++)

matr[i][j]=1;

//se completeaza cadranul II

for(i=0;i<*n/2;i++)

for(j=0;j<*n/2;j++)

matr[i][j]=2;

//se completeaza cadranul III

for(i=*n/2+1;i<*n;i++)

for(j=0;j<*n/2;j++)

matr[i][j]=3;

//se completeaza cadranul IV

for(i=*n/2+1;i<*n;i++)

for(j=*n/2+1;j<*n;j++)

matr[i][j]=4;

return matr;

}

FILE * scrie_fisier(int **matr, int n , int m){

FILE* f;

char *nume_fis;

int i, j;

m=n;

printf("Introduceti numele si extensia fisierului: ");

scanf("%s",nume_fis);

f=fopen(nume_fis,"w+");

if(f==NULL)

return NULL;

fprintf(f,"%d %d",n,m);

fprintf(f,"\n");

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

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

fprintf(f,"%d ",matr[i][j]);

fprintf(f,"\n");}

return f;

}

void citeste_fisier(FILE * fis){

int n, m, i, j;

int elem;

fseek(fis,0,SEEK_SET);

fscanf(fis,"%d%d",&n,&m);

printf("Elementele matricei sunt: \n");

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

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

{fscanf(fis,"%d",&elem);

printf("%d ",elem);}

printf("\n"); }

}

Citirea și scrierea datelor unei structuri

La citirea sau scrierea unei structuri, aceasta este tratată ca un domeniu lung de octeți.

Pentru citirea unei structuri dintr-un fișier se poate folosi funcția fread().

Dacă funcția fread () se execută cu succes, ea va returna numărul de structuri citite. Dacă apare o eroare sau s-a ajuns la sfârșitul fișierului, funcția va returna valoarea 0.

Funcția fread() este declarată astfel:

#include <stdio.h>

size_t fread (void *buffer, size_t dim_buffer, size_t nr_element, FILE *flux);

unde:

– parametrul buffer – specifică un pointer la datele citite;

– parametrul dim_buffer – specifică dimensiunea datelor în octeți;

– parametrul nr_element – specifică numărul de structuri care se doresc a fi citite;

– parametrul flux – este un pointer la fișierul din care se dorește să se citească;

Pentru scrierea unei structuri într-un fișier se poate folosi funcția fwrite().

Daca functța fwrite () se execută cu succes, ea va returna numărul de structuri scrise. Dacă apare o eroare funcția va returna valoarea 0.

Funcția fwite() este declarată astfel:

#include <stdio.h>

size_t fwrite (void *buffer, size_t dim_buffer, size_t nr_element, FILE *flux);

unde:

– parametrul buffer – specifică un pointer la datele care se doresc a fi scrise;

– parametrul dim_buffer – specifică dimensiunea datelor în octeți;

– parametrul nr_element – specifică numărul de structuri care se doresc a fi scrise;

– parametrul flux – este un pointer la fișierul în care se dorește să se scrie.

Observație: Pentru scrierea unei structuri într-un fișier se recomandă ca acesta să fie deschis ca fișier binar (să se folosească parametrul wb sau wb+ în funcția fopen). Pentru citirea unei structuri dintr-un fișier se recomandă ca acesta să fie deschis ca fișier binar (să se folosească parametrul rb sau rb+ în funcția fopen).

Următorul program (fread_fwrite1.cpp) utilizează funcțiile fwrite() și fread() pentru a scrie structura elev_examen intr_un fișier cu numele “elev.dat”. Fișierul este deschis ca fișier binar pentru acces la scriere și citire (parametrul wb+). Programul folosește funcția fseek() pentru repoziționarea pointerului de fișier la începutul fișierului și funcția feof() pentru testarea sfârșitului de fișier.

#include <stdio.h>

void main()

{

struct Elev

{ char nume_elev [20];

float nota_r, nota_m, nota_ig, media_gen;};

struct Elev elev_examen;

FILE *iesire;

int n, i;

iesire = fopen("elev.dat", "wb+");

if (iesire == NULL)

printf("Eroare la deschidere elev.dat\n");

else

{

printf("Introduceti numarul de elevi ");

scanf("%d", &n);

printf("Introduceti informatiile despre elevi\n");

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

{

printf("Numele si prenumele(20 de caractere): "); gets("");

gets(elev_examen.nume_elev);

printf("Limba romana: "); scanf("%f", &elev_examen.nota_r);

printf("Matematica: "); scanf("%f", &elev_examen.nota_m);

printf("Istorie/Geografie: "); scanf("%f", &elev_examen.nota_ig);

elev_examen.media_gen = (elev_examen.nota_r + elev_examen.nota_m +

elev_examen.nota_ig)/3;

fwrite (&elev_examen, sizeof(elev_examen), 1, iesire);

}

fseek (iesire ,0 , SEEK_SET);

printf("Continutul fisierului elev.dat\n");

fread (&elev_examen, sizeof(elev_examen), 1, iesire);

while (!feof(iesire))

{

printf ("%s\t%.2f\t%.2f\t%.2f\t%.2f\n", elev_examen.nume_elev,

elev_examen.nota_r, elev_examen.nota_m, elev_examen.nota_ig,

elev_examen.media_gen);

fread (&elev_examen, sizeof(elev_examen), 1, iesire);

}

fclose(iesire);}}

Următorul program (fread_fwrite.cpp) realizează sortarea înregistrărilor din fișierul “elev.dat” după numele elevului și utilizează funcțiile fwrite() și fread() pentru a citi structura elev_examen și pentru a rescrie în fișier datele ordonate. Fișierul este deschis ca fișier binar pentru acces la citire și scriere (parametrul rb+). Programul folosește funcția fseek() pentru repoziționarea pointerului de fișier pe înregistrarea curentă și pe înregistrarea următoare.

#include <stdio.h>

#include <string.h>

void main()

{struct Elev

{ char nume_elev [20];

float nota_r, nota_m, nota_ig, media_gen;};

struct Elev elev_examen, elev_examen1, elev_temp;

FILE *intrare;

int schimb;

int poz_curent;

int poz_curent1;

intrare = fopen("elev.dat", "rb+");

if (intrare == NULL)

printf("Eroare la deschidere elev.dat\n");

else

{

do

{

schimb = 0;

fseek (intrare ,0 , SEEK_SET);

poz_curent1 = ftell(intrare);

while(!feof(intrare))

{

poz_curent = poz_curent1;

fseek (intrare ,poz_curent, SEEK_SET);

fread (&elev_examen, sizeof(elev_examen), 1, intrare);

poz_curent1 = ftell(intrare);

fread (&elev_examen1, sizeof(elev_examen1), 1, intrare);

if (strcmp(elev_examen.nume_elev, elev_examen1.nume_elev) > 0)

{elev_temp= elev_examen;

elev_examen = elev_examen1;

elev_examen1 = elev_temp;

fseek (intrare ,poz_curent, SEEK_SET);

fwrite (&elev_examen, sizeof(elev_examen), 1, intrare);

fseek (intrare ,poz_curent1, SEEK_SET);

fwrite (&elev_examen1, sizeof(elev_examen1), 1, intrare);

schimb = 1;}

}

} while (schimb);

fseek (intrare ,0 , SEEK_SET);

printf("Continutul fisierului elev.dat cu date sortate\n");

fread (&elev_examen, sizeof(elev_examen), 1, iesire);

while (!feof(intrare))

{

printf ("%s\t%.2f\t%.2f\t%.2f\t%.2f\n", elev_examen.nume_elev,

elev_examen.nota_r, elev_examen.nota_m, elev_examen.nota_ig,

elev_examen.media_gen);

fread (&elev_examen, sizeof(elev_examen), 1, iesire);

}

fclose(intrare);

}

}

Determinarea modului în care un program poate accesa un fișier

Pentru a testa dacă există un anumit fișier și dacă se poate deschide acest fișier în modul dorit de program se poate folosi funcția access(). Funcția access() returnează valoarea 0, dacă poate accesa fișierul în modul specificat, și valoarea -1, dacă apare o eroare.

Funcția este declarată astfel:

#include <io.h>

int access (const char *nume_fisier, int mod_acces);

unde:

– parametrul nume_fisier – specifică un nume de fișier care se dorește a fi testat;

– parametrul mod_acces – modul în care programul are voie să deschidă fișierul.

În tabelul următor sunt specificate valorile pe care le poate lua parametrul mod_acces.

Următorul program (functia_access.cpp) utilizează funcția access() pentru a determina modul în care se poate accesa fișierul specificat în linia de comandă.

#include <stdio.h>

#include <io.h>

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

{int mod_acces;

mod_acces = access(argv[1], 0);

if (mod_acces)

printf("Fisierul %s nu exista\n");

else

{mod_acces = access(argv[1], 2);

if (mod_acces)

printf("Fisierul nu poate fi scris\n");

else

printf("Fisierul poate fi scris\n");

mod_acces = access(argv[1], 4);

if (mod_acces)

printf("Fisierul nu poate fi citit\n");

else

printf("Fisierul poate fi citit\n");

mod_acces = access(argv[1], 6);

if (mod_acces)

printf("Fisierul nu poate fi citit/scris\n");

else

printf("Fisierul poate fi citit/scris\n");}

}

Stabilirea dreptului de acces la un fișier

Pentru a modifica drepturile de acces pentru scriere sau citire se poate utiliza funcția chmod().

Dacă funcția chmod() a modificat cu succes atributele fișierului, va returna valoarea 0. Dacă apare o eroare, funcția va returna valoarea -1.

Funcția este declarată astfel:

#include <sys\stat.h>

#include <io.h>

int chmod (const char *nume_fisier, int mod_acces);

unde:

– parametrul nume_fisier – specifică un nume de fișier pentru care se dorește modificarea drepturilor de acces;

– parametrul mod_acces – modul de acces care se dorește pentru fișier.

În tabelul următor sunt specificate valorile pe care le poate lua parametrul mod_acces.

Următorul program (functia_chmod.cpp) utilizează funcția chmod() pentru a stabili accesul exclusiv în citire pentru un fișier specificat în linia de comandă.

#include <stdio.h>

#include <sys\stat.h>

#include <io.h>

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

{

if (chmod(argv[1], S_IREAD))

printf("Eroare la setare %s\n", argv[1]);

}

Ștergerea unui fișier

Pentru ștergerea unui fișier se poate utiliza funcția remove().

Dacă ștergerea fișierului se realizează cu succes, funcția returnează valoarea 0. Dacă apare o eroare , funcția returnează valoarea -1.

Funcția este declarată astfel:

#include <stdio.h>

int remove (const char *nume_fisier);

Următorul program (functia_remove.cpp) utilizează funcția remove() pentru a șterge un fișier specificat în linia de comandă.

#include <stdio.h>

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

{

if (remove(argv[1]))

printf("Eroare la stergere %s\n", argv[1]);

}

Redenumirea sau mutarea unui fișier

Pentru redenumirea sau mutarea unui fișier se poate utiliza funcția rename().

Dacă redenumirea sau mutarea fișierului se realizează cu succes, funcția returnează valoarea 0. Dacă apare o eroare , funcția returnează o valoare diferită de 0.

Funcția este declarată astfel:

#include <stdio.h>

int rename (const char *nume_fisier_vechi, const char *nume_fisier_nou);

Următorul program (functia_rename.cpp) utilizează funcția rename() pentru a redenumi un fișier specificat în linia de comandă.

#include <stdio.h>

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

{if (argc < 3)

printf("Trebuie specificat numele fisierului sursa si destinatie\n");

else if (rename(argv[1], argv[2]))

printf("Eroare la redenumirea fisierului\n");

}

Lecția 12

Cuvinte importante:

– lucru cu directoare în limbajul C: schimbarea directorului curent; crearea unui director; ștergerea unui director; aflarea drive-ului curent; aflarea directorului curent;

– redirectarea intrărilor și ieșirilor în limbajul C; redirectarea intrărilor și ieșirilor în limbajul C++;

– fișiere în limbajul C++: fluxurile de I/O în C++;

– utilizarea manipulatorilor și indicatoarelor de formatare pentru formatarea ieșirilor și intrărilor în limbajul C++;

– declararea fișierelor în C++ ; deschiderea fișierelor în C++; închiderea unui fișier deschis în limbajul C++;

– citirea și scrierea datelor în fluxurile de fișiere;

– determinarea poziției curente (pointerului de poziție) dintr-un flux de fișier; setarea poziției pointerului de poziție dintr-un flux de fișier.

Lucru cu directoare în limbajul C

Schimbarea directorului curent

Pentru schimbarea directorului curent, se poate folosi funcția chdir(). Dacă funcția chdir() se execută cu succes, ea returnează valoarea 0. Dacă directorul nu există, funcția chdir() returnează valoarea -1.

Funcția este declarată astfel:

#include <dir.h>

int chdir(const char *cale_director);

unde: – parametrul cale_director – specifică numele complet (inclusiv calea) al noului director curent.

Notă: Dacă se apelează funcția chdir() cu un șir de caractere care nu conține litera unității de disc, ea va căuta directorul pe unitatea curentă.

De exemplu, următorul apel al funcției chdir() selectează directorul data de pe unitatea curentă de disc:

int stare;

stare = chdir (“\\data”);

Crearea unui director

Pentru crearea unui director, se poate folosi funcția mkdir(). Dacă funcția mkdir() se execută cu succes, ea returnează valoarea 0. Dacă nu se poate crea directorul, funcția mkdir() returnează valoarea -1.

Funcția este declarată astfel:

#include <dir.h>

int mkdir(const char *cale_director);

unde: – parametrul cale_director – specifică numele complet (inclusiv calea) al directorului creat.

Notă: Dacă se apelează funcția mkdir() cu un șir de caractere care nu conține litera unității de disc, ea va crea directorul pe unitatea curentă.

1. Următorul exemplu crează directorul data pe unitatea curentă de disc:

int stare;

stare = mkdir (“\\data”);

2. Următorul exemplu crează directorul datatemp pe unitatea curentă, în directorul curent:

stare = mkdir (“datatemp”);

Ștergerea unui director

Pentru ștergerea unui director, se poate folosi funcția rmdir(). Dacă funcția rmdir() se execută cu succes, ea returnează valoarea 0. Dacă directorul nu există sau nu poate fi șters, funcția rmdir() returnează valoarea -1.

Funcția este declarată astfel:

#include <dir.h>

int rmdir(const char *cale_director);

unde: – parametrul cale_director – specifică numele complet (inclusiv calea) al directorului de șters.

Notă: Dacă se apelează funcția rmdir() cu un șir de caractere care nu conține litera unității de disc, ea va șterge directorul de pe unitatea curentă.

1. Următorul exemplu șterge directorul data de pe unitatea curentă de disc:

int stare;

stare = rmdir (“\\data”);

2. Următorul exemplu șterge directorul datatemp de pe unitatea curentă, din directorul curent:

stare = rmdir (“datatemp”);

Aflarea drive-ului curent

Pentru aflarea drive-ului curent, se poate folosi funcția getdisk(). Funcția getdisk() returnează un întreg care reprezintă numărul drive-ului curent și anume: 0 pentru A; 1 pentru B; 2 pentru C și așa mai departe.

Funcția este declarată astfel:

#include <dir.h>

int getdisk(void);

Următorul exemplu, ilustrează modul de folosire a funcției getdisk() pentru aflarea drive-ului curent:

int disk;

disk = getdisk() + ‘A’;

printf("Drive-ul curent este: %c\n", disk);

Aflarea directorului curent

Pentru aflarea directorului curent, se poate folosi funcția getcurdir(). Dacă funcția getcurdir() se execută cu succes se returnează valoare 0. În caz de eroare funcția returnează valoarea -1.

Funcția este declarată astfel:

#include <dir.h>

int getcurdir(int drive, char *nume_director);

unde:

– parametrul drive – specifică un număr de drive și anume: 0 pentru drive-ul implicit, 1 pentru drive-ul A, 2 pentru drive-ul B, 3 pentru drive-ul C și așa mai departe;

– parametrul nume_director – este un pointer de tip șir care specifică numele directorului care este returnat la apelul funcției getcurdir(); numele nu conține specificatorul de drive și nu începe cu un caracter backslash (\).

Următorul exemplu, ilustrează modul de folosire a funcției getcurdir() pentru aflarea directorului curent:

#include <stdio.h>

#include <string.h>

#include <dir.h>

void main(void)

{int disk;

char nume_dir [64];

disk = getdisk() + 'A';

printf("Drive-ul curent este: %c\n", disk);

strcpy(nume_dir, "\\");

getcurdir (0, nume_dir +1 );

printf("Directorul curent este: %s\n", nume_dir);}

Redirectarea intrărilor și ieșirilor în limbajul C

Redirectarea ieșirii prin folosirea indicatorului de fișier stdout

Când se execută o comandă, sistemul de operare Dos sau Unix asociază dispozitivul de ieșire implicit cu ecranul monitorului. Sistemul de operare face referință la monitor ca la dispozitivul standard de ieșire sau stdout. Utilizând operatorul de redirectare a ieșirii (>), specific limbajului de comandă al sistemului de operare, se poate indica sistemului de operare să îndrepte ieșirea unui program de la ecran către un fișier sau către un alt dispozitiv.

Următoarea comandă, de exemplu, cere sistemului Dos să redirecteze ieșirea comenzii DIR (care realizează afișarea tuturor subdirectoarelor și fișierelor unui director) de la ecranul monitorului către fișierul “TEST_REDIRECTARE.TXT” în care sunt stocate informații despre subdirectoarele și fișierele unui director:

C:\> DIR > TEST_REDIRECTARE.TXT <Enter>

Pentru a accepta redirectarea ieșirii, limbajul C definește constanta stdout (în fișierul antet stdio.h).

Constanta stdout poate fi folosită de operațiile de ieșire cu fișiere pentru redirectarea ieșirilor, adică pentru modificarea sursei implicite de ieșire.

Redirectarea intrării prin folosirea indicatorului de fișier stdin

Când se execută o comandă, sistemul de operare Dos sau Unix asociază dispozitivul de intrare implicit cu tastatura calculatorului. Sistemul de operare face referință la tastatură ca la dispozitivul standard de intrare sau stdin. Utilizând operatorul de redirectare a intrării (<) specific limbajului de comandă al sistemului de operare, se poate indica sistemului de operare să îndrepte intrarea unui program de la tastatură către un fișier sau către un alt dispozitiv.

Următoarea comandă, de exemplu, cere sistemului Dos să redirecteze intrarea comenzii MORE (care realizează afișarea ecran cu ecran a conținutului unui fișier) de la tastatură către fișierul “TEST_DATECC.TXT ” pe care îl afișează pe ecran:

C:\> MORE < TEST_DATECC.TXT <Enter>

Pentru a accepta redirectarea intrării, limbajul C definește constanta stdin (în fișierul antet stdio.h).

Constanta stdin poate fi folosită de operațiile de intrare cu fișiere pentru redirectarea intrărilor, adică pentru modificarea sursei implicite de intrare.

Combinarea redirectării intrării și ieșirii

Sursele implicite de intrare și de ieșire pot fi redirectate în aceeași comandă.

De exemplu, următoarea comandă indică sistemului Dos să sorteze conținutul fișierului “TEST_DATEC.TXT” și să scrie ieșirea sortată în fișierul “TEST_DATECC.TXT”:

C:\>SORT < TEST_DATEC.TXT > TEST_DATECC.TXT <Enter>

Pentru a înțelege procesul executat de sistemul de operare, linia de comandă se va citi de la stânga către dreapta. Operatorul de redirectare a intrării (<) directează intrarea comenzii SORT de la fișierul “TEST_DATEC.TXT”. În continuare, operatorul de redirectare a ieșirii (>) directează ieșirea comenzii SORT către fișierul “TEST_DATECC.TXT ”.

Utilizarea constantelor stdin și stdout

Programele scrise în limbajul C pot redirecta intrările și ieșirile de la tastatură și imprimantă către alte fișiere sau dispozitive prin folosirea indicatorilor de fișier definiți cu ajutorul constantelor stdin și stdout

Următorul program (redirect_io.cpp) citește linii de text de la indicatorul de fișier stdin și convertește textul în majuscule. Apoi, programul scrie liniile de text la indicatorul de fișier stdout:

#include <stdio.h>

#include <string.h>

void main()

{char sir[256];

char *sir_m;

while (fgets(sir, sizeof(sir), stdin))

{

sir_m = strupr(sir);

fputs(sir_m, stdout);

}}

Comanda REDIRECT_IO se poate folosi cu operatorii de redirectare ai sursei implicite de intrare și ieșire (tastatură și ecranul) astfel:

1. C:\>REDIRECT_IO < TEST_DATEC.TXT – preia datele din intrarea redirectată, de la stdin la “TEST_DATEC.TXT”, și afișează la ecran datele din acest fișier.

2. C:\>REDIRECT_IO < TEST_DATEC.TXT > TEST_DATECC.TXT – preia datele din intrarea redirectată, de la stdin la “TEST_DATEC.TXT”, și scrie datele la ieșirea redirectată, de la stdout la fișierul “TEST_DATECC.TXT”.

3. C:\>REDIRECT_IO > TEST_DATECC.TXT – preia datele de la tastatură și scrie datele la ieșirea redirectată, de la stdout la fișierul “TEST_DATECC.TXT”.

4. C:\>REDIRECT_IO – preia datele de la tastatură linie cu linie și le afișează pe ecran cu majuscule tot linie cu linie (deci, la sursele implicite de intrare și ieșire). Pentru a încheia programul, trebuie să se apese combinația de taste de sfârșit de fișier, CTRL+Z (sub Dos) sau CTRL+D (sub Unix).

Operatorul de redirectare pipe (canal de transfer) ( | ) este disponibil în sistemul de operare Dos și Unix.

Operatorul pipe permite redirectarea ieșirii unui program pentru a deveni intrarea altui program.

De exemplu, următoarea comandă indică sistemului de operare Dos să redirecteze ieșirea comenzii DIR pentru a deveni intrare pentru comanda SORT:

C:\>DIR | SORT <Enter>

Următorul exemplu folosește programul REDIRECT_IO în combinație cu comanda Dos FIND. Astfel, programul REDIRECT_IO redirectează intrarea la fișierul “TEST_DATEC.TXT”, apoi ieșirea din program care este ecranul monitorului (stdout) devine intrare pentru comanda Dos FIND (care caută liniile ce conțin cuvântul “FISIER”):

C:\>REDIRECT_IO < TEST_DATEC.TXT | FIND "FISIER"

Următorul program (find_io.cpp) va afișa fiecare apariție a unui cuvânt sau expresie în cadrul unei intrări redirectate:

#include <stdio.h>

#include <string.h>

main(int argc, char *argv[])

{char sir[256];

if (argc < 2)

{fprintf(stderr, "Utilizare program: %s [cuvant de gasit] <

[nume_fisier]",argv[0]);

return(1);}

else

{

while (fgets(sir, sizeof(sir), stdin))

if (strstr(sir, argv[1]))

fputs(sir, stdout);

return(0);}}

Pentru a afișa fiecare apariție a cuvântului “FREAD” în cadrul liniilor fișierului “TEST_DATECE.TXT” se poate invoca programul FIND_IO și apoi comanda Dos SORT pentru sortarea liniilor găsite ca mai jos:

C:\>FIND_IO "FREAD" < TEST_DATECCC.TXT | SORT

Prevenirea redirectării ieșirii de la ecranul monitorului

Pentru a preveni redirectarea accidentală a mesajelor de eroare sau a altor mesaje care trebuie să apară pe ecran, chiar dacă se redirectează ieșirea, limbajul C folosește indicatorul de fișier stderr. Acest indicator de fișier nu poate fi redirectat de la ecranul monitorului. Atunci când programul are o ieșire redirectată, pentru afișarea mesajelor de eroare la ecranul monitorului se folosește funcția fprintf() cu indicatorul de fișier stderr.

Următorul program (redirect_io1,cpp) ilustrează modul de folosire a indicatorului de fișier stderr:

#include <stdio.h>

#include <string.h>

void main()

{char sir[256];

char *sir_m;

while (fgets(sir, sizeof(sir), stdin))

{

fprintf(stderr, "Pozitia curenta in sursa este octetul %d\n", ftell(stdin));

sir_m = strupr(sir);

fputs(sir_m, stdout);}}

La aplicarea comenzii:

C:\>REDIRECT_IO1 < TEST_DATEC.TXT > TEST_DATECC.TXT

pe ecran se afișează:

Pozitia curenta in sursa este octetul 98

Pozitia curenta in sursa este octetul 139

Pozitia curenta in sursa este octetul 155

Pozitia curenta in sursa este octetul 161

Pozitia curenta in sursa este octetul 166

Redirectarea intrărilor și ieșirilor în limbajul C++

Limbajul C++ folosește fluxul de intrare cin care permite redirectarea intrării de la tastatură către un fișier sau către alt dispozitiv. Fluxul cin corespunde indicatorului de fișier stdin.

De asemenea, limbajul C++ folosește fluxul de ieșire cout care permite redirectarea ieșirii de la ecran către un fișier sau către alt dispozitiv. Fluxul cout corespunde indicatorului de fișier stdout.

Limbajul C++ folosește fluxul cerr pentru prevenirea redirectării ieșirii de la ecranul monitorului. Fluxul cerr corespunde indicatorului de fișier stderr.

Fișiere în limbajul C++

Fluxuri de I/O din C++

Limbajul C++ pune la dispoziție clasa de bază ios (input-output stream) care definește operațiile fundamentale de intrare-ieșire. Din această clasă de bază limbajul C++ derivează clasele asociate fluxurilor de intrare și de ieșire. Fluxurile de intrare și de ieșire sunt definite în cadrul fișierelor antet iostream.h și fstream.h.

Fluxuri de ieșire din C++

În cel mai simplu sens, un flux de ieșire reprezintă o destinație a octeților.

Următoarele fluxuri de ieșire sunt asociate cu fișierele:

– ostream – utilizat pentru scrierea pe ecran (catre cout și cerr);

– ofstream – utilizat pentru scrierea într-un fișier pe disc.

Fluxuri de intrare din C++

În cel mai simplu sens, un flux de intrare reprezintă o sursă de octeți.

Următoarele fluxuri de intrare sunt asociate cu fișierele:

– istream – utilizat pentru citirea de la tastatură (de la cin );

– ifstream – utilizat pentru citirea dintr-un fișier de pe disc.

Fluxul de intrare/ieșire, care este și sursa și destinație a octeților, este utilizat atât pentru citirea dintr-un fișier cât și pentru scrierea într-un fișier pe disc. Fluxul de intrare/ieșire este declarat cu numele fstream.

Utilizarea manipulatorilor pentru formatarea ieșirilor și intrărilor

Manipulatorii sunt comenzi din cadrul fluxurilor de intrare sau de ieșire care permit formatarea intrărilor sau a ieșirilor în modul dorit.

Manipulatorii sunt definiți în fișierele antet iostream.h și iomanip.h. Fișierul antet iomanip.h definește numai manipulatorii care primesc parametrii. În tabelul de mai jos sunt descriși manipulatorii folosiți mai des.

Indicatoarele de formatare (iosflags) de tip ios care pot fi folosite cu manipulatorii sunt prezentate în continuare.

Definițiile acestor indicatoare de formatare sunt cuprinse în fișierul antet iostream.h.

Următorul program (setw.cpp) utilizează manipulatorul setw pentru a stabili diferite dimensiuni pentru afișarea diferitelor numere și a unui șir de caractere:

#include <iostream.h>

#include <iomanip.h>

void main(void)

{cout << setw(5) << 1 << endl << setw(6) << 2;

cout << '\n' << setw(7) << 3;

cout << '\n' << setw(8) << 23.345 << endl << setw(5) << 10.2;

cout << '\n' << setw(40) << "Exemplu de dimensionare lungime sir" ;}

După execuție, pe ecran se afișează următoarele:

1

2

3

23.345

10.2

Exemplu de dimensionare lungime șir

Următorul program (left_right.cpp) utilizează manipulatorul setiosflags cu indicatoarele ios::left și ios::right pentru a stabili diferite tipuri de alinieri pentru afișarea diferitelor numere și a unui șir de caractere:

#include <iostream.h>

#include <iomanip.h>

void main(void)

{int i;

cout << "Aliniere stanga\n";

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

{cout.width(5);

cout << setiosflags(ios::left) << i;}

cout << '\n' << setw(40) << setiosflags(ios::left) << "Exemplu de aliniere sir la stanga" ;

cout << "\nAliniere dreapta\n";

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

{cout.width(5);

cout << setiosflags(ios::right) << i;}

cout << '\n' << setw(40) << setiosflags(ios::right) << "Exemplu de aliniere sir la

dreapta" ;}

După execuția programului, pe ecran se afișează:

Aliniere stânga

0 1 2

Exemplu de aliniere șir la stânga

Aliniere dreapta

0 1 2

Exemplu de aliniere șir la dreapta

Următorul program (setprec.cpp) utilizează manipulatorii setprecision și setiosflags pentru modificarea numărului de zecimale afișate:

#include <iostream.h>

#include <iomanip.h>

void main(void)

{float numar = 1.2345;

int i;

cout << "Numarul neformatat" << endl;

cout << numar << endl;

cout << "Numarul formatat la precizia implicita de 6 zecimale" << endl;

cout << setiosflags(ios::fixed) << numar << endl;

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

{cout << "Numarul formatat cu " << i << " zecimale" << endl;

cout << setprecision (i) << setiosflags(ios::fixed) << numar << endl;}}

Notă: Din exemplul ilustrat rezultă că în această implementare cei doi manipulatori trebuie să fie folosiți împreună, adică întâi se precizează numărul de zecimale dorite, prin manipulatorul setprecision (i), și apoi se setează indicatorul de formatare fixed cu ajutorul manipulatorului setiosflags.

După execuție, pe ecran se afișează următoarele:

Numărul neformatat

1.2345

Numărul formatat la precizia implicită de 6 zecimale

1.234500

Numărul formatat cu 0 zecimale

1

Numărul formatat cu 1 zecimale

1.2

Numărul formatat cu 2 zecimale

1.23

Numărul formatat cu 3 zecimale

1.235

În mod implicit, atunci când se utilizează un flux de ieșire cu manipulatorul setw, pentru specificarea umplerii cu caractere, dacă dimensiunea este mai mare decât a numărului sau a șirului de caracter specificat, C++ utilizează caracterul spațiu pentru a umple spațiile suplimentare.

Prin utilizarea manipulatorului setfill se pot specifica diferite caractere de umplere pentru pentru fluxul de ieșire.

Următorul program (setfill.cpp)

#include <iostream.h>

#include <iomanip.h>

void main(void)

{int i;

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

{cout << setfill('.') << setw(5 + i) << i << '\n';}}

După execuția programului, pe ecran se afișează:

….0

…..1

……2

…….3

Declararea fișierelor în C++

Așa cum s-a precizat, limbajul C++ pune la dispoziție fluxurile de fișier ifstream, ofstream și fstream (de intrare, de ieșire și de intrare/ieșire).

Pentru a utiliza într-un program operații de intrare/ieșire folosind fișiere trebuie să se declare variabile de tipurile ifstream, ofstream sau fstream, astfel:

ifstream f_i; // se declară un flux de intrare denumit f_i

ofstream f_o; // se declară un flux de ieșire denumit f_o

fstream f_io // se declară un flux de intrare și de ieșire denumit f_io

Observații:

1. Declarațiile precedente respectă sintaxa declarațiilor de variabile și anume: ifstream, ofstream și fstream reprezintă tipul, iar f_i, f_o și f_io reprezintă numele variabilelor. Tipul însă este o clasă de obiecte, iar variabilele f_i, f_o, f_io sunt obiecte propriu-zise ale acelei clase.

2. O clasă de obiecte este asemănătoare tipului struct, dar clasa conține atât date, cât și funcțiile necesare prelucrării acestora. Datele și funcțiile incluse într-o clasă se numesc membri. Variabilele de tip clasă se numesc obiecte. Pentru a se face referire la un membru al unui obiect se utilizează operatorul punct (.), denumit operator de selecție directă, astfel: <nume_obiect>.<nume_membru>

Deschiderea fișierelor în C++

Pentru a utiliza fluxul (stream-ul) declarat trebuie ca acesta să fie deschis. La deschidere se asociază fluxul cu un fișier fizic (existent pe suport extern) și, eventual, se precizează modul de deschidere, care determină operațiile permise cu fișierul respectiv.

Deschiderea se poate realiza în două moduri:

a) după declararea variabilei de tip flux, prin apelul funcției membru open, precizând ca parametrii un șir de caractere, care reprezintă specificatorul fișierului fizic, conform sistemului de operare utilizat și (eventual) modul de deschidere;

b) la declarare, specificând, după numele fluxului care se declară, numai parametrii corespunzători funcției membru open, încadrați între paranteze rotunde.

Notă: La deschidere, este obligatoriu să se specifice modul de deschidere dacă fișierul este declarat de tip fstream. Pentru tipul ifstream se utilizează, implicit, modul ios::in, iar pentru tipul ofstream se utilizează, implicit, modul ios::out.

Modurile de deschidere permise în C++ sunt descrise în tabelul de mai jos.

Exemplul 1:

ifstream f_i; sau ifstream f_i (“test.dat”);

f_i.open (“test.dat”);

Exemplul 2:

ofstream f_o; sau ofstream f_o (“test.dat”);

f_o.open (“test.dat”);

Exemplul 3:

fstream f_io; sau fstream f_io (“test.dat”, ios::in);

f_io.open (“test.dat”, ios::in);

Exemplul 4:

fstream f_io; sau fstream f_io (“test.dat”, ios::out);

f_io.open (“test.dat”, ios::out);

Exemplul 5:

fstream f_io;

f_io.open (“test.dat”, ios::in | ios::binary);

sau

f_io.open (“test.dat”, ios::out | ios::binary);

Observație: Dacă după operația de deschidere a fișierului variabila-flux corespunzătoare are valoarea NULL, atunci se deduce că operația de deschidere a eșuat (de exemplu, dacă dorim să deschidem un fișier de intrare care nu există). De aceea, este necesar să se testeze succesul operației de deschidere înainte de a efectua orice altă operație cu fișierul.

Testarea stării unei operații I/O cu fișiere

Pentru a determina dacă o operație de deschidere, citire sau scriere a unui fișier a reușit se pot utiliza funcțiile membre ale fluxurilor ifstream, ofstream și fstream, descrise în tabelul de mai jos.

Închiderea unui fișier deschis

După realizarea tuturor operațiilor cu un fișier, acesta trebuie închis. Închiderea unui fișier se realizează prin apelarea unei funcții membre a fluxului respectiv și anume close().

De exemplu:

f_i.close() sau f_o.close()

Operația de închidere este obligatorie, în special pentru fluxurile de ieșire.

Citirea și scrierea datelor în fluxurile de fișiere

Utilizarea funcțiilor membre read și write

Pentru citirea datelor dintr-un flux de fișier se poate folosi funcția membru read a clasei istream. Formatul funcției read este:

#include <iostream.h>

#include <fstream.h>

istream &read (unsigned char *buffer, int nr_octeti);

Funcția membru read citește un număr de octeți (dat de parametrul nr_octeti) din fluxul de intrare. Apoi plasează acești nr_octeti în bufferul de memorie care începe la adresa indicată de parametrul buffer.

Pentru scrierea datelor într-un flux de fișier se poate folosi funcția membru write a clasei ostream.

Formatul funcției write este:

#include <iostream.h>

#include <fstream.h>

ostream &write (const unsigned char *buffer, int nr_octeti);

Funcția membru write scrie la fluxul de ieșire numărul de octeți specificat de parametrul nr_octeti, începând de la locația de memorie indicată de parametrul buffer.

Notă: Funcțiile membre read și write pot fi folosit pentru citirea datelor de tip text sau binare.

Următorul program (flux_rw_text.cpp) utilizează funcțiile membre read și write pentru a copia un fișier de tip text, citind din fluxul de fișier caracter cu caracter.

#include <iostream.h>

#include <stdlib.h>

#include <fstream.h>

main()

{char caracter[1];

ifstream intrare("test_date.txt", ios::in);

if (intrare.fail())

{cout << "Eroare la deschiderea fisierului ";

return(1);}

ofstream iesire("test_datec.txt", ios::out);

if (iesire.fail())

{cout << "Eroare la deschiderea fisierului ";

return(1);}

do

{

intrare.read(caracter, sizeof(caracter));

if (intrare.good())

iesire.write(caracter, sizeof(caracter));

} while (! intrare.eof());

intrare.close();

iesire.close();

return (0);

}

Următorul program (flux_rw_bin.cpp) utilizează funcțiile membre read și write pentru a copia un fișier de tip binar.

#include <iostream.h>

#include <fstream.h>

main()

{char caracter[1];

ifstream intrare("functia_abs.exe", ios::in | ios::binary);

if (intrare.fail())

{cout << "Eroare la deschiderea fisierului ";

return(1);}

ofstream iesire("functia_abs1.exe", ios::out | ios::binary);

if (iesire.fail())

{cout << "Eroare la deschiderea fisierului ";

return(1);}

do

{intrare.read(caracter, sizeof(caracter));

if (intrare.good())

iesire.write(caracter, sizeof(caracter));} while (! intrare.eof());

intrare.close();

iesire.close();

return (0);}

Următorul program (r_w_struct.cpp) utilizează funcțiile membre read și write pentru a scrie o structură pe disc și apoi pentru a o citi și afișa.

#include <iostream.h>

#include <fstream.h>

#include <string.h>

#include <iomanip.h>

struct stare

{ char nume[20];

float balanta;

unsigned long nr_cont;};

main(void)

{ struct stare cont;

struct stare *pcont;

int n, i;

ofstream outbal("cont.asc", ios::out | ios::binary);

if(!outbal)

{cout << "Eroare la deschiderea fisierului" << endl;

return (1);}

cout <<"Introduceti numarul de persoane "; cin >> n;

cout << "Introduceti informatiile despre conturile bancare\n";

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

{ cout <<"Numele si prenumele: "; cin.get();

cin.getline (cont.nume, 20);

cout << "Continut cont: lei "; cin >> cont.balanta;

cout << "Numar cont: "; cin >> cont.nr_cont;

pcont = &cont;

outbal.write((unsigned char *) pcont, sizeof(struct stare));}

outbal.close();

ifstream inbal("cont.asc", ios::in | ios::binary);

if(!inbal)

{ cout << "Eroare la deschiderea fisierului" << endl;

return(1);}

do

{inbal.read((unsigned char*) &cont, sizeof(struct stare));

if (!inbal.eof())

{cout << cont.nume << endl;

cout << "Numarul contului: " << cont.nr_cont << endl;

cout << "Continut cont: lei " << setprecision(2)<< setiosflags (ios::fixed)

<< cont.balanta << endl;}

} while (!inbal.eof());

inbal.close();

return (0);}

Utilizarea funcțiilor membre get () supraîncărcate pentru citirea datelor dintr-un fișier

Funcția get () este membră a clasei istream și poate fi utilizată în două variante supraîncărcate.

Formatele funcției get() sunt:

#include <iostream.h>

#include <fstream.h>

istream &get (char *buffer, int nr_car, char delim = “\n”);

int get(void);

Prima funcție get() supraîncărcată citește caractere în șirul de caractere dat de parametrul buffer. Funcția citește până când: fie s-a depășit numărul de caractere din parametrul nr_car, fie întâlnește caracterul specificat de parametrul delim. Funcția nu scoate din flux caracterul delimitator.

Cea dea doua funcție get () supraîncărcată returnează caracterul citit ca pe o valoare întreagă. Ea va returna constanta EOF dacă întâlnește marcajul de sfârșit de fișier.

Observație: Pentru scrierea unui caracter într-un fișier se poate folosi funcția put() învățată deja.

Utilizarea funcției membră getline () pentru citirea datelor dintr-un fișier linie cu linie

Formatele funcției get() sunt:

#include <iostream.h>

#include <fstream.h>

istream &getline (char *buffer, int nr_car, char delim = “\n”);

Funcția getline () la fel cu prima variantă de funcție get() supraîncărcată cu diferența că getline () nu preia din fluxul de intrare caracterul delimitator, în timp ce get () preia din fluxul de intrare și caracterul delimitator.

Determinarea poziției curente (pointerului de poziție) dintr-un flux de fișier

Pentru determinarea poziției curente dintr-un flux (pointerului de poziție) se poate folosi funcțiile membre tellg și tellp. Functia tellg() se folosește cu fluxuri de intrare, iar tellp() se folosește cu fluxuri de ieșire.

Formatul acestor două funcții este:

#include <iostream.h>

#include <fstream.h>

long intrare.tellg(void);

long iesire.tellp(void);

Setarea poziției pointerului de poziție dintr-un flux de fișier

Pentru a muta pointerul de poziție la o locație dorită în fișier se utilizează funcțiile seekg (pentru fluxuri de intrare) și seekp (pentru fluxuri de ieșire).

#include <iostream.h>

#include <fstream.h>

istream &seekg (streamoff deplasament [, seek_dir origine]);

ostream &seekp (streamoff deplasament [, seek_dir origine]);

unde:

– parametrul deplasament – precizează numărul de octeți de deplasament;

– parametrul origine – precizează locația (octetul) din fișier de la care trebuie să se aplice deplasamentul; dacă nu se precizează acest parametru deplasamentul se aplică de la începutul fișierului.

Parametrul origine poate lua următoarele valori

– beg – locația de început a fișierului;

– cur – locația curentă în fișier;

– end – locația de sfârșit a fișierului.

Următorul program (functia_seek_tel.cpp) ilustrează modul de folosire a funcțiilor tellp și seekp pentru a se face salturi în diferite locații dintr-un fișier în scopul înlocuirii unui caracter din fișier cu un altul specificat. De asemenea se afișează și poziția curentă în fișier. Programul folosește un flux de tip fstream.

#include <iostream.h>

#include <fstream.h>

main()

{char caracter;

long octet;

cout << "Introduceti locatia din fisier" << '\n' << "in care doriti inlocuirea ";

cin >> octet;

cout << "Introduceti caracterul de inlocuit "; cin >> caracter;

fstream iesire("test_date.txt", ios::in | ios::out | ios::binary);

if(!iesire)

{cout << "Fisierul nu poate fi deschis";

return(1);}

iesire.seekp(octet, ios::beg);

iesire.put(caracter);

cout << "Pozitia curenta este: " << iesire.tellp() << endl;

iesire.close();

return (0);}

Utilizarea operatorul de citire >> și operatorul de scriere << pentru citirea si scrierea datelor într-un fișier

Operatorul de citire >> se folosește în același mod cu citirea de la fluxul cin. Una dintre particularitățile citirii cu operatorul >> este faptul că ignoră caracterele albe. Prin urmare, dacă se dorește să se citească toate caracterele din fișier (inclusiv spațiul, Tab, Enter) acest mod de citire este inadecvat.

Exemplu de citire cu operatorul >> se prezintă în secvența de cod de mai jos:

ifstream intrare (“test.dat”);

int n;

intrare >> n;

Operatorul de scriere << se folosește în același mod cu scrierea de la fluxul cout.

Exemplu de scriere cu operatorul << se prezintă în secvența de cod de mai jos:

ofstream iesire (“test.dat”);

iesire << “Am scris in fisier”

Următorul program (cin_cout_fisier.cpp) citește de la tastatură un număr natural n impar și generează o matrice pătratică având n linii și n coloane după modelul din exemplul de mai jos (pentru n=5):

0 1 1 1 0

2 0 1 0 4

2 2 0 4 4

2 0 3 0 4

0 3 3 3 0

Matricea generată este stocată într-un fișier și apoi este citită din fișier și afișată la ecran.

Programul este structurat în patru funcții:

– funcția gen_matrice2() care generează matricea după modelul de mai sus;

– funcția scrie_fisier() care stochează matricea generată într-un fișier de tip text folosind operatorul de scriere << ;

– funcția citeste_fisier() care citește fișierul creat și afișează datele la ecran;

– funcția main() care apelează cele trei funcții de mai sus.

#include<iostream.h>

#include<fstream.h>

#include<conio.h>

char* scrie_fisier(int **, int, int);

void citeste_fisier(char*);

int ** gen_matrice2(int&);

void main(){

int nr, lin, col, i;

int ** a;

char* nume_fisier;

a=gen_matrice2(nr);

if(a){

lin=nr;

col=nr;

nume_fisier = scrie_fisier(a,lin, col);

if(nume_fisier != "\0"){

citeste_fisier(nume_fisier);}

delete *a;

delete a;}

getch();

}

int ** gen_matrice2(int &n){

int i, j;

int ** matr;

cout<<"Introduceti un numar natural impar:";

cin>>n;

matr = new int* [n];

if (matr==NULL)

{cout<<"Alocare esuata";

return matr;}

else

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

{matr[i]=new int[n];

if(matr[i]==NULL)

{cout<<"Alocare esuata";

return matr;}

}

//se completeaza diagonala principala cu 0

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

matr[i][i]=0;

//se completeaza diagonala secundara cu 0

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

matr[i][n-i-1]=0;

// se completeaza zona I

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

for(j=i+1;j<n-i-1;j++)

matr[i][j]=1;

//se completeaza zona II

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

for(i=j+1;i<n-j-1;i++)

matr[i][j]=2;

//se completeaza zona III

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

for(j=n-i;j<i;j++)

matr[i][j]=3;

//se completeaza zona IV

for(j=n/2+1;j<n;j++)

for(i=n-j;i<j;i++)

matr[i][j]=4;

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

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

cout<<matr[i][j]<<" ";

cout<<endl;}

return matr;

}

char * scrie_fisier(int **matr, int n , int m){

int i, j;

m=n;

char* nume_fis;

nume_fis = new char[20];

cout<< "Introduceti numele si extensia fisierului: ";

cin>>nume_fis;

fstream f(nume_fis, ios::out);

if(f==NULL)

{cout<<"Eroare la deschidere fisier "<<nume_fis<<endl;

return "\0";}

else

{f<<n<<" "<<m;

f<<endl;

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

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

f<<matr[i][j]<<" ";

f<<endl;}

f.close();

return nume_fis;

}

}

void citeste_fisier(char* nume_f){

int n, m, i, j;

int elem;

fstream f_matrice(nume_f,ios::in);

if(f_matrice==NULL)

cout<<"Eroare la deschidere fisier";

else

{f_matrice>>n>>m;

cout<<"Elementele matricei sunt: "<<endl;

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

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

{f_matrice>>elem;

cout<<elem<<" ";}

cout<<endl; }

f_matrice.close();

}

}

Lecția 13

Cuvinte importante:

– preprocesare: constante, macrocomenzi, directive preprocesor, crearea propriilor macroinstrucțiuni cu parametrii, apelul macroinstrucțiunilor cu parametrii, crearea propriilor fișiere antet;

– utilizarea unui proiect și a fișierelor antet;

– utilizarea bibliotecilor-obiect de funcții;

– utilitarul MAKE.

Preprocesare

Preprocesorul este un program lansat în execuție automat înainte de compilare. El execută toate directivele preprocesor incluse în program, efectuând substituții de texte.

Toate directivele preprocesor incluse în program încep cu caracterul # și sunt puse înaintea oricăror declarații de variabile sau funcții (deci, sunt plasate la începutul programului).

Directive preprocesor

Directiva #define permite definirea unei constante simbolice.

Sintaxa folosită este:

#define <identificator_constanta> valoare

unde:

– <identificator_constanta> – specifică numele constantei simbolice definite; numele constantei se scrie cu majuscule;

– <valoare> – specifică valoarea atribuită constantei; constantele pot conține valori de diferite tipuri diferite (int, float, char etc.).

Ca efect, preprocesorul va substitui în program orice apariție a identificatorului de constantă cu valoarea acesteia.

Notă: Se recomandă utilizarea constantelor simbolice atunci când se dorește să se asocieze o denumire mai sugestivă la o valoare. De asemenea, prin utilizarea constantelor simbolice programul devine mai ușor de modificat.

Exemple:

1. Următoarea directivă crează o constantă denumită DIM_LINIE și îi atribuie valoarea 128:

#define DIM_LINIE 128

Când preprocesorul de C/C++ va întâlni numele constantei DIM_LINIE în program, îl va înlocui cu valoarea ei.

2. Următoarele directive crează două constante denumite TRUE și FALSE și le atribuie valoarea 1 și respectiv 0:

#define TRUE 1

#define FALSE 0

În program aceste două constante simbolice vor fi înlocuite cu valorile lor.

3. Următorul program (macro_define.cpp) utilizează două constante predefinite cu macroinstrucțiunea #define.

#include <stdio.h>

#define TITLU "Acesta este un exemplu de directiva preprocesor"

#define CAPITOL "Macroinstructiunea #define"

void main(void)

{printf("%s\n", TITLU);

printf(CAPITOL);}

Înainte de a începe compilarea, preprocesorul va înlocui fiecare nume de constantă cu valoarea ei, ca mai jos:

void main(void)

{printf("%s\n", "Acesta este un exemplu de directiva preprocesor");

printf("Macroinstructiunea #define");}

Utilizarea constantei predefinite __FILE__

Constanta predefinită __FILE__ este utilizată pentru a memora numele fișierului sursă curent.

Următorul program (constanta_FILE.cpp) ilustrează modul de utilizare a constantei __FILE__:

#include <stdio.h>

void main()

{

printf("Programul %s este in versiunea BETA\n", __FILE__);}

După execuția programului pe ecran se afișează următoarele:

Programul constanta_FILE.cpp este in versiunea BETA

Utilizarea constantei predefinite __cplusplus

După cum s-a văzut până acum, unele dintre instrucțiunile și comenzile prezentate sunt valabile și în C și în C++, pe când altele se aplică numai pentru C++. Constanta __cplusplus este folosită pentru a testa modul de lucru al compilatorului care poate fi C++ sau C standard. Dacă se utilizează un compilator de C standard, constanta va fi nedefinită.

Observație: Directiva de preprocesare #ifdef folosită în programul următor testează dacă programul are definit sau nu un identificator sau o constantă simbolică cu numele respectiv.

Următorul program (testcpp1.cpp) ilustrează modul de utilizare a constantei __cplusplus pentru a determina modul de lucru curent al compilatorului:

#include<conio.h>

#ifdef __cplusplus

#include <iostream.h>

#else

#include <stdio.h>

#endif

void main()

{

#ifdef __cplusplus

cout << "Se utilizeaza C++";

#else

printf("Se utilizeaza C\n");

#endif

getch(); }

După execuția programului pe ecran se afișează:

Se utilizeaza C++

Notă: Cele mai multe compilatoare acceptă opțiuni în linia de comandă sau opțiuni de compilare în mediul de programare respectiv, care le indică să compileze folosind modul de lucru (limbajul) C++ sau modul de lucru (limbajul) C standard.

Utilizarea constantelor predefinite __DATE__ și __TIME__

Constanta predefinită __DATE__ este utilizată pentru a memora un șir de caractere de forma "Mmm dd yyyy" care conține data calendaristică la care s-a început compilarea.

Constanta predefinită __TIME__ este utilizată pentru a memora un șir de caractere de forma "hh:mm:ss" care conține ora la care s-a început compilarea.

Următorul program (const_TIME_DATE.cpp) ilustrează modul de utilizare a constantelor __DATE__ și __TIME__:

#include <stdio.h>

#include<conio.h>

void main()

{

printf("Ora la care s-a efectuat compilarea %s \n", __TIME__);

printf("Data la care s-a efectuat compilarea %s \n", __DATE__);

getch();

}

După execuția programului pe ecran se afișează următoarele:

Ora la care s-a efectuat compilarea 14:32:01

Data la care s-a efectuat compilarea Apr 19 2015

Utilizarea directivei de preprocesare #define pentru macroinstrucțiuni cu parametrii

Macroinstrucțiunile permit realizarea unor operații asemănătoare funcțiilor care lucrează cu parametrii. Există totuși câteva deosebiri.

Când preprocesorul întâlnește, într-un program, un apel la o macroinstrucțiune, aceasta este înlocuită cu instrucțiunile componente înainte să înceapă compilarea. Prin urmare, dacă programul utilizează de 10 ori o anumită macroinstrucțiune, vor fi inserate 10 copii diferite ale macroinstrucțiunii, printre celelalte instrucțiuni ale programului. Rezultă că dimensiunea programului executabil va crește.

Spre deosebire de macroinstrucțiuni, atunci când programul apelează o funcție, el conține numai o copie a codului funcției pe care îl execută, ceea ce îi reduce dimensiunea. Când utilizează o funcție, programul execută codul funcției. Totuși, funcțiile au dezavantajul prelucrărilor suplimentare pe stiva pe care le implică fiecare apel. De aceea execuția funcției durează ceva mai mult decât cea a unei macroinstrucțiuni echivalente.

De obicei, macroinstrucțiunile se folosesc pentru prelucrări care realizează un număr redus de operații.

Crearea propriilor macroinstrucțiuni cu parametrii

Crearea unei macroinstrucțiuni cu parametrii se realizează cu ajutorul directivei preprocesor #define.

Sintaxa definiției unei macroinstrucțiuni este:

#define <nume_macro>(<lista_nume_parametrii_formali) (<corp_macro>)

unde:

– <nume_macro> – specifică numele unei macroinstrucțiuni cu parametrii;

– <lista_nume_parametrii_formali> – specifică o listă de identificatori delimitați prin virgulă; fiecare identificator joacă rolul unui argument formal sau înlocuitor (placeholder) nu ca la funcțiile C/C++ unde parametrii formali primesc valorile parametrilor reali din apelul funcției;

– <corp_macro> – specifică setul de instrucțiuni de executat.

Notă:

1. Nu se plasează spațiu între numele macroinstrucțiunii și parametrii săi formali. Dacă se lasă spațiu atunci preprocesorul înlocuiește în program și secvența care reprezintă lista parametrilor formali și setul de instrucțiuni care reprezintă corpul macroinstrucțiunii.

2. În corpul macroinstrucțiunii se poate folosi caracterul punct și virgulă (;). Dar acest caracter trebuie folosit cu grijă pentru că preprocesorul va plasa acest caracter la fiecare apariție în cadrul programului.

De exemplu, să presupunem că s-a plasat caracterul punct și virgulă la sfârșitul definiției macroinstrucțiunii SUMA, așa cum se ilustrează mai jos:

#define SUMA(x, y) ((x) + (y));

Dacă această macroinstrucțiune se apelează într-o funcție cum ar fi printf(), atunci va rezulta eroare de compilare deoarece preprocesorul pune și punctul și virgula existentă după macroinstrucțiune.

3. Dacă definiția unei macroinstrucțiuni trebuie să continue pe linia următoare, se plasează un caracter backslash (\) la capătul liniei.

4. În corpul macroinstrucțiunii identificatorii parametrilor formali trebuie să fie incluși între paranteze pentru a accepta expresii.

5. După cum se observă din definiția macroinstrucțiunii, aceasta nu are specificat un tip de dată pentru rezultatul pe care îl returnează, ca la funcție.

Apelul unei macroinstrucțiuni cu parametrii în cadrul programului se face după sintaxa:

<nume_macro> (<lista_nume_parametrii_actuali>)

unde:

– <lista_nume_parametrii_actuali> – specifică o listă a parametrilor actuali (delimitați prin virgulă), care trebuie să coincidă ca număr și ordine cu fiecare dintre parametrii formali din definiția macroinstrucțiunii.

Notă: La apelul macroinstrucțiunii se poate lăsa spațiu între numele macroinstrucțiunii și lista parametrilor actuali

La întâlnirea unui apel de macroinstrucțiune, în cadrul programului, preprocesorul execută două seturi de înlocuiri (substituiri):

– mai întâi, numele macroinstrucțiunii și parametrii formali din paranteze, care sunt în definiția macroinstrucțiunii, sunt înlocuiți, în program, cu setul de instrucțiunii din corpul macroinstrucțiunii;

– apoi, orice nume de parametru formal întâlnit în corpul macroinstrucțiunii este înlocuit cu numele parametrului corespunzător care apare în lista parametrilor actuali.

Următoarea program (macro_suma.cpp) utilizează macroinstrucțiunea SUM pentru a aduna două valori:

#include <stdio.h>

#include<conio.h>

#define SUM(x, y) ((x) + (y))

void main()

{printf("Aduna 3 + 5 = %d\n", SUM(3, 5));

printf("Aduna 3.4 + 3.1 = %f\n", SUM(3.4, 3.1));

printf("Aduna -100 + 1000 = %d\n", SUM(-100, 1000));

getch();}

În cadrul definiției macroinstrucțiunii SUM, x și y reprezintă parametrii formali ai macroinstrucțiunii.

Atunci când preprocesorul întâlnește în program un apel al macroinstrucțiunii cum ar fi SUM(3, 5) se efectuează următoarele substituiri:

– mai întâi, SUM(x, y) cu (x) + (y) și

– apoi, x cu 3 și y cu 5.

În program, substituirile preprocesorului vor avea ca rezultat următorul cod:

printf("Aduna 3 + 5 = %d\n", ((3) + (5)));

printf("Aduna 3.4 + 3.1 = %f\n",((3.4) + (3.1)) );

printf("Aduna -100 + 1000 = %d\n", ((-100) +(1000)));

Următorul program (macro_minmax.cpp) folosește macroinstrucțiunile MIN și MAX pentru a calcula minimul și maximul dintre două valori:

#include <stdio.h>

#include<conio.h>

#define MIN(x, y) (((x) < (y)) ? (x): (y))

#define MAX(x, y) (((x) > (y)) ? (x): (y))

void main()

{printf("Maximul dintre 10.0 si 25.0 este %f\n", MAX(10.0, 25.0));

printf("Minimum dintre 3.4 si 3.1 este %f\n", MIN(3.4, 3.1));

getch();}

Substituirile preprocesorului vor avea ca rezultat următorul cod:

printf("Maximul dintre 10.0 si 25.0 este %f\n", (((10.0) > (25.0)) ? (10.0) : (25.0)));

printf("Minimum dintre 3.4 si 3.1 este %f\n", (((3.4) < (3.1)) ? (3.4) : (3.1)));

Următorul program (creare_macroinstr.cpp) crează macroinstrucțiunea cu numele alocare_dim pentru alocarea dinamică a unui bloc de memorie de n elemente de tip int sau float.

#include<conio.h>

#include<iostream.h>

#define alocare_dim(tip, n) (tip*)malloc(sizeof(tip)*n)

void main(){

int n, i;

int * intreg_p;

float * real_p;

cout<<"Introduceti nr. de elemente ale tabloului: ";

cin>>n;

intreg_p=alocare_dim(int,n);

if(intreg_p){

cout<< "Introduceti elementele tabloului de numere intregi:"<<endl;

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

cin>>intreg_p[i]; }

real_p=alocare_dim(float,n);

if(real_p){

cout<< "Introduceti elementele tabloului de numere reale:"<<endl;

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

cin>>real_p[i]; }

getch();

}

Directiva #include este utilizată pentru a include într-un program un fișier antet (header) specific unei biblioteci standard de funcții C/C++ sau creat de programator.

Un fișier antet (header) conține prototipuri de funcții, declarații de constante și definiții de macroinstrucțiuni. Fișierul antet are extensia .h. Fișierul antet este specific fiecărei bibliotecii standard de funcții C/C++ sau este creat de către programator.

Pentru a include într-un program un fișier antet se utilizează directiva preprocesor #include. Directiva #include are două formate de sintaxă:

a) #include <<nume_fisier_antet>.h>

b) #include “< nume_fisier_antet>.h”.

În varianta a) compilatorul de C/C++ va căuta respectivul fișier antet mai întâi în propriul său director de fișiere antet (specificat prin opțiuni sau prin variabile de mediu în funcție de compilator). De regulă, compilatorul de C/C++ plasează fișierele antet specifice bibliotecilor run-time în subdirectorul include a mediului de programare respectiv, dar nu este o regulă generală.

În varianta b) compilatorul va căuta fișierul antet mai întâi în interiorul directorului curent, iar după aceea, dacă nu este găsit, în directoarele standard. Forma cu „” permite și specificarea căii complete către fișierul inclus; în acest caz, nu se mai face căutarea și în directoarele standard.

Exemple:

1. #include <math.h> – include în program fișierul antet specific bibliotecii de funcții matematice a compilatorului de C/C++, existent în directoarele standard;

2. #include <iostream.h> – include în program fișierul antet specific bibliotecii cu funcții de intrare/ieșire a compilatorului C/C++, existent în directoarele standard;

3. #include <stdio.h> – include în program fișierul antet specific bibliotecii cu funcții de intrare/ieșire a compilatorului C/C++, existent în directoarele standard;

4. #include “C:\mariana\Limbajul C\functii_proiect.h” – include în program fișierul antet cu numele “functii_proiect.h” creat de programator în directorul “C:\mariana\Limbajul C”; dacă fișierul nu există nu mai este căutat în altă parte și se generează o eroare de compilare.

Compilare condiționată

Compilarea condiționată permite să se aleagă dintr-un text general părțile care se compilează împreună.

Compilarea condiționată se realizează folosind construcțiile: #ifdef , #ifndef, #ifdef-else, #if defined.

Acestea pot fi folosite pentru dezvoltarea programelor și pentru scrierea codului mai portabil de la o mașină la alta.

Directivele #ifdef și #ifndef

Pentru a testa dacă un anumit identificator (constanta predefinită de compilator, constante simbolice și macroinstrucțiuni definite de programator) a fost definit anterior în program, preprocesorul utilizează directiva #ifdef…#endif.

Sintaxa directivei #ifdef este:

#ifdef <nume_simbol>

<set_instructiuni>

#endif

Efectul directivei este: dacă simbolul respectiv a fost definit anterior în program, preprocesorul va efectua instrucțiunile din <set_instructiuni> până la întâlnirea instrucțiunii #endif.

Directiva #ifndef se folosește atunci când se dorește ca preprocesorul să efectueze anumite instrucțiuni, dacă programul nu a definit anterior un anumit simbol.

Sintaxa directivei este:

#ifndef <nume_simbol>

<set_instructiuni>

#endif

Următoarea instrucțiune utilizează directiva #ifndef pentru a indica preprocesorului să definească macroinstrucțiunea _majusc (care returnează codul ASCII al caracterului transformat în majuscule din parametrul macroinstrucțiunii) dacă nu a fost definită una similară în program ca în exemplul următor:

#include<conio.h>

#include<iostream.h>

#ifndef _majusc

#define _majusc(c)((((c) >= 'a') && ((c) <= 'z')) ? (c) – 'a' + 'A' : c)

#endif

void main()

{

char litera = 'i' ;

cout<<"Codul ASCII pentru: "<< litera<< " este: "<<_majusc(litera)<<endl ;

getch(); }

Directiva #ifdef-else se folosește atunci când se dorește ca preprocesorul să execute un set de instrucțiuni dacă condiția testată cu #ifdef este adevărată și alt set de instrucțiuni dacă condiția este falsă.

Sintaxa directivei este:

#ifdef <nume_simbol>

<set_instructiuni_1>

#else

<set_instructiuni_2>

#endif

Într-un slide anterior s-a prezentat modul de folosire al acestei directive preprocesor împreună cu constanta predifinită _cplusplus.

Directiva #if defined

Directiva #if defined este folosită tot pentru testarea definirii unui simbol.

Sintaxa directivei este:

#if defined(<nume_simbol>)

<set_instructiuni>

#endif

Această directivă are avantajul că dă posibilitatea de a combina testările, întrucât este compusă din directiva #if și operatorul defined.

Următoarea directivă #if defined testează dacă simbolul BIBLIO_M a fost definit și în același timp dacă simbolul FACT_M nu a fost definit anterior în program. Dacă această condiție este adevărată atunci se include în fișierul antet “fis_meu.h”:

#if defined(BIBLIO_M ) && !defined(FACT_M)

#include “fis_meu.h”

#endif

Notă: Se poate folosi #if defined pentru a construi condiții care utilizează operatorii logici ai limbajului C/C++ (inclusiv &&, | | și !).

Utilizarea unui proiect și a fișierelor antet proprii

O aplicație C/C++ nu este alcătuită în mod obligatoriu dintr-un singur fișier-sursă. Definițiile funcțiilor din care este alcătuit programul se pot afla în mai multe fișiere. Acest lucru permite elaborarea aplicațiilor în echipă, precum și reutilizarea anumitor funcții.

În final, după elaborarea tuturor fișierelor sursă, acestea pot fi asamblate, prin crearea unui proiect.

În mediul de programare C++ Builder XE5, crearea unui nou proiect se realizează cu ajutorul opțiunii New -> Other -> Console Application din meniul File al mediului de programare. Se deschide o fereastra în care se selectează numele fișierului sursă (.cpp) care conține funcția main () pe baza căruia se va crea proiectul într-un anume director.

După efectuarea pasului menționat, pe ecran apare fereastra de mai jos.

La pasul următor se redenumește proiectul (care are extensia .cbproj) și se salvează în directorul în care se dorește să existe acesta. S-a dat numele proiect_exemplul.cbproj.

Pentru adăugarea unui fișier-sursă (.c sau .cpp) se selectează opțiunea Add din meniul contextual (pop-up), care apare după ce se execută clic cu butonul drept pe nodul proiectului. S-a adăugat fișierul funcții_proiect.cpp.

Astfel, s-a creat proiectul cu numele proiect_exemplu1.cbproj cu două fișiere-sursă :

– functii_proiect.cpp, conține două funcții care efectuează următoarele: generează toate numerele prime mai mici decât n prin metoda ciurul lui Eratostene și calculează produsul elementelor de deasupra diagonalei unei matrice pătratice cu n linii și n coloane (funcții explicate deja în lecția 8).

– proiect_exemplu1.cpp, conține funcția main() în cadrul căreia vom apela cele două funcții. Tot în acest program se citește numărul până la care se dorește generarea numerelor aleatoare și, de asemenea, numărul de linii ale matricei pătratice (program prezentat deja în lecția 8).

Conținutul fișierului “functii_proiect.cpp” este:

#include <iostream.h>

void gen_nr_prim (int ciur [], int n)

{

int i, j;

for (i=2; i<n; i++) //initial toate numerele sunt in ciur, presupuse prime

ciur [i] = 1;

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

if (ciur[i]) //i este prim

for (j=2; j*i<n; j++) // se cern toti multiplii lui i

ciur[i*j] = 0;

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

if (ciur[i])

cout << "Numarul prim generat este " << i << endl;

double prod_sus_dig (int *a[], int n)

{

int i, j;

double p = 1;

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

for (j=i+1; j<n; j++)

p = p*a[i][j];

return p;

}

Conținutul fișierului “proiect_exemplu1.cpp” este:

#include <iostream.h>

void gen_nr_prim (int *, int );

double prod_sus_dig (int *[], int );

void main ()

{int *a, nr;

int **matr; // adresa de inceput a vectorului de pointeri la linii

int lin, col, aloc = 0;

int inum = 1;

int i, j;

double produs;

cout << "Introduceti numarul n pana la care se vor genera numere prime: ";

cin >> nr;

a = new int [nr];

if (a)

{gen_nr_prim (a, nr);

delete a;}

else

cout << “Alocare esuata";

cout << "Introduceti numarul de linii ale matricei patratice "; cin >> lin;

col = lin;

matr = new int *[lin]; // alocare zona de memorie pentru pointerii de linie

if (!matr)

{cout << “Alocare esuata\n";

aloc = 1;}

else

{for (i = 0; i < lin; i++)

{

matr[i] = new int[col]; // alocare zona de memorie pentru elementele fiecariei linii

if (!matr[i])

{cout << “Alocare esuata\n";

aloc = 1;}

else

aloc = 0;}

}

if (!aloc)

{

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

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

matr[i][j]=inum++;

produs = prod_sus_dig (matr, lin);

cout << "Produsul elementelor de deasupra diagonalei principale este " << produs;

delete *matr; //dealocarea zonei elementelor matricei

delete matr; //dealocarea zonei pointerilor de linie

}

}

Pentru ștergerea unui fișier-sursă (.c sau .cpp) se selectează opțiunea Remove From Project din meniul contextual (pop-up) care apare după ce se execută clic cu butonul drept pe nodul dorit a fi șters din proiect.

Pentru transformarea proiectului în fișier executabil, se selectează opțiunea Make din meniul contextual obținut prin execuția cu butonul drept al mouse-ului pe nodul numelui proiectului. Programul executabil va fi un fișier cu numele proiectului (proiect_exemplu1.exe) și extensia .exe.

Utilizarea unui fișier antet împreună cu programele C/C++ realizate

Dacă o aplicație informatică realizată în C/C++ conține mai multe funcții care se afla în fișiere-sursă distincte sau în biblioteci rune-time distincte, este util să se creeze un fișier antet propriu aplicației (cu extensia .h). În acest fișier se pot include declarațiile tuturor funcțiilor folosite de aplicația informatică, constante predefinite sau specifice aplicației și macroinstrucțiuni proprii aplicației.

De exemplu, să recreăm proiectul prezentat deja, astfel încât el să folosească și un fișier antet creat de programator.

Fișierul antet functii_proiect.h se crează tot cu ajutorul mediului de programare C++Builder. Acest fișier conține numai declarațiile funcțiilor prezentate deja, ca mai jos:

Functii_proiect.h

void gen_nr_prim (int *, int );

double prod_sus_dig (int *[], int );

Adăugarea la proiect a acestui fișier header se face la fel ca pentru orice fișier-sursă.

După adăugarea acestui fișier header, proiectul proiect-exemplu2.cbproj arată astfel:

Programul principal cu numele proiect_exemplu2.cpp face apel la fișierul antet cu numele functii_proiect.h, ca mai jos:

#include <iostream.h>

#include <conio.h>

#include "functii_proiect.h"

void main ()

{

//instructiuni program

}

Fișierul-sursă “functii_proiect.cpp” rămâne neschimbat.

Apoi proiectul proiect_exemplu2.cbproj se transformă în program executabil.

Utilizarea bibliotecilor-obiect de funcții

Crearea și utilizarea propriilor biblioteci-obiect de funcții reprezintă o altă modalitate de reutilizare a unor funcții pentru aplicații diverse.

Majoritatea compilatoarelor dispun de un program special care permite crearea de biblioteci de funcții. Mediul de programare C++Builder are înglobat programul Tlib care permite crearea de fișiere biblioteci-obiect de funcții (cu extensia .lib).

Crearea și gestionarea unui fișier bibliotecă-obiect de funcții

Majoritatea programelor care gestionează biblioteci-obiect de funcții permit următoarelor operații:

– crearea unei biblioteci;

– adăugarea unuia sau a mai multor fișiere-obiect la bibliotecă;

– înlocuirea unui fișier-obiect cu un altul;

– ștergerea unuia sau mai multor fișiere obiect din bibliotecă.

Mediul C++Builder realizează crearea unei biblioteci-obiect de funcții cu ajutorul unui proiect de tip Static Library.

Pentru crearea unui proiect de tip Static Library se procedează astfel:

1. Se alege opțiunea New->Other a meniului File; pe ecran apare fereastra de mai jos:

2. Se redenumește proiectul (care are exensia .cbproj) și se salvează în directorul în care se dorește să existe acesta. S-a dat numele functii_proiect.lib și s-a creat în directorul lib.

3. Pentru adăugarea fișierelor-sursă se selectează opțiunea Add din meniul contextual (pop-up), care apare după ce se execută clic cu butonul drept pe nodul proiectului creat. Astfel, s-au adăugat două fișiere: gen_nr_prim.cpp (care conține funcția de generare de numere prime) și prod_sus_dig.cpp (care conține funcția ce calculează produsul elementelor de deasupra diagonalei principale). Se observă că nu se mai folosește un singur fișier care conține ambele funcții ci două fișiere care apoi se reunesc într-un singur fișier cu extensia .lib.

4. Se selectează opțiunea Make din meniul contextual (pop-up), care apare după ce se execută clic cu butonul drept pe nodul proiectului functii_proiect.lib.

5. După parcurgerea celor patru pași, biblioteca-obiect (cu numele functii_proiect.lib) creată este adăugată la fișierul-proiect al aplicației (cu numele proiect_exemplu3.cbproj) prin procedeul deja descris (opțiunea Add pe nodul proiectului).

În final fereastra în care au fost create cele două proiecte arată astfel:

Un alt exemplu pentru crearea de proiecte este determinarea punctelor sa dintr-o matrice prezentata într-o lecție anterioară.

Proiectul are numele “prelucrari_matrice” și conține:

– fișierul “prelucrari_matrice.cpp” (conține funcția main()) care apelează trei funcții și afișează punctele sa.

– fișierul header “functii_prel_matr.h” care are conținutul de mai jos:

int ** citeste_matr(int&,int&);

void afis_matr(int *[],int, int);

char ** puncte_sa(int *[], int, int, int&);

S-a creat un alt proiect de tip Static Library cu numele “functii_proiect.lib”

S-a adăugat la acest proiect trei fișiere sursă:

citeste_matr.cpp folosit pentru alocarea dinamică a zonei de memorie a matricei și citirea maticei de la tastatură;

afis_matr.cpp folosit pentru afișarea matricei;

puncte_sa.cpp folosit pentru determinarea punctelor sa din matrice.

S-a folosit opțiunea Make pentru crearea bibliotecii-obiect (cu numele functii_proiect.lib).

Apoi această bibliotecă statică cu cele trei funcții a fost adăugată la proiectul cu numele “prelucrari_matrice.exe”

Codul-sursă al fișierului “prelucrari matrice.cpp” este:

#include <iostream.h>

#include<conio.h>

#include "functii_prel_matr.h"

void main(){

int **a; // adresa pointerilor la linii

char ** t_rezultat; // adresa pointerilor la sirurile de caractere

int lin, col;

int i, j;

int nr_puncte_sa;

a=citeste_matr(lin,col);

if(a){

afis_matr(a, lin, col);

t_rezultat=puncte_sa(a,lin, col, nr_puncte_sa);

if(t_rezultat){

cout<<"punctele sa sunt:"<<endl;

i=0;

while(i<nr_puncte_sa ){

cout<<t_rezultat[i]<<endl;

i++ ;}

cout<<"Numarul de puncte sa = "<<i;

delete *t_rezultat; // dealocarea zonei elementelor sir

delete t_rezultat; // dealocarea zonei pointerilor sirurilor

}

else

cout<<"alocare eronata";

delete *a; //dealocarea zonei elementelor linilor

delete a; } //dealocarea zonei pointerilor de linie

else

cout<<"Alocare esuata";

getch(); }

Codul sursă pentru cele trei fișiere cu funcțiile apelate de programul principal este:

#include<iostream.h>

int ** citeste_matr(int &n, int &m) {

int **matr;

int aloc;

int i, j;

cout << "Introduceti numarul de linii "; cin >> n;

cout << "Introduceti numarul de coloane: "; cin >> m;

matr = new int *[n];

if (!matr)

{cout<<"Alocare esuata"<<endl;

aloc=0;}

else

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

{matr[i] = new int[m];

if (!matr[i])

{cout<<"Alocare esuata"<<endl;;

aloc = 0;}

else

aloc = 1;} }

if (aloc)

{

cout<<"Introduceti elementele de pe linie"<<endl;

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

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

{cout<<"matr["<<i<<","<<j<<"]=" ;

cin>>matr[i][j];}

}

return matr; }

#include<iostream.h>

void afis_matr(int *matr[], int n, int m){

int i,j;

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

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

cout<<matr[i][j]<<" ";

cout<<endl;}

}

#include<iostream.h>

#include<string.h>

char ** puncte_sa(int *matr[], int n, int m, int &nr_sa ) {

int i, j, k;

int aloc=1;

int puncte_sa;

nr_sa=0;

char *p;

char sir[10];

char ** tablou_rezultat;

tablou_rezultat = new char*[50];

if (tablou_rezultat)

for (i = 0; i < 50; i++){

tablou_rezultat[i]=new char[50];

if (tablou_rezultat[i])

aloc=1;

else

aloc=0;

}

else

{cout<<"alocare esuata";

aloc = 0;}

if(aloc) {

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

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

puncte_sa = 1;

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

if(matr[i][k]<matr[i][j]) // comparare cu elementele de pe linia i

puncte_sa=0;

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

if(matr[k][j]>matr[i][j]) // comparare cu elementele de pe coloana j

puncte_sa=0;

if(puncte_sa){

//creaza elementele tabloului de siruri de caractere

strcpy(tablou_rezultat[nr_sa],"a[");

itoa(i,sir,10);

strcat(tablou_rezultat[nr_sa],sir);

strcat(tablou_rezultat[nr_sa]," ");

strcat(tablou_rezultat[nr_sa],",");

itoa(j,sir,10);

strcat(tablou_rezultat[nr_sa],sir);

strcat(tablou_rezultat[nr_sa],"]= ");

itoa(matr[i][j], sir, 10);

strcat(tablou_rezultat[nr_sa],sir);

nr_sa++; // numara punctele sa

}

}

}

return tablou_rezultat;

}

În final fereastra în care au fost create cele două proiecte arată astfel:

Construirea aplicațiilor cu ajutorul utilitarului MAKE

MAKE este un instrument de programare care ajută la construirea de aplicații complexe (ce conțin fișiere cod-sursă, fișiere cod-obiect, biblioteci, alte fișiere executabile etc., dependente unele de altele) după ce s-au făcut una sau mai multe modificări ale fișierelor componente ale aplicației.

Utilitarul MAKE.exe simplifică reconstruirea unui fișier executabil după ce s-au efectuat modificări în diferitele module componente ale aplicației informatice complexe (reface atât compilarea cât și link-editarea).

Utilitarul MAKE funcționează împreună cu un fișier text ASCII specific aplicației. Acest fișier (cu extensia .mak) specifică diferitele fișiere ale aplicației pe care compilatorul le va utiliza pentru a construi aplicația și listează pașii pe care compilatorul și link-editorul trebuie să îi parcurgă atunci când se modifică aplicația. De asemenea el verifică și dependențele între fișierele aplicației.

Fișierul de tip make este asemănător programelor, adică, el conține: condiții, reguli implicite și explicite, macrouri, directive.

Mediile de programare bazate pe Windows, cum ar fi C++Builder sau Microsoft Visual C++ construiesc și gestionează fișierul de tip make atunci când se crează fișierul proiect al aplicației și se execută opțiunea Make sau Make All.

Atunci când se execută comanda (opțiunea) MAKE cu fișierul make corespunzător, se va examina mai întâi linia dependentelor.

Dacă fișierul destinație nu există sau dacă fișierul destinație este mai vechi decât oricare alt fișier de care el este dependent (ceea ce înseamnă că s-a modificat unul dintre fișierele componente după ce s-a compilat ultima dată fișierul destinație), MAKE va executa comanda care urmează după liniile de descriere a dependentelor (de regulă comanda de recompilare a aplicației).

Observație: Comanda utilitarul MAKE este destul de utilizată în sistemul de operare Unix, mai ales la instalarea unor pachete de programe care trebuie recompilate pentru o anumită platformă hardware.

Lecția 14

Cuvinte importante:

– Structura de date “stivă”: caracteristici, operații ce pot fi efectuate asupra stivei;

– Structura de date “coadă”: caracteristici, operații ce pot fi efectuate asupra cozii;

– Utilizarea funcțiilor ca membrii ai tipului de date struct: declarare, apel;

– Recapitularea lecțiilor desfășurate pe parcursul semestrului.

Structura de date “stivă”

Stiva este o structură de date abstractă pentru care atât operația de inserare a unui element, cât și operația de extragere a unui element se realizează la un singur capăt, denumit vârful stivei.

Singurul element din stivă la care există acces direct este cel de la vârf.

Operațiile care pot fi executate asupra unei structuri de tip stivă sunt:

– crearea unei stive vide;

– inserarea unui element în stivă;

– extragerea unui element din stivă;

– accesarea elementului de la vârful stivei.

Stiva este definită ca o structură de date care funcționează după principiul LIFO (Last In First Out – ultimul intrat primul ieșit). Acest mod de funcționare face ca ultimul element inserat în stivă să fie primul extras.

Stiva este utilă în situații în care este necesară memorarea unor informații și regăsirea acestora într-o anumită ordine, descrisă de principiul LIFO.

Implementarea unei stive

Stiva este o structură de date, ce poate fi implementată în diferite moduri. Stiva se poate implementa static cu ajutorul unui vector în care se rețin elementele stivei.

Pentru ca un vector să funcționeze ca o stivă, singurele operații permise sunt operațiile caracteristice stivei. Vârful stivei indică poziția ultimului element introdus în stivă. Elementele sunt memorate în vector începând cu poziția 0.

Declararea unei stive ca vector cu un număr dat de elemente se poate face astfel:

int <max_elem>;

typedef <tip> <nume_tip_stiva>[<max_elem>];

<nume_tip_stiva> <variabila_stiva>;

<tip> <variabila_varf>;

unde:

– <max_elem> – specifică numărul maxim de elemente din stivă;

– <tip> – specifică tipul elementelor stivei;

– <nume_tip_stiva> – specifică numele tipului stivă;

– <variabila_stiva> – specifică numele variabilei de tip stivă cu numele <nume_tip_stiva>;

– <variabila_varf> – specifică numele variabilei care identifică vârful stivei.

De exemplu, pentru implementarea unei stive cu 10 elemente de tip int, se pot folosi declarațiile următoare:

typedef int stiva [10];

stiva S;

int varf;

Crearea unei stive vide

Pentru a crea o stivă vidă se inițializează vârful stivei cu -1, astfel:

<variabila_varf> = -1;

Inserarea unui element în stivă

Pentru a insera un element x într-o stivă trebuie să se verifice, în primul rând, dacă stiva nu este plină. Dacă stiva este plină, inserarea nu se poate face, altfel se va mări vârful stivei și se va plasa la vârf noul element. Deci, la inserare vârful stivei urcă.

De exemplu, dacă se dorește să se insereze elementul cu valoarea 5 în stiva S de mai jos, se obține:

Secvența de cod pentru inserarea unui element x în stiva S este prezentată în continuare:

if (varf == 9)

cout << “Stiva este plina\n”;

else

S[++varf] = x;

Extragerea unui element din stivă

Pentru a extrage un element dintr-o stivă trebuie să se verifice, în primul rând, dacă există elemente în stivă (deci dacă stiva nu este vidă). Dacă da, se reține elementul de la vârful stivei într-o variabilă, după care se micșorează cu o unitate vârful stivei. Deci, la extragere vârful stivei coboară.

De exemplu, dacă se dorește să se extragă un element din stiva S de mai jos, se obține:

Secvența de cod pentru extragerea unui element din stiva S este prezentată în continuare:

if (varf < 0)

cout << “Stiva este vida\n”;

else

x = S[varf–] ;

Accesarea elementului de la vârf

Prin modul restrictiv de funcționare, stiva permite numai accesarea elementului de la vârf. Dacă se dorește să se afle valoarea unui alt element al stivei, ar trebui “să se golească” stiva (deci să se extragă succesiv elemente) până la elementul dorit.

Accesarea elementului de la vârf presupune determinarea valorii acestuia, valoare care este reținută într-o variabilă.

Secvența de cod pentru accesarea elementului de la vârful stivei S este:

x = S[varf];

Notă: Pentru a executa operații cu stiva alocată static este suficient să se cunoască vârful stivei.

Următorul program (stiva.cpp) implementează o stivă denumitǎ cu 10 elemente de tip întreg și efectuează operațiile de inserare a unui element în stivă, de accesare a elementului din vârf și de extragere a unui element din stivă:

#include <iostream.h>

typedef int stiva [10];

void ins_elem (stiva, int &, int &);

int varf_elem (stiva, int&);

int ext_elem (stiva, int &);

main()

{stiva S;

int varf = -1, i, n, x;

int elem_ramas = 0;

cout << "Introduceti numarul de elemente de inserat in stiva " ;

cin >> n;

if (n >10)

{cout << "S-a depasit numarul maxim de elemente admise in stiva";

return 1;}

else

{cout << "Introduceti numerele ce se vor insera in stiva:\n";

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

{cin >> x;

ins_elem (S, x, varf);}}

if (n)

cout << "Elementul din varful stivei este " << varf_elem (S, varf) << endl;

cout << "Precizati numarul de elemente de extras din stiva:" ;

cin >> n;

if (n >10)

{cout << "S-a depasit numarul maxim de elemente admise in stiva";

return 1;}

else

if (varf < 0)

cout << "Stiva este vida\n" ;

else

{

elem_ramas = varf – n +1;

for (i = varf; i >= elem_ramas ; i–)

if (i >= 0)

cout << "Elementul [" << i << "] " << ext_elem (S, varf) << endl;

}

if (elem_ramas >=0)

cout << "Numarul de elemente ramase in stiva " << elem_ramas;

else

{elem_ramas = 0;

cout << "Numarul de elemente ramase in stiva " << elem_ramas;}

return 0;}

void ins_elem (stiva SS, int &nr, int &vf )

{

if (vf == 9)

cout << "Stiva este plina\n";

else

SS[++vf] = nr;

}

int varf_elem (stiva SS, int &vf)

{

return (SS [vf]);

}

int ext_elem (stiva SS, int &vf)

{

if (vf < 0)

{cout << "Stiva este vida\n";

return 0;}

else

return (SS[vf–]);

}

După execuția programului pe ecran se afișează:

Introduceti numarul de elemente de inserat in stiva 6

Introduceti numerele ce se vor insera in stiva:

23

45

67

89

1

12

Elementul din varful stivei este 12

Precizati numarul de elemente de extras din stiva:4

Elementul [5] 12

Elementul [4] 1

Elementul [3] 89

Elementul [2] 67

Numarul de elemente ramase in stiva 2

Structura de date “coadă”

Coada este o structură de date abstractă pentru care operația de inserare a unui element se realizează la un capăt, în timp ce operația de extragere a unui element se realizează la celălalt capăt.

Singurul element din coadă la care există acces direct este cel de la început.

Operațiile care pot fi executate asupra unei structuri de tip coadă sunt:

– crearea unei cozi vide;

– inserarea unui element în coadă;

– extragerea unui element din coadă;

– accesarea unui element din coadă.

Datorită faptului că întotdeauna este extras (“servit”) primul element din coadă, iar inserarea oricărui nou element se face la sfârșit (“la coadă”), coada este definită ca o structură de date care funcționează după principiul FIFO (First In First Out – primul intrat primul ieșit).

În informatică, cozile sunt utilizate frecvent. De exemplu, pentru a mări viteza de execuție, microprocesoarele Intel utilizează o coadă de instrucțiuni în care sunt memorate instrucțiunile ce urmează a fi executate.

Implementarea unei cozi

Coada este o structură de date, ce poate fi implementată în diferite moduri. Ca și în cazul stivei, coada poate fi implementată static, reținând elementele sale într-un vector. Elementele cozii sunt memorate în vector de la poziția primului element introdus în coadă (poziția de început) până la poziția ultimului element introdus în coadă (poziția de sfârșit). Rezultă că, numărul elementelor cozii este: poziția de sfârșit – poziția de început +1. Elementele sunt memorate în vector începând cu poziția 0.

Declararea unei cozi ca vector cu un număr dat de elemente se poate face astfel:

int <max_elem>;

typedef <tip> <nume_tip_coada>[<max_elem>];

<nume_tip_coada> <variabila_coada>;

<tip> <variabila_inceput>, <variabila_sfarsit>;

unde:

– <tip> – specifică tipul elementelor cozii;

– <nume_tip_coada> – specifică numele tipului cozii;

– <max_elem> – specifică numărul maxim de elemente din coadă;

– <variabila_coada> – specifică numele variabilei de tip coadă cu numele <nume_tip_coada>;

– <variabila_inceput> – specifică numele variabilei care identifică începutul cozii;

– <variabila_sfarsit> – specifică numele variabilei care identifică sfârșitul cozii.

De exemplu, pentru implementarea unei cozi cu 10 elemente de tip int, se pot folosi declarațiile următoare:

typedef int coada [10];

coada C;

int inceput, sfarsit;

ÎÎ

Crearea unei cozi vide

Pentru a crea o coadă vidă trebuie să se inițializeze variabilele care indică poziția de început și de sfârșit a cozii, astfel:

<variabila_inceput> = 0;

<variabila_sfarsit> = -1;

Inserarea unui element în coadă

Pentru a insera un element în coadă trebuie să se verifice, în primul rând, dacă coada nu este plină, adică valoarea variabilei care indică poziția de sfârșit a cozii nu depășește dimensiunea maximă a vectorului – 1. Dacă inserarea se poate face, se mărește cu o unitate valoarea variabilei care indică poziția sfârșitului cozii și se plasează la sfârșit noul element.

De exemplu, dacă se dorește să se insereze elementul cu valoarea 5 în coada C de mai jos, se obține:

Secvența de cod pentru inserarea unui element x în coada C este prezentată în continuare:

if (sfarsit == 9)

cout << “Coada s-a umplut””;

else

C[++sfarsit] = x;

Extragerea unui element din coadă

Pentru a extrage un element dintr-o coadă C trebuie să se verifice, în primul rând, dacă valoarea variabilei care indică poziția de început a cozii nu depășește valoarea variabilei care indică sfârșitul cozii (coada nu este vidă). Dacă coada nu este vidă, se reține elementul de la începutul cozii într-o variabilă, după care se mărește cu o unitate valoarea variabilei care indică poziția începutului cozii.

De exemplu, dacă se dorește să se extragă un element din coada C de mai jos, se obține:

Secvența de cod pentru extragerea unui element din coada C este prezentată în continuare:

if (inceput > sfarsit)

cout << “Coada este vida””;

else

x = C[inceput++];

Accesarea unui element din coadă

Singurul element al unei cozi care poate fi accesat direct este primul. Dacă se dorește să se afle valoarea unui alt element, va trebui să se extragă succesiv elemente până la cel dorit (adică să se “golească” coada).

Accesarea primului element are ca scop determinarea valorii acestuia, valoare care este reținută într-o variabilă.

Secvența de cod pentru accesarea primului element din coadă este:

x = C[inceput];

Observație: În acest mod de alocare statică a unei cozi se observă că, pe măsură ce se inserează și se extrag elemente, atât sfârșitul, cât și începutul cozii “se mișcă” înspre limita maximă a vectorului. Dezavantajul este că în vector rămân poziții neutilizate (de la 0 până la început – 1). De exemplu, ar putea să apară o situație în care coada conține un element, dar operația de inserare să nu mai fie posibilă pentru că:

inceput = sfarsit = lungime_maxima_coada – 1.

O soluție ar fi să se deplaseze toate elementele din coadă către începutul cozii, de fiecare dată când ștergem un element. Dar, această operație este extrem de ineficientă. Este mai bine să se păstreze toate elementele pe loc, deplasând numai începutul și sfârșitul cozii, reutilizând circular spațiul de memorie.

Implementarea unei cozi circulare

Pentru a evita situația în care nu se mai pot insera noi elemente, deși coada nu este plină, începutul cozii și sfârșitul cozii se pot întoarce către începutul tabloului. Rezultă o coadă circulară (denumită și tampon inelar).

Principiul întoarcerii către începutul tabloului

Sa presupunem o coadă cu dimensiunea maximă de 10 elemente. Să presupunem că la un moment dat s-a ajuns după ștergeri și inserări succesive la situația de mai jos:

Cum se poate insera un element nou care are, de exemplu, valoarea 50 ?

Noul element va fi inserat în celula cu indicele 0. Situația este ilustrată mai jos:

Această situație poartă numele de secvență întreruptă; elementele din coadă sunt dispuse în două secvențe disjuncte din tablou. Sfârșitul cozii este acum situat în fața începutului cozii, adică invers față de dispunerea inițială.

În această nouă dispunere a elementelor, presupunem că, se șterge un număr suficient de elemente. După extrageri succesive, și începutul cozii va ajunge la poziția 0 în tablou. În acest mod se ajunge la dispunerea inițială, în care începutul cozii este situat în fața sfârșitului cozii iar elementele sunt dispuse într-o secvență continuă.

Declararea unei cozi circulare ca vector cu un număr dat de elemente se poate face astfel:

int <max_elem>;

typedef <tip> <nume_tip_coada>[<max_elem>];

<nume_tip_coada> <variabila_coada>;

<tip> <variabila_inceput>, <variabila_sfarsit>, <nr_elemente>;

unde:

– <nr_elemente> – indică numărul curent de elemente din coadă.

De exemplu, pentru implementarea unei cozi circulare cu 10 elemente de tip int, se pot folosi declarațiile următoare:

typedef int coada [10];

coada C;

int inceput, sfarsit, nelem;

Inserarea unui element în coada circulară

Pentru a insera un element în coada circulară trebuie să se verifice, în primul rând, dacă coada nu este plină, adică numărul de elemente din coadă este egal cu dimensiunea maximă a cozii. Dacă inserarea se poate face, se testează dacă valoarea variabilei care indică poziția de sfârșit a cozii nu depășește dimensiunea maximă a vectorului. Dacă s-a ajuns la dimensiunea maximă a cozii, atunci poziția de sfârșit a cozii se inițializează la -1. Dacă nu, se mărește cu o unitate valoarea variabilei care indică poziția sfârșitului cozii și se plasează la sfârșit noul element. Apoi, se mărește cu o unitate numărul curent de elemente din coadă.

Secvența de cod pentru inserarea unui element x în coadă C circulară este prezentată în continuare:

if (nelem == 10)

cout << “Coada este plina\n”

else

{

if (sfarsit == 9)

sfarsit = -1;

C[++sfarsit] = x;

nelem++;

}

Extragerea unui element din coada circulară

Pentru a extrage un element dintr-o coadă circulară C trebuie să se verifice, în primul rând, dacă numărul de elemente din coadă este diferit de 0 (coada nu este vidă). Dacă da, se reține elementul din poziția care specifică începutul cozii într-o variabilă temporară, după care se mărește cu o unitate valoarea variabilei care indică poziția începutului cozii. Dacă în urma incrementării, poziția de început a cozii devine egală cu dimensiunea maximă a cozii, atunci poziția de început a cozii se inițializează la 0. Apoi, se micșorează cu o unitate numărul curent de elemente din coadă.

Secvența de cod pentru extragerea unui element x din coada C circulară este prezentată în continuare:

if (nelem == 0)

cout << “Coada este vida\n”;

else

{

x = C[inceput++];

if (inceput == 10)

inceput = 0;

nelem–;

}

Următorul program (coada.cpp) implementează o coadă circulară cu 10 elemente de tip întreg și efectuează operațiile de inserare a unui element în coadă, de accesare a primului element și de extragere a unui element din coadă:

#include <iostream.h>

typedef int coada [10];

void ins_elem (coada, int &, int &, int &);

int prim_elem (coada, int&);

int ext_elem (coada, int &, int &);

main()

{coada C;

int inceput = 0, sfarsit = -1, nelem = 0;

int i, n, x;

cout << "Introduceti numarul de elemente de inserat in coada " ;

cin >> n;

if (n >10)

{cout << "S-a depasit numarul maxim de elemente admise in coada";

return 1;}

else

{cout << "Introduceti numerele ce se vor insera in coada:\n";

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

{cin >> x;

ins_elem (C, x, sfarsit, nelem);}}

if (n)

cout << "Primul element din coada este " << prim_elem (C, inceput) << endl;

cout << "Precizati numarul de elemente de extras din coada:" ;

cin >> n;

if (n >10)

{cout << "S-a depasit numarul maxim de elemente admise in coada";

return 1;}

else

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

if (nelem)

cout << "Elementul [" << i << "] " << ext_elem (C, inceput, nelem) << endl;

cout << "Numarul de elemente ramase in coada " << (sfarsit – inceput + 1);

return 0;}

void ins_elem (coada CC, int &nr, int &sf, int &ne)

{if (ne == 10)

cout << "Coada este plina\n";

else

{if (sf == 9)

sf = -1;

CC[++sf] = nr;

ne++;

}}

int prim_elem (coada CC, int &inc)

{

return (CC [inc]);

}

int ext_elem (coada CC, int &inc, int &ne)

{

int nr;

if (ne == 0)

{cout << "Coada este vida\n";

return 0; }

else

{

nr = CC[inc++];

if (inc == 10)

inc = 0;

ne–;

return (nr);

}

}

După execuția programului pe ecran se afișează:

Introduceti numărul de elemente de inserat in coada 6

Introduceti numerele ce se vor insera in coada:

23

45

67

89

1

12

Primul element din coadă este 23

Precizati numarul de elemente de extras din coada:4

Elementul [0] 23

Elementul [1] 45

Elementul [2] 67

Elementul [3] 89

Numarul de elemente ramase in coada 2

Utilizarea funcțiilor ca membrii ai tipului de date struct

În limbajul C, se pot folosi pointeri la funcții ca membrii ai unei structuri de date struct.

De exemplu, formatul unui tip de date de tip struct cu numele stiva care conține pointeri la funcții arată astfel:

struct stiva

{int zona_s[10];

int vf;

void (*ptr_ins_elem) (int);

int (*ptr_varf_elem) ();

int (*ptr_ext_elem) ();};

Limbajul C++ aduce facilități în folosirea funcțiilor ca membrii ai tipului de date struct, în sensul că declarațiile funcțiilor se introduc pur și simplu în cadrul structurii respective. Corpul funcției poate fi plasat în interiorul structurii struct sau în afara acesteia.

Definirea unei funcții membru în interiorul tipului de date struct în limbajul C++

Când tipul de date struct conține un membru care este o funcție, se poate plasa corpul funcției în interiorul structurii.

Ca exemplu se poate lua structura abstractă stiva. Această structură de date are două caracteristici sau atribute:

– zona de memorie cu o anumită dimensiune (tabloul de memorie);

– vârful stivei prin care se inserează sau din care se extrag elemente din stivă.

De asemenea, principalele operații care se pot executa asupra stivei sunt:

– inserarea unui element în stivă;

– extragerea unui element din stivă;

– accesarea elementului din vârful stivei.

Atât atributele cât și operațiile ce pot fi executate asupra stivei pot fi reunite într-un tip de date de tip struct.

Tipul de date struct cu numele stiva, este ilustrat mai jos:

struct stiva

{ int zona_s[10];

int vf;

void ins_elem (int &nr)

{if (vf == 9)

cout << "Stiva este plina\n";

else

zona_s[++vf] = nr;};

int varf_elem ()

{return (zona_s [vf]);};

int ext_elem ()

{if (vf < 0)

{cout << "Stiva este vida\n";

return 0;}

else

return zona_s[vf–];};

};

în care:

variabilele zona_s[10] și vf reprezintă zona de memorie alocată stivei și, respectiv, vârful stivei;

ins_elem – este o funcție care realizează operația de inserare a unui element în stivă;

vf_elem – este o funcție care realizează accesarea elementului din vârful stivei;

ext_elem – este o funcție care realizează extragerea unui element din stivă.

Definirea unei funcții membru în exteriorul tipului de date struct în limbajul C++

Când tipul de date struct conține un membru care este o funcție, se poate plasa corpul funcției în exteriorul structurii.

De exemplu, tipul de date struct cu numele stiva, care conține funcții membre definite în afara structurii este ilustrat mai jos:

struct stiva

{int zona_s[10];

int vf;

void ins_elem (int &nr);

int varf_elem ();

int ext_elem ();

};

void stiva::ins_elem (int &nr)

{if (vf == 9)

cout << "Stiva este plina\n";

else

zona_s[++vf] = nr;}

int stiva::varf_elem ()

{return (zona_s [vf]);}

int stiva::ext_elem ()

{if (vf < 0)

{cout << "Stiva este vida\n";

return 0;}

else

return zona_s[vf–];}

Notă:

1. Din exemplul prezentat se observă că, pentru a identifica funcția ca membru al structurii, atunci când ea este definită în afara structurii, este necesar ca numele ei să fie precedat de numele structurii, urmat de două puncte duble (::).

2. O funcție membră a tipului de date struct poate să conțină, în corpul său, instrucțiunii care fac referire la variabilele membre ale tipului de dată struct.

De exemplu, funcția varf_elem, din exemplul prezentat, folosește atât membrul cu numele zona_s cât și membrul cu numele vf:

int stiva::varf_elem ()

{return (zona_s [vf]);}

Transmiterea parametrilor către o funcție membră

Atunci când se plasează o funcție ca un membru la tipul de dată struct, se poate trata funcția exact la fel ca orice funcție C++. Deci, se pot transmite parametrii către funcție și se pot declara variabile locale în cadrul funcției.

De exemplu, funcția ins_elem, din exemplul prezentat, are un parametru cu numele nr în care se vor memora elementele stivei transmise de programul principal.

Declararea unei variabile de tip struct care are funcții membre se face la fel ca la o variabilă de tip struct obișnuită.

De exemplu, variabila S este declarată ca o variabilă de tipul struct cu numele stiva, ca mai jos:

stiva S;

Apelul unei funcții care este membră a unui tip de date struct

Pentru a apela o funcție în cadrul programului, este necesar ca numele ei să fie precedat de numele variabilei de tip structură, urmat de operatorul punct (.).

De exemplu, apelul funcției ins_elem în cadrul programului este de forma:

S.ins_elem (x);

Exemplul următor (stiva_struct1.cpp) ilustrează modul de folosire a funcțiilor ca membrii ai tipului de date struct, pentru crearea unei stive și efectuarea operațiilor specifice stivei.

#include <iostream.h>

struct stiva

{ int zona_s[10];

int vf;

void ins_elem (int &nr);

int varf_elem ();

int ext_elem ();};

void stiva::ins_elem (int &nr)

{if (vf == 9)

cout << "Stiva este plina\n";

else

zona_s[++vf] = nr;}

int stiva::varf_elem ()

{return (zona_s [vf]);}

int stiva::ext_elem ()

{if (vf < 0)

{cout << "Stiva este vida\n"; return 0;}

else

return zona_s[vf–];}

main()

{

int i, n, x;

int elem_ramas = 0;

stiva S;

S.vf = -1;

cout << "Introduceti numarul de elemente de inserat in stiva " ;

cin >> n;

if (n >10)

{cout << "S-a depasit numarul maxim de elemente admise in stiva";

return 1;}

else

{

cout << "Introduceti numerele ce se vor insera in stiva:\n";

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

{cin >> x;

S.ins_elem (x);}

}

if (n)

cout << "Elementul din varful stivei este " << S.varf_elem () << endl;

cout << "Precizati numarul de elemente de extras din stiva:" ;

cin >> n;

if (n >10)

{cout << "S-a depasit numarul maxim de elemente admise in stiva";

return 1;}

else

if (S.vf < 0)

cout << "Stiva este vida\n" ;

else

{

elem_ramas = S.vf – n +1;

for (i = S.vf; i >= elem_ramas ; i–)

if (i >= 0)

cout << "Elementul [" << i << "] " << S.ext_elem () << endl;

}

if (elem_ramas >=0)

cout << "Numarul de elemente ramase in stiva " << elem_ramas;

else

{elem_ramas = 0;

cout << "Numarul de elemente ramase in stiva " << elem_ramas;}

return 0;

}

Problemă de rezolvat:

Să se refacă programul ”coada.cpp” (prezentat în această lecție) care implementează o coadă circulară cu 10 elemente de tip întreg și efectuează operațiile de inserare a unui element în coadă, de accesare a primului element și de extragere a unui element din coadă, astfel încât acesta să folosească funcțiile ca membrii ai tipului de date struct pentru crearea unei cozi și efectuarea operațiilor specifice cozii.

Recapitularea lecțiilor desfășurate pe parcursul semestrului

Similar Posts

  • Imagistica Prin Rezonanta Magnetica Rmndocx

    === Imagistica prin rezonanta magnetica – RMN === Imagistica prin rezonanță magnetică-RMN Facultatea de medicină și farmacie “Victor Babeș” Grupa 2 INTRODUCERE RMN-ul este una dintre cele mai moderne tehnici imagistice folosite în prezent în spitale. Este o tehnică neinvazivă și neiradiantă, cu ajutorul căreia se pot obține imagini de rezoluție și claritate superioare ale…

  • Centrul Cultural

    CUPRINS Plan de idei PARTEA I INTRODUCERE A. Explicarea alegerii temei de disertație și relaționarea ei cu diploma și studiul din prediplomă. Evidențierea importanței subiectului disertației pentru diplomă. B. Argument: Care este relația oraș – centru cultural în ziua de astăzi? Poate un centru cultural să canalizeze tot necesarul de spații culturale și de loisir…

  • Inflatia Perspectivele Acesteia

    === 593f6f967caf160549ff9a39f204b1a69fe42a4f_347989_1 === Facultatea de Economie și Administrarea Afacerilor Specializare: Contabilitate și informatică de gestiune Inflația – perspective Cuprins INTRODUCERE CAP. 1 INFLAȚIA ÎN ROMÂNIA – EVOLUȚII ȘI FACTORII DETERMINANȚI Puncte de vedere cu privire la natura inflației Interacțiunea diferitelor tipuri de inflație  1.2. Prognoze privind activitatea economică a României în perioada 2012 – 2016…

  • Dezvoltarea Competentelor Asociate Unui Lider Inteligent Emotional

    === 87f8981791775c6f0477231fbffb9caebbcdd1f1_143155_1 === ϹUΡRІΝЅ Arɡumеnt СAΡІТΟLUL 1 ocοϲ RΟLUL ІΝТΕLІGΕΝȚΕІ ΕМΟȚІΟΝALΕ ÎΝ LΕADΕRЅНІΡ  oc οϲ 1.1. Іntеlіɡеnța еmоțіоnală oc- ϲоnϲерt οϲϲоmрlех 1.1oc.1 οϲІntеlіɡеnța еmоțіоnală: dеfіnіțіе, abоrdărі aϲtualе oc οϲ1.1.2 Ѕuроrtul nеurоlоɡіϲ ocal іntеlіɡеnțеі οϲеmоțіоnalе 1.2. Lеadеrһірoc-ul οϲtranѕfоrmațіоnal șі іntеlіɡеnța еmоțіоnală: іntеrfеrеnțе oc οϲ ϹAΡІΤОLUL 2 ΕVALUARΕ ȘІ DΕΖVОLΤARΕ ocA ІΝΤΕLІGΕΝȚΕІ ΕМОȚІОΝALΕ οϲ (ІΕ): ІΝЅΤRUМΕΝΤΕ ΕFІϹAϹΕ oc 2….

  • Apararea Si Securitatea Nationala In Paradigma Integrarii Romaniei In Structurile Europene Si Euroatlantice

    CUPRINS === APARAR~1 === INTRODUCERE În concordanță cu poziția geostrategică a României în spațiul sud-est european – țară de frontieră a NATO și a Uniunii Europene – politica de apărare națională are ca obiectiv apărarea și promovarea intereselor vitale ale României, precum și participarea activă a țării noastre la asigurarea securității zonelor de interes NATO…

  • Mitul Salvatorului

    Cοmunіcɑrе șі Rеlɑțіі рublіcе Рοrtrеtіzɑrеɑ реrsοnɑјеlοr mɑsculіnе dіn fіlmеlе Мɑrvеl. Міtul sɑlvɑtοruluі Cuрrіns: Ιntrοducеrе Мɑrvеl Cοmіcs Τеmɑtіcɑ ɑbοrdɑtă în fіlmеlе Мɑrvеl Temă și mit din perspectiva lui Mircea Eliade Реrsοnɑјеlе mɑsculіnе dіn fіlmеlе Мɑrvеl Міtul sɑlvɑtοruluі Suреrmɑn Εrοіі dіn Аtlɑntіdɑ Suреrmɑn vs. Εrοіі Аtlɑntіdеі Cοncluzіі Віblіοgrɑfіе Ιntrοducеrе În lucrarea sa, Formarea legendelor, Arnold van Gennep…