This is an old revision of the document!


Laborator 02 - Diferențe C/C++

Autor: Răzvan Cristea

Obiective Specifice

Studentul va fi capabil la finalul acestui laborator să:

  • recunoască diferențele dintre C și C++
  • realizeze unui program în C++ care să valorifice caracteristicile și facilitățile specifice limbajului

Introducere

În cadrul acestui laborator vom evidenția principalele diferențe dintre limbajele C și C++. Vom analiza, prin exemple simple, modul în care C++ extinde caracteristicile limbajului C și vom pune bazele unei înțelegeri mai profunde a acestuia. Astfel, vom avea un punct de plecare solid pentru paradigma Orientată Obiect (OO) pe care o vom studia începând cu laboratorul următor. Pentru a înțelege diferențele dintre cele două limbaje mai întâi ar trebui să știm care sunt asemănările, iar pentru asta recomandăm citirea laboratorului 1.

Diferențe C/C++

Citirea și afișarea variabilelor

În C pentru citirea și afișare variabilor se realiza utilizând funcțiile scanf și printf. În C++ vom folosi operatorul >> pentru citirea datelor de la tastatură sau din fișiere și operatorul << pentru afișarea datelor în fișiere sau consolă.

Citirea și afișarea în C
#include <stdio.h>
 
int main()
{
    int x;
 
    printf("Introduceti un numar: ");
    scanf("%d", &x);
 
    printf("Numarul introdus de utilizator este: %d\n", x);
 
    return 0;
}
Citirea și afișarea în C++
#include <iostream>
 
int main()
{
    int x;
 
    std::cout << "Introduceti un numar: "; // cout vine de la console output
    std::cin >> x; // cin vine de la console input
 
    std::cout << "Numarul introdus de utilizator este: " << x << '\n';
 
    return 0;
}

Este de menționat faptul ca în C++ și funcțiile scanf și printf se pot utiliza, dar ca și recomandare ar fi mai indicată utilizarea operatorilor de citire și afișare ai limbajului, deoarece sunt mai specializați pentru ceea ce vom învăța pe parcursul semestrului.

Citirea și scrierea datelor în fișiere

În această secțiune vom prezenta modul de citire și scriere a datelor în fișierele text în limbajul C++. La fel ca în limbajul C aici avem posibilitatea să folosim funcțiile fscanf și fprintf însă se recomandă folosirea librăriei fstream care este specializată pentru lucrul cu fișiere. Dacă dorim să citim date dintr-un fișier vom volosi variabile de tipul ifstream (input file stream) iar pentru pentru scriere se utilizează tipul ofstream (output file stream).

Mai jos este prezentat un exemplu simplu de folosire a datelor de tip ifstream și ofstream.

#include <fstream>
#include <iostream>
 
void scrieInFisier(const char* numeFisier)
{
    std::ofstream outFile(numeFisier);
 
    if (!outFile) // verificam daca fisierul a fost deschis
    {
        std::cerr << "Eroare la deschiderea fisierului pentru scriere!\n";
        return;
    }
 
    outFile << "Ana" << " " << 20 << " " << 9.5 << '\n';
    outFile << "Ion" << " " << 22 << " " << 8.75 << '\n';
    outFile << "Maria" << " " << 19 << " " << 10.0 << '\n';
 
    outFile.close();
}
 
void citesteDinFisier(const char* numeFisier)
{
    std::ifstream inFile(numeFisier);
 
    if (!inFile) // verificam daca fisierul a fost deschis
    {
        std::cerr << "Eroare la deschiderea fisierului pentru citire!\n";
        return;
    }
 
    int varsta;
    double nota;
    char nume[50];
 
    std::cout << "Inregistrarile citite din fisier se pot observa mai jos\n\n";
 
    while (inFile >> nume >> varsta >> nota) // citim pana cand nu mai avem date
    {
        std::cout << "Nume: " << nume
            << ", Varsta: " << varsta
            << ", Nota: " << nota << '\n';
    }
 
    inFile.close();
}
 
int main()
{
    const char* denumireFisier = "studenti.txt";
 
    scrieInFisier(denumireFisier);
    citesteDinFisier(denumireFisier);
 
    return 0;
}

Iar în consolă output-ul arată ca mai jos.

Inregistrarile citite din fisier se pot observa mai jos

Nume: Ana, Varsta: 20, Nota: 9.5
Nume: Ion, Varsta: 22, Nota: 8.75
Nume: Maria, Varsta: 19, Nota: 10

