Sunteți pe pagina 1din 14

Laborator de Structuri de Date si Algoritmi – Lucrarea nr.

Recapitularea unor notiuni C/C++

1. Recapitularea unor notiuni C/C++


1.1. Structuri
1.2. Pointeri
1.3. Referinte
1.4. Scriere / citire cu ajutorul cin şi cout
1.5. Operatorii new şi delete
2. Aplicatii

1. Recapitularea unor noţiuni C/C++.

1.1. Structuri
1.1.1. Definire

O structură este un tip de date nou. Atunci când definim o structură, trebuie să specificăm
numele structurii şi câmpurile ei:

struct student {
char* nume;
int nota;
};

Am introdus tipul struct student. Pentru a evita repetarea lui struct putem să
introducem un pseudonim pentru tipul struct student si anume Student astfel:

typedef struct student Student;

Cele două declaraţii de mai sus pot fi comprimate in una singură:

typedef struct {
char* nume;
int nota;
} Student ;

In C++, tipul definit de declaraţia:

struct Student {
char* nume;
int nota;
};

1
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

poate fi denumit struct Student sau, doar simplu, Student. In continuare ne vom
folosi de această facilitate C++ care măreşte lizibilitatea programelor.

1.1.2. Obiecte de tip structură

Am definit structura Student având CÂMPURILE "nume" (adresa unui sir de caractere)
si "nota" de tip int.

Student
┌─────────────────┐
nume nota
╔═════════╤══════╗
║ ■ │ 10 ║
╚════╪════╧══════╝
│ ╔═══╤═══╤═══╤═══╤════╗
└─────────────> ║'R'│'a'│'d'│'u'│'\0'║
╚═══╧═══╧═══╧═══╧════╝

Odata definit tipul, acesta poate fi folosit pentru a declara variabile:

Student s1, s2; // s1 si s2 sint doua variabile de tip Student


Student grupa[5]; // un tablou de variable Student
Student* ps; // o variabila pointer la Student
Student* idx[5]; // o variabila tablou de pointeri la Student

1.1.3. Operatorii . si ->

Folosirea câmpurilor unei structuri se face NUMAI CU REFERIRE LA UN OBIECT de


tipul respectiv. Obiectul este referit printr-o valoare stânga care semnifică obiectul
structură sau adresa obiectului structură.

Operatorul . cere in stânga sa o valoare stânga de tip structura iar in dreapta, numele
câmpului selectat, rezultatul fiind o valoare-stânga care se refera la câmpul selectat. De
exemplu, din declaraţiile de mai sus, câmpurile variabilei grupa[3] vor fi denumite:
grupa[3].nume si grupa[3].nota

grupa[3]
╔════════════════╤═══════════════╗
║ grupa[3].nume │ grupa[3].nota ║
╚════════════════╧═══════════════╝

grupa[3] . nota
└──┬───┘ └─┬┘
┌────┘ │
variabila_structura . câmp

2
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

Operatorul -> cere in stânga sa o expresie de tip "pointer la structura" iar in dreapta
numele câmpului selectat, rezultatul fiind o valoare-stânga care se refera la câmpul
selectat:

ps
╔═══════╗
║ ■ ║
╚═══╪═══╝ *ps
│ ╔════════════════╤═══════════════╗
└───────>║ ps->nume │ ps->nota ║
╚════════════════╧═══════════════╝

ps->nota
┌─────┘ │
pointer_la_structura -> câmp

1.2. Pointeri
1.2.1. Definire

Pentru un tip de date T o variabila "pointer la T" se defineşte astfel:

T* ptrT; // ptrT este un pointer la T

O variabila pointer la T poate retine adresa unui obiect de tip T.

Exemple:

int* pi; // pi este un pointer la int


char* tab; // tab este un pointer la char
Nod *nou, *cap; // nou si cap sunt pointeri la tipul Nod

1.2.2. Iniţializare

Prima operaţie care se face cu un pointer este iniţializarea. Un "pointer la T" poate fi
iniţializat cu:

a) adresa unui T (care exista)

pi = &i;

pi i
╔═══════╗ ╔═══════╗
║ ■───╫──────> ║ ║
╚═══════╝ ╚═══════╝
*pi

b) valoarea 0 (sau NULL) care semnifica adresa invalida


3
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

