В языке C# как и в C++ можно при желании перегружать операции для работы с объектами своих классов. Перегрузка операций позволяет получать более естественный и лучше читаемый текст программы. Перегрузка операций, как и перегрузка методов, является одной из форм полиморфизма.
Перегружать можно одноместные и двухместные операции.
При перегрузке операций в C# существует ряд ограничений:
нельзя придумать свои знаки операций;
нельзя изменить приоритет операции, например, если для чисел приоритет операции умножения (*) выше, чем сложения (+), то и в классе пользователя при перегрузке этих операций сохраняется то же старшинство действий;
метод, реализующий перегрузку какой-либо операции, должен быть статическим и открытым;
параметры можно передавать в метод для реализации операции только по значению (ref и out запрещены);
нельзя перегружать ни какие формы операции присваивания (=, += и т.д.);
операции сравнения необходимо реализовывать парами (симметричными по смыслу):
<= и >=
< и >
== и !=
если перегружаются операции сравнения, то необходимо также перегрузить два метода, наследуемые из класса object: Equals() и GetHashCode().
Теперь рассмотрим ряд примеров по перегрузке операций. Для большей конкретности создадим какой-нибудь класс, например, класс, моделирующий обыкновенные дроби. Вот как может выглядеть начало этого класса:
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 . . .
|
|
|