În mod normal nu este necesară apelarea funcției close pentru a închide fluxurile de citire și de scriere în fișier, deoarece acestea au un mecanism automat de închidere atunci când se termină execuția unei funcții. În cazul exemplului de mai sus nu este necesară apelarea funcției close deoarece variabilele de tip ifstream și ofstream sunt variabile locale în funcțiile în care apar. Dacă am fi scris tot codul în funcția main atunci apelarea funcției close devenea obligatorie după ce terminam partea de scriere în fișier.

Alocarea dinamică a memoriei

Dacă în cazul citirii și afișării variabilelor lucrurile nu erau cu mult diferite, dacă discutăm despre alocare dinamică există câteva diferențe seminficative pe care o sa le observăm în exemplele următoare.

Alocare dinamică în C

Pentru a aloca dinamic în C folosim la alegere funcția malloc sau funcția calloc, singura diferență între ele fiind faptul că funcția calloc initializează cu 0 valorile. Pentru eliberarea memoriei se folosește funcția free.

#include <iostream>
 
int main()
{
    int* ptr1 = (int*)malloc(sizeof(int));
    *ptr1 = 5;
 
    // sau folosind calloc
 
    int* ptr2 = (int*)calloc(1, sizeof(int));
 
    std::cout << *ptr2 << '\n';
 
    *ptr2 = *ptr1;
 
    std::cout << *ptr1 << '\n';
    std::cout << *ptr2 << '\n';
 
    free(ptr1);
    free(ptr2);
 
    return 0;
}

Dacă dorim să alocăm dinamic a un vector putem proceda în felul următor.

#include <iostream>
 
int main()
{
    int nrElemente = 5;
    int* vector = (int*)malloc(nrElemente * sizeof(int));
 
    vector[0] = 3;
    vector[1] = 2;
    vector[2] = -2;
    vector[3] = 10;
    vector[4] = 8;
 
    std::cout << "Vectorul alocat dinamic este: ";
 
    for (int i = 0; i < nrElemente; i++)
    {
        std::cout << vector[i] << ' ';
    }
 
    free(vector);
 
    return 0;
}

Iar dacă vrem să realocăm spațiul din vector folosim funcția realloc.

#include <iostream>
 
int main()
{
    int nrElemente = 5;
    int* vector = (int*)malloc(nrElemente * sizeof(int));
 
    vector[0] = 3;
    vector[1] = 2;
    vector[2] = -2;
    vector[3] = 10;
    vector[4] = 8;
 
    std::cout << "Vectorul alocat dinamic este: ";
 
    for (int i = 0; i < nrElemente; i++)
    {
        std::cout << vector[i] << ' ';
    }
 
    nrElemente = 8;
 
    vector = (int*)realloc(vector, nrElemente * sizeof(int));
 
    vector[5] = 15;
    vector[6] = 20;
    vector[7] = 25;
 
    std::cout << "\nVectorul realocat dinamic este: ";
 
    for (int i = 0; i < nrElemente; i++)
    {
        std::cout << vector[i] << ' ';
    }
 
    free(vector);
 
    return 0;
}
Alocare dinamică în C++

În C++ alocarea și dezalocarea memoriei sunt mai simple, deoarece aici nu mai avem funcții ci operatori specifici. Pentru a aloca memoria în C++ se folosește operatorul new, iar pentru a elibera memoria folosim operatorul delete.

Alocarea dinamică pentru o singură adresă de memorie

În exemplul de mai jos puteți vedea diferite variante de alocare și dezalocare pentru un singur spațiu de memorie.

#include <iostream>
 
int main()
{
    int* ptr1 = new int; // alocare dinamica fara initializare, compilatorul va atribui o valoare in mod aleator
    int* ptr2 = new int(10); // alocare dinamica cu initializare
 
    std::cout << *ptr1 << '\n';
    std::cout << *ptr2 << '\n';
 
    /*delete ptr1, ptr2; // desi nu da eroare de compilare va elibera doar spatiul pentru ptr1, nu se recomanda aceasta scriere pentru ca va genera memory leak-uri usor*/
 
    delete ptr1;
    delete ptr2;
 
    return 0;
}

Pentru a face o eliberare corectă a memoriei numărul de delete-uri trebuie să fie egal cu numărul de new-uri. Dacă această regulă este respectată atunci nu vor apărea memory leak-uri (scurgeri de memorie).

Alocarea dinamică pentru un bloc de memorie

Dacă intenționăm să alocăm dinamic un vector în C++ vom folosi tot operatorul new, dar puțin diferit.

#include <iostream>
 
