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

Помощь 21.05.2019

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

String s1 = "Иванов" ; string s2 = " Сергей" ; string s3 = s1 + s2; // s3 теперь содержит "Иванов Сергей"

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

Терминологическое примечание . Часто в русскоязычных текстах смешивают понятия «операция» и «оператор». Оператор — это наименьшая автономная часть языка программирования, команда или набор команд (составной оператор). Опера́ция - конструкция языка, аналогичная по записи математическим операциям, то есть специальный способ записи некоторых действий. Путаница связана еще и с тем, что в С и C++ присваивание и инкремент/декремент являются и операторами, и операциями. Чаще всего, операция является частью оператора. Поэтому далее будем употреблять термин «перегрузка операции».

Язык Си шарп предоставляет возможность строить специальные классы, которые также уникально реагируют на один и тот же набор базовых лексем (вроде операции +). Необходимо знать, что абсолютно каждую встроенную операцию C# перегружать нельзя. В таблице описаны возможности перегрузки основных операций:

Операция C# Возможность перегрузки
+, -, !, ++, —, true, false Этот набор унарных операций может быть перегружен
+, -, *, /, %, &, |, ^, <<, >> Эти бинарные операции могут быть перегружены
==, !=, <, >, <=, >= Эти операции сравнения могут быть перегружены. C# требует совместной перегрузки «подобных» операций (т.е. < и >, <= и >=, == и!=)
Операция не может быть перегружена. Аналогичную функциональность предлагают индексаторы
() Операция () не может быть перегружена, но ту же функциональность предоставляют специальные методы преобразования
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= Сокращенные операции присваивания не могут перегружаться; однако вы получаете их автоматически, перегружая соответствующую бинарную операцию

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

// Форма перегрузки унарной операции public static возвращаемый_тип operator op (тип_параметра операнд) { // операторы } // Форма перегрузки бинарной операции public static возвращаемый_тип operator op (тип_параметра1 операнд1, тип_параметра2 операнд2) { // операторы }

Вместо op подставляется перегружаемая операция, например + или / , а возвращаемый_тип обозначает конкретный тип значения, возвращаемого указанной операцией. Это значение может быть любого типа, но зачастую оно указывается такого же типа, как и у класса, для которого перегружается операция. Такая корреляция упрощает применение перегружаемых операций в выражениях. Для унарных операций операнд обозначает передаваемый операнд, а для бинарных операций то же самое обозначают операнд1 и операнд2 . Заметим, что операторные методы должны иметь оба спецификатора типа — public и static.

Пример перегрузки бинарной операции:

