C supraîncărcarea operației de comparare a șirurilor. Supraîncărcarea operațiilor binare. Supraîncărcarea operatorilor unari

Ultima actualizare: 20.10.2017

Supraîncărcarea operatorului vă permite să definiți acțiunile pe care le va efectua un operator. Supraîncărcarea presupune crearea unei funcții al cărei nume conține cuvântul operator și simbolul operatorului care este supraîncărcat. O funcție de operator poate fi definită ca membru al unei clase sau în afara unei clase.

Puteți supraîncărca numai operatorii care sunt deja definiți în C++. Nu puteți crea operatori noi.

Dacă funcția de operator este definită ca functie separatași nu este membru al clasei, atunci numărul de parametri ai unei astfel de funcții coincide cu numărul de operanzi ai operatorului. De exemplu, o funcție care reprezintă un operator unar va avea un parametru, iar o funcție care reprezintă un operator binar va avea doi parametri. Dacă un operator ia doi operanzi, atunci primul operand este transmis primului parametru al funcției, iar al doilea operand este transmis celui de-al doilea parametru. În acest caz, cel puțin unul dintre parametri trebuie să reprezinte tipul clasei

Să ne uităm la un exemplu cu clasa Counter, care reprezintă un cronometru și stochează numărul de secunde:

#include << 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; }

Aici funcția operator nu face parte din clasa Counter și este definită în afara acesteia. Această funcție supraîncărcă operatorul de adăugare pentru tipul Contor. Este binar, deci este nevoie de doi parametri. ÎN în acest caz, adăugăm două obiecte Counter. Funcția returnează, de asemenea, un obiect Counter care stochează numărul total de secunde. Adică, în esență, operația de adăugare aici se rezumă la adăugarea secundelor ambelor obiecte:

Operator contor + (Contor c1, Contor c2) ( returnează Contor(c1.secunde + c2.secunde); )

Nu este necesar să returnați un obiect de clasă. Poate fi, de asemenea, un obiect de tip primitiv încorporat. Și putem defini, de asemenea, supraîncărcări suplimentare ale operatorului:

Operator int + (Contor c1, int s) ( return c1.secunde + s; )

Această versiune adaugă un obiect Counter la un număr și returnează, de asemenea, numărul. Prin urmare, operandul din stânga al operației trebuie să fie de tip Counter, iar operandul din dreapta trebuie să fie de tip int. Și, de exemplu, putem aplica această versiune a operatorului după cum urmează:

Contorul c1(20); int secunde = c1 + 25; // 45 std::cout<< seconds << std::endl;

De asemenea, funcțiile operatorului pot fi definite ca membri ai claselor. Dacă o funcție operator este definită ca membru al unei clase, atunci operandul din stânga este accesat prin indicatorul this și reprezintă obiectul curent, iar operandul din dreapta este transmis funcției similare ca singur parametru:

#include class Counter ( public: Counter(int sec) ( secunde = sec; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } Counter operator + (Counter c2) { return Counter(this->secunde + c2.secunde); ) operator int + (int s) ( return this->seconds + s; ) int secunde; ); int main() ( Counter c1(20); Counter c2(10); Counter c3 = c1 + c2; c3.display(); // 30 secunde int secunde = c1 + 25; // 45 return 0; )

În acest caz, accesăm operandul din stânga în funcțiile operatorului prin intermediul indicatorului this.

Ce operatori ar trebui redefiniti unde? Operatorii de atribuire, indexare (), apelare (()), accesare a unui membru de clasă prin pointer (->) ar trebui definiți ca funcții de membru de clasă. Operatorii care schimbă starea unui obiect sau sunt asociați direct cu obiectul (incrementare, decrementare) sunt, de obicei, definiți și ca funcții membre ale clasei. Toți ceilalți operatori sunt adesea definiți ca funcții individuale, mai degrabă decât ca membri ai unei clase.

Operatori de comparație

Un număr de operatori sunt supraîncărcați în perechi. De exemplu, dacă definim operatorul ==, atunci trebuie să definim și operatorul !=. Și la definirea operatorului< надо также определять функцию для оператора >. De exemplu, să supraîncărcăm acești operatori:

