Претоварване на оператори в C ++: основи, примери

Във всяка наука има стандартни обозначения, които улесняват разбирането на идеите. Например в математиката става дума за умножение, разделяне, добавяне и друга символична нотация. Изразът (x + y * z) е много по-лесен за разбиране от "умножаване на y, z и добавяне към x". Представете си, че до XVI век математиката не е имала символична нотация, всички изрази са написани устно, сякаш е художествен текст с описание. И обичайните признаци на операции за нас се появиха по-късно. Трудно е да се надценява смисъла на кратък запис на характер. Въз основа на тези съображения езиците за програмиране бяха добавени към претоварването на операторите. Помислете за примера.


Пример за претоварване на оператор

Почти като всеки език, C ++ поддържа множество оператори, работещи с типове данни, вградени в стандартния език. Но повечето програми използват специални типове за решаване на определени задачи. Например, сложна математика или матрична алгебра се реализират в програмата чрез представяне на комплексни числа или матрици в потребителски C ++ типове. Вградените оператори не са в състояние да разпространяват работата си и да извършват необходимите процедури за определени класове, колкото и очевидни да изглеждат. Следователно, за добавяне на матрици, например, обикновено се създава отделна функция. Очевидно, извикването на sum_matrix (A, B) в кода ще има по-малко ясен знак от израза A + B.
Разгледайте приблизителния клас на комплексните числа:

//представете сложно число под формата на двойка числа сс плаваща запетая.
класов комплекс {{12} двоен ре, im;
публичен:
комплекс (двойно r, двойно i): re (r), im (i) {} //конструктор
сложен оператор + (комплекс); //претоварващ комплект
сложен оператор * (комплекс); претоварването на умножението
;

невалиден главен () {
комплекс a {1 2}, b {3}}, c {0}};
c = a + b;
с = а.оператор + (б); ////операторната функция може да се нарече всяка функция, този запис е еквивалентен на a + b
c = a * b + комплекс (1 3); //Изпълняваме обичайните правила на приоритета на операциите по прибавяне и умножение
}

По подобен начин можете да направите, например, претоварване на I /O оператори в C ++ и да ги адаптирате за извеждане на такива сложни структури като матрици.

На разположение на операторите за претоварване

Пълен списък на всички оператори, за които може да се използва механизмът за претоварване:

+

*

/

39]

110

нови

%

^

& amp;

|

~

!

=

.

& gt;

+ =

- =

* =

/=

^ =

и

| 62]

=

=

==

84]

! =

> =

&;

||

++

- *

,

- & gt;

изтриване

изтриване []

Както може да се види от таблицата, претоварването е допустимо за повечето езикови оператори. Не е необходимо претоварване на оператора. Това се прави единствено за удобство. Следователно, претоварването на операторите в Java например отсъства. А сега за такъв важен момент.

Оператори, чието претоварване е забранено

  • Разрешение за видимост - «::»;
  • Изборът на член е ".";
  • Избор на член чрез указател към член - ". *";
  • Троен условен оператор - «?:»;
  • размер на оператора;
  • Операторът на типа.

Десният операнд на данните на операторите е името, а не стойността. Следователно разрешението за тяхното претоварване би могло да доведе до писането на много двусмислени дизайни и значително ще усложни живота на програмистите. Въпреки че има много езици за програмиране, които позволяват претоварването на всички оператори - например претоварването на операторите на Python.


& lt; script type = "text /javascript" & gt;
може blockSettings2 = {blockId: "R-A-70350-2", renderTo: "yandex_rtb_R-A-70350-2", async:! 0};

if (document.cookie.indexOf ("abmatch =") & gt; = 0) {
blockSettings2 = {blockId: "RA-70350-2", renderTo: "yandex_rtb_R-A-70350- 2 ", statId: 70350async:! 0};
}

Функция (a, b, c, d, e) {a [c] = a [c] || [], a [c] .push (функция () {Ya .Context.AdvManager.render (blockSettings2)}), e = b.getElementsByTagName ("script") , d = b.createElement ("script"), d.type = "text /javascript", d.src = "//an.yandex.ru/system/context.js", d.async =! 0e.parentNode.insertBefore (d, e)} (това, този.документ, "yandexContextAsyncCallbacks");

Ограничения

Ограничение на претоварването на оператора:

  • Не можете да промените двоичния оператор на унарните и обратно, тъй като не можете да добавите третия операнд.
  • Не можете да създавате нови оператори, различни от тези, които са. Това ограничениенасърчава премахването на много неясноти. Ако има нужда от нов оператор, можете да използвате функция, която ще изпълни желаното действие за тези цели.
  • Операторната функция може да бъде член на клас или да има поне един тип аргумент. Изключение са новите и изтриване на оператори. Това правило забранява промяна на значението на изразите, ако те не съдържат потребителски типове обекти. По-специално, не можете да създадете операторска функция, която да работи само с указатели или да принуди оператора на добавянето да работи като умножение. Изключения са операторите "=", "& amp;" и "," за класовите обекти.
  • Операторската функция с първия термин, принадлежаща на един от вградените типове данни на езика на C ++, не може да бъде член на класа.
  • Името на всяка операторска функция започва с ключовата дума оператор, последвана от символично обозначение на самия оператор.
  • Вградените оператори се дефинират по такъв начин, че между тях има връзка. Например, следните оператори са еквивалентни един на друг: ++ x; х + = 1; x = x + 1. След предефиниране връзката между тях няма да се запази. За да запазят своята работа заедно по подобен начин с новите типове програмисти ще трябва да се грижат за себе си.
  • Компилаторът не може да мисли. Изрази z + 5 и 5 + z (където z - комплексно число) ще бъдат разглеждани от компилатора по различни начини. Първият е "комплекс + номер", а вторият е "брой + комплекс". Следователно за всеки израз трябва да определите собствено изявление за добавяне.
  • При търсене на дефиниция на оператор, компилаторът не дава предпочитание на никакви функционални членове на класа, нито на помощни функции,които са определени извън клас. За компилатора те са еднакви.

Интерпретации на двоични и единни оператори.

Двоичен оператор се дефинира като функция на член с една променлива или като функция с две променливи. За всеки бинарен оператор @ изразът a @ b @ е валиден конструкт:


& lt; script type = "text /javascript" & gt;
може да blockSettings3 = {blockId: "R-A-70350-3", renderTo: "yandex_rtb_R-A-70350-3", async:! 0};
blockSettings3 = {blockId: "RA-70350-3", renderTo: "yandex_rtb_R-A-70350-" 3 ", statId: 70350async: 0};
}

Функция (a, b, c, d, e) {a [c] = a [c] || [], a [c] .push (функция () {Ya .Context.AdvManager.render (blockSettings3)}), e = b.getElementsByTagName ("скрипт") , d = b.createElement ("скрипт"), d.type = "text /javascript", d.src = "//an.yandex.ru/system/context.js", d.async =! 0e.parentNode.insertBefore (d, e)} (това, този.документ, "yandexContextAsyncCallbacks");

а.оператор @ (б) или оператор @ (а, б).

Да вземем за пример един клас от комплексни числа за дефиниране на операции като членове на клас и помощни.

класов комплекс {{174} двойно ре, im;
публичен:
комплексен; оператор + = (комплекс z);
комплекс & на; оператор * = (комплекс z);
};
//спомагателни функции
сложен оператор + (комплекс z1 комплекс z2);
сложен оператор + (комплекс z, double a);

Кой от операторите ще бъде избран и дали изобщо ще бъде избран, се определя от вътрешните механизми на езика, които ще бъдат разгледани по-долу. Обикновено това се случва в съответствие с видовете.

Избор, за да се опише функция като член на клас или извън нея - правилно, общо взето, вкус. В примера по-горе, принципът на подбор е следният: ако операцията промени левия операнд (например a + = b), напишете го вътре в класа и използвайте прехвърлянето на променливата на адреса за неговата пряка промяна; ако операцията не е нищомодифицира и просто връща нова стойност (например a + b) - извън дефиницията на класа.

Определението за претоварване на унарните оператори в C ++ възниква по същия начин, с тази разлика, че те са разделени на два вида:

  • префикс оператор, разположен към операнда, - @, например, i ++. o Дефинирано като a.operator @ () или оператор @ (aa);
  • постфикс оператор, разположен след операнда, - b @, например, i ++. o Определени като b.operator @ (int) или оператор @ (b, int)

По същия начин, както при бинарните оператори, в случая, когато изявлението на оператора е както в класа, така и извън него, изборът ще бъде направен от механизмите на C ++.

Правила за избор на оператор

Нека двоичният оператор @ се прилага към обекти x от клас X и y от клас Y. Правилата за разделителната способност x @ y ще бъдат както следва:

  1. ако X е клас, потърсете в него дефиницията на оператора @ като термин X или базовия клас X;
  2. да се види контекстът, в който е разположен изразът x @ y;
  3. ако X принадлежи към пространството от имена N, потърсете оператора N;
  4. Ако Y принадлежи към пространството на имената M, потърсете изявлението на оператора M.

Ако няколко оператора на оператор @ са намерени в 1-4, изборът ще бъде направен съгласно правилата за разрешение на претоварените функции.

Търсенето на единични оператори се извършва по същия начин.

Уточнете дефиницията на класовия комплекс

