Continuando sulla scia della geometria piana, progettiamo le seguenti classi: Triangolo
, Isoscele
, Equilatero
. Si suppone che tutte le classi vengano dichiarate nel file ex12_2_6.h
, il quale includa anche i files di intestazione iostream.h
e math.h
.
class Triangolo { public: Triangolo (double A, double B, double C) : a(A), b(B), c(C) { id = ++counter; cout << "Triangolo::Triangolo() " << id << "\n"; } double perimetro () const { return a + b + c; } double area () const { // utilizziamo la formula di Erone double p = perimetro() / 2; return sqrt(p * (p-a) * (p-b) * (p-c)); } ~Triangolo () { } protected: double a, b, c; // lunghezze dei lati del triangolo unsigned getId () { return id; } private: static unsigned counter; unsigned id; // identificativo triangoli generici };
La classe Triangolo
contiene come campi privati un contatore statico, il quale ha la funzione di essere incrementato per ogni triangolo allocato, e un unsigned rappresentante un identificativo univoco del Triangolo
. Essi sono private in quanto appartengono alla struttura interna della classe Triangolo
. Anche le lunghezze dei lati appartengono concettualmente alla implementazione nascosta della classe, tuttavia si è preferito dichiararli protected in maniera tale che esse, una volta che la classe verrà specializzata tramite la derivazione, possano appartenere propriamente alla struttura interna delle classi derivate, senza utilizzate funzioni di accesso come quelle mostrate nella prima parte delle presente sezione. In generale il compromesso migliore consiste nell'utilizzare delle funzioni di accesso protette a campi interni privati, come mostrato per il campo id
. Infatti, dichiarata in tale maniera la classe Isoscele
:
class Isoscele : public Triangolo { public: // `a' rappresenta la base // `b' rappresenta il lato obliquo Isoscele (double a, double b) : Triangolo(a, b, b) { // un triangolo isoscele ha lo stesso id // del triangolo generico dal quale deriva // il seguente statement e` ERRATO in quanto // il campo `unsigned id' e` private // id = Triangolo::id; // e` invece corretto utilizzare la funzione protected // `unsigned getId()': Isoscele deriva da Triangolo id = Triangolo::getId(); cout << "Isoscele::Isoscele() " << id << "\n"; } double altezza () const { double semibase = Triangolo::a / 2; return sqrt(pow(b, 2) - pow(semibase, 2)); } double area () const { return a * altezza() * .5; } void setBase (double b) { Triangolo::a = b; /* OK: `a' e` un campo protetto */ } void setAltezza (double h) { double semibase = Triangolo::a / 2; Triangolo::b = Triangolo::c = sqrt(pow(h, 2) + pow(semibase, 2)); } ~Isoscele () { } protected: unsigned getId () { return id; } private: unsigned id; // identificativo triangolo isoscele };nel caso si voglia modificare l'implementazione della classe
Triangolo
sarebbe necessario aggiornare anche la Isoscele
, il che è contrario allo spirito del C++, che quanto possibile inibisce la possibilità di dovere affrontare un aggiornamento ``a catena'' del codice scritto.
Come si vede la funzione getId()
e i campi dati a
, b
, c
vengono utilizzati pubblicamente all'interno della Isoscele
. Addirittura non è neanche richiesto l'utilizzo dell'operatore ::
, ove non esistano ambiguità. Per cui un equivalente sintattico della setBase()
è semplicemente:
a = b; |
b
si intende l'argomento della funzione stessa, mentre se avessimo voluto accedere il campo dati della classe base avremmo dovuto forzatamente utilizzare Triangolo::b
. In nessun caso nell'esempio precedente sarebbe stato necessario utilizzare il prefisso Triangolo::
, che abbiamo aggiunto per chiarezza.
L'ultima classe è la seguente:
class Equilatero : public Isoscele { public: Equilatero (double x) : Isoscele (x, x) { id = Isoscele::getId(); cout << "Equilatero::Equilatero() " << id << "\n"; } double perimetro() const { return Triangolo::a * 3; } ~Equilatero () { } protected: private: unsigned id; // identificativo triangolo equilatero };la quale ci mostra come la qualifica di membro protected non vada ``perso'' con derivazioni successive; il compilatore ci ha infatti permesso di accedere il campo
a
della classe Triangolo
; anche in questo caso il prefisso Triangolo::
è opzionale. Si noti invece che, per chiarezza, è sempre una buona idea specificare la classe di appartenenza di funzioni che siano state sovrapposte in precedenti derivazioni, come nel caso della getId()
nel costruttore di Equilatero
. Nel caso non venga specificato alcunché, e se naturalmente la funzione non viene sovrapposta nella classe specifica, il compilatore utilizza la funzione sovrapposta nella classe più ``vicina''; nel nostro caso avrebbe utilizzato la Isoscele::getId()
, che è proprio quella voluta. Si tenga presente che può non essere immediato stabilire lo schema di derivazione, con relative sovrapposizioni, per progetti anche leggermente articolati.
Un esempio che ci mostri il funzionamento delle classi appena definite è il seguente:
// ex12_2_6.cpp #include "ex12_2_6.h" unsigned Triangolo::counter = 0; void main() { Triangolo t(3, 4, 5); Isoscele i1(6, 5); Isoscele i2(0, 0); i2.setBase(16); i2.setAltezza(6); Equilatero e(10); cout << "triangolo generico(a=3,b=4,c=5):\n" << "\t2p = " << t.perimetro() << "\n" << "\tA = " << t.area() << "\n" << "triangolo isoscele(base=6,obliquo=5):\n" << "\t2p = " << i1.perimetro() << "\n" << "\tA = " << i1.area() << "\n" << "triangolo isoscele(base=16,altezza=6):\n" << "\t2p = " << i2.perimetro() << "\n" << "\tA = " << i2.area() << "\n" << "triangolo equilatero(lato=10):\n" << "\t2p = " << e.perimetro() << "\n" << "\tA = " << e.area() << "\n"; // e.a = 5; // NO: "member `a' is protected" }
output:
Triangolo::Triangolo() 1 Triangolo::Triangolo() 2 Isoscele::Isoscele() 2 Triangolo::Triangolo() 3 Isoscele::Isoscele() 3 Triangolo::Triangolo() 4 Isoscele::Isoscele() 4 Equilatero::Equilatero() 4 triangolo generico(a=3,b=4,c=5): 2p = 12 A = 6 triangolo isoscele(base=6,obliquo=5): 2p = 16 A = 12 triangolo isoscele(base=16,altezza=6): 2p = 36 A = 48 triangolo equilatero(lato=10): 2p = 30 A = 43.3013
// ex12_2_7.cpp class Base { public: int f() { return 1; } protected: int g() { return 2; } private: int h() { return 4; } }; class Derivata : private Base { public: int F() { return 8; } protected: int G() { return 16; } private: int H() { return 32; } }; class Derivata2 : public Base { public: int z() { // return f() + g() + h(); // NO return f() + g(); } protected: private: }; void main() { Derivata d; d.F(); // d.G(); // NO // d.H(); // NO // d.f(); // NO // d.g(); // NO // d.h(); // NO Derivata2 d2; d2.f(); // d2.g(); // NO // d2.h(); // NO d2.z(); }
// ex12_2_8.cpp class Nonno { public: int f() { return 1; } }; class Padre : public Nonno { public: int g() { return f(); } }; class Figlio : Padre { public: int h() { return g(); } }; void main() { Figlio f; // f.f(); // NO // f.g(); // NO f.h(); Padre p; p.f(); p.g(); }