Перегрузка знаков операций в C#
Перегрузка знаков операций (операторов) позволяет трактовать знак арифметической или логической операции в зависимости от объектов (типов данных), к которым этот знак относится. Например, операция сложения двух чисел отличается от суммирования векторов. Знак ‘+’ по отношению к строкам имеет смысл конкатенации (сцепления) строк, а вовсе не суммирование кодов букв. При перегрузке знаков операций подразумевается, что, по крайней мере, один из операндов является объектом некоторого класса.
Отметим, что никакие перегрузки знаков операций не должны изменять их исходного предназначения. Если знак операции сложения перегружен для добавления элемента в список, из этого не следует, что его больше нельзя применять для сложения чисел.
Другими словами, когда в программе записано выражение, в котором используется знак некоторой операции, то компилятор проверяет тип данного, связанный с этой операцией, а затем реализует выполнение требуемых действий, если в программе эта операция переопределена для данного случая. Например, если знак операции сложения перегружен для комплексных чисел, тогда в результате сложения двух комплексных чисел будут суммироваться отдельно действительные и мнимые составляющие этих чисел.
Особо следует подчеркнуть, что тип данного может иметь сколь угодно много составляющих, а перегрузка знака операции может потребовать достаточно сложных вычислений. Например, можно рассматривать систему материальных точек, а знак операции сложения перегрузить применительно к добавлению к системе новой материальной точки с корректировкой центра тяжести системы. Каждая точка задается тремя координатами и массой. Знак сложения в этом случае суммирует не координаты и массы, а объекты (объект, состоящий из одной точки, суммируется с объектом, содержащим одну или более точек), вычисляя новый центр тяжести. Перегружаемый знак операции относится не к отдельным элементам, а к объекту в целом.
Для реализации какой-либо операции, как правило, вызывается соответствующая подпрограмма. Поэтому перегрузка знаков операций сводится к перегрузке функций, а транслятор по списку аргументов подбирает требуемую функцию. Например, знак операции суммирования трактуется по-разному применительно к обычным числам, комплексным числам, координатам точек, строкам текста и т.д.
Принципиальная возможность перегрузки знаков операция основана на двух моментах:
• Во время анализа алгебраического выражения выявляются типы данных, входящих в выражение, с соответствующим преобразованием типа и трактовкой данных применительно к знаку операции. (Даже в обычных арифметических операциях для выполнения сложения, например, для целых и вещественных чисел вызываются разные подпрограммы). Поэтому, если в выражении (арифметическом или логическом) появился объект некоторого класса, должна быть вызвана функция для выполнения операции, заданной знаком операции, применительно к этому типу объекта.
• Перегруженная функция опознается, как по имени функции, так и по списку ее параметров. В случае перегрузки знаков операций в качестве имени функции используется ключевое слово operator вместе со знаком перегружаемой операции.
Результат перегрузки знака операции, как правило, принимает участие в вычислении алгебраического или логического выражения. По этой причине все функции, обеспечивающие перегрузку знака операции, возвращают объект непосредственно в это выражение.
Перегрузка знаков операций не должна отменять эту операцию для других типов данных. Поэтому перегруженный знак операции всегда связан с определенным классом. Именно в применении к этому классу и определяется знак операции. Для перегрузки знаков операции оформляется функция - член класса. Оформление этой функции отличается от оформления обычной функции-члена класса наличием ключевого слова operator, после которого записан знак перегружаемой операции.
// Общая форма перегрузки бинарного оператора.
public static тип_возврата operator знак_операции (тип_операнда_1 операнд1,
тип_операнда_2 операнд2)
{
// Операторы реализации знака операции для перегрузки
}
Например, Пусть имеется некоторый объект, имеющий имя (класса) MyObj, тогда пример оформления перегрузки будет иметь вид:
public static MyObj operator + (MyObj mo1, MyObj mo2)
{
MyObj mr;
// Операторы реализации знака операции для перегрузки
return mr;
}
В приведенной форме подпрограммы для перегрузки атрибуты public static и operator являются обязательными. «тип возврата» указывает на тип возвращаемого из подпрограммы объекта. Если в списке аргументов подпрограммы записан один параметр, операция унарная (в операции принимает участие только один операнд). Если в списке аргументов подпрограммы записаны два параметра, операция бинарная (в операции принимают участие два операнда).
При перегрузке знаков операций нельзя изменять приоритеты знаков операций и нельзя знакам двуместных операций назначать одноместные операции.
• Без ограничений можно перегружать перечисленные далее унарные и бинарные знаки операций:
+, -, !, ++, --, true, false, +, -, *, /, %, &, |, ^, <<, >>
• Операции логических отношений можно перегружать только парами (если перегружается операция <, то должна также быть перегружена операция >).
• Нельзя перегружать операции [ ] и ( ). Но их функциональность может быть реализована с помощью индексаторов и подпрограммами преобразований типов.
• Сдвоенные знаки операций (сокращенные операции) явно не перегружаются, но их перегрузка выполняется автоматически при перегрузке соответствующих знаков операций (+ для += и т. д.).
• Нельзя перегружать знак равенства (=).
Перегрузка знаков операций может быть трех видов;
• перегрузка двухместных операций;
• перегрузка одноместных операций;
• перегрузка с сохранением свойства коммутативности операции.
Знаки инкремента (++) и декремента (--) можно реализовать как для постфиксной формы, так и для префиксной формы.
Для постфиксной формы в списке аргументов необходимо указать тип int, а для префиксной формы тип void. Параметр типа int при реализации метода игнорируется.
Ниже приведена программа, в которой перегружается знак операции сложения (+) применительно к сливанию в один сосуд двух растворов с разной удельной плотностью.
Итоговая плотность (ds) вычисляется по формуле
Где d1 и d2 плотности первой и второй жидкостей, а v1 и v2 их объемы. Ниже приведен текст программы.
class liq
{
private double vol; // Объем
private double den; // Плотность
public liq() { } // Пустой конструктор
public double Vol // Свойства для объема
{
get { return vol; }
set { vol = value; }
}
public double Den // Свойства для плотности
{
get { return den; }
set { den = value; }
}
public static liq operator +(liq ob1, liq ob2) // Перегрузка сложения
{
double ds; //
liq prom = new liq();
prom.vol = ob1.vol + ob2.vol;
ds = (ob1.vol * ob1.den + ob2.vol * ob2.den) / (ob1.vol + ob2.vol);// Расчет
prom.den = ds;
return prom;
}
}
class Program
{
static void Main(string[] args)
{
liq bot1 = new liq();// Создание первого объекта
liq bot2 = new liq();// Создание второго объекта
liq bot3 = new liq(); // Создание объекта для результата
string[] mas = new string[2];
string line;
double v, d;
Console.WriteLine
(" Введите объем и плотность первой жидкости через пробел");
line = Console.ReadLine();
mas = line.Split( );
v =double.Parse(mas[0]);
d = double.Parse(mas[1]);
bot1.Vol = v;
bot1.Den = d;
Console.WriteLine
("Введите объем и плотность второй жидкости через пробел");
line = Console.ReadLine();
mas = line.Split( );
v = double.Parse(mas[0]);
d = double.Parse(mas[1]);
bot2.Vol = v;
bot2.Den = d;
bot3 = bot1 + bot2;
Console.WriteLine("Ответ: Объем = " + bot3.Vol + " Плотность = " +
bot3.Den);
Console.ReadKey();
}
}
Вывод программы может иметь такой вид:
При перегрузке знаков бинарных операций с одной из сторон знака операции может быть величина, определяемая встроенным типом данных, например, число. В этом случае следует обеспечить сохранение коммутативности операции. Поэтому приходится выполнять перегрузку для двух случаев: когда простое число размещено справа от знака операции, и когда оно размещено слева от знака операции.
Ниже приведен пример перегрузки знака вычитания. В задаче задается строка и число. Вычитание подразумевает удаление из строки символа с заданным индексом. (В программе индексы отсчитываются от нуля, а для пользователя от 1).
namespace Overload
{
class Rem
{
public string src { get; set; }
string dst1, dst2;
public int pos { get; set; } // Свойства для try-catch
public Rem() { } // Конструктор без параметров
public Rem (string s, int p) // Конструктор с параметрами
{
src = s;
pos = p;
}
// Перегрузка знака вычитания
public static Rem operator- (Rem r, int pos)
{
r.dst1 = r.src.Remove(pos -1);
r.dst2 = r.src.Substring(pos);
r.src = r.dst1 + r.dst2;
return r; // Возврат объекта после вычитания
}
}
class Program
{
static void Main(string[] args)
{
int ind;
string st;
Console.WriteLine(" Введите строку");
st = Console.ReadLine();
Console.WriteLine(" Введите позицию исключаемого символа");
ind = int.Parse(Console.ReadLine());
Rem ob = new Rem(st,0);// Инициализация объекта
try
{
ob = ob - ind;
if (ob.pos > st.Length) throw new Exception();//Исключение
}
catch
{
Console.WriteLine(
" Не борзей! В строке всего {0} символов, а ты исключаешь {1}-й",
b.pos+1, ind );
}
Console.WriteLine(" Строка после удаления символа
{0}",
ob.src);
Console.ReadKey();
}
}
Вывод программы
Другим примером может служить сложение координаты точки с числом. В примере ниже приведена перегрузка знака сложения координаты точки с числом. Число прибавляется только к координате Х.
class Program
{
class coord
{
private int x, y; // Координаты
public coord() { x = 0; y = 0; } //
public coord(int n, int k) { x = n; y = k; }
// Перегрузка сложения объекта с числом
public static coord operator +(coord ob, int n)
{
coord prom = new coord();
prom.x = ob.x + n;
return prom;
}
// Перегрузка сложения числа с объектом
public static coord operator +(int n, coord ob)
{
coord prom = new coord(); ;
prom.x = n + ob.x;
return prom;
}
public int X // Свойства для координаты Х
{
get { return x; }
set { x = value; }
}
// Основная программа
static void Main(string[] args)
{
coord v = new coord(1, 2);
coord s = new coord();
Console.WriteLine("
Исходные координаты x = {0} y = {1}", 1, 2);
Console.WriteLine("
Введите добавляемое число");
int n = int.Parse(Console.ReadLine());
s = v + n; // Сложение координаты с числом
Console.WriteLine("Сложение координаты объекта X с числом x = {0}", s.X);
s = n + v; // Сложение числа с координатой
Console.WriteLine("Сложение числа с координатой X объекта x = {0}", s.X);
Console.ReadKey();
}
}
}
Результат выполнения программы (изменяется только координата Х).
Порядок записи операндов не влияет на результат.
При перегрузке унарного оператора в классе объявляется объект, с его полями выполняется операция, затем следует возврат измененного объекта.
namespace Plus_Plus_Overload
{
class OverLoad
{
public int x;
public static OverLoad operator++(OverLoad ob) // Собственно перегрузка
{
OverLoad ov = new OverLoad();
ov.x = ob.x +1;
return ov; //
}
public OverLoad() { } // Конструктор без параметров
public OverLoad(int a) // Конструктор с параметрами
{
x = a;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Введите число");
int m = int.Parse(Console.ReadLine());
OverLoad w = new OverLoad(m);
OverLoad t = new OverLoad();
t = ++w;
Console.WriteLine("После наращивания (prefix)
" + t.x);
w++;
t = w;
Console.WriteLine("После наращивания (postfix)
" + t.x);
Console.ReadKey();
}
}
}
Обратите внимание: При постфиксном наращивании (инкременте) сначала выполняется присваивание, а затем наращивание, но это обнаружить нельзя, если инкремент (или декремент) переменной выполняется отдельно (переменная не входит в состав арифметического выражения). Поэтому присваивание выполнено в отдельном операторе.
Ниже приведена программа, в которой перегружаются знаки отношения «больше» и «меньше». Программа сравнивает площади двух прямоугольников.
Примечание: При перегрузке знака > компилятор требует также перегрузить знак <.
namespace Log_Rel_OverLoad
{
class Log_Rel
{
double h, w;
public Log_Rel(double a, double b){h = a; w = b;} // Конструктор
public static bool operator >(Log_Rel x, Log_Rel y)
{
if (x.h * x.w > y.h * y.w) return true;
else return false;
}
public static bool operator <(Log_Rel x, Log_Rel y)
{
if (x.h * x.w < y.h * y.w) return true;
else return false;
}
public double H // Свойство для высоты
{
get { return h; }
set { den = value; }
}
public double W // Свойство для ширины
{
get { return w; }
set { w = value; }
}
}
class Program
{
static void Main(string[] args)
{
double a, b;
Console.WriteLine("Введите a и b");
a = double.Parse( Console.ReadLine());
b = double.Parse( Console.ReadLine());
Log_Rel lg = new Log_Rel(3, 4);
Log_Rel mg = new Log_Rel(a, b);
if (lg > mg) Console.WriteLine("Площадь больше");
else Console.WriteLine("Площадь меньше");
Console.ReadKey();
}
}
}
Пример выполнения программы.
Кроме перегрузки логических отношений может быть реализована перегрузка значений true и false. Такая перегрузка должна выполняться попарно, и она необходима в случаях, когда объект должен использоваться в сравнениях. Например, обычная ситуация при выборе объектов по соотношению цены и качества не может выполняться путем простого сравнения отдельных составляющих, поскольку отдельные параметры качества могут иметь весовые коэффициенты.
Перегрузка знаков операций в C#
Лекции по предмету «Программирование»