duminică, 23 martie 2008

Clase polimorfice. Clase abstracte.

Intr-o clasa de baza folosim specificatorul virtual pentru a-i spune compilatorului ca vom suprascrie metoda respectiva. Sa luam un exemplu:

class A
{
virtual f1(){cout<< "f1A";}
f2(){cout<< "f2A";}
};

class B:A
{
f1(){cout<< "f1B";}
f2(){cout<< "f2B";}
f3(){cout<< "f3B";}
};

Pentru urmatoare secventa de cod programul ne va afisa f1B, f2B, f3B (ceea ce era si de asteptat, clasele sunt instantiate clar, nimic deosebit):

B b;
b.f1();
b.f2();
b.f3();

Specificatorul virtual este folositor daca lucram in felul urmator:

A* b;
b=new B;
b->f1();
b->f2();
b->f3();

In primul rand eroare la compilare: f3 nu se regaseste si in A, si cum b este declarata de tipul A compilatorul se ataca.
Output-ul va fi: f1B, f2A.
In primul rand - pot declara o variabila ca pointer la clasa de baza si s-o instantiez cu o clasa derivata. Insa asa pot apela doar metodele care exista in clasa de baza (fie ca le-am suprascris sau nu). La fel, daca functia o suprascriem cu mai multi parametri in clasa derevivata vom avea eroare la compilare. Clasa de baza trebuie mostenita public.
In al doilea rand - in momentul in care apelez o functie din b, conteaza daca aceasta a fost declarata in clasa de baza ca fiind virtuala sau nu. Daca a fost declarata virtuala, se apeleaza metoda suprascrisa din clasa derivata. Daca nu este virtuala, chiar daca este suprascrisa in clasa derivata, va fi apelata metoda din clasa de baza.
Clasele care au metode virtuale se numesc clase polimorfice.

Putem avea metode virtuale pure - metode care doar sunt declarate in clasa de baza, fara a fi implementate:

class A
{
virtual int funt()=0;
};

O clasa care contine metode virtuale pure se numeste clasa abstracta. Nu putem crea o instanta a acestei clase, trebuie neaparat sa o derivam si sa implementam metodele virtuale pure.

TEMA LABORATOR:
Sa presupunem ca avem doua produse: bond si swap.
1. Bond este caracterizat prin nume, nr de zile total, zile scurse si valoare initiala, valoare curenta.
2. Swap este caracterizat prin nume, valoare initiala, valoare datorata, indice, valoare curenta.
Valoarea curenta se calculeaza in modul urmator:
1. valoare initiala + (valoare intiala* zile scurse/nr total de zile)
2. valoare initiala + (valoare datorata * indice)
Initial, pt 1. zile scurse = 0 si pt 2. indice = 10%.
Sa se realizeze un program care sa ofere utilizatorului urmatoarele optinui:
a. introdu bond
b. introdu swap
c. ziua urmatoare
In momentul in care se alege c. se afiseaza calculeaza valoarea curenta pentru fiecare produs, se afiseaza numele si valoarea curenta pentru fiecare produs, si pt 1. se incrementeaza zile scurse cu 1 iar pt 2. indice *=0.01

C++ Stuff

Compilatorul de C++ nu se ataca la faze de genul:

char *c = new char[2];
c[5] = 4;

S-ar putea sa se atace pe la c[9]=4; sau o valoare mai mare cand isi da si el seama ca te joci cu memorie pe care nu ti-ai marcat-o ca fiind a ta.
Pentru programe mici s-ar putea sa nu prea conteze. Insa pentru programe care folosesc mai multa memorie si scrii heap-ul aiurea s-ar putea sa te trezesti cu rezultate total neasteptate care sunt foarte greu de debbug-at.

duminică, 16 martie 2008

Mostenire

Pentru mai multe detalii puteti citi:
http://www.codersource.net/cpp_tutorial_inheritance.html
http://www.cs.bu.edu/teaching/cpp/inheritance/intro/
http://www.cplusplus.com/doc/tutorial/inheritance.html

