Η κληρονομικότητα (inheritance) στη γλώσσα C++ είναι ένας μηχανισμός που επιτρέπει τη δημιουργία νέων κλάσεων βασισμένων σε υπάρχουσες κλάσεις. Αυτό σημαίνει ότι μια κλάση μπορεί να κληρονομεί τα χαρακτηριστικά και τη συμπεριφορά μιας άλλης κλάσης, γεγονός που μας επιτρέπει να αποκτήσουμε νέες κλάσεις με βάση υπάρχουσες, επαναχρησιμοποιώντας και επεκτείνοντας τον υπάρχοντα κώδικα.
Η κληρονομικότητα στην C++ υλοποιείται μέσω της δήλωσης της κλάσης που επιθυμούμε να κληρονομήσουμε (γνωστή ως βασική κλάση ή γονική κλάση) και τη χρήση ειδικών λέξεων-κλειδιών όπως η “public”, “private” ή “protected” για να καθορίσουμε το επίπεδο πρόσβασης των κληρονομημένων χαρακτηριστικών και μεθόδων. Μέσω της κληρονομικότητας, μπορούμε να αξιοποιήσουμε τον πολυμορφισμό και την αφαίρεση, δημιουργώντας ιεραρχίες κλάσεων με διαφορετικά επίπεδα λειτουργικότητας και συμπεριφοράς.
Ο μηχανισμός της κληρονομικότητας επιτρέπει τη δημιουργία πιο οργανωμένου και ευέλικτου κώδικα, καθώς μας επιτρέπει να αποσπούμε κοινή λειτουργικότητα και χαρακτηριστικά σε μια βασική κλάση και να τα επεκτείνουμε ή να τα τροποποιούμε στις παράγωγες κλάσεις.
Ας υποθέσουμε ότι έχουμε μια γονική κλάση με το όνομα “Animal” που περιλαμβάνει τα βασικά χαρακτηριστικά και συμπεριφορές ενός ζώου. Από αυτήν την κλάση θα κληρονομήσουν διάφορες παράγωγες κλάσεις που αντιπροσωπεύουν συγκεκριμένα είδη ζώων.
Ας δούμε ένα αναλυτικό παράδειγμα:
#include <iostream> using namespace std; // Η γονική κλάση Animal class Animal { private: string species; public: Animal(string species) { this->species = species; } void eat() { cout << "The animal is eating." << endl; } void sleep() { cout << "The animal is sleeping." << endl; } }; // Παράγωγη κλάση Dog που κληρονομεί από την Animal class Dog : public Animal { public: Dog() : Animal("Dog") {} void bark() { cout << "The dog is barking." << endl; } }; // Παράγωγη κλάση Cat που κληρονομεί από την Animal class Cat : public Animal { public: Cat() : Animal("Cat") {} void meow() { cout << "The cat is meowing." << endl; } }; int main() { Dog myDog; Cat myCat; myDog.eat(); // Το σκυλί τρώει myDog.bark(); // Το σκυλί γαβγίζει myCat.eat(); // Η γάτα τρώει myCat.meow(); // Η γάτα μουρμουρίζει return 0; }
Στο παράδειγμα αυτό, η γονική κλάση “Animal” διαθέτει το χαρακτηριστικό “species” και τις μεθόδους “eat” και “sleep”. Οι παράγωγες κλάσεις “Dog” και “Cat” κληρονομούν από την κλάση “Animal” και προσθέτουν τις μεθόδους “bark” και “meow” αντίστοιχα.
Στην main
, δημιουργούμε ένα αντικείμενο myDog
της κλάσης “Dog” και ένα α
ντικείμενο myCat
της κλάσης “Cat”. Στη συνέχεια, καλούμε τη μέθοδο eat
για κάθε αντικείμενο για να δείξουμε ότι η βασική συμπεριφορά της κλάσης “Animal” είναι κληρονομημένη από τις παράγωγες κλάσεις. Τέλος, καλούμε τις επιπλέον μεθόδους bark
και meow
για τα αντίστοιχα αντικείμενα myDog
και myCat
.
Το αποτέλεσμα της εκτέλεσης του προγράμματος θα είναι:
The animal is eating. The dog is barking. The animal is eating. The cat is meowing.
Μέσω της κληρονομικότητας, ο κώδικας οργανώνεται με τρόπο που επιτρέπει την ανακύκλωση κοινής λειτουργικότητας και την επέκταση των χαρακτηριστικών και συμπεριφοράς με νέες παράγωγες κλάσεις.
Στο παρακάτω παράδειγμα, η κλάση Car (παιδί) κληρονομεί τα χαρακτηριστικά και τις μεθόδους από την κλάση Vehicle (γονιός):
#include <iostream> using namespace std; // Βασική κλάση (γονέας) class Vehicle { public: string brand = "Ford"; // Συνάρτηση που εκτελεί τον ήχο της κόρνας void honk() { cout << "Tuut, tuut!" << endl; } }; // Κληρονομημένη κλάση (παιδί) class Car : public Vehicle { public: string model = "Mustang"; }; int main() { Car myCar; // Κλήση της συνάρτησης honk() από το αντικείμενο myCar myCar.honk(); // Εκτύπωση του brand και του model του αυτοκινήτου cout << myCar.brand + " " + myCar.model << endl; return 0; }
Ο παραπάνω κώδικας συνδυάζει ένα παράδειγμα αντικειμενοστραφούς προγραμματισμού στη γλώσσα C++.
Καταρχάς, ορίζουμε δύο κλάσεις: την Vehicle
(Οχήματος) και την Car
(Αυτοκίνητο). Η κλάση Vehicle
είναι η βασική κλάση και περιέχει τη μεταβλητή brand
που αντιπροσωπεύει το μάρκα του οχήματος, καθώς και τη συνάρτηση honk()
που εμφανίζει τον ήχο της κόρνας.
Η κλάση Car
κληρονομεί από την Vehicle
και προσθέτει μια επιπλέον μεταβλητή model
που αντιπροσωπεύει το μοντέλο του αυτοκινήτου.
Στη συνέχεια, στη συνάρτηση main()
, δημιουργούμε ένα αντικείμενο myCar
τύπου Car
. Αυτό το αντικείμενο κληρονομεί τις ιδιότητες της κλάσης Car
, καθώς και της κλάσης Vehicle
από την οποία προέρχεται.
Καλούμε τη συνάρτηση honk()
από το αντικείμενο myCar
, προκαλώντας την εκτέλεση της εντολής εμφάνισης του κλάσματος της κόρνας (“Tuut, tuut!”).
Τέλος, εκτυπώνουμε τη συνένωση του brand
και του model
του αυτοκινήτου, χρησιμοποιώντας τον τελεστή + για τη συνένωση των δύο συμβολοσειρών.
[adinserter block=”2″]
Ο όρος “πολυεπίπεδη κληρονομία” αναφέρεται στη δυνατότητα μιας κλάσης να κληρονομείται από μια άλλη κλάση, η οποία ήδη έχει κληρονομηθεί από μια άλλη κλάση.
Σε απλούστερους όρους, μπορούμε να έχουμε μια κλάση Α που κληρονομεί τη λειτουργικότητα από μια κλάση Β, και ταυτόχρονα μια κλάση Γ να κληρονομεί τη λειτουργικότητα από την κλάση Α. Αυτή η διαδικασία μπορεί να επαναληφθεί για περισσότερες κλάσεις, δημιουργώντας ένα πολυεπίπεδο ιεραρχικό δέντρο κληρονομίας.
Κάθε κλάση σε αυτή την πολυεπίπεδη ιεραρχία κληρονομίας κληρονομεί τις ιδιότητες και τις συναρτήσεις των γονικών της κλάσεων. Αυτό σημαίνει ότι η κλάση Α μπορεί να έχει τη λειτουργικότητα της κλάσης Β και να την επεκτείνει, ενώ η κλάση Γ μπορεί να έχει τη λειτουργικότητα και των κλάσεων Α και Β και να την επεκτείνει περαιτέρω.
Η πολυεπίπεδη κληρονομία επιτρέπει τη δημιουργία περίπλοκων ιεραρχιών κλάσεων με πολλαπλά επίπεδα κληρονομίας, επιτρέποντας την οργάνωση του κώδικα σε μια πιο ιεραρχική και δομημένη μορφή.
Στο παρακάτω παράδειγμα, η κλάση MyGrandChild παράγεται από την κλάση MyChild (η οποία παράγεται από την κλάση MyClass).
#include <iostream> using namespace std; // Βασική κλάση (γονέας) class MyClass { public: void myFunction() { cout << "I am the parent class." << endl; } }; // Κληρονομημένη κλάση (παιδί) class MyChild : public MyClass { }; // Κληρονομημένη κλάση (εγγόνι) class MyGrandChild : public MyChild { }; int main() { MyGrandChild myObj; // Δημιουργία αντικειμένου της κλάσης MyGrandChild myObj.myFunction(); // Κλήση της συνάρτησης myFunction() από το αντικείμενο myObj return 0; }
Ο παραπάνω κώδικας δημιουργεί μια ιεραρχία κλάσεων στη γλώσσα C++.
Συγκεκριμένα:
- Η βασική κλάση ονομάζεται
MyClass
και περιλαμβάνει μια δημόσια μέλος συνάρτησηmyFunction()
, η οποία εκτυπώνει το μήνυμα “I am the parent class.” στην οθόνη. - Η κλάση
MyChild
κληρονομεί από την κλάσηMyClass
χρησιμοποιώντας τη λέξη-κλειδίpublic
. - Η κλάση
MyGrandChild
κληρονομεί από την κλάσηMyChild
χρησιμοποιώντας επίσης τη λέξη-κλειδίpublic
.
Στη συνέχεια, η συνάρτηση main()
δημιουργεί ένα αντικείμενο της κλάσης MyGrandChild
με όνομα myObj
. Αφού δημιουργηθεί το αντικείμενο, καλείται η μέλος συνάρτηση myFunction()
από το αντικείμενο myObj
, προκαλώντας την εκτύπωση του μηνύματος “I am the parent class.” στην οθόνη.
Ο κώδικας εκτελείται και τερματίζει χωρίς να παράγει κάποια επιπρόσθετη λειτουργία. Η εκτύπωση του μηνύματος “I am the parent class.” αποτελεί το αποτέλεσμα της κλήσης της συνάρτησης myFunction()
από το αντικείμενο myObj
.
Η πολλαπλή κληρονομία επιτρέπει σε μια κλάση να κληρονομεί χαρακτηριστικά από περισσότερες από μία βασικές κλάσεις. Για να επιτευχθεί αυτό, μπορούμε να χρησιμοποιήσουμε μια λίστα με τις βασικές κλάσεις, χωρισμένες με κόμματα.
Αναλυτικά, μπορούμε να δηλώσουμε μια κλάση η οποία κληρονομεί από δύο ή περισσότερες βασικές κλάσεις ως εξής:
class DerivedClass : public BaseClass1, public BaseClass2, ... { // Σώμα της κλάσης };
Με αυτόν τον τρόπο, η DerivedClass
κληρονομεί τα χαρακτηριστικά και τις συναρτήσεις τόσο από την BaseClass1
όσο και από την BaseClass2
, και ούτω καθεξής, αν υπάρχουν περισσότερες βασικές κλάσεις που προστίθενται στη λίστα.
Η πολλαπλή κληρονομία επιτρέπει στις παράγωγες κλάσεις να έχουν πρόσβαση σε μέλη και συναρτήσεις από όλες τις βασικές κλάσεις που κληρονομούνται. Αυτό παρέχει ευελιξία και δυνατότητα επαναχρησιμοποίησης κώδικα, καθώς μπορούμε να ορίσουμε συμπεριφορές από πολλαπλές πηγές και να συνδυάσουμε λειτουργικότητες από διάφορες κλάσεις σε ένα αντικείμενο.
Παράδειγμα:
#include <iostream> using namespace std; // Βασική κλάση 1 class MyClass1 { public: void myFunction1() { cout << "Είμαι η πρώτη γονική κλάση." << endl; } }; // Βασική κλάση 2 class MyClass2 { public: void myFunction2() { cout << "Είμαι η δεύτερη γονική κλάση." << endl; } }; // Παράγωγη κλάση class MyChild : public MyClass1, public MyClass2 { }; int main() { MyChild myObj; myObj.myFunction1(); // Κλήση της μεθόδου myFunction1 από την κλάση MyClass1 myObj.myFunction2(); // Κλήση της μεθόδου myFunction2 από την κλάση MyClass2 return 0; }
Ο παραπάνω κώδικας δημιουργεί τρεις κλάσεις: MyClass1
, MyClass2
και MyChild
.
Η κλάση MyClass1
έχει μια δημόσια μέθοδο με όνομα myFunction1
, η οποία εκτυπώνει το μήνυμα “Είμαι η πρώτη γονική κλάση.”.
Η κλάση MyClass2
έχει μια δημόσια μέθοδο με όνομα myFunction2
, η οποία εκτυπώνει το μήνυμα “Είμαι η δεύτερη γονική κλάση.”.
Η κλάση MyChild
είναι μια παράγωγη κλάση που κληρονομεί από τις κλάσεις MyClass1
και MyClass2
.
Στη συνάρτηση main
, δημιουργείται ένα αντικείμενο τύπου MyChild
με όνομα myObj
. Αφού το αντικείμενο αυτό έχει κληρονομήσει τις μεθόδους από τις γονικές κλάσεις, μπορούμε να καλέσουμε τις μεθόδους myFunction1
και myFunction2
για το αντικείμενο myObj
. Οι μέθοδοι αυτές εκτυπώνουν τα αντίστοιχα μηνύματα στην οθόνη.
Έτσι, ο κώδικας εκτυπώνει τα παρακάτω μηνύματα:
Είμαι η πρώτη γονική κλάση. Είμαι η δεύτερη γονική κλάση.
Αυτό συμβαίνει επειδή η κλάση MyChild
κληρονομεί τις μεθόδους myFunction1
από την MyClass1
και myFunction2
από την MyClass2
.
[adinserter block=”3″]
Από την ενότητα που αφορά τους Προσδιοριστές Πρόσβασης στη C++, έχουμε μάθει ότι υπάρχουν τρεις διαθέσιμοι προσδιοριστές: public, private και protected. Μέχρι τώρα, έχουμε χρησιμοποιήσει τον public προσδιοριστή (όπου τα μέλη της κλάσης είναι προσβάσιμα από οποιονδήποτε έξω από την κλάση) και τον private προσδιοριστή (όπου τα μέλη είναι προσπελάσιμα μόνο εντός της κλάσης).
Ο τρίτος προσδιοριστής, το protected, είναι παρόμοιος με τον private προσδιοριστή, αλλά με την επιπλέον δυνατότητα πρόσβασης στις κληρονομημένες κλάσεις. Αυτό σημαίνει ότι τα προστατευόμενα μέλη μιας κλάσης είναι προσπελάσιμα από την κληρονομημένη κλάση, αλλά όχι από οποιονδήποτε έξω από την ιεραρχία της κληρονομίας.
Άρα, ο προσδιοριστής protected επιτρέπει την πρόσβαση στα μέλη της κλάσης από την ίδια την κλάση και από τις κληρονομημένες κλάσεις, ενώ παραμένει απροσπέλαστος από άλλες κλάσεις ή στοιχεία εκτός της ιεραρχίας της κληρονομίας.
Παράδειγμα:
#include <iostream> using namespace std; // Βασική κλάση (Base class) class Vehicle { public: string brand = "Ford"; // Μεταβλητή μέλος που αντιπροσωπεύει τη μάρκα του οχήματος void honk() { // Μέθοδος που παράγει τον ήχο της κόρνας cout << "Tuut, tuut!" << endl; } private: int speedLimit = 120; // Μεταβλητή μέλος που αντιπροσωπεύει το όριο ταχύτητας }; // Κληρονομημένη κλάση (Derived class) class Car : public Vehicle { public: string model = "Mustang"; // Μεταβλητή μέλος που αντιπροσωπεύει το μοντέλο του αυτοκινήτου }; // Κύρια συνάρτηση (Main function) int main() { Car myCar; // Δημιουργία αντικειμένου της κλάσης Car myCar.honk(); // Κλήση της μεθόδου honk() για το αυτοκίνητο myCar cout << myCar.brand + " " + myCar.model << endl; // Εκτύπωση της μάρκας και του μοντέλου του αυτοκινήτου //cout << myCar.speedLimit; // Προκαλεί σφάλμα (error) καθώς η μεταβλητή speedLimit είναι ιδιωτική return 0; }
Ο παραπάνω κώδικας σε C++ περιλαμβάνει τη δήλωση και τον ορισμό δύο κλάσεων, μια βασική κλάση με το όνομα “Vehicle” και μια κληρονομημένη κλάση με το όνομα “Car”.
Η κλάση “Vehicle” διαθέτει μια μεταβλητή μέλος “brand” που αντιπροσωπεύει τη μάρκα του οχήματος, καθώς και μια μέθοδο “honk()” που εμφανίζει τον ήχο της κόρνας. Επιπλέον, περιλαμβάνει μια ιδιωτική μεταβλητή μέλος “speedLimit” που αντιπροσωπεύει το όριο ταχύτητας.
Η κλάση “Car” είναι μια κληρονομημένη κλάση από την κλάση “Vehicle” και περιλαμβάνει μια μεταβλητή μέλος “model” που αντιπροσωπεύει το μοντέλο του αυτοκινήτου.
Στην κύρια συνάρτηση “main()”, δημιουργείται ένα αντικείμενο της κλάσης “Car” με όνομα “myCar”. Κατόπιν, καλείται η μέθοδος “honk()” του αντικειμένου για να εμφανιστεί ο ήχος της κόρνας. Τέλος, εκτυπώνεται η μάρκα και το μοντέλο του αυτοκινήτου, χρησιμοποιώντας τις μεταβλητές μελης “brand” και “model” του αντικειμένου. Σχολιασμένη είναι η γραμμή που προσπαθεί να εκτυπώσει την ταχύτητα (speedLimit), καθώς αυτή η μεταβλητή είναι ιδιωτική και δεν είναι προσβάσιμη από την κύρια συνάρτηση.
Έτσι, ο κώδικας δημιουργεί ένα αντικείμενο αυτοκινήτου τύπου “Car”, το οποίο κληρονομεί τις ιδιότητες της βασικής κλάσης “Vehicle” και επιτρέπει την πρόσβαση στις μεταβλητές μελης και τις μεθόδους της.