int main()
{
    int nrElemente = 5;
    int* vector = new int[nrElemente]; // se folosesc [] pentru a anunta compilatorul ca vrem sa alocam spatiu pentru un bloc de memorie continuu
 
    for (int i = 0; i < nrElemente; i++)
    {
        std::cout << "vector[" << i << "] = ";
        std::cin >> vector[i];
    }
 
    std::cout << "\nElementele vectorului alocat sunt: ";
 
    for (int i = 0; i < nrElemente; i++)
    {
        std::cout << vector[i] << ' ';
    }
 
    delete[] vector; // se folosesc [] pentru a anunta compilatorul ca ne dorim sa eliberam memoria unui bloc contiguu
 
    return 0;
}

În C++ nu există operator pentru realocarea memoriei. Nu este recomandată utilizarea funcției realloc pe un vector care a fost alocat cu operatorul new, deoarece va duce la un comportament nedefinit. Dacă vrem să realocăm vectorul va trebui mai întâi să îl dezalocăm și apoi să îl realocăm cu noua dimensiune.

#include <iostream>
 
int main()
{
    int nrElemente = 6;
    int* vector = new int[nrElemente];
 
    for (int i = 0; i < nrElemente; i++)
    {
        std::cout << "vector[" << i << "] = ";
        std::cin >> vector[i];
    }
 
    std::cout << "\nElementele vectorului alocat sunt: ";
 
    for (int i = 0; i < nrElemente; i++)
    {
        std::cout << vector[i] << ' ';
    }
 
    delete[] vector;
 
    nrElemente = 3;
 
    vector = new int[nrElemente];
 
    std::cout << "\n\n===================================================\n\n";
 
    for (int i = 0; i < nrElemente; i++)
    {
        std::cout << "vector[" << i << "] = ";
        std::cin >> vector[i];
    }
 
    std::cout << "\nElementele vectorului realocat sunt: ";
 
    for (int i = 0; i < nrElemente; i++)
    {
        std::cout << vector[i] << ' ';
    }
 
    std::cout << '\n';
 
    delete[] vector;
 
    return 0;
}

Tipul referință

În C++, referințele reprezintă o extensie esențială pentru gestionarea variabilelor. Practic, o referință este un alias pentru o variabilă deja existentă. Spre deosebire de pointeri, care stochează adresa unei variabile și necesită dereferențiere explicită, referințele acționează direct asupra variabilei asociate, eliminând necesitatea manipulării adreselor.

Odată ce am asociat o referință unei variabile, orice operație efectuată asupra acesteia se reflectă direct asupra variabilei originale. Putem privi referința ca pe o persoană care are două prenume: indiferent de numele folosit, este vorba despre aceeași persoană. Astfel, deși se aseamănă cu pointerii, referințele se disting prin simplitate și prin modul mai natural în care permit accesul la date. Diferențele între referințe și pointeri sunt mai multe la număr însă le vom prezenta pe cele mai importante mai jos.

  1. Referința când este declarată trebuie instant inițializată
  2. După inițializare referința nu mai poate fi schimbată
  3. Referințele nu pot fi nule
  4. Referințele nu trebuie dereferențiate
#include <iostream>
 
int main()
{
    int x = 10;
    int& ref = x;
 
    ref = 16;
 
    std::cout << x << '\n'; // x devine 16 deoarece ref este un alias pentru el
 
    x = 20;
 
    std::cout << ref << '\n'; // ref este 20 datorita faptului ca se refera la x
 
    int y = 0;
    ref = y; // poate parea schimbarea referintei dar in realitate este doar atribuirea valorii 0 lui ref
 
    std::cout << x << '\n'; // x este 0 din motive evidente
 
    return 0;
}

Pentru a înțelge mai bine, în desenul de mai jos se poate observa de fapt cine este ref și că nu face altceva decât să partajeze aceeași zonă de memorie ca și x.

Cu ajutorul acestui tip de date putem să ne facem viața mult mai ușoară, deoarece putem trimite parametrii unei funcții prin referință.

#include <iostream>
 
void alocareVector(int*& v, const int& dim)
{
    v = new int[dim];
}
 
void citireVector(int*& v, const int& dim)
{
    for (int i = 0; i < dim; i++)
    {
        std::cout << "vector[" << i << "] = ";
        std::cin >> v[i];
    }
}
 
void afisareVector(const int* const& v, const int& dim)
{
    std::cout << "Elementele vectorului alocat sunt: ";
 
    for (int i = 0; i < dim; i++)
    {
        std::cout << v[i] << ' ';
    }
 
    std::cout << '\n';
}
 
void dezalocareVector(int*& v)
{
    if (v != nullptr)
    {
        delete[] v;
    }
}
 