Сега ще конструираме клас от комплексни числа по по-детайлен начинда покаже редица предварително обявени правила.

клас комплекс {
double re, im;
публични:
комплексни; operator + = (complex z) {//работи с изрази на формата z1 + = z2
re + = z.re;
im + = z.im;
връщане * това;
}
комплексен; оператор + = (double a) {//работи с изрази от вида z1 + = 5;
re + = a;
връщане * това;
}
complex (): re

, im

{} //конструктор за инициализация по подразбиране. По този начин всички декларирани цели числа ще имат начални стойности (0 0)
комплекс (двойни r): re (r), im

{} //конструкторът прави възможно изразът на формата комплекс z = 11; еквивалент на запис z = комплекс
;
комплекс (двойно r, двойно i): re (r), im (i) {} //конструктор
};
сложен оператор + (сложен z1 комплекс z2) {//работи с изрази на вида z1 + z2
комплекс res = z1;
връщане res + = z2; //използваме оператор, дефиниран като член функция
}
сложен оператор + (комплекс z, double a) {//обработва изрази на формата z + 2
комплекс res = z;
връщане res + = a;
}
сложен оператор + (double a, complex z) {//обработва изразите на формата 7 + z
комплекс res = z;
връщане res + = a;
}
//


Както може да се види от кода, претоварването на операторите има доста сложен механизъм, който може значително да нарасне. Въпреки това, такъв подробен подход ви позволява да претоварвате дори при много сложни структури от данни. Например, претоварване на операторите на C ++ в шаблонен клас. Такова създаване на функции за всички и всичко може да бъде досадно и да доведе до грешки. Например, ако добавите трети тип разгледани функции, тогава ще трябва да обмислите операциите поради комбинация от три типа. Ще трябва да напишем 3 функции с един аргумент, 9 - с две и 27 - с три. Ето защо, в някои случаи, изпълнението на всички тези функции и значително намаляване на тяхколичествата могат да бъдат постигнати чрез конвертиране на типове.

Специални оператори

Индексиращият оператор "[]" винаги трябва да се дефинира като член на класа, тъй като намалява поведението на даден обект към масив. В този случай аргументът за индексиране може да бъде от всякакъв тип, което позволява, например, да се създават асоциативни масиви. Операторът на повикването на функцията (() може да се разглежда като двоична операция). Например, в конструкцията "израз (списък на изразите)" левият операнд на двоичната операция () ще бъде "израз", а десният е списък с изрази. Функцията operator () () трябва да е член на класа. Оператор на последователност "," (запетая) се извиква за обекти, ако до тях има запетая. Операторът обаче не участва в прехвърлянето на аргументите на функцията. Операторът на имена "- & gt;" също трябва да бъде дефиниран като член на функцията. В своето съдържание тя може да бъде дефинирана като унитарен постфикс оператор. В същото време той задължително трябва да върне или връзка или указател, който позволява достъп до обекта. Операторът на присвояване също се дефинира само като член на класа поради неговата връзка с левия операнд. Операторите на прехвърлянето «=», адресите «&» и последователността «,» трябва да бъдат дефинирани в публичния блок.

Резултат

Операторското претоварване помага да се реализира един от ключовите аспекти на ООП при полиморфизма. Но е важно да се разбере, че претоварването не е нищо повече от друг начин за извикване на функции. Задачата на претоварващите оператори често е да се подобри разбирането на кода, отколкото да се гарантира спечелването на всякакви въпроси.
И това не е всичко. Също така трябва да се има предвид, че претоварването на операторите е сложен механизъм с множество капани. Ето защо е много лесно да се направи грешка. Това е основната причина, поради която повечето програмисти съветват да се въздържат от претоварване на оператора и да прибягват до него само в крайни случаи и с пълно доверие в техните действия.

Препоръки

  • Изпълнете претоварване на оператора само за да симулирате обикновен запис. За да направите код за четене. Ако кодът стане по-сложен от гледна точка на структура или четливост, трябва да се откажете от претоварването на операторите и да използвате функциите.
  • За големи операнди, за да се спести място, използвайте аргументи за въвеждане с постоянни препратки за предаване.
  • Оптимизиране на връщаните стойности.
  • Не докосвайте операцията за копиране, ако е подходяща за вашия клас.
  • Ако копието по подразбиране не е подходящо, променете или изрично забранете копирането.
  • Функциите за членство следва да бъдат предпочитани пред функциите, които не са членове, в случаите, когато функциите изискват достъп до представяне на клас.
  • Посочете пространството от имена и посочете връзката между функциите на класа.
  • Използвайте функции, които не са членове, за симетрични оператори.
  • Използвайте оператора () за индекси в многомерни масиви.
  • Използвайте предпазливост при имплицитни трансформации.
  • Свързани публикации