Mostenirea presupune sa avem o clasa de baza A pe care sa o derivam in clasa derivata B (sau mai multe clase derivate C,D,E...). Putem avea si mostenire multipla - clase de baza A si B le mostenim in clasa derivata C.

Sintaxa:

class B: A
{
...
};

Clasa B mosteneste clasa A.

class C: A,B
{...};

Clasa C mosteneste clasele A si B.

Ce inseamna mostenirea? (Vom considera exemplul mostenirii simple B este derivata din A) Clasa B va avea toati membrii clasei A, pe langa care mai putem adauga extensii specifice clasei derivate. Sa presupunem ca am implementat o clasa lista simplu inlantuita numita L1. Pentru a construi o lista dublu inlantuita este suficient sa mostenim L1 si sa mai adaugam o proprietate si o metoda pentru deplasarea la stanga.

O clasa derivata mosteneste toti membrii clasei de baza cu exceptia:
  • constructorilor si destructorilor
  • operatorul de atribuire
  • clasele si metodele friend
Desi nu sunt mosteniti, constructorul si destructorul clasei de baza sunt intotdeauna apelati cand cream un nou obiect de tipul clasei derivate.
Putem de asemenea apela si constructorii cu parametri:

class A{
A(int,int);
};

class B:A
{ B(int a):A(a,a);
};

Despre specificatorii de access am mai discutat in introducere. Ii mai putem aplica si la felul in care mostenim o clasa: putem mosteni clasa A privat(implicit), public si protected. Specificatorii se refera la proprietatile membrilor mosteniti din clasa de baza in clasa derivata.

Putem suprascrie metodele din clasele de baza. Este recomandat (si vom vedea laboratorul viitor de ce) ca in fata metodelor pe care le vrem suprascrise in clasa derivata sa folosim specificatorul virtual:

class A
{
...
virtual int f();
};

duminică, 9 martie 2008

Proiect 1

Clasa "matrice" (matrice de double), avand:
- membri privati pentru matricea propriuzisa, numarul de linii si numarul
de coloane;
- constructor pentru initializarea cu un numar dat pe toate componentele
(primeste ca parametru numarul respectiv si numarul de linii si de
coloane);
- constructori pentru initializare si copiere;
- metode publice pentru citire si afisare (preferabil suprascrierea operatorilor >> si <<); - metoda-operator public de atribuire =; - metoda publica pentru reactualizarea numarului de linii si coloane si initializarea componentelor cu un numar dat (primeste ca parametru numarul respectiv, numarul liniilor si al coloanelor); - operator []; implementarea se va face astfel incat daca a este o matrice, i si j doi intregi iar x un real, sa fie posibile constructii ca: "cin>>a[i][j]", "cout<< x="a[i][j]">, >= (comparare pe componente), !(daca matricea are 0 pe toate componentele, furnizeaza intregul 0, altfel furnizeaza intregul 1), operatorii ==, !=, <, <=, >, >= se vor scrie in trei variante, pentru a putea lucra cu operanzi matrice-matrice, real-matrice, matrice-real (in ultimele doua cazuri se va compara matricea data cu o matrice de aceleasi dimensiuni care are pe fiecare componenta numarul respectiv); aveti grija la operatiile care sunt comutative;
- functii "nrlinii", "nrcoloane", "nrelemente" care furnizeaza
numarul liniilor, coloanelor, respectiv nr elementelor matricii;

Operatori, constructor de copiere

Sa presupunem ca avem de creat o clasa vector. Cel mai economic ar fi sa o declaram in felul urmator:

class vector
{
int dimensiune;
int *elemente;
...
};

Ar fi bine ca toate metodele clasei sa fie cat mai intuitive pentru cel care o va utilzia in program. Adunarea sa se faca a+b si nu a.adunare(b), cin>>a si nu a.citeste etc.
La fel si pentru a[i] si nu a.getElement(i). Pentru asta suprascriem operatorul [].

int vector::operator [](int i)
{
return elemente[i]; //sau *(elemente+i), eventual si cu o verificare impotriva erorilor
}

