LABORATORUL 13

Operații de intrare/ieșire în C++. Clase și obiecte.

Durată: 2 ore Limbaj: C++ Precedent: Laboratorul 12 PDF îndrumar EN English version

C++ păstrează sistemul de intrare/ieșire din C, dar adaugă unul propriu, construit pe principiile programării orientate pe obiecte: mai sigur, mai flexibil și extensibil la tipuri definite de utilizator. Această ultimă lucrare face și trecerea de la structuri la clase.

1Obiectivele lucrării

  • Utilizarea streamurilor cin, cout, cerr și clog
  • Formatarea ieșirii cu manipulatori
  • Lucrul cu fișiere prin ifstream și ofstream
  • Definirea unei clase cu date membre și funcții membre
  • Scrierea constructorilor și a destructorului
  • Aplicarea principiului încapsulării

2Ierarhia de clase I/O

Operațiile de intrare/ieșire se realizează cu ajutorul a două ierarhii de clase, declarate în fișierele iostream.h și fstream.h:

ios istream ostream iostream fstreambase ifstream ofstream fstream
Fig. 1 - Ierarhiile de clase folosite în operațiile de intrare/ieșire

3Streamurile predefinite

StreamSemnificațieEchipament implicitEchivalent în C
cinintrare standardtastaturăstdin
coutieșire standardecranstdout
cerrieșire standard pentru eroriecranstderr
clogieșire pentru erori, cu memorie tamponecran-
Operatorii de flux<< este operatorul de inserție (scriere), >> cel de extracție (citire). Ambii sunt operatori supraîncărcați, ceea ce permite înlănțuirea: cout << a << " " << b << endl;
Avantajul față de printfTipul datelor este dedus automat de compilator. Nu există riscul de a folosi un specificator greșit, iar operatorii pot fi extinși pentru clasele proprii, astfel încât cout << obiect să funcționeze natural.

4Manipulatori de format

ManipulatorEfectAntet necesar
endllinie nouă + golirea tamponuluiiostream
setw(n)lățime minimă de n caractereiomanip
setprecision(n)numărul de cifre semnificativeiomanip
fixednotație zecimală fixăiostream
scientificnotație științificăiostream
left / rightalinierea în câmpiostream
setfill(c)caracterul de umplereiomanip
hex / oct / decbaza de numerațieiostream
boolalphaafișează true/false în loc de 1/0iostream
Diferență importantăsetw acționează doar asupra următoarei valori afișate, apoi se resetează. Ceilalți manipulatori (fixed, setprecision, hex) rămân activi până la o nouă schimbare.

5Fișiere în C++

ClasăRolEchivalent C
ifstreamcitire din fișierfopen(..., "r")
ofstreamscriere în fișierfopen(..., "w")
fstreamcitire și scrierefopen(..., "r+")
Închiderea automatăLa ieșirea din domeniul de vizibilitate, destructorul obiectului închide singur fișierul. Spre deosebire de C, uitarea unui fclose nu mai este posibilă - este primul beneficiu concret al abordării orientate pe obiecte.

6Clase și obiecte

O clasă extinde noțiunea de structură: pe lângă date, conține și funcțiile care operează asupra lor. Un obiect este o instanță concretă a unei clase.

structura unei clase
class NumeClasa {
  private:                      // implicit pentru class
    tip date_membre;            // accesibile doar din interiorul clasei

  public:
    NumeClasa();                // constructor implicit
    NumeClasa(parametri);       // constructor cu parametri
    ~NumeClasa();               // destructor

    tip functie_membra(...);    // interfata publica
};
SpecificatorAcces din interiorul claseiDin exteriorDin clase derivate
privatedanunu
protecteddanuda
publicdadada
class vs. struct în C++Singura diferență este accesul implicit: private pentru class, public pentru struct. În rest, o structură din C++ poate avea funcții membre, constructori și destructor.

Constructori și destructor

ElementCând se apeleazăCaracteristici
Constructorla crearea obiectuluiare numele clasei, nu returnează nimic, poate fi supraîncărcat
Constructor de copierela inițializarea dintr-un alt obiectNumeClasa(const NumeClasa &alt)
Destructorla distrugerea obiectului~NumeClasa(), unic, fără parametri

7Simulator: ciclul de viață al unui obiect

Urmăriți momentele exacte în care se apelează constructorul și destructorul, inclusiv pentru obiectele create într-un bloc interior.

