Κληρονομικότητα
Διομήδης Σπινέλλης
Τμήμα Διοικητικής Επιστήμης και Τεχνολογίας
Οικονομικό Πανεπιστήμιο Αθηνών
dds@aueb.gr
Εισαγωγή
- Συχνά μια σειρά κλάσεων αντικειμένων μπορεί να μοντελοποιηθεί με
τη μορφή μιας ιεραρχίας.
- Για παράδειγμα:
- Όλα τα μέλη της Πανεπιστημιακής Κοινότητας είναι
φυσικά πρόσωπα και έχουν ως ιδιότητα το όνομα και το επώνυμό τους.
- Οι φοιτητές έχουν ακόμα ως ιδιότητα τον αριθμό μητρώου τους και
το έτος που φοιτούν.
- Όσοι έχουν σχέση εργασίας με το Πανεπιστήμιο έχουν ως ιδιότητα το
μισθό τους.
- Το διοικητικό προσωπικό έχει ως πρόσθετη ιδιότητα την ειδικότητά του.
- Οι διδάσκοντες έχουν ως πρόσθετη ιδιότητα τα μαθήματα που διδάσκουν.
Κληρονομικότητα σε κλάσεις
- Η ιεραρχία των κλάσεων εκφράζεται στη C++ με τον ορισμό μιας κλάσης που
είναι υποκλάση (subclass) μιας άλλης κλάσης (που
ονομάζεται βασική κλάση (base class)).
- Η σύνταξη για τον ορισμό αυτό είναι της μορφής:
class υποκλάση : public βασική_κλάση { ...
- Κάθε υποκλάση κληρονομεί (inherits) τις μεθόδους
και τις ιδιότητες που έχουν οριστεί ως public στη βασική κλάση.
- Η υποκλάση δεν έχει πρόσβαση στις μεθόδους και τις ιδιότητες
που έχουν οριστεί ως private στη βασική κλάση.
- Η υποκλάση μπορεί να ορίσει νέες ιδιότητες και μεθόδους και να
αντικαταστήσει ιδιότητες και μεθόδους που έχει ορίσει η βασική κλάση.
- Παράδειγμα:
class shape {
private:
int x, y; // Position
public:
void setposition(int x, int y);
};
void shape::setposition(int x, int y)
{
shape::x = x;
shape::y = y;
}
class circle : public shape {
private:
int radius;
public:
void setradius(int r);
};
class rectangle : public shape {
private:
int height, width;
public:
void setdimensions(int h, int w);
};
main()
{
circle c;
rectangle r;
r.setposition(1, 2);
c.setposition(3, 4);
}
- Μπορούμε να ορίσουμε μια άλλη κατηγορία μεθόδων και ιδιοτήτων
με όνομα protected. Αυτές είναι προσβάσιμες από τις υποκλάσεις της
κλάσης μας, αλλά όχι από άλλες συναρτήσεις.
Παράδειγμα:
class shape {
private:
int x, y;
protected:
int getx(); // Can be used by subclasses
int gety();
public:
void setposition(int x, int y);
}
- Ένας δείκτης σε μια υποκλάση μπορεί αυτόματα να μετατραπεί σε
δείκτη στη βασική της κλάση. Το αντίθετο δεν επιτρέπεται.
Παράδειγμα:
main()
{
circle c;
rectangle r;
shape *sp;
r.setposition(1, 2);
c.setposition(3, 4);
sp = &c; // Implici conversion
sp->setposition(3, 3); // Set the position of c
}
- Μια κλάση μπορεί να δηλωθεί με
πολλαπλή κληρονομικότητα (multiple inheritance) με
τη σύνταξη:
class υποκλάση : public βασική_κλάση1, public βασική_κλάση2, ... { ...
Στην περίπτωση αυτή η κλάση κληρονομεί τα μέλη και τις μεθόδους όλων
των βασικών κλάσεων που δηλώθηκαν.
Ιδεατές συναρτήσεις
- Αν σε μια κλάση δηλώσουμε μια συνάρτηση ως virtual τότε οι
υποκλάσεις της κλάσης αυτής μπορούν να αντικαταστήσουν τη συνάρτηση
με μια που θα ορίσουν αυτές.
- Όταν κληθεί η συνάρτηση που έχει αντικατασταθεί από μια υποκλάση
μέσω ενός δείκτη της βασικής κλάσης ο οποίος έχει προέλθει από δείκτη
κάποιας υποκλάσης τότε θα κληθεί η αντίστοιχη συνάρτηση της υποκλάσης
από την οποία έχει προέλθει ο δείκτης.
- Η δυνατότητα αυτή επιτρέπει:
- το δυναμικό καθορισμό της συμπεριφοράς ενός αντικειμένου ανάλογα με
την κλάση του κατά την εκτέλεση του προγράμματος,
- την αλλαγή της συμπεριφοράς μιας παλιάς κλάσης από μια νεώτερη
(υποκλάση της) και
- την ενοποιημένη διαχείριση διαφορετικών αντικειμένων μέσω της βασικής
τους κλάσης.
- Η δυνατότητα αυτή προάγει τη C++ από γλώσσα που υποστηρίζει τα
αντικείμενα σε αντικειμενοστρεφή γλώσσα.
Παράδειγμα
Το παρακάτω παράδειγμα ορίζει τη βασική κλάση shape και τις υποκλάσεις
της circle και rectangle.
Η μέθοδος area ορίζεται ως virtual με αποτέλεσμα να μπορεί να οριστεί
για τις υποκλάσεις και κατά την εκτέλεση του προγράμματος να εκτελεστεί
η σωστή έκδοσή της.
Η συνάρτηση printarea της shape εκμεταλλεύεται τη δυνατότητα αυτή και
μπορεί να κληθεί (και να δουλέψει σωστά) με όρισμα οποιαδήποτε από τις
υποκλάσεις της shape.
#include <iostream.h>
class shape {
private:
double x, y; // Position
public:
void setposition(double x, double y);
void printarea();
virtual double area();
};
double
shape::area()
{
return (0);
}
void
shape::printarea()
{
cout << area() << "\n";
}
void shape::setposition(double x, double y)
{
shape::x = x;
shape::y = y;
}
class circle : public shape {
private:
double radius;
public:
void setradius(double r);
virtual double area();
};
void
circle::setradius(double r)
{
radius = r;
}
double
circle::area()
{
return (3.1415927 * radius * radius);
}
class rectangle : public shape {
private:
double height, width;
public:
void setdimensions(double h, double w);
virtual double area();
};
void
rectangle::setdimensions(double h, double w)
{
height = h;
width = w;
}
double
rectangle::area()
{
return (height * width);
}
main()
{
circle c;
rectangle r;
shape *sp;
r.setposition(1, 2);
c.setposition(3, 4);
r.setdimensions(10, 10);
c.setradius(1);
c.printarea();
r.printarea();
sp = &r;
cout << sp->area();
}
Αφηρημένες κλάσεις
Για παράδειγμα, ένας σχεδιασμός του πληροφοριακού συστήματος του
Πανεπιστημίου μπορεί να ορίσει την αφηρημένη κλάση person ως βασική
κλάση για τις υποκλάσεις student, employee και visitor.
Αν και δε θα μπορεί να οριστεί μια μεταβλητή για την αφηρημένη κλάση
person, αυτή μπορεί να περιέχει ορισμένα βασικά χαρακτηριστικά όπως
birth_date και να επιβάλει την υλοποίηση συγκεκριμένων μεθόδων
όπως home_page_URL() ορίζοντάς τις ως θεωρητικές.
Ασκήσεις
Άσκηση 4 (προαιρετική)
- Να υλοποιήσετε σε C++ τις κλάσεις icsd_student και math_student.
Οι κλάσεις αυτές πρέπει να έχουν ως ιδιότητα τον αριθμό μαθημάτων
που έχει περάσει ένας φοιτητής, αντίστοιχες μεθόδους
πρόσβασης στην ιδιότητα αυτή και μια μέθοδο που να επιστρέφει αληθές
αν ο φοιτητής μπορεί να πάρει πτυχίο (έχοντας περάσει 40 μαθήματα
για την icsd_student και 38 για την math_student).
Να χρησιμοποιήσετε κληρονομικότητα για να ελαχιστοπιήσετε τις
μεθόδους των κλάσεων.
- Γράψτε ένα μικρό πρόγραμμα που να ελέγχει τη λειτουργία των κλάσεων αυτών.