Operator bool == (Contor c1, Contor c2) ( return c1.seconds == c2.seconds; ) operator bool != (Counter c1, Counter c2) ( return c1.seconds != c2.seconds; ) operator bool > ( Counter c1, Counter c2) ( return c1.seconds > c2.seconds; ) operator bool< (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; // adevărat std::cout<< b1 << std::endl; std::cout << b2 << std::endl; return 0; }

Operatori de atribuire

#include class Counter ( public: Counter(int sec) ( secunde = 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; }

Operatii de crestere si scadere

Redefinirea operatorilor de creștere și decreștere poate fi deosebit de dificilă, deoarece trebuie să definim atât formele de prefix, cât și de postfix pentru acești operatori. Să definim operatori similari pentru tipul Counter:

#include class Counter ( public: Counter(int sec) ( secunde = 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; }

Contor& operator++ () (secunde += 5; returnează *acest; )

În funcția în sine, puteți defini o logică pentru creșterea valorii. În acest caz, numărul de secunde crește cu 5.

Operatorii Postfix trebuie să returneze valoarea obiectului înainte de increment, adică starea anterioară a obiectului. Pentru a face forma postfix diferită de forma prefix, versiunile postfix primesc un parametru suplimentar de tip int, care nu este utilizat. Deși în principiu îl putem folosi.

Operatorul contor++ (int) ( Counter prev = *this; ++*this; return prev; )

Astăzi ne vom familiariza cu o caracteristică minunată a oricărui limbaj C++ - supraîncărcarea operatorului. Dar mai întâi, să ne dăm seama de ce este nevoie de acest lucru.

Cu tipurile de bază puteți utiliza orice operator: +, -, *, ++, = și mulți alții. De exemplu:

int a=2,b=3,c;
c = a + b;

Aici, operația + este mai întâi efectuată pe variabile de tip int, iar apoi rezultatul este atribuit variabilei c folosind operația =. Nu poți face asta cu cursurile. Să creăm o clasă simplă:

cod în limbaj c++ clasa Counter ( public: int counter; Counter() : c(0) () ); Contorul a,b,c; a.contor = 2; b.contor = 3; c = a + b;

Aici compilatorul va arunca o eroare pe ultima linie: nu știe exact cum să acționeze când folosește operațiile = și + pe obiecte din clasa Counter. Puteți rezolva această problemă astfel:

cod în limbaj c++ class Counter ( public: int counter; Counter() : count(0) () void AddCounters (Counter& a, Counter& b) ( counter = a.counter + b.counter; ) ); Contorul a,b,c; a.contor = 2; b.contor = 3; c.AddCounters(a,b);

De acord, utilizarea operațiilor + și = în acest caz ar face programul mai ușor de înțeles. Deci aici este de folosit operațiuni standard C++ cu clase, aceste operațiuni trebuie supraîncărcate.

Supraîncărcarea operatorilor unari

Să începem cu operații simple - unare. În primul rând, ne interesează creșterea. Când utilizați această operație pe tipuri de bază, una poate fi adăugată sau scăzută la o variabilă:

cod în limbaj c++ int a=1; ++a; // a = 2; --A; // a = 1;

Acum să învățăm clasa Counter să folosească preincrement:

cod în limbaj c++ class Counter ( private: counter; public: Counter() : counter(0) () void operator++ () ( counter += 1; // De asemenea, puteți contra++ sau ++counter - // nu contează în acest caz . ) ); Contor a; ++a; ++a; ++a;

Acest cod funcționează. Când se folosește operația ++ (este important ca acest semn să fie înaintea identificatorului!) pe un obiect din clasa Counter, contorul variabil al obiectului a este mărit.

În acest exemplu, am supraîncărcat operatorul ++. Acest lucru se face prin crearea unei metode în interiorul clasei. Singura caracteristică importantă a acestei metode este numele identificatorului. Numele de identificare pentru operațiunile supraîncărcate constă din cuvântul cheie operator și numele operației. În toate celelalte privințe, această metodă este definită ca oricare alta.

Utilizați operatori supraîncărcați cu tipuri personalizate foarte simplu - la fel ca în cazul tipurilor de date obișnuite.

Supraîncărcarea operatorului Postfix

Exemple de operație postfix:

int a = 3;
a++;
A--;

Adică aici semnul operațiunii este plasat după numele identificatorului. Pentru a utiliza operațiuni postfix cu tipuri de date personalizate, aveți nevoie de foarte puțin:

cod în limbaj c++ public: void operator++ () ( counter += 1; ) void operator++ (int) ( counter += 1; )

Singura diferență între operația de prefix și operația de postfix este cuvânt cheie int în lista de argumente. Dar int nu este un argument! Acest cuvânt spune că operatorul postfix este supraîncărcat. Acum operatorul ++ poate fi folosit atât înainte, cât și după identificatorul obiectului:

Contor a;
++a;
++a;
a++;
a++;

Supraîncărcare operatii binare

Supraîncărcarea operatorilor cu două argumente este foarte asemănătoare cu supraîncărcarea operatorilor binari:

cod în limbaj c++ Operator Counter+ (Counter t) ( Counter summ; summ.counter = counter + t.counter; return summ; ) Counter c1,c2,c3; c1.contor = 3; c2.contor = 2; c3 = c1 + c2;

Care variabilă va apela funcția operator+? În operatorii binari supraîncărcați, metoda operandului din stânga este întotdeauna apelată. În acest caz, metoda operator+ apelează obiectul c1.

Trecem un argument metodei. Argumentul este întotdeauna operandul potrivit.

În plus, în acest caz, operația + trebuie să returneze un rezultat pentru a-l atribui obiectului c3. Returnăm un obiect Counter. Valoarea returnată este atribuită variabilei c3.

Observați că am supraîncărcat operatorul +, dar nu am supraîncărcat operatorul =! Desigur, trebuie să adăugați această metodă la clasa Counter:

cod în limbaj c++ Operator Counter= (Counter t) ( Counter assign; counter = t.counter; assign.counter = t.counter; return assign; )

În cadrul metodei, am creat o variabilă de atribuire suplimentară. Această variabilă este utilizată astfel încât să puteți lucra cu următorul cod:

Contor c1(5),c2,c3;
c3 = c2 = c1;

Deși, puteți folosi metode mai elegante pentru valoarea de returnare, cu care ne vom familiariza în curând.

Am acoperit elementele de bază ale utilizării supraîncărcării operatorului. În acest material, operatorii C++ supraîncărcați vor fi prezentați atenției dumneavoastră. Fiecare secțiune este caracterizată de semantică, adică comportament asteptat. În plus, vor fi afișate modalități tipice de declarare și implementare a operatorilor.

În exemplele de cod, X indică tipul definit de utilizator pentru care este implementat operatorul. T este un tip opțional, fie definit de utilizator, fie încorporat. Parametrii operatorului binar vor fi numiți lhs și rhs . Dacă un operator este declarat ca metodă de clasă, declarația sa va fi prefixată cu X:: .

operator=

  • Definiție de la dreapta la stânga: Spre deosebire de majoritatea operatorilor, operator= este asociativ corect, i.e. a = b = c înseamnă a = (b = c) .

Copie

  • Semantică: atribuirea a = b . Valoarea sau starea lui b este transmisă la a . În plus, este returnată o referință la a. Acest lucru vă permite să creați lanțuri precum c = a = b.
  • Anunț tipic: X& X::operator= (X const& rhs) . Sunt posibile și alte tipuri de argumente, dar acestea nu sunt folosite des.
  • Implementare tipică: X& X::operator= (X const& rhs) ( dacă (acest != &rhs) ( //efectuează o copiere în funcție de element, sau: X tmp(rhs); //copiere constructor swap(tmp); ) return *this; )

Mutare (din C++11)

  • Semantică: atribuire a = temporar() . Valoarea sau starea valorii corecte este atribuită lui a prin mutarea conținutului. Se returnează o referință la a.
  • : X& X::operator= (X&& rhs) ( //ia curajul de la rhs return *aceasta; )
  • Compiler generat operator= : Compilatorul poate crea doar două tipuri de acest operator. Dacă operatorul nu este declarat în clasă, compilatorul încearcă să creeze operatori publici de copiere și mutare. Din C++11, compilatorul poate crea un operator implicit: X& X::operator= (X const& rhs) = implicit;

    Instrucțiunea generată pur și simplu copiază/mută elementul specificat dacă o astfel de operație este permisă.

operator+, -, *, /, %

  • Semantică: operații de adunare, scădere, înmulțire, împărțire, împărțire cu rest. Este returnat un nou obiect cu valoarea rezultată.
  • Declarație tipică și implementare: operator X+ (X const lhs, X const rhs) ( X tmp(lhs); tmp += rhs; return tmp; )

    De obicei, dacă operator+ există, este logic să supraîncărcați și operator+= pentru a utiliza notația a += b în loc de a = a + b . Dacă operator+= nu este supraîncărcat, implementarea va arăta cam așa:

    Operator X+ (X const& lhs, X const& rhs) ( // creează un nou obiect care reprezintă suma lhs și rhs: return lhs.plus(rhs); )

operator unar+, —

  • Semantică: semn pozitiv sau negativ. operator+ de obicei nu face nimic și, prin urmare, este foarte greu folosit. operator- returnează argumentul cu semnul opus.
  • Declarație tipică și implementare: X X::operator- () const ( return /* o copie negativă a *this */; ) X X::operator+ () const ( return *this; )

operator<<, >>

  • Semantică: În tipurile încorporate, operatorii sunt utilizați pentru a deplasa biți argumentul stânga. Supraîncărcarea acestor operatori cu exact această semantică este rară; singurul care îmi vine în minte este std::bitset . Cu toate acestea, au fost introduse noi semantici pentru lucrul cu fluxuri, iar supraîncărcarea instrucțiunilor I/O este destul de comună.
  • Declarație tipică și implementare: deoarece nu puteți adăuga metode la clasele standard iostream, operatorii de schimbare pentru clasele pe care le definiți trebuie să fie supraîncărcați ca funcții gratuite: operator ostream&<< (ostream& os, X const& x) { os << /* the formatted data of rhs you want to print */; return os; } istream& operator>> (istream& is, X& x) ( SomeData sd; SomeMoreData smd; if (este >> sd >> smd) ( rhs.setSomeData(sd); rhs.setSomeMoreData(smd); ) return lhs; )

    În plus, tipul operandului din stânga poate fi orice clasă care ar trebui să se comporte ca un obiect I/O, adică operandul din dreapta poate fi un tip încorporat.

    MyIO& MyIO::operator<< (int rhs) { doYourThingWith(rhs); return *this; }

Operator binar&, |, ^

  • Semantică: operațiuni cu biți „și”, „sau”, „exclusiv sau”. Acești operatori sunt supraîncărcați foarte rar. Din nou, singurul exemplu este std::bitset .

operator+=, -=, *=, /=, %=

  • Semantică: a += b înseamnă de obicei același lucru cu a = a + b . Comportamentul altor operatori este similar.
  • Definiție și implementare tipică: Deoarece operațiunea modifică operandul din stânga, nu este de dorit turnarea de tip ascuns. Prin urmare, acești operatori trebuie să fie supraîncărcați ca metode de clasă. X& X::operator+= (X const& rhs) ( //aplica modificări la *this return *this; )

operator&=, |=, ^=,<<=, >>=

  • Semantică: similar cu operator+= , dar pentru operații logice. Acești operatori sunt supraîncărcați la fel de rar ca operator| etc. operator<<= и operator>>= nu sunt utilizate pentru operațiuni I/O deoarece operator<< и operator>> schimbând deja argumentul din stânga.

operator==, !=

  • Semantică: Test pentru egalitate/inegalitate. Sensul egalității variază foarte mult în funcție de clasă. În orice caz, luați în considerare următoarele proprietăți ale egalităților:
    1. Reflexivitate, adică a == a .
    2. Simetria, adică dacă a == b , atunci b == a .
    3. Tranzitivitatea, adică dacă a == b și b == c , atunci a == c .
  • Declarație tipică și implementare: operator bool== (X const& lhs, X cosnt& rhs) ( return /* verifica orice înseamnă egalitate */ ) operator bool!= (X const& lhs, X const& rhs) ( return !(lhs == rhs); )

    A doua implementare a operator!= evită repetarea codului și elimină orice posibilă ambiguitate cu privire la oricare două obiecte.

operator<, <=, >, >=

  • Semantică: verificați raportul (mai mult, mai puțin etc.). Este de obicei folosit dacă ordinea elementelor este definită în mod unic, adică nu are sens să compari obiecte complexe cu mai multe caracteristici.
  • Declarație tipică și implementare: operator bool< (X const& lhs, X const& rhs) { return /* compare whatever defines the order */ } bool operator>(X const& lhs, X const& rhs) ( întoarcere rhs< lhs; }

    Operator de implementare> operator de utilizare< или наоборот обеспечивает однозначное определение. operator<= может быть реализован по-разному, в зависимости от ситуации . В частности, при отношении строго порядка operator== можно реализовать лишь через operator< :

    Operator bool== (X const& lhs, X const& rhs) ( return !(lhs< rhs) && !(rhs < lhs); }

operator++, –

  • Semantică: a++ (post-increment) incrementează valoarea cu 1 și revine vechi sens. ++a (preincrement) revine nou sens. Cu operatorul de decrementare - totul este similar.
  • Declarație tipică și implementare: X& X::operator++() ( //preincrement /* cumva increment, de exemplu *this += 1*/; return *this; ) X X::operator++(int) ( //postincrement X oldValue(*this); + +(*acest); returnează oldValue; )

operator()

  • Semantică: execuția unui obiect funcție (functor). De obicei, folosit nu pentru a modifica un obiect, ci pentru a-l folosi ca funcție.
  • Fără restricții privind parametrii: Spre deosebire de operatorii anteriori, în acest caz nu există restricții privind numărul și tipul de parametri. Un operator poate fi supraîncărcat doar ca metodă de clasă.
  • Exemplu de anunț: Foo X::operator() (Bar br, Baz const& bz);

operator

  • Semantică: accesează elementele unui tablou sau container, de exemplu în std::vector , std::map , std::array .
  • Anunţ: Tipul de parametru poate fi orice. Tipul de returnare este de obicei o referință la ceea ce este stocat în container. Adesea, operatorul este supraîncărcat în două versiuni, const și non-const: Element_t& X::operator(Index_t const& index); const Element_t& X::operator(Index_t const& index) const;

operator!

  • Semantică: negaţie în sens logic.
  • Declarație tipică și implementare: bool X::operator!() const ( return !/*o anumită evaluare a *aceasta*/; )

operator explicit bool

  • Semantică: Utilizați într-un context logic. Cel mai adesea folosit cu indicatori inteligente.
  • Implementarea: explicit X::operator bool() const ( returnează /* dacă este adevărat sau fals */; )

operator&&, ||

  • Semantică: logic „și”, „sau”. Acești operatori sunt definiți numai pentru tipul boolean încorporat și funcționează pe o bază leneșă, adică al doilea argument este luat în considerare numai dacă primul nu determină rezultatul. Când sunt supraîncărcate, această proprietate se pierde, astfel încât acești operatori sunt rareori supraîncărcați.

operator unar*

  • Semantică: Dereferința indicatorului. De obicei, supraîncărcat pentru clasele cu pointere și iteratoare inteligente. Returnează o referință către locul în care arată obiectul.
  • Declarație tipică și implementare: T& X::operator*() const ( return *_ptr; )

operator->

  • Semantică: Accesați un câmp prin indicator. La fel ca și precedentul, acest operator este supraîncărcat pentru a fi utilizat cu pointeri și iteratoare inteligente. Dacă operatorul -> este întâlnit în codul dvs., compilatorul redirecționează apelurile către operator-> dacă este returnat rezultatul unui tip personalizat.
  • Implementare obișnuită: T* X::operator->() const ( return _ptr; )

operator->*

  • Semantică: acces la un pointer la câmp prin indicator. Operatorul ia un pointer către un câmp și îl aplică la orice indica *acest lucru, deci objPtr->*memPtr este același cu (*objPtr).*memPtr . Foarte rar folosit.
  • Posibila implementare: șablon T& X::operator->*(T V::* memptr) ( return (operator*()).*memptr; )

    Aici X este indicatorul inteligent, V este tipul indicat de X și T este tipul indicat de indicatorul de câmp. Nu este surprinzător că acest operator este rareori supraîncărcat.

operator unar&

  • Semantică: operator de adresă. Acest operator este supraîncărcat foarte rar.

operator

  • Semantică: Operatorul virgulă încorporat aplicat la două expresii le evaluează ambele în ordine scrisă și returnează valoarea celei de-a doua. Nu este recomandat să-l supraîncărcați.

operator~

  • Semantică: operator de inversare pe biți. Unul dintre cei mai rar folositi operatori.

Operatori de turnare

  • Semantică: Permite turnarea implicită sau explicită a obiectelor de clasă în alte tipuri.
  • Anunţ: //conversie la T, explicit sau implicit X::operator T() const; //conversie explicită în U const& explicit X::operator U const&() const; //conversie la V& V& X::operator V&();

    Aceste declarații arată ciudat deoarece le lipsește un tip de returnare. Face parte din numele operatorului și nu este specificat de două ori. Merită să ne amintim asta un numar mare de fantomele ascunse pot implica erori neașteptateîn funcţionarea programului.

operator nou, nou, ștergere, ștergere

Acești operatori sunt complet diferiți de toți cei de mai sus, deoarece nu funcționează cu tipuri definite de utilizator. Supraîncărcarea lor este foarte complexă și, prin urmare, nu va fi luată în considerare aici.

Concluzie

Ideea principală este următoarea: nu supraîncărcați operatorii doar pentru că știți cum să o faceți. Supraîncărcați-le doar în cazurile în care vi se pare firesc și necesar. Dar amintiți-vă că dacă supraîncărcați un operator, va trebui să supraîncărcați pe alții.

Ultima actualizare: 08.12.2018

Alături de metode, putem supraîncărca și operatorii. De exemplu, să presupunem că avem următoarea clasă Counter:

Contor de clasă ( public int Value ( get; set; ) )

Această clasă reprezintă un contor, a cărui valoare este stocată în proprietatea Value.

Și să presupunem că avem două obiecte din clasa Counter - două contoare pe care dorim să le comparăm sau să le adăugăm pe baza proprietății lor Value, folosind operații standard de comparare și adăugare:

Contor c1 = contor nou(Valoare = 23); Contor c2 = contor nou (Valoare = 45); rezultat bool = c1 > c2; Contorul c3 = c1 + c2;

Dar mai departe acest moment Nici compararea, nici adăugarea nu este disponibilă pentru obiectele Counter. Aceste operații pot fi utilizate pe un număr de tipuri primitive. De exemplu, implicit putem adăuga valori numerice, dar cum să adăugați obiecte tipuri complexe- compilatorul nu cunoaște clase și structuri. Și pentru asta trebuie să supraîncărcăm operatorii de care avem nevoie.

Supraîncărcarea operatorului constă în definirea unei metode speciale în clasa pentru ale cărei obiecte dorim să definim un operator:

operator public static return_type operator(parametri) ( )

Această metodă trebuie să aibă modificatori statici publici, deoarece supraîncărcarea operatorului va fi folosită pentru toate obiectele acestei clase. Urmează numele tipului de returnare. Tipul returnat reprezintă tipul ale cărui obiecte dorim să le primim. De exemplu, ca urmare a adăugării a două obiecte Counter, ne așteptăm să obținem un nou obiect Counter. Și ca rezultat al comparării celor două, dorim să obținem un obiect de tip bool, care indică dacă expresia condiționată este adevărată sau falsă. Dar, în funcție de sarcină, tipurile de returnare pot fi orice.

Apoi, în loc de numele metodei, există operatorul cheie și operatorul însuși. Și apoi parametrii sunt enumerați în paranteze. Operatorii binari iau doi parametri, operatorii unari iau un parametru. Și în orice caz, unul dintre parametri trebuie să reprezinte tipul - clasa sau structura în care este definit operatorul.

De exemplu, să supraîncărcăm un număr de operatori pentru clasa Counter:

Class Counter ( public int Valoare ( get; set; ) public static Counter operator +(Counter c1, Counter c2) ( return new Counter ( Value = c1.Value + c2.Value ); ) public static operator bool >(Counter c1, Counter c2) ( return c1.Value > c2.Value; ) operator public static bool<(Counter c1, Counter c2) { return c1.Value < c2.Value; } }

Deoarece toți operatorii supraîncărcați sunt binari, adică sunt executați pe două obiecte, există doi parametri pentru fiecare supraîncărcare.

Deoarece în cazul operației de adăugare dorim să adăugăm două obiecte din clasa Counter, operatorul acceptă două obiecte din această clasă. Și din moment ce dorim să obținem un nou obiect Counter ca urmare a adăugării, această clasă este folosită și ca tip de returnare. Toate acțiunile acestui operator se reduc la crearea unui nou obiect, a cărui proprietate Value combină valorile proprietății Value a ambilor parametri:

Operatorul public static Counter +(Counter c1, Counter c2) ( returnează un nou contor (Valoare = c1.Value + c2.Value); )

Au fost redefiniți și doi operatori de comparație. Dacă suprascriem unul dintre acești operatori de comparație, atunci trebuie să suprascriem și pe al doilea dintre acești operatori. Operatorii de comparație înșiși compară valorile proprietăților Value și, în funcție de rezultatul comparației, returnează fie adevărat, fie fals.

Acum folosim operatori supraîncărcați în program:

Static void Main(string args) ( Counter c1 = new Counter ( Value = 23 ); Counter c2 = new Counter ( Value = 45 ); bool result = c1 > c2; Console.WriteLine(result); // false Counter c3 = c1 + c2; Console.WriteLine(c3.Value); // 23 + 45 = 68 Console.ReadKey(); )

Este de remarcat faptul că, deoarece definiția operatorului este în esență o metodă, putem de asemenea să supraîncărcăm această metodă, adică să creăm o altă versiune pentru ea. De exemplu, să adăugăm un alt operator la clasa Counter:

Operator public static int +(Counter c1, int val) ( return c1.Value + val; )

Această metodă adaugă valoarea proprietății Value și un anumit număr, returnând suma acestora. Și putem folosi și acest operator:

Contor c1 = contor nou(Valoare = 23); int d = c1 + 27; // 50 Console.WriteLine(d);

Trebuie avut în vedere că la supraîncărcare, acele obiecte care sunt transmise operatorului prin parametri nu ar trebui să se modifice. De exemplu, putem defini un operator de increment pentru clasa Counter:

Operator public static Counter ++(Counter c1) ( c1.Value += 10; return c1; )

Deoarece operatorul este unar, este nevoie de un singur parametru - un obiect al clasei în care acest operator definit Dar aceasta este o definiție incorectă a incrementului, deoarece operatorul nu ar trebui să modifice valorile parametrilor săi.

Și o supraîncărcare mai corectă a operatorului de increment ar arăta astfel:

Operatorul public static Counter ++(Counter c1) ( returnează un nou Counter (Valoare = c1.Value + 10); )

Adică, este returnat un nou obiect care conține o valoare incrementată în proprietatea Value.

În acest caz, nu trebuie să definim operatori separați pentru creșterea prefixului și postfixului (precum și decrementarea), deoarece o implementare va funcționa în ambele cazuri.

De exemplu, folosim operația de creștere a prefixului:

Counter counter = new Counter() (Valoare = 10); Console.WriteLine($"(counter.Value)"); // 10 Console.WriteLine($"((++counter).Value)"); // 20 Console.WriteLine($"(counter.Value)"); // 20

Ieșire din consolă:

Acum folosim incrementul postfix:

Counter counter = new Counter() (Valoare = 10); Console.WriteLine($"(counter.Value)"); // 10 Console.WriteLine($"((counter++).Value)"); // 10 Console.WriteLine($"(counter.Value)"); // 20

Ieșire din consolă:

De asemenea, merită remarcat faptul că putem suprascrie operatorii adevărat și fals. De exemplu, să le definim în clasa Counter:

Class Counter ( public int Valoare ( get; set; ) public static bool operator true(Counter c1) ( return c1.Value != 0; ) public static bool operator false(Counter c1) ( return c1.Value == 0; ) // restul conținutului clasei)

Acești operatori sunt supraîncărcați atunci când dorim să folosim un obiect de tip ca condiție. De exemplu:

Counter counter = new Counter() (Valoare = 0); if (contor) Console.WriteLine(true); else Console.WriteLine(false);

Când supraîncărcați operatori, trebuie să țineți cont de faptul că nu toți operatorii pot fi supraîncărcați. În special, putem supraîncărca următorii operatori:

    operatori unari +, -, !, ~, ++, --

    operatori binari +, -, *, /, %

    operatii de comparatie ==, !=,<, >, <=, >=

    operatori logici &&, ||

    operatori de atribuire +=, -=, *=, /=, %=

Și există o serie de operatori care nu pot fi supraîncărcați, de exemplu, operatorul de egalitate = sau operatorul ternar?:, precum și o serie de alții.

O listă completă a operatorilor supraîncărcați poate fi găsită în documentația msdn

Atunci când supraîncărcăm operatori, trebuie să ne amintim că nu putem modifica precedența unui operator sau asociativitatea acestuia, nu putem crea un operator nou sau nu putem modifica logica operatorilor în tipuri, care sunt implicite în .NET.

O zi buna!

Dorinta de a scrie Acest articol a apărut după citirea postării, deoarece multe subiecte importante nu au fost tratate în ea.

Cel mai important lucru de reținut este că supraîncărcarea operatorului este mai mult mod convenabil apeluri de funcții, așa că nu vă lăsați dus de supraîncărcarea operatorului. Ar trebui să fie folosit numai atunci când va ușura scrierea codului. Dar nu atât de mult încât să îngreuneze lectura. La urma urmei, după cum știți, codul este citit mult mai des decât este scris. Și nu uitați că nu vi se va permite niciodată să supraîncărcați operatorii în tandem cu tipurile încorporate; posibilitatea de supraîncărcare este disponibilă numai pentru tipurile/clasele definite de utilizator.

Sintaxa de supraîncărcare

Sintaxa de supraîncărcare a operatorului este foarte asemănătoare cu definirea unei funcții numită operator@, unde @ este identificatorul operatorului (de exemplu +, -,<<, >>). Sa luam in considerare cel mai simplu exemplu:
clasa Integer ( private: int value; public: Integer(int i): value(i) () const Integer operator+(const Integer& rv) const ( return (valoare + rv.valoare); ) );
În acest caz, operatorul este încadrat ca membru al unei clase, argumentul determină valoarea situată în partea dreaptă a operatorului. În general, există două modalități principale de a supraîncărca operatorii: funcții globale care sunt prietenoase cu clasa sau funcții inline ale clasei în sine. Vom lua în considerare ce metodă este mai bună pentru ce operator la sfârșitul subiectului.

În majoritatea cazurilor, operatorii (cu excepția celor condiționali) returnează un obiect sau o referință la tipul căruia îi aparțin argumentele (dacă tipurile sunt diferite, atunci decideți cum să interpretați rezultatul evaluării operatorului).

Supraîncărcarea operatorilor unari

Să ne uităm la exemple de supraîncărcare a operatorilor unari pentru clasa Integer definită mai sus. În același timp, să le definim sub forma unor funcții prietenoase și să luăm în considerare operatorii de decrementare și de creștere:
clasa Integer ( privat: int value; public: Integer(int i): value(i) () //unary + friend const Integer& operator+(const Integer& i); //unary - friend const Integer operator-(const Integer& i) ; //prefix increment friend const Integer& operator++(Integer& i); //postfix increment friend const Integer operator++(Integer& i, int); //prefix decrement friend const Integer& operator--(Integer& i); //postfix decrement friend const Operator întreg--(Integer& i, int); ); //unar plus nu face nimic. const Integer& operator+(const Integer& i) ( returnează i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //Versiunea prefix returnează valoarea după increment const Integer& operator++(Integer& i) ( i.value++; return i; ) //versiunea postfix returnează valoarea înainte de increment const Operatorul întreg++(Integer& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //Versiunea prefix returnează valoarea după decrement const Integer& operator--(Integer& i) ( i.value--; return i; ) //versiunea postfix returnează valoarea înainte de decrement const Integer operator--(Integer& i, int) ( Integer oldValue(i. valoare); i .valoare--;return oldValue; )
Acum știți cum compilatorul face distincția între versiunile de prefix și postfix de decrement și increment. În cazul în care vede expresia ++i, se apelează operatorul funcției++(a). Dacă vede i++, atunci operatorul++(a, int) este apelat. Adică, funcția operator++ supraîncărcată este numită și pentru asta este folosit parametrul dummy int din versiunea postfix.

Operatori binari

Să ne uităm la sintaxa pentru supraîncărcarea operatorilor binari. Să supraîncărcăm un operator care returnează o valoare l, unul operator condiționalși un operator care creează o nouă valoare (să le definim global):
clasa Integer (privat: int valoare; public: Integer(int i): value(i) () prieten const Integer operator+(const Integer& stânga, const Integer& dreapta); prieten Integer& operator+=(Integer& stânga, const Integer& dreapta); prieten operator bool==(const Integer& stânga, const Integer& dreapta); ); const Integer operator+(const Integer& stânga, const Integer& dreapta) ( return Integer (stânga.valoare + dreapta.valoare); ) Integer& operator+=(Integer& stânga, const Integer& dreapta) ( stânga.valoare += dreapta.valoare; returnează stânga; ) operator bool==(const Integer& stânga, const Integer& dreapta) ( returnează stânga.valoare == dreapta.valoare; )
În toate aceste exemple, operatorii sunt supraîncărcați pentru același tip, dar acest lucru nu este necesar. Puteți, de exemplu, să supraîncărcați adăugarea tipului nostru Integer și Float definit de asemănarea acestuia.

Argumente și valori returnate

După cum puteți vedea, exemplele folosesc diferite căi transmiterea de argumente la funcții și returnarea valorilor operatorului.
  • Dacă argumentul nu este modificat de un operator, în cazul, de exemplu, a unui plus unar, acesta trebuie transmis ca referință la o constantă. În general, acest lucru este valabil pentru aproape toți operatorii aritmetici (adunare, scădere, înmulțire...)
  • Tipul de valoare returnată depinde de natura operatorului. Dacă operatorul trebuie să returneze o nouă valoare, atunci trebuie creat un nou obiect (ca în cazul plusului binar). Dacă doriți să împiedicați modificarea unui obiect ca valoare l, atunci trebuie să-l returnați ca constantă.
  • Operatorii de atribuire trebuie să returneze o referință la elementul modificat. De asemenea, dacă doriți să utilizați operatorul de atribuire în constructe precum (x=y).f(), unde funcția f() este apelată pentru variabila x, după ce o atribuiți lui y, atunci nu returnați o referință la constantă, returnați doar o referință.
  • Operatorii logici ar trebui să returneze un int în cel mai rău caz și un bool în cel mai bun caz.

Optimizarea valorii returnate

Când creați obiecte noi și le returnați dintr-o funcție, ar trebui să utilizați notații similare cu exemplul operatorului binar plus descris mai sus.
return Integer(left.value + right.value);
Sincer să fiu, nu știu ce situație este relevantă pentru C++11; toate argumentele suplimentare sunt valabile pentru C++98.
La prima vedere, aceasta arată similar cu sintaxa pentru crearea unui obiect temporar, adică nu pare să existe nicio diferență între codul de mai sus și acesta:
Integer temp(stânga.valoare + dreapta.valoare); temperatura de retur;
Dar de fapt, în acest caz, constructorul va fi apelat în prima linie, apoi va fi apelat constructorul de copiere, care va copia obiectul, iar apoi, la derularea stivei, va fi apelat destructorul. Când folosește prima intrare, compilatorul creează inițial obiectul în memorie în care trebuie copiat, salvând astfel apelurile către constructorul de copiere și destructor.

Operatori speciali

C++ are operatori care au sintaxă și metode de supraîncărcare specifice. De exemplu, operatorul de indexare. Este întotdeauna definit ca membru al unei clase și, deoarece obiectul indexat este destinat să se comporte ca un tablou, ar trebui să returneze o referință.
Operator virgulă
Operatorii „speciali” includ și operatorul virgulă. Este apelat pe obiecte care au o virgulă lângă ele (dar nu este apelat pe listele de argumente ale funcției). A veni cu un caz de utilizare semnificativ pentru acest operator nu este ușor. Habrauser în comentariile la articolul anterior despre supraîncărcare.
Operator de dereferire pointer
Supraîncărcarea acestor operatori poate fi justificată pentru clasele de indicatori inteligente. Acest operator este definit în mod necesar ca o funcție de clasă și îi sunt impuse unele restricții: trebuie să returneze fie un obiect (sau o referință), fie un pointer care permite accesul la obiect.
Operator de atribuire
Operatorul de atribuire este definit în mod necesar ca o funcție de clasă deoarece este legat în mod intrinsec de obiectul din stânga „=". Definirea globală a operatorului de atribuire ar face posibilă suprascrierea comportamentului implicit al operatorului „=". Exemplu:
clasa Integer ( private: int value; public: Integer(int i): value(i) () Integer& operator=(const Integer& right) ( //verificați auto-atribuirea if (this == &right) ( return *this; ) valoare = dreapta.valoare; return *aceasta; ) );

După cum puteți vedea, la începutul funcției se face o verificare pentru auto-atribuire. În general, în acest caz, însușirea de sine este inofensivă, dar situația nu este întotdeauna atât de simplă. De exemplu, dacă obiectul este mare, puteți petrece mult timp copiere inutilă, sau când lucrați cu pointeri.

Operatori nesupraîncărcați
Unii operatori din C++ nu sunt deloc supraîncărcați. Se pare că acest lucru a fost făcut din motive de securitate.
  • Operatorul de selecție a membrilor clasei „.”.
  • Operator pentru dereferențiarea unui pointer către un membru al clasei „.*”
  • În C++ nu există niciun operator de exponențiere (ca în Fortran) „**”.
  • Este interzisă definirea propriilor operatori (pot fi probleme cu stabilirea priorităților).
  • Prioritățile operatorului nu pot fi modificate
După cum am aflat deja, există două moduri de operare - ca funcție de clasă și ca funcție globală prietenoasă.
Rob Murray, în cartea sa C++ Strategies and Tactics, a definit următoarele recomandări la alegerea formei de operator:

De ce este asta? În primul rând, unii operatori sunt inițial limitati. În general, dacă nu există nicio diferență semantică în modul în care este definit un operator, atunci este mai bine să-l proiectați ca o funcție de clasă pentru a sublinia conexiunea, plus în plus, funcția va fi inline. În plus, uneori poate fi nevoie de a reprezenta operandul din stânga ca obiect al unei alte clase. Probabil cel mai frapant exemplu este redefinirea<< и >> pentru fluxurile I/O.

Literatură

Bruce Eckel - Filosofia C++. Introducere în standardul C++.

Etichete:

  • C++
  • supraîncărcarea operatorilor
  • supraîncărcarea operatorului
Adaugă etichete