>> Inapoi <<

                               
                               
                               
                              --------
                               Curs 1
                              --------
                              

Bibliografie: 
  Walter Sawitch - Problem Solving with C++. 
                   Addison Wesley Publishing Company, 1996
  Liviu Negrescu - Limbajele C si C++ pentru incepatori, Vol.II,
                   editura MicroInformatica, Cluj-Napoca, 1996
Referinte:
	      http://www.cs.tuiasi.ro/library
              http://www.infoiasi.ro/~stefan

-----------
Introducere
-----------
 Un prim exemplu de program C++:

    #include 

    void main
     {
      int a;

      cout << "Dati un numar\n";
      cin >> a;
      cout << "Ati tastat numarul " << a << endl;
     }
-----------
Observatii:  << este operatorul de insertie
-----------  >> este operatorul de extractie
                "\n" este echivalent cu endl
                cout este "console out"
                cin este "console in"

Afisarea numerelor double (cu format) se face astfel:

    #include 

    void main()
     {
      double b;

      cout << "Dati un numar real:" << endl;
      cin >> b;

      cout.setf(ios::fixed);
      cout.setf(ios::showpoint);
      cout.precision(2);

      cout << b;
     }
-----------
Observatie. cout.precision(2) face ca numarul sa fie afisat cu 2 zecimale
-----------
Daca apoi, se doreste afisarea a trei zecimale, se face doar setarea  
cout.precision(3); In general, cout afiseaza un format asemanator cu cel de 
intrare.


--------------------------
Supraincarcarea functiilor   (caz particular de polimorfism)
--------------------------
--------
Exemplu:
--------
    #include 

    double media(double n1, double n2)
     {
      return ((n1+n2)/2);
     }

    double media(double n1, double n2, double n3)
     {
      return ((n1+n2+n3)/3.0);
     }

    int main()
     {
      cout << "Media dintre 6.0, 7.5 si 8.75 este "
           << media(6.0,7.5,8.75) << endl;
      cout << "Media dintre 8.5 si 10 este "
           << media(8.5,10.0);
      return 0;
     }

Deci avem 2 functii ave cu numar diferit de parametri. In acest caz 
compilatorul o va alege pe cea ce are numarul corect de parametri.


------------------------------------
Supraincarcarea numelui unei functii
------------------------------------
Inseamna definirea a doua sau mai multe functii cu acelasi nume. Cand 
supraincarcarea numelui unei functii trebuie sa aiba numar diferit de 
parametri formali sau anumiti parametri formali de tipuri diferite. Cand avem 
un apel de functie compilatorul foloseste definitia functiei a carei numar si 
tipuri de parametri formali se potriveste cu argumentele din apelul functiei.

Folosirea aceluiasi nume pentru a desemna lucruri diferite se numeste 
polimorfism (termen derivat din cuvinte grecesti ce inseamna "multe forme"). 
Supraincarcarea este un exemplu de polimorfism.

---------------------------------
Exemplu (special de ambiguitate):
---------------------------------
   void f(int a) { cout << a; }
   void f(float a) { cout << a; }

Avem 2 cazuri atunci cand apelam una din functii folosind constante:
  I. daca apelam functia f cu o constanta de tip intreg atunci compilatorul va 
     folosi f(int a) ceea ce este corect;
 II. daca apelam functia f cu o constanta de tip float atunci compilatorul va 
     semanala eroarea "Ambiguity between f(int) and f(float)"

In aceste conditii daca in functia main (sa zicem) apelam:
   f(1.2) - "Ambiguity between f(int) and f(float)"
   f(1) - se executa f(int a)
   f(a) - unde int a=2; -> se executa f(int a)
   f(x) - unde float x=2.0; -> se executa f(float a)


------------------------------
Apeluri ale functiilor din C++
------------------------------
Se stie ca in C exista 2 tipuri de apeluri ale functiilor:
   - prin valoare (se transmite valoarea)
   - prin referinta (se transmite adresa)
