This shows you the differences between two versions of the page.
sd-ca:laboratoare:laborator-01 [2015/02/27 10:21] dragos.dimitriu [Exercitii] |
sd-ca:laboratoare:laborator-01 [2016/02/21 19:49] (current) radu.stochitoiu |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laborator 01 - Introducere in C++ ====== | + | ====== Articol 01 - Introducere in C++ ====== |
- | Responsabili | + | În cadrul acestui articol ne propunem să ilustrăm conceptele din C++ cu care veți lucra pe parcursul acestui semestru. |
- | * [[mailto:daniel.ciocirlan@cti.pub.ro | Daniel Ciocîrlan]] | + | |
- | * [[mailto:andrei.vasiliu2211@cti.pub.ro | Andrei Vasiliu]] | + | |
- | În cadrul acestui laborator ne propunem să ilustrăm conceptele din C++ cu care veți lucra pe parcursul acestui semestru. | + | Într-un mod extrem de simplist spus C++ este un superset al limbajului C, iar tot ceea ce ați învățat în C la [[http://ocw.cs.pub.ro/courses/programare| PC]] se poate compila cu un compilator pentru limbajul C++, funcționalitatea rămânând aceeași. |
- | + | ||
- | Într-un mod extrem de simplist spus C++ este un superset al limbajului C, iar tot ceea ce ați învățat în C la [[http://ocw.cs.pub.ro/courses/programare-ca| PC]] se poate compila cu un compilator pentru limbajul C++, funcționalitatea rămânând aceeași. | + | |
===== Obiective ===== | ===== Obiective ===== | ||
Line 13: | Line 9: | ||
Ne dorim să: | Ne dorim să: | ||
*Realizăm tranziția de la C la C++ | *Realizăm tranziția de la C la C++ | ||
+ | * înțelege conceptul de referințe din C++ | ||
+ | * înțelege conceptul de read-only introdus prin identificatorul const | ||
*Înțelegem ce presupune definirea unei clase | *Înțelegem ce presupune definirea unei clase | ||
- | *Învățăm ce înseamnă constructor / destructor | ||
===== De ce C++? ===== | ===== De ce C++? ===== | ||
- | Pentru că C++ permite implementarea structurilor de date cu tipuri de date generice, prin intermediul template-urilor, într-un mod care nu presupune trecerea la programarea orientată pe obiecte. În cadrul acestui laborator nu ne așteptăm să dobândiți cunoștințe (elementare sau avansate) legate de programarea obiectuală, întrucât în anul II există un [[http://elf.cs.pub.ro/poo|curs]] dedicat acestui lucru. | + | Pentru că C++ permite implementarea structurilor de date cu tipuri de date generice, prin intermediul template-urilor, într-un mod care nu presupune trecerea la programarea orientată pe obiecte. În cadrul acestui articol nu ne așteptăm să dobândiți cunoștințe (elementare sau avansate) legate de programarea obiectuală, întrucât în anul II există un [[http://elf.cs.pub.ro/poo|curs]] dedicat acestui lucru. |
- | Vă încurajăm însă să citiți cât mai multe despre C++ pe parcurs și să cereți lămuriri suplimentare din partea asistenților de la laborator. | ||
===== Sintaxa C++ ===== | ===== Sintaxa C++ ===== | ||
- | |||
- | ==== De la structuri C la clase C++ ==== | ||
==== Definirea structurii ==== | ==== Definirea structurii ==== | ||
- | În cadrul laboratorului de Programarea Calculatoarelor am învățat să declarăm și să folosim tipuri de date complexe, [[http://ocw.cs.pub.ro/courses/programare-ca/laboratoare/lab10 | structuri]] în limbajul C. Pentru a recapitula, iată mai jos un exemplu simplu de astfel de structură, pentru a reprezenta un număr complex. | + | În cadrul laboratorului de Programarea Calculatoarelor am învățat să declarăm și să folosim tipuri de date complexe, [[http://ocw.cs.pub.ro/courses/programare/laboratoare/lab10| structuri]] în limbajul C. Pentru a recapitula, iată mai jos un exemplu simplu de astfel de structură, pentru a reprezenta un număr complex. |
<columns 400px 100% -> | <columns 400px 100% -> | ||
Line 172: | Line 166: | ||
</code> | </code> | ||
- | ==== Clase ==== | ||
- | |||
- | Formal am făcut deja primii pași mai sus pentru a implementa o clasă în C++, utilizând keyword-ul //struct//. | ||
- | |||
- | Totuși, ce înseamnă o clasă? Nu trebuie decât să ne gândim la ce am făcut mai sus: | ||
- | *am definit un tip de date | ||
- | *i-am adăugat atribute (am definit ce proprietăți îl caracterizează: partea reală și partea imaginară) | ||
- | *i-am adăugat metode (am definit cum se comportă: inițializarea și conjugarea) | ||
- | |||
- | Cu această adăugare menționată, putem să ne referim la ceea ce înseamnă o **clasă**, respectiv un **obiect**. | ||
- | |||
- | Ne referim la o **clasă** ca fiind o amprentă (blueprint) sau descriere generală. | ||
- | Un **obiect** sau o **instanță a clasei** este o variabilă concretă ce se conformează descrierii clasei. | ||
- | |||
- | Vom numi **clasă** tipul de date definit de //struct complex// sau //class complex// și **obiect** o instanțiere (o alocare dinamică sau locală) a tipului de date. | ||
- | |||
- | Când discutăm despre tipul de date //complex// ne referim la clasă. | ||
- | Când discutăm despre variabila //number// ne referim la un obiect, o instanță a clasei. | ||
- | |||
- | ==== Keyword-ul "class" vs. "struct" ==== | ||
- | |||
- | Și totuși, C++ adăugă keyword-ul //class//. Care este diferența între //class// și //struct//? | ||
- | Iată cum definim complet clasa de mai sus, separând antetul de implementare și de programul principal. | ||
- | |||
- | <columns 100% 100% -> | ||
- | <code c++ complex.h> | ||
- | class Complex { | ||
- | double re; | ||
- | double im; | ||
- | | ||
- | Complex conjugate(); | ||
- | }; | ||
- | </code> | ||
- | |||
- | <newcolumn> | ||
- | |||
- | <code c++ complex.cc> | ||
- | #include "complex.h" | ||
- | Complex Complex::conjugate() { | ||
- | Complex conjugate; | ||
- | conjugate.re = this->re; | ||
- | conjugate.im = -(this->im); | ||
- | | ||
- | return conjugate; | ||
- | } | ||
- | </code> | ||
- | |||
- | <newcolumn> | ||
- | |||
- | <code c++ main.cc> | ||
- | #include <stdio.h> | ||
- | #include "complex.h" | ||
- | |||
- | int main() { | ||
- | Complex number; | ||
- | number.re = 2; | ||
- | number.im = 4; | ||
- | | ||
- | printf("%.2lf %.2lf\n", number.re, number.im); | ||
- | | ||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | </columns> | ||
- | |||
- | |||
- | ====Compilare==== | ||
- | |||
- | Sursele C++ se compilează folosind compilatorul **g++**. Acesta permite exact aceleași opțiuni de bază ca și **gcc**, compilatorul utilizat pentru sursele de C. | ||
- | |||
- | * Încercați să compilați și să rulați codul din cele 3 fișiere de mai sus. | ||
- | |||
- | <code bash> | ||
- | g++ complex.cc main.cc -o exemplu | ||
- | </code> | ||
- | Ce observați? | ||
- | |||
- | Înlocuiți acum keyword-ul //class// cu keyword-ul //struct// și compilați din nou. | ||
- | |||
- | ==== Specificatori de acces ==== | ||
- | Am observat mesajul de eroare în urma compilării fișierelor de mai sus. | ||
- | |||
- | Astfel, **singura diferență** folosirea celor două keyword-uri este nivelul implicit de vizibilitate a metodelor și atributelor. | ||
- | ***private** - pentru clasele declarate cu **class** | ||
- | ***public** - pentru clasele declarate cu **struct** | ||
- | |||
- | Membri precedați de label-ul **private** pot fi folosiți numai în interiorul clasei, în cadrul metodelor acesteia. | ||
- | Ei nu pot fi citiți sau modificați din afara clasei. | ||
- | |||
- | Iată cum puteam remedia soluția: | ||
- | <code c++ complex.h> | ||
- | class Complex { | ||
- | public: | ||
- | double re; | ||
- | double im; | ||
- | |||
- | Complex conjugate(); | ||
- | }; | ||
- | </code> | ||
- | |||
- | ==== Constructori și destructori ==== | ||
- | |||
- | Studiați codul de mai jos. | ||
- | |||
- | <columns 100% 100% -> | ||
- | <code c++ complex.h> | ||
- | class Complex { | ||
- | public: | ||
- | // Constructor | ||
- | Complex(double re, double im); | ||
- | | ||
- | // Destructor | ||
- | ~Complex(); | ||
- | | ||
- | double getRe(); | ||
- | double getIm(); | ||
- | | ||
- | Complex conjugate(); | ||
- | |||
- | private: | ||
- | double re; | ||
- | double im; | ||
- | }; | ||
- | </code> | ||
- | |||
- | <newcolumn> | ||
- | |||
- | <code c++ complex.cc> | ||
- | #include "complex.h" | ||
- | Complex::Complex(double re, double im) { | ||
- | this->re = re; | ||
- | this->im = im; | ||
- | } | ||
- | |||
- | Complex::~Complex() { | ||
- | } | ||
- | |||
- | Complex Complex::conjugate() { | ||
- | Complex conjugat(re, -im); | ||
- | return conjugat; | ||
- | } | ||
- | |||
- | double Complex::getRe() { | ||
- | return re; | ||
- | } | ||
- | |||
- | double Complex::getIm() { | ||
- | return im; | ||
- | } | ||
- | </code> | ||
- | |||
- | </columns> | ||
- | |||
- | <code c++ main.cc> | ||
- | #include <stdio.h> | ||
- | #include "complex.h" | ||
- | |||
- | int main() { | ||
- | Complex number(2, 3); | ||
- | printf("%lf %lf\n", number.getRe(), number.getIm()); | ||
- | | ||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | </columns> | ||
- | |||
- | ==== Constructor ==== | ||
- | |||
- | Observăm două bucăți din cod în mod special: | ||
- | <code c++> | ||
- | Complex::Complex(double re, double im); | ||
- | </code> | ||
- | Linia de mai sus **nu are tip** returnat, spre deosebire de celelalte linii. | ||
- | Acesta este **constructorul** clasei, care este apelat în momentul alocării unui obiect. | ||
- | |||
- | Ce operații sunt uzuale în constructor? | ||
- | *inițializarea membrilor clasei cu valori predefinite sau date ca parametru | ||
- | *alocarea memoriei pentru anumiți membri | ||
- | |||
- | A doua bucată observată este: | ||
- | <code c++> | ||
- | Complex numar(2, 3); | ||
- | </code> | ||
- | Până acum nu ați mai alocat astfel structurile. Ce se întâmplă în spate este exact ceea ce intuiți: este apelat constructorul obiectului și se execută instrucțiunile acestuia pentru variabila numar (reprezentată ca pointer prin this, direct în interiorul constructorului). | ||
- | |||
- | |||
- | În constructorul definit mai sus, tot ceea ce se întâmplă este să se inițializeze membri. Pentru asta, C++ vă pune la dispoziție o sintaxă simplă: | ||
- | <code c++> | ||
- | Complex::Complex(double real, double imaginar) : | ||
- | re(real), | ||
- | im(imaginar) { | ||
- | } | ||
- | </code> | ||
- | |||
- | Cei doi constructori sunt identici ca funcționalitate. | ||
- | |||
- | ==== Destructor ==== | ||
- | Așa cum probabil ați observat, **constructorul** este apelat în mod **explicit** de către voi. **Destructorul** însă, în cazul de mai sus, este apelat **implicit** la terminarea blocului care realizează dealocărea automată a obiectului. | ||
- | |||
- | Un destructor nu are parametri și se declară în interiorul clasei astfel: | ||
- | <code c++> | ||
- | ~Complex(); | ||
- | </code> | ||
- | |||
- | Dacă în constructor sau în interiorul clasei ați fi alocat memorie, cel mai probabil în destructor ați fi făcut curat și ați fi apelat free pe membrul respectiv. | ||
==== Alocarea / Dealocarea dinamică ==== | ==== Alocarea / Dealocarea dinamică ==== | ||
Line 437: | Line 226: | ||
**Atenție** Nu convertiți matricea de mai sus la un pointer dublu, deoarece cele două nu sunt același lucru (prima este un vector de ''N'' pointeri care pointează către liniile matricei, pe când a doua este o zonă continuă de memorie în care compilatorul accesează elementele la fel ca într-o matrice clasică). | **Atenție** Nu convertiți matricea de mai sus la un pointer dublu, deoarece cele două nu sunt același lucru (prima este un vector de ''N'' pointeri care pointează către liniile matricei, pe când a doua este o zonă continuă de memorie în care compilatorul accesează elementele la fel ca într-o matrice clasică). | ||
- | ===== Exercitii ===== | ||
- | *[**2p**] Parcurgeți laboratorul în întregime și discutați cu asistentul vostru eventualele neclarități. | + | ===== Referințe ===== |
+ | In C++ există două modalități de a lucra cu adrese de memorie: | ||
+ | * pointeri (la fel ca cei din C) | ||
+ | * referințe. | ||
+ | |||
+ | Referinţa poate fi privită ca un pointer constant inteligent, a cărui iniţializare este forţată de către compilator (la definire) şi care este dereferenţiat automat. | ||
+ | |||
+ | Semantic, referințele reprezintă aliasuri ale unor variabile existente. La crearea unei referinţe, aceasta trebuie iniţializată cu adresa unui obiect (nu cu o valoare constantă). | ||
+ | |||
+ | Sintaxa pentru declararea unei referințe este: | ||
+ | |||
+ | tip& referinta = valoare; | ||
+ | |||
+ | Exemplu: | ||
+ | <code > | ||
+ | int x=1, y=2; | ||
+ | int& rx = x; //referinta | ||
+ | rx = 4; //modificarea variabilei prin referinta | ||
+ | rx = 15; //modificarea variabilei prin referinta | ||
+ | rx =y; //atribuirea are ca efect copierea continutului | ||
+ | //din y in x si nu modificarea adresei referintei | ||
+ | </code> | ||
+ | |||
+ | Spre deosebire de pointeri: | ||
+ | * referinţele sunt iniţializate la creare (pointerii se pot iniţializa oricând) | ||
+ | * referinţa este legată de un singur obiect şi această legătură nu poate fi modificată pentru un alt obiect | ||
+ | * referințele nu au operații speciale, toți operatorii aplicați asupra referințelor sunt de fapt aplicați asupra variabilei referite(de exemplu extragerea adresei unei referințe va returna adresa variabilei referite) | ||
+ | * __nu există referinţe nule__ – ele sunt întotdeauna legate de locaţii de memorie | ||
+ | |||
+ | Referinţele se folosesc: | ||
+ | * în listele de parametri ale funcţiilor | ||
+ | * ca valori de întoarcere ale funcţiilor | ||
+ | __Motivul__ pentru aceste tipuri de utilizări este unul destul de simplu: când se transmit parametrii funcțiilor, se copiază conținutul variabilelor transmise pe stivă, lucru destul de costisitor. Prin transmiterea de referințe, nu se mai copiază nimic, așadar intrarea sau ieșirea dintr-o funcție sunt mult mai putin costisitoare. | ||
+ | |||
+ | ==== Keyword const==== | ||
+ | |||
+ | În C++, există mai multe întrebuințări ale cuvântului cheie **const**: | ||
+ | * specifică un obiect a cărui valoare nu poate fi modificată | ||
+ | * specifică metodele unui obiect read-only care pot fi apelate | ||
+ | |||
+ | Pentru a specifica, un obiect a cărui valoare nu poate fi modificată, **const** se poate folosi în următoarele feluri: | ||
+ | * ''const tip variabila'' => specifică o variabilă constantă | ||
+ | * ''tip const& referinta_ct = variabilă;'' => specifică o referință constantă la un obiect, obiectul neputând fi modificat | ||
+ | * ''const int *p_int'' => specifică un pointer la int modificabil, dar conținutul locației de memorie către care ''p_int'' arată __nu__ se poate modifica. | ||
+ | * ''int * const p_int'' => specifică un pointer la int care nu poate fi modificat (Variabilei ''p_int'' nu i se poate asigna nici o valoare, dar conținutul locației de memorie către care ''p_int'' arată se poate modifica) | ||
+ | |||
+ | |||
+ | Orice obiect constant poate apela doar funcții declarate constante. O funcție constantă se declară folosind sintaxa: | ||
+ | <code c> | ||
+ | void fct_nu_modifica_obiect() const; //am utilizat cuvântul cheie const | ||
+ | //dupa declarația funcției fct_nu_modifica_obiect | ||
+ | </code> | ||
+ | |||
+ | Această declaratie a functiei garantează faptul că obiectul pentru care va fi apelată nu se va modifica. | ||
+ | |||
+ | Regula de bază a apelării membrilor de tip funcție ai claselor este: | ||
+ | * funcțiile ''const'' pot fi apelate pe toate obiectele | ||
+ | * funcțiile non-const pot fi apelate doar pe obiectele non-const. | ||
+ | |||
+ | Exemple: | ||
+ | |||
+ | <code cpp> | ||
+ | //declarație | ||
+ | class Complex { | ||
+ | private: | ||
+ | int re; | ||
+ | int im; | ||
+ | public: | ||
+ | Complex(); | ||
+ | int GetRe() const; | ||
+ | int GetIm() const; | ||
+ | void SetRe(int re); | ||
+ | void SetIm(int im); | ||
+ | }; | ||
+ | |||
+ | |||
+ | //apelare | ||
+ | Complex c1; | ||
+ | const Complex c2; | ||
+ | c1.GetRe(); //corect | ||
+ | c1.SetRe(5); //corect | ||
+ | c2.GetRe(); //corect | ||
+ | c2.SetRe(5); //incorect | ||
+ | |||
+ | </code> | ||
+ | |||
+ | |||
+ | ==== Funcții care returnează referințe ==== | ||
+ | |||
+ | Pentru clasa Complex, definim funcţiile care asigură accesul la partea reală, respectiv imaginară a unui număr complex: | ||
+ | double getRe(){ return re; } | ||
+ | double getIm(){ return im; } | ||
+ | |||
+ | Dacă am dori modificarea părţii reale a unui număr complex printr-o atribuire de forma: | ||
+ | z.getRe()=2.; | ||
+ | constatăm că funcţia astfel definită nu poate apărea în partea stângă a unei atribuiri. | ||
+ | |||
+ | Acest neajuns se remediază impunând funcţiei să returneze o referinţă la obiect, adică: | ||
+ | double& getRe(){ return re; } | ||
+ | |||
+ | Codul de mai sus returnează o referință către membrul ''re'' al obiectului ''Complex z'', așadar orice atribuire efectuată asupra acestui câmp va fi vizibilă și în obiect. | ||
+ | |||
+ | ====Compilare==== | ||
+ | |||
+ | Sursele C++ se compilează folosind compilatorul **g++**. Acesta permite exact aceleași opțiuni de bază ca și **gcc**, compilatorul utilizat pentru sursele de C. | ||
+ | |||
+ | * Încercați să compilați și să rulați codul din cele 3 fișiere de mai sus. | ||
+ | |||
+ | <code bash> | ||
+ | g++ complex.cc main.cc -o exemplu | ||
+ | </code> | ||
+ | Ce observați? | ||
+ | |||
+ | Înlocuiți acum keyword-ul //class// cu keyword-ul //struct// și compilați din nou. | ||
+ | |||
+ | <hidden> | ||
*[**3p**] Clasa Complex | *[**3p**] Clasa Complex | ||
*[**2p**] Adăugați clasei Complex metode pentru adunare, scădere și înmulțire cu un alt număr complex. | *[**2p**] Adăugați clasei Complex metode pentru adunare, scădere și înmulțire cu un alt număr complex. | ||
Line 452: | Line 355: | ||
*[**2p**] Adăugați clasei Fracție metode pentru înmulțire/împărțire cu o altă fracție, respectiv pentru determinarea numărului zecimal echivalent. | *[**2p**] Adăugați clasei Fracție metode pentru înmulțire/împărțire cu o altă fracție, respectiv pentru determinarea numărului zecimal echivalent. | ||
*[**1p**] Arătați funcționalitatea prin adăugarea unui cod simplist în fișierul main.cc | *[**1p**] Arătați funcționalitatea prin adăugarea unui cod simplist în fișierul main.cc | ||
- | <hidden> | + | |
- | *[**8p**] Clasa MyArrayList - va ține un vector de elemente (de tip ''int'' în cazul nostru) și va avea metode de redimensionare în caz că i se depășește capacitatea curentă | + | |
- | * [**2p**] Creați clasa MyArrayList și adăugați-i un membru astfel încât să poată reține un vector de elemente de tip int. | + | |
- | * [**1p**] Specificați în constructor dimensiunea inițială a vectorului intern. | + | |
- | * [**2p**] Adăugați clasei MyArrayList o metodă de adăugare a unui element nou. Dacă vectorul intern este deja plin, se va aloca un nou vector de dimensiune dublă, se va copia conținutul original și se va adăuga apoi noul element ca în mod obișnuit. | + | |
- | * [**2p**] Testați clasa MyArrayList printr-un cod simplu care arată cum se adaugă elemente și cum se redimensionează structura voastră de date. | + | |
- | * [**1p**] Verificați cu Valgrind că nu aveți memory leaks. | + | |
*[**5p**] Clasa MappingEntry - conține 2 membri de tipuri potențial diferite (aici vom folosi ''int'' și ''char*'') și realizează, din punct de vedere conceptual, asocierea între două valori (una se numește //cheie//, iar cealaltă //valoare//). | *[**5p**] Clasa MappingEntry - conține 2 membri de tipuri potențial diferite (aici vom folosi ''int'' și ''char*'') și realizează, din punct de vedere conceptual, asocierea între două valori (una se numește //cheie//, iar cealaltă //valoare//). | ||
*[**2p**] Implementați și folosiți clasa MappingEntry de mai sus adăugând constructor, destructor. | *[**2p**] Implementați și folosiți clasa MappingEntry de mai sus adăugând constructor, destructor. |