Laboratorul de azi are ca scop acomodarea voastra cu limbajul C++. Asta presupune tranzitia de la limbajul C, dar si intelegerea conceptelor elementare, noi, ce tin de C++. Asadar, materialul de astazi poate fi vazut ca un fel de 'Crash Course' pentru C++. El reprezinta minimul necesar care trebuie stiut pentru a intelege continutul laboratoarelor urmatoare, dar nu elimina necesitatea voastra de a exersa cu limbajul pentru a deveni familiari cu acesta.
Inainte de fiecare laborator, vom prezenta surse aditionale de informare, unde veti putea gasi explicatii suplimentare si exemple concrete.
In cazul laboratorului curent, recomandam urmatoarele capitole din cartea Absolute C++
Putem scrie comentarii pe o linie folosind simbolurile
//
Ce este intre 'forward-slash-uri' si sfarsitul liniei se ignora.
// Comentariu C++ /* Comentariu C */ /* * Comentariu Stil Documentatie (Asemanator C) */
Structurile si uniunile pot fi referite doar prin nume, deci fara a fi nevoiti sa utilizam cuvintele cheie struct sau union de fiecare data. Asta arata ca struct-urile si union-urile sunt in C++ tipuri de date recunoscute automat.
struct str { // Cod aferent structurii }; union uni { // Cod aferent union }; str a, b; uni c, d; void f(str x) {/* cod functie */}
Pentru a modifica campurile unei variabile de tip struct, putem utiliza functiile membre (metode). Aceste functii au urmatoarele caracteristici:
Vom prezenta un exemplu de utilizare al acestor functii. Utilitatea lor se va vedea mai bine in cazul programarii obiectuale.
Exemplul poate fi rulat aici
#include <iostream> #include <cstdlib> using namespace std; struct point{ private: //altfel, sunt implicit public int x; int y; public: //pentru a fi vizibile de oriunde void modificare(int, int); //implementarile functiilor se face void afisare(); //dupa definirea structurii }; //Implementarea metodelor din struct void point::modificare(int xx, int yy) { x = xx; //functiile membre au acces la campurile private y = yy; } void point::afisare() { cout << x << " " << y << endl; } int main() { point p; //declararea unei variabile de tip struct point //Apelam metodele pentru variabila p. Nu o dam ca parametru: p.afisare(); //va afisa ce se afla in memorie, la adresele campurilor x si y p.modificare(3, 3); //modificam valorile lui x si y folosind functia membra p.afisare(); //va afisa noile valori 3 si 3 return 0; }
O functie inline e definita in felul urmator:
inline TIP_RETURNAT NUME_FUNCTIE(LISTA_ARGUMENTE) {/* cod functie */}
De ce sunt utile functiile inline? La apelul unei functii inline, in loc de apelarea propriu-zisa a functiei, se substituie codul efectiv al functiei la compilare. Avantajul este in termeni de viteza. Nu se va mai 'cauta' definitia functiei, va fi 'direct' unde trebuie. Dezavantajul este cresterea dimensiunii fisierului binar generat. Asadar, functiile inline reprezinta un trade-off intre viteza si spatiu.
Pentru a rula exemplul de mai jos, click aici.
#include <iostream> using namespace std; // Declaram functia 'max' drept inline inline int max(int a, int b) { return (a > b) ? a : b; } /* Echivalent in C */ #define max(a,b) (((a) > (b)) ? (a) : (b)) int main() { int nr1 = 2, nr2 = 6; cout << max(nr1, nr2) << endl; return 0; }
In C++ este permisa supradefinirea functiilor, adica existenta mai multor functii cu acelasi nume, dar care difera prin tipul si/sau numarul parametrilor. Decizia de a apela o functie sau alta se face dupa tipul sau numarul parametrilor.
Aceasta inseamna ca o functie este recunoscuta nu numai prin numele sau, ci prin “semnatura”, adica nume, tip si lista de parametri.
In exemplul de fata, se considera patru functii cu acelasi nume (cub). Trei dintre ele au un unic parametru (int, float si double), iar a patra are doi parametri. Ultima functie apeleaza functia cub specifica tipului float. Pentru a urmari apelurile, fiecare functie este insotita de un mesaj de identificare la consola.
Pentru a experimenta cu codul de mai jos, click aici.
#include <iostream> using namespace std; int cub (int n) { cout << "Apel functie cub int\n"; return n*n*n; } double cub (double n) { cout << "Apel functie cub double\n"; return n*n*n; } float cub (float n) { cout << "Apel functie cub float\n"; return n*n*n; } float cub (float x, float a) { cout << "Apel functie cub diferenta\n"; return cub (x-a); // Aici se va apela cub cu float } int main() { cout << cub(3) << endl << endl; double var = 3.33335678976; cout << cub(var) << endl << endl; float var2 = 3.2; cout << cub(var2) << endl << endl; cout << cub(9.9, 3.3) << endl << endl; return 0; }
Prin acesti operatori se pot face scrieri si citiri la consola in formate predefinite. Aceasta ne scuteste de folosirea functiilor de tip printf/scanf etc. si ofera o forma comoda de I/O.
Codul de mai jos poate fi executat de aici.
#include <iostream> using namespace std; int main() { int i, j, k; cout << "Introduceti 3 intregi:\n"; cin >> i >> j >> k; cout << "Intregii sunt:\n"; cout << i << " " << j << " " << k << "\n"; return 0; }
O declaratie de forma
int x; int &y = x;
precizeaza ca y este o referinta catre un intreg (catre x). Din punct de vedere sintactic, y este tot un intreg, care nu “exista” insa (nu are suport propriu de memorie) ci este o alta referire la variabila x. Utilitatea principala a referintelor este impunerea transferului prin referinta a parametrilor la o functie.
Acest fapt este ilustrat prin functia schimba de mai jos.
void schimba (int & a,int & b) { int temp = a; a = b; b = temp; }
Declaratiile de parametri spun ca a si b sunt referinte catre intregi, deci la un apel de forma:
int x = 7, y = 3; schimba(x, y);
in variabilele a si b din functie se vor gasi referinte catre intregii x si y din programul apelant. Aceasta face ca functia sa poata modifica efectiv aceste variabile.
De comparat cu metoda de implementare a transferului prin referinta la C standard, unde trebuiau utilizati explicit pointeri
void schimba_C (int * a, int * b) { int temp = *a; *a = *b; *b = temp; }
cu apelul
int x = 7, y = 3; schimba_C (&x, &y);
Codul complet il puteti rula de aici.
#include <iostream> using namespace std; void schimba (int & a,int & b) { int temp = a; a = b; b = temp; } int main() { int a = 4, b = 20; cout << "Inainte de interschimbare: " << endl; cout << a << " " << b << endl; // Apelam interschimbarea schimba(a,b); cout << "Dupa interschimbare:" << endl; cout << a << " " << b << endl; return 0; }
Alocarea de memorie se face in C++ cu operatorul new (care face parte din limbaj). Enumeram urmatoarele forme de alocare:
// Variabila int var = new int;
int dimensiune = 10; int *vect = new int[dimensiune];
// Varianta C++ int *vect = new int[20](); /* Echivalent, in C, ati fi procedat astfel: */ int i; int *vect = malloc(sizeof(int) * 20); for (i = 0; i < 20; i++) { vect[i] = 0; }
Daca se doreste o alocare oarecare (fara a avea un tip precizat), se poate folosi tipul char (adica alocare la nivel de octet), sau unsigned char, “botezat” eventual cu typedef.
// vrem sa alocam 20 de bytes, pentru un sir de caractere // 1 byte == sizeof(char) char *s1 = new char[sizeof(char) * 20 + 1]; // sau char *s2 = new char[20*sizeof(unsigned char) + 1]
Metoda prezentata mai sus este nefolosita in industrie, tipul char* fiind inlocuit de tipul C++ string. Pentru mai multe detalii asupra clasei string, click aici. De asemenea, puteti inspecta Capitolul 9 (Chapter 9, pag 367-) : Strings, din cartea Absolute C++ pentru explicatii suplimentare, dar si exemple concrete.
Functia print_tab de mai jos are trei parametri, dintre care ultimul este de tip pointer la char, care este initializat implicit pe sirul constant “Mesaj implicit”. Functia se poate apela cu un parametru explicit, sau ca si cand ar fi doar cu doi parametri, caz in care se va utiliza al treilea parametru implicit.
// Definitie void print_tab (int *a, int n, char *mesaj = "Mesaj implicit\n") { for (int i = 0; i < n; i++) cout << a[i] << " "; cout << "\n" << mesaj; } // Apeluri print_tab (q, 10, "Mesaj explicit\n"); print_tab (q, 10);
Ca sa testati toate conceptele prezentate in acest laborator, rulati codul de mai jos apasand aici.
#include <cstdlib> #include <iostream> using namespace std; int cub (int n) { cout << "Apel functie cub int\n"; return n*n*n; } double cub (double n) { cout << "Apel functie cub double\n"; return n*n*n; } float cub (float n) { cout << "Apel functie cub float\n"; return n*n*n; } float cub (float x, float a) { float m; cout << "Apel functie cub diferenta\n"; return cub (x-a); // Aici se va apela cub cu float } void schimba (int & a,int & b) { int temp = a; a = b; b = temp; } void print_tab (int *a, int n, char *mesaj = "Mesaj implicit\n") { for (int i = 0; i < n; i++) cout << a[i] << " "; cout << "\n" << mesaj; } int main(int argc, char *argv[]) { int i = 12; float f = 12.5F; double d = 12.5; int j = 21, k; int *p, *q; // // Test functii cub // cout << cub(i) << " " << cub (f) << " " << cub (d) << "\n"; cout << cub (f, 2.5) << "\n"; cout << "i = " << i << " j = " << j << " inainte de schimba(i,j)\n"; schimba (i, j); cout << "i = " << i << " j = " << j << " dupa schimba(i.j)\n"; // // Test << si >> // cout << "Introduceti 3 intregi:\n"; cin >> i >> j >> k; cout << "Intregii sunt:\n"; cout << i << " " << j << " " << k << "\n"; // // Test operatori new si delete // p = new int; *p = 30; // Alocare simpla cout << *p << "\n"; delete p; p = new int (100); // Alocare cu initializare cout << *p << "\n"; delete p; q = new int [10]; // Alocare tablou de 10 int for (i = 0; i < 10; i++) *(q + i) = i + 1; //q[i]=i+1; for (i = 0; i < 10; i++) cout << q[i] << " "; cout << "\n"; // // Test functie cu argument implicit // print_tab (q, 10, "Mesaj explicit\n"); print_tab (q, 10); delete [] q; // Eliberare tablou // // La compilatoarele mai vechi, eliberarea de tablouri (i.e. operatorul delete []) nu este posibila. // Se foloseste in acest caz delete simplu. // return 0; }