>> 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).