cap = 0; cap
╔═══════╗
║ 0 ║
╚═══════╝
c) valoarea altui "pointer la T". De exemplu: daca p si q sunt de tip T* si p conţine
adresa unei variabile de tip T (a fost iniţializat in prealabil), atribuirea q = p va face ca
ambii pointeri sa indice aceeaşi variabilă.

p
╔═══════╗ ╔═════════╗
║ ■───╫─────────>║ ║
╚═══════╝ ┌───>╚═════════╝
q │
╔═══════╗ │
║ ■───╫─────┘
╚═══════╝

d) adresa unui spaţiu de memorie alocat in zona de alocare dinamica. Spaţiul alocat poate
sa conţină un singur obiect de tip T, acesta se exprima:

in C: p = (T*) malloc( sizeof(T) );


in C++: p = new T;

p *p
╔═══════╗ ╔═══════╗
║ ■───╫──────> ║ ║
╚═══════╝ ╚═══════╝

sau poate să conţină mai multe (n) obiecte de tip T:

in C: p = (T*) malloc( n*sizeof(T) );


in C++: p = new T[n];

p *p ≡ p[0] p[1] p[2] p[3] p[4]


╔═══════╗ ╔══════╤══════╤══════╤══════╤══════╗
║ ■────╫──> ║ │ │ │ │ ║
╚═══════╝ ╚══════╧══════╧══════╧══════╧══════╝

Exprimările din C++ sunt in mod evident mult mai simple si le vom folosi pe acestea in
continuare.

Iată un exemplu:

typedef Student* PStudent;


PStudent* ptps; // pointer la tablou de pointeri la studenţi
ptps = new PStudent[nr];
1.2.3. Dereferenţierea

4
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

Este operaţia prin care având un "pointer la T" (care conţine adresa unui T) obţinem o
valoare stânga care se refera la obiectul pointat (vezi operatorul *).

Pentru a obţine obiectul pointat folosim operatorul * astfel:

*pi = 5; // obiectul pointat ia valoarea 5

!!! Atentie !!!


Aceasta operaţie poate fi aplicata numai pointerilor care conţin intr-adevăr adresa unui
obiect. De exemplu, nu puteţi face dereferentierea unui pointer nul (cu valoare 0) sau a
unui pointer neiniţializat. Este valabil si pentru operatorul -> care conţine si el o
dereferentiere care se observa in scrierea echivalenta: ps->nota este echivalent cu
(*ps).nota

1.2.4. Pointeri si tablouri

Numele unui "tablou de T" este convertit automat la tipul "pointer la T", deci poate fi
folosit pentru a iniţializa un "pointer la T". Valoarea acestui pointer este adresa primului
element al tabloului:

T tab[10];
T* ptrT = tab; // ptrT conţine adresa primului element

Un "pointer la T" este deseori folosit pentru a se referi pe rând la elementele unui tablou.

Următoarele operaţii semnifica:

ptrT++ // pointează la următorul element din tablou


// creează o valoare stânga!
ptrT-- // pointează la elementul precedent din tablou
// creează o valoare stânga!

1.2.5. Eliberarea spaţiului alocat dinamic

Daca un pointer a fost iniţializat cu adresa unui spaţiu din zona de alocare dinamică,
atunci când nu mai avem nevoie de spaţiul respectiv (adică nu mai avem nevoie de
obiectul din spaţiul respectiv) vom elibera spaţiul. El va putea fi astfel utilizat pentru
alocări ulterioare.

Daca p este un pointer care a fost iniţializat printr-o alocare de memorie, eliberarea
memoriei alocate se exprimă:

in C: free(p);
in C++: delete p;

5
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

!!!Atentie!!!
Nici free() nici delete nu modifică valoarea pointerului p, dar obiectul a cărui adresa
este conţinută de p nu trebuie sa fie referit după eliberare.

1.2.6. Observaţie
In laboratoarele următoare, vor exista cazuri in care anumiţi pointeri nu sunt variabile
simple, ci vor fi componente ale unor structuri de date complexe. Toate regulile de mai
sus se păstrează.

1.3. Referinţe
O referinta este un alt nume pentru o variabila, un pseudonim. In esenta, o referinta este
un pointer implicit.

Referintele sunt declarate folosind &.

1.3.1. Utilizare

