>> Inapoi <<


                                 --------
                                  Curs 4
                                 --------
                                 
             ----------------------------------------------------
              Siruri (de caractere). Pointeri si siruri dinamice
             ----------------------------------------------------
             

-----------------
 Siruri si clase
-----------------

  Mai intai prezentam siruri de clase. Dorim ca fiecare element sa contina
articole de tipuri diferite.

   Exemplu:
    Avem acel exemplu cu bani U.S.D. Vom scrie un program C++ (de fapt un
fisier de aplicatie, adica doar functia main()) care citeste o lista (sir a
carui tip de baza este clasa Bani) si calculeaza cat de mult difera fiecare
suma de cel mai mare element.

// Fisierul "fis4-1.cpp".
// Acest program contine un vector de elemente de tip "class Bani"
// definita in programul "curs3-5.h" si "curs3-5.cpp". Prezentam
// doar partea care este in plus.

// ----------------------------------------------------------------
// Fisierul "curs4-1.h" contine in plus prototipul:

    friend int operator <(const Bani& suma1, const Bani& suma2);
    // Preconditie: "suma1" si "suma2" au deja valori
    // Returneaza true daca "suma1" este mai mica decat "suma2";
    // altfel false.

// ----------------------------------------------------------------
// Fisierul "curs4-1.cpp" contine in plus definitia:

int operator <(const Bani& suma1, const Bani& suma2)
 {
  return (suma1.tot_centi < suma2.tot_centi);
 }

// ----------------------------------------------------------------
// Fisierul "main4-1.cpp" este:
// Fisier de aplicatii care contine functia main().

#include 
#include "curs4-1.h"

int main()
 {
  Bani suma[5], max;
  int i;

  cout << "Dati cele 5 sume de bani:\n";
  cin >> suma[0];
  max = suma[0];

  for (i = 1; i < 5; i++)
   {
    cin >> suma[i];
    if (max < suma[i]) max = suma[i];
   }

  Bani diferenta[5];
  for (i = 0; i < 5; i++)
    diferenta[i] = max - suma[i];

  cout << "Cea mai mare suma este " << max << endl;
  cout << "Sumele si diferentele fata de cea mai mare suma sunt:\n";
  for (i = 0; i < 5; i++)
    cout << suma[i] << " difera prin " << diferenta[i] << endl;

  return 0;
 }

  In continuare studiem posibilitatea definirii intr-o clasa a unui sir ca o
variabila membru.

   Exemplu:
  Dorim sa scriem un program C++ care sa memoreze durata probelor unui
innotator (alergator) pe distante diferite. Dorim sa obtinem timpul cel mai
bun.

    class Proba
     {
      private:
       double durata[10];
       int distanta;
      public:
       void citire();
       void afisare();
       ...
     };
     
  Declaram o variabila ce apartine clasei Proba.
  
   Proba timp;
   
  Mai intai citim distanta, apoi cele 10 durate. La sfarsit, proba care are
timpul cel mai mic.

// Fisierul "curs4-2.h"
// Header ce descrie clasa Proba, prototipurile functiilor si operatorilor.
// Acest header se mai numeste si fisier de interfata.

#include 
#include 
#define NMAX 10

class Proba
 {
  public:
    Proba(int distanta1, int durata1[]);
    Proba(int distanta1);
    Proba();
    friend int min(const Proba& obiect1);
    void citire();
    void afisare();
    void afisare_min(int i);
  private:
    int distanta;
    double durata[NMAX];
 };
 
// Fisierul "curs4-2.cpp"
// Fisier de implementare care contine definitiile tuturor functiilor
// si operatorilor de supraincarcare.

#include "curs4-2.h"

Proba :: Proba(int distanta1, int durata1[])
 {
  distanta = distanta1;
  int i;
  for (i = 0; i < NMAX; i++)
   durata[i] = durata1[i];
 }

Proba :: Proba(int distanta1)
 {
  distanta = distanta1;
  int i;
  for (i = 0; i < NMAX; i++)
   durata[i] = 0;
 }