Este corect definita functia de mai sus?

O alta problema care apare la clase care au membri pointeri ar fi atribuirea. Sa consideram urmatoarea bucata de cod:

vector a,b;
//initializam pe a
b=a;

Copierea se va face default de compilator - copie exact valorile din a in b. Dar elemente este o adresa, deci va copia adresa din a in b, si nu valorile de la adresa respectiva. Daca modificam valori in a.elemente si vor modifica si in b. Problema aceasta se rezolva scriind un constructor de copiere - constructor care primeste ca parametru tipul clasei careia ii apartine si/sau suprascriind operatorul =. Pentru constructor de copiere:

vector::vector(vector &a){...}

Constructorul de copiere va fi apelat in mai multe cazuri:
  • daca initializam o instanta a unei clase cu un obicet deja existent (vector a(b); echivalent cu a scrie vector a=b)
  • daca avem parametri de tipul cu constructor de copiere transmiri prin valoare (int ceva(vector a){..}) pentru ca se realizeaza o copie a obicetului pe stiva
Suprascrierea operatorului = este evidenta.

NOTA: 1. Este recomandat ca operatorii de tip = sa returneze referinte, pentru a putra face atribuiri inlantuite: a=b=c=d.
2. Daca avem o instanta a unei clase care ocupa multa memorie este recomandat sa o transmitem prin referinta si nu sa-i facem inca o copie in memorie (transmiterea prin valoare).
3. Daca pentru un operator transmitem parametrii prin referinta, e bine sa folosim specificatorul const, pentru a fi siguri ca nu se modifica in cadrul functiei:
vector operator + (const vector & a,const vector &b){...}


Tema laborator: Realizati o clasa vector care sa o folosim in urmatorul main:

int main()
{
int m;
cin>>m;
vector a(m);
cin>>a;
vector b;
cin b;
vector c=a+b;
cout<< a << "+" << b << "=" << c;
cout<< a << "*" << b << "=" << a*b; //produs scalar
for(int i=0;i< m;i++)
cout << a[i]<<" ";
count<< endl;
}

NOTA: Declararea variabilelor in C++
In C era obligatoriu sa-ti declari variabilele la inceputul blocului de instructiuni. In C++ poti sa le declari oriunde, insa pentru a face codul usor de citit este recomandat sa le declarati tot la inceputul blocului.
Un caz aparte este for-ul, daca il scriem for(int i....). Variabila i este recunoscuta doar in cadrul for-ului sau si dupa ce iesim din for?
Standard-ul initial C++ spunea ca o sa fie recunoscuta in tot blocul din care face for parte.
Realitatea spune ca depinde de compilator (in versiunile mai noi de Visual va fi recunoscuta doar in cadrul for-ului, insa Linux-ul s-ar putea sa respecte standardul).

miercuri, 5 martie 2008

Clasa Rational

#include

using namespace std;

class rational
{
private:
int numitor;
int numarator;
public:
rational();
rational(int, int);
~rational();
friend rational operator + (rational, int);
friend istream &operator >>(istream &,rational&);
friend ostream &operator <<(ostream&,rational);
};

rational::rational(int a,int b=1)
{
numitor = b;
numarator = a;
}
rational::~rational()
{
}
rational::rational()
{
numitor = 0;
numarator = 1;
}
rational operator + (rational a, int b)
{
rational rezultat;
rezultat.numarator=b*a.numitor+a.numarator;
rezultat.numitor=a.numitor;
return rezultat;
}
istream& operator >>(istream& cin,rational &a)
{
cin>>a.numarator>>a.numitor;
return cin;
}
ostream& operator <<(ostream& cout,rational a)
{
cout<< a.numarator<<"/"<< a.numitor< return cout;
}
int main()
{
rational a;
rational b(3);
rational c(3,2);
cin>>a;
cout<< a<<"+2="<<(a+2);
cout<< b<<"+2="<<(b+2);
cout<< b<<"+2="<<(c+2);
}

sâmbătă, 1 martie 2008