----------------------------------------
Exemplu (interschimbarea a doi intregi):
----------------------------------------
   #include 

   void schimba(int& a, int& b)
    {
    // Preconditie: parametri formali se numesc "parametri adresa".
    // Postconditie: valorile celor doi intregi de la aceste adrese sunt 
    // intershimbate.
    // Mecanismul acesta de apel prin referinta (adresa) este nou si specific 
    // limbajului C++. "Legaturile de adresa" sunt facute de
    // catre compilator si sunt transparente programatorului. Ideea este ca se 
    // aloca spatiu de memorie pentru parametri adresa 
    // "a" si "b" cu specificarea ca inainte de parasirea functiei, valorile 
    // de la adresele parametrilor actuali (de exemplu "i" si "j") sunt
    // actualizate cu valorile de la adresele lui "a", respectiv "b". Pe durata 
    // executiei functiei, parametrii actuali sunt inaccesibili
    // in cazul nostru, variabilele "i" si "j". Se mai spune ca variabilele 
    // actuale sunt "trimise cu totul" (in sensul ca le stiu adresa lor).

    int aux;
    aux = a;
    a = b;
    b = aux;
   }

  void schimba(int *adresa1, int *adresa2)
   {
    // Preconditie: primesc ca parametri adresele celor doua variabile
    // Postconditie: valorile celor doi intregi de la aceste adrese sunt 
    // intershimbate.
    // Mecanismul acesta de apel prin referinta (adresa) este vechi si 
    // specific limbajului C. "Legaturile de adresa" trebuie facute de
    // catre programator.
    int aux;
    aux = *adresa1;
    *adresa1 = *adresa2;
    *adresa2 = aux;
   }

  void main()
   {
    int i = 2, j = 3;
    cout << "i = " << i << "; j = " << j << endl;
    schimba(i, j);
    cout << "i = " << i << "; j = " << j << endl;
    schimba(&i, &j);
    cout << "i = " << i << "; j = " << j << endl;
   }


-------------------------------------------------
 Fluxuri de I/O (intrare/iesire) - Fisiere de I/O
-------------------------------------------------
Un stream (curent,curs) este un flux de caractere (sau alte tipuri de date). 
Daca fluxul este in program, atunci se cheama flux de intrare altfel este flux 
de iesire. In C++, un flux este un tip special de variabile cunoscut ca obiect. 
Tipul pentru variabile de flux-fisiere de intrare este numit ifstream (input 
file stream), iar pentru iesire ofstream (output file stream).

--------
Exemplu:
--------
    ifstream in_stream;
    ofstream out_stream;
  
Aceste doua clase sunt definite in fisierul header , fisier ce 
trebuie inclus cu #include . Variabilele de flux trebuie declarate, 
adica conectate la un fisier. Asta inseamna deschiderea fisierului.
  
   in_stream.open("fis.dat");
   
Putem extrage acum din fisierul "infile.dat" (citim doi intregi):
 
   int a,b;
   in_stream >> a >> b;
   
La fel, putem face deschiderea in acces de scriere:
   
   ofstream out_stream;
   out_stream.open("outfile.dat");
   
Putem scrie acum in fisierul "outfile.dat":
 
   out_stream << "a=" << a << "b=" << b;
   
Fiecare fisier de intrare sau iesire folosit de programul dumneavoastra doua 
nume. Numele fisierului extern este numele real al fisierului, dar acesta este 
folosit doar in apelul de deschidere a fisierului, care conecteaza fisierul 
catre un flux. Inchiderea unui fisier se face astfel:
  
   in_stream.close();
   out_stream.close();
   
unde in_stream si out_stream sunt obiecte din clasele ifstream si ofstream, "." 
este operatorul punct, iar close() este o functie membru din aceste clase.
--------
Exemplu:
--------
Clasa ifstream are o functie membru numita open si clasa ofstream are de 
asemeni o functie membru numita open. Clasa ofstream are de asemeni o functie 
membru numita precision. (dar clasa ifstream nu are nici o functie membru 
numita precision).

In general, un obiect este o variabila care are functii asociate. Aceste functii 
se numesc functii membru. O clasa este un tip a carui variabile sunt obiecte. 
Tipul obiectului determina ce functii membru are obiectul. Apelarea unei functii 
membru pentru un obiect se face folosind operatorul "." Sintaxa generala este:

    obiect_apelat.nume_functie_membru(lista_argumente)
   
