Ένας δυαδικός τελεστής, όπως, για παράδειγμα, ο τελεστής πρόσθεσης + πρέπει να οριστεί είτε ως μη στατική συνάρτηση μέλους κλάσης με μία παράμετρο είτε ως συνάρτηση μέλους μη κλάσης με δύο παραμέτρους.

Μηνύματα μεταγλωττιστή που σας δίνονται

Main.cpp:17:5: σφάλμα: Το C++ απαιτεί έναν προσδιοριστή τύπου για όλες τις δηλώσεις operator+(Cat a, Cat b) ( ^ main.cpp:18:16: σφάλμα: δεν είναι δυνατή η προετοιμασία του αντικειμένου επιστροφής τύπου "int" με μια τιμή rvalue τύπου "Cat *" επιστροφή νέα Cat(a.value + b.value); ^~~~~~~~~~~~~~~~~~~~~~~~~

πείτε ότι ο τελεστής που ορίσατε δεν έχει τύπο επιστροφής.

Μόνο οι συναρτήσεις μετατροπής ενδέχεται να μην έχουν τύπο επιστροφής. Στην περίπτωση του τελεστή προσθήκης, πρέπει να καθορίσετε τον τύπο της τιμής επιστροφής.

Όταν προσθέτετε δύο αντικείμενα κλάσης, δεν έχει νόημα να επιστρέψετε έναν δείκτη στο αντικείμενο. Σε αυτήν την περίπτωση, δεν θα μπορείτε να συνδέσετε τελεστές προσθήκης χωρίς πρόσθετους τελεστές και, επιπλέον, μπορεί να διαρρεύσει μνήμη.

Ο χειριστής πρέπει να επιστρέψει το ίδιο το αντικείμενο, είτε με είτε χωρίς τον προσδιορισμό const.

Όπως αναφέρθηκε παραπάνω, ένας τελεστής μπορεί να δηλωθεί ως μη στατική συνάρτηση μέλους κλάσης με μία παράμετρο.

Σε αυτήν την περίπτωση, ο τελεστής + μπορεί να μοιάζει με αυτό

Κατηγορία Cat ( private: int value = 1; public: Cat(int _value) (value = _value; ) Cat operator +(const Cat &a) const ( return Cat(this->value + a.value); ) ) ;

Κατηγορία Cat ( private: int value = 1; public: Cat(int _value) (value = _value; ) const Cat operator +(const Cat &a) const ( return Cat(this->value + a.value); ) )

Θα μπορούσατε να υπερφορτώσετε τον τελεστή και για αναφορές rvalue, όπως στο

χειριστής Cat +(const Cat &&a) const ( return Cat(this->value + a.value); )

χειριστής Cat +(Cat &&a) const ( return Cat(this->value + a.value); )

αλλά για αυτό απλή τάξη, που δεν συλλαμβάνει μεγάλους πόρους, δεν έχει σημασία.

Σημειώστε την παρουσία του προσδιοριστικού condt μετά τη λίστα παραμέτρων. Αυτό σημαίνει ότι το ίδιο το αντικείμενο, που θα υπάρχει στην αριστερή πλευρά του τελεστή, δεν θα αλλάξει, όπως το δεξί αντικείμενο, αφού η παράμετρος τελεστή που αντιστοιχεί σε αυτό ορίζεται επίσης με τον προσδιορισμό const.

Λάβετε υπόψη ότι εφόσον ο κατασκευαστής κλάσης δηλώνεται ως κατασκευαστής μετατροπής, μπορείτε να προσθέσετε αντικείμενα Cat με αριθμούς σε αυτήν την περίπτωση. Για παράδειγμα,

#περιλαμβάνω class Cat ( private: int value = 1; public: Cat(int _value) ( ​​ value = _value; ) const Operator Cat +(const Cat &a) const ( return Cat(this->

Το αν αυτό είναι δικαιολογημένο εξαρτάται από εσάς να αποφασίσετε, με βάση το νόημα που αποδίδεται σε αυτόν τον τελεστή προσθήκης. Εάν δεν θέλετε να επιτρέψετε μια τέτοια σιωπηρή μετατροπή από έναν αριθμό σε ένα αντικείμενο Cat, τότε μπορείτε να δηλώσετε τον κατασκευαστή ως ρητό. Σε αυτήν την περίπτωση, το πρόγραμμα δεν θα μεταγλωττιστεί εάν προσπαθήσει να προσθέσει ένα αντικείμενο Cat σε έναν αριθμό.

#περιλαμβάνω class Cat ( private: int value = 1; public: explicit Cat(int _value) ( value = _value; ) const Cat operator +(const Cat &a) const ( return Cat(this->value + a.value); ) ) ; int main() ( Cat c1(10); c1 + 5.5; return 0; )

Για αυτό το πρόγραμμα, ο μεταγλωττιστής θα δημιουργήσει ένα μήνυμα σφάλματος παρόμοιο με το παρακάτω

Prog.cpp:24:5: σφάλμα: δεν ταιριάζει με το "operator+" (οι τύποι τελεστών είναι "Cat" και "double") c1 + 5.5; ^

Ο δεύτερος τρόπος για να δηλώσετε αυτόν τον τελεστή είναι να τον δηλώσετε ως συνάρτηση, η οποία δεν είναι μέλος της κλάσης. Εφόσον αυτή η συνάρτηση πρέπει να έχει πρόσβαση στην τιμή μέλους ιδιωτικής κλάσης , θα πρέπει να δηλωθεί ως συνάρτηση φίλου της κλάσης.

Μπορείτε να ορίσετε την ίδια τη συνάρτηση τόσο εντός του ορισμού κλάσης όσο και εκτός αυτής.

Για παράδειγμα,

#περιλαμβάνω κατηγορία Cat ( private: int value = 1; public: Cat(int _value) (value = _value; ) friend const Cat operator +(const Cat &a, const Cat &b) ( return Cat(a.value + b.value ); ) ); int main() ( Cat c1(10); Cat c2(5); Cat c3 = c1 + c2; return 0; )

Λάβετε υπόψη ότι είναι κακή ιδέα να δηλώνετε μεταβλητές που ξεκινούν με υπογράμμιση. Γενικά, τέτοια ονόματα που ξεκινούν με υπογράμμιση προορίζονται για τον μεταγλωττιστή.

Είναι σύνηθες στις παραμέτρους του κατασκευαστή να δίνονται ονόματα που αντιστοιχούν στα ονόματα των μελών της κλάσης. Σε αυτήν την περίπτωση, μπορείτε να δείτε αμέσως ποια παράμετρος αρχικοποιεί ποιο μέλος της κλάσης. Θα μπορούσατε λοιπόν να ορίσετε έναν κατασκευαστή σαν αυτόν

Cat(int value) : value(value) ( ​​)

Εάν η τιμή του μέλους κλάσης δεν μπορεί να λάβει αρνητικές τιμές, τότε είναι προτιμότερο να δηλωθεί αυτή και η αντίστοιχη παράμετρος κατασκευαστή ότι έχει τον τύπο unsigned int .

Καλή μέρα!

Η επιθυμία να γραφτεί αυτό το άρθρο εμφανίστηκε μετά την ανάγνωση της ανάρτησης, επειδή πολλά σημαντικά θέματα δεν αποκαλύφθηκαν σε αυτό.

Το πιο σημαντικό πράγμα που πρέπει να θυμάστε είναι ότι η υπερφόρτωση χειριστή είναι απλώς ένας πιο βολικός τρόπος για να καλέσετε λειτουργίες, επομένως μην παρασυρθείτε με την υπερφόρτωση του χειριστή. Θα πρέπει να χρησιμοποιείται μόνο όταν διευκολύνει τη σύνταξη κώδικα. Όχι όμως τόσο που να δυσκολεύει το διάβασμα. Άλλωστε, όπως γνωρίζετε, ο κώδικας διαβάζεται πολύ πιο συχνά από ό, τι γράφεται. Και μην ξεχνάτε ότι δεν θα σας επιτραπεί ποτέ να υπερφορτώνετε τελεστές σε συνδυασμό με ενσωματωμένους τύπους, μόνο προσαρμοσμένοι τύποι/κλάσεις μπορούν να υπερφορτωθούν.

Σύνταξη υπερφόρτωσης

Η σύνταξη για την υπερφόρτωση τελεστή είναι πολύ παρόμοια με τον ορισμό μιας συνάρτησης με όνομα [email προστατευμένο], όπου @ είναι το αναγνωριστικό του τελεστή (π.χ. +, -,<<, >>). Σκεφτείτε το απλούστερο παράδειγμα:
class Integer ( private: int value; public: Integer(int i): value(i) () const Ακέραιος τελεστής+(const Integer& rv) const ( return (value + rv.value); ) );
Σε αυτήν την περίπτωση, ο τελεστής πλαισιώνεται ως μέλος της κλάσης, το όρισμα καθορίζει την τιμή που βρίσκεται στη δεξιά πλευρά του τελεστή. Γενικά, υπάρχουν δύο κύριοι τρόποι υπερφόρτωσης τελεστών: καθολικές λειτουργίες, φιλικές προς την τάξη ή ενσωματωμένες λειτουργίες της ίδιας της τάξης. Ποια μέθοδος, για ποιον χειριστή είναι καλύτερος, θα εξετάσουμε στο τέλος του θέματος.

Στις περισσότερες περιπτώσεις, οι τελεστές (εκτός από όρους) επιστρέφουν ένα αντικείμενο ή μια αναφορά στον τύπο στον οποίο αναφέρονται τα ορίσματά του (εάν οι τύποι είναι διαφορετικοί, τότε αποφασίζετε πώς να ερμηνεύσετε το αποτέλεσμα της αξιολόγησης του τελεστή).

Υπερφόρτωση Unary Operators

Εξετάστε παραδείγματα υπερφόρτωσης unary τελεστών για την κλάση Integer που ορίστηκε παραπάνω. Ταυτόχρονα, θα τις ορίσουμε ως συναρτήσεις φίλου και θα εξετάσουμε τους τελεστές μείωσης και αύξησης:
κλάση Integer ( private: int value; public: Integer(int i): value(i) () //unary + friend const Ακέραιος& τελεστής+(const Integer& i); //unary - φίλος const Ακέραιος τελεστής-(const Integer& i) . Ακέραιος τελεστής--(Integer& i, int); ); // unary plus δεν κάνει τίποτα. const Ακέραιος& operator+(const Integer& i) ( return i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //prefix version επιστρέφει τιμή μετά την αύξηση const Ακέραιος& operator++(Integer& i) ( i.value++; return i; ) //postfix version επιστρέφει την τιμή πριν από την αύξηση const Ακέραιος τελεστής++(Integer& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //επιστρέφει η έκδοση προθέματος η τιμή μετά τη μείωση const Ακέραιος& operator--(Integer& i) ( i.value--; return i; ) //postfix version επιστρέφει τιμή πριν από τη μείωση const Ακέραιος τελεστής--(Integer& i, int) ( Integer oldValue(i.value ); i .value--; επιστροφή oldValue;)
Τώρα ξέρετε πώς ο μεταγλωττιστής διακρίνει μεταξύ των εκδόσεων προθέματος και μετάθεμα της μείωσης και της αύξησης. Στην περίπτωση που δει την έκφραση ++i, τότε καλείται η συνάρτηση τελεστή++(α). Αν δει i++, τότε καλείται ο τελεστής++(a, int). Δηλαδή, καλείται η συνάρτηση υπερφορτωμένου operator++ και για αυτό χρησιμοποιείται η παράμετρος dummy int στην έκδοση postfix.

Δυαδικοί τελεστές

Εξετάστε τη σύνταξη για την υπερφόρτωση δυαδικών τελεστών. Ας υπερφορτώσουμε έναν τελεστή που επιστρέφει μια τιμή l, ένα υπό όρους χειριστήκαι μια δήλωση που δημιουργεί μια νέα τιμή (τις ορίζουμε συνολικά):
κλάση Integer ( private: int value; public: Integer(int i): value(i) () friend const Ακέραιος τελεστής+(const Ακέραιος& αριστερά, const Ακέραιος& δεξιά); φίλος Ακέραιος& χειριστής+=(Ακέραιος& αριστερά, const Ακέραιος& δεξιά); φίλος; bool operator==(const Integer& αριστερά, const Integer& δεξιά); ); const Ακέραιος τελεστής+(const Ακέραιος& αριστερά, const Ακέραιος& δεξιά) ( return Integer(left.value + right.value); ) Integer& operator+=(Integer& left, const Integer& right) ( left.value += right.value; return left; ) bool operator==(const Integer& left, const Integer& right) ( return left.value == right.value; )
Σε όλα αυτά τα παραδείγματα, οι τελεστές είναι υπερφορτωμένοι για τον ίδιο τύπο, ωστόσο, αυτό δεν απαιτείται. Είναι δυνατόν, για παράδειγμα, να υπερφορτωθεί η προσθήκη του τύπου Integer μας και ενός Float που ορίζεται στην ομοιότητα του.

Ορίσματα και τιμές επιστροφής

Όπως μπορείτε να δείτε, τα παραδείγματα χρησιμοποιούν διάφορους τρόπουςμεταβίβαση ορισμάτων σε συναρτήσεις και επιστροφή τιμών τελεστή.
  • Εάν το όρισμα δεν τροποποιηθεί από τον τελεστή, στην περίπτωση, για παράδειγμα, ενός μοναρίου συν, πρέπει να μεταβιβαστεί ως αναφορά σε μια σταθερά. Γενικά, αυτό ισχύει για όλους σχεδόν τους αριθμητικούς τελεστές (πρόσθεση, αφαίρεση, πολλαπλασιασμός...)
  • Ο τύπος της επιστρεφόμενης τιμής εξαρτάται από τη φύση του τελεστή. Εάν ο χειριστής πρέπει να επιστρέψει μια νέα τιμή, τότε πρέπει να δημιουργηθεί ένα νέο αντικείμενο (όπως στην περίπτωση του δυαδικού συν). Εάν θέλετε να αποτρέψετε την αλλαγή ενός αντικειμένου ως τιμή l, τότε θα πρέπει να το επιστρέψετε ως const.
  • Οι χειριστές ανάθεσης πρέπει να επιστρέψουν μια αναφορά στο αλλαγμένο στοιχείο. Επίσης, εάν θέλετε να χρησιμοποιήσετε τον τελεστή εκχώρησης σε δομές όπως (x=y).f(), όπου καλείται η συνάρτηση f() για τη μεταβλητή x, αφού της αντιστοιχίσετε το y, τότε μην επιστρέψετε αναφορά σε μια σταθερά, απλώς επιστρέψτε μια αναφορά.
  • Οι λογικοί τελεστές θα πρέπει να επιστρέφουν το int στη χειρότερη και το bool στην καλύτερη.

Βελτιστοποίηση απόδοσης αξίας

Όταν δημιουργείτε νέα αντικείμενα και τα επιστρέφετε από μια συνάρτηση, θα πρέπει να χρησιμοποιείτε τη σημείωση όπως στο παράδειγμα τελεστή δυαδικού συν που περιγράφεται παραπάνω.
return Integer(left.value + right.value);
Για να είμαι ειλικρινής, δεν ξέρω ποια κατάσταση είναι σχετική για την C++11, όλα τα παρακάτω επιχειρήματα ισχύουν για την C++98.
Με την πρώτη ματιά, αυτό μοιάζει με τη σύνταξη για τη δημιουργία ενός προσωρινού αντικειμένου, που σημαίνει ότι δεν υπάρχει διαφορά μεταξύ του παραπάνω κώδικα και αυτού:
Ακέραιος temp(left.value + right.value); θερμοκρασία επιστροφής?
Αλλά στην πραγματικότητα, σε αυτήν την περίπτωση, ο κατασκευαστής θα κληθεί στην πρώτη γραμμή, μετά θα κληθεί ο κατασκευαστής αντιγραφής, ο οποίος θα αντιγράψει το αντικείμενο και, στη συνέχεια, όταν ξετυλιχθεί η στοίβα, θα κληθεί ο καταστροφέας. Όταν χρησιμοποιείται η πρώτη καταχώρηση, ο μεταγλωττιστής αρχικά δημιουργεί ένα αντικείμενο στη μνήμη στο οποίο πρέπει να αντιγραφεί, αποθηκεύοντας έτσι μια κλήση στον κατασκευαστή αντιγραφής και τον καταστροφέα.

Ειδικοί Χειριστές

Υπάρχουν τελεστές στη C++ που έχουν συγκεκριμένη σύνταξη και μέθοδο υπερφόρτωσης. Για παράδειγμα, ο τελεστής ευρετηρίου . Ορίζεται πάντα ως μέλος της κλάσης και δεδομένου ότι η συμπεριφορά του ευρετηριασμένου αντικειμένου ως πίνακα προορίζεται, θα πρέπει να επιστρέψει μια αναφορά.
χειριστής κόμματος
Οι «ειδικοί» τελεστές περιλαμβάνουν και τον τελεστή κόμματος. Καλείται για αντικείμενα που έχουν κόμμα δίπλα τους (αλλά δεν καλείται στις λίστες ορισμάτων συναρτήσεων). Η δημιουργία ενός ουσιαστικού παραδείγματος χρήσης αυτού του τελεστή δεν είναι τόσο εύκολη. Ο Habrauser στα σχόλια του προηγούμενου άρθρου σχετικά με την υπερφόρτωση .
Χειριστής αποαναφοράς δείκτη
Η υπερφόρτωση αυτών των τελεστών μπορεί να δικαιολογηθεί για έξυπνες κατηγορίες δεικτών. Αυτός ο τελεστής ορίζεται απαραίτητα ως συνάρτηση κλάσης και του επιβάλλονται ορισμένοι περιορισμοί: πρέπει να επιστρέψει είτε ένα αντικείμενο (ή μια αναφορά), είτε έναν δείκτη που σας επιτρέπει να έχετε πρόσβαση στο αντικείμενο.
χειριστή ανάθεσης
Ο τελεστής εκχώρησης ορίζεται απαραίτητα ως συνάρτηση κλάσης επειδή είναι άρρηκτα συνδεδεμένος με το αντικείμενο στα αριστερά του "=". Ο καθολικός καθορισμός του τελεστή εκχώρησης θα επέτρεπε την παράκαμψη της τυπικής συμπεριφοράς του τελεστή "=". Παράδειγμα:
κλάση Integer ( private: int value; public: Integer(int i): value(i) () Integer& operator=(const Integer& right) ( // έλεγχος για αυτο-ανάθεση if (this == &right) ( return *this; ) value = right.value; return *this; ) );

Όπως μπορείτε να δείτε, στην αρχή της λειτουργίας, γίνεται έλεγχος για αυτο-ανάθεση. Γενικά, σε αυτή την περίπτωση, η αυτο-ανάθεση είναι ακίνδυνη, αλλά η κατάσταση δεν είναι πάντα τόσο απλή. Για παράδειγμα, εάν το αντικείμενο είναι μεγάλο, μπορείτε να αφιερώσετε πολύ χρόνο κάνοντας περιττή αντιγραφή ή όταν εργάζεστε με δείκτες.

Χειριστές χωρίς υπερφόρτωση
Ορισμένοι τελεστές στη C++ δεν είναι καθόλου υπερφορτωμένοι. Προφανώς, αυτό γίνεται για λόγους ασφαλείας.
  • Ο τελεστής επιλογής μέλους τάξης ".".
  • Δείκτης προς τελεστή αποαναφοράς μέλους κλάσης ".*"
  • Δεν υπάρχει τελεστής εκθέσεως στη C++ (όπως στο Fortran) "**".
  • Απαγορεύεται να ορίσετε τους χειριστές σας (είναι πιθανά προβλήματα με την ιεράρχηση προτεραιοτήτων).
  • Η προτεραιότητα χειριστή δεν μπορεί να αλλάξει
Όπως έχουμε ήδη ανακαλύψει, υπάρχουν δύο τρόποι τελεστών - με τη μορφή μιας συνάρτησης κλάσης και με τη μορφή μιας καθολικής συνάρτησης φίλου.
Ο Rob Murray, στο βιβλίο του C++ Strategies and Tactics, έχει προσδιορίσει τις ακόλουθες οδηγίες για την επιλογή της φόρμας χειριστή:

Γιατί αυτό? Πρώτον, ορισμένοι χειριστές είναι αρχικά περιορισμένοι. Γενικά, εάν δεν υπάρχει διαφορά σημασιολογικά πώς να ορίσετε έναν τελεστή, τότε είναι καλύτερα να τον τακτοποιήσετε ως συνάρτηση κλάσης για να τονίσετε τη σύνδεση, συν, επιπλέον, η συνάρτηση θα είναι ενσωματωμένη (inline). Επιπλέον, μερικές φορές μπορεί να είναι απαραίτητο να αναπαραστήσουμε τον αριστερό τελεστή με ένα αντικείμενο άλλης κλάσης. Ίσως το πιο εντυπωσιακό παράδειγμα είναι ο επαναπροσδιορισμός<< и >> για ροές I/O.

Πολλές γλώσσες προγραμματισμού χρησιμοποιούν τελεστές: τουλάχιστον αναθέσεις (= , := ή παρόμοια) και αριθμητικοί τελεστές(+ , - , * και /). Στις περισσότερες στατικά πληκτρολογημένες γλώσσες, αυτοί οι τελεστές συνδέονται με τύπους. Για παράδειγμα, στην Java, η προσθήκη με τον τελεστή + είναι δυνατή μόνο για ακέραιους αριθμούς, αριθμούς κινητής υποδιαστολής και συμβολοσειρές. Αν ορίσουμε τις δικές μας κλάσεις για μαθηματικά αντικείμενα, όπως πίνακες, μπορούμε να εφαρμόσουμε μια μέθοδο για την πρόσθεσή τους, αλλά μπορεί να καλείται μόνο με κάτι σαν αυτό: a = b.add(c) .

Στην C++, δεν υπάρχει τέτοιος περιορισμός - μπορούμε να υπερφορτίσουμε σχεδόν οποιονδήποτε γνωστό τελεστή. Οι δυνατότητες είναι ατελείωτες: μπορείτε να επιλέξετε οποιονδήποτε συνδυασμό τύπων τελεστών, ο μόνος περιορισμός είναι ότι πρέπει να υπάρχει τουλάχιστον ένας τελεστής τύπου που ορίζεται από το χρήστη. Δηλαδή, ορίστε έναν νέο τελεστή σε ενσωματωμένους τύπους ή ξαναγράψτε έναν υπάρχοντα ειναι ΑΠΑΓΟΡΕΥΜΕΝΟ.

Πότε πρέπει να υπερφορτώνετε τους χειριστές;

Θυμηθείτε το κύριο πράγμα: υπερφόρτωση τελεστών εάν και μόνο εάν έχει νόημα. Αν δηλαδή η έννοια της υπερφόρτωσης είναι προφανής και δεν φέρει κρυφές εκπλήξεις. Οι υπερφορτωμένοι χειριστές θα πρέπει να ενεργούν το ίδιο με τις βασικές τους εκδόσεις. Φυσικά, οι εξαιρέσεις επιτρέπονται, αλλά μόνο σε περιπτώσεις που συνοδεύονται από κατανοητές εξηγήσεις. Καλό παράδειγμαείναι οι χειριστές<< и >> τυπική βιβλιοθήκη iostream, η οποία σαφώς συμπεριφέρεται διαφορετικά από την κανονική.

Ακολουθούν καλά και κακά παραδείγματα υπερφόρτωσης χειριστή. Η παραπάνω προσθήκη μήτρας είναι μια ενδεικτική περίπτωση. Εδώ η υπερφόρτωση του τελεστή προσθήκης είναι διαισθητική και, αν εφαρμοστεί σωστά, είναι αυτονόητη:

Πίνακας a, b; Πίνακας c = a + b;

Ένα παράδειγμα κακής υπερφόρτωσης τελεστή προσθήκης θα ήταν η προσθήκη αντικειμένων δύο παίκτη σε ένα παιχνίδι. Τι εννοούσε ο δημιουργός της τάξης; Ποιο θα είναι το αποτέλεσμα; Δεν γνωρίζουμε τι κάνει η λειτουργία και επομένως είναι επικίνδυνη η χρήση αυτού του χειριστή.

Πώς να υπερφορτώνετε τους χειριστές;

Η υπερφόρτωση χειριστή είναι παρόμοια με την υπερφόρτωση συναρτήσεων με ειδικά ονόματα. Στην πραγματικότητα, όταν ο μεταγλωττιστής βλέπει μια έκφραση που περιέχει έναν τελεστή και έναν τύπο που ορίζεται από το χρήστη, αντικαθιστά αυτήν την έκφραση με μια κλήση στην κατάλληλη συνάρτηση του υπερφορτωμένου τελεστή. Τα περισσότερα από τα ονόματά τους ξεκινούν με λέξη-κλειδίχειριστή , ακολουθούμενο από το σύμβολο του αντίστοιχου τελεστή. Όταν η ονομασία δεν αποτελείται από ειδικούς χαρακτήρες, για παράδειγμα, στην περίπτωση χειριστή cast ή διαχείρισης μνήμης (νέο , διαγραφή κ.λπ.), η λέξη χειριστής και η ονομασία χειριστή πρέπει να διαχωρίζονται με ένα κενό (νέος τελεστής), Διαφορετικά ο χώρος μπορεί να αγνοηθεί (operator+ ).

Οι περισσότεροι τελεστές μπορούν να υπερφορτωθούν τόσο με μεθόδους κλάσης όσο και απλές λειτουργίες, αλλά υπάρχουν μερικές εξαιρέσεις. Όταν ο υπερφορτωμένος τελεστής είναι μια μέθοδος κλάσης, ο τύπος του πρώτου τελεστή πρέπει να είναι αυτή η κλάση (πάντα *this) και ο δεύτερος πρέπει να δηλωθεί στη λίστα παραμέτρων. Επίσης, οι δηλώσεις μεθόδων δεν είναι στατικές, με εξαίρεση τις εντολές διαχείρισης μνήμης.

Κατά την υπερφόρτωση ενός τελεστή σε μια μέθοδο κλάσης, αποκτά πρόσβαση στα ιδιωτικά πεδία της κλάσης, αλλά η κρυφή μετατροπή του πρώτου ορίσματος δεν είναι διαθέσιμη. Επομένως, οι δυαδικές συναρτήσεις συνήθως υπερφορτώνονται ως ελεύθερες συναρτήσεις. Παράδειγμα:

Κλάση Rational ( public: //Ο κατασκευαστής μπορεί να χρησιμοποιηθεί για σιωπηρή μετατροπή από int: Rational(int numerator, int παρονομαστής = 1); Rational operator+(Rational const& rhs) const; ); int main() ( Ορθολογική a, b, c; int i; a = b + c; //ok, δεν απαιτείται μετατροπή a = b + i; //ok, σιωπηρή μετατροπή του δεύτερου ορίσματος a = i + c; //ΣΦΑΛΜΑ: το πρώτο όρισμα δεν μπορεί να μετατραπεί σιωπηρά )

Όταν οι unary τελεστές υπερφορτώνονται ως ελεύθερες συναρτήσεις, η μετατροπή κρυφού ορίσματος είναι διαθέσιμη σε αυτούς, αλλά συνήθως δεν χρησιμοποιείται. Από την άλλη πλευρά, αυτή η ιδιότητα είναι απαραίτητη για δυαδικούς τελεστές. Η κύρια συμβουλή λοιπόν θα ήταν:

Εφαρμόστε μοναδικούς τελεστές και δυαδικούς τελεστές όπως " Χ=» ως μέθοδοι κλάσης και άλλοι δυαδικοί τελεστές ως ελεύθερες συναρτήσεις.

Ποιοι χειριστές μπορούν να υπερφορτωθούν;

Μπορούμε να υπερφορτώσουμε σχεδόν οποιονδήποτε τελεστή C++, με την επιφύλαξη των ακόλουθων εξαιρέσεων και περιορισμών:

  • Δεν μπορείτε να ορίσετε έναν νέο τελεστή, όπως τελεστή**.
  • Οι ακόλουθοι τελεστές δεν μπορούν να υπερφορτωθούν:
    1. ?: (τριαδικός τελεστής);
    2. :: (πρόσβαση σε ένθετα ονόματα);
    3. . (πρόσβαση σε πεδία).
    4. .* (πρόσβαση πεδίου με δείκτη).
    5. τελεστές sizeof , typeid και cast.
  • Οι ακόλουθοι τελεστές μπορούν να υπερφορτωθούν μόνο ως μέθοδοι:
    1. = (ανάθεση);
    2. -> (πρόσβαση στα πεδία με δείκτη).
    3. () (κλήση συνάρτησης).
    4. (πρόσβαση με ευρετήριο).
    5. ->* (πρόσβαση δείκτη σε πεδίο με δείκτη).
    6. τελεστές μετατροπής και διαχείρισης μνήμης.
  • Ο αριθμός των τελεστών, η σειρά εκτέλεσης και η συσχέτιση των τελεστών καθορίζονται από την τυπική έκδοση.
  • Τουλάχιστον ένας τελεστής πρέπει να είναι τύπος που ορίζεται από το χρήστη. Το Typedef δεν μετράει.

Στο επόμενο μέρος, θα γνωρίσετε υπερφορτωμένους τελεστές C++, ομαδικά και μεμονωμένα. Κάθε ενότητα χαρακτηρίζεται από σημασιολογία, δηλ. αναμενόμενη συμπεριφορά. Επιπλέον, θα παρουσιαστούν τυπικοί τρόποι δήλωσης και υλοποίησης τελεστών.

Τελευταία ενημέρωση: 20/10/2017

Η υπερφόρτωση χειριστή σάς επιτρέπει να ορίσετε τις ενέργειες που θα εκτελέσει ο χειριστής. Η υπερφόρτωση συνεπάγεται τη δημιουργία μιας συνάρτησης της οποίας το όνομα περιέχει τη λέξη τελεστή και το σύμβολο του υπερφορτωμένου τελεστή. Μια συνάρτηση χειριστή μπορεί να οριστεί ως μέλος της κλάσης ή εκτός της κλάσης.

Μπορείτε να υπερφορτώνετε μόνο τελεστές που έχουν ήδη οριστεί στη C++. Δεν μπορείτε να δημιουργήσετε νέους τελεστές.

Εάν μια συνάρτηση τελεστή ορίζεται ως αυτόνομη συνάρτηση και δεν είναι μέλος μιας κλάσης, τότε ο αριθμός των παραμέτρων μιας τέτοιας συνάρτησης είναι ίδιος με τον αριθμό των τελεστών του τελεστή. Για παράδειγμα, μια συνάρτηση που αντιπροσωπεύει έναν δυαδικό τελεστή θα έχει μία παράμετρο, ενώ μια συνάρτηση που αντιπροσωπεύει έναν δυαδικό τελεστή θα έχει δύο παραμέτρους. Εάν ο τελεστής παίρνει δύο τελεστές, τότε ο πρώτος τελεστής περνά στην πρώτη παράμετρο της συνάρτησης και ο δεύτερος τελεστής στη δεύτερη παράμετρο. Σε αυτήν την περίπτωση, τουλάχιστον μία από τις παραμέτρους πρέπει να αντιπροσωπεύει τον τύπο της κλάσης

Εξετάστε ένα παράδειγμα με μια κλάση Counter που αντιπροσωπεύει ένα χρονόμετρο και αποθηκεύει τον αριθμό των δευτερολέπτων:

#περιλαμβάνω << seconds << " seconds" << std::endl; } int seconds; }; Counter operator + (Counter c1, Counter c2) { return Counter(c1.seconds + c2.seconds); } int main() { Counter c1(20); Counter c2(10); Counter c3 = c1 + c2; c3.display(); // 30 seconds return 0; }

Εδώ η συνάρτηση τελεστή δεν είναι μέρος της κλάσης Counter και ορίζεται εκτός αυτής. Αυτή η λειτουργία υπερφορτώνει τον τελεστή προσθήκης για τον τύπο μετρητή. Είναι δυαδικό, άρα χρειάζεται δύο παραμέτρους. Σε αυτήν την περίπτωση, προσθέτουμε δύο αντικείμενα Counter. Η συνάρτηση επιστρέφει επίσης ένα αντικείμενο Counter που αποθηκεύει τον συνολικό αριθμό των δευτερολέπτων. Δηλαδή, στην ουσία, η λειτουργία προσθήκης εδώ μειώνεται στην προσθήκη των δευτερολέπτων και των δύο αντικειμένων:

Χειριστής μετρητή + (Μετρητής c1, μετρητής c2) ( μετρητής επιστροφής (c1.seconds + c2.seconds); )

Σε αυτήν την περίπτωση, δεν είναι απαραίτητο να επιστρέψετε ένα αντικείμενο της κλάσης. Μπορεί επίσης να είναι ένα αντικείμενο ενσωματωμένου πρωτόγονου τύπου. Και μπορούμε επίσης να ορίσουμε πρόσθετες υπερφορτωμένες λειτουργίες χειριστή:

Int operator + (Μετρητής c1, int s) ( επιστροφή c1.seconds + s; )

Αυτή η έκδοση προσθέτει το αντικείμενο Counter σε έναν αριθμό και επιστρέφει τον αριθμό επίσης. Επομένως, ο αριστερός τελεστής της πράξης πρέπει να είναι τύπου Counter και ο δεξιός τελεστής τύπου int. Και, για παράδειγμα, μπορούμε να εφαρμόσουμε αυτήν την έκδοση του τελεστή ως εξής:

Μετρητής c1(20); int δευτερόλεπτα = c1 + 25; // 45 std::out<< seconds << std::endl;

Επίσης, οι συναρτήσεις χειριστή μπορούν να οριστούν ως μέλη κλάσεων. Εάν μια συνάρτηση τελεστή ορίζεται ως μέλος μιας κλάσης, τότε ο αριστερός τελεστής είναι προσβάσιμος μέσω αυτού του δείκτη και αντιπροσωπεύει το τρέχον αντικείμενο και ο δεξιός τελεστής μεταβιβάζεται ως η μόνη παράμετρος σε μια τέτοια συνάρτηση:

#περιλαμβάνω class Counter ( public: Counter(int sec) ( seconds = sec; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } Counter operator + (Counter c2) { return Counter(this->δευτερόλεπτα + c2.seconds); ) int operator + (int s) ( return this->seconds + s; ) int seconds; ) int main() ( Μετρητής c1(20); Μετρητής c2(10); Μετρητής c3 = c1 + c2; c3.display(); // 30 δευτερόλεπτα int δευτερόλεπτα = c1 + 25; // 45 επιστροφή 0; )

Σε αυτήν την περίπτωση, έχουμε πρόσβαση στον αριστερό τελεστή στις συναρτήσεις τελεστή μέσω αυτού του δείκτη.

Ποιους τελεστές να επαναπροσδιορίσετε; Οι τελεστές για εκχώρηση, ευρετηρίαση (), κλήση (()), πρόσβαση σε ένα μέλος κλάσης με δείκτη (->) θα πρέπει να ορίζονται ως συναρτήσεις μέλους κλάσης. Οι τελεστές που αλλάζουν την κατάσταση ενός αντικειμένου ή σχετίζονται άμεσα με ένα αντικείμενο (αύξηση, μείωση κ.λπ.) συνήθως ορίζονται επίσης ως συναρτήσεις μέλους κλάσης. Όλοι οι άλλοι τελεστές ορίζονται συχνότερα ως ξεχωριστές συναρτήσεις παρά ως μέλη κλάσης.

Χειριστές σύγκρισης

Ένας αριθμός χειριστών υπερφορτώνεται ανά ζεύγη. Για παράδειγμα, αν ορίσουμε τον τελεστή ==, πρέπει να ορίσουμε και τον τελεστή !=. Και κατά τον ορισμό του χειριστή< надо также определять функцию для оператора >. Για παράδειγμα, ας υπερφορτώσουμε αυτούς τους τελεστές:

bool operator == (Μετρητής c1, Μετρητής c2) ( return c1.seconds == c2.seconds; ) bool operator != (Μετρητής c1, Counter c2) ( return c1.seconds != c2.seconds; ) bool operator > ( Μετρητής c1, Μετρητής c2) ( επιστροφή c1.seconds > c2.seconds; ) bool operator< (Counter c1, Counter c2) { return c1.seconds < c2.seconds; } int main() { Counter c1(20); Counter c2(10); bool b1 = c1 == c2; // false bool b2 = c1 >c2; // true std::cout<< b1 << std::endl; std::cout << b2 << std::endl; return 0; }

Χειριστές ανάθεσης

#περιλαμβάνω class Counter ( public: Counter(int sec) ( seconds = sec; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } Counter& operator += (Counter c2) { seconds += c2.seconds; return *this; } int seconds; }; int main() { Counter c1(20); Counter c2(10); c1 += c2; c1.display(); // 30 seconds return 0; }

Πράξεις αύξησης και μείωσης

Ο επαναπροσδιορισμός των τελεστών προσαύξησης και μείωσης μπορεί να είναι ιδιαίτερα δύσκολος επειδή πρέπει να ορίσουμε και τη φόρμα προθέματος και μετάθεμα για αυτούς τους τελεστές. Ας ορίσουμε παρόμοιους τελεστές για τον τύπο Counter:

#περιλαμβάνω class Counter ( public: Counter(int sec) ( seconds = sec; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } // префиксные операторы Counter& operator++ () { seconds += 5; return *this; } Counter& operator-- () { seconds -= 5; return *this; } // постфиксные операторы Counter operator++ (int) { Counter prev = *this; ++*this; return prev; } Counter operator-- (int) { Counter prev = *this; --*this; return prev; } int seconds; }; int main() { Counter c1(20); Counter c2 = c1++; c2.display(); // 20 seconds c1.display(); // 25 seconds --c1; c1.display(); // 20 seconds return 0; }

Μετρητής& χειριστής++ () ( δευτερόλεπτα += 5; επιστροφή *αυτό; )

Στην ίδια τη συνάρτηση, μπορείτε να ορίσετε κάποια λογική αυξάνοντας την τιμή. Σε αυτήν την περίπτωση, ο αριθμός των δευτερολέπτων αυξάνεται κατά 5.

Οι χειριστές Postfix πρέπει να επιστρέψουν την τιμή του αντικειμένου πριν από την αύξηση, δηλαδή την προηγούμενη κατάσταση του αντικειμένου. Για να ξεχωρίσετε τη φόρμα postfix από τη φόρμα προθέματος, οι εκδόσεις postfix λαμβάνουν μια πρόσθετη παράμετρο τύπου int, η οποία δεν χρησιμοποιείται. Αν και καταρχήν μπορούμε να το χρησιμοποιήσουμε.

Χειριστής μετρητή++ (int) ( Μετρητής προηγούμενος = *αυτό; ++*αυτός; επιστροφή προηγούμενο; )