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

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

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

При перегрузке операций в C# существует ряд ограничений:

<=  и  >=

<   и  >

==  и  !=

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

public class Drobi

{

   int a; // Числитель

   int b; // Знаменатель


   // Конструкторы и другие методы

   ...............................

}


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

В языке C# можно перегрузить следующие одноместные операции:

+ (унарный плюс)   - (унарный минус)

!   ~   ++   --   true   false

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

public static Тип_результата operator Знак_операции (Формальный_Параметр)

{

   // тело метода, реализующего перегрузку операции

}

Как видим, метод, перегружающий операцию, всегда объявляется открытым (public) и статичным (static). Признаком того, что делается именно перегрузка операции, служит ключевое слово operator, за которым должен располагаться знак перегружаемой операции.

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

Приведём пару примеров перегрузки одноместных операций для нашего класса Drobi.

Пример 1. Перегрузка операции - (смена знака):

public static Drobi operator - (Drobi x)

{

   Drobi t = new Drobi();

   t.a = - x.a;

   t.b = x.b;

   return t;

}

Здесь в методе создаётся новый объект t типа Drobi и затем вычисляются значения полей этого объекта. Метод возвращает результат — переменную t типа Drobi, исходный объект остаётся неизменным. Пример использования операции:

Drobi x = new Drobi(2,5);

Drobi y = new Drobi();

y = -x;

После этих действий переменная y будет иметь значение -2/5.

Пример 2. Перегрузка операции ++ (автоувеличение):

public static Drobi operator ++ (Drobi x)

{

   Drobi t = new Drobi();

   t.a = x.a + x.b;

   t.b = x.b;

   return t;

}

Пример использования операции автоувеличения:

y++;

Если до этого y было равно -2/5, то теперь y примет значение 3/5.

Замечание. Операции автоувеличения (++) и автоуменьшения (--) имеют две формы: префиксную (например, ++i) и постфиксную (например, i++). Но при перегрузке этих операций (инкремент и декремент) обе формы вызывают один и тот же метод. Поэтому перегружаем метод один раз, а затем используем в любой из форм. Хотя, если посмотреть на нашу реализацию перегрузки операции автоувеличения, то по характеру действий в методе нетрудно понять, что это метод для префиксной формы записи.

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

В языке C# можно перегрузить следующие двухместные операции:

+   -   *   /   %   &   |   ^   <<   >>   ==   !=   <   >   >=   <=

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

public static Тип_результата operator Знак_операции (Формальный_Параметр1, Формальный_Параметр2)

{

   // тело метода, реализующего перегрузку операции

}

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

Пример 3. Перегрузка операции сложения +:

public static Drobi operator + (Drobi x, Drobi y)

{

   int t1 = x.a * y.b + x.b * y.a;

   int t2 = x.b * y.b;

   socrat(ref t1, ref t2);

   return new Drobi(t1, t2);

}

Переменные целого типа t1 и t2 — это значения числителя и знаменателя дроби, которая является суммой исходных дробей x и y.

Так как при выполнении ряда операций, например, сложения, могут получаться дроби, в которых возможно сокращение, вызываем закрытый статический метод socrat() (пример реализации можно посмотреть в полном тексте программы). Этот метод по сокращению дроби приходится делать статическим, потому-что из статического метода (а метод, перегружающий сложение, обязан быть статическим!) можно вызвать тоже только статический метод.

В конце метода создаём безымянный объект типа Drobi, который и будет результатом работы метода.

Пример использования операции сложения (здесь x и y — объекты класса Drobi):

Drobi z = new Drobi();

z = x + y;


Перегрузка операций сравнения

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

Во-первых, операции сравнения необходимо реализовывать парами (см. выше), а во-вторых при этом надо перегрузить методы Equals() и GetHashCode().

Метод Equals() позволяет сравнивать два объекта, а метод GetHashCode() возвращает так называемый хеш-код объекта, который однозначно идентифицирует каждый объект класса и используется для быстрого поиска объектов по ключу.

Методы Equals() и GetHashCode() взаимосвязаны, т.е. если метод Equals() определяет два объекта одинаковыми, то метод GetHashCode() для обоих объектов будет выдавать одинаковый хеш-код.

Пример 4. Перегрузка операций > (больше) и < (меньше) и возможная реализация методов Equals() и GetHashCode():

public static bool operator > (Drobi x, Drobi y)

{

   if(x.a * y.b > x.b * y.a)

      return true;

   else

      return false;

   }

public static bool operator < (Drobi x, Drobi y)

{

   if(x.a * y.b < x.b * y.a)

      return true;

   else

      return false;

   }

public override bool Equals(object o)

{

   if(o.GetType() != GetType())

      return false;

   Drobi t = (Drobi) o;

   return (a == t.a && b == t.b);

}

public override int GetHashCode()

{

   return 0;

}

Пример использования перегрузки операций сравнения (здесь x и y — объекты класса Drobi):