Un alt caz de polimorfism este folosirea aceluiasi nume pentru functii membru 
din clase diferite, cum este cazul lui open. Exista o functie membru fail care 
testeaza daca o operatie de flux a esuat sau nu. Aceasta functie membru este 
valabila pentru ifstream si ofstream.
--------
Exemplu:
--------
    in_stream.open("fisier.dat");
    if (in_stream.fail())
     {
      cout << "Fisier de intrare deschis gresit" << endl;
      exit(1);
     }
-----------
Observatie: Functia exit este caracteristica limbajului C si este definita in 
----------- fisierul antet .
#include 
#include 
#include 

int main()
 {
  // Acest program citeste doua nume de fisiere unul de intrare, unul de iesire. 
  // Apoi citim din fisierul de intrare trei numere intregi, 
  // apoi in fisierul de iesire scriem suma lor.
  char fis_in[16], fis_out[16];
  ifstream in_stream;
  ofstream out_stream;
  cout << "Dati numele fisierului de intrare.\n";
  cin >> fis_in;
  in_stream.open(fis_in);
  if (in_stream.fail())
   {
    cout << "Eroare la deschidere fisier de intrare.\n";
    exit(1);
   }
  cout << "Dati numele fisierului de iesire.\n";
  cin >> fis_out;
  out_stream.open(fis_out);
  if (out_stream.fail())
   {
    cout << "Eroare la deschidere fisier de iesire.\n";
    exit(1);
   }
  int a, b, c;
  in_stream >> a >> b >> c;
  out_stream << "Suma celor 3 numere este " << a + b + c << endl;
  in_stream.close();
  out_stream.close();
  return 0;
 }

Am vazut ca formatarea afisarii unui double se face cu:
   	
   	out_stream.setf(ios::fixed);
   	out_stream.setf(ios::showpoint);
   	out_stream.precision(2);
   	
Functia membru setf (set flag) seteaza un "stegulet". Exista 6 tipuri de 
flag-uri ce pot fi setate (sau nesetate):
   
 -------------------------------------------------------------------------------
 |  Flag           |   Inteles (semantica intuitiva)             |  Implicit  |
 |---------------------------------------------------------------|------------|
 | ios::fixed      |      numerele in punct flotant              |  nesetat   |
 |                 | nu se afiseaza in format exponential        |            |
 | ios::scientific |        numerele un punct flotant            |  nesetat   |
 |                 |  sunt afisate in notatia exponentiala       |            |
 | ios::showpoint  | punctul zecimal si zecimalele zero sunt     |  nesetat   |
 |                 | mereu afisate pentru numere in punct flotant|            |
 | ios::showpos    |   semnul + este afisat in fata              |  nesetat   |
 |                 |   valorilor pozitive                        |            |
 | ios::right      | daca este setat si valoarea latimii campului|  setat     |
 |                 | este data cu un apel al functiei membru     |            |
 |                 | width, atunci urmatorul articol va fi la    |            |
 |                 | capatul din dreapta al spatiului specificat |            |
 |                 |  cu witdh                                   |            |
 | ios::left       | daca este setat si valoarea latimii campului| nesetat    |
 |                 | este data cu un apel al functiei membru     |            |
 |                 | width, atunci urmatorul articol va fi la    |            |
 |                 | capatul din stanga al spatiului specificat  |            |
 |                 | cu witdh                                    |            |
 |----------------------------------------------------------------------------|
   
Resetarea acestor valori se face cu unsetf (flags)
--------
Exemplu: cout.unsetf(ios::showpos);
--------
Un manipulator este o functie membru de flux apelata in mod netraditional. 
Acestia sunt plantati dupa operatorul de insertie <<. Deocamdata prezentam 
doar 2 manipulatori: setwidth si setprecision (manipulatorul setwidth este 
echivalent cu functia membru witdh).

--------
Exemplu:
-------- 
   cout << "Afisam intregii " << setw(4) << 10
                          << setw(5) << 20 << setw(6) << 30;
        