Stupid bug

Ce returneaza functia?

#define True 1
int isUpdated()
{
boolean updated;
if(True == updated)
{
return 1;
{
return 0;
}

Polimorfism si suprascrierea operatorilor

Transmiterea parametrilor prin referinta: daca in C trebuia sa transmitem adresa variabilei pentru a putea sa ii schimbam valoarea, in C++ putem transmite parametrii prin referinta folosind operatorul &.

int functie(int &a,int b)
{
b=a; //la iesirea din functie b va avea aceeasi valoarea, chiar daca am facut o atribuire
a=b+5; // a va avea valoarea schimbata pentru ca a fost transmis prin referinta
}

Polimorfism in C++: putem folosi acelasi nume de functie pentru a realiza mai multe actiuni - trebuie sa difere numarul parametrilor sau tipul parametrilor (cred). Daca difera doar tipul returnat, sau daca difera tipul parametrilor la modul ca intr-o parte avem int iar in alta parte double compilatorul va genera erori. Atentie la functiile cu parametri impliciti.

Functii cu parametri impliciti:

int clasa_mea::functie(int a, int b=0)
{
this->a = a;
this->b = b;
}
...
obiect.functie(5,3); //obiect.a va lua valoarea 5 iar b valoarea 3
obiect.functie(5); //daca nu specificam al doilea parametru aceasta va primi valoarea implicita specificata la implementare

Sa presupunem ca pe langa functia de mai sus definim inca una cu un singur parametru:

int clasa_mea::functie(int a)
{
this->a = a;
this->b = 500;
}

In momentul in care apelam obiect.functie(5) programul nu va sti care dintre cele doua implementari trebuie sa o apeleze => bad polymorphism.

Operatorii din C/C++ sunt functii deci pot si suprascrisi:

class clasa_mea
{
...
clasa_mea operator +(alta_clasa b)
{
//operatii smechere scrise inline din lene
}
};

Operatorul + cu un singur operand? Si cum il apelam? Restul metodelor le apelam obiect.functie(); deci pentru functie() aveam o structura de date cu care lucram in cadrul ei, chiar daca nu are parametri - instanta care apeleaza operatorul. Deci operatorul + de mai sus are doi parametri: unul de tip clasa_mea, cel care apeleaza functia/operatorul si unul de tip alta_clasa.
Operatorul il apelam cum am apela orice alt operator:

clasa_mea a;
alta_clasa b;
clasa_mea c=a+b;

Dar 1. daca respectam incapsularea, membrii variabile vor fi privati 2. cum fac pentru comutativitate? Daca scriem ca mai sus primul parametru va fi intotdeauna de tipul clasa_mea; daca vreau sa fac alta_clasa+clasa_mea. Daca alta clasa este definita de mine nu voi avea acces la membrii privati din clasa_mea. Daca alta_clasa este un tip din C++ cum fac?

Putem scrie o functie care nu face parte din nici o clasa, in felul acesta putem specifica ce ordine vrem pentru operatori:

clasa_mea operator+(clasa_mea a, alta_clasa b){...}

Insa si asa vom avea probleme cu membrii privati. Pentru asta folosim specificatorul friend:

class clasa_mea
{
...
friend clasa_mea operator +(clasa_mea a,alta_clasa b)
{
//operatii smechere scrise inline din lene
}
};


Acesta ii spune clasei ca operatorul + nu face parte din clasa_mea, insa ii poate accesa membrii privati. Poate fi folosit pentru orice functii, nu neaparat pentru operatori (cred ca merge si pentru membri ai altor clase - daca vrei sa faceti programe ciudate).

Cand suprascriem operatori trebuie sa respectam numarul parametrilor.

cin si cout (de fapt >> si <<) sunt operatori (deja suprascrisi chiar, ca pot fi folositi si la shiftarea pe biti) deci pot fi suprascrisi. Sintaxa lor standard este cam asa:

istream& operator >> (istream& cin, int &a){...}
ostream& operator << (ostream& cout, int a){...}

Ar trebui sa returneze ceva?