Proba :: Proba()
 {
  distanta = 0;
  int i;
  for (i = 0; i < NMAX; i++)
   durata[i] = 0;
 }

int min(const Proba& obiect1)
 {
  int min = obiect1.durata[0];
  int index = 0;
  // "index" reprezinta valoarea indicelui pentru care se realizeaza
  // maximul din vectorul "obiect1.durata"
  int i;
  for (i = 1; i < NMAX; i++)
   if (min > obiect1.durata[i])
    {
     min = obiect1.durata[i];
     index = i;
    }
  return index;
 }

void Proba :: citire()
 {
  cout << "Dati distanta ";
  cin >> distanta;
  int i;
  cout << "Dati cei " << NMAX << " timpi:\n";
  for (i = 0; i < NMAX; i++)
   {
    cout << "durata[" << i << "]=";
    cin >> durata[i];
   }
 }

void Proba :: afisare()
 {
  cout << "Distanta este " << distanta << endl;
  int i;
  cout << "Cei " << NMAX << " timpi sunt:\n";
  for (i = 0; i < NMAX; i++)
    cout << "durata[" << i << "]=" << durata[i] << endl;
 }

void Proba :: afisare_min(int i)
 {
  cout << "minimul este " << durata[i] << " si se realizeaza pentru "
       << "indexul " << i << endl;
 }
 
// Fisierul "main4-4.cpp"
// Program care demostreaza folosirea clasei StringVar

#include 
#include "curs4-4.h"

void conversatie(int lung_max);

int main()
 {
  conversatie(30);
  cout << "Sfarsitul demonstratiei.\n";
  return 0;
 }

void conversatie(int lung_max)
 {
  StringVar nume1(lung_max), nume2("Constantinescu");

  cout << "Care este numele dvs. ?\n";
  nume1.citeste_linie(cin);
  cout << "Numele fiului iubit este " << nume2 << endl;
  cout << "Numele dvs. este " << nume1 << endl;
 }

  string1 are 14 caractere si string2 are loc pentru 11+ sfarsitul '\0'. In
unele implementari C++ aceasta va implica plasarea ultimelor 3 caractere
undeva in memorie.

  Pentru evitarea acestor probleme, vom defini propria noastra clasa String ce
va contine doua variabile membre:

1. Un sir de caractere numit caracter (nu plasam '\0' in sirul de caractere)
2. O variabila de tip int care memoreaza numarul de caractere memorate in
sirul de caractere. Aceasta variabila va fi numita lungime_curenta.
  
  Clasa String va contine operatiile uzuale pentru sirurile de caractere 
  -concatenarea lor- prin supraincarcarea operatorului +
  
   Exemplu:
   
// Fisierul "curs4-3.h"
// Header ce descrie clasa String, prototipurile functiilor si operatorilor.
// Acest header se mai numeste si fisier de interfata.

#include 
const int MAX_STRING_LENGTH = 100;

