Кратко съдържание


Предефиниране на оператори



страница27/73
Дата21.07.2018
Размер9.03 Mb.
#76887
1   ...   23   24   25   26   27   28   29   30   ...   73

Предефиниране на оператори


Както и в други обектно-ориентирани езици (например C++), в C# някои опе­ра­то­ри могат да бъдат предефинирани. Могат да се предефинират унар­ни (приемащи един аргумент) и бинарни (приемащи два аргумента) опе­ра­то­ри, действащи върху дефинирани от потребителя типове. Пре­де­фи­нирането на оператори се извършва, като разработчикът предоставя собствена имплементация за действието на вградените оператори върху дефинираните от него типове.

Приоритет и асоциативност


Операторите, освен с броя на аргументите си, се характеризират с при­о­ри­тет и асоциативност. Когато се съставят изрази, съдържащи при­ла­га­не на повече от един оператор, редът на прилагането им се определя от при­оритета – операторите се прилагат в реда на намаляване на при­о­ри­те­та им. Нека например разгледаме израза a*b+c. В този случай умноже­ни­е­то ще се извърши преди събирането, тъй като е с по-висок приоритет, т.е. ако a=1, b=2 и c=5 резултатът ще бъде 7.

Ако имаме израз, който прилага много пъти един и същ оператор, редът на прилагането на тези оператори не може да се определи с помощта на при­оритета им. В такъв случай той зависи от асоциативността, която може да бъде лява и дясна. Например, ако имаме израза 1024 / 128 / 8, Ре­зул­татът от този израз е 1, тъй като операторът / е лявоасоциативен, т.е. се прилага от ляво на дясно.

В .NET Framework може да се предефинира действието на операторите върху де­фи­ни­раните от потребителя типове, но не и техните приоритет и асо­ци­а­тив­ност.

Операторите в C#


По долу даден е списък с всички оператори в C#, изреден по ред на при­о­ри­те­та им, намаляващ от ляво надясно и отгоре надолу:

  • основни: (x) x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked

  • унарни: + - ! ++x --x (T)x

  • мултипликативни: * / %

  • адитивни: + -

  • побитови (bitshift): << >>

  • за сравнение: < > <= >= is as

  • за равенство: == !=

  • логически: & ^ |

  • условни: && || c?x:y

  • за присвояване: = += -= *= /= %= <<= >>= &= ^= |=

Предефинируеми оператори


Не всички оператори в C# могат да се предефинират. Предефинируеми са унарните оператори +, -, !, ~, ++, --, true и false, бинарните +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >= и <=, и операторите за яв­но (имплицитно) и неявно (експлицитно) преобразуване на типове.

Дефиницията на предефиниран оператор в C# представлява дефиниция на статичен метод, приемащ един или два параметъра и връщащ някакъв резултат, към който е указана ключовата дума operator.


Предефиниране на оператори – пример


За да илюстрираме предефинирането на оператори ще дефинираме тип "обикновена дроб" (Fraction), който съдържа в себе си обикновена дроб (съставена от числител и знаменател). Ще предефинираме всички основни математи­чески операции за работа с обикновени дроби (събиране, изваж­дане, умножение, деление и т.н.), както и някои други оператори, които улесняват работата с типа Fraction. Ето една примерна реализация:

FractionsTest.cs

public struct Fraction

{

private long mNumerator;



private long mDenominator;
public Fraction(long aNumerator, long aDenominator)

{

// Cancel the fraction and make the denominator positive



long gcd = GreatestCommonDivisor( aNumerator, aDenominator);

mNumerator = aNumerator / gcd;

mDenominator = aDenominator / gcd;
if (mDenominator < 0)

{

mNumerator = -mNumerator;



mDenominator = -mDenominator;

}

}


private static long GreatestCommonDivisor(

long aNumber1, long aNumber2)

{

aNumber1 = Math.Abs(aNumber1);



aNumber2 = Math.Abs(aNumber2);

while (aNumber1 > 0)

{

long newNumber1 = aNumber2 % aNumber1;



aNumber2 = aNumber1;

aNumber1 = newNumber1;

}

return aNumber2;



}
public static Fraction operator +(Fraction aF1, Fraction aF2)

{

long num = aF1.mNumerator*aF2.mDenominator +



aF2.mNumerator*aF1.mDenominator;

long denom = aF1.mDenominator*aF2.mDenominator;

return new Fraction(num, denom);

}
public static Fraction operator -(Fraction aF1, Fraction aF2)

{

long num =



aF1.mNumerator*aF2.mDenominator -

aF2.mNumerator*aF1.mDenominator;

long denom = aF1.mDenominator*aF2.mDenominator;

return new Fraction(num, denom);

}
public static Fraction operator *(Fraction aF1, Fraction aF2)

{

long num = aF1.mNumerator*aF2.mNumerator;



long denom = aF1.mDenominator*aF2.mDenominator;

return new Fraction(num, denom);

}
public static Fraction operator /(Fraction aF1, Fraction aF2)

{

long num = aF1.mNumerator*aF2.mDenominator;



long denom = aF1.mDenominator*aF2.mNumerator;

return new Fraction(num, denom);

}
// Unary minus operator

public static Fraction operator -(Fraction aFrac)

{

long num = -aFrac.mNumerator;



long denom = aFrac.mDenominator;

return new Fraction(num, denom);

}
// Explicit conversion to double operator

public static explicit operator double(Fraction aFrac)

{

return (double) aFrac.mNumerator / aFrac.mDenominator;



}
// Operator ++ (the same for prefix and postfix form)

public static Fraction operator ++(Fraction aFrac)

{

long num = aFrac.mNumerator + aFrac.mDenominator;



long denom = aFrac.mDenominator;

return new Fraction(num, denom);

}
// Operator -- (the same for prefix and postfix form)

public static Fraction operator --(Fraction aFrac)

{

long num = aFrac.mNumerator - aFrac.mDenominator;



long denom = aFrac.mDenominator;

return new Fraction(num, denom);

}
public static bool operator true(Fraction aFraction)

{

return aFraction.mNumerator != 0;



}
public static bool operator false(Fraction aFraction)

{

return aFraction.mNumerator == 0;



}
public static implicit operator Fraction(double aValue)

{

double num = aValue;



long denom = 1;

while (num - Math.Floor(num) > 0)

{

num = num * 10;



denom = denom * 10;

}

return new Fraction((long)num, denom);



}
public override string ToString()

{

if (mDenominator != 0)



{

return String.Format("{0}/{1}",

mNumerator, mDenominator);

}

else



{

return ("NaN"); // not a number

}

}

}


