- •Тема 6. Основы программирования на языке c#
- •Условная операция
- •Операции checked и unchecked
- •Операция поглощения null
- •Безопасность типов
- •Преобразования типов
- •Неявные преобразования
- •Явные преобразования
- •Упаковка и распаковка
- •Проверка равенства объектов
- •Виртуальный метод Equals()
- •Статический метод Equals()
- •Проверка типов значений на равенство
- •Перегрузка операций
- •Как работают операции
- •Пример перегрузки операции: структура Vector
- •Добавление дополнительных перегрузок
- •Перегрузка операций сравнения
- •Пользовательские приведения
- •Реализация пользовательских приведений
- •Приведение между классами
- •Приведение между базовым и производным классами
- •Упаковывающие и распаковывающие приведения
- •Множественные приведения
Добавление дополнительных перегрузок
В дополнение к сложению векторов, можно было бы умножать и вычитать их, а также сравнивать между собой. В настоящем разделе мы продолжим развитие примера Vector, добавив еще несколько перегрузок операций.
Сейчас мы не станем разрабатывать полный набор, который, вероятно, понадобился бы для полнофункционального типа Vector, а просто продемонстрируем другие аспекты перегрузки операций. Для начала перегрузим операцию умножения, чтобы добавить поддержку умножения векторов на скаляры и векторов на другие векторы.
Умножение вектора на скаляр означает просто индивидуальное умножение каждого его компонента на скаляр; например, 2*(1.0,2.5,2.0) вернет (2.0,5.0,4.0). Соответствующая перегрузка операции выглядит следующим образом:
public static Vector operator* (double lhs, Vector rhs)
{
return new Vector(lhs*rhs.x, lhs*rhs.y, lhs*rhs.z);
}
Однако этого недостаточно. Если а и b объявлены как Vector, то это позволит написать код вроде такого:
b = 2 * а;
Компилятор просто неявно преобразует целое число 2 в double, чтобы выражение соответствовало сигнатуре перегрузки. Однако следующий код не скомпилируется:
b = а * 2;
Дело в том, что компилятор трактует перегруженные операции точно так же, как перегрузку методов. Он проверяет все доступные перегрузки данной операции, чтобы отыскать наиболее подходящий. Приведенный выше оператор требует, чтобы первый параметр имел тип Vector, а второй целое число либо нечто, неявно преобразуемое в целое. Но мы не предоставили такой перегрузки. Компилятор не может поменять местами параметры, хотя в данном случае их порядок не важен. Придется явно определить перегрузку, которая принимает сначала Vector, а потом double. Существуют два способа ее реализации. Первый способ предполагает написание операции умножения вектора на число точно так же, как это было сделано в первом случае:
public static Vector operator* (Vector lhs, double rhs)
{
return new Vector(rhs * lhs.x, rhs * lhs.y, rhs *lhs.z);
}
Но, имея уже написанный код, реализующий в точности ту же операцию, эффективнее его использовать повторно:
public static Vector operator* (Vector lhs, double rhs)
{
return rhs * lhs;
}
Этот код сообщает компилятору, что если он видит умножение Vector на double, то может просто поменять порядок операндов и вызвать другую перегрузку. Какому варианту отдать предпочтение дело вкуса. В примерах настоящей главы используется вторая версия, поскольку она выглядит проще и лучше иллюстрирует идею. Эта версия также облегчает сопровождение, поскольку позволяет избежать дублирования кода умножения в двух отдельных перегрузках.
Далее понадобится перегрузить операцию умножения для поддержки перемножения векторов. Математики предлагают множество способов перемножения векторов, но нас интересует один, известный как скалярное произведение, который возвращает в результате скаляр. Это повод продемонстрировать на данном примере, что арифметические операции не обязаны возвращать тип, совпадающий с типом класса, в котором они определены.
В математической терминологии, если есть два вектора (х,у,z) и (X,Y,Z), то значение их скалярного произведения определяется как результат выражения х*Х + y*Y + z*Z. Это может показаться странным способом перемножения таких двух вещей, но на самом деле он довольно полезен, поскольку может применяться для вычисления множества других показателей. Конечно, если вы пишете код, отображающий сложную трехмерную графику, например, использующую Direct3D или DirectDraw, то почти наверняка обнаружите, что скалярное произведение векторов понадобится для вычисления положения объектов на экране. Что сейчас нас интересует так это дать возможность людям, использующим класс Vector, писать выражения вроде double X = а * b для вычисления скалярного произведения двух объектов Vector (а и b). Соответствующая перегрузка будет выглядеть так:
public static double operator * (Vector lhs, Vector rhs)
{
return lhs.x * rhs.x + lhs.у * rhs.у + lhs.z * rhs.z;
}
Теперь, когда мы разобрались с арифметическими операциями, можно проверить их работоспособность с помощью простого тестового метода:
static void Main()
{
// демонстрация арифметических операций
Vector vect1, vect2, vect3;
vect1 = new Vector (1.0,1.5,2.0);
vect2 = new Vector(0.0,0.0,-10.0);
vect3 = vectl + vect2;
Console.WriteLine("vect1 = " + vect1);
Console.WriteLine("vect2 = " + vect2) ;
Console.WriteLine("vect3 = vect1 + vect2 = " + vect3);
Console.WriteLine("2*vect3 = " + 2*vect3);
vect3 += vect2;
Console.WriteLine("vect3 += vect2 дает " + vect3);
vect3 = vect1*2;
Console.WriteLine("Присваивание vect3 = vect1*2 дает " + vect3);
double dot = vect1*vect3;
Console.WriteLine("vectl*vect3 = " + dot);
}
Запуск этого кода (Vectors2 . cs) даст следующий результат:
vect1 = (1, 1.5, 2)
vect2 =(0, 0, -10 )
vect3 = vectl + vect2 = (1 , 1.5 , -8)
2*vect3 = (2, 3, -16 )
vect3 += vect2 дает (1 , 1.5 , -18)
Присваивание vect3 = vectl*2 дает (2, 3, 4)
vect1*vect3 = 14.5
Вывод показывает, что перегруженные операции выдают корректные результаты, но если посмотреть на тестовый код внимательно, то можно удивиться, заметив, что он использует операцию, которая не была перегружена, а именно операцию сложения с присваиванием, +=.
vect3 += vect2;
Console.WriteLine("vect3+=vect2 дает " + vect3);
Хотя += обычно считается единой операцией, она может быть разбита на два отдельных шага: сложение и присваивание. В отличие от языка C++, в C# на самом деле не разрешено перегружать операцию =, но если вы перегрузите операцию +, то компилятор автоматически использует эту перегрузку и в операции +=. Тот же принцип применим ко всем операциям присваивания, т.е. -=, *=, /=, &= и т.д.
