Добавить в цитаты Настройки чтения

Страница 307 из 371



     r10 = r1+r2; r11 = r3+r4; r12 = r5 = r6 = r10 = r11;

     r1.PrintRational("r1:(0,0)"); r2.PrintRational("r2:(1,1)");

     r3.PrintRational("r3:(10,8)"); r4.PrintRational("r4:(2,6)");

     r5.PrintRational("r5:(4,-12); r6.PrintRational ("r6:(-12,-14)");

     r7.PrintRational("r7:(r1+r2)" r8.PrintRational ("r8:(r3+r4)");

     r9.PrintRational("9:(r5+r6)"); r10.PrintRational ("r10:(r1+r2)");

     r11.PrintRational("r11:(r3+r4)");

     r12.PrintRational("r12:(r5+r6+r10+r11)");

}

Обратите внимание на вычисление r12; здесь ощутимо видно преимущество операций, позволяющих записывать сложные выражения в простой форме. Результаты вычислений показаны на рис. 16.4.

Аналогичным образом определим остальные операции над рациональными числами:

public Rational Minus(Rational a)

{

     int u,v;

     u = m*a.n — n*a.m; v= n*a.n;

     return (new Rational(u, v));

}//Minus public static Rational operator — (Rational r1, Rational r2)

{

     return (r1.Minus(r2));

}

public Rational Mult(Rational a)

{

     int u,v;

     u = m*a.m; v= n*a.n;

     return (new Rational(u, v));

}//Mult

public static Rational operator *(Rational rl, Rational r2)

{

     return (r1.Mult(r2));

}

public Rational Divide(Rational a)

{

      int u,v;

      u = m*a.n; v= n*a.m;

      return (new Rational(u, v));

}//Divide

public static Rational operator /(Rational r1, Rational r2)

{

      return (r1.Divide(r2));

}

Вот тест, проверяющий работу этих операций'.

public void TestOperRational()

{

     Rational r1=new Rational(1,2), r2 = new Rational(1,3);

     Rational r3, r4, r5, r6;

     r3 = r1-r2; r4=r1*r2; r5=r1/r2; r6=r3+r4*r5;

     r1.PrintRational("r1: (1,2)"); r2.PrintRational("r2: (1,3)");

     r3.PrintRational("r3: (r1-r2)"); r4.PrintRational("r4: (r1*r2)")

     г5.PrintRational("r5: (r1/r2)");

     г6. PrintRational("r6: (r3+r4*r5)");

}

Результаты работы этого теста показаны на рис. 16.5. Обратите внимание: при перегрузке операций сохраняется общепринятый приоритет операций. Поэтому при вычислении выражения r3+r4*r5 вначале будет выполняться умножение рациональных чисел, а потом уже сложение.

Рис. 16.5. Операции и выражения над рациональными числами

Константы класса Rational

Рассмотрим важную проблему определения констант в собственном классе. Определим две константы 0 и 1 класса Rational. Кажется, что сделать это невозможно из-за ограничений, накладываемых на объявление констант. Напомню, константы должны быть инициализированы в момент объявления, и их значения должны быть заданы константными выражениями, известными в момент компиляции. Но в момент компиляции у класса Rational нет никаких известных константных выражений. Как же быть? Справиться с проблемой поможет статический конструктор, созданный для решения подобных задач. Роль констант класса будут играть статические поля, объявленные с атрибутом readonly, то есть доступные только для чтения. Нам также будет полезен закрытый конструктор класса. Еще укажем, что введение констант класса требует использования экзотических средств языка С#. Вначале определим закрытый конструктор:

private Rational(int a, int b, string t)

{

      m = a; n = b;

}





He забудем, что при перегрузке методов (в данном случае конструкторов) сигнатуры должны различаться, и поэтому пришлось ввести дополнительный аргумент t для избежания конфликтов. Поскольку конструктор закрытый, то гарантируется корректное задание аргументов при его вызове. Определим теперь константы класса, которые, как я уже говорил, задаются статическими полями с атрибутом readonly:

//Константы класса 0 и 1 — Zero и One

       public static readonly Rational Zero, One;

А теперь зададим статический конструктор, в котором определяются значения констант:

static Rational()

{

     Console.WriteLine("static constructor Rational");

     Zero = new Rational(0, 1, "private");

     One = new Rational (1, 1, "private");

}//Статический конструктор

Как это все работает? Статический конструктор вызывается автоматически один раз до начала работы с объектами класса. Он и задаст значения статических полей Zero, One, представляющих рациональные числа с заданным значением. Поскольку эти поля имеют атрибут static и readonly, то они доступны для всех объектов класса и не изменяются в ходе вычислений, являясь настоящими константами класса. Прежде чем привести пример работы с константами, давайте добавим в наш класс важные булевы операции над рациональными числами — равенство и неравенство, больше и меньше. При этом две последние операции сделаем перегруженными, позволяя сравнивать рациональные числа с числами типа double:

public static bool operator ==(Rational r1, Rational r2)

}

     return ((r1.m==r2.m) & & (r1.n==r2.n));

}

     public static bool operator!=(Rational r1, Rational r2)

}

     return ((r1.m! =r2.m) || (r1.n!=r2.n));

}

     public static bool operator <(Rational r1, Rational r2)

}

     return (r1.m*r2. n < r2.m* r1.n);

}

     public static bool operator >(Rational r1, Rational r2)

}

     return (r1.m * r2.n > r2.m* r1.n);

}

     public static bool operator <(Rational rl, double r2)

{

     return((double)r1.m / (double)rl.n < r2);

}

     public static bool operator >(Rational rl, double r2)

{

     return((double)r1.m / (double)r1.n > r2);

}

Наш последний пример демонстрирует работу с константами, булевыми и арифметическими выражениями над рациональными числами:

public void TestRationalConst()

{

     Rational r1 = new Rational(2,8), r2 =new Rational(2,5);

     Rational r3 = new Rational(4, 10), r4 = new Rational(3,7);

     Rational r5 = Rational.Zero, r6 = Rational.Zero;

     if ((r1!= Rational.Zero) && (r2 == r3)) r5 =(r3+Rational.One)*r4;

     r6 = Rational.One + Rational.One;

     r1.PrintRational ("r1: (2,8)");

     r2.PrintRational ("r2: (2,5)");

     r3.PrintRational ("r3: (4,10)");

     r4.PrintRational ("r4: (3,7)");

     r5.PrintRational ("r5: ((r3 +1)*r4)");

     r6.PrintRational ("r6: (1 + 1)");

}

Результаты работы этого примера показаны на рис. 16.6.

Рис. 16.6. Константы и выражения типа Rational

17. Структуры и перечисления

Понятие развернутого и ссылочного типа. Структуры — реализация развернутых классов. Синтаксис структур. Сравнение структур и классов. Встроенные структуры. Перечисление — частный случай класса. Особенности перечислений. Примеры.

Развернутые и ссылочные типы

Рассмотрим объявление объекта класса T с инициализацией:

Т х = new Т();

Напомню, как выполняется этот оператор. В памяти создается объект типа T, основанного на классе T, и сущность х связывается с этим объектом. Сущность, не прошедшая инициализацию (явную или неявную), не связана ни с одним объектом, а потому не может использоваться в вычислениях — у нее нет полей, хранящих значения, она не может вызывать методы класса. Объектам нужна память, чтобы с ними можно было работать. Есть две классические стратегии выделения памяти и связывания объекта, создаваемого в памяти, и сущности, объявленной в тексте.