class String
 {
  public:
    friend String operator +(const String& sir1, const String& sir2);
    // Returneaza concatenarea dintre "sir1" si "sir2". Daca
    // concatenarea dintre "sir1" si "sir2" depaseste
    // MAX_STRING_LENGTH, atunci se truncheaza ultimele caractere.

    friend void copie_bucata(String& destinatie, const String& sursa,
		int numar);
    // Copie primele "numar" caractere ale sirului "sursa" catre
    // sirul "destinatie". Daca "numar" > strlen("sursa"), atunci
    // se copie doar caracterele din "sursa".
    // Preconditie: "numar" >= 0.

    friend int operator ==(const String& sir1, const String& sir2);
    // Returneaza TRUE daca "sir1" coincide cu "sir2".

    String();
    // Initializeaza obiectul cu sirul vid.

    String(const char a[]);
    // Preconditie: Sirul "a" contine un sir terminat prin '\0'.
    // Actiune: Initializeaza obiectul cu sirul "a".
    // Daca strlen("a") > MAX_STRING_LENGTH, atunci numai primele
    // MAX_STRING_LENGTH caractere din "a" sunt folosite la
    // valoarea obiectului.

    int length() const;
    // Returneaza lungimea obiectului apelat.

    char un_caracter(int pozitie) const;
    // Preconditie: 0<=n< numarul de caractere ale obiectului apelat.
    // Returneaza caracterul de pe pozitia n (primul caracter este 0).

    void seteaza_caracter(int pozitie, char noul_caracter);
    // Preconditie: 0<=n<= numarul de caractere ale obiectului apelat.
    // Actiune: Schimba caracterul de la pozitia n cu noul_caracter.

    friend istream& operator >>(istream& intrare, String& sir);
    // Supraincarca operatorul >> pentru a putea citi cuvinte.
    // Preconditie: daca "intrare" este un flux fisier de intrare,
    // atunci "intrare" este deja conectat la un fisier.
    // Actiune: se citeste primul sir de caractere diferit de spatii.

    friend ostream& operator <<(ostream& iesire, const String& sir);
    // Supraincarca operatorul << pentru a afisa valori de tip String.
    // Preconditie: daca "intrare" este un flux fisier de intrare,
    // atunci "intrare" este deja conectat la un fisier.
    // Actiune: se citeste primul sir de caractere diferit de spatii.

    void citeste_linia(istream& intrare);
    // Preconditie: daca "intrare" este un flux fisier de intrare,
    // atunci "intrare" este deja conectat la un fisier.
    // Postconditie: se citeste o linie de caractere pana la
    // primul '\n'.

  private:
    char caracter[MAX_STRING_LENGTH];
    int lungime;
 };

// Fisierul "curs4-3.cpp"
// Fisier de implementare care contine definitiile tuturor functiilor
// si operatorilor de supraincarcare.

#include "curs4-3.h"
#include 
#include 
#include 
const int FALSE = 0;
const int TRUE = 1;

String operator +(const String& sir1, const String& sir2)
 {
  String temp;

  temp.lungime = sir1.lungime + sir2.lungime;
  if (temp.lungime > MAX_STRING_LENGTH)
    temp.lungime = MAX_STRING_LENGTH;

  int i;
  for (i = 0; i < sir1.lungime; i++)
    temp.caracter[i] = sir1.caracter[i];
  int offset = i;

  for (i = offset; i < temp.lungime; i++)
    temp.caracter[i] = sir2.caracter[i - offset];

  return temp;
 }

// foloseste stdlib.h
void copie_bucata(String& destinatie, const String& sursa, int numar)
 {
  if (numar < 0)
   {
    cout << "Eroare: Apel ilegal al lui copie_bucata.\n";
    exit(1);
   }

  if (numar > sursa.lungime)
    numar = sursa.lungime;

  for (int i = 0; i < numar; i++)
    destinatie.caracter[i] = sursa.caracter[i];

  destinatie.lungime = numar;
 }

int operator ==(const String& sir1, const String& sir2)
 {
  if (sir1.lungime != sir2.lungime)
   {
    return FALSE;
   }
  else
   {
    int i = 0, nepotrivire = FALSE;
    while ((i < sir1.lungime) && (! nepotrivire))
     {
      if (sir1.caracter[i] != sir2.caracter[i])
	nepotrivire = TRUE;
      else
	i++;
     }
    return (! nepotrivire);
   }
 }

String :: String()
 {
  caracter[0] = '\0';
  lungime = 0;
 }

String :: String(const char a[])
 {
  int i;
  for (i = 0; (i < MAX_STRING_LENGTH && a[i] != '\0'); i++)
    caracter[i] = a[i];

  lungime = i;
 }

int String :: length() const
 {
  return lungime;
 }

// foloseste stdlib.h
char String :: un_caracter(int pozitie) const
 {
  if ((pozitie >= lungime) || (pozitie < 0))
   {
    cout << "Eroare: Pozitie ilegala in \"un_caracter\".\n";
    exit(1);
   }
  return (caracter[pozitie]);
 }

