Перегруженные операции
Определение операций. Конъюнкция и дизъюнкция
После того как объявлены операторы 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.