Constructor și destructor - ordinea apelurilor
Ordinea distrugeriiObiectele se distrug în ordinea inversă creării. Acest mecanism, numit RAII, garantează eliberarea resurselor (memorie, fișiere, conexiuni) chiar și atunci când funcția se termină pe o cale neașteptată.

8Încapsularea

Datele membre se declară private, iar accesul se face exclusiv prin funcții publice. Avantajul nu este ascunderea în sine, ci controlul: orice modificare poate fi validată.

de ce contează
class Temperatura {
  private:
    double celsius;

  public:
    // fara incapsulare, oricine ar putea scrie t.celsius = -500;
    bool setCelsius(double v) {
        if (v < -273.15) return false;    // validare: zero absolut
        celsius = v;
        return true;
    }
    double getCelsius() const { return celsius; }
    double getFahrenheit() const { return celsius * 9.0 / 5.0 + 32.0; }
};
Funcții constMarcarea unei funcții membre cu const garantează că aceasta nu modifică starea obiectului. Compilatorul verifică promisiunea și semnalează orice încălcare - o formă simplă și eficientă de documentare executabilă.

9Cod sursă

formatare.cpp - manipulatori
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    double pi = 3.14159265358979;

    cout << "Implicit:      " << pi << endl;
    cout << "fixed, 4 zec:  " << fixed << setprecision(4) << pi << endl;
    cout << "scientific:    " << scientific << pi << endl;

    cout << fixed << setprecision(2);

    // tabel aliniat
    cout << "\n" << left  << setw(14) << "Produs"
         << right << setw(8)  << "Buc"
         << setw(10) << "Pret" << endl;
    cout << setfill('-') << setw(32) << "" << setfill(' ') << endl;

    cout << left << setw(14) << "Rezistor"
         << right << setw(8) << 120 << setw(10) << 0.15 << endl;
    cout << left << setw(14) << "Condensator"
         << right << setw(8) << 45  << setw(10) << 0.80 << endl;

    // baze de numeratie
    int v = 255;
    cout << "\n" << dec << v << " = " << hex << v
         << " (hex) = " << oct << v << " (oct)" << dec << endl;

    cout << boolalpha << "\n3 > 5 este " << (3 > 5) << endl;

    return 0;
}
clasa_student.cpp - clasă completă
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

class Student {
  private:
    string nume;
    int    varsta;
    double medie;

  public:
    // constructor implicit
    Student() : nume("necunoscut"), varsta(0), medie(0.0) {
        cout << "  [constructor implicit]" << endl;
    }

    // constructor cu parametri, folosind lista de initializare
    Student(string n, int v, double m) : nume(n), varsta(v), medie(m) {
        cout << "  [constructor pentru " << nume << "]" << endl;
    }

    // constructor de copiere
    Student(const Student &alt)
        : nume(alt.nume), varsta(alt.varsta), medie(alt.medie) {
        cout << "  [copiere pentru " << nume << "]" << endl;
    }

    ~Student() {
        cout << "  [destructor pentru " << nume << "]" << endl;
    }

    // functii de acces, cu validare
    bool setMedie(double m) {
        if (m < 1.0 || m > 10.0) return false;
        medie = m;
        return true;
    }

    string getNume()  const { return nume; }
    double getMedie() const { return medie; }
    bool   promovat() const { return medie >= 5.0; }

    void afiseaza() const {
        cout << left << setw(18) << nume
             << right << setw(4) << varsta << " ani"
             << setw(8) << fixed << setprecision(2) << medie
             << "  " << (promovat() ? "promovat" : "restanta") << endl;
    }
};