void String :: seteaza_caracter(int pozitie, char noul_caracter)
 {
  if ((pozitie >= lungime) || (pozitie < 0) ||
      (pozitie >= MAX_STRING_LENGTH))
   {
    cout << "Eroare: Pozitie ilegala in \"un_caracter\".\n";
    exit(1);
   }
  caracter[pozitie] = noul_caracter;
  if (pozitie == lungime)
    lungime++;
 }

// foloseste iostream.h si ctype.h
istream& operator >>(istream& intrare, String& sir)
 {
  char next;
  cin >> next; // Atentie: Aceasta sare peste spatiile initiale
  for (int i = 0; (! isspace(next)) && (i < MAX_STRING_LENGTH); i++)
   {
    sir.caracter[i] = next;
    intrare.get(next);
   }
  sir.lungime = i;

  // daca cuvantul nu incape, atunci elimina partea ce depaseste
  while (! isspace(next))
    intrare.get(next);

  return intrare;
 }

// foloseste iostream.h
ostream& operator <<(ostream& iesire, const String& sir)
 {
  for (int i = 0; i < sir.lungime; i++)
    iesire << sir.caracter[i];

  return iesire;
 }

void String :: citeste_linia(istream& intrare)
 {
  char next;
  cin >> next; // Atentie: Aceasta sare peste spatiile initiale
  for (int i = 0; (next != '\n') && (i < MAX_STRING_LENGTH); i++)
   {
    caracter[i] = next;
    intrare.get(next);
   }
  caracter[i] = '\0';
  lungime = i;
 }
 
// Fisierul "main4-3.cpp"
// Fisier de aplicatii care contine functia main().

#include 
#include "curs4-3.h"

int main()
 {
  String prenume, nume, nume_inregistrat;

  cout << "Dati prenumele si numele:\n";
  cin >> prenume >> nume;
  nume_inregistrat = prenume + ", " + nume;

  cout << "Numele dvs este: ";
  cout << nume_inregistrat << endl;

  cout << "Numele dvs pe litere este: ";
  for (int i = 0; i < nume.length(); i++)
    cout << nume.un_caracter(i) << " ";
  cout << endl;

  String motto("Noi traim bine in tara.");
  cout << "Motto-ul nostru este: ";
  cout << motto << endl;

  cout << "Sugerati alt motto (scris pe o linie):\n";
  motto.citeste_linia(cin);
  cout << "Noul nostru motto va fi:\n";
  cout << motto << endl;

  return 0;
 }
 

--------------------------
 Clase si siruri dinamice
--------------------------

  Am vazut in exemplul precedent o posibilitate de a defini vectori pentru
simularea sirurilor de caractere. Acea definitie a clasei necesita o
dimensiune maxima apriorica sirului de caractere. Vom defini o clasa numita
StringVar, a carei obiecte sunt variabile sir. Un obiect al acestei clase va
fi implementat folosind un vector dinamic a carui dimensiune este determinata
cand programul este pus in executie.
  Deci obiectul tipului StringVar va avea toate avantajele sirurilor dinamice
plus trasaturi suplimentare.

   Exemplu:
// Fisierul "curs4-4.h"
// Header ce descrie clasa StringVar, prototipurile functiilor si operatorilor.
// Acest header se mai numeste si fisier de interfata.
// Un obiect al acestei clase va fi definit astfel
//   StringVar obiect(lungime)   si nu obiect[lungime]

#include 
const int MAX_STRING_LENGTH = 100;

