Российский Государственный Технологический Университет им. К.Э. Циолковского
Опубликован: 02.03.2007 | Доступ: свободный | Студентов: 5678 / 802 | Оценка: 3.96 / 3.45 | Длительность: 27:04:00
ISBN: 978-5-9556-0086-4
Лекция 6:

Перегруженные операции

< Лекция 5 || Лекция 6: 123 || Лекция 7 >

Определение операций. Конъюнкция и дизъюнкция

После того как объявлены операторы 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() со следующими операторными функциями:

  1. Операция x && y представляется в виде выражения, построенного на основе трехместной операции
    T.false(x)? x: T.&(x, y),

    где T.false(x) является выражением вызова объявленной в классе операторной функции false, а T.&(x, y) – выражением вызова объявленной в классе T операторной функции &.

    Таким образом, сначала определяется "истинность" операнда x, и если значением соответствующей операторной функции является ложь, результатом операции оказывается значение, вычисленное для x. В противном случае определяется "истинность" операнда y, и результирующее значение определяется как КОНЪЮНКЦИЯ истинностных значений операндов x и y.

  2. Операция 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.
< Лекция 5 || Лекция 6: 123 || Лекция 7 >
kewezok kewezok
kewezok kewezok
Елена Шляхт
Елена Шляхт
Объясните плиз в чем отличие а++ от ++а
Почему результат разный?
int a=0, b=0;
Console.WriteLine(a++); //0
Console.WriteLine(++b); //1
a++;
++b;
Console.WriteLine(a); //2
Console.WriteLine(b); //2