int main()
{
    int nrElemente = 5;
    int* vector = nullptr;
 
    alocareVector(vector, nrElemente);
    citireVector(vector, nrElemente);
    afisareVector(vector, nrElemente);
    dezalocareVector(vector);
 
    return 0;
}

Funcțiile folosesc referințe la pointeri (de exemplu, int*& v) pentru a permite modificarea efectivă a pointerilor în funcția apelantă. Astfel, funcțiile pot aloca memorie sau modifica adresele pointerilor direct în cadrul funcției care le-a apelat, fără a returna un nou pointer.

Parametrii transmiși ca referințe constante (de exemplu, const int& dim) sunt utilizați pentru a asigura că valoarea acestora nu este modificată în interiorul funcției, protejând astfel valorile originale. Acest lucru ajută la prevenirea modificărilor accidentale și la creșterea clarității.

Funcția afisareVector folosește referințe constante la pointeri (const int* const& v), pentru a garanta că atât adresa vectorului, cât și conținutul acestuia nu vor fi modificate în timpul afișării, menținând integritatea datelor.

C++ introduce posibilitatea de a inițializa pointerii cu nullptr, care este specific doar pentru acest tip de date. Acesta funcționează similar cu NULL, dar cu un avantaj important: nullptr este un tip de date dedicat pointerilor, ceea ce previne atribuirea sa accidentală altor tipuri de variabile. În C++, NULL este definit doar ca un macro define care reprezintă valoarea 0 și poate fi atribuit chiar și variabilelor care nu sunt de tip pointer, lucru care poate duce la confuzii nedorite.

#include <iostream>
 
int main()
{
    int x = NULL; // valid
    int* ptr = NULL; // valid
 
    ptr = nullptr; // valid
    x = nullptr; // eroare de compilare, x nu este un pointer
 
    return 0;
}

Funcții cu același nume

În C++ avem avantajul de a declara funcții, procedeu cunoscut sub numele de supraîncărcare a funcțiilor, cu același nume dar care să difere prin numărul și/sau tipul parametrilor. Această modalitate de declarare a funcțiilor este cunoscută în Programarea Orientată Obiect sub numele de polimorfism, care în limba greacă înseamnă multe forme. Vom oferi mai multe detalii pe parcursul întregului semestru despre acest principiu al POO.

Să urmărim exemplul de cod de mai jos care ilustrează polimorfismul a patru funcții.

#include <iostream>
 
int suma(int a, int b)
{
    return a + b;
}
 
float suma(float a, float b)
{
    return a + b;
}
 
float suma(float a, int b)
{
    return a + b;
}
 
float suma(int a, float b)
{
    return a + b;
}
 
int main()
{
    std::cout << suma(2, 5) << '\n'; // prima functie denumita suma
    std::cout << suma(2.5f, 5.5f) << '\n'; // a doua functie denumita suma
    std::cout << suma(2.85f, 8) << '\n'; // a treia functie denumita suma
    std::cout << suma(10, 8.5f) << '\n'; // // a patra functie denumita suma
 
    return 0;
}

Trebuie subliniat faptul că tipul de return al unei funcții nu este luat în considerare în contextul polimorfismului prin supraîncărcare. Ceea ce definește polimorfismul în acest caz este exclusiv semnătura funcției, adică numele împreună cu lista și tipurile parametrilor. De aceea, două funcții care diferă doar prin tipul valorii returnate nu sunt considerate supraîncărcări valide. În schimb, diferențele în numărul, tipul sau chiar ordinea parametrilor constituie forme acceptate de polimorfism, așa cum se poate observa în cazul ultimelor două funcții din exemplul de mai sus.

Concluzii

Prin parcurgerea acestui laborator, putem spune cu certitudine că limbajul C++ reprezintă o evoluție firească a limbajului C, adăugând numeroase concepte și mecanisme care ne fac munca de programatori mai simplă și mai expresivă. Am văzut că librăria fstream, alocarea dinamică realizată într-un mod simplificat, introducerea referințelor, posibilitatea de a supraîncărca funcții și suportul pentru Programarea Orientată Obiect diferențiază C++ de C și ne oferă un cadru mai flexibil pentru dezvoltarea aplicațiilor. Totuși, este important să reținem că C++ rămâne compatibil cu C la nivel de bază, ceea ce îl face un limbaj versatil, potrivit atât pentru stilul procedural, cât și pentru cel orientat pe obiecte.

poo-is-ab/laboratoare/02.1759342922.txt.gz · Last modified: 2025/10/01 21:22 by razvan.cristea0106
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0