class StringVar
 {
  public:
    StringVar(int lungime);
    // Initializeaza obiectul care poate avea lungime maxima "lungime".
    // Seteaza valoarea obiectului cu sirul vid.

    StringVar();
    // Initializeaza obiectul care poate avea lungime maxima 255.
    // Seteaza valoarea obiectului cu sirul vid.

    StringVar(const char a[]);
    // Preconditie: Sirul a contine caractere terminate cu '\0'.
    // Initializeaza obiectul cu valoarea lui "a" si cu lungimea
    // maxima egala cu strlen("a").

    StringVar(const StringVar& obiect_sir);
    // Constructorul de copiere

    ~StringVar();
    // Returneaza memoria dinamica folosita de obiect in heap.

    int length() const;
    // Returneaza lungimea obiectului apelat.

    void citeste_linie(istream& intrare);
    // Preconditie: daca "intrare" este un flux fisier de intrare,
    // atunci "intrare" este deja conectat la un fisier.
    // Actiune: se citesc caracterele pana la primul '\n' intalnit.
    // Daca obiectul apelat nu are suficient spatiu de memorie pentru
    // toate caracterele, atunci se trunchiaza.

    friend ostream& operator <<(ostream& iesire, const StringVar& sir);
    // Supraincarca operatorul << pentru a afisa valori de tip StringVar.
    // Preconditie: daca "intrare" este un flux fisier de intrare,
    // atunci "intrare" este deja conectat la un fisier.
    // Actiune: se citeste primul sir de caractere diferit de spatii.

  private:
    char *valoare;  // pointer catre sirul dinamic care tine valoare sirului.
    int lungime_maxima;
  };

// Fisierul "curs4-4.cpp"
// Fisier de implementare care contine definitiile tuturor functiilor
// si operatorilor de supraincarcare.

#include 
#include 
#include 
#include 
#include "curs4-4.h"

// foloseste stddef.h si stdlib.h
StringVar :: StringVar(int lungime)
 {
  lungime_maxima = lungime;
  valoare = new char[lungime_maxima + 1]; // + 1 este pentru '\0'
  if (valoare == NULL)
   {
    cout << "Eroare: Insuficienta memorie.\n";
    exit(1);
   }
  valoare[0] = '\0';
 }

// foloseste stddef.h si stdlib.h
StringVar :: StringVar()
 {
  lungime_maxima = 255;
  valoare = new char[lungime_maxima + 1]; // + 1 este pentru '\0'
  if (valoare == NULL)
   {
    cout << "Eroare: Insuficienta memorie.\n";
    exit(1);
   }
  valoare[0] = '\0';
 }

// foloseste string.h, stddef.h si stdlib.h
StringVar :: StringVar(const char a[])
 {
  lungime_maxima = strlen(a);
  valoare = new char[lungime_maxima + 1]; // + 1 este pentru '\0'
  if (valoare == NULL)
   {
    cout << "Eroare: Insuficienta memorie.\n";
    exit(1);
   }
  strcpy(valoare, a);
 }

// foloseste string.h, stddef.h si stdlib.h
StringVar :: StringVar(const StringVar& obiect_sir)
 {
  lungime_maxima = obiect_sir.length();
  valoare = new char[lungime_maxima + 1]; // + 1 este pentru '\0'
  if (valoare == NULL)
   {
    cout << "Eroare: Insuficienta memorie.\n";
    exit(1);
   }
  strcpy(valoare, obiect_sir.valoare);
 }

StringVar :: ~StringVar()
 {
  delete [] valoare;
 }

// foloseste string.h
int StringVar :: length() const
 {
  return strlen(valoare);
 }

// foloseste iostream.h
void StringVar :: citeste_linie(istream& intrare)
 {
  intrare.getline(valoare, lungime_maxima + 1);
 }

// foloseste iostream.h
ostream& operator <<(ostream& iesire, const StringVar& sir)
 {
  iesire << sir.valoare;
  return iesire;
 }

// Fisierul "main4-4.cpp"
// Program care demostreaza folosirea clasei StringVar

#include 
#include "curs4-4.h"

void conversatie(int lung_max);

int main()
 {
  conversatie(30);
  cout << "Sfarsitul demonstratiei.\n";
  return 0;
 }

void conversatie(int lung_max)
 {
  StringVar nume1(lung_max), nume2("Constantinescu");

  cout << "Care este numele dvs. ?\n";
  nume1.citeste_linie(cin);
  cout << "Numele fiului iubit este " << nume2 << endl;
  cout << "Numele dvs. este " << nume1 << endl;
 }
 

 -------------
  Destructori
 -------------
 
   Un destructor este o functie membru a unei clase care este apelat automat
cand un obiect al clasei nu mai este in domeniu. Asta insemna ca daca un
obiect al clasei este o variabila locala a unei functii atunci destructorul
este automat apelat ca o ultima actiune inaintea sfarsitului apelului
functiei. 
  Destructorii sunt folositi pentru a elimina orice variabila dinamica care a
