In cadrul acestui laborator, vom aprofunda inca un concept important al Programarii Orientate pe Obiecte, derivarea. Pe parcursul acestui laborator vom aborda notiuni precum: mostenire, ierarhie de clase, modificatori de acces, shadowing, upcasting, pragma once, redefinire.
Ca referinte externe, recomandam urmatorul capitol din Absolute C++:
//cod class Produs { private: char *nume; int pret; char cod[10]; }; class Electrocasnic { private: char *nume; int pret; char cod[10]; int durata_garantie; }; class Aliment { private: char *nume; int pret; char cod[10]; int durata_expirare; }; class Jucarie { private: char *nume; int pret; char cod[10]; int varsta_recomandata[2]; }; class TV { private: char *nume; int pret; char cod[10]; int durata_garantie; double diagonala; }; class Masina_cafea { private: char *nume; int pret; char cod[10]; int durata_garantie; char *tip; //expresor, filtru }; /* iar exemplele pot continua */ //cod
Putem observa ca avem o clasa de baza, Produs, si ca putem distinge mai multe categorii de produse. Aceste categorii de produse sunt niste clase care, pe langa faptul ca au comportamentul clasei Produs, au in plus atribute si comportamente specifice (clase specializate).
De exemplu: Masina_cafea si TV-ul sunt electrocasnice, dar acestea au comportamente (si atribute) specifice. (Un TV nu poate sa faca cafea, iar masina_cafea nu difuzeaza stirile de la ora 17:00)
//cod class Produs { //clasa de baza protected: char *nume; int pret; char cod[10]; }; class Electrocasnic : public Produs {//clasa derivata (din Produs), dar si clasa de baza protected: int durata_garantie; }; class Aliment : public Produs{//clasa derivata (din Produs) private://daca implementam o clasa care sa fie derivata din clasa ALiment, folosim protected int durata_expirare; }; class Jucarie : public Produs{//clasa derivata (din Produs) private://daca implementam o clasa care sa fie derivata din clasa Jucarie, folosim protected int varsta_recomandata[2]; }; class TV : public Electrocasnic{//clasa derivata (din Electrocasnic) private: double diagonala; }; class Masina_cafea : public Electrocasnic{//clasa derivata (din Electrocasnic) private: char *tip; //expresor, filtru }; /* iar exemplele pot continua */ //cod
Derivarea este un procedeu prin care se creeaza un nou tip de date (o noua clasa) folosind o clasa existenta, mai exact, adaugam un cod nou (atribute noi, metode noi) codului deja existent ⇒ se preia interfata clasei de baza.
In clasa derivata sunt mostenite toate atributele si metodele clasei de baza si le pot accesa direct daca sunt declarate public sau protected.
1. Putem sa derivam mai multe clase din clasa de baza?
Da, se poate face acest lucru. Putem sa avem oricate clase derivate dintr-o clasa de baza si oricate niveluri de derivare (cat timp este logic).
2. Pot sa am mai multe clase parinte in acelasi timp?
Desigur, acest mecanism se numeste mostenire multipla. Exemplu:
Avem urmatoarele clase de baza:
Presupunem ca studentul nostru se angajeaza, astfel se creaza o noua clasa Student_Angajat (are note, are salariu, lucreaza la o firma si studiaza la o facultate). Observam ca noua clasa, Student_Angajat, are ca si parinti ambele clase (Angajat, Student).
Tot ce este declarat public este vizibil pentru toate lumea. Daca avem metode sau atribute publice intr-o clasa, le putem apela folosind operatorul “.”.
Private
Tot ce este declarat private este vizibil doar la nivel de clasa. Daca avem atribute declarate ca fiind publice, acestea sunt vizibile doar in clasa respectiva. De aceea, folosim metode prin care extragem valorile atributelor private.
Protected
Tot ce este declarat protected este inaccesibil din exteriorul clasei, exceptie facand doar clasele derivate dintr-o clasa cu atribute protected. Acestea, clasele derivate, pot sa acceseze atributele protected fara a fi nevoie de a folosi metode speciale care returneaza valori.
#include <iostream> using namespace std; class Cerc { public: double raza; double arie_cerc() { return 3.14 * raza * raza; } }; int main() { //Exemplu public Cerc circle; circle.raza = 6.5; cout << "Raza cercului este: "<< circle.raza << endl; //o sa afiseze 6.5 cout << "Aria cercului este: "<< circle.arie_cerc() << endl; //o sa afiseze aria cercului return 0; }
#include <iostream> using namespace std; class Cub { private: int latura; public: int arie_cub () { return 6 * latura * latura; } }; int main() { //Exemplu private Cub cube; cube.latura = 5; /*incercam sa ii atribuim o valoare atributului latura din clasa Cub, inafara clasei nu putem face acest lucru fara o metoda specifica */ cout << "Aria cubului este: " << cube.arie_cub() << endl; //o sa afiseze o eroare return 0; }
#include <iostream> using namespace std; class Parinte { protected: int id_clasa; }; class Copil : public Parinte { public: void setId (int id) { id_clasa = id; } void print_id () { cout << "Id_copil: " << id_clasa; } }; int main () { //Exemplu protected Copil obiect; obiect.setId(77); obiect.print_id(); //o sa afiseze ,,Id copil: 77" return 0; }
Indicator de vizibilitate | Accesibilitate | Zona |
---|---|---|
public | accesibil | din exteriorul ierahiei |
accesibil | din clasele derivate | |
protected | inaccesibil | din exteriorul ierarhiei |
accesibil | din clasele derivate si orice alte clase derivate din clasele derivate | |
private | inaccesibil | din exteriorul ierarhiei |
inaccesibil | din clasele derivate |
class A { public: int x; protected: int y; private: int z; }; class B : public A { // x este public // y este protected // z nu este accesibil din B }; class C : protected A { // x este protected // y este protected // z nu este accesibil din C }; class D : private A { // x este private // y este private // z nu este accesibil din D };
Insa, vrem sa fie (aproape mereu) publica (class Derivata: public Baza).
Rareori este protected (class Derivata : protected Baza).
In general, operatorii implementati ca functii membre se mostenesc. O exceptie este operatorul de atribuire, acesta nu se mosteneste.
Functiile friend pot accesa atributele si metodele protected/public/private din clasa cu care sunt prietene.
Nu pot fi virtuale (notiune aprofundata in cadrul laboratorului 7).
Amandoua mecanismele reutilizeaza codul scris pentru o clasa de baza/simpla intr-o alta clasa mai complexa. In ambele cazuri se folosesc liste de initializare pentru constructori pentru a crea obiectele de baza (chiar daca apelul difera prin sintaxa).
Erorile se gasesc in codul nou ⇒ mai usor de cautat/gasit.
Clasele sunt clar separate. (Codul poate fi refolosit si in alte locuri fara a avea erori)
#include <iostream> using namespace std; class Baza1 { protected: int atribut1; public: Baza1(int i = 0) : atribut1(i){} void set_atribut1(int i) { atribut1 = i; } void afisare_atribut1() { cout << "\nAtribut1 = " << atribut1 << endl; } }; class Baza2 { protected: int atribut2; public: Baza2(int i = 0) : atribut2(i){} void set_atribut2(int i) { atribut2 = i; } void afisare_atribut2() { cout << "\nAtribut2 = " << atribut2 << endl; } }; class Derivata : public Baza1, public Baza2 { private: int atribut3; public: Derivata() {} Derivata(int a1, int a2, int a3) : Baza1(a1),Baza2(a2),atribut3(a3){} void set_atribut3(int i) { atribut3 = i; } void set_atribute(int a1, int a2, int a3) { atribut1 = a1; atribut2 = a2; atribut3 = a3; } void afisare_atribute() { cout << "\nAtribut1 = " << atribut1 << "\nAtribut2 = " << atribut2 << "\nAtribut3 = " << atribut3 << endl; } }; int main() { Derivata obiect(1,2,3); obiect.afisare_atribute(); cout << "\n" << "________________" << endl; obiect.set_atr1(5); obiect.set_atr2(6); obiect.set_atr3(7); obiect.afisare_atribut1(); obiect.afisare_atribute(); return 0; }
class A { protected: int atr; }; class B : public A { private: //am mostenit si atributul ,,atr" din clasa A int atr; // are priotate fata de atributul cu acelasi nume din A public: void set_atr (int i, int j) { A::atr = i; //pentru a avea acces la atributul ,,atr" din A //il apelam folosind A:: atr = j; } };
Daca o clasa este derivata dintr-o alta clasa de baza (relatie “is a”) ⇒ obiectele de tipul clasei derivate, sunt, in acelasi timp, si obiecte de tipul clasei de baza.
obiect_clasa_baza = obiect_clasa derivata (upcasting, conversie la tipul obiectului de baza)
Putem face acest lucru prin 2 metode:
In aceste cazuri, versiunile metodelor din clasa de baza o sa fie ascunse pentru noua clasa (nu avem acces direct la functiile clasei de baza cu acelasi nume ca cele din clasa derivata). Le putem apela explicit folosind nume_clasa_baza :: nume_functie.
In cazul in care avem ierarhii complicate de clase, trebuie sa avem grija sa nu includem header-ul unei clase de mai multe ori in alt header sau in programul principal.
#pragma once //include aceasta sursa (header_clasa_baza.h) o singura data in momentul compilarii #include <iostream> using namespace std; class Baza { protected: int atribut; public: Baza(int i):atribut(i){} };
Alternativa
#ifndef HEADER_CLASA_BAZA #define HEADER_CLASA_BAZA #include <iostream> using namespace std; class Baza { protected: int atribut; public: Baza(int i):atribut(i){} }; #endif