====== Κληρονομικότητα πολλών γονικών κλάσεων ====== Η C++ δίνει την δυνατότητα να έχετε περισσότερες από μία γονικές κλάσεις για μία κλάση απόγονο. Εκ πρώτης όψεως αυτό έρχεται σε αντίθεση με τη βασική αρχή στη Java που επιτρέπει μόνο μία γονική κλάση κατά την διαδικασία της κληρονομικότητας. Γενικά, η πολλαπλή κληρονομικότητα θα πρέπει να χρησιμοποιείται με πολύ προσοχή και όπου είναι δυνατόν να αποφεύγεται. Δεν πρέπει όμως να ξεχνάμε ότι τα //interfaces// στη C+ ορίζονται μέσω //abstract// κλάσεων. Έτσι εάν θέλετε μία κλάση να υλοποιεί ένα //interface// και παράλληλα να είναι απόγονος άλλης κλάσης τότε δεν έχετε παρά να χρησιμοποιήσετε πολλαπλή κληρονομικότητα. Ας υποθέσουμε ότι έχετε την κλάση //LinkedStack// η οποία υλοποιεί το interface //Stack// και παράλληλα χρησιμοποιεί μία συνδεδεμένη λίστα για να υλοποιήσει τη επιθυμητή λειτουργικότητα της στοίβας. Ας υποθέσουμε ότι η συγκεκριμένη κλάση επεκτείνει την κλάση //LinkedList//. Σε αυτή την περίπτωση η κλάση //LinkedStack// θα κληρονομεί δύο κλάσεις //α)// την //abstract// κλάση //Stack// και //β)// την κλάση //LinkedList//. Δείτε τον παρακάτω κώδικα που αποτελεί υλοποιεί τον σχήμα που περιγράψαμε. #include using namespace std; #ifndef __STACK_H__ #define __STACK_H__ class Stack { public: virtual int size()=0; virtual void push(int o)=0; virtual int pop()=0; virtual int top()=0; }; #endif #include using namespace std; #ifndef __LINKEDLIST_H__ #define __LINKEDLIST_H__ class LinkedNode { int element; LinkedNode *next; public: LinkedNode(LinkedNode *nxt, int e); LinkedNode(int e); int getElement(); LinkedNode* getNext(); void setElement(int e); void setNext(LinkedNode *nxt); }; class LinkedList { LinkedNode *head; int listSize; public: LinkedList(); int size(); void insertFirst(int o); void insertAfter(LinkedNode *prev, int o); int deleteFirst(); int deleteAfter(LinkedNode *prev); int valueAt(int pos); }; #endif #include "LinkedList.h" /* LinkedNode functions * */ LinkedNode::LinkedNode(LinkedNode *nxt, int e) { next = nxt; element = e; } LinkedNode::LinkedNode(int e) : LinkedNode(NULL,e) { } int LinkedNode::getElement() { return element; } LinkedNode* LinkedNode::getNext() { return next; } void LinkedNode::setElement(int e) { element = e; } void LinkedNode::setNext(LinkedNode *nxt) { next = nxt; } /* LinkedList functions * */ LinkedList::LinkedList() { listSize = 0; head = NULL; } int LinkedList::size() { return listSize; } void LinkedList::insertFirst(int o) { LinkedNode *node = new (nothrow) LinkedNode(head, o); head = node; listSize++; } void LinkedList::insertAfter(LinkedNode *prev, int o) { LinkedNode *node = new (nothrow) LinkedNode(prev->getNext(), o); prev->setNext(node); listSize++; } int LinkedList::deleteFirst() { if(size()==0) return -1; LinkedNode *node = head; head = head->getNext(); int o = node->getElement(); delete node; listSize--; return o; } int LinkedList::deleteAfter(LinkedNode *prev) { LinkedNode *node = prev->getNext(); prev->setNext(node->getNext()); int o = node->getElement(); delete node; listSize--; return o; } int LinkedList::valueAt(int pos) { if(pos >= size() ) return -1; LinkedNode *curr = head; for(int i=0; igetNext() ) ; return curr->getElement(); } #include using namespace std; #include "LinkedList.h" #include "Stack.h" #ifndef __LINKEDSTACK_H__ #define __LINKEDSTACK_H__ class LinkedStack : public Stack, private LinkedList { public: LinkedStack(); int size(); void push(int o); int pop(); int top(); }; #endif #include "LinkedStack.h" LinkedStack::LinkedStack() {} int LinkedStack::size() { return LinkedList::size(); } void LinkedStack::push(int o) { insertFirst(o); } int LinkedStack::pop() { return deleteFirst(); } int LinkedStack::top() { return valueAt(0); } #include "LinkedStack.h" #define ARRAY_SIZE 10 void invertArray(int array[], int arraySize, Stack &stack) { for(int i=0; i Ο κώδικας μεταγλωττίζεται ως εξής: g++ -g -std=c++11 -o stack StackUsage.cpp ArrayStack.cpp LinkedList.cpp LinkedStack.cpp Σε αυτό το σημείο θα πρέπει να σημειώσουμε ότι η επιλογή της κληρονομικότητας στη συγκεκριμένη περίπτωση **είναι λανθασμένη** και έγινε μόνο και μόνο για λόγους παρουσίασης της πολλαπλής κληρονομικότητας. Όπως έχουμε εξηγήσει και σε προηγούμενες ενότητες το κριτήριο για να χρησιμοποιήσουμε κληρονομικότητα ή όχι είναι εάν ο νέος τύπος που προκύπτει από τη σχέση κληρονομικότητα διατηρεί όλα τα χαρακτηριστικά του γονικού τύπου. Στο συγκεκριμένο παράδειγμα, το ερώτημα που πρέπει να τεθεί είναι εάν η κλάση //LinkedStack// είναι και //LinkedList//. Η απάντηση είναι αρνητική, πράγμα που οδηγεί στο συμπέρασμα ότι η επιλογή τη κληρονομικότητας είναι λανθασμένη. Σωστότερη επιλογή θα ήταν η κλάση //LinkedList// να αποτελεί πεδίο της κλάσης //LinkedStack//. **Προκειμένου να μην δημοσιοποιήσουμε την παραπάνω λάθος σχέση κληρονομικόητας**, ορίζουμε τη σχέση κληρονομικότητας της κλάσης //LinkedStack// σε σχέση με την κλάση //LinkedList// ως //private//. Σε αυτή την περίπτωση τα //public// και //protected// μέλη της κλάσης //LinkedList// γίνονται //private// στην κλάση //LinkedStack//. Έτσι οι κλάσεις ή οι συναρτήσεις που χρησιμοποιούν τη //LinkedStack// είναι αδύνατο να γνωρίζουν ότι η συγκεκριμένη κλάση είναι απόγονος της //LinkedList//. Επίσης είναι αδύνατο να χρησιμοποιήσουν κάποια από τις μεθόδους της //LinkedList// μέσω της //LinkedStack//. Εάν η σχέση κληρονομικότητας είχε οριστεί //__public__//, τότε οι μέθοδοι και οι κλάσεις που χρησιμοποιούν τη //LinkedStack// θα γνωρίζουν τη σχέση κληρονομικότητας και θα μπορούν να χρησιμοποιήσουν τα //public// μέλη της κλάσης //LinkedList//. Επίσης, οι απόγονοι θα μπορούν να χρησιμοποιήσουν τα //public// και //protected// μέλη της κλάσης //LinkedList//. Αντίστοιχα, εάν η σχέση κληρονομικότητας είχε οριστεί //__protected__//, τότε οι απόγονοι της //LinkedStack// θα γνωρίζουν τη σχέση κληρονομικότητας και θα μπορούν να χρησιμοποιήσουν τα //public// και //protected// μέλη της κλάσης //LinkedList//.