Перегруженные операции
Определение операций. Конъюнкция и дизъюнкция
После того как объявлены операторы true и false, могут быть объявлены операторные функции — конъюнкция и дизъюнкция. Эти функции работают по следующему принципу:
- после оценки истинности (критерий оценки задается при объявлении операций true и false ) операндов конъюнкции или дизъюнкции функция возвращает ссылку на один из операндов либо на вновь создаваемый в функции объект;
- в соответствии с ранее объявленными операциями true и false, операторные конъюнкция и дизъюнкция возвращают логическое значение, соответствующее отобранному или вновь созданному объекту:
public static Point2D operator | (Point2D par1, Point2D par2) { if (par1) return par1; // Определить "правильность" объекта par1 можем! if (par2) return par2; // Определить "правильность" объекта par2 можем! return new Point2D(10.0F, 10.0F); // Вернули ссылку на // новый "неправильный" объект. } public static Point2D operator & (Point2D par1, Point2D par2) { if (par1 && par2) return par1; // Вернули ссылку на один из "правильных" // объектов. else return new Point2D(10.0F, 10.0F); // Вернули ссылку на // новый "неправильный" объект. }
Выражение вызова операторной функции "дизъюнкция" имеет вид
if (p0 | p1) Console.WriteLine("true!");
А как же || и &&?
Эти операции сводятся к ранее объявленным операторным функциям.
Обозначим символом T тип, в котором была объявлена данная операторная функция.
Если при этом операнды операций && или || являются операндами типа T и для них были объявлены соответствующие операторные функции operator &() и/или operator |(), то для успешной эмуляции операций && или || должны выполняться следующие условия:
- тип возвращаемого значения и типы каждого из параметров данной операторной функции должны быть типа T. Операторные функции operator & и operator |, определенные на множестве операндов типа T, должны возвращать результирующее значение типа T ;
- к результирующему значению применяется объявленная в классе T операторная функция operator true (operator false).
При этом приобретают смысл операции && или ||. Их значение вычисляется в результате комбинации операторных функций operator true() или operator false() со следующими операторными функциями:
- Операция x && y представляется в виде выражения, построенного на основе трехместной операции
T.false(x)? x: T.&(x, y),
где T.false(x) является выражением вызова объявленной в классе операторной функции false, а T.&(x, y) – выражением вызова объявленной в классе T операторной функции &.
Таким образом, сначала определяется "истинность" операнда x, и если значением соответствующей операторной функции является ложь, результатом операции оказывается значение, вычисленное для x. В противном случае определяется "истинность" операнда y, и результирующее значение определяется как КОНЪЮНКЦИЯ истинностных значений операндов x и y.
- Операция x || y представляется в виде выражения, построенного на основе трехместной операции
T.true(x)? x: T.|(x, y),
где T.true(x) является выражением вызова объявленной в классе операторной функции, а T.|(x, y) – выражением вызова объявленной в классе T операторной функции |.
Таким образом, сначала определяется "истинность" операнда x, и если значением соответствующей операторной функции является истина, результатом операции оказывается значение, вычисленное для x. В противном случае определяется "истинность" операнда y, и результирующее значение определяется как ДИЗЪЮНКЦИЯ истинностных значений операндов x и y.
При этом в обоих случаях "истинностное" значение x вычисляется один раз, а значение выражения, представленного операндом y, не вычисляется вообще либо определяется один раз.
И вот результат...
using System; namespace ThisInConstruct { class Point2D { private float x, y, xTmp, yTmp; public float X { get { return x; } } public float PropertyY { get { return y; } set { string ans = null; Console.Write ("Are You sure to change the y value of object of Point2D? (y/n) >> "); ans = Console.ReadLine(); if (ans.Equals("Y") || ans.Equals("y")) { y = value; Console.WriteLine("The value y of object of Point2D changed..."); } } } public Point2D(float xKey, float yKey) { Console.WriteLine("Point2D({0}, {1}) is here!", xKey, yKey); // Какой-нибудь сложный обязательный // код инициализации данных-членов класса. int i = 0; xTmp = xKey; yTmp = yKey; while (i < 100) { x = xKey; y = yKey; i++; } } // А все другие конструкторы в обязательном порядке предполагают // регламентные работы по инициализации значений объекта – и делают при этом // еще много чего... public Point2D():this(0.0F,0.0F) { int i; for (i = 0; i < 100; i++) { // Хорошо, что значения уже проинициализированы! // Здесь своих проблем хватает. } Console.WriteLine("Point2D() is here!"); } public Point2D(Point2D pKey):this(pKey.x, pKey.y) { int i; for (i = 0; i < 100; i++) { // Хорошо, что значения уже проинициализированы! // Здесь своих проблем хватает. } Console.WriteLine("Point2D({0}) is here!", pKey.ToString()); } // Перегруженные операции обязаны возвращать значения! // Операторные функции! Must be declared static and public. // Префиксная и постфиксная формы ++ и –— не различаются по результату // выполнения. Тем не менее они здесь реализуются: // одна как постфиксная… public static Point2D operator ++ (Point2D par) { par.x = (par.xTmp)++; par.y = (par.yTmp)++; return par; } // другая – как префиксная … public static Point2D operator -- (Point2D par) { par.x = --(par.xTmp); par.y = --(par.yPmp); return par; } // Бинарные операции также обязаны возвращать значения! public static Point2D operator + (Point2D par1, Point2D par2) { return new Point2D(par1.x+par2.x,par1.y+par2.y); } // От перемены мест слагаемых сумма ... : // Point2D + float public static Point2D operator + (Point2D par1, float val) { return new Point2D(par1.x+val,par1.y+val); } // float + Point2D public static Point2D operator + (float val, Point2D par1) { return new Point2D(val+par1.x,val+par1.y); } // Перегрузка булевских операторов. Это ПАРНЫЕ операторы. // Объекты типа Point2D приобретают способность судить об истине и лжи! // А что есть истина? Критерии ИСТИННОСТИ (не путать с истиной) // могут быть самые разные. public static bool operator true (Point2D par) { if (Math.Sqrt(par.x*par.x + par.y*par.y) < 10.0) return true; else return false; } public static bool operator false (Point2D par) { double r = (Math.Sqrt(par.x*par.x + par.y*par.y)); if (r > 10.0 || r.Equals(10.0)) return false; else return true; } //======================================================================== public static Point2D operator | (Point2D par1, Point2D par2) { if (par1) return par1; if (par2) return par2; else return new Point2D(10.0F, 10.0F); } public static Point2D operator & (Point2D par1, Point2D par2) { if (par1 && par2) return par1; else return new Point2D(10.0F, 10.0F); } } // Стартовый класс. class C1 { static void Main(string[] args) { Console.WriteLine("__________"); Point2D p0 = new Point2D(); Console.WriteLine("__________"); Point2D p1 = new Point2D(GetVal(), GetVal()); Console.WriteLine("__________"); Point2D p2 = new Point2D(p1); Console.WriteLine("__________"); // Меняем значение y объекта p1... Console.WriteLine("**********"); p1.PropertyY = GetVal(); Console.WriteLine("**********"); Console.WriteLine("p0.x == {0}, p0.y == {1}", p0.X,p0.PropertyY); Console.WriteLine("p1.x == {0}, p1.y == {1}", p1.X,p1.PropertyY); Console.WriteLine("p2.x == {0}, p2.y == {1}", p2.X,p2.PropertyY); Console.WriteLine("##########"); p0++; ++p1; // После объявления операторных функций true и false объекты класса Point2D // могут включаться в контекст условного оператора и оператора цикла. if (p1) Console.WriteLine("true!"); else Console.WriteLine("false!"); // Конъюнкции и дизъюнкции. Выбирается объект p0 или p1, // для которого вычисляется истинностное значение. if (p0 | p1) Console.WriteLine("true!"); if (p0 & p1) Console.WriteLine("true!"); if (p0 || p1) Console.WriteLine("true!"); if (p0 && p1) Console.WriteLine("true!"); for ( ; p2; p2++) { Console.WriteLine("p2.x == {0}, p2.y == {1}", p2.X,p2.PropertyY); } Console.WriteLine("p0.x == {0}, p0.y == {1}", p0.X,p0.PropertyY); Console.WriteLine("p1.x == {0}, p1.y == {1}", p1.X,p1.PropertyY); Console.WriteLine("p2.x == {0}, p2.y == {1}", p2.X,p2.PropertyY); } public static float GetVal() { float fVal; string str = null; while (true) { try { Console.Write("float value, please >> "); str = Console.ReadLine(); fVal = float.Parse(str); return fVal; } catch { Console.WriteLine("This is not a float value..."); } } } } }Листинг 6.3.
Пример. Свойства и индексаторы
using System; namespace PointModel { class SimplePoint { float x, y; public SimplePoint[] arraySimplePoint = null; public SimplePoint() { x = 0.0F; y = 0.0F; } public SimplePoint(float xKey, float yKey): this() { x = xKey; y = yKey; } public SimplePoint(SimplePoint PointKey):this(PointKey.x, PointKey.y) { } public SimplePoint(int N) { int i; if (N > 0 && arraySimplePoint == null) { arraySimplePoint = new SimplePoint[N]; for (i = 0; i < N; i++) { arraySimplePoint[i] = new SimplePoint((float)i, (float)i); } } } } class MyPoint { float x, y; MyPoint[] arrayMyPoint = null; int arrLength = 0; int ArrLength { get { return arrLength; } set { arrLength = value; } } bool isArray = false; bool IsArray // Это свойство только для чтения! { get { return isArray; } } MyPoint() { x = 0.0F; y = 0.0F; } public MyPoint(float xKey, float yKey): this() { x = xKey; y = yKey; } public MyPoint(MyPoint PointKey): this(PointKey.x, PointKey.y) { } public MyPoint(int N) { int i; if (N > 0 && IsArray == false) { this.isArray = true; this.arrLength = N; arrayMyPoint = new MyPoint[N]; for (i = 0; i < N; i++) { arrayMyPoint[i] = new MyPoint((float)i, (float)i); } } } bool InArray(int index) { if (!IsArray) return false; if (index >= 0 && index < this.ArrLength) return true; else return false; } // Внимание! Объявление индексатора. // Индексатор НЕ является операторной функцией. // Он объявляется как НЕСТАТИЧЕСКОЕ множество операторов // со стандартным заголовком. При его объявлении используется // ключевое слово this. public MyPoint this[int index] { get { if (IsArray == false) return null; if (InArray(index)) return arrayMyPoint[index]; else return null; } set { if (IsArray == false) return; if (InArray(index)) { arrayMyPoint[index].x = value.x; arrayMyPoint[index].y = value.y; } else return; } } // Объявление еще одного (перегруженного!) индексатора. // В качестве значения для индексации (параметра индексации) // используется символьная строка. public MyPoint this[string strIndex] { get { int index = int.Parse(strIndex); if (IsArray == false) return null; if (InArray(index)) return arrayMyPoint[index]; else return null; } set { int index = int.Parse(strIndex); if (IsArray == false) return; if (InArray(index)) { arrayMyPoint[index].x = value.x; arrayMyPoint[index].y = value.y; } else return; } } } class Class1 { static void Main(string[] args) { SimplePoint spArr = new SimplePoint(8); SimplePoint wsp = new SimplePoint(3.14F, 3.14F); SimplePoint wsp0; spArr.arraySimplePoint[3] = wsp; try { spArr.arraySimplePoint[125] = wsp; } catch (IndexOutOfRangeException exc) { Console.WriteLine(exc); } try { wsp.arraySimplePoint[7] = wsp; } catch (NullReferenceException exc) { Console.WriteLine(exc); } try { wsp0 = spArr.arraySimplePoint[125]; } catch (IndexOutOfRangeException exc) { Console.WriteLine(exc); } wsp0 = spArr.arraySimplePoint[5]; MyPoint mpArr = new MyPoint(10); MyPoint wmp = new MyPoint(3.14F, 3.14F); MyPoint wmp0; mpArr[3] = wmp; mpArr[125] = wmp; wmp[7] = wmp; wmp0 = mpArr[125]; wmp0 = mpArr[5]; // В качестве индексатора используются строковые выражения. wmp0 = mpArr["5"]; // В том числе использующие конкатенацию строк. wmp0 = mpArr["1"+"2"+"5"]; } } }Листинг 6.4.
explicit и implicit. Преобразования явные и неявные
Возвращаемся к проблеме преобразования значений одного типа к другому, что, в частности, актуально при выполнении операций присваивания. И если для элементарных типов проблема преобразования решается (с известными ограничениями, связанными с "опасными" и "безопасными" преобразованиями), то для вновь объявляемых типов алгоритмы преобразования должны реализовываться разработчиками этих классов.
Логика построения явных и неявных преобразователей достаточно проста. Программист самостоятельно принимает решение относительно того:
- каковым должен быть алгоритм преобразования;
- будет ли этот алгоритм выполняться неявно или необходимо будет явным образом указывать на соответствующее преобразование.
Ниже рассматривается пример, содержащий объявления классов Point2D и Point3D. В классах предусмотрены алгоритмы преобразования значений от одного типа к другому, которые активизируются при выполнении операций присвоения:
using System; // Объявления классов. class Point3D { public int x,y,z; public Point3D() { x = 0; y = 0; z = 0; } public Point3D(int xKey, int yKey, int zKey) { x = xKey; y = yKey; z = zKey; } // Операторная функция, в которой реализуется алгоритм преобразования // значения типа Point2D в значение типа Point3D. Это преобразование // осуществляется НЕЯВНО. public static implicit operator Point3D(Point2D p2d) { Point3D p3d = new Point3D(); p3d.x = p2d.x; p3d.y = p2d.y; p3d.z = 0; return p3d; } } class Point2D { public int x,y; public Point2D() { x = 0; y = 0; } public Point2D(int xKey, int yKey) { x = xKey; y = yKey; } // Операторная функция, в которой реализуется алгоритм преобразования // значения типа Point3D в значение типа Point2D. Это преобразование // осуществляется с ЯВНЫМ указанием необходимости преобразования. // Принятие решения относительно присутствия в объявлении ключевого // слова explicit вместо implicit оправдывается тем, что это // преобразование сопровождается потерей информации. Существует мнение, // что об этом обстоятельстве программисту следует напоминать всякий раз, // когда он в программном коде собирается применить данное преобразование. public static explicit operator Point2D(Point3D p3d) { Point2D p2d = new Point2D(); p2d.x = p3d.x; p2d.y = p3d.y; return p2d; } } // Тестовый класс. Здесь все происходит. class TestClass { static void Main(string[] args) { Point2D p2d = new Point2D(125,125); Point3D p3d; // Сейчас это только ссылка! // Этой ссылке присваивается значение в результате // НЕЯВНОГО преобразования значения типа Point2D к типу Point3D p3d = p2d; // Изменили значения полей объекта. p3d.x = p3d.x*2; p3d.y = p3d.y*2; p3d.z = 125; // Главное – появилась новая информация, // которая будет потеряна в случае присвоения значения типа Point3D // значению типа Point2D. // Ключевое слово explicit в объявлении соответствующего // метода преобразования вынуждает программиста подтверждать, // что он в курсе возможных последствий этого преобразования. p2d = (Point2D)p3d; } }Листинг 6.5.