Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ооп теория

.pdf
Скачиваний:
21
Добавлен:
14.02.2015
Размер:
3.58 Mб
Скачать

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("r9: (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)

301

{

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 r1, 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()

{

302

Rational

r1=new

Rational(1,2),

r2

=

new

Rational(1,3);

 

 

 

 

 

Rational r3, r4, r5, r6 ;

 

 

 

r3 = r1r2; 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)");

 

 

 

r5.PrintRational("r5: (r1/r2)"); r6.PrintRational("r6: (r3+r4*r5)");

}

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

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

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

Рассмотрим важную проблему определения констант в собственном классе. Определим две константы 0 и 1 класса Rational. Кажется, что сделать это невозможно из-за ограничений, накладываемых на объявление констант.

303

Напомню, константы должны быть инициализированы в момент объявления,

и их значения должны быть заданы константными выражениями, известными в момент компиляции. Но в момент компиляции у класса Rational нет никаких известных константных выражений. Как же быть? Справиться с проблемой поможет статический конструктор, созданный для решения подобных задач. Роль констант класса будут играть статические поля,

объявленные с атрибутом readonly, то есть доступные только для чтения. Нам также будет полезен закрытый конструктор класса. Еще укажем, что введение констант класса требует использования экзотических средств языка

C#. Вначале определим закрытый конструктор:

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

{

m = a; n = b;

}

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

задаются статическими полями с атрибутом readonly:

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

public static readonly Rational Zero, One;

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

значения констант:

static Rational()

{

304

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 r1, double r2)

{

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

}

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

{

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

}

305

Наш последний пример демонстрирует работу с константами,

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

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

306

Тема 17. СТРУКТУРЫ И ПЕРЕЧИСЛЕНИЯ СОДЕРЖАНИЕ ЛЕКЦИИ:

РАЗВЕРНУТЫЕ И ССЫЛОЧНЫЕ ТИПЫ

o КЛАССЫ И СТРУКТУРЫ

СТРУКТУРЫ

o СИНТАКСИС СТРУКТУР

o КЛАСС RATIONAL ИЛИ СТРУКТУРА RATIONAL

oВСТРОЕННЫЕ СТРУКТУРЫ

ЕЩЕ РАЗ О ДВУХ СЕМАНТИКАХ ПРИСВАИВАНИЯ

ПЕРЕЧИСЛЕНИЯ

o ПЕРСОНЫ И ПРОФЕССИИ

РАЗВЕРНУТЫЕ И ССЫЛОЧНЫЕ ТИПЫ

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

T x = new T();

Напомню, как выполняется этот оператор. В памяти создается объект типа T,

основанного на классе T, и сущность x связывается с этим объектом.

Сущность, не прошедшая инициализацию (явную или неявную), не связана ни с одним объектом, а потому не может использоваться в вычислениях - у

нее нет полей, хранящих значения, она не может вызывать методы класса.

Объектам нужна память, чтобы с ними можно было работать. Есть две классические стратегии выделения памяти и связывания объекта,

создаваемого в памяти, и сущности, объявленной в тексте.

Определение 1. Класс T относится к развернутому типу, если память отводится сущности x; объект разворачивается на памяти, жестко связанной с сущностью.

307

Определение 2. Класс T относится к ссылочному типу, если память отводится объекту; сущность x является ссылкой на объект.

Для развернутого типа характерно то, что каждая сущность ни с кем не разделяет свою память; сущность жестко связывается со своим объектом. В

этом случае сущность и объект можно и не различать, они становятся неделимым понятием. Для ссылочных типов ситуация иная - несколько сущностей могут ссылаться на один и тот же объект. Такие сущности разделяют память и являются разными именами одного объекта. Полезно понимать разницу между сущностью, заданной ссылкой, и объектом, на который в текущий момент указывает ссылка.

Развернутые и ссылочные типы порождают две различные семантики присваивания - развернутое присваивание и ссылочное присваивание.

Рассмотрим присваивание:

y = x;

Когда сущность y и выражение x принадлежат развернутому типу, то при присваивании изменяется объект. Значения полей объекта, связанного с сущностью y, изменяются, получая значения полей объекта, связанного с x.

Когда сущность y и выражение x принадлежат ссылочному типу, то изменяется ссылка, но не объект. Ссылка y получает значение ссылки x, и обе они после присваивания указывают на один и тот же объект.

Язык программирования должен позволять программисту в момент определения класса указать, к развернутому или ссылочному типу относится класс. К сожалению, язык C# не позволяет этого сделать напрямую - в нем у класса нет модификатора, позволяющего задать развернутый или ссылочный тип. Какие же средства языка позволяют частично решить эту важную задачу? В лекции 3, где рассматривалась система типов языка C#,

308

отмечалось, что все типы языка делятся на ссылочные и значимые. Термин

"значимый" является синонимом термина "развернутый". Беда только в том,

что деление на значимые и ссылочные типы предопределено языком и не управляется программистом. Напомню, к значимым типам относятся все встроенные арифметические типы, булев тип, структуры; к ссылочным типам

- массивы, строки, классы. Так можно ли в C# спроектировать свой собственный класс так, чтобы он относился к значимым типам? Ответ на этот вопрос положительный, хотя и с рядом оговорок. Для того чтобы класс отнести к значимым типам, его нужно реализовать как структуру.

КЛАССЫ И СТРУКТУРЫ

Структура - это частный случай класса. Исторически структуры используются в языках программирования раньше классов. В языках PL/1, C

и Pascal они представляли собой только совокупность данных (полей класса),

но не включали ни методов, ни событий. В языке С++ возможности структур были существенно расширены и они стали настоящими классами, хотя и c

некоторыми ограничениями. В языке C# - наследнике С++ - сохранен именно такой подход к структурам.

Чем следует руководствоваться, делая выбор между структурой и классом?

Полагаю, можно пользоваться следующими правилами:

если необходимо отнести класс к развернутому типу, делайте его структурой;

если у класса число полей относительно невелико, а число возможных объектов относительно велико, делайте его структурой. В этом случае память объектам будет отводиться в стеке, не будут создаваться лишние ссылки, что позволит повысить эффективность работы;

в остальных случаях проектируйте настоящие классы.

309

Поскольку на структуры накладываются дополнительные ограничения, то может возникнуть необходимость в компромиссе - согласиться с ограничениями и использовать структуру либо пожертвовать развернутостью и эффективностью и работать с настоящим классом. Стоит отметить: когда говорится, что все встроенные типы - int и другие - представляют собой классы, то, на самом деле, речь идет о классах, реализованных в виде структур.

Структуры

Рассмотрим теперь более подробно вопросы описания структур, их синтаксиса, семантики и тех особенностей, что отличают их от классов.

Синтаксис структур

Синтаксис объявления структуры аналогичен синтаксису объявления класса:

[атрибуты][модификаторы]struct имя_структуры[:список_интерфейсов] {тело_структуры}

Какие изменения произошли в синтаксисе в сравнении с синтаксисом класса,

описанным в лекции 16? Их немного. Перечислим их:

ключевое слово class изменено на слово struct;

список родителей, который для классов, наряду с именами интерфейсов, мог включать имя родительского класса, заменен списком интерфейсов. Для структур не может быть задан родитель

(класс или структура). Заметьте, структура может наследовать интерфейсы;

для структур неприменимы модификаторы abstract и sealed. Причиной является отсутствие механизма наследования.

310