fost creata de catre obiect astfel incat memoria ocupata de aceste variabile
dinamice se returneaza catre heap. Numele unui destructor este format din ~
urmata de numele clasei.
  

--------------------------
 Constructorul de copiere
--------------------------

  Cand un parametru al unui apel prin valoare este de tip pointer comportarea
acestuia poate crea probleme.
  Un constructor de copiere este un constructor care are un parametru ce este
de acelasi tip ca si clasa. Acest paramentru trebuie sa fie parametrul apel
prin referinta (normal acesta este precedat de modificatorul const).
  Constructorul de copiere pentru o clasa este apelat automat cand o functie
returneaza o valoare de tipul clasei. Constructorul de copiere este de asemeni
apelat automat cand un argument este parametru apel prin valoare al unui tip
ce clasa. Un constructor de copiere poate fi de asemeni folosit in acelasi mod
ca alti construtori.
  Orice clasa care foloseste pointeri si operatorul new ar trebui sa aiba un
constructor de copiere.

   Exemplu:
    void afisare (StringVar sir)
     { 
      cout << "Sirul este:" << sir << endl;
     }
     
  Consideram codul, care include un apel al functiei de mai sus:
  
   StringVar mesaj("Ce mai faci?");
   afisare(mesaj);
   cout << "Dupa apel:" << mesaj << endl;
   
I. Presupunem ca nu avem constructori de copiere. Se observa imediat ca dupa
apelul functiei afisare se apeleaza automat destructorul clasei care va
returna memoria folosita de sir folosita in heap. Dar sir si mesaj sunt
variabile pointer care au aceeasi adresa, deci practic dupa apel variabila
mesaj nu va mai fi accesibila.
II.Presupunem ca avem constructor de copiere. Atunci in momentul apelului
    afisare(mesaj);
   constructorul de copiere este automat apelat si se va crea o copie a
argumentului de apel astfel incat parametrul formal sa va unifica cu copia
parametrului actual.
  In cazul exemplului nostru, modificarea variabilei sir nu va implica si
modificarea variabilei mesaj. Cu alte cuvinte, variabila mesaj va avea aceeasi
valoare.


------------------------------------------
 Supraincarcarea operatorului de asignare
------------------------------------------

  Exemplu:
   StringVar sir1(10),sir2(10);
   
   sir1=sir2;
   
  De obicei, versiunea predefinita a intructiunii de asignare (a doua obiecte)
va copia valorile variabilelor membre ale lui sir2 in variabilele membre ale
lui sir1. Aceasta poate cauza probleme, deoarece variabila membru valoare este
un pointer. Deci sir1.valoare si sir2.valoare puncteaza la aceleasi adrese de
memorie. 
  Modificarea lui sir1 (sau a lui sir2) va cauza si modificarea (fara voie) a
lui sir2 (sau a lui sir1).
  Asadar vom supraincarca operatorul de asignare. Acesta nu se poate
supraincarca ca ceilalti operatori, cum ar fi << si + (= este de fapt o
instructiune de asignare) cand supraincarcam operatorul de asignare acesta
trebuie sa fie un membru al clasei, nu poate fi friend al clasei.
  Vom adauga in sectiunea public a clasei:
  
   void operator = (const StringVar & parte_dreapta);
   
  Deci un apel de genul (instructiune)
   sir1 = sir2;
  va insemna ca sir1 este obiectul apelat si sir2 este argumentul operatorului
membru = .
  Definitia operatorului poate fi:
   StringVar::operator = ( const StringVar& parte_dreapta )
    {
     int noua_lungime = strlen(parte_dreapta.valoare);
     if (noua_lungime > lungime_maxima)
      {
       delete [] valoare;
       lungime_maxima=noua_lungime;
       valoare = new char[lungime_maxima+1];
       if ( valoare == NULL )
        {
         cout << "Eroare: Memorie insuficienta\n";
         exit(1);
        }
      }
     for(int i=0;i