Υπερφόρτωση Τελεστών (Συνέχεια) Αντικειμενοστραφής Προγραμματισμός
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 2 Αναφορές: Ξανά * Μία αναφορά (reference) είναι το όνομα μιας θέσης αποθήκευσης. Δηλώνεται χρησιμοποιώντας το σύμβολο «&» μετά τον τύπο. Στο ακόλουθο παράδειγμα: int robert; int& bob=robert; Η bob γίνεται ψευδώνυμο (alias) της μεταβλητής robert. Όποια αλλαγή γίνει στην bob θα ισχύει και για τη robert. Σπάνια χρησιμοποιούμε τις αναφορές όπως παραπάνω. Έχουμε ήδη δει μια χρήση τους: το πέρασμα παραμέτρων με αναφορά. Ουσιαστικά οι αναφορές ισοδυναμούν με σταθερούς δείκτες (const pointers) χωρίς να είναι ταυτόσημες.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 3 Επιστρέφοντας Αναφορές * Όπως ακριβώς μπορεί μια συνάρτηση να επιστρέφει τιμή ενός συγκεκριμένου τύπου, έτσι μπορεί να επιστρέφει και αναφορά σε αυτόν τον τύπο. Η σύνταξη είναι παρόμοια: double& sampleFunction(double& variable); Το «&» πρέπει να μπαίνει τόσο στη δήλωση όσο και στον ορισμό της συνάρτησης. * Η έκφραση επιστροφής πρέπει να είναι κάτι που μπορεί να σχετιστεί με μια αναφορά πχ., μεταβλητή του κατάλληλου τύπου και όχι έκφραση στο στυλ x+5. * Δε θα πρέπει να επιστρέφονται τοπικές μεταβλητές αφού μετά το πέρας της συνάρτησης καταστρέφονται και επομένως καταστρέφονται και οι αναφορές σε αυτές.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 4 Παράδειγμα double& sampleFunction(double& variable){ return variable; } Στη main: double m = 99; cout << sampleFunction(m) << endl; sampleFunction(m) = 42; cout << m << endl; Έξοδος: 99 42
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 5 Χρήση ΠΡΟΣΟΧΗ Όπως έχει ήδη αναφερθεί όταν μία συνάρτηση μέλος επιστρέφει μια μεταβλητή μέλος τύπου κλάσης αυτή πρέπει να επιστρέφεται σαν σταθερή τιμή. Αν επιστραφεί αναφορά τότε μπορεί να παρακαμφθεί το γεγονός ότι η κλάση μέλος είναι private με τον ίδιο τρόπο που εξηγήθηκε όταν μιλούσαμε για την επιστροφή const τιμής. class A{ public: const SomeClass getMember(){return member;}... private: SomeClass member;... }; Θα χρησιμοποιήσουμε επιστροφή αναφορών στην υπερφόρτωση ορισμένων τελεστών.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 6 Υπερφόρτωση τελεστή << * Ο τελεστής << είναι ένας δυαδικός τελεστής. * Όταν γράφουμε: cout << “Welcome Champions League” το πρώτο όρισμα είναι το cout (τύπου ostream) και το δεύτερο όρισμα είναι το αλφαριθμητικό “Welcome Champions League” * Στην τάξη Money για να τυπώσουμε αντικείμενα της κλάσης χρησιμοποιούσαμε την output() ως εξής: Money amount(100); cout << “I have ”; amount.output(); cout << “in my wallet\n”; Αυτό δεν είναι λάθος αλλά καλύτερα ακόμα θα ήταν να μπορούσαμε να γράψουμε: Money amount(100); cout << “I have ” << amount << “in my wallet\n”;
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 7 Υπερφόρτωση τελεστή << Στην έκφραση cout << amount; το πρώτο όρισμα είναι το cout και το δεύτερο το amount. Άρα η δήλωση για την υπερφόρτωση του τελεστή << (σαν φίλια συνάρτηση) θα πρέπει να μοιάζει με: friend operator<<(ostream& outs, const Money& amount); Αλλά τι τιμή πρέπει να επιστρέφει? Ας σκεφτούμε πως αποτιμάται η έκφραση: cout << “I have ” << amount << “in my wallet\n”; O τελεστής << είναι δυαδικός και η αποτίμηση γίνεται ακριβώς όπως και με τον τελεστή + δηλ: ((cout << “I have ”) << amount) << “in my wallet\n”;
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 8 Υπερφόρτωση τελεστή << Για να αποτιμηθεί σωστά το κομμάτι της έκφρασης (cout << “I have ”) << amount πρέπει η (cout << “I have ”) να επιστρέφει cout. Στη συνέχεια για να αποτιμηθεί σωστά η ((cout << “I have ”) << amount) << “in my wallet\n”; θα πρέπει η cout << amount να επιστρέφει και αυτή cout. Εδώ χρειάζεται κάποια προσοχή. Η επιστρεφόμενη τιμή δεν πρέπει να είναι μία τιμή τύπου ostream αλλά το cout. Ο τρόπος για να το πετύχουμε είναι βάζοντας τον τελεστή << να επιστρέφει μία αναφορά σε ostream.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 9 Ορισμός υπερφόρτωσης << ostream& operator<<(ostream& outputStream, const Money& amount){ int absDollars = abs(amount.dollars); int absCents = abs(amount.cents); if (amount.dollars<0 || amount.cents <0) outputStream << "$-"; else outputStream << "$"; outputStream << absDollars; if (absCents>=10) outputStream << '.' << absCents; else outputStream << '.' << '0' << absCents; return outputStream; }
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 10 Ορισμός υπερφόρτωσης >> istream& operator>>(istream& inputStream, Money& amount){ char dollarSign; inputStream >> dollarSign; //hoping if (dollarSign != '$'){ cout << "There is no dollar sign at money input.\n"; exit(1); } double amountAsDouble; inputStream >> amountAsDouble; amount.dollars = amount.dollarsPart(amountAsDouble); amount.cents = amount.centsPart(amountAsDouble); return inputStream; }
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 11 Συζήτηση - Έχοντας υπερφορτώσει τους τελεστές > δε χρειαζόμαστε πλέον τις input() και output(). - Προσέξτε πόσο πιο απλή γίνεται η συγγραφή της main για παράδειγμα: cout << yourAmount << "+" << myAmount << " equals " << ourAmount << endl; Αντί του: yourAmount.output(); cout << "+"; myAmount.output(); cout << "equals "; ourAmount.output(); cout << endl;
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 12 Συζήτηση (2) - Προσέξτε ότι δεν μπορούμε να υπερφορτώσουμε πραγματικά τους τελεστές > σαν μέλη κλάσης αφού το πρώτο όρισμα πρέπει να είναι κάποιο Ι/Ο stream. - Όταν όμως ένας τελεστής υπερφορτώνεται σαν μέλος το πρώτο όρισμα είναι το αντικείμενο που τον καλεί. - Κατά συνέπεια οι τελεστές > δεν είναι δυνατόν να υπερφορτωθούν σαν μέλη διατηρώντας τη συνηθισμένη χρήση τους.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 13 Υπερφόρτωση ++ και -- - Οι τελεστές ++ και -- έχουν 2 χρήσεις: σαν πρόθεμα πχ., ++x και σαν επίθεμα πχ., x--. - Υπερφορτώνοντας τον τελεστή ++ είτε σαν μέλος χωρίς ορίσματα, είτε σαν μη μέλος με ένα όρισμα παίρνουμε τον υπερφορτωμένο προθεματικό τελεστή. - Για να πάρουμε τον επιθεματικό τελεστή προσθέτουμε μια επιπλέον int παράμετρο. Η παράμετρος αυτή δε χρησιμοποιείται και δε χρειάζεται να δώσετε δεύτερο όρισμα όταν πχ., χρησιμοποιείτε x++. Είναι απλά ένα σημάδι στον compiler ώστε να μπορεί να ξεχωρίσει τον προθεματικό από τον επιθεματικό ορισμό.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 14 Παράδειγμα class Money{ public:.. Money operator++(); //adds one dollar (prefix) Money operator++(int); //adds one dollar (postfix).. }; Money Money::operator++(){ dollars++; return Money(dollars, cents); }
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 15 Παράδειγμα Money Money::operator++(int ignoreMe){ int temp = dollars; dollars++; return Money(temp, cents); } int main(){... cout << "Testing the overloading of ++" << endl; cout << "++diffAmount = " << ++diffAmount << endl; cout << "diffAmount = " << diffAmount << endl; cout << "diffAmount++ = " << diffAmount++ << endl; cout << "diffAmount = " << diffAmount << endl;... }
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 16 Παράδειγμα - Όταν έχω cout << ++diffAmount; το ++diffAmount αντικαθίσταται από το αποτέλεσμα της κλήσης του operator++(), το οποίο είναι ένα ανώνυμο αντικείμενο Money που έχει ίδια τιμή με το diffAmount. - Όταν έχω cout << diffAmount++; το diffAmount++ αντικαθίσταται από το αποτέλεσμα της κλήσης του operator++(int), το οποίο είναι ένα ανώνυμο αντικείμενο Money που έχει την αρχική τιμή του diffAmount.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 17 Συζήτηση - Όταν χρησιμοποιούνται οι τελεστές ++ και -- σε βασικούς τύπους πχ. int, ο προθεματικός τελεστής επιστρέφει αναφορά και ο επιθεματικός τιμή. - Επειδή η επιστροφή αναφοράς με τους τελεστές ++ και -- ανοίγει την πόρτα σε πολλά προβλήματα όταν θα τους υπερφορτώνουμε στις κλάσεις μας θα χρησιμοποιούμε επιστροφή τιμής (κατά το παράδειγμα).
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 18 l-value και r-value - Ο όρος l-value χρησιμοποιείται για παραστάσεις που μπορούν να εμφανιστούν στα αριστερά ενός τελεστή εκχώρησης. - Ο όρος r-value χρησιμοποιείται για παραστάσεις που μπορούν να εμφανιστούν μόνο στα δεξιά ενός τελεστή εκχώρησης. - Αν θέλετε το αντικείμενο που επιστρέφεται να είναι l-value θα πρέπει να επιστραφεί αναφορά.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 19 Υπερφόρτωση [ ] - Υπερφορτώνεται μόνο σαν μέλος. - Στην έκφραση a[2], a είναι το καλούν αντικείμενο και το 2 είναι μία παράμετρος θέσης (index). - Αν θέλουμε να έχουμε χρήση ως l-value θα πρέπει να επιστραφεί αναφορά. - Έστω ότι έχω Money m; και θέλω με m[0] να επιστρέφονται τα δολάρια και με m[1] τα cents. - Θα πρέπει να επιστρέφεται μια αναφορά int.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 20 Παράδειγμα int& Money::operator[](int index){ if (index == 0) return dollars; else if (index == 1) return cents; else { cout << "Invalid index\n"; exit(1); } Στη main: cout << "Testing the overloading of []\n"; cout << "diffAmount.dollars = " << diffAmount[0] << endl; cout << "diffAmount.dollars = " << diffAmount[1] << endl; diffAmount[0]=10; cout << "diffAmount.dollars = " << diffAmount.getDollars() << endl;
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 21 Υπερφόρτωση = - Πρέπει να υπερφορτώνεται ως μέλος. - Αν δεν υπερφορτώνεται τότε κατασκευάζεται αυτόματα από τον compiler. - Ο προεπιλεγμένος τελεστής = το μόνο που κάνει είναι να αντιγράφει τις τιμές των μεταβλητών μελών. - Για απλές κλάσεις αυτό αρκεί. Θα δούμε όμως και παραδείγματα όπου θα χρειαστεί να υπερφορτώνουμε εμείς τον τελεστή =.
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 22 Υπερφόρτωση ως L-value και R- value - Μπορούμε να υπερφορτώσουμε μια συνάρτηση/τελεστή έτσι ώστε να έχει διαφορετική συμπεριφορά όταν χρησιμοποιείται σαν l-value και r-value. Αν και δε θα χρησιμοποιήσουμε αυτή τη δυνατότητα εντούτοις το γενικό παράδειγμα έχει ως εξής: class SomeClass{ public: int& f(); //used as l-value const int& f() const; //used as r-value … };
Αντικειμενοστραφής Προγραμματισμός Υπερφόρτωση Τελεστών (Συνέχεια)/ Slide 23 Γενικοί κανόνες για υπερφόρτωση τελεστών - Τουλάχιστον μια παράμετρος (αν ο τελεστής δεν είναι μέλος) πρέπει να είναι τύπου κλάσης. - Οι περισσότεροι τελεστές μπορούν να υπερφορτωθούν σαν: μέλη, μη μέλη, φίλιες συναρτήσεις. - Οι παρακάτω τελεστές υπερφορτώνονται μόνο σαν (μη στατικά) μέλη μιας κλάσης: =, [ ], -> και (). - Δεν μπορούμε να δημιουργήσουμε καινούργιο τελεστή μόνο να αλλάξουμε τη σημασία των ήδη υπαρχόντων. - Δεν μπορούμε να αλλάξουμε το πλήθος ορισμάτων που δέχεται ένας τελεστής, καθώς και την προτεραιότητά του. - Οι παρακάτω τελεστές δεν μπορούν να υπερφορτωθούν: “.”, “::”, “?:” - Υπερφορτωμένος τελεστής δεν μπορεί να έχει προεπιλεγμένα ορίσματα.