Referinte independente

La crearea unei referinte independente, in fapt, se creeaza un alt nume pentru o variabila.
Toate referintele independente trebuie initializate la declarare. Ulterior nu se poate
modifica ce obiect refera o referinta.

int a;
int &ref = a; //referinta independenta (ref este un alt nume pt. a)

a = 10;
cout << a << " " << ref << endl;

ref = 100;
cout << a << " " << ref << endl;

int b = 19;
ref = b; //face ca a si ref sa ia valoarea lui b
//(ref nu devine o referinta pt. b)
cout << a << " " << ref << endl;

ref--; //decrementeaza pe a
cout << a << " " << ref << endl;

Secventa de cod de mai sus are ca efect afisarea urmatoarelor valori:


10 10
100 100
19 19
18 18

6
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

Pasarea argumentelor unei functii

In C++ exista doua modalitati de transmitere a parametrilor la functii: prin valoare


(copiere) sau prin referinta. Prin valoare inseamna ca o copie a obiectului original se
face pe stiva apelatului, iar modifcarea acestuia in functia apelata nu afecteaza obiectul
din apelant. Pe de alta parte, in cazul transmiterii prin referinta (care contine adresa unui
obiect, dar se comporta ca un obiect) modificarile in apelat inseamna modificari directe
asupra obiectului din apelant. Se poate deosebi intre cele doua modalitati astfel: daca
parametrul functiei e precedat de & atunci e transmitere prin referinta; altfel, e prin
valoare (copiere).

Transmiterea prin referinţă este utilizată atunci când dorim ca la revenirea din funcție
variabila transmisă să reţină modificarile aduse in functie.
În acest caz, parametrii actuali trebuie să fie referinţe la variabile. La transmitere,
subprogramul reţine în stivă adresa variabilei. La compilare, orice referinţa la o variabilă
este tradusă în subprogram ca adresare indirectă. Acesta este motivul pentru care în
subprogram putem adresa variabila normal (nu indirect), cu toate că, pentru o variabilă
transmisă, se reţine adresa ei.

Exemple:

Inversarea semnului unui intreg

Transmiterea prin valoare

void neg(int i) { void neg(int *i) {


i = -i; *i = -*i;
} }

int main() { int main() {


int x = 10; int x = 10;
neg(x); neg(&x);
cout << "Dupa inversare: " cout << "Dupa inversare: "
<< x << endl; << x << endl;
return 0; return 0;
} }

Dupa inversare: 10 Dupa inversare: -10

Functia a lucrat cu variabila locala i care Functia a lucrat cu variabila locala i care
reprezinta o copie a variabilei x reprezinta adresa lui x

7
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

Transmiterea prin referinta


void neg(int &i) {
i = -i;
}

int main() {
int x = 10;
neg(x);
cout << "Dupa inversare: " << x << endl;
return 0;
}
Dupa inversare: -10

Functia a lucrat cu variabila locala i care reprezinta o referinta la x (un alt nume pentru
x)

Interschimbarea valorilor a două variabile

Transmiterea prin valoare


void swap(int a, int b) { void swap(int *a, int *b) {
int aux = a; int aux = *a;
a = b; *a = *b;
b = aux; *b = aux;
} }

int main() { int main() {


int x = 5; int x = 5;
int y = 10; int y = 10;
swap(x, y); swap(&x, &y);
cout << "Dupa swap: x=" << x cout << "Dupa swap: x=" << x
<< " y=" << y << endl; << " y=" << y << endl;
return 0; return 0;
} }

Dupa swap: x=5 y=10 Dupa swap: x=10 y=5

Functia a lucrat cu variabilele locale a si b Functia a lucrat cu variabilele locale a si b


care reprezinta copii ale variabilelor x si care reprezinta adresele variabilelor x si y,
y, respectiv. respectiv.

8
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

Transmiterea prin referinta


void swap(int &a, int &b) {
int aux = a;
a = b;
b = aux;
}

int main() {
int x = 5;
int y = 10;
swap(x, y);
cout << "Dupa swap: x=" << x << " y=" << y << endl;
return 0;
}
Dupa swap: x=10 y=5

Functia a lucrat cu variabilele locale a si b care reprezinta referinte


la x si y, respectiv (alte nume pentru x si y)

