Проект к данной лекции Вы можете скачать здесь.
В ООП главная роль, которую играют классы, состоит в задании типа данных. В этой лекции, как и во многих других, говоря о классах, будем иметь в виду именно эту роль. Хотя следует помнить, что некоторые классы могут играть только одну роль - роль модуля, и для них невозможно создавать объекты. Такие сервисные классы подробно рассматривались в предыдущей лекции, а примеры таких классов появлялись многократно.
Итак, будем рассматривать классы, задающие тип данных. Такой класс Т представляет описание множества объектов - экземпляров класса, задавая их свойства и поведение. Если класс представляет собой текст - статическое описание, то объекты класса создаются динамически в процессе работы программы. Как правило, объекты класса Т создаются в других классах, являющихся клиентами класса Т. Рассмотрим в клиентском классе объявление объекта класса T, выполненное с инициализацией:
T x = new T();
Напомню, как выполняется этот оператор, создающий объект класса Т. Объектам нужна память, чтобы с ними можно было работать. Рассмотрим две классические стратегии выделения памяти и stack ) и куча ( ). Сущности x в стеке всегда отводится память, но какая память - зависит от того, к развернутому или ссылочному типу относится класс Т.
Определение 1.Если класс T относится к развернутому типу, то в стеке сущности x отводится память, необходимая объекту класса T. Говорят, что объект разворачивается на памяти, жестко связанной с сущностью x. Эту память сущность x ни с кем не разделяет.
Определение 2.Если класс T относится к ссылочному типу, то память объекту отводится в куче, а в стеке сущности x отводится дополнительная память, содержащая ссылку на объект.
Для T. Более того, типы объектов, с которыми динамически связывается сущность, могут быть различными, хотя и требуется определенное
Развернутые и ссылочные типы порождают две различные семантики. Рассмотрим присваивание:
y = x;
Когда сущность y принадлежит y, при присваивании изменяются, получая значения полей объекта, связанного с x. Напомним, что поля y хранятся в стеке.
Когда сущность y принадлежит ссылочному типу, происходит присваивание ссылок. Ссылка y получает значение ссылки x, и обе они после присваивания указывают на один и тот же объект. Объект в куче, на который до присваивания указывала ссылка y, теряет одну из своих ссылок и, возможно, становится "висячим" объектом без ссылок, становясь добычей для сборщика мусора.
Рассмотрим операцию проверки объектов на эквивалентность:
if (x == y)
Для x и y эквивалентны, если эквивалентны значения всех их полей. Для ссылочных типов объекты x и y эквивалентны, если эквивалентны значения ссылок.
Язык программирования должен позволять программисту в момент определения класса указать, к развернутому или ссылочному типу относится класс. В языке C# это делается следующим образом. Если объявление класса задается с использованием служебного слова class, то такой класс относится к ссылочным типам.
public class A { ... }
Если объявление класса задается с использованием служебного слова struct, то такой класс относится к развернутым типам, которые также называют value типами.
public struct B { ... }
Напомню: к
Структура - это частный случай класса. Исторически структуры используются в языках программирования раньше классов. В языках PL/1, C, Pascal они представляли собой только совокупность данных (полей класса), но не включали ни методов, ни событий. В языке С++ возможности структур были существенно расширены, и они стали настоящими классами, хотя и c некоторыми ограничениями. В языке C# сохранен именно такой подход к структурам.
Чем следует руководствоваться, делая выбор между структурой и классом? Полагаю, можно пользоваться следующими правилами:
Поскольку на структуры накладываются дополнительные ограничения, может возникнуть необходимость в компромиссе - согласиться с ограничениями и использовать структуру либо пожертвовать развернутостью и эффективностью и работать с настоящим классом. Стоит отметить: когда говорится, что встроенные типы - int и другие - представляют собой классы, то, на самом деле, речь идет о классах, реализованных в виде структур.
Рассмотрим теперь более подробно вопросы описания структур, их синтаксиса, семантики и тех особенностей, что отличают их от классов.
[атрибуты][модификаторы]struct имя_структуры[:список_интерфейсов]
{тело_структуры}
Какие изменения произошли в синтаксисе заголовка структуры в сравнении с синтаксисом класса? Их немного. Перечислим их:
class изменено на слово struct ;abstract и sealed. Причиной является отсутствие механизма наследования.Все, что может быть вложено в тело класса, может быть вложено и в тело структуры: поля, методы, конструкторы и прочее, включая классы и интерфейсы.
Аналогично классу, структура может иметь статические и нестатические поля и методы, может иметь несколько конструкторов, в том числе статические и закрытые конструкторы. Для структур можно создавать собственные константы, используя поля с атрибутом readonly и статический конструктор. Структуры похожи на классы по своему описанию и ведут себя сходным образом, хотя и имеют существенные различия в семантике присваивания и проверке эквивалентности.
Рассмотрим ограничения, накладываемые на структуры.
Самое серьезное ограничение связано с object, наследуя все свойства и методы этого класса. Структура может наследовать один или несколько интерфейсов, реализуя методы этих интерфейсов.
С чем связано такое серьезное ограничение. Одно из объяснений состоит в следующем. Рассмотрим присваивание
y = x;
При присваивании допустимо, чтобы y принадлежал родительскому классу, а x - его потомку. При ссылочном присваивании никаких проблем не возникает. В результате присваивания ссылка y будет указывать на объект потомка, который помимо полей родителя может иметь дополнительные поля. Главное, что все поля родителя будут определены. После такого присваивания возможно и обратное присваивание с приведением типа
x = (T)y;
Для структур, принадлежащих к значимому типу, подобное присваивание между родителем и потомком было бы невозможным. Родителю нельзя присвоить объект потомка, поскольку у родителя меньше полей, чем у потомка, поэтому можно было бы присвоить лишь часть полей. А уж в обратную сторону операция присваивания была бы принципиально невозможна. И это одна из причин, по которой решились отказаться от наследования для значимых типов в языке C#.
Второе серьезное ограничение связано с процессом создания объектов и их инициализацией.
Рассмотрим объявление:
int[] x; int y = x[0];
Это объявление корректно. А теперь рассмотрим похожее объявление:
int u; int v = u;
Это объявление некорректно, возникнет ошибка периода компиляции, сообщающая, что переменная v не инициализирована и не может быть использована в вычислениях. В чем разница? Сущность x - это массив и, следовательно, относится к ссылочным типам. При создании объектов ссылочного типа все поля объектов инициализируются некоторыми значениями. В данном конкретном случае все элементы целочисленного типа получат значение по умолчанию, равное нулю. Сущность u арифметического типа относится к
Вернемся к классу Rational, спроектированному в предыдущей лекции. Очевидно, что его вполне разумно представить в виде структуры. Наследование для него необязательно. Семантика присваивания Rational - прямой кандидат в структуры. Зададимся вопросом, насколько просто объявление класса превратить в объявление структуры? Достаточно ли заменить слово class словом struct? В данном случае заменой одного слова не обойтись. Нужно справиться и с другими вышеперечисленными ограничениями на структуры. Сделать это не трудно. И я приведу объявление структуры SRational, оставляя читателю возможность самому найти все различия между описаниями класса Rational и описанием структуры. Замечу, что,
следуя правилам хорошего стиля, требующего давать полям содержательные имена, изменены имена полей. Для русскоязычных читателей английские названия, возможно, менее приятны, чем аббревиатуры m и n.
/// <summary>
/// Структура SRational.
/// определяет новый тип данных - рациональные числа и основные
/// операции над ними - сложение, умножение, вычитание и деление.
/// Рациональное число задается парой целых чисел
/// (numerator,denominator) и изображается
/// обычно в виде дроби numerator/denominator.
/// Число numerator называется числителем,
/// denominator - знаменателем. Для рационального числа существует
/// множество его представлений, например, 1/2, 2/4, 3/6, 6/12.
/// Среди всех представлений
/// можно выделить то, в котором числитель и знаменатель взаимно
/// несократимы. Такой представитель будет храниться в полях класса.
/// </summary>
public struct SRational
{
const string NONE_EXIST =
"Не существует рационального числа " +
"со знаменателем, равным нулю!";
//Поля класса. Числитель и знаменатель рационального числа.
int numerator, denominator;
/// <summary>
/// Конструктор класса. Создает рациональное число
/// эквивалентное numerator/denominator,
/// но со взаимно несократимыми числителем и знаменателем.
/// Если denominator = 0, то выбрасывается исключение,
/// сообщающее о невозможности создать
/// рациональное число со знаменателем 0
/// </summary>
/// <param name="numerator">числитель</param>
/// <param name="denominator">знаменатель</param>
public SRational(int numerator, int denominator)
{
if (denominator == 0)
throw new SRationalException(NONE_EXIST);
if (numerator == 0)
{
this.numerator = 0;
this.denominator = 1;
return;
}
//приведение знака
if (denominator < 0)
{
denominator = -denominator;
numerator = -numerator;
}
//приведение к несократимой дроби
int m = numerator, n = denominator;
{
int p = 0;
m = Math.Abs(m); n = Math.Abs(n);
do
{
p = m % n; m = n; n = p;
} while (n != 0);
}//Nod
this.numerator = numerator / m;
this.denominator = denominator / m;
}
/// <summary>
/// Представление рационального числа
/// в виде строки
/// </summary>
/// <returns>строка в формате numerator/denominator
/// </returns>
public override string ToString()
{
return numerator + "/" + denominator;
}
public SRational Plus(SRational a)
{
int u, v;
u = numerator * a.denominator + denominator * a.numerator;
v = denominator * a.denominator;
return (new SRational(u, v));
}//Plus
public static SRational operator +(SRational r1, SRational r2)
{
return (r1.Plus(r2));
}
public SRational Minus(SRational a)
{
int u, v;
u = numerator * a.denominator - denominator * a.numerator;
v = denominator * a.denominator;
return (new SRational(u, v));
}//Minus
public static SRational operator -(SRational r1, SRational r2)
{
return (r1.Minus(r2));
}
public SRational Mult(SRational a)
{
int u, v;
u = numerator * a.numerator;
v = denominator * a.denominator;
return (new SRational(u, v));
}//Mult
public static SRational operator *(SRational r1, SRational r2)
{
return (r1.Mult(r2));
}
public SRational Divide(SRational a)
{
int u, v;
u = numerator * a.denominator;
v = denominator * a.numerator;
return (new SRational(u, v));
}//Divide
public static SRational operator /(SRational r1, SRational r2)
{
return (r1.Divide(r2));
}
//Константы класса 0 и 1 - ZERO и ONE
public static readonly SRational ZERO, ONE;
SRational(int num, int den, string t)
{
numerator = num; denominator = den;
}//Закрытый конструктор
static SRational()
{
ZERO = new SRational(0, 1, "");
ONE = new SRational(1, 1, "");
}//Статический конструктор
//Операции отношения
public static bool operator ==(SRational r1, SRational r2)
{
return ((r1.numerator == r2.numerator)
(r1.denominator == r2.denominator));
}
public static bool operator !=(SRational r1, SRational r2)
{
return ((r1.numerator != r2.numerator) ||
(r1.denominator != r2.denominator));
}
public static bool operator <(SRational r1, SRational r2)
{
return (r1.numerator * r2.denominator <
r2.numerator * r1.denominator);
}
public static bool operator >(SRational r1, SRational r2)
{
return (r1.numerator * r2.denominator >
r2.numerator * r1.denominator);
}
public static bool operator <(SRational r1, double r2)
{
return ((double)r1.numerator / r1.denominator < r2);
}
public static bool operator >(SRational r1, double r2)
{
return ((double)r1.numerator / r1.denominator > r2);
}
public override bool Equals(object obj)
{
return this == (SRational)obj;
}
public override int GetHashCode()
{
return numerator + denominator;
}
}
/// <summary>
/// Класс, задающий исключения при работе
/// с рациональными числами.
/// </summary>
public class SRationalException : Exception
{
public SRationalException() { }
public SRationalException(string message) :
base(message) { }
public SRationalException(string message,
Exception e)
: base(message, e) { }
}
Все ранее построенные примеры работы с классом Rational применимы и при работе со структурой SRational и будут давать эквивалентные результаты за одним исключением. При вызове конструктора без аргументов для класса Rational создается корректное рациональное число 0/1. Для структуры конструктор по умолчанию создает некорректное число 0/0. Эту ситуацию никак нельзя исправить, поскольку этот конструктор нельзя переопределить. Единственный выход - не пользоваться этим конструктором при работе со структурами.
Построим пример работы с объектами структуры SRational:
public void TwoSemantics()
{
SRational sr1 = new SRational(1, 3),
sr2 = new SRational(3, 5);
SRational sr3, sr4;
sr3 = sr1 + sr2; sr4 = sr3;
if (sr3 > 1) sr3 = sr1 + sr3 + SRational.ONE;
else sr3 = sr2 + sr3 - SRational.ONE;
Console.WriteLine("Структура SRational");
Console.WriteLine("sr1 = " + sr1.ToString());
Console.WriteLine("sr2 = " + sr2.ToString());
Console.WriteLine("sr3 = " + sr3.ToString());
Console.WriteLine("sr4 = " + sr4.ToString());
}
В этом примере используются константы, работает статический конструктор, закрытый конструктор, перегруженные операции сравнения, арифметические выражения над рациональными числами. В результате вычислений r3 получит значение 8/15, r4 - 14/15. Заметьте, аналогичный пример для класса Rational даст те же результаты. Для класса Rational и структуры Rational нельзя обнаружить разницу между ссылочным и развернутым присваиванием. Это связано с особенностью класса Rational - он по построению относится к неизменяемым ( ) классам, аналогично классу string. Операции этого класса не изменяют поля объекта, а каждый раз создают новый объект. В этом случае можно считать, что объекты класса обладают присваиванием
Как уже говорилось, все Point, PointF, Size, SizeF, Rectangle, находящиеся в пространстве имен System.Drawing (при работе с консольным проектом это пространство нужно подключать). Данные структуры активно используются при работе с графическими объектами. Структуры Point, PointF определяют точки на плоскости. Свойства X и Y позволяют задавать координаты точки, целочисленные или вещественные. Для структур Size и SizeF свойства Height и Width задают высоту и ширину. Структуры Point и Size позволяют сформировать прямоугольную область - структуру Rectangle. Конструктору прямоугольника можно
передать в качестве аргументов две структуры: точку, задающую координаты левого верхнего угла прямоугольника, и размер - высоту и ширину прямоугольника.
Между четырьмя структурами определены взаимные преобразования: точки можно преобразовать в размеры и наоборот, сложение и вычитание определено над точками и размерами, но не над точками, плавающий тип которых разными способами можно привести к целому. Ряд операций над этими структурами продемонстрирован в следующем примере:
public void TestPointAndSize()
{
Point pt1 = new Point(3,5), pt2 = new Point(7,10), pt3;
PointF pt4 = new PointF(4.55f,6.75f);
Size sz1 = new Size(10,20), sz2;
SizeF sz3 = new SizeF(10.3f, 20.7f);
pt3 = Point.Round(pt4);
sz2 = new Size(pt1);
Console.WriteLine ("pt1: " + pt1);
Console.WriteLine ("sz2 = new Size(pt1): " + sz2);
Console.WriteLine ("pt4: " + pt4);
Console.WriteLine ("pt3 = Point.Round(pt4): " + pt3);
pt1.Offset(5,7);
Console.WriteLine ("pt1.Offset(5,7): " + pt1);
Console.WriteLine ("pt2: " + pt2);
pt2 = pt2+ sz2;
Console.WriteLine ("pt2= pt2+ sz2: " + pt2);
}//TestPointAndSize
Результаты его выполнения показаны на рис. 2.1.
(рис 2.1) Операции над точками и размерамиОтметим, что метод ToString, определенный для этих структур, выдает строку со значениями полей в приемлемой для восприятия форме.
В заключение разговора о ссылочных и развернутых типах построим класс CPoint, являющийся аналогом структуры Point. Не буду приводить описание этого класса, надеюсь, оно достаточно понятно. Ограничусь примером, в котором аналогичные действия выполняются над объектами, принадлежащими структуре Point и классу CPoint:
public void TestClassAndStructPoint()
{
string s = "Структуры: присваивание развернутого типа!";
Console.WriteLine(s);
Point pt1 = new Point(3, 5), pt2;
pt2 = pt1;
Console.WriteLine("pt1: " + pt1);
Console.WriteLine("pt2 = pt1: " + pt2);
pt1.Offset(5, 7);
Console.WriteLine("pt1.Offset(5,7): " + pt1);
Console.WriteLine("pt2: " + pt2);
s = "Классы: присваивание ссылочного типа!";
Console.WriteLine(s);
CPoint cpt1 = new CPoint(3, 5), cpt2;
cpt2 = cpt1;
Console.WriteLine("cpt1: " + cpt1);
Console.WriteLine("cpt2=cpt1: " + cpt2);
cpt1.Offset(5, 7);
Console.WriteLine("cpt1.Offset(5,7): " + cpt1);
Console.WriteLine("cpt2: " + cpt2);
}
Результаты вычислений показаны на рис. 2.2.
(рис 2.2) Две семантики присваиванияДействия над объектами Point и CPoint выполняются аналогичные, а результаты получаются разные: в конце вычислений pt1 и pt2 различны, а cpt1 и cpt2 совпадают. Объяснение этому понятно: в первом случае присваивание структур pt2 и pt1 - значимое, а для объектов cpt1 и cpt2 - ссылочное.
Как обычно, для демонстрации примеров данной лекции построено Решение с именем лекции Ch2_2. В Решение включен единственный консольный проект с именем StructuresAndClasses. К проекту присоединены две DLL с именами ClassSamples и Geometrical_Figures, включенные в Решение Ch2_1, которое приведено в предыдущей лекции. Классы CPoint и SRational из этих библиотек используются в консольном проекте. Консольный проект содержит традиционный класс Testing, большое число его методов представляют собой различные тесты, которые иллюстрируют работу с различными объектами классов из DLL, присоединенных к консольному проекту.
В задачах и проекте этой лекции все классы необходимо создавать в двух вариантах - как классы, принадлежащие ссылочному типу, и как структуры, принадлежащие
Point, описывающим точку на плоскости, заданную декартовыми и полярными координатами. Среди методов класса Point предусмотреть метод Distance, вычисляющий расстояние до заданной точки. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с классом Point.Point и Line, описывающими точку и линию на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Line предусмотреть метод, определяющий принадлежность заданной точки линии.Point и Square, описывающими точку и квадрат на плоскости со сторонами, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Square предусмотреть метод, определяющий принадлежность заданной точки квадрату.Point и Circle, описывающими точку и круг на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Circle предусмотреть метод, определяющий принадлежность заданной точки кругу.Point и Rectangle, описывающими точку и прямоугольник на плоскости со сторонами, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Rectangle предусмотреть метод, определяющий принадлежность заданной точки прямоугольнику.Point и Rhomb, описывающими точку и ромб на плоскости с осями, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Rhomb предусмотреть метод, определяющий принадлежность заданной точки ромбу.Point и Tetragon, описывающими точку и четырехугольник на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Tetragon предусмотреть метод, определяющий принадлежность заданной точки четырехугольнику.Point и Triangle , описывающими точку и треугольник на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Triangle предусмотреть метод, определяющий принадлежность заданной точки треугольнику.Account и Accounts, описывающими счет в банке и множество счетов клиентов, хранящихся в файле. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Lexeme и AriphmeticExpression, описывающими лексему и арифметическое выражение. Среди методов класса AriphmeticExpression предусмотреть метод, представляющий выражение в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Lexeme и LogicExpression, описывающими лексему и логическое выражение. Среди методов класса LogicExpression предусмотреть метод, представляющий выражение в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Lexeme и Assignment, описывающими лексему и оператор присваивания. Среди методов класса Assignment предусмотреть метод, представляющий оператор в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Student и Exam, описывающими студента и экзамен. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Poisk, содержащего методы линейного и Sorting, содержащего методы сортировки. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.InputArray, позволяющий вводить одномерный массив с проверкой корректности ввода элементов. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.P1, P2, $$\dots$$ Pn со случайными координатами. Построить множество точек P1, P2, $$\dots$$ Pm ( m <= n ), обход которых позволяет построить многоугольник без пересечений, если это возможно. Уметь вычислять площадь многоугольника, определять, является ли он выпуклым, принадлежит ли заданная точка внутренней области (границам) многоугольника. Построить реализацию в виде класса Polygon, включенного в состав DLL. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с объектами класса Polygon.В практикуме даются примеры разработки различных проектов.
Проект к данной лекции Вы можете скачать здесь.
В ООП главная роль, которую играют классы, состоит в задании типа данных. В этой лекции, как и во многих других, говоря о классах, будем иметь в виду именно эту роль. Хотя следует помнить, что некоторые классы могут играть только одну роль - роль модуля, и для них невозможно создавать объекты. Такие сервисные классы подробно рассматривались в предыдущей лекции, а примеры таких классов появлялись многократно.
Итак, будем рассматривать классы, задающие тип данных. Такой класс Т представляет описание множества объектов - экземпляров класса, задавая их свойства и поведение. Если класс представляет собой текст - статическое описание, то объекты класса создаются динамически в процессе работы программы. Как правило, объекты класса Т создаются в других классах, являющихся клиентами класса Т. Рассмотрим в клиентском классе объявление объекта класса T, выполненное с инициализацией:
T x = new T();
Напомню, как выполняется этот оператор, создающий объект класса Т. Объектам нужна память, чтобы с ними можно было работать. Рассмотрим две классические стратегии выделения памяти и stack ) и куча ( ). Сущности x в стеке всегда отводится память, но какая память - зависит от того, к развернутому или ссылочному типу относится класс Т.
Определение 1.Если класс T относится к развернутому типу, то в стеке сущности x отводится память, необходимая объекту класса T. Говорят, что объект разворачивается на памяти, жестко связанной с сущностью x. Эту память сущность x ни с кем не разделяет.
Определение 2.Если класс T относится к ссылочному типу, то память объекту отводится в куче, а в стеке сущности x отводится дополнительная память, содержащая ссылку на объект.
Для T. Более того, типы объектов, с которыми динамически связывается сущность, могут быть различными, хотя и требуется определенное
Развернутые и ссылочные типы порождают две различные семантики. Рассмотрим присваивание:
y = x;
Когда сущность y принадлежит y, при присваивании изменяются, получая значения полей объекта, связанного с x. Напомним, что поля y хранятся в стеке.
Когда сущность y принадлежит ссылочному типу, происходит присваивание ссылок. Ссылка y получает значение ссылки x, и обе они после присваивания указывают на один и тот же объект. Объект в куче, на который до присваивания указывала ссылка y, теряет одну из своих ссылок и, возможно, становится "висячим" объектом без ссылок, становясь добычей для сборщика мусора.
Рассмотрим операцию проверки объектов на эквивалентность:
if (x == y)
Для x и y эквивалентны, если эквивалентны значения всех их полей. Для ссылочных типов объекты x и y эквивалентны, если эквивалентны значения ссылок.
Язык программирования должен позволять программисту в момент определения класса указать, к развернутому или ссылочному типу относится класс. В языке C# это делается следующим образом. Если объявление класса задается с использованием служебного слова class, то такой класс относится к ссылочным типам.
public class A { ... }
Если объявление класса задается с использованием служебного слова struct, то такой класс относится к развернутым типам, которые также называют value типами.
public struct B { ... }
Напомню: к
Структура - это частный случай класса. Исторически структуры используются в языках программирования раньше классов. В языках PL/1, C, Pascal они представляли собой только совокупность данных (полей класса), но не включали ни методов, ни событий. В языке С++ возможности структур были существенно расширены, и они стали настоящими классами, хотя и c некоторыми ограничениями. В языке C# сохранен именно такой подход к структурам.
Чем следует руководствоваться, делая выбор между структурой и классом? Полагаю, можно пользоваться следующими правилами:
Поскольку на структуры накладываются дополнительные ограничения, может возникнуть необходимость в компромиссе - согласиться с ограничениями и использовать структуру либо пожертвовать развернутостью и эффективностью и работать с настоящим классом. Стоит отметить: когда говорится, что встроенные типы - int и другие - представляют собой классы, то, на самом деле, речь идет о классах, реализованных в виде структур.
Рассмотрим теперь более подробно вопросы описания структур, их синтаксиса, семантики и тех особенностей, что отличают их от классов.
[атрибуты][модификаторы]struct имя_структуры[:список_интерфейсов]
{тело_структуры}
Какие изменения произошли в синтаксисе заголовка структуры в сравнении с синтаксисом класса? Их немного. Перечислим их:
class изменено на слово struct ;abstract и sealed. Причиной является отсутствие механизма наследования.Все, что может быть вложено в тело класса, может быть вложено и в тело структуры: поля, методы, конструкторы и прочее, включая классы и интерфейсы.
Аналогично классу, структура может иметь статические и нестатические поля и методы, может иметь несколько конструкторов, в том числе статические и закрытые конструкторы. Для структур можно создавать собственные константы, используя поля с атрибутом readonly и статический конструктор. Структуры похожи на классы по своему описанию и ведут себя сходным образом, хотя и имеют существенные различия в семантике присваивания и проверке эквивалентности.
Рассмотрим ограничения, накладываемые на структуры.
Самое серьезное ограничение связано с object, наследуя все свойства и методы этого класса. Структура может наследовать один или несколько интерфейсов, реализуя методы этих интерфейсов.
С чем связано такое серьезное ограничение. Одно из объяснений состоит в следующем. Рассмотрим присваивание
y = x;
При присваивании допустимо, чтобы y принадлежал родительскому классу, а x - его потомку. При ссылочном присваивании никаких проблем не возникает. В результате присваивания ссылка y будет указывать на объект потомка, который помимо полей родителя может иметь дополнительные поля. Главное, что все поля родителя будут определены. После такого присваивания возможно и обратное присваивание с приведением типа
x = (T)y;
Для структур, принадлежащих к значимому типу, подобное присваивание между родителем и потомком было бы невозможным. Родителю нельзя присвоить объект потомка, поскольку у родителя меньше полей, чем у потомка, поэтому можно было бы присвоить лишь часть полей. А уж в обратную сторону операция присваивания была бы принципиально невозможна. И это одна из причин, по которой решились отказаться от наследования для значимых типов в языке C#.
Второе серьезное ограничение связано с процессом создания объектов и их инициализацией.
Рассмотрим объявление:
int[] x; int y = x[0];
Это объявление корректно. А теперь рассмотрим похожее объявление:
int u; int v = u;
Это объявление некорректно, возникнет ошибка периода компиляции, сообщающая, что переменная v не инициализирована и не может быть использована в вычислениях. В чем разница? Сущность x - это массив и, следовательно, относится к ссылочным типам. При создании объектов ссылочного типа все поля объектов инициализируются некоторыми значениями. В данном конкретном случае все элементы целочисленного типа получат значение по умолчанию, равное нулю. Сущность u арифметического типа относится к
Вернемся к классу Rational, спроектированному в предыдущей лекции. Очевидно, что его вполне разумно представить в виде структуры. Наследование для него необязательно. Семантика присваивания Rational - прямой кандидат в структуры. Зададимся вопросом, насколько просто объявление класса превратить в объявление структуры? Достаточно ли заменить слово class словом struct? В данном случае заменой одного слова не обойтись. Нужно справиться и с другими вышеперечисленными ограничениями на структуры. Сделать это не трудно. И я приведу объявление структуры SRational, оставляя читателю возможность самому найти все различия между описаниями класса Rational и описанием структуры. Замечу, что,
следуя правилам хорошего стиля, требующего давать полям содержательные имена, изменены имена полей. Для русскоязычных читателей английские названия, возможно, менее приятны, чем аббревиатуры m и n.
/// <summary>
/// Структура SRational.
/// определяет новый тип данных - рациональные числа и основные
/// операции над ними - сложение, умножение, вычитание и деление.
/// Рациональное число задается парой целых чисел
/// (numerator,denominator) и изображается
/// обычно в виде дроби numerator/denominator.
/// Число numerator называется числителем,
/// denominator - знаменателем. Для рационального числа существует
/// множество его представлений, например, 1/2, 2/4, 3/6, 6/12.
/// Среди всех представлений
/// можно выделить то, в котором числитель и знаменатель взаимно
/// несократимы. Такой представитель будет храниться в полях класса.
/// </summary>
public struct SRational
{
const string NONE_EXIST =
"Не существует рационального числа " +
"со знаменателем, равным нулю!";
//Поля класса. Числитель и знаменатель рационального числа.
int numerator, denominator;
/// <summary>
/// Конструктор класса. Создает рациональное число
/// эквивалентное numerator/denominator,
/// но со взаимно несократимыми числителем и знаменателем.
/// Если denominator = 0, то выбрасывается исключение,
/// сообщающее о невозможности создать
/// рациональное число со знаменателем 0
/// </summary>
/// <param name="numerator">числитель</param>
/// <param name="denominator">знаменатель</param>
public SRational(int numerator, int denominator)
{
if (denominator == 0)
throw new SRationalException(NONE_EXIST);
if (numerator == 0)
{
this.numerator = 0;
this.denominator = 1;
return;
}
//приведение знака
if (denominator < 0)
{
denominator = -denominator;
numerator = -numerator;
}
//приведение к несократимой дроби
int m = numerator, n = denominator;
{
int p = 0;
m = Math.Abs(m); n = Math.Abs(n);
do
{
p = m % n; m = n; n = p;
} while (n != 0);
}//Nod
this.numerator = numerator / m;
this.denominator = denominator / m;
}
/// <summary>
/// Представление рационального числа
/// в виде строки
/// </summary>
/// <returns>строка в формате numerator/denominator
/// </returns>
public override string ToString()
{
return numerator + "/" + denominator;
}
public SRational Plus(SRational a)
{
int u, v;
u = numerator * a.denominator + denominator * a.numerator;
v = denominator * a.denominator;
return (new SRational(u, v));
}//Plus
public static SRational operator +(SRational r1, SRational r2)
{
return (r1.Plus(r2));
}
public SRational Minus(SRational a)
{
int u, v;
u = numerator * a.denominator - denominator * a.numerator;
v = denominator * a.denominator;
return (new SRational(u, v));
}//Minus
public static SRational operator -(SRational r1, SRational r2)
{
return (r1.Minus(r2));
}
public SRational Mult(SRational a)
{
int u, v;
u = numerator * a.numerator;
v = denominator * a.denominator;
return (new SRational(u, v));
}//Mult
public static SRational operator *(SRational r1, SRational r2)
{
return (r1.Mult(r2));
}
public SRational Divide(SRational a)
{
int u, v;
u = numerator * a.denominator;
v = denominator * a.numerator;
return (new SRational(u, v));
}//Divide
public static SRational operator /(SRational r1, SRational r2)
{
return (r1.Divide(r2));
}
//Константы класса 0 и 1 - ZERO и ONE
public static readonly SRational ZERO, ONE;
SRational(int num, int den, string t)
{
numerator = num; denominator = den;
}//Закрытый конструктор
static SRational()
{
ZERO = new SRational(0, 1, "");
ONE = new SRational(1, 1, "");
}//Статический конструктор
//Операции отношения
public static bool operator ==(SRational r1, SRational r2)
{
return ((r1.numerator == r2.numerator)
(r1.denominator == r2.denominator));
}
public static bool operator !=(SRational r1, SRational r2)
{
return ((r1.numerator != r2.numerator) ||
(r1.denominator != r2.denominator));
}
public static bool operator <(SRational r1, SRational r2)
{
return (r1.numerator * r2.denominator <
r2.numerator * r1.denominator);
}
public static bool operator >(SRational r1, SRational r2)
{
return (r1.numerator * r2.denominator >
r2.numerator * r1.denominator);
}
public static bool operator <(SRational r1, double r2)
{
return ((double)r1.numerator / r1.denominator < r2);
}
public static bool operator >(SRational r1, double r2)
{
return ((double)r1.numerator / r1.denominator > r2);
}
public override bool Equals(object obj)
{
return this == (SRational)obj;
}
public override int GetHashCode()
{
return numerator + denominator;
}
}
/// <summary>
/// Класс, задающий исключения при работе
/// с рациональными числами.
/// </summary>
public class SRationalException : Exception
{
public SRationalException() { }
public SRationalException(string message) :
base(message) { }
public SRationalException(string message,
Exception e)
: base(message, e) { }
}
Все ранее построенные примеры работы с классом Rational применимы и при работе со структурой SRational и будут давать эквивалентные результаты за одним исключением. При вызове конструктора без аргументов для класса Rational создается корректное рациональное число 0/1. Для структуры конструктор по умолчанию создает некорректное число 0/0. Эту ситуацию никак нельзя исправить, поскольку этот конструктор нельзя переопределить. Единственный выход - не пользоваться этим конструктором при работе со структурами.
Построим пример работы с объектами структуры SRational:
public void TwoSemantics()
{
SRational sr1 = new SRational(1, 3),
sr2 = new SRational(3, 5);
SRational sr3, sr4;
sr3 = sr1 + sr2; sr4 = sr3;
if (sr3 > 1) sr3 = sr1 + sr3 + SRational.ONE;
else sr3 = sr2 + sr3 - SRational.ONE;
Console.WriteLine("Структура SRational");
Console.WriteLine("sr1 = " + sr1.ToString());
Console.WriteLine("sr2 = " + sr2.ToString());
Console.WriteLine("sr3 = " + sr3.ToString());
Console.WriteLine("sr4 = " + sr4.ToString());
}
В этом примере используются константы, работает статический конструктор, закрытый конструктор, перегруженные операции сравнения, арифметические выражения над рациональными числами. В результате вычислений r3 получит значение 8/15, r4 - 14/15. Заметьте, аналогичный пример для класса Rational даст те же результаты. Для класса Rational и структуры Rational нельзя обнаружить разницу между ссылочным и развернутым присваиванием. Это связано с особенностью класса Rational - он по построению относится к неизменяемым ( ) классам, аналогично классу string. Операции этого класса не изменяют поля объекта, а каждый раз создают новый объект. В этом случае можно считать, что объекты класса обладают присваиванием
Как уже говорилось, все Point, PointF, Size, SizeF, Rectangle, находящиеся в пространстве имен System.Drawing (при работе с консольным проектом это пространство нужно подключать). Данные структуры активно используются при работе с графическими объектами. Структуры Point, PointF определяют точки на плоскости. Свойства X и Y позволяют задавать координаты точки, целочисленные или вещественные. Для структур Size и SizeF свойства Height и Width задают высоту и ширину. Структуры Point и Size позволяют сформировать прямоугольную область - структуру Rectangle. Конструктору прямоугольника можно
передать в качестве аргументов две структуры: точку, задающую координаты левого верхнего угла прямоугольника, и размер - высоту и ширину прямоугольника.
Между четырьмя структурами определены взаимные преобразования: точки можно преобразовать в размеры и наоборот, сложение и вычитание определено над точками и размерами, но не над точками, плавающий тип которых разными способами можно привести к целому. Ряд операций над этими структурами продемонстрирован в следующем примере:
public void TestPointAndSize()
{
Point pt1 = new Point(3,5), pt2 = new Point(7,10), pt3;
PointF pt4 = new PointF(4.55f,6.75f);
Size sz1 = new Size(10,20), sz2;
SizeF sz3 = new SizeF(10.3f, 20.7f);
pt3 = Point.Round(pt4);
sz2 = new Size(pt1);
Console.WriteLine ("pt1: " + pt1);
Console.WriteLine ("sz2 = new Size(pt1): " + sz2);
Console.WriteLine ("pt4: " + pt4);
Console.WriteLine ("pt3 = Point.Round(pt4): " + pt3);
pt1.Offset(5,7);
Console.WriteLine ("pt1.Offset(5,7): " + pt1);
Console.WriteLine ("pt2: " + pt2);
pt2 = pt2+ sz2;
Console.WriteLine ("pt2= pt2+ sz2: " + pt2);
}//TestPointAndSize
Результаты его выполнения показаны на рис. 2.1.
(рис 2.1) Операции над точками и размерамиОтметим, что метод ToString, определенный для этих структур, выдает строку со значениями полей в приемлемой для восприятия форме.
В заключение разговора о ссылочных и развернутых типах построим класс CPoint, являющийся аналогом структуры Point. Не буду приводить описание этого класса, надеюсь, оно достаточно понятно. Ограничусь примером, в котором аналогичные действия выполняются над объектами, принадлежащими структуре Point и классу CPoint:
public void TestClassAndStructPoint()
{
string s = "Структуры: присваивание развернутого типа!";
Console.WriteLine(s);
Point pt1 = new Point(3, 5), pt2;
pt2 = pt1;
Console.WriteLine("pt1: " + pt1);
Console.WriteLine("pt2 = pt1: " + pt2);
pt1.Offset(5, 7);
Console.WriteLine("pt1.Offset(5,7): " + pt1);
Console.WriteLine("pt2: " + pt2);
s = "Классы: присваивание ссылочного типа!";
Console.WriteLine(s);
CPoint cpt1 = new CPoint(3, 5), cpt2;
cpt2 = cpt1;
Console.WriteLine("cpt1: " + cpt1);
Console.WriteLine("cpt2=cpt1: " + cpt2);
cpt1.Offset(5, 7);
Console.WriteLine("cpt1.Offset(5,7): " + cpt1);
Console.WriteLine("cpt2: " + cpt2);
}
Результаты вычислений показаны на рис. 2.2.
(рис 2.2) Две семантики присваиванияДействия над объектами Point и CPoint выполняются аналогичные, а результаты получаются разные: в конце вычислений pt1 и pt2 различны, а cpt1 и cpt2 совпадают. Объяснение этому понятно: в первом случае присваивание структур pt2 и pt1 - значимое, а для объектов cpt1 и cpt2 - ссылочное.
Как обычно, для демонстрации примеров данной лекции построено Решение с именем лекции Ch2_2. В Решение включен единственный консольный проект с именем StructuresAndClasses. К проекту присоединены две DLL с именами ClassSamples и Geometrical_Figures, включенные в Решение Ch2_1, которое приведено в предыдущей лекции. Классы CPoint и SRational из этих библиотек используются в консольном проекте. Консольный проект содержит традиционный класс Testing, большое число его методов представляют собой различные тесты, которые иллюстрируют работу с различными объектами классов из DLL, присоединенных к консольному проекту.
В задачах и проекте этой лекции все классы необходимо создавать в двух вариантах - как классы, принадлежащие ссылочному типу, и как структуры, принадлежащие
Point, описывающим точку на плоскости, заданную декартовыми и полярными координатами. Среди методов класса Point предусмотреть метод Distance, вычисляющий расстояние до заданной точки. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с классом Point.Point и Line, описывающими точку и линию на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Line предусмотреть метод, определяющий принадлежность заданной точки линии.Point и Square, описывающими точку и квадрат на плоскости со сторонами, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Square предусмотреть метод, определяющий принадлежность заданной точки квадрату.Point и Circle, описывающими точку и круг на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Circle предусмотреть метод, определяющий принадлежность заданной точки кругу.Point и Rectangle, описывающими точку и прямоугольник на плоскости со сторонами, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Rectangle предусмотреть метод, определяющий принадлежность заданной точки прямоугольнику.Point и Rhomb, описывающими точку и ромб на плоскости с осями, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Rhomb предусмотреть метод, определяющий принадлежность заданной точки ромбу.Point и Tetragon, описывающими точку и четырехугольник на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Tetragon предусмотреть метод, определяющий принадлежность заданной точки четырехугольнику.Point и Triangle , описывающими точку и треугольник на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Triangle предусмотреть метод, определяющий принадлежность заданной точки треугольнику.Account и Accounts, описывающими счет в банке и множество счетов клиентов, хранящихся в файле. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Lexeme и AriphmeticExpression, описывающими лексему и арифметическое выражение. Среди методов класса AriphmeticExpression предусмотреть метод, представляющий выражение в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Lexeme и LogicExpression, описывающими лексему и логическое выражение. Среди методов класса LogicExpression предусмотреть метод, представляющий выражение в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Lexeme и Assignment, описывающими лексему и оператор присваивания. Среди методов класса Assignment предусмотреть метод, представляющий оператор в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Student и Exam, описывающими студента и экзамен. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.Poisk, содержащего методы линейного и Sorting, содержащего методы сортировки. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.InputArray, позволяющий вводить одномерный массив с проверкой корректности ввода элементов. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.P1, P2, $$\dots$$ Pn со случайными координатами. Построить множество точек P1, P2, $$\dots$$ Pm ( m <= n ), обход которых позволяет построить многоугольник без пересечений, если это возможно. Уметь вычислять площадь многоугольника, определять, является ли он выпуклым, принадлежит ли заданная точка внутренней области (границам) многоугольника. Построить реализацию в виде класса Polygon, включенного в состав DLL. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с объектами класса Polygon.В практикуме даются примеры разработки различных проектов.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.