Using System; namespace ПерегрузкаОпераций { class Vector { // Координаты точки в трехмерном пространстве public int x, y, z; // конструктор public Vector(int x = 0, int y = 0, int z = 0) { this.x = x; this.y = y; this.z = z; } // Перегружаем бинарную операцию + (сложение векторов) public static Vector operator + (Vector v1, Vector v2) { Vector v = new Vector(); v.x = v1.x + v2.x; v.y = v1.y + v2.y; v.z = v1.z + v2.z; return v; } // Перегружаем бинарную операцию - (разность векторов) public static Vector operator - (Vector v1, Vector v2) { Vector v = new Vector(); v.x = v1.x - v2.x; v.y = v1.y - v2.y; v.z = v1.z - v2.z; return v; } public void printV(string s) { Console.WriteLine(s + x + " " + y + " " + z); } } class Program { static void Main(string args) { Vector V1 = new Vector(3, 4, 5); Vector V2 = new Vector(-3, -4, 5); V1.printV("Координаты первого вектора: "); V2.printV("Координаты второго вектора: "); Vector V3 = V1 + V2; // ПЕРЕГРУЗКА! V3.printV("Координаты суммы векторов: "); V3 = V1 - V2; // ПЕРЕГРУЗКА! V3.printV("Координаты разности векторов: "); Console.ReadLine(); } } }

Результат:Здесь выполняются операции сложения и разности двух векторов (выделено красным цветом).

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

ГЛАВА 13. Перегрузка операторов и нестандартные преобразования

Из главы 7 вы узнали о применении оператора для индексации объектов, как если бы они были массивами. В этой главе мы рассмотрим две тесно связанные функции С# для создания интерфейсов структур и классов, упрощающих их понимание и применение: перегрузку операторов (operator overloading) и нестандартные, определенные пользователем преобразования. Я начну с общего обзора перегрузки операторов, чтобы были ясны ее преимущества, затем рассмотрю конкретный синтаксис переопределения поведения операторов по умолчанию, а также реальный пример приложения, в котором оператор + перегружается для объединения нескольких объектов Invoice. Потом вы увидите листинг с перегружаемыми бинарными и унарными операторами и применением некоторых ограничений. Обсуждение перегрузки операторов завершится рекомендациями по принятию решения о перегрузке операторов в ваших классах. Закончив с перегрузкой операторов, вы изучите новую концепцию - нестандартные преобразования. Я опять же начну с основ этой возможности, а затем углублюсь в описание класса, показав, как применять преобразования для структур и классов к другим структурам и классам или базисным типам С#.

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

Перегрузка операторов позволяет переопределить операторы С# для применения их к типам, определенным пользователем. Перегрузку oпeраторов назвали "синтаксическим сахаром" ("syntactic sugar""), имея в виду, что это лишь другой способ вызова метода. Это также должно говорить о том, что эта возможность ничего фундаментального в язык не привносит. Хотя с формальной точки зрения это и так, перегрузка операторов связана с одним из важнейших аспектов ООП - абстракцией. Допустим, вы хотите просуммировать счета для конкретного заказчика. Применив перегрузку операторов, вы можете написать код, в котором оператор += перегружен:

Invoice summarylnvoice = new InvoiceO;

foreach (Invoice invoice in customer.GetlnvoicesQ)

summarylnvoice += invoice; >

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

Синтаксис и пример

Итак, перегрузка оператора - это разновидность вызова метода. Для переопределения оператора применяется такой шаблон (здесь on - перегружаемый оператор):

public static возвращаемое_значение operator"/? (объект! [, объект2\) При перегрузке операторов учитывайте следующие факты.

  • Все методы, представляющие перегружаемые операторы, должны быть определены как public и static.
  • Формально возвращаемое^значение может быть любого типа. Однако общая практика - возвращать тип, для которого определяется метод (кроме операторов true и false - они должны всегда возвращать булево значение).
  • Число передаваемых аргументов (объект!, объект!) зависит от типа перегружаемого оператора. Для унарных (т. е. с одним операндом) операторов должен указываться один аргумент. Для перегрузки бинарного (т. е. с двумя операндами) оператора передаются два аргумента.
  • В случае унарных операторов аргумент этого метода должен быть того же типа, что и включенный в него класс или структура. Иначе говоря, если вы переопределяете унарный оператор "!" для класса Foo,

1 В английском языке по звучанию напоминает "синтетический сахар". - Прим. перев.

этот метод в качестве аргумента должен принимать только переменные типа Foo.

  • Если перегружается бинарный оператор, тип первого аргумента должен совпадать с типом вложенного класса, а второй может быть любого типа.

В псевдокоде предыдущего раздела я использовал оператор += с классом Invoice. По причинам, которые скоро будут вам понятны, такие составные операторы на самом деле перегрузить нельзя. Переопределить можно только "базовый" оператор, в данном случае +. Вот синтаксис определения метода operator+ метода Invoice:

public static Invoice operator+ (Invoice invoice!, Invoice invoice2) <

// Создаем новый объект Invoice.

// invoice! в новый объект Invoice.

// Добавляем необходимое содержимое из

// invoice2 в новый объект Invoice.

// Возвращаем новый объект Invoice. >

Теперь рассмотрим пример, более приближенный к действительности, с двумя классами: Invoice и InvoiceDetailLine. Invoice имеет член-переменную типа Array List, который представляет совокупность позиций из всех счетов. Чтобы можно было суммировать позиции из нескольких счетов, я перегрузил оператор + (см. метод operator+). Метод Invoice.оре-rator+ создает новый объект Invoice и проходит по массивам обоих объектов, добавляя каждую позицию к новому объекту Invoice. Затем этот объект возвращается вызывающему методу. Разумеется, в реальном приложении все будет значительно сложней, а здесь я лишь.показываю, как на самом деле могут перегружаться операторы.

using System;

using System.Collections;

class InvoiceDetailLine {

double lineTotal; public double LineTotal {

get {

return this.lineTotal;

} }

public InvoiceDetailLine(double LineTotal) {

this.lineTotal = LineTotal; } }

class Invoice {

public ArrayList DetailLines;

public InvoiceO {

DetailLines = new ArrayListQ; }

public void Printlnvoice() {

Console.WriteLine("\nn,03vmKfl Nbr\tBcero");

double total = 0;

foreach(InvoiceDetailLine detailLine in DetailLines)

{

Console.WriteLine("{0}\t\t{1}", i++, detailLine.LineTotal);

total += detailLine.LineTotal; }

Console.WriteLine("=====\t\t==="); Console.WriteLine("Bcero\t\t{1}", i++, total); }

public static Invoice operator* (Invoice invoicel,

Invoice invoice2) {

Invoice returnlnvoice = new InvoiceO;

invoicel.DetailLines) {

returnlnvoice.DetailLines.Add(detailLine); >

foreach (InvoiceDetailLine detailLine in

invoice2.DetailLines) {

returnlnvoice.DetailLines.Add(detailLine); }

return returnlnvoice; } }

class InvoiceAddApp {

public static void Main() {

Invoice 11 = new InvoiceO; for (int i = 0; i < 2; i++) {

11.DetailLines.Add(new InvoiceDetailLine(i + 1)); }

Invoice i2 = new InvoiceO; for (int i = 0; i < 2; i++) <

12.DetailLines.Add(new InvoiceDetailLine(i + 1)); }

Перегружаемые операторы

Перегружаться могут только перечисленные ниже унарные и бинарные операторы.

Унарные операторы: +, -, !, ~, ++, -, true, false

Бинарные операторы: +, -, *, /, %, &, \, Л, ", ", ==, !=, >, <, >=, <=

ПРИМЕЧАНИЕ Запятая здесь разделяет различные перегружаемые операторы. Сам оператор "запятая", применяемый в операторе for и в вызовах методов, перегружаться не может.

Ограничения на перегрузку операторов

Оператор присваивания = перегрузить нельзя. Однако когда вы перегружаете бинарный оператор, оператор присваивания в его составном эквиваленте неявно перегружается. Например, если перегружается оператор +, оператор += неявно перегружается, так что вызывается определенный пользователем метод operator+.

Операторы можно перегружать, однако, как вы знаете из главы 7, индексация объектов, определенных пользователем, поддерживается посредством индексаторов.

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

Операторы, которые в настоящее время в языке С# не определены, также не перегружаются. Например, вы не можете определить ** как разновидность возведения в степень, поскольку в С# не определен оператор **. Кроме того, нельзя изменить синтаксис операторов. Вы не можете изменить бинарный оператор *, чтобы он принимал три аргумента, поскольку его синтаксис по определению подразумевает два аргумента. И, наконец, старшинство операторов тоже неизменяемо - правила старшинства неизменны (см. главу 10).

Правила разработки

Вы увидели, что собой представляет перегрузка операторов и как она применяется в С#. Теперь рассмотрим один аспект этой полезной возможности, который часто упускают из виду - руководящие принципы, применяемые при разработке. Чего следует избегать, так это известной тенденции использовать какую-то возможность ради нее самой, или "решения ради решения". Между тем при правильном подходе к проектированию нужно помнить поговорку "Код сложнее читать, чем писать". Не забывайте о пользователях своего класса, когда решаете, нужно ли и где перегружать операторы. Вот практическое правило: оператор нужно перегружать, только если в результате интерфейс класса станет более интуитивно понятным. Например, есть определенный смысл предоставить возможность суммировать счета.

Кроме того, не забывайте поставить себя на место пользователя вашего класса. Например, вы пишете класс Invoice для счетов и хотите, чтобы для них могла предоставляться скидка. Нам с вами может быть известно, что в счет добавлена строка, учитывающая кредит, но суть инкапсуляции в том, что пользователю класса не нужно ничего знать о подробностях его реализации. Следовательно, неплохой идеей может быть перегрузка оператора * (как показано ниже), поскольку это сделает интерфейс класса Invoice естественнее и понятнее:

invoice *= .95; // Скидка 5Х.

Нестандартные преобразования

Ранее я упоминал, что оператор "скобки", применяемый при приведении типов, не может быть перегружен и вместо этого используется нестандартное преобразование. Если коротко, нестандартные преобразования позволяют объявить преобразования для структур и классов, так что эти struct или class могут быть преобразованы в другие структуры, классы или базисные типы С#. Когда и зачем это делать? Скажем, в вашем приложении нужны две температурные шкалы, Цельсия и Фаренгейта, чтобы быстро переводить температуру из одной шкалы в другую. В случае нестандартных преобразований придерживайтесь такого синтаксиса:

Fahrenheit f = 98.6F;

Celsius с = (Celsius)f; // Нестандартное преобразование.

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

Fahrenheit f = 98.6F;

Celsius с = f.ConvertToCelsius();

Синтаксис и пример

В синтаксисе нестандартного преобразования применяется ключевое слово operator.

public static implicit operatorрезулътирующий_тип (исх_тип операнд); public"static explicit operator результирующий_mun (ucxjnun операнд).

Правил, связанных с синтаксисом преобразований, немного.

  • Любой метод преобразования для struct или class (вы можете определить их сколько хотите) должен быть static.
  • Преобразование должно быть определено или как неявное, или как явное. Ключевое слово implicit означает, что клиенту не нужно будет приводить тип - это будет сделано автоматически (неявно). Ключевое слово explicit указывает, что клиент должен явно привести значение к желаемому типу.
  • Все преобразования должны или принимать (как аргумент) тип, который определит преобразование, или возвращать этот тип.
  • Как и при перегрузке операторов в методах преобразования применяется ключевое слово operator, но без добавленного к нему оператора.

Когда я впервые читал эти правила, у меня было смутное представление о том, что делать дальше. Чтобы в этом разобраться, рассмотрим пример. У нас есть две структуры (Celsius и Fahrenheit), позволяющие клиенту преобразовывать значение типа float в любую температурную шкалу. Сначала я представлю структуру Celsius и сделаю несколько замечаний, а затем вы увидите законченное работающее приложение.

struct Celsius {

public Celsius(float temp)

{

this.temp = temp;

}

Celsius c;

с = new Celsius(temp);

return(c); }

public static implicit operator float(Celsius c) {

public float temp; }

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

Далее. Я объявил конструктор с единственным аргументом типа float. Это значение хранится в члене-переменной temp. А теперь посмотрите на оператор преобразования, следующий за конструктором структуры.

Это метод, который будет вызываться, когда клиент попытается преобразовать число с плавающей точкой к Celsius или использовать такое число в методе, где должна быть структура Celsius. Этот метод ничего особенного не делает и фактически представляет собой шаблон, который можно применять в большинстве основных преобразований. Здесь я создаю экземпляр структуры Celsius и возвращаю эту структуру. В последнем методе просто производится преобразование из шкалы Фаренгейта в шкалу Цельсия.

Вот законченное приложение, включая структуру Fahrenheit.

using System;

struct Celsius {

public Celsius(float temp)

{

this.temp = temp;

}

public static implicit operator Celsius(float temp) {

Celsius c;

с = new Celsius(temp);

return(c); }

public static implicit operator float(Celsius c)

{

return((((c.temp - 32) / 9) * 5)); }

public float temp; }

struct Fahrenheit {

public Fahrenheit(float temp)

<

this.temp = temp; >

public static implicit operator Fahrenheit(float temp) {

Fahrenheit f; f = new Fahrenheit(temp); return(f); }

public static implicit operator float(Fahrenheit f)

{

return((((f.temp 9) / 5) + 32));

>

public float temp; }

class TemplApp

{

public static void Main()

<

float t;

t=98.6F;

Console.Мг11е("Преобразование {0} в градусы Цельсия = ", t);

Console.WriteLine((Celsius)t);

Console.Write("npeo6pa3oeaHne {0} в градусы

Фаренгейта = ", t); Console.WriteLine((Fahrenheit)t); > }

Скомпилировав и запустив это приложение, вы получите:

Преобразование 98.6 в градусы Цельсия = 37 Преобразование 0 в градусы Фаренгейта = 32

Все это неплохо работает, и выражение (Celsius)98.6F понятней, чем вызов статического метода класса. Но учтите, что преобразующему методу можно передавать только значения типа float. Например, этот код скомпилирован не будет:

Celsius с = new Celsius(55); Console.WriteLine((Fahrenheit)c);

Кроме того, поскольку отсутствует метод для преобразования в шкалу Цельсия, который принимал бы структуру Fahrenheit (и наоборот), код предполагает, что полученное значение требует преобразования. Словом, если я вызову (Celsius)98.6F, я получу значение 37. Однако если значение затем снова передать преобразующему методу, он не будет знать, что оно уже преобразовано и логически уже представляет температуру по Цельсию - для преобразующего метода это всего лишь число с плавающей точкой. В результате значение снова будет преобразовано. Следовательно, нам нужно изменить приложение, чтобы каждая из структур принимала в качестве допустимого аргумента другую структуру.

Когда я задумал это сделать, мне стало страшно: я подумал, что это очень сложная задача. И зря! Смотрите, как все просто:

using System;

class Temperature {

public Temperature(float Temp)

<

this.temp = Temp;

}

protected float temp; public float Temp <

get {

return this.temp; } } }

class Celsius ; Temperature {

public Celsius(float Temp) : base(Temp) {}

public static implicit operator Celsius(float Temp) {

return new Celsius(Temp); }

public static implicit operator Celsius(Fahrenheit F) {

return new Celsius(F.Temp);

}

public static implicit operator float(Celsius C)

{

return((((C.temp - 32) / 9) * 5));

} }

class Fahrenheit: Temperature

{

public Fahrenheit(float Temp) : base(Temp) {}

public static implicit operator Fahrenheit(float Temp)

{

return new Fahrenheit(Temp);

public static implicit operator Fahrenheit(Celsius C)

{

return new Fahrenheit(C.Temp);

}

public static implicit operator float(Fahrenheit F)

{

return((((F.temp * 9) / 5) + 32));

} }

class Temp2App

{

public static void DisplayTemp(Celsius Temp)

{

Console.Write("Преобразование {0} {1} в градусы "+ "Фаренгейта = ", Temp.ToStringO, Temp.Temp); Console.WriteLine((Fah renheit)Temp); }

public static void DisplayTemp(Fahrenheit Temp)

{

Console.Write("Преобразование {0} {1} в градусы Цельсия = ",

Temp.ToStringO, Temp.Temp); Console.WriteLine((Celsius)Temp); }

public static void Main() {

Fahrenheit f = new Fahrenheit(98.6F);

DisplayTemp(f);

Celsius с = new Celsius(OF); DisplayTemp(c); } }

Обратите внимание: я изменил типы Celsius и Fahrenheit из struct в class. Это сделано только для того, чтобы иметь два примера: один со структурами, другой - с классами. Но более важная причина - возможность совместного использования члена temp, имея классы Celsius и Fahrenheit, производные от одного базового класса Temperature. При выводе я здесь применяю унаследованный от System. Object метод ToString.

public static implicit operator Celsius(float temp) {

с = new Celsius(temp);

return(c); }

public static implicit operator Celsius(Fahrenheit f) {

Celsius c;

с = new Celsius(f.temp);

return(c); }

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

Подведем итоги

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

Как известно, в языке С# тип переменной определяет набор значений, которые она может хранить, а также набор операций, которые можно выполнять над этой переменной. Например, над значением переменной типа 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);}

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

Иногда возникает необходимость создать один и тот же метод, но с разным набором параметров. И в зависимости от имеющихся параметров применять определенную версию метода. Такая возможность еще называется перегрузкой методов (method overloading).

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

    Имя метода

    Количество параметров

    Типы параметров

    Порядок параметров

    Модификаторы параметров

Но названия параметров в сигнатуру НЕ входят. Например, возьмем следующий метод:

Public int Sum(int x, int y) { return x + y; }

У данного метода сигнатура будет выглядеть так: Sum(int, int)

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

    Количеству параметров

    Типу параметров

    Порядку параметров

    Модификаторам параметров

Например, пусть у нас есть следующий класс:

Class Calculator { public void Add(int a, int b) { int result = a + b; Console.WriteLine($"Result is {result}"); } public void Add(int a, int b, int c) { int result = a + b + c; Console.WriteLine($"Result is {result}"); } public int Add(int a, int b, int c, int d) { int result = a + b + c + d; Console.WriteLine($"Result is {result}"); return result; } public void Add(double a, double b) { double result = a + b; Console.WriteLine($"Result is {result}"); } }

Здесь представлены четыре разных версии метода Add, то есть определены четыре перегрузки данного метода.

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

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

Add(int, int) Add(int, int, int) Add(int, int, int, int) Add(double, double)

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

Class Program { static void Main(string args) { Calculator calc = new Calculator(); calc.Add(1, 2); // 3 calc.Add(1, 2, 3); // 6 calc.Add(1, 2, 3, 4); // 10 calc.Add(1.4, 2.5); // 3.9 Console.ReadKey(); } }

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

Result is 3 Result is 6 Result is 10 Result is 3.9

Также перегружаемые методы могут отличаться по используемым модификаторам. Например:

Void Increment(ref int val) { val++; Console.WriteLine(val); } void Increment(int val) { val++; Console.WriteLine(val); }

В данном случае обе версии метода Increment имеют одинаковый набор параметров одинакового типа, однако в первом случае параметр имеет модификатор ref. Поэтому обе версии метода будут корректными перегрузками метода Increment.

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

Int Sum(int x, int y) { return x + y; } int Sum(int number1, int number2) { return x + y; } void Sum(int x, int y) { Console.WriteLine(x + y); }

Сигнатура у всех этих методов будет совпадать:

Sum(int, int)

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

Основы перегрузки операторов

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

// Операция + с целыми. int а = 100; int b = 240; int с = а + b; //с теперь равно 340

Здесь нет ничего нового, но задумывались ли вы когда-нибудь о том, что одна и та же операция + может применяться к большинству встроенных типов данных C#? Например, рассмотрим такой код:

// Операция + со строками. string si = "Hello"; string s2 = " world!"; string s3 = si + s2; // s3 теперь содержит "Hello world!"

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

Язык C# предоставляет возможность строить специальные классы и структуры, которые также уникально реагируют на один и тот же набор базовых лексем (вроде операции +). Имейте в виду, что абсолютно каждую встроенную операцию C# перегружать нельзя. В следующей таблице описаны возможности перегрузки основных операций:

Операция C# Возможность перегрузки
+, -, !, ++, --, true, false Этот набор унарных операций может быть перегружен
+, -, *, /, %, &, |, ^, > Эти бинарные операции могут быть перегружены
==, !=, <, >, <=, >= Эти операции сравнения могут быть перегружены. C# требует совместной перегрузки "подобных" операций (т.е. < и >, <= и >=, == и!=)
Операция не может быть перегружена. Oднако, аналогичную функциональность предлагают индексаторы
() Операция () не может быть перегружена. Однако ту же функциональность предоставляют специальные методы преобразования
+=, -=, *=, /=, %=, &=, |=, ^=, >= Сокращенные операции присваивания не могут перегружаться; однако вы получаете их автоматически, перегружая соответствующую бинарную операцию

Перегрузка операторов тесно связана с перегрузкой методов. Для перегрузки оператора служит ключевое слово operator , определяющее операторный метод, который, в свою очередь, определяет действие оператора относительно своего класса. Существуют две формы операторных методов (operator): одна - для унарных операторов, другая - для бинарных. Ниже приведена общая форма для каждой разновидности этих методов:

// Общая форма перегрузки унарного оператора. public static возвращаемый_тип operator op(тип_параметра операнд) { // операции } // Общая форма перегрузки бинарного оператора. public static возвращаемый_тип operator op(тип_параметра1 операнд1, тип_параметра2 операнд2) { // операции }

Здесь вместо op подставляется перегружаемый оператор, например + или /, а возвращаемый_тип обозначает конкретный тип значения, возвращаемого указанной операцией. Это значение может быть любого типа, но зачастую оно указывается такого же типа, как и у класса, для которого перегружается оператор. Такая корреляция упрощает применение перегружаемых операторов в выражениях. Для унарных операторов операнд обозначает передаваемый операнд, а для бинарных операторов то же самое обозначают операнд1 и операнд2 . Обратите внимание на то, что операторные методы должны иметь оба спецификатора типа - public и static.

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

Давайте рассмотрим применение перегрузки бинарных операторов на простейшем примере:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class MyArr { // Координаты точки в трехмерном пространстве 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; } // Перегружаем бинарный оператор + public static MyArr operator +(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; return arr; } // Перегружаем бинарный оператор - public static MyArr operator -(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; return arr; } } class Program { static void Main(string args) { MyArr Point1 = new MyArr(1, 12, -4); MyArr Point2 = new MyArr(0, -3, 18); Console.WriteLine("Координаты первой точки: " + Point1.x + " " + Point1.y + " " + Point1.z); Console.WriteLine("Координаты второй точки: " + Point2.x + " " + Point2.y + " " + Point2.z + "\n"); MyArr Point3 = Point1 + Point2; Console.WriteLine("\nPoint1 + Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point3 = Point1 - Point2; Console.WriteLine("\nPoint1 - Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Console.ReadLine(); } } }

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

Унарные операторы перегружаются таким же образом, как и бинарные. Главное отличие заключается, конечно, в том, что у них имеется лишь один операнд. Давайте модернизируем предыдущий пример, дополнив перегрузки операций ++, --, -:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class MyArr { // Координаты точки в трехмерном пространстве 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; } // Перегружаем бинарный оператор + public static MyArr operator +(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; return arr; } // Перегружаем бинарный оператор - public static MyArr operator -(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; return arr; } // Перегружаем унарный оператор - public static MyArr operator -(MyArr obj1) { MyArr arr = new MyArr(); arr.x = -obj1.x; arr.y = -obj1.y; arr.z = -obj1.z; return arr; } // Перегружаем унарный оператор ++ public static MyArr operator ++(MyArr obj1) { obj1.x += 1; obj1.y += 1; obj1.z +=1; return obj1; } // Перегружаем унарный оператор -- public static MyArr operator --(MyArr obj1) { obj1.x -= 1; obj1.y -= 1; obj1.z -= 1; return obj1; } } class Program { static void Main(string args) { MyArr Point1 = new MyArr(1, 12, -4); MyArr Point2 = new MyArr(0, -3, 18); Console.WriteLine("Координаты первой точки: " + Point1.x + " " + Point1.y + " " + Point1.z); Console.WriteLine("Координаты второй точки: " + Point2.x + " " + Point2.y + " " + Point2.z + "\n"); MyArr Point3 = Point1 + Point2; Console.WriteLine("\nPoint1 + Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point3 = Point1 - Point2; Console.WriteLine("Point1 - Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point3 = -Point1; 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(); } } }



Рекомендуем почитать

Наверх