int main()
{
    cout << "Creare obiecte:" << endl;
    Student s1("Popescu Ion", 20, 9.15);
    Student s2("Ionescu Maria", 21, 4.30);
    Student s3 = s1;                       // apeleaza constructorul de copiere

    cout << "\nLista:" << endl;
    s1.afiseaza();
    s2.afiseaza();
    s3.afiseaza();

    cout << "\nIncercare de setare a mediei la 15: ";
    cout << (s2.setMedie(15.0) ? "acceptat" : "RESPINS de validare") << endl;

    cout << "\nDistrugere (ordine inversa):" << endl;
    return 0;
}
fisiere.cpp - I/O cu fișiere în C++
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main()
{
    // scriere
    {
        ofstream fout("date_cpp.txt");
        if (!fout) { cerr << "Nu pot scrie!" << endl; return 1; }

        fout << "Popescu Ion 20 9.15" << endl;
        fout << "Ionescu Maria 21 8.70" << endl;
    }   // fisierul se inchide AUTOMAT aici, prin destructor

    // citire
    ifstream fin("date_cpp.txt");
    if (!fin) { cerr << "Nu pot citi!" << endl; return 1; }

    string nume, prenume;
    int varsta;
    double medie;

    cout << "Continutul fisierului:" << endl;
    while (fin >> nume >> prenume >> varsta >> medie) {
        cout << "  " << nume << " " << prenume
             << ", " << varsta << " ani, media " << medie << endl;
    }
    fin.close();

    // citire linie cu linie
    ifstream f2("date_cpp.txt");
    string linie;
    int nr = 0;
    cout << "\nLinie cu linie:" << endl;
    while (getline(f2, linie)) {
        cout << "  " << ++nr << ": " << linie << endl;
    }

    return 0;
}

10Atelier de cod

Casetele acceptă și C++: cout, string, clase cu constructori și destructori. Urmăriți în ieșire momentul exact în care se apelează fiecare.

Ciclul de viață al unui obiect
#include <iostream>
#include <string>
using namespace std;

class Contor {
  private:
    string nume;
    int    valoare;

  public:
    Contor(string n) : nume(n), valoare(0) {
        cout << "  [se creeaza " << nume << "]" << endl;
    }
    ~Contor() {
        cout << "  [se distruge " << nume << ", ajuns la " << valoare << "]" << endl;
    }
    void creste() { valoare++; }
    int  citeste() const { return valoare; }
};

int main()
{
    cout << "Intram in main:" << endl;
    Contor a("a");

    {
        cout << "Intram intr-un bloc interior:" << endl;
        Contor b("b");
        b.creste(); b.creste();
        cout << "  b are valoarea " << b.citeste() << endl;
    }

    a.creste();
    cout << "a are valoarea " << a.citeste() << endl;
    cout << "Iesim din main:" << endl;
    return 0;
}
De încercatObservați că b este distrus la acolada care închide blocul interior, nu la sfârșitul lui main. Durata de viață a unui obiect este legată de domeniul în care a fost declarat.
Exercițiu - completați clasa
#include <iostream>
#include <iomanip>
using namespace std;

class Dreptunghi {
  private:
    double lungime, latime;

  public:
    Dreptunghi(double L, double l) : lungime(L), latime(l) {}

    /* Adaugati trei metode:
         double arie() const        -> lungime * latime
         double perimetru() const   -> 2 * (lungime + latime)
         bool   patrat() const      -> true daca laturile sunt egale  */
};

int main()
{
    Dreptunghi d1(4.0, 2.5);
    Dreptunghi d2(3.0, 3.0);

    cout << fixed << setprecision(2);
    cout << "d1: arie = " << d1.arie() << ", perimetru = " << d1.perimetru() << endl;
    cout << "d2: arie = " << d2.arie() << ", perimetru = " << d2.perimetru() << endl;
    cout << boolalpha << "d2 este patrat? " << d2.patrat() << endl;
    return 0;
}

11Sarcini de lucru

  • Afișați un tabel aliniat folosind setw, left, right și setfill.
  • Verificați experimental că setw acționează doar asupra următoarei valori afișate.
  • Definiți o clasă Punct cu coordonate private și metode pentru distanța până la origine.
  • Adăugați clasei un constructor implicit, unul cu parametri și un destructor care afișează mesaje.
  • Urmăriți în consolă ordinea apelurilor pentru mai multe obiecte, inclusiv într-un bloc interior.
  • Implementați validarea datelor în funcțiile de tip set și testați valori nepermise.
  • Scrieți și citiți date dintr-un fișier folosind ofstream și ifstream.
  • Comparați varianta C++ cu cea în C a aceluiași program și notați diferențele de lizibilitate.

12Aplicație de aprofundare

Proiect finalImplementați o clasă Vector care gestionează un tablou alocat dinamic: constructor cu dimensiune, constructor de copiere, destructor care eliberează memoria, metode de adăugare cu redimensionare automată, acces cu verificarea limitelor și supraîncărcarea operatorului << pentru afișare directă cu cout. Adăugați salvarea și încărcarea din fișier. Verificați cu mesaje în constructor și destructor că nu apar pierderi de memorie.

13Întrebări de verificare

14Resurse