Operatoriaus perkrovos pagrindai. Operatoriaus perkrovos pagrindai Perkrovos skiriamoji geba ir A operatoriai

Minimalus priskyrimo operatorius yra

Void Cls::operator=(Cls other) ( swap(*this, other); )

Pagal standartą tai yra kopijavimas priskyrimo operatorius.
Tačiau jis taip pat gali atlikti judėjimą, jei Cls turi perkėlimo konstruktorių:

Cls a, b; a = std::move(b); // Veikia kaip // Cls other(std::move(b)); a.operatorius=(kita); // ^^^^^^^^^^ // perkelti: skambinti Cls::Cls(Cls&&)

Po apsikeitimo dabartiniai klasės nariai patenka į laikiną kitą objektą ir ištrinami, kai baigiasi priskyrimo sakinys.
Kopijos priskyrimas sau padarys papildomą kopiją, tačiau klaidų nebus.

Rezultato tipas gali būti bet koks.
Automatiškai sugeneruotas priskyrimo operatorius turi grąžinimo tipą Cls& ir grąžina *this . Tai leidžia rašyti kodą, pavyzdžiui, a = b = c arba (a = b) > c .
Tačiau daugelis kodo stiliaus konvencijų to nepatvirtina, ypač žr. CppCoreGuidelines ES.expr „Venkite sudėtingų posakių“ .

Kad šis priskyrimo operatorius veiktų, jums reikia kopijavimo / perkėlimo konstruktorių ir apsikeitimo funkcijos.
Kartu tai atrodo taip:

Klasės Cls ( viešas: Cls() () // Kopijuoti konstruktorių Cls(const Cls& other) : x(other.x), y(other.y) () // Perkelti konstruktorių Cls(Cls&& other) noexcept ( swap(*) this, other); ) // Priskyrimo operatorius void operator=(Cls other) noexcept ( swap(*this, other); ) // Keistis draugu void swap(Cls& a, Cls& b) noexcept ( naudojant std::swap; / / Standartinės funkcijos įtraukimas į perkrovų sąrašą... swap(a.x, b.x); // ... ir iškvietimas naudojant argumentų tipo paiešką (ADL). swap(a.y, b.y); ) private: X x; Y y ; );

Kopijavimo konstruktorius nukopijuoja kiekvieną klasės narį.

Perkėlimo konstruktorius sukuria tuščius klasės narius, o tada keičia juos savo argumentu. Galite perkelti kiekvieną narį atskirai, bet patogiau naudoti swap .

Apsikeitimo funkcija gali būti nemokama draugo funkcija. Daugelis algoritmų tikisi nemokamos apsikeitimo funkcijos ir iškviečia ją per argumentų tipo paiešką (ADL).
Anksčiau taip pat buvo rekomenduojama rašyti metodas sukeisti, kad galėtumėte parašyti f().swap(x); , tačiau atsiradus judėjimo semantikai to nebereikėjo.

Jei funkcijos negali padaryti išimčių, jos turi būti pažymėtos noexcept. Tai reikalinga std::move_if_noexcept ir kitoms funkcijoms, kurios gali naudoti efektyvesnį kodą, jei priskyrimas ar konstruktorius nenumato išimčių. Šiai klasei sutrikimas

Std::is_nothrow_copy_constructible == 0 std::is_nothrow_move_constructible == 1 std::is_nothrow_copy_assignable == 0 std::is_nothrow_move_assignable == 1

Nors priskyrimo operatorius pažymėtas noexcept , iškvietus jį su const Cls& argumentu, bus nukopijuotas, o tai gali sukelti išimtį. Todėl is_nothrow_copy_assignable grąžina false .

Gera diena!

Noras parašyti šį straipsnį atsirado perskaičius įrašą, nes daug svarbių temų jame neapžvelgta.

Svarbiausia atsiminti, kad operatoriaus perkrova yra tik patogesnis būdas iškviesti funkcijas, todėl nesijaudinkite dėl operatoriaus perkrovos. Jis turėtų būti naudojamas tik tada, kai tai palengvins kodo rašymą. Bet ne tiek, kad būtų sunku skaityti. Juk, kaip žinia, kodas skaitomas daug dažniau nei rašomas. Ir nepamirškite, kad jums niekada nebus leista perkrauti operatorių kartu su integruotais tipais; perkrovimo galimybė yra tik vartotojo apibrėžtiems tipams / klasėms.

Perkrovos sintaksė

Operatoriaus perkrovos sintaksė labai panaši į funkcijos, vadinamos operator@, apibrėžimą, kur @ yra operatoriaus identifikatorius (pvz., +, -,<<, >>). Pažvelkime į paprastą pavyzdį:
class Integer ( private: int value; public: Integer(int i): value(i) () const Integer operator+(const Integer& rv) const ( return (vertė + rv.value); ) );
Šiuo atveju operatorius yra įrėmintas kaip klasės narys, argumentas nustato reikšmę, esančią dešinėje operatoriaus pusėje. Apskritai, yra du pagrindiniai būdai, kaip perkrauti operatorius: visuotinės funkcijos, kurios yra draugiškos klasei, arba pačios klasės funkcijos. Kuris metodas kuriam operatoriui yra geresnis, svarstysime temos pabaigoje.

Daugeliu atvejų operatoriai (išskyrus sąlyginius) grąžina objektą arba nuorodą į tipą, kuriam priklauso jo argumentai (jei tipai skiriasi, tuomet jūs nusprendžiate, kaip interpretuoti operatorių vertinimo rezultatą).

Vienareikšmių operatorių perkrovimas

Pažvelkime į vienkartinio operatoriaus perkrovos pavyzdžius aukščiau apibrėžtai sveikųjų skaičių klasei. Tuo pačiu apibrėžkime jas draugiškų funkcijų pavidalu ir apsvarstykime mažėjimo ir didinimo operatorius:
klasė Sveikasis skaičius ( privatus: int vertė; viešas: sveikas skaičius (int i): vertė (i) () //unary + draugas const Integer& operatorius+(const Integer& i); //unary - draugas const Sveikasis skaičius operatorius-(const Integer& i) ; //priedėlio padidinimas draugo konst Sveikasis skaičius& operatorius++(Sveikasis skaičius& i); //postfix padidinimas draugo konst. Sveikasis skaičius operatorius++(Sveikasis skaičius& i, int); //priešdėlis mažinti draugo konst. Sveikasis skaičius& operatorius--(Sveikasis skaičius& i); //postfix sumažinti draugo konst. Integer operatorius--(Sveikasis skaičius& i, int); ); //unary plus nieko nedaro. const Integer& operator+(const Integer& i) ( return i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //priedėlio versija grąžina vertę po padidėjimo const Integer& operator++(Integer& i) ( i.value++; return i; ) //postfix versija grąžina reikšmę prieš padidėjimą const Integer operatorius++(Sveikasis skaičius& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //priešdėlio versija grąžina reikšmė po decrement const Integer& operator--(Integer& i) ( i.value--; return i; ) //postfix versija grąžina reikšmę prieš decrement const Integer operatorius--(Integer& i, int) ( Integer oldValue(i. vertė); i .value--;return oldValue; )
Dabar žinote, kaip kompiliatorius atskiria sumažinimo ir didinimo versijas su priešdėliu ir postfiksu. Tuo atveju, kai mato išraišką ++i, iškviečiamas funkcijos operatorius++(a). Jei mato i++, tada iškviečiamas operatorius++(a, int). Tai yra, perkrauta operatorius++ funkcija vadinama, ir tam naudojamas postfix versijos parametras dummy int.

Dvejetainiai operatoriai

Pažiūrėkime į dvejetainių operatorių perkrovimo sintaksę. Perkraukime vieną operatorių, kuris grąžina l reikšmę, vieną sąlyginį operatorių ir vieną operatorių, kuris sukuria naują reikšmę (apibrėžkime juos globaliai):
klasė Sveikasis skaičius ( privatus: int vertė; viešas: sveikas skaičius (int i): vertė (i) () draugas const Integer operator+ (const Integer& left, const Integer& right); draugas Sveikasis skaičius& operator+=(Sveikas skaičius& kairėn, const Integer& dešinėn); draugas bool operatorius==(const Sveikasis skaičius& kairėje, const Sveikasis skaičius& dešinėje); ); const Integer operatorius+(const Integer& left, const Integer& right) ( 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; )
Visuose šiuose pavyzdžiuose to paties tipo operatoriai yra perkrauti, tačiau tai nėra būtina. Pavyzdžiui, galite perkrauti mūsų sveikojo skaičiaus ir slankiojo skaičiaus pridėjimą pagal panašumą.

Argumentai ir grąžinimo reikšmės

Kaip matote, pavyzdžiuose naudojami skirtingi argumentų perdavimo funkcijoms ir operatorių reikšmių grąžinimo būdai.
  • Jei argumento nepakeičia operatorius, pavyzdžiui, vienkartinio pliuso atveju, jis turi būti perduodamas kaip nuoroda į konstantą. Apskritai tai galioja beveik visiems aritmetiniams operatoriams (sudėčiai, atimčiai, daugybai...)
  • Grąžinamos vertės tipas priklauso nuo operatoriaus pobūdžio. Jei operatorius turi grąžinti naują reikšmę, turi būti sukurtas naujas objektas (kaip ir dvejetainio pliuso atveju). Jei norite, kad objektas nebūtų modifikuojamas kaip l reikšmė, turite grąžinti jį kaip konstantą.
  • Priskyrimo operatoriai turi grąžinti nuorodą į pakeistą elementą. Be to, jei norite naudoti priskyrimo operatorių tokiose konstrukcijose kaip (x=y).f(), kur funkcija f() iškviečiama kintamąjį x, priskyrę jį y, negrąžinkite nuorodos į pastovus, tiesiog grąžinkite nuorodą.
  • Loginiai operatoriai blogiausiu atveju turėtų grąžinti int, o geriausiu atveju bool.

Grąžinimo vertės optimizavimas

Kurdami naujus objektus ir grąžindami juos iš funkcijos, turėtumėte naudoti žymėjimą, panašų į aukščiau aprašytą dvejetainio pliuso operatoriaus pavyzdį.
return Integer(left.value + right.value);
Tiesą sakant, aš nežinau, kokia situacija yra svarbi C++11; visi kiti argumentai galioja C++98.
Iš pirmo žvilgsnio tai atrodo panaši į laikinojo objekto kūrimo sintaksę, tai yra, atrodo, kad nėra jokio skirtumo tarp aukščiau esančio kodo ir šio:
Integer temp(left.value + right.value); grąžinimo temperatūra;
Bet iš tikrųjų šiuo atveju pirmoje eilutėje bus iškviestas konstruktorius, tada bus iškviestas kopijavimo konstruktorius, kuris nukopijuos objektą, o tada, išvyniojus krūvą, bus iškviestas destruktorius. Naudojant pirmąjį įrašą, kompiliatorius iš pradžių sukuria objektą atmintyje, į kurį jį reikia nukopijuoti, taip išsaugodamas iškvietimus kopijavimo konstruktoriui ir naikintojui.

Specialieji operatoriai

C++ turi operatorius, turinčius specifinę sintaksę ir perkrovos metodus. Pavyzdžiui, indeksavimo operatorius. Jis visada apibrėžiamas kaip klasės narys ir, kadangi indeksuotas objektas turi veikti kaip masyvas, jis turėtų grąžinti nuorodą.
Kablelių operatorius
„Specialieji“ operatoriai taip pat apima kablelio operatorių. Jis iškviečiamas objektuose, prie kurių yra kablelis (tačiau funkcijų argumentų sąrašuose jis nešaukiamas). Sugalvoti prasmingą šio operatoriaus naudojimo atvejį nėra lengva. Habrauseris ankstesnio straipsnio apie perkrovą komentaruose.
Rodyklės nuorodos pašalinimo operatorius
Šių operatorių perkrovimas gali būti pateisinamas išmaniųjų rodyklių klasėms. Šis operatorius būtinai apibrėžiamas kaip klasės funkcija ir jai taikomi tam tikri apribojimai: jis turi grąžinti arba objektą (arba nuorodą), arba rodyklę, leidžiančią pasiekti objektą.
Priskyrimo operatorius
Priskyrimo operatorius būtinai apibrėžiamas kaip klasės funkcija, nes jis yra iš esmės susietas su objektu, esančiu kairėje nuo „=“. Visapusiškai apibrėžus priskyrimo operatorių, būtų galima nepaisyti numatytosios operatoriaus „="" elgsenos. Pavyzdys:
klasė Sveikasis skaičius ( privatus: int vertė; viešasis: Integer(int i): reikšmė(i) () Integer& operator=(const Integer& right) ( //patikrinkite, ar nėra priskyrimo sau if (this == &right) ( return *this; ) reikšmė = right.value; grąžinti *tai; ) );

Kaip matote, funkcijos pradžioje tikrinamas savęs priskyrimas. Apskritai šiuo atveju savęs pasisavinimas yra nekenksmingas, tačiau situacija ne visada tokia paprasta. Pavyzdžiui, jei objektas yra didelis, galite sugaišti daug laiko bereikalingai kopijuodami arba dirbdami su rodyklėmis.

Neperkraunami operatoriai
Kai kurie C++ operatoriai visai neperkrauti. Matyt, tai buvo padaryta saugumo sumetimais.
  • Klasės narių atrankos operatorius ".".
  • Operatorius, skirtas pašalinti žymeklį į klasės narį ".*"
  • C++ kalboje nėra eksponencijos operatoriaus (kaip Fortran) „**“.
  • Draudžiama apibrėžti savo operatorius (gali kilti problemų nustatant prioritetus).
  • Operatoriaus prioritetų keisti negalima
Kaip jau išsiaiškinome, yra du operatorių būdai – kaip klasės funkcija ir kaip draugiška globali funkcija.
Robas Murray savo knygoje C++ strategijos ir taktikos apibrėžia šias operatoriaus formos pasirinkimo gaires:

Kodėl taip? Pirma, kai kurie operatoriai iš pradžių yra riboti. Apskritai, jei nėra semantinio skirtumo, kaip apibrėžiamas operatorius, geriau jį sukurti kaip klasės funkciją, kad būtų pabrėžtas ryšys, be to, funkcija bus tiesioginė. Be to, kartais gali prireikti kairįjį operandą pavaizduoti kaip kitos klasės objektą. Turbūt ryškiausias pavyzdys yra iš naujo apibrėžti<< и >> I/O srautams.

Gera diena!

Noras parašyti šį straipsnį atsirado perskaičius įrašą Overloading C++ Operators, nes daug svarbių temų jame neapžvelgta.

Svarbiausia atsiminti, kad operatoriaus perkrova yra tik patogesnis būdas iškviesti funkcijas, todėl nesijaudinkite dėl operatoriaus perkrovos. Jis turėtų būti naudojamas tik tada, kai tai palengvins kodo rašymą. Bet ne tiek, kad būtų sunku skaityti. Juk, kaip žinia, kodas skaitomas daug dažniau nei rašomas. Ir nepamirškite, kad jums niekada nebus leista perkrauti operatorių kartu su integruotais tipais; perkrovimo galimybė yra tik vartotojo apibrėžtiems tipams / klasėms.

Perkrovos sintaksė

Operatoriaus perkrovos sintaksė labai panaši į funkcijos, vadinamos operator@, apibrėžimą, kur @ yra operatoriaus identifikatorius (pvz., +, -,<<, >>). Pažvelkime į paprastą pavyzdį:
class Integer ( private: int value; public: Integer(int i): value(i) () const Integer operator+(const Integer& rv) const ( return (vertė + rv.value); ) );
Šiuo atveju operatorius yra įrėmintas kaip klasės narys, argumentas nustato reikšmę, esančią dešinėje operatoriaus pusėje. Apskritai, yra du pagrindiniai būdai, kaip perkrauti operatorius: visuotinės funkcijos, kurios yra draugiškos klasei, arba pačios klasės funkcijos. Kuris metodas kuriam operatoriui yra geresnis, svarstysime temos pabaigoje.

Daugeliu atvejų operatoriai (išskyrus sąlyginius) grąžina objektą arba nuorodą į tipą, kuriam priklauso jo argumentai (jei tipai skiriasi, tuomet jūs nusprendžiate, kaip interpretuoti operatorių vertinimo rezultatą).

Vienareikšmių operatorių perkrovimas

Pažvelkime į vienkartinio operatoriaus perkrovos pavyzdžius aukščiau apibrėžtai sveikųjų skaičių klasei. Tuo pačiu apibrėžkime jas draugiškų funkcijų pavidalu ir apsvarstykime mažėjimo ir didinimo operatorius:
klasė Sveikasis skaičius ( privatus: int vertė; viešas: sveikas skaičius (int i): vertė (i) () //unary + draugas const Integer& operatorius+(const Integer& i); //unary - draugas const Sveikasis skaičius operatorius-(const Integer& i) ; //priedėlio padidinimas draugo konst Sveikasis skaičius& operatorius++(Sveikasis skaičius& i); //postfix padidinimas draugo konst. Sveikasis skaičius operatorius++(Sveikasis skaičius& i, int); //priešdėlis mažinti draugo konst. Sveikasis skaičius& operatorius--(Sveikasis skaičius& i); //postfix sumažinti draugo konst. Integer operatorius--(Sveikasis skaičius& i, int); ); //unary plus nieko nedaro. const Integer& operator+(const Integer& i) ( return i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //priedėlio versija grąžina vertę po padidėjimo const Integer& operator++(Integer& i) ( i.value++; return i; ) //postfix versija grąžina reikšmę prieš padidėjimą const Integer operatorius++(Sveikasis skaičius& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //priešdėlio versija grąžina reikšmė po decrement const Integer& operator--(Integer& i) ( i.value--; return i; ) //postfix versija grąžina reikšmę prieš decrement const Integer operatorius--(Integer& i, int) ( Integer oldValue(i. vertė); i .value--;return oldValue; )
Dabar žinote, kaip kompiliatorius atskiria sumažinimo ir didinimo versijas su priešdėliu ir postfiksu. Tuo atveju, kai mato išraišką ++i, iškviečiamas funkcijos operatorius++(a). Jei mato i++, tada iškviečiamas operatorius++(a, int). Tai yra, perkrauta operatorius++ funkcija vadinama, ir tam naudojamas postfix versijos parametras dummy int.

Dvejetainiai operatoriai

Pažiūrėkime į dvejetainių operatorių perkrovimo sintaksę. Perkraukime vieną operatorių, kuris grąžina l reikšmę, vieną sąlyginį operatorių ir vieną operatorių, kuris sukuria naują reikšmę (apibrėžkime juos globaliai):
klasė Sveikasis skaičius ( privatus: int vertė; viešas: sveikas skaičius (int i): vertė (i) () draugas const Integer operator+ (const Integer& left, const Integer& right); draugas Sveikasis skaičius& operator+=(Sveikas skaičius& kairėn, const Integer& dešinėn); draugas bool operatorius==(const Sveikasis skaičius& kairėje, const Sveikasis skaičius& dešinėje); ); const Integer operatorius+(const Integer& left, const Integer& right) ( 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; )
Visuose šiuose pavyzdžiuose to paties tipo operatoriai yra perkrauti, tačiau tai nėra būtina. Pavyzdžiui, galite perkrauti mūsų sveikojo skaičiaus ir slankiojo skaičiaus pridėjimą pagal panašumą.

Argumentai ir grąžinimo reikšmės

Kaip matote, pavyzdžiuose naudojami skirtingi argumentų perdavimo funkcijoms ir operatorių reikšmių grąžinimo būdai.
  • Jei argumento nepakeičia operatorius, pavyzdžiui, vienkartinio pliuso atveju, jis turi būti perduodamas kaip nuoroda į konstantą. Apskritai tai galioja beveik visiems aritmetiniams operatoriams (sudėčiai, atimčiai, daugybai...)
  • Grąžinamos vertės tipas priklauso nuo operatoriaus pobūdžio. Jei operatorius turi grąžinti naują reikšmę, turi būti sukurtas naujas objektas (kaip ir dvejetainio pliuso atveju). Jei norite, kad objektas nebūtų modifikuojamas kaip l reikšmė, turite grąžinti jį kaip konstantą.
  • Priskyrimo operatoriai turi grąžinti nuorodą į pakeistą elementą. Be to, jei norite naudoti priskyrimo operatorių tokiose konstrukcijose kaip (x=y).f(), kur funkcija f() iškviečiama kintamąjį x, priskyrę jį y, negrąžinkite nuorodos į pastovus, tiesiog grąžinkite nuorodą.
  • Loginiai operatoriai blogiausiu atveju turėtų grąžinti int, o geriausiu atveju bool.

Grąžinimo vertės optimizavimas

Kurdami naujus objektus ir grąžindami juos iš funkcijos, turėtumėte naudoti žymėjimą, panašų į aukščiau aprašytą dvejetainio pliuso operatoriaus pavyzdį.
return Integer(left.value + right.value);
Tiesą sakant, aš nežinau, kokia situacija yra svarbi C++11; visi kiti argumentai galioja C++98.
Iš pirmo žvilgsnio tai atrodo panaši į laikinojo objekto kūrimo sintaksę, tai yra, atrodo, kad nėra jokio skirtumo tarp aukščiau esančio kodo ir šio:
Integer temp(left.value + right.value); grąžinimo temperatūra;
Bet iš tikrųjų šiuo atveju pirmoje eilutėje bus iškviestas konstruktorius, tada bus iškviestas kopijavimo konstruktorius, kuris nukopijuos objektą, o tada, išvyniojus krūvą, bus iškviestas destruktorius. Naudojant pirmąjį įrašą, kompiliatorius iš pradžių sukuria objektą atmintyje, į kurį jį reikia nukopijuoti, taip išsaugodamas iškvietimus kopijavimo konstruktoriui ir naikintojui.

Specialieji operatoriai

C++ turi operatorius, turinčius specifinę sintaksę ir perkrovos metodus. Pavyzdžiui, indeksavimo operatorius. Jis visada apibrėžiamas kaip klasės narys ir, kadangi indeksuotas objektas turi veikti kaip masyvas, jis turėtų grąžinti nuorodą.
Kablelių operatorius
„Specialieji“ operatoriai taip pat apima kablelio operatorių. Jis iškviečiamas objektuose, prie kurių yra kablelis (tačiau funkcijų argumentų sąrašuose jis nešaukiamas). Sugalvoti prasmingą šio operatoriaus naudojimo atvejį nėra lengva. Habrowser AxisPod ankstesnio straipsnio apie perkrovą komentaruose kalbėjo apie vieną dalyką.
Rodyklės nuorodos pašalinimo operatorius
Šių operatorių perkrovimas gali būti pateisinamas išmaniųjų rodyklių klasėms. Šis operatorius būtinai apibrėžiamas kaip klasės funkcija ir jai taikomi tam tikri apribojimai: jis turi grąžinti arba objektą (arba nuorodą), arba rodyklę, leidžiančią pasiekti objektą.
Priskyrimo operatorius
Priskyrimo operatorius būtinai apibrėžiamas kaip klasės funkcija, nes jis yra iš esmės susietas su objektu, esančiu kairėje nuo „=“. Visapusiškai apibrėžus priskyrimo operatorių, būtų galima nepaisyti numatytosios operatoriaus „="" elgsenos. Pavyzdys:
klasė Sveikasis skaičius ( privatus: int vertė; viešasis: Integer(int i): reikšmė(i) () Integer& operator=(const Integer& right) ( //patikrinkite, ar nėra priskyrimo sau if (this == &right) ( return *this; ) reikšmė = right.value; grąžinti *tai; ) );

Kaip matote, funkcijos pradžioje tikrinamas savęs priskyrimas. Apskritai šiuo atveju savęs pasisavinimas yra nekenksmingas, tačiau situacija ne visada tokia paprasta. Pavyzdžiui, jei objektas yra didelis, galite sugaišti daug laiko bereikalingai kopijuodami arba dirbdami su rodyklėmis.

Neperkraunami operatoriai
Kai kurie C++ operatoriai visai neperkrauti. Matyt, tai buvo padaryta saugumo sumetimais.
  • Klasės narių atrankos operatorius ".".
  • Operatorius, skirtas pašalinti žymeklį į klasės narį ".*"
  • C++ kalboje nėra eksponencijos operatoriaus (kaip Fortran) „**“.
  • Draudžiama apibrėžti savo operatorius (gali kilti problemų nustatant prioritetus).
  • Operatoriaus prioritetų keisti negalima
Kaip jau išsiaiškinome, yra du operatorių būdai – kaip klasės funkcija ir kaip draugiška globali funkcija.
Robas Murray savo knygoje C++ strategijos ir taktikos apibrėžia šias operatoriaus formos pasirinkimo gaires:

Kodėl taip? Pirma, kai kurie operatoriai iš pradžių yra riboti. Apskritai, jei nėra semantinio skirtumo, kaip apibrėžiamas operatorius, geriau jį sukurti kaip klasės funkciją, kad būtų pabrėžtas ryšys, be to, funkcija bus tiesioginė. Be to, kartais gali prireikti kairįjį operandą pavaizduoti kaip kitos klasės objektą. Turbūt ryškiausias pavyzdys yra iš naujo apibrėžti<< и >> I/O srautams.

Operatoriaus perkrovos pagrindai

C#, kaip ir bet kuri programavimo kalba, turi paruoštą žetonų rinkinį, naudojamą pagrindinėms operacijoms atlikti su integruotais tipais. Pavyzdžiui, žinoma, kad + operacija gali būti taikoma dviem sveikiesiems skaičiams, kad būtų gauta jų suma:

// Operacija + su sveikaisiais skaičiais. int a = 100; int b = 240; int c = a + b; //s dabar yra lygus 340

Čia nėra nieko naujo, bet ar kada pagalvojote, kad tą pačią + operaciją galima pritaikyti daugeliui C# integruotų duomenų tipų? Pavyzdžiui, apsvarstykite šį kodą:

// Operacija + su eilutėmis. string si = "Sveiki"; string s2 = "pasaulis!"; eilutė s3 = si + s2; // s3 dabar yra "Hello world!"

Iš esmės + operacijos funkcionalumas yra unikaliai pagrįstas duomenų tipais (šiuo atveju eilutėmis arba sveikaisiais skaičiais). Kai + operacija taikoma skaitiniams tipams, gauname operandų aritmetinę sumą. Tačiau kai ta pati operacija taikoma eilučių tipams, rezultatas yra eilučių sujungimas.

C# kalba suteikia galimybę kurti specialias klases ir struktūras, kurios taip pat unikaliai reaguoja į tą patį pagrindinių žetonų rinkinį (pvz., + operatorius). Nepamirškite, kad absoliučiai kiekvienas įtaisytas C# operatorius negali būti perkrautas. Šioje lentelėje aprašomos pagrindinių operacijų perkrovos galimybės:

C# operacija Perkrovos galimybė
+, -, !, ++, --, tiesa, klaidinga Šis unarinių operatorių rinkinys gali būti perkrautas
+, -, *, /, %, &, |, ^, > Šios dvejetainės operacijos gali būti perkrautos
==, !=, <, >, <=, >= Šie palyginimo operatoriai gali būti perkrauti. C# reikalauja bendrai perkrauti "like" operatorius (t.y.< и >, <= и >=, == ir!=)
Operacija negali būti perkrauta. Tačiau indeksuotojai siūlo panašias funkcijas
() () operacijos negalima perkrauti. Tačiau specialūs konvertavimo metodai suteikia tą pačią funkciją
+=, -=, *=, /=, %=, &=, |=, ^=, >= Trumpų paskyrimų operatoriai negali būti perkrauti; tačiau juos gaunate automatiškai, perkraudami atitinkamą dvejetainę operaciją

Operatoriaus perkrova yra glaudžiai susijusi su metodo perkrova. Norėdami perkrauti operatorių, naudokite raktinį žodį operatorius, kuris apibrėžia operatoriaus metodą, kuris savo ruožtu apibrėžia operatoriaus veiksmą, palyginti su jo klase. Yra dvi operatoriaus metodų formos: viena – vienanariams, kita – dvejetainiams operatoriams. Toliau pateikiama bendra kiekvieno šių metodų varianto forma:

// Bendra unarinio operatoriaus perkrovos forma. public static return_type operator op(parameter_type operand) ( // operacijos ) // Bendra dvejetainio operatoriaus perkrovimo forma. viešas statinis return_type operatorius op(parameter_type1 operand1, parameter_type2 operaand2) ( // operacijos )

Čia op pakeičiamas perkrautu operatoriumi, pavyzdžiui, + arba /, ir grąžinimo_tipasžymi konkretų vertės tipą, kurį grąžina nurodyta operacija. Ši reikšmė gali būti bet kokio tipo, tačiau dažnai nurodoma, kad ji turi būti tokio paties tipo kaip ir klasė, kuriai operatorius yra perkrautas. Ši koreliacija leidžia lengviau naudoti perkrautus operatorius išraiškose. Unariniams operatoriams operandasžymi perduodamą operandą, o dvejetainiams operatoriams – tas pats operandas1 Ir operandas2. Atkreipkite dėmesį, kad operatorių metodai turi turėti ir viešus, ir statinius tipo specifikacijas.

Dvejetainių operatorių perkrova

Pažvelkime į dvejetainio operatoriaus perkrovimo naudojimą naudodami paprastą pavyzdį:

Sistemos naudojimas; naudojant System.Collections.Generic; naudojant System.Linq; naudojant System.Text; vardų sritis ConsoleApplication1 ( class MyArr ( // Taško koordinatės trimatėje erdvėje public int x, y, z; public MyArr(int x = 0, int y = 0, int z = 0) ( this.x = x; this.y = y; this.z = z; ) // Perkraukite dvejetainį operatorių + viešą statinį MyArr operatorių +(MyArr obj1, MyArr obj2) ( MyArr arr = new MyArr(); arr.x = obj1.x + obj2 .x; arr. y = obj1.y + obj2.y; arr.z = obj1.z + obj2.z; grįžti arr; ) // Perkrauti dvejetainį operatorių - viešas statinis MyArr operatorius -(MyArr obj1, MyArr obj2) ( MyArr arr = naujas ManoArr (); arr.x = obj1.x - obj2.x; arr.y = obj1.y - obj2.y; arr.z = obj1.z - obj2.z; grįžti arr; ) ) klasė Programa ( statinė void Pagrindinis (eilutės args) ( MyArr Point1 = new MyArr(1, 12, -4); MyArr Point2 = new MyArr(0, -3, 18); Console.WriteLine ("Pirmojo taško koordinatės: " + Point1.x + " " + Point1.y + " " + Point1.z); Console.WriteLine("Antrojo taško koordinatės: " + Point2.x + " " + Point2.y + " " + Point2. z + "\n"); MyArr Point3 = 1 taškas + 2 taškas; Console.WriteLine("\nPoint1 + Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Taškas3 = taškas1 – taškas2; Console.WriteLine("\nTaškas1 - Taškas2 = " + Taškas3.x + " " + Taškas3.y + " " + Taškas3.z); Console.ReadLine(); ) ) )

Vienareikšmių operatorių perkrovimas

Vienanariai operatoriai yra perkrauti taip pat, kaip ir dvejetainiai operatoriai. Žinoma, pagrindinis skirtumas yra tas, kad jie turi tik vieną operandą. Modernizuokime ankstesnį pavyzdį, pridėdami operatorių perkrovas ++, --, -:

Sistemos naudojimas; naudojant System.Collections.Generic; naudojant System.Linq; naudojant System.Text; vardų sritis ConsoleApplication1 ( class MyArr ( // Taško koordinatės trimatėje erdvėje public int x, y, z; public MyArr(int x = 0, int y = 0, int z = 0) ( this.x = x; this.y = y; this.z = z; ) // Perkraukite dvejetainį operatorių + viešą statinį MyArr operatorių +(MyArr obj1, MyArr obj2) ( MyArr arr = new MyArr(); arr.x = obj1.x + obj2 .x; arr. y = obj1.y + obj2.y; arr.z = obj1.z + obj2.z; grįžti arr; ) // Perkrauti dvejetainį operatorių - viešas statinis MyArr operatorius -(MyArr obj1, MyArr obj2) ( MyArr arr = naujas MyArr (); arr.x = obj1.x - obj2.x; arr.y = obj1.y - obj2.y; arr.z = obj1.z - obj2.z; grįžti arr; ) / / Perkraukite unarinį operatorių – viešas statinis MyArr operatorius -(MyArr obj1) ( MyArr arr = new MyArr(); arr.x = -obj1.x; arr.y = -obj1.y; arr.z = -obj1.z ; return arr; ) // Perkraunamas unarinis operatorius ++ viešas statinis MyArr operatorius ++(MyArr obj1) ( obj1.x += 1; obj1.y += 1; obj1.z +=1; return obj1; ) / / Perkraunamas unarinis operatorius -- viešas statinis MyArr operatorius ---(MyArr obj1) ( obj1.x -= 1; obj1.y - = 1; obj1.z - = 1; grąžinti obj1; ) ) class Program ( static void Main(string args) ( MyArr Point1 = new MyArr(1, 12, -4); MyArr Point2 = new MyArr(0, -3, 18); Console.WriteLine("Pirmosios koordinatės taškas: " + Point1.x + " " + Point1.y + " " + Point1.z); Console.WriteLine("Antrojo taško koordinatės: " + Point2.x + " " + Point2.y + " " + Point2.z + "\n"); MyArr Point3 = 1 taškas + 2 taškas; Console.WriteLine("\n1 taškas + 2 taškas = " + taškas3.x + " " + taškas 3.y + " " + taškas 3.z); taškas 3 = Taškas1 – taškas 2; Console.WriteLine("Taškas1 - Taškas 2 = " + Taškas3.x + " " + Taškas3.y + " " + Taškas3.z); Taškas3 = -Taškas1; Console.WriteLine("-Point1 = " + Point3 .x + " " + Point3.y + " " + Point3.z); Point2++; Console.WriteLine("Point2++ = " + Point2.x + " " + Point2.y + " " + Point2.z); Point2- -; Console. WriteLine("Point2-- = " + Point2.x + " " + Point2.y + " " + Point2.z); Console.ReadLine(; ) ) )