sonyps4.ru

Перегрузка операторов c примеры с массивами. Перегрузка операций

Последнее обновление: 20.10.2017

Перегрузка операторов позволяет определить действия, которые будет выполнять оператор. Перегрузка подразумевает создание функции, название которой содержит слово operator и символ перегружаемого оператора. Функция оператора может быть определена как член класса, либо вне класса.

Перегрузить можно только те операторы, которые уже определены в C++. Создать новые операторы нельзя.

Если функция оператора определена как отдельная функция и не является членом класса, то количество параметров такой функции совпадает с количеством операндов оператора. Например, у функции, которая представляет унарный оператор, будет один параметр, а у функции, которая представляет бинарный оператор, - два параметра. Если оператор принимает два операнда, то первый операнд передается первому параметру функции, а второй операнд - второму параметру. При этом как минимум один из параметров должен представлять тип класса

Рассмотрим пример с классом Counter, который представляет секундомер и хранит количество секунд:

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

Здесь функция оператора не является частью класса Counter и определена вне его. Данная функция перегружает оператор сложения для типа Counter. Она является бинарной, поэтому принимает два параметра. В данном случае мы складываем два объекта Counter. Возвращает функция также объект Counter, который хранит общее количесто секунд. То есть по сути здесь операция сложения сводится к сложению секунд обоих объектов:

Counter operator + (Counter c1, Counter c2) { return Counter(c1.seconds + c2.seconds); }

При этом необязательно возвращать объект класса. Это может быть и объект встроенного примитивного типа. И также мы можем определять дополнительные перегруженные функции операторов:

Int operator + (Counter c1, int s) { return c1.seconds + s; }

Данная версия складывает объект Counter с числом и возвращает также число. Поэтому левый операнд операции должен представлять тип Counter, а правый операнд - тип int. И, к примеру, мы можем применить данную версию оператора следующим образом:

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

Также функции операторов могут быть определены как члены классов. Если функция оператора определена как член класса, то левый операнд доступен через указатель this и представляет текущий объект, а правый операнд передается в подобную функцию в качестве единственного параметра:

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

В данном случае к левому операнду в функциях операторов мы обращаемся через указатель this.

Какие операторы где переопределять? Операторы присвоения, индексирования (), вызова (()), доступа к члену класса по указателю (->) следует определять в виде функций-членов класса. Операторы, которые изменяют состояние объекта или непосредственно связаны с объектом (инкремент, декремент,), обычно также определяются в виде функций-членов класса. Все остальные операторы чаще определяются как отдельные функции, а не члены класса.

Операторы сравнения

Ряд операторов перегружаются парами. Например, если мы определяем оператор == , то необходимо также определить и оператор != . А при определении оператора < надо также определять функцию для оператора > . Например, перегрузим данные операторы:

Bool operator == (Counter c1, Counter c2) { return c1.seconds == c2.seconds; } bool operator != (Counter c1, Counter c2) { return c1.seconds != c2.seconds; } bool operator > (Counter c1, Counter c2) { return 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; }

Операторы присвоения

#include 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:

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

Counter& operator++ () { seconds += 5; return *this; }

В самой функции можно определить некоторую логику по инкременту значения. В данном случае количество секунд увеличивается на 5.

Постфиксные операторы должны возвращать значение объекта до инкремента, то есть предыдущее состояние объекта. Чтобы постфиксная форма отличалась от префиксной постфиксные версии получают дополнительный параметр типа int, который не используется. Хотя в принципе мы можем его использовать.

Counter operator++ (int) { Counter prev = *this; ++*this; return prev; }

Доброго времени суток!

Желание написать данную статью появилось после прочтения поста Перегрузка C++ операторов , потому что в нём не были раскрыты многие важные темы.

Самое главное, что необходимо помнить - перегрузка операторов, это всего лишь более удобный способ вызова функций, поэтому не стоит увлекаться перегрузкой операторов. Использовать её следует только тогда, когда это упростит написание кода. Но, не настолько, чтобы это затрудняло чтение. Ведь, как известно, код читается намного чаще, чем пишется. И не забывайте, что вам никогда не дадут перегрузить операторы в тандеме со встроенными типами, возможность перегрузки есть только для пользовательских типов/классов.

Синтаксис перегрузки

Синтаксис перегрузки операторов очень похож на определение функции с именем operator@, где @ - это идентификатор оператора (например +, -, <<, >>). Рассмотрим простейший пример:
class Integer { private: int value; public: Integer(int i): value(i) {} const Integer operator+(const Integer& rv) const { return (value + rv.value); } };
В данном случае, оператор оформлен как член класса, аргумент определяет значение, находящееся в правой части оператора. Вообще, существует два основных способа перегрузки операторов: глобальные функции, дружественные для класса, или подставляемые функции самого класса. Какой способ, для какого оператора лучше, рассмотрим в конце топика.

В большинстве случаев, операторы (кроме условных) возвращают объект, или ссылку на тип, к которому относятся его аргументы (если типы разные, то вы сами решаете как интерпретировать результат вычисления оператора).

Перегрузка унарных операторов

Рассмотрим примеры перегрузки унарных операторов для определенного выше класса Integer. Заодно определим их в виде дружественных функций и рассмотрим операторы декремента и инкремента:
class Integer { private: int value; public: Integer(int i): value(i) {} //унарный + friend const Integer& operator+(const Integer& i); //унарный - friend const Integer operator-(const Integer& i); //префиксный инкремент friend const Integer& operator++(Integer& i); //постфиксный инкремент friend const Integer operator++(Integer& i, int); //префиксный декремент friend const Integer& operator--(Integer& i); //постфиксный декремент friend const Integer operator--(Integer& i, int); }; //унарный плюс ничего не делает. const Integer& operator+(const Integer& i) { return i.value; } const Integer operator-(const Integer& i) { return Integer(-i.value); } //префиксная версия возвращает значение после инкремента const Integer& operator++(Integer& i) { i.value++; return i; } //постфиксная версия возвращает значение до инкремента const Integer operator++(Integer& i, int) { Integer oldValue(i.value); i.value++; return oldValue; } //префиксная версия возвращает значение после декремента const Integer& operator--(Integer& i) { i.value--; return i; } //постфиксная версия возвращает значение до декремента const Integer operator--(Integer& i, int) { Integer oldValue(i.value); i.value--; return oldValue; }
Теперь вы знаете, как компилятор различает префиксные и постфиксные версии декремента и инкремента. В случае, когда он видит выражение ++i, то вызывается функция operator++(a). Если же он видит i++, то вызывается operator++(a, int). То есть вызывается перегруженная функция operator++, и именно для этого используется фиктивный параметр int в постфиксной версии.

Бинарные операторы

Рассмотрим синтаксис перегрузки бинарных операторов. Перегрузим один оператор, который возвращает l-значение, один условный оператор и один оператор, создающий новое значение (определим их глобально):
class Integer { private: int value; public: Integer(int i): value(i) {} friend const Integer operator+(const Integer& left, const Integer& right); friend Integer& operator+=(Integer& left, const Integer& right); friend bool operator==(const Integer& left, const Integer& right); }; const Integer operator+(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; }
Во всех этих примерах операторы перегружаются для одного типа, однако, это необязательно. Можно, к примеру, перегрузить сложение нашего типа Integer и определенного по его подобию Float.

Аргументы и возвращаемые значения

Как можно было заметить, в примерах используются различные способы передачи аргументов в функции и возвращения значений операторов.
  • Если аргумент не изменяется оператором, в случае, например унарного плюса, его нужно передавать как ссылку на константу. Вообще, это справедливо для почти всех арифметических операторов (сложение, вычитание, умножение...)
  • Тип возвращаемого значения зависит от сути оператора. Если оператор должен возвращать новое значение, то необходимо создавать новый объект (как в случае бинарного плюса). Если вы хотите запретить изменение объекта как l-value, то нужно возвращать его константным.
  • Для операторов присваивания необходимо возвращать ссылку на измененный элемент. Также, если вы хотите использовать оператор присваивания в конструкциях вида (x=y).f(), где функция f() вызывается для для переменной x, после присваивания ей y, то не возвращайте ссылку на константу, возвращайте просто ссылку.
  • Логические операторы должны возвращать в худшем случае int, а в лучшем bool.

Оптимизация возвращаемого значения

При создании новых объектов и возвращении их из функции следует использовать запись как для вышеописанного примера оператора бинарного плюса.
return Integer(left.value + right.value);
Честно говоря, не знаю, какая ситуация актуальна для C++11, все рассуждения далее справедливы для C++98.
На первый взгляд, это похоже на синтаксис создания временного объекта, то есть как будто бы нет разницы между кодом выше и этим:
Integer temp(left.value + right.value); return temp;
Но на самом деле, в этом случае произойдет вызов конструктора в первой строке, далее вызов конструктора копирования, который скопирует объект, а далее, при раскрутке стека вызовется деструктор. При использовании первой записи компилятор изначально создаёт объект в памяти, в которую нужно его скопировать, таким образом экономится вызов конструктора копирования и деструктора.

Особые операторы

В C++ есть операторы, обладающие специфическим синтаксисом и способом перегрузки. Например оператор индексирования . Он всегда определяется как член класса и, так как подразумевается поведение индексируемого объекта как массива, то ему следует возвращать ссылку.
Оператор запятая
В число «особых» операторов входит также оператор запятая. Он вызывается для объектов, рядом с которыми поставлена запятая (но он не вызывается в списках аргументов функций). Придумать осмысленный пример использования этого оператора не так-то просто. Хабраюзер AxisPod в комментариях к предыдущей статье о перегрузке рассказал об одном .
Оператор разыменования указателя
Перегрузка этих операторов может быть оправдана для классов умных указателей. Этот оператор обязательно определяется как функция класса, причём на него накладываются некоторые ограничения: он должен возвращать либо объект (или ссылку), либо указатель, позволяющий обратиться к объекту.
Оператор присваивания
Оператор присваивания обязательно определяется в виде функции класса, потому что он неразрывно связан с объектом, находящимся слева от "=". Определение оператора присваивания в глобальном виде сделало бы возможным переопределение стандартного поведения оператора "=". Пример:
class 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++ не перегружаются в принципе. По всей видимости, это сделано из соображений безопасности.
  • Оператор выбора члена класса ".".
  • Оператор разыменования указателя на член класса ".*"
  • В С++ отсутствует оператор возведения в степень (как в Fortran) "**".
  • Запрещено определять свои операторы (возможны проблемы с определением приоритетов).
  • Нельзя изменять приоритеты операторов
Как мы уже выяснили, существует два способа операторов - в виде функции класса и в виде дружественной глобальной функции.
Роб Мюррей, в своей книге C++ Strategies and Tactics определил следующие рекомендации по выбору формы оператора:

Почему так? Во-первых, на некоторые операторы изначально наложено ограничение. Вообще, если семантически нет разницы как определять оператор, то лучше его оформить в виде функции класса, чтобы подчеркнуть связь, плюс помимо этого функция будет подставляемой (inline). К тому же, иногда может возникнуть потребность в том, чтобы представить левосторонний операнд объектом другого класса. Наверное, самый яркий пример - переопределение << и >> для потоков ввода/вывода.

Литература

Брюс Эккель - Философия C++. Введение в стандартный C++ .

Теги: Добавить метки

Сегодня мы познакомимся с замечательной возможностью нашего любомого языка C++ - перегрузкой операций. Но сначала разберёмся, для чего это нужно.

С базовыми типами вы можете использовать любые операции: +, -, *, ++, = и многие другие. Например:

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

Здесь над переменными типа int сначала выполняется операция +, а затем результат присваивается переменной c с помощью операции =. Над классами такое проделать не получится. Создадим простой класс:

код на языке c++ class Counter { public: int counter; Counter() : c(0) {} }; Counter a,b,c; a.counter = 2; b.counter = 3; c = a + b;

Здесь компилятор выдаст ошибку на последней строке: он не знает как именно нужно действовать при использовании операций = и + над объектами класса Counter. Можно решить данную проблему вот так:

код на языке c++ class Counter { public: int counter; Counter() : count(0) {} void AddCounters (Counter& a, Counter& b) { counter = a.counter + b.counter; } }; Counter a,b,c; a.counter = 2; b.counter = 3; c.AddCounters(a,b);

Согласитесь, использование операции + и = в данном случае сделало бы программу более понятной. Так вот, чтобы использовать стандартные операции C++ с классами, эти операции нужно перегрузить (overload).

Перегрузка унарных операций

Начнём с простых операций - унарных. Прежде всего нас интересует инкремент. При использовании данной операции над базовыми типами, к переменной можно прибавить или отнять единицу:

код на языке c++ int a=1; ++a; // a = 2; --a; // a = 1;

Теперь научим класс Counter использовать преинкремент (предекремент):

код на языке c++ class Counter { private: counter; public: Counter() : counter(0) {} void operator++ () { counter += 1; // Можно также counter++ или ++counter - // это не имеет значения в данном случае. } }; Counter a; ++a; ++a; ++a;

Этот код работает. При использовании операции ++ (важно чтобы этот знак находился до идентификатора!) над объектом класса Counter, происходит увеличение переменной counter объекта a.

В данном примере мы перегрузили операцию ++. Делается это созданием метода внутри класса. Единственной важной особенностью данного метода - имя идентификатора. Имя идентификатора для перегруженных операций состоит из ключевого слова operator и названия операции. Во всём остальном этот метод определяется как и любые другие.

Использовать перегруженные операции с пользовательскими типами очень просто - так же как и с обычными типами данных.

Перегрузка постфиксных операций

Примеры постфиксной операции:

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

То есть, здесь знак операции ставится после имени идентификатора. Для использования постфиксных операций с пользовательскими типами данных нужно совсем немного:

код на языке c++ public: void operator++ () { counter += 1; } void operator++ (int) { counter += 1; }

Единственным отличием префиксной операции от постфиксной - ключевое слово int в списке аргументов. Но int - это не аргумент! Это слово говорит, что перегружается постфиксная операция. Теперь операцию ++ можно использовать как перед идентификатором объекта, так и после:

Counter a;
++a;
++a;
a++;
a++;

Перегрузка бинарных операций

Перегрузка операций с двумя аргументами очень похожа на перегрузку бинарных операций:

код на языке c++ Counter operator+ (Counter t) { Counter summ; summ.counter = counter + t.counter; return summ; } Counter c1,c2,c3; c1.counter = 3; c2.counter = 2; c3 = c1 + c2;

Какая переменная вызовет функцию operator+? В перегруженных бинарных операциях всегда вызывается метод левого операнда. В данном случае метод operator+ вызывает объект c1.

В метод мы передаём аргумент. Аргументом всегда является правый операнд.

Кроме того, в данном случае операция + должна вернуть какой-то результат, чтобы присвоить его объекту c3. Мы возвращаем объект Counter. Возвращаемое значение присваивается переменной c3.

Заметьте, мы перегрузили операцию +, но не перегружали операцию =! Конечно же нужно добавить этот метод к классу Counter:

код на языке c++ Counter operator= (Counter t) { Counter assign; counter = t.counter; assign.counter = t.counter; return assign; }

Внутри метода мы создали дополнительную переменную assign. Данная переменная используется, чтобы можно было работать вот с таким кодом:

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

Хотя, для возвращаемого значения можно воспользоваться и более элегантными способами, с которыми мы вскоре познакомимся.

Как известно, в языке С# тип переменной определяет набор значений, которые она может хранить, а также набор операций, которые можно выполнять над этой переменной. Например, над значением переменной типа int программа может выполнять сложение, вычитание, умножение и деление. С другой стороны, использование оператора “плюс” для сложения двух экземпляров реализованного программистом класса лишено смысла.

Когда в программе определяется класс, то по существу определяется новый тип данных. Тогда язык C# позволяет определить операции, соответствующие этому новому типу данных.

Перегрузка операций состоит в изменении смысла операции при использовании его с определенным классом.

Например, пусть имеется:

myclass a,bc;…//a,b,c-экземпляры класса myclass

c=a+b; //перегруженная операция сложения для класса myclass

Перегрузка операций обычно применяется для классов, описывающих математические или физические понятия, то есть таких классов, для которых требуется выполнить соответствующие операции.

Общий синтаксис объявления перегруженной операции:

[атрибуты] спецификаторы operator тело операции,

Спецификаторы – public,static,extern

operator – ключевое слово, определяющее перегруженную операцию

тело операции-действия, которые выполняются при использовании операции в выражении

Перегружать можно только стандартные операции.

Алгоритм перегрузки операции :

    Определить класс, которому данная операция будет назначена.

    Для перегрузки операций используется ключевое слово operator .

    Переопределяя операцию, необходимо указать метод, который C# вызывает каждый раз, когда класс использует перегруженную операцию. Этот метод, в свою очередь, выполняет соответствующую операцию.

Правила перегрузки операции :

    Операция должна быть объявлена как public static

    Параметры в операцию должны передаваться по значению (не ref, не out)

    Двух одинаковых перегруженных операций в классе не должно быть

Если программа перегружает операцию для определенного класса, то смысл этой операции изменяется только для указанного класса , оставшаяся часть программы будет продолжать использовать эту операцию для выполнения ее стандартных действий.

Перегрузка унарных операций

К унарным операциям, которые можно перегружать в языке С# относятся:

    унарные + и –

    логическое!,

    true, false – обычно перегружаются для типов SQL

Синтаксис объявления перегруженной унарной операции:

public static тип_возвр_знач operator унарная_операция (один параметр),

где параметр – это класс, для которого перегружается данная операция

Например,

public static myclass operator ++(myclass x)

public static int operator +(myclass x)

public static bool operator true(myclass x)

Перегруженная операция возвращает:

    унарные + и –, ! величину любого типа

    Величину типа класса

    true, false – величину типа bool

Префиксные и постфиксные ++ и – не различаются при перегрузке.

Пример перегрузки унарных операций на примере класса

Одномерный массив

public MyArray(int size)

a=new int;

public MyArray(params int mas)

length =mas.length;

a=new int;

for (int i=0;i

public static MyArray operator ++(MyArray x) // перегрузка унарного оператора ++

MyArray temp=new MyArray(x.length);

for (int i=0;i

temp[i]=++x.a[i]; //попробуйте temp.a[i]=++x.a[i]

//индексатор, в случае выхода за рамки массива – генерируется исключение!

public int this

get {if (i>=0 && i

set { if (i>=0 && i

public void Print(string name)

Console.WriteLine(name+”:”);

for (int i=0;i

Console.WriteLine(“\t”+a[i]);

Console.WriteLine();

public void Enter()

//в цикле - ввод элементов массива – реализуйте сами!

//данные класса – сам массив и его размерность

MyArray a1=new MyArray(5,2,-1,1,-2);

a1.Print(“Первый массив ”);

a 1++; //теперь к экземпляру класса можно применить операцию ++

a1.Print(“Использование операции ++ для всех элементов массива ”);

MyArray a2=new MyArray(5);

a2.Print(“Второй массив ”);

a 2++;

a2.Print(“Использование операции ++ для всех элементов массива”);

catch (Exception e)

{Console.WriteLine(e.Message);}

Последнее обновление: 12.08.2018

Наряду с методами мы можем также перегружать операторы. Например, пусть у нас есть следующий класс Counter:

Class Counter { public int Value { get; set; } }

Данный класс представляет некоторый счетчик, значение которого хранится в свойстве Value.

И допустим, у нас есть два объекта класса Counter - два счетчика, которые мы хотим сравнивать или складывать на основании их свойства Value, используя стандартные операции сравнения и сложения:

Counter c1 = new Counter { Value = 23 }; Counter c2 = new Counter { Value = 45 }; bool result = c1 > c2; Counter c3 = c1 + c2;

Но на данный момент ни операция сравнения, ни операция сложения для объектов Counter не доступны. Эти операции могут использоваться для ряда примитивных типов. Например, по умолчанию мы можем складывать числовые значения, но как складывать объекты комплексных типов - классов и структур компилятор не знает. И для этого нам надо выполнить перегрузку нужных нам операторов.

Перегрузка операторов заключается в определении в классе, для объектов которого мы хотим определить оператор, специального метода:

Public static возвращаемый_тип operator оператор(параметры) { }

Этот метод должен иметь модификаторы public static , так как перегружаемый оператор будет использоваться для всех объектов данного класса. Далее идет название возвращаемого типа. Возвращаемый тип представляет тот тип, объекты которого мы хотим получить. К примеру, в результате сложения двух объектов Counter мы ожидаем получить новый объект Counter. А в результате сравнения двух мы хотим получить объект типа bool, который указывает истинно ли условное выражение или ложно. Но в зависимости от задачи возвращаемые типы могут быть любыми.

Затем вместо названия метода идет ключевое слово operator и собственно сам оператор. И далее в скобках перечисляются параметры. Бинарные операторы принимают два параметра, унарные - один параметр. И в любом случае один из параметров должен представлять тот тип - класс или структуру, в котором определяется оператор.

Например, перегрузим ряд операторов для класса Counter:

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

Поскольку все перегруженные операторы - бинарные - то есть проводятся над двумя объектами, то для каждой перегрузки предусмотрено по два параметра.

Так как в случае с операцией сложения мы хотим сложить два объекта класса Counter, то оператор принимает два объекта этого класса. И так как мы хотим в результате сложения получить новый объект Counter, то данный класс также используется в качестве возвращаемого типа. Все действия этого оператора сводятся к созданию, нового объекта, свойство Value которого объединяет значения свойства Value обоих параметров:

Public static Counter operator +(Counter c1, Counter c2) { return new Counter { Value = c1.Value + c2.Value }; }

Также переопределены две операции сравнения. Если мы переопределяем одну из этих операций сравнения, то мы также должны переопределить вторую из этих операций. Сами операторы сравнения сравнивают значения свойств Value и в зависимости от результата сравнения возвращают либо true, либо false.

Теперь используем перегруженные операторы в программе:

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(); }

Стоит отметить, что так как по сути определение оператора представляет собой метод, то этот метод мы также можем перегрузить, то есть создать для него еще одну версию. Например, добавим в класс Counter еще один оператор:

Public static int operator +(Counter c1, int val) { return c1.Value + val; }

Данный метод складывает значение свойства Value и некоторое число, возвращая их сумму. И также мы можем применить этот оператор:

Counter c1 = new Counter { Value = 23 }; int d = c1 + 27; // 50 Console.WriteLine(d);

Следует учитывать, что при перегрузке не должны изменяться те объекты, которые передаются в оператор через параметры. Например, мы можем определить для класса Counter оператор инкремента:

Public static Counter operator ++(Counter c1) { c1.Value += 10; return c1; }

Поскольку оператор унарный, он принимает только один параметр - объект того класса, в котором данный оператор определен. Но это неправильное определение инкремента, так как оператор не должен менять значения своих параметров.

И более корректная перегрузка оператора инкремента будет выглядеть так:

Public static Counter operator ++(Counter c1) { return new Counter { Value = c1.Value + 10 }; }

То есть возвращается новый объект, который содержит в свойстве Value инкрементированное значение.

При этом нам не надо определять отдельно операторы для префиксного и для постфиксного инкремента (а также декремента), так как одна реализация будет работать в обоих случаях.

Например, используем операцию префиксного инкремента:

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

Консольный вывод:

Теперь используем постфиксный инкремент:

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

Консольный вывод:

Также стоит отметить, что мы можем переопределить операторы true и false . Например, определим их в классе Counter:

Class Counter { public int Value { get; set; } public static bool operator true(Counter c1) { return c1.Value != 0; } public static bool operator false(Counter c1) { return c1.Value == 0; } // остальное содержимое класса }

Эти операторы перегружаются, когда мы хотим использовать объект типа в качестве условия. Например:

Counter counter = new Counter() { Value = 0 }; if (counter) Console.WriteLine(true); else Console.WriteLine(false);

При перегрузке операторов надо учитывать, что не все операторы можно перегрузить. В частности, мы можем перегрузить следующие операторы:

    унарные операторы +, -, !, ~, ++, --

    бинарные операторы +, -, *, /, %

    операции сравнения ==, !=, <, >, <=, >=

    логические операторы &&, ||

    операторы присваивания +=, -=, *=, /=, %=

И есть ряд операторов, которые нельзя перегрузить, например, операцию равенства = или тернарный оператор?: , а также ряд других.

Полный список перегружаемых операторов можно найти в документации msdn

При перегрузке операторов также следует помнить, что мы не можем изменить приоритет оператора или его ассоциативность, мы не можем создать новый оператор или изменить логику операторов в типах, который есть по умолчанию в.NET.



Загрузка...