if(x > y)

   Console.WriteLine("Да, x > y");

else

   Console.WriteLine("Нет, x не больше y");


Полный текст программы

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

using System;

namespace Prim_Class_Drobi

{

   public class Drobi

   {

      int a; // Числитель

      int b; // Знаменатель


      // Конструкторы

      public Drobi()

      {

         a = 0;

         b = 1;

      }

      public Drobi(int a0, int b0)

      {

         a = a0;

         b = b0;

         if(b == 0)

         {

            b = 1;

            Console.WriteLine("Недопустимое значение для знаменателя:{0}. Заменяем на 1", b0);

         }

         else if(b < 0)

         {

            a = -a;

            b = -b;

         }

         socrat(ref a, ref b);

      }

      // Смена знака

      public static Drobi operator - (Drobi x)

      {

         Drobi t = new Drobi();

         t.a = - x.a;

         t.b = x.b;

         return t;

      }

      // Автоувеличение

      public static Drobi operator ++ (Drobi x)

      {

         Drobi t = new Drobi();

         t.a = x.a + x.b;

         t.b = x.b;

         return t;

      }

      // Сложение дробей

      public static Drobi operator + (Drobi x, Drobi y)

      {

         int t1 = x.a * y.b + x.b * y.a;

         int t2 = x.b * y.b;

         socrat(ref t1, ref t2);

         return new Drobi(t1, t2);

      }

      // Умножение дробей

      public static Drobi operator * (Drobi x, Drobi y)

      {

         int t1 = x.a * y.a;

         int t2 = x.b * y.b;

         socrat(ref t1, ref t2);

         return new Drobi(t1, t2);

      }

      // Сокращение дроби

      private static void socrat(ref int a, ref int b)

      {

         int x = Math.Abs(a), y = b;

         while(x > 0 && y > 0)

         {

            if(x > y)

               x = x % y;

            else

               y = y % x;

         }

         int k = x + y;

         if(k > 1)

         {

            a /= k;

            b /= k;

         }

      }

      // Печать дроби

      public void print()

      {

         Console.WriteLine("{0}/{1}", a, b);

      }

      // Перегрузка операций сравнения

      public static bool operator > (Drobi x, Drobi y)

      {

         if(x.a * y.b > x.b * y.a)

            return true;

         else

            return false;

      }

      public static bool operator < (Drobi x, Drobi y)

      {

         if(x.a * y.b < x.b * y.a)

            return true;

         else

            return false;

      }

      public static bool operator == (Drobi x, Drobi y)

      {

         if(x.a == y.a && x.b == y.b)

            return true;

         else

            return false;

      }

      public static bool operator != (Drobi x, Drobi y)

      {

         if(x.a != y.a || x.b != y.b)

            return true;

         else

            return false;

      }

      public static bool operator >= (Drobi x, Drobi y)

      {

         if(x.a * y.b >= x.b * y.a)

            return true;

         else

            return false;

      }

      public static bool operator <= (Drobi x, Drobi y)

      {

         if(x.a * y.b <= x.b * y.a)

            return true;

         else

            return false;

      }

      public override bool Equals(object o)

      {

         if(o.GetType() != GetType())

            return false;

         Drobi t = (Drobi) o;

         return (a == t.a && b == t.b);

      }

      public override int GetHashCode()

      {

         return 0;

      }

   }

   class Program

   {

      public static void Main(string[] args)

      {

         Console.WriteLine("Тестирование класса Drobi:");

         Drobi x = new Drobi(2,5);

         Console.Write("Исходная дробь x=");

         x.print();

         Drobi y = new Drobi();

         y = -x;

         Console.Write("После смены знака: y=");

         y.print();

         if(x > y)

            Console.WriteLine("Да, x > y");

         else

            Console.WriteLine("Нет, x не больше y");

         if(x >= y)

            Console.WriteLine("Да, x >= y");

         else

            Console.WriteLine("Нет, x не больше или равно y");

         if(x == y)

            Console.WriteLine("Да, x равно y");

         else

            Console.WriteLine("Нет, x не равно y");

         y++;

         Console.Write("Увеличили на 1: y=");

         y.print();

         Drobi z = new Drobi();

         z = x + y;

         Console.Write("Сумма дробей z=");

         z.print();

         Drobi z2 = new Drobi();

         z2 = x * y;

         Console.Write("Произведение дробей z2=");

         z2.print();

         Console.Write("Press any key to continue . . . ");

         Console.ReadKey(true);

      }

   }

}


Результаты работы:

Тестирование класса Drobi:

Исходная дробь x=2/5

После смены знака: y=-2/5

Да, x > y

Да, x >= y

Нет, x не равно y

Увеличили на 1: y=3/5

Сумма дробей z=1/1

Произведение дробей z2=6/25

Press any key to continue . . .


Назад
На верх
Вперёд
Hosted by uCoz