Variabilele a si b sunt variabile locale functiei swap care sunt initializate la momentul
apelului acestei functii ca referinte la variabilele cu care s-a realizat apelul.

Interschimbarea a doi pointeri


Când vrem ca o funcţie, atunci când este apelata, sa modifice valoarea unei variabile din
funcţia apelanta, trebuie sa trimitem ca argument un pointer la acea variabilă. De
exemplu, o funcţie care interschimbă valoarea a doi "pointeri la student" va trebui să
primească ca parametri doi "pointeri la pointeri la student":

void Schimba(Student** unu, Student** doi)


{
Student* trei;
trei = *unu;
*unu = *doi;
*doi = trei;
}

Daca argumentele au tipuri mai complicate, atunci sintaxa din interiorul funcţiei devine
greoaie. Pentru a rezolva aceasta problema putem trimite ca argumente REFERINTE. Un
argument referinţa trebuie interpretat ca fiind un PSEUDONIM pentru argumentul pasat.
Orice modificare a referinţei se va face asupra argumentului pasat:

void Schimba(Student*& unu, Student*& doi)


{
Student* trei;
trei = unu;
unu = doi;
doi = trei;
}
In acest fel, sintaxa din interiorul funcţiei a devenit mai simplă.

9
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

1.3.2. Restrictii pentru referinte


- Nu se poate crea o referinta la o alta referinta (nu se poate obtine adresa unei
referinte)
- Nu se pot crea tablouri de referinte
- Nu se poate crea un pointer la o referinta
- O variabila de tip referinta trebuie initializata la declarare, cu exceptia cazului in
care reprezinta parametrul unei functii sau o valoare de retur
- Referintele null sunt interzise

1.4. Scriere / citire cu ajutorul cin şi cout

În C++ s-a elaborat o modalitate mai simplă de scriere/citire la consolă


comparativ cu funcţiile scanf/printf din C. La începutul execuţiei fiecărui program sunt
instanţiate automat 2 variabile globale speciale - cin şi cout. Ele sunt folosite pentru
citire, respectiv scriere la consolă.

Pentru a citi o variabilă de la consolă, se foloseşte următoarea sintaxă:

int a;
cin >> a;

Operatorul >> are un rol special pentru variabila cin. Expresia

cin >> a;

semnifică faptul că de la consolă este citită o valoare şi depozitată în variabila a. Tipul


variabilei din dreapta poate fi de orice tip simplu – int, char, float, double sau şir de
caractere – char*. Pentru fiecare tip citirea se va face în mod corect.
Pentru a scrie o variabilă la consolă, folosim sintaxa:

char str[] = "abc";


cout << str;

În mod similar, operatorul << are o semnificaţie specială pentru variabila cout.
Expresia:

cout << str;

semnifică faptul că variabila str este scrisă la consola. Variabilele scrise pot fi de
aceleaşi tipuri ca şi cele citite cu cin.
Observaţi că în exemplul de mai sus a fost scrisă la consolă o variabilă de tip
char[], tip care nu a fost menţionat în lista de tipuri suportate pentru operandul dreapta.
Totuşi, utilizarea lui a fost posibilă într-o expresie cu cout. De ce?
Variabilele cin şi cout sunt definite în header-ul <iostream>. Pentru a le putea
folosi, trebuie să includem la începutul programului următoarele linii:

10
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

#include <iostream>
using namespace std;

Aceste variabile speciale fac parte din categoria “obiecte”. Obiectele vor fi
studiate în detaliu la Programare orintată obiect (POO).
Iată un exemplu complet folosind noile facilităţi de scriere/citire:

// exemplu: cin si cout Introduceti un numar: 12


#include<conio.h> Si un sir de caractere: abc
#include <iostream> Numarul este: 12
using namespace std; Sirul este: abc

int main()
{
int iVal;
char sVal[30];

cout << "Introduceti un numar: ";


cin >> iVal;
cout << "Si un sir de caractere: ";
cin >> sVal;
cout << "Numarul este: " << iVal << "\n"
<< "Sirul este: " << sVal << endl;
_getch();
return 0;
}

Un aspect nou este cuvântul endl. Acesta este o variabilă globală tip şir de caractere cu
valoarea "\n" (sfârşit de linie).

Sintaxa:

cout << endl;

este echivalentă cu:

cout << "\n";

dar are avantajul că este mai lizibilă.

Atât expresiile cu cin cât şi cele cu cout pot fi înlănţuite. Expresia:

cout << a << " " << b;

este echivalentă cu:

cout << a;

11
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

cout << " ";


cout << b;

Comparativ cu funcţiile printf / scanf din C, expresiile cu cin şi cout sunt mai simple şi
mai uşor de înţeles. Nu mai este nevoie de specificatori de format. Dezavantajul este că
nu putem face afişări formatate pe un anumit număr de caractere. O afişare de genul:

printf("%7.2f", f);

nu are echivalent folosind cout.


De aceea, folosind cout nu vom putea afişa o matrice sau un vector de structuri sub formă
de tabel, aşa cum am făcut în C. Pentru astfel de afişări vom folosi printf.

1.5. Operatorii new şi delete

În C++, lucrul cu memoria dinamică este facilitat de doi operatori speciali - new şi delete.
Alocarea dinamică de memorie se face cu operatorul new. El returnează un pointer către
începutul blocului de memorie proaspăt alocat. Sintaxa operatorului este următoarea:

TIP *p, *pvector;


p = new TIP;
pvector = new TIP[nr_elemente];

De exemplu, pentru pointeri la variabile simple:


int *pi;
pi = new int;
char *pch;
pch = new char;
Şi pentru pointeri la vectori:
int *vi;
vi = new int[10];
char *psir;
psir = new char[80];

Atunci când nu mai avem nevoie de o variabilă alocată dinamic, aceasta trebuie
dealocată. Memoria dealocată devine disponibilă pentru noi cereri de alocare. Pentru
dealocari se folosesc operatorii delete – pentru variabile simple, şi delete[] – pentru
vectori. Exemplu:

delete vi;
delete[] psir;

12
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

2. APLICAŢII
1. Sa se scrie un program care citeste studentii dintr-o grupa si ii afiseaza. Programul va fi
impartit in trei module:

Modulul Student

Interfata acestui modul va fi STUDENT.H :

#ifndef _STUDENT_
#define _STUDENT_

struct Student {
char* nume;
int nota;
};

void InitStudent (Student&);


void AfisStudent (Student);
void StergeStudent (Student&);

#endif

Implementarea (fisierul STUDENT.CPP) cuprinde:

■ InitStudent citeste numele studentului (pentru care va aloca spatiu) si nota.


■ AfisStudent afiseaza campurile structurii.
■ StergeStudent va elibera spatiul de memorie ocupat de nume.

Modulul Grupa

Interfata acestui modul va fi GRUPA.H :

#ifndef _GRUPA_
#define _GRUPA_

#include "student.h"

struct Grupa {
Student* tab;
int nr;
int id; // numarul grupei, de exemplu 1105
};

void InitGrupa (Grupa&);

13
Laborator de Structuri de Date si Algoritmi – Lucrarea nr. 1

void AfisGrupa (Grupa);


void StergeGrupa (Grupa&);

#endif

Implementarea (fisierul GRUPA.CPP) cuprinde:

■ InitGrupa citeste numarul grupei si numarul de studenti, dupa care va aloca spatiu cu
malloc pentru acestia. Fiecare student va fi apoi initializat cu InitStudent.
■ AfisGrupa afiseaza studentii.
■ StergeGrupa va elibera spatiul de memorie ocupat de cei nr studenti.
Modulul Program principal

Se va gasi in fisierul MAIN.CPP:

#include <stdio.h>
#include "student.h"
#include "grupa.h"

void main()
{
Grupa g;
InitGrupa(g); // citeste studentii
AfisGrupa(g); // afiseaza grupa
StergeGrupa(g); // elibereaza spatiul
}

2. In cadrul programului anterior, adaugati o functie care cauta un student dupa nume si
afiseaza nota acestuia in cazul in care este gasit, sa un mesaj corespunzator daca nu este
gasit.

3. In cadrul programului anterior, adaugati o functie care afiseaza nota cea mai mica si
nota cea mai mare obtinute de studentii din cadrul unei grupe.

NOTARE
Problema 1 – 7p
Problema 2 – 3p
Problema 3 – 2p

Aplicatiile neterminate in timpul orelor de laborator raman ca teme pentru studiu


individual!

14

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