Iesirea va fi: Afisam intregii  10   20    30
  
Manipulatorul precision este echivalent cu functia membru precision.
--------
Exemplu:
--------        cout.setf(ios::fixed);
                cout.setf(ios:showpoint);
                cout << "$" << setprecision(2) << 10.3 << endl
                     <<  setprecision(3) << "$" << 20.5 << endl;
         
Iesirea va fi: $10.30
                    $20.500
-----------              
Observatie: 
----------- 
   Pentru lucrul cu acesti operatori(setw, setprecision) trebuie 
sa includeti directiva:   #include 
        

-----------------------------------
Fluxuri ca argumente pentru functii
-----------------------------------
Un flux (stream) poate fi un argument pentru o functie. Singura restrictie 
este aceea ca parametrul formal al functiei trebuie sa fie referinta. 
--------  
Exemplu:
--------
// Programul urmator ilustreaza instructiuni de formatare a iesirii citeste 
// toate numerele din fisierul "fis_in.dat" si le scrie la
// ecran si in fisierul "fis_out.dat" intr-o forma frumoasa.
#include 
#include 
#include 
#include 
void afisare_frumoasa(ifstream& fisier_urat, ofstream& fisier_frumos, int 
precizie, int latime_camp);
// Preconditie: Fluxurile fisier_urat si fisier_frumos sunt conectate la 
// fisiere folosind functia open.
// Postconditie: Numerele din fisierul fisier_urat vor fi afisate la ecran si 
// in fisier_frumos. Numerele sunt cate unul pe linie, in
// notatie punct fix (si nu exponentiala) cu precizie cifre dupa punctul 
// zecimal; fiecare numar este precedat de + sau -, in 
// total avand la dispozitie latime_camp caractere.
int main()
 {
  ifstream fin;
  ofstream fout;
  fin.open("fis_in.dat");
  if (fin.fail())
   {
    cout << "Deschiderea fisierului de intrare a esuat.\n";
    exit(1);
   }
  fout.open("fis_out.dat");
  if (fout.fail())
   {
    cout << "Deschiderea fisierului de iesire a esuat.\n";
    exit(1);
   }
  afisare_frumoasa(fin, fout, 5, 12);
  fin.close();
  fout.close();
  cout << "Sfarsitul programului.\n";
  return 0;
 }

// folosim acum bibliotecile iostream.h, fstream.h si iomanip.h
void afisare_frumoasa(ifstream& fisier_urat, ofstream& fisier_frumos, int 
precizie, int latime_camp)
 {
  fisier_frumos.setf(ios::fixed);         // nu notatie exponentiala
  fisier_frumos.setf(ios::showpoint); // afiseaza punctul zecimal
  fisier_frumos.setf(ios::showpos);   // afiseaza semnul (+,-)
  fisier_frumos.precision(precizie);
  // acum facem setarile si pentru afisarea pe ecran
  cout.setf(ios::fixed);
  cout.setf(ios::showpoint);
  cout.setf(ios::showpos);
  cout.precision(precizie);
  double urmator;
  while (fisier_urat >> urmator)
   {
    cout << setw(latime_camp) << urmator << endl;
    fisier_frumos << setw(latime_camp) << urmator << endl;
   }
 }


---------------------------
Functiile membru get si put
---------------------------
Functia get permite citirea unui caracter de la intrare si memorarea lui 
intr-o variabila de tip char. get este o functie membru pentru fluxul cin si 
se poate extinde la orice fisier de intrare. Am vazut ca cin cu operatorul de 
extractie >> citeste un caracter (sau orice alta intrare). Unele lucruri sunt 
facute automat, cum ar fi trecerea peste spatii. Folosind functia membru get, 
nimic nu se face automat (trecerea peste spatii trebuie facuta de catre 
programator).
--------
Exemplu:
--------	char a;
   		cin.get(a);
-----------   		
Observatie: argumentul a este de tip output, adica isi modifica valoarea
-----------
--------   
Exemplu:
--------        char c1,c2,c3;
  		cin.get(c1);
   		cin.get(c2);
   		cin.get(c3);
   		
Presupunem ca la intrare avem: 
                     AB
                     CD
atunci se va unifica astfel: C1='A',C2='B',C3='\n'
--------                  
Exemplu: detectam folosind get sfarsitul liniei.
--------
     cout << "Dati o linie pentru a o dubla\n";
     char c;
     do
      {
       cin.get(c);
       cout << c << c;
      }
     while (c != '\n');

-----------------------------------------
Deosebiri si asemanari intre '\n' si "\n" 
-----------------------------------------
Asemanari: intr-o instructiune cout, ele produc acelasi efect.
Deosebiri: '\n' - valoare de tip char (se poate fi memorat intr-o variabila de 
           tip char)
           "\n" - este un sir format dintr-un singur caracter dar nu poate fi 
           memorat intr-o variabila de tip char
--------
Exemplu:                            __________
--------     'a' - un caracter, "a"=|'a'|'\0'| - un sir de 2 caractere
                                    ----------

Sintaxa generala a functiei membru get este:
  
        Flux_de_intrare.get(variabila_de_tip_char)

Flux_de_intrare este un obiect al clasei ifstream. Daca Flux_de_intrare este 
egal cu cin, atunci trebuie sa adaugam biblioteca . Daca este un 
fisier diferit de cel standard, atunci adaugam .

Functia membru put este valabila pentru fluxuri de iesire. Ea are un argument 
care este o expresie de tip char. Cand functia este apelata, valoarea 
argumentului este trimisa catre fluxul de iesire.

Sintaxa: Flux_de_iesire.put(Expresie_caracter);

-------- 
Exemple:       cout.put(simbol_urmator);
--------       cout.put('a');
          

----------------------
Functia membru putback
----------------------
Uneori dorim sa stim urmatorul caracter dintr-un flux de intrare. Insa folosind 
get, pointerul catre fisier indica deja urmatorul caracter. Dorim acum sa punem 
inapoi caracterul in fluxul de intrare. Functia putback este un membru pentru 
orice flux de intrare. Acesta are un argument de tip char si plaseaza valoarea 
argumentului inapoi in fluxul de intrare.
--------
Exemplu: scriem un cod C care citeste caracter cu caracter pana la primul spatiu 
-------- care este introdus inapoi in fluxul de intrare:

	fin.get(a);
	while (a != ' ')
	 {
	  fout.put(a);
	  fin.get(a);
	 }
	fin.putback(a);
-----------	
Observatie: fin este un obiect al clasei ifstream, iar fout este un obiect al 
----------- clasei ofstream.

------------------
Functia membru eof
------------------
Fiecare flux al unui fisier de intrare are o functie membru eof (end-of-file), 
folosita pentru determinarea situatiei cand intreg fisierul a fost citit si 
nu a mai rama nici o intrare pentru program. Functia eof nu are argumente, deci 
daca fluxul de intrare se cheama fin, atunci un apel al functiei eof se scrie:

 	fin.eof();
 	
Expresia fin.eof() returneaza true daca programul a fost citit (adica s-a trecut 
de sfarsitul fisierului de intrare), altfel expresia de mai sus intoarce false.
--------
Exemplu:
--------
    if (! fin.eof())
      cout << "Nu este sfarsitul fisierului.\n";
    else
      cout << "Sfarsitul fisierului.\n";
--------     
Exemplu:
--------
    // presupunem ca fluxul de intrare in_stream este conectat la un fisier de 
    // intrare. Vom lista la ecran continutul fisierului.
    
    in_stream.get(next);
    while (! in_stream.eof())
     {
      cout << next;
      in_stream.get(next);
     }

Functii (macrouri) din : toupper, tolower, isupper, islower, isalpha, 
isdigit, isspace (de repetat din C).


---------
Mostenire
---------
Una din trasaturile cele mai importante ale C++ o reprezinta folosirea claselor 
derivate. O clasa este derivata din alta clasa daca clasa derivata se obtine din 
prima adaugand cateva trasaturi (proprietati).
--------
Exemplu:
-------- Clasa fluxurilor fisierelor de intrare (ifstream) este derivata din 
         clasa tuturor fluxurilor de intrare (istream) prin adaugarea de 
         functii membru suplimentare cum ar fi open si close. Fluxul cin 
         apartine clasei tuturor fluxurilor de intrare, dar nu apartine clasei 
         fluxurilor fisierelor de intrare pentru ca cin nu are functiile membru 
         open si close.


---------------------------
Mostenire in clasele stream
---------------------------
Un obiect este o variabila care are functii membru, iar o clasa, este un tip a 
carui variabile sunt obiecte. Fluxurile (cum ar fi cin, cout, fluxuri fisiere de 
intrare, fluxuri fisiere de iesire) sunt obiecte, deci tipurile fluxurilor 
(ifstream, ofstream) sunt clase. Cin si fluxurile fisiere de intrare sunt 
fluxuri de intrare. Putem folosi operatorul de extractie >> pe ambele tipuri de 
fluxuri. Un flux fisier de intrare poate fi conectat la un fisier folosind 
functia open, dar nu si cin. Un flux fisier de intrare este de tip ifstream, iar 
cin este de tip istream. Clasele ifstream si istream sunt diferite. Legatura 
dintre ele este ca ifstream este o clasa derivata a lui istream.
--------
Exemplu:
-------- Consideram o functie care citeste doi intregi din fluxul de intrare 
         fisier_sursa si afiseaza suma lor la ecran:

 void suma(ifstream& fisier_sursa)
  {
   int n1,n2;
   fisier_sursa >> n1 >> n2;
   cout << n1 << "+" << n2 << "=" << (n1+n2) << endl;
  }
  
I.  Presupunem ca avem urmatoarea declaratie:
	
    ifstream fin; // conectat la un fisier cu open
   
    putem face acum apelul:
    suma(fin);
	    
II. Presupunem ca dorim citirea de la tastatura. Un apel de genul
     suma(cin);
nu va functiona deoarece cin nu este de tip ifstream. Deoarece cin este de tip 
istream, mai scriem inca o functie numita suma1, in care argumentul este 
istream& fisier_sursa. Acum apelul 
      suma1(cin); 
va functiona. De asemeni, va functiona si apelul 
      suma1(fin);
Justificarea este ca ifstream este o clasa derivata a clasei istream. Orice 
flux de tip ifstream este de asemeni de tip istream, deci un
parametru formal de tip istream argumentul folosit in apelul functiei poate fi 
un flux de intrare conectat la un fisier sau la fluxul cin.
Clasele derivate sunt des folosite pentru mostenire si relatiile dintre functii.
Daca clasa B este clasa derivata a clasei A, atunci clasa B se numeste fiul 
(copilul) clasei A si clasa A se numeste parintele clasei B. Clasa derivata 
mosteneste functiile membru ale clasei parinte.
--------
Exemplu:  am vazut ca orice flux de intrare mosteneste operatorul de extractie 
--------  << din clasa tuturor functiilor.

-----------------------------------
Exercitii propuse spre implementare
-----------------------------------
1. Scrieti un program C++ ce foloseste stream-uri astfel incat sa inlocuiti 
fiecare caracter dintr-un fisier de intrare cu un sir de caractere in fisierul 
fisierul de iesire.
2. Scrieti un program C++ care citeste dintr-un fisier inregistrari de forma 
(cate o inregistrare pe linie):

       string              double
      /      \           /   |   \
    nume  prenume    nota1 nota2 nota3
  
si creeaza un alt fisier care contine informatiile din primul fisier + media 
celor 3 note pe respectiva linie (la sfarsit).
3. Scrieti un program C++ care citeste numere sortate crescator din doua 
fisiere si scrie in al treilea fisier reuniunea lor sortata crescator. 
ATENTIE ! Se utilizeaza spatiu suplimentar de memorie constant.
4. Scrieti un program care citeste un fisier C++ inlocuind versiunile incorecte
cin <<  si  cout >> cu versiunile corecte cin >>  si  cout << . 
ATENTIE! Intre cin si << pot fi oricate spatii (inclusiv 0).