class FractionsTest

{

static void Main()



{

Fraction f1 = (double)1/4;

Console.WriteLine("f1 = {0}", f1);

Fraction f2 = (double)7/10;

Console.WriteLine("f2 = {0}", f2);

Console.WriteLine("-f1 = {0}", -f1);

Console.WriteLine("f1 + f2 = {0}", f1 + f2);

Console.WriteLine("f1 - f2 = {0}", f1 - f2);

Console.WriteLine("f1 * f2 = {0}", f1 * f2);

Console.WriteLine("f1 / f2 = {0}", f1 / f2);

Console.WriteLine("f1 / f2 as double = {0}",

(double)(f1 / f2));

Console.WriteLine(

"-(f1+f2)*(f1-f2/f1) = {0}", -(f1+f2)*(f1-f2/f1));

}

}


Горният пример дефинира клас, представляващ обвивка на обикновена дроб, или иначе казано, той моделира множеството на рационалните числа. За да мо­гат обектите от типа Fraction действително да имат пове­дение като на числа, той пре­дефинира унарните оператори -, ++, --, true, false и би­нар­ните +, -, * и /.

Забелязваме също предефинирането на явно преобразуване от Fraction към double и имплицитно от double към Fraction. Добре е да обърнем вни­мание на това, че вида на преобразуването не е избран случайно. Яв­но преобразуване се дефинира, когато имаме конвертиране със загуба, тъй като изисква изрично упоменаване на преобразованието. В горния при­мер конвертирането към double е такова, защото някои рационални числа не могат да бъдат представени с плаваща запетая без загуба на точност. Ако дефинираме преобразуването към double като имплицитно би било възможно по не­вни­мание да присвоим дроб на число с плаваща запетая, но като изиск­ва­ме изрично преобразуване компилаторът не допуска потенциално опас­на­та операция. Тъй като конвертирането на число с плаваща запетая към ра­ционално винаги може да се извърши без загуба няма нужда да го опре­деляме като явно.

Виждаме, че е допустимо и предефинирането на операторите true и false. Това позволява използването на инстанции от тип Fraction в булеви из­рази. Най-лесно това може да се илюстрира с един прост пример. Нека раз­гледаме следната модифицирана версия на метода ToString() на Fraction:


public override string ToString()

{

if (this)



{

return String.Format("{0}/{1}", mNumerator, mDenominator);

}

else


{

return ("0");

}

}

Така промененият метод, освен че връща текстовото представяне на дробта, също и проверява дали тя е нулева дроб и стойност 0 в този случай. При изчисляването на стойността на булевият израз (this) се изпълнява тялото на предефинирания оператор true.


Проследяване на изпълнението на предефинирани оператори


Използвайки дебъгера на Visual Studio .NET ще проследим изпълнението на кода от примера. За целта:

  1. Отваряме приложението Demo-6-Operators.sln и го компилираме.

  2. С [F11] стартираме програмата в режим на проследяване и мар­керът се позиционира на първия ред, където присвояваме стойност от тип double към обекта f1 от клас Fraction:



  1. Когато още веднъж натиснем [F11], забелязваме, че при това при­сво­я­ва­не по премълчаване се изпълнява предефинираният опера­тор за им­плицитно преобразуване и ходът на изпълнение на прог­ра­мата про­дължава в тялото на неговата дефиниция:



  1. Продължаваме с [F11] да проследяваме изпълнението на програ­мата и виждаме как при изпълняването на всяка операция с обек­тите от тип Fraction се изпълнява кода на съответните преде­фини­рани оператори.




Сподели с приятели:
1   ...   23   24   25   26   27   28   29   30   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница