Класс - это обобщенное понятие, определяющие характеристики и поведение некоторого множества объектов, называемых экземплярами класса. "Классический" класс содержит данные, определяющие object.
Все программы, рассмотренные ранее, состояли из одного класса с одним методом Main и несколькими вспомогательными статическими методами. Теперь рассмотрим понятие "класс" более подробно.
Описание класса содержит ключевое слово class, за которым следует его имя, а далее в фигурных скобках - тело класса. Кроме того, для класса можно задать его базовые классы (предки) и ряд необязательных атрибутов и спецификаторов, определяющих различные характеристики класса:
[ атрибуты ] [ спецификаторы ] class имя_класса [ : предки ]
{тело_класса}
Простейший пример класса:
class Demo{}
Спецификаторы определяют свойства класса, а также доступность класса для других элементов программы. Возможные значения спецификаторов перечислены в следующей таблице:
| № | Спецификатор | Описание |
|---|---|---|
| 1 | new |
Задает новое описание класса взамен унаследованного от предка. Используется для вложения классов (в иерархии объектов). |
| 2 | public |
Доступ к классу не ограничен |
| 3 | protected |
Доступ только из данного или производного класса. Используется для вложенных классов. |
| 4 | internal |
Доступ только из данной программы (сборки). |
| 5 | protected internal |
Доступ только из данного и производного класса и из данной программы (сборки). |
| 6 | private |
Доступ только из элементов класса, внутри которых описан данный класс. Используется для вложенных классов. |
| 7 | static |
Статический класс. Позволяет обращатся к методам класса без создания экземпляра класса |
| 8 | sealed |
Бесплодный класс. Запрещает наследование данного класса. Применяется в иерархии объектов. |
| 9 | abstract |
Абстрактный класс. Применяется в иерархии объектов. |
Спецификаторы 2-6 называются спецификаторами доступа. Они определяют, откуда можно непосредственно обращаться к данному классу. Спецификаторы доступа могут комбинироваться с остальными спецификаторами.
Класс можно описывать непосредственно внутри пространства имен или внутри другого класса. В последнем случае класс называется вложенным. В зависимости от места описания класса некоторые из этих спецификаторов могут быть запрещены. В данном разделе мы рассмотрим классы, которые описываются непосредственно в пространстве имен. Для таких классов допускаются только два спецификатора: public и internal. По умолчанию, то есть если ни один спецификатор доступа не указан, подразумевается спецификатор internal.
Объекты создаются явным или неявным образом, то есть либо программистом, либо системой. Программист создает экземпляр класса с помощью операции new, например:
Demo a = new Demo (); // Создается экземпляр класса Demo
Если достаточный для хранения объекта объем памяти выделить не удалось, то генерируется исключение OutOfMemoryException.
Для каждого объекта при его создании в памяти выделяется отдельная область, в которой хранятся его данные. В классе могут присутствовать
До сих пор мы использовали в программах только данные (переменные и константы) и методы. В общем случае класс может содержать следующие функциональные элементы:
В данном разделе мы рассмотрим первые четыре категории элементов класса
Прежде чем приступить к проектированию классов, необходимо поговорить о присваивании и сравнении объектов. Механизм выполнения присваивания один и тот же для величин любого типа, как ссылочного, так и размерного, однако результаты различаются. При присваивании значения копируется значение, а при присваивании ссылки - ссылка, поэтому после присваивания одного объекта другому мы получим две ссылки, указывающие на одну и ту же область памяти:
(рис 13.1) 13_01
Пусть были созданы три объекта а, b и с, а затем выполнено присваивание b = с. Теперь ссылки b и с указывают на один и тот же объект. Старое значение b становится недоступным и очищается сборщиком мусора.
Аналогичная ситуация с операцией проверки на равенство. Величины b и с равны, т.к. они ссылаются на одну и ту же область памяти. Но а не равно b даже при равенстве их значений.
Данные, содержащиеся в классе, могут быть переменными или константами и задаются в соответствии с правилами, рассмотренными в теме "Идентификаторы". При описании данных также можно указывать атрибуты и спецификаторы, задающие различные характеристики элементов. Синтаксис описания элемента данных приведен ниже:
[атрибуты] [спецификаторы] [const] тип имя [ = начальное_значение ]
Рассмотрим возможные спецификаторы для данных:
| № | Спецификатор | Описание |
|---|---|---|
| 1 | new | Новое описание поля, скрывающее унаследованный элемент класса |
| 2 | public |
Доступ к элементу не ограничен |
| 3 | protected |
Доступ только из данного и производных классов |
| 4 | internal |
Доступ только из данной сборки |
| 5 | protected internal |
Доступ только из данного и производных классов и из данной сборки |
| 6 | private |
Доступ только из данного класса |
| 7 | static |
Одно поле для всех экземпляров класса |
| 8 | readonly |
Поле доступно только для чтения (значение таких полей можно установить либо при описании, либо в конструкторе) |
| 9 | |
Поле может изменяться другим процессом или системой |
Для констант можно использовать только спецификаторы 1-6.
По умолчанию элементы класса считаются закрытыми private. Для полей класса этот вид доступа является предпочтительным, поскольку поля определяют внутреннее строение класса, которое должно быть скрыто от пользователя. Все методы класса имеют непосредственный доступ к его закрытым полям.
Поля, описанные со спецификатором static, а также константы существуют в единственном экземпляре для всех объектов класса, поэтому к ним обращаются не через имя экземпляра, а через имя класса. Обращение к полю класса выполняется с помощью операции доступа (точка). Справа от точки задается имя поля, слева - имя экземпляра для обычных полей или имя класса для статических. Рассмотрим пример создания класса Demo и два способа обращения к его полям.
class Circle
{
public int x=0;
public int y=0;
public int radius=3;
public const double pi = 3.14;
public static string name = "Окружность";
double p;
double s;
}
class Program
{
static void Main()
{
Circle cr = new Circle(); //создание экземпляра класса
Console.WriteLine("pi=" + Circle.pi);// обращение к константе
Console.Write(Circle.name);// обращение к статическому полю
//обращение к обычным полям
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", cr.x, cr.y, cr.radius);
// Console.WriteLine(cr.p); - вызовет ошибку, т.к. поле p имеет тип private
Console.Write("Введите коэффициент= ");
int kof = int.Parse(Console.ReadLine());
cr.x -= kof; cr.y += kof; cr.radius *= kof;
Console.WriteLine(" Новая окружность с центром в точке ({0},{1}) и радиусом {2}",
cr.x, cr.y, cr.radius);
//cr.s = 2 * Circle.pi * cr.radius; - вызовет ошибку, т.к. поле s имеет тип private
}
}
Методы находятся в памяти в единственном экземпляре и используются всеми объектами одного класса совместно, поэтому необходимо обеспечить работу методов нестатических экземпляров с полями именно того объекта, для которого они были вызваны. Для этого в любой нестатический метод автоматически передается скрытый параметр this, в котором хранится ссылка на вызвавший функцию экземпляр.
В явном виде параметр this применяется для того, чтобы возвратить из метода ссылку на вызвавший объект, а также для идентификации поля в случае, если его имя совпадает с именем параметра метода, например:
class Circle
{
public int x=0;
public int y=0;
public int radius=3;
public const double pi = 3.14;
public static string name = "Окружность";
public Circle T() //метод возвращает ссылку на экземпляр класса
{
return this;
}
public void Set(int x, int y, int r)
{
this.x = x;
this.y = y;
radius=r;
}
}
class Program
{
static void Main()
{
Circle cr = new Circle(); //создание экземпляра класса
Console.WriteLine("pi=" + Circle.pi);// обращение к константе
Console.Write(Circle.name);// обращение к статическому полю
//обращение к обычным полям
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", cr.x, cr.y, cr.radius);
cr.Set(1, 1, 10);
Console.WriteLine("Новая окружность с центром в точке ({0},{1}) и радиусом {2}",
cr.x, cr.y, cr.radius);
Circle b=cr.T();//получаем ссылку на объект cr, аналог b=c
Console.WriteLine("Новая ссылка на окружность с центром в точке ({0},{1})
и радиусом {2}", b.x, b.y, b.radius);
}
}
Конструктор предназначен для инициализации объекта. Конструкторы делятся на конструкторы класса (для статических классов) и конструкторы экземпляра класса (всех остальных классов).
Конструктор экземпляра вызывается автоматически при создании объекта класса с помощью операции new. Имя конструктора совпадает с именем класса. Рассмотрим основные свойства конструкторов:
void.null.До сих пор мы задавали начальные значения полей класса при описании класса. Это удобно в том случае, когда для всех экземпляров класса начальные значения некоторого поля одинаковы. Если же при создании объектов требуется присваивать полю разные значения, это следует делать с помощью явного задания конструктора. В следующем примере добавлен конструктор и метод Print для вывода информации об объекте:
class Circle
{
public int x;
public int y;
public int radius;
public const double pi = 3.14;
public static string name = "Окружность";
public Circle(int x, int y, int r)//конструктор
{
this.x = x;
this.y = y;
radius = r;
}
public void Print()
{
Console.Write(name);
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", x, y, radius);
Console.WriteLine();
}
}
class Program
{
static void Main()
{
Circle a = new Circle(0, 0, 1); //вызов конструктора
a.Print();
Circle b=new Circle(10, 10, 5);//вызов конструктора
b.Print();
}
}
Часто бывает удобно задать в классе несколько конструкторов, чтобы обеспечить возможность инициализации объектов разными способами. Для этого конструкторы должны иметь разные сигнатуры. Рассмотрим это на примере:
class Circle
{
public int x;
public int y;
public int radius;
public const double pi = 3.14;
public static string name = "Окружность";
public Circle(int x, int y, int r)//конструктор 1
{
this.x = x;
this.y = y;
radius = r;
}
public Circle(int r)//конструктор 2
{
radius = r;
}
public void Print()
{
Console.Write(name);
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", x, y, radius);
Console.WriteLine();
}
}
class Program
{
static void Main()
{
Circle a = new Circle(0, 0, 1); //вызов конструктора 1
a.Print();
Circle b=new Circle(5);//вызов конструктора 2
b.Print();
}
}
Обратите внимание на то, что в конструктое 2 не были инициализированы поля x, y, поэтому им присваивается значение 0.
Если один из конструкторов выполняет какие-либо действия, а другой должен делать то же самое плюс еще что-нибудь, то удобно вызвать первый конструктор из второго. Для этого используется уже известное вам ключевое слово this в другом контексте, например:
public Circle(int x, int y, int r):this(r) //конструктор 1
{
this.x = x;
this.y = y;
}
public Circle(int r) //конструктор 2
{
radius = r;
}
Конструкция, находящаяся после двоеточия, называется
Статические классы содержат только статические члены, в том числе и конструктор, которые хранятся в памяти в единственном экземпляре. Поэтому создавать экземпляры класса нет смысла.
Чтобы подчеркнуть этот факт, в первой версии С# для статических классов создавали два конструктора, один - пустой закрытый (private) конструктор, второй - статический конструктор, не имеющий параметров. Первый конструктор предотвращал попытки создания экземпляров класса. Второй конструктор автоматически вызывается системой до первого обращения к любому элементу статического класса, выполняя необходимые действия по инициализации. Вышесказанное отражено в следующем примере:
class Demo
{
static int a;
static int b;
private Demo(){} //закрытый конструктор
static Demo() //статический конструктор
{
a=10;
b=2;
}
public static void Print ()
{
Console.WriteLine("{0}+{1}={2}",a,b,a+b);
Console.WriteLine("{0}*{1}={2}",a,b,a*b);
Console.WriteLine("{0}-{1}={2}",a,b,a-b);
}
}
class Program
{
static void Main()
{
//Demo S=new Demo(); //ошибка содать экземпляр класса нельзя
Demo.Print();
}
}
В версию 2.0 введена возможность описывать статический класс, то есть класс с модификатором static. Экземпляры такого класса создавать запрещено, и кроме того, от него запрещено наследовать. Все элементы такого класса должны явным образом объявляться с модификатором static (константы и
static class Demo
{
static int a=20;
static int b=10;
public static void Print ()
{
Console.WriteLine("{0}+{1}={2}",a,b,a+b);
Console.WriteLine("{0}*{1}={2}",a,b,a*b);
Console.WriteLine("{0}-{1}={2}",a,b,a-b);
}
}
class Program
{
static void Main()
{
Demo.Print();
}
}
Иногда требуется создать поле, которое с одной стороны, должно быть доступно для использования, с другой стороны, возможность что-то сделать с этим полем имеет ограничения. Например, полю нельзя присваивать произвольное значение, а только значения из какого-то диапазона. Свойство предлагает простой и удобный способ решения этой проблемы.
Синтаксис свойства:
[атрибуты] [спецификаторы] тип имя_свойства
{
[get код_доступа]
[set код_доступа]
}
Значения спецификаторов для свойств и методов аналогичны. Чаще всего свойства объявляются как открытые (со спецификатором public ).
Код доступа представляет собой блоки операторов, которые выполняются при получении ( get ) или установке ( set ) свойства. Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, то свойство доступно только для чтения. Если отсутствует часть get, то свойство доступно только для записи.
Рассмотрим пример работы со свойствами, обращая внимание на то, что синтаксически чтение и запись свойства выглядят почти как методы. При этом get должен содержать оператор return, возвращающий выражение, для типа которого должно существовать
class Circle
{
//закрытые поля
int x;
int y;
int radius;
public static string name = "Окружность";
public Circle(int x, int y, int r):this(r)//конструктор 1
{
this.x = x;
this.y = y;
}
public Circle(int r)//конструктор 2
{
radius = r;
}
public void Print()
{
Console.Write(name);
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", x, y, radius);
Console.WriteLine();
}
public int X //свойство для обращения к полю x
{
get
{
return x;
}
set
{
x = value;
}
}
public int Y //свойство для обращения к полю y
{
get
{
return y;
}
set
{
y = value;
}
}
public int R //свойство для обращения к полю radius
{
get
{
return radius;
}
set
{
radius = value;
}
}
public double P //свойство только для чтения
{
get
{
return 2* Math.PI *radius;
}
}
public double S //свойство только для чтения
{
get
{
return Math.PI *radius*radius;
}
}
}
class Program
{
static void Main()
{
Circle a = new Circle(0, 0, 1); //вызов конструктора
a.Print();
//установка новых значений
a.X=1;
a.Y=1;
a.R=10;
//a.S=100; //ошибка - свойство доступно только для чтения
Console.WriteLine("центр=({0},{1})
радиус={2}
периметр={3:f2}
площадь={4:f2}",
a.X, a.Y, a.R, a.P, a.S);
}
}
С добавлением новых классов в программу резко увеличивается ее размер, что затрудняет ее прочтение. Поэтому следует руководствоваться одним простым принципом "один класс - один файл". Для того чтобы создать новый файл для класса Circle выполним следующие действия:
(рис 13.2) 13_02
(рис 13.3) 13_03
В поле Name напишите Сircle.cs и нажмите кнопку Add.
Теперь окно Solution Explorer выглядит следующим образом:
(рис 13.4) 13_04
А файл Circle.cs выглядит следующим образом:
using System;
namespace ConsoleApplication1
{
public class Circle
{
public Circle()
{
//
// TODO: Add constructor logic here
//
}
}
}
namespace ConsoleApplication1 на namespace MyProgram, для того чтобы идентификаторы файлов Program.cs и Circle.cs были определены в одном пространстве имен.Circle из файла Program.cs в файл Circle.cs.Point, разработав следующие элементы класса:int x, y;(a, b).Triangle , разработав следующие элементы класса:int a, b, c;Rectangle, разработав следующие элементы класса:int a, b;Money, разработав следующие элементы класса:int first;//номинал купюрыint second; //количество купюрint [] IntArray;int n.int [,] IntArray;int n.nxn.i -того столбца.double [][] DoubelArray;int n, m.nxm.StringBuilder Line ;int n.n символов.Regex r ;string text ;DataTime data.1.01.2009Используя дополнительную литературу и Интернет, рассмотрите следующие темы:
В С# существует специальный вид метода, называемый деструктором, который вызывается сборщиком мусора непосредственно перед удалением объекта из памяти.
В деструкторе описываются действия, гарантирующие корректность последующего удаления объекта. Например, проверяется все ли ресурсы, используемые объектом, освобождены (файлы закрыты,
Синтаксис деструктора:
[атрибуты] [extern] ~имя_класса()
{тело_деструктора}
Деструктор не имеет параметров, не возвращает значения и не требует указания спецификаторов доступа. Его имя совпадает с именем класса и предваряется ~ ), символизирующей обратные по отношению к конструктору действия. Тело деструктора представляет собой блок или просто точку с запятой. Если деструктор определен как внешний, то используется спецификатор extern. Пример работы деструктора:
class DemoArray
{
int[] MyArray;//закрытый массив
string name; //закрытое поле
public DemoArray(int size,int x, string name)//конструктор
{
MyArray = new int[size];
this.name = name;
for (int i=0;i<size; ++i) MyArray[i]=x;
}
public void Print ()//метод
{
Console.Write(name+ " : ");
foreach (int a in MyArray) Console.Write(a+" ");
Console.WriteLine();
}
public int LengthN //свойство
{
get { return MyArray.Length; }
}
~DemoArray()//деструктор
{
Console.WriteLine("сработал деструктор для объекта "+this.name);
}
}
class Program
{
static void Main()
{
DemoArray a= new DemoArray(5,2, "один");
a.Print();
DemoArray b = new DemoArray(6,1, "два");
b.Print();
a = b;
a.Print();
}
}
В общем случае применение деструкторов замедляет процесс сборки мусора. Поэтому создавать деструкторы следует только тогда, когда необходимо освободить какие-то ресурсы перед удалением объекта.
Индексатор представляет собой разновидность свойства и обычно применяется для организации доступа к скрытым полям класса по индексу, например, так же, как мы обращаемся к элементу массива. Синтаксис индексатора аналогичен синтаксису свойства:
[атрибуты] [спецификаторы] тип this [список параметров] // последние [ ] являются элементами синтаксиса
{
[get код_доступа]
[set код_доступа]
}
Спецификаторы аналогичны спецификаторам свойств и методов. Индексаторы чаще всего объявляются со спецификатором public, поскольку они входят в
Код доступа представляет собой блоки операторов, которые выполняются при получении ( get ) или установке ( set ) значения некоторого элемента класса. Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, индексатор доступен только для чтения, если отсутствует часть get, индексатор доступен только для записи.
Список параметров содержит одно или несколько описаний индексов, по которым выполняется доступ к элементу. Чаще всего используется один индекс целого типа.
В качестве примера рассмотрим индексатор, который позволяет получить n-член последовательности Фиббоначи:
class DemoFib
{
public int this[int i] //индексатор, доступный только для чтения
{
get
{
if (i <=0) throw new Exception("недопустимое значение индекса");
else if (i==1 || i==2) return 1;
else
{int a=1, b=1, c;
for (int j=3; j<=i; ++j)
{
c=a+b;
a=b;
b=c;
}
return b;
}
}
}
}
class Program
{
static void Main()
{
Console.Write("n=");
int n=int.Parse(Console.ReadLine());
DemoFib a=new DemoFib();
try
{
Console.WriteLine("a[{0}]={1}",n,a[n]);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Индексаторы очень удобно применять для создания специализированных массивов, на работу с которыми накладываются какие-либо ограничения. Рассмотрим в качестве примера класс-массив, значения элементов которого находятся в диапазоне [0, 100]. Кроме того, при доступе к элементу проверяется, не вышел ли индекс за допустимые границы.
class DemoArray
{
int[] MyArray;//закрытый массив
public DemoArray(int size)//конструктор
{
MyArray = new int[size];
}
public int LengthArray //свойство, возвращающее размерность
{
get { return MyArray.Length; }
}
public int this[int i] //индексатор
{
get
{
if (i <0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
else return MyArray[i];
}
set
{
if (i <0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
else if (value >= 0 value <= 100) MyArray[i] = value;
else throw new Exception("присваивается недопустимое значение");
}
}
}
class Program
{
static void Main()
{
DemoArray a = new DemoArray(10);
for (int i=0; i<a.LengthArray; i++)
{
a[i] = i * i; // использование индексатора в режиме записи
Console.Write(a[i]+" ");// использование индексатора в режиме чтения
}
Console.WriteLine();
try
{
//a[10]=100;
//a[0]=200;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
try содержатся две закомментированные команды. Посмотрите, как проведет себя программа, если убрать комментарии: вначале один, затем другой.Язык С# допускает использование многомерных индексаторов. Они применяются для работы с многомерными массивами. Рассмотрим на примере предыдущую задачу при условии, что организуется двумерный массив.
class DemoArray
{
int[,] MyArray;//закрытый массив
int n, m;//закрытые поля: размерность массива
public DemoArray(int sizeN, int sizeM)//конструктор
{
MyArray = new int[sizeN, sizeM];
this.n = sizeN;
this.m = sizeM;
}
public int LengthN //свойство, возвращающее количество строк
{
get { return n; }
}
public int LengthM //свойство, возвращающее количество строк
{
get { return m; }
}
public int this[int i, int j] //индексатор
{
get
{
if (i < 0 || i >= n || j < 0 || j >= m) throw new Exception("выход за границы массива");
else return MyArray[i, j];
}
set
{
if (i < 0 || i >= n || j < 0 || j >= m) throw new Exception("выход за границы массива");
else if (value >= 0 value <= 100) MyArray[i, j] = value;
else throw new Exception("присваивается недопустимое значение");
}
}
}
class Program
{
static void Main()
{
DemoArray a = new DemoArray(3, 3);
for (int i = 0; i < a.LengthN; i++,Console.WriteLine())
{
for (int j = 0; j < a.LengthM; j++)
{
a[i, j] = i *j; // использование индексатора в режиме записи
Console.Write("{0,5}", a[i, j]);// использование индексатора в режиме чтения
}
}
Console.WriteLine();
try
{
//Console.WriteLine(a[3,3]);
//a[0,0]=200;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
try содержатся две закомментированные команды. Посмотрите, как проведет себя программа если убрать комментарии - вначале один, затем другой.С# позволяет переопределить большинство операций так, чтобы при использовании их объектами конкретного класса выполнялись действия, отличные от стандартных. Это дает возможность применять объекты собственных типов данных в составе выражений, например:
newObject x, y, z; ... z = x+y; // используется операция сложения, переопределенная для класса newObject
Определение собственных операций класса называют
Операции класса описываются с помощью методов специального вида, синтаксис которых выглядит следующим образом:
[ атрибуты] спецификаторы объявитель_операции
{тело}
В качестве спецификаторов одновременно используются ключевые слова public и static. Кроме того, операцию можно объявить как внешнюю - extern. Объявление операции может выглядеть по-разному, в зависимости от того, что мы перегружаем: унарную или бинарную операцию.
При описании операций необходимо соблюдать следующие правила:
public static );ref и out );В классе можно переопределять следующие унарные операции: + - ! ~ ++ --, а также константы true и false. При этом, если была перегружена константа true, то должна быть перегружена и константа false, и наоборот.
Синтаксис объявителя унарной операции:
тип operator унарная_операция (параметр)
Примеры заголовков унарных операций:
public static int operator + (DemoArray m) public static DemoArray operator --(DemoArray m) public static bool operator true (DemoArray m)
Параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. При этом операция должна возвращать:
+, -, !, ~ величину любого типа;++, -- величину типа класса, для которого она определяется;true и false величину типа bool.Операции не должны изменять значение передаваемого им операнда. Операция, возвращающая величину типа класса, для которого она определяется, должна создать новый объект этого класса, выполнить с ним необходимые действия и передать его в качестве результата.
В качестве примера рассмотрим класс DemoArray, реализующий одномерный массив, в котором содержатся следующие функциональные элементы:
true и false (при обращении к объекту будет возвращаться значение true, если все элементы массива положительные, в противном случае, будет возвращаться значение false).class DemoArray
{
int[] MyArray;//закрытый массив
public DemoArray(int size)//конструктор 1
{
MyArray = new int[size];
}
public DemoArray(params int[] arr)//конструктор 2
{
MyArray = new int[arr.Length];
for (int i = 0; i < MyArray.Length; i++) MyArray[i] = arr[i];
}
public int LengthArray //свойство, возвращающее размерность
{
get { return MyArray.Length; }
}
public int this[int i] //индексатор
{
get
{
if (i < 0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
return MyArray[i];
}
set
{
if (i < 0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
else MyArray[i] = value;
}
}
public static DemoArray operator -(DemoArray x) //перегрузка операции унарный минус
{
DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i] = -x[i];
return temp;
}
public static DemoArray operator ++(DemoArray x) //перегрузка операции инкремента
{
DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i] = x[i]+1;
return temp;
}
public static bool operator true(DemoArray a) //перегрузка константы true
{
foreach (int i in a.MyArray)
{
if (i<0)
{
return false;
}
}
return true;
}
public static bool operator false(DemoArray a)//перегрузка константы false
{
foreach (int i in a.MyArray)
{
if (i>0)
{
return true;
}
}
return false;
}
public void Print(string name) //метод - выводит поле-массив на экран
{
Console.WriteLine(name + ": ");
for (int i = 0; i < MyArray.Length; i++)
Console.Write(MyArray[i] + " ");
Console.WriteLine();
}
}
class Program
{
static void Main()
{
try
{
DemoArray Mas = new DemoArray(1, -4, 3, -5, 0); //вызов конструктора 2
Mas.Print("Исходный массив");
Console.WriteLine("\nУнарный минус");
DemoArray newMas=-Mas; //применение операции унарного минуса
Mas.Print("Mассив Mas"); //обратите внимание, что создается новый объект и знаки меняются
newMas.Print("Массив newMas"); //только у нового массива
Console.WriteLine("\nОперация префиксного инкремента");
DemoArray Mas1=++Mas;
Mas.Print("Mассив Mas");
Mas1.Print("Mассив Mas1=++Mas");
Console.WriteLine("\nОперация постфиксного инкремента");
DemoArray Mas2=Mas++;
Mas.Print("Mассив Mas");
Mas2.Print("Mассив Mas2=Mas++");
if (Mas)
Console.WriteLine("\nВ массиве все элементы положительные\n");
else Console.WriteLine("\nВ массиве есть не положительные элементы\n");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
DemoArray переопределение унарного плюса (все элементы массива преобразуются в положительные), унарного декремента (все элементы массива уменьшаются на единицу)При разработке класса можно перегрузить следующие бинарные операции: + - * / % | ^ << >> == != < > <= >=. Обратите внимание, операций присваивания в этом списке нет.
Синтаксис объявителя бинарной операции:
тип operator бинарная_операция (параметр1, параметр 2)
Примеры заголовков бинарных операций:
public static DemoArray operator + (DemoArray a, DemoArray b) public static bool operator == (DemoArray a, DemoArray b)
При переопределении бинарных операций нужно учитывать следующие правила:
В качестве примера вернемся к классу DemoArray, реализующему одномерный массив, и добавим в него две версии +:
class DemoArray
{
...
public static DemoArray operator +(DemoArray x, int a) //вариант 1
{
DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i]=x[i]+a;
return temp;
}
public static DemoArray operator +(DemoArray x, DemoArray y) //вариант 2
{
if (x.LengthArray == y.LengthArray)
{
DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i] = x[i] + y[i];
return temp;
}
else throw new Exception("несоответствие размерностей");
}
}
class Program
{
static void Main()
{
try
{
DemoArray a = new DemoArray(1, -4, 3, -5, 0);
a.Print("Массива a");
DemoArray b=a+10;
b.Print("\nМассива b");
DemoArray c = a+b;
c.Print("\nМассива c");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
DemoArray переопределение бинарного минуса (из всех элементов массива вычитается заданное число) и операции (поэлементно сравнивает два массива, если соответствующие элементы попарно совпадают, то операция возвращает значение true, иначе false ).Операции преобразования типов обеспечивают возможность явного и
explicit operator целевой_тип (параметр) //явное преобразование implicit operator целевой_тип (параметр) //неявное преобразование
Эти операции выполняют преобразование из типа параметра в тип, указанный в заголовке операции. Одним из этих типов должен быть класс, для которого выполняется преобразование.
Явное преобразование выполняется при использовании операции приведения типа.
При определении операции преобразования типа следует учитывать следующие особенности:
Следовательно, для одного и того класса нельзя определить одновременно и явную, и неявную версию. Однако, т.к.
В качестве примера вернемся к классу DemoArray, реализующему одномерный массив, и добавим в него неявную версию переопределения типа DemoArray в тип одномерный массив и наоборот:
class DemoArray
{
…
public static implicit operator DemoArray (int []a) //неявное преобразование типа int [] в DemoArray
{
return new DemoArray(a);
}
public static implicit operator int [](DemoArray a) //неявное преобразование типа DemoArray в int []
{
int []temp=new int[a.LengthArray];
for (int i = 0; i < a.LengthArray; ++i)
temp[i] = a[i];
return temp;
}
}
class Program
{
static void arrayPrint(string name, int[]a) //метод, который позволяет вывести на экран одномерный массив
{
Console.WriteLine(name + ": ");
for (int i = 0; i < a.Length; i++)
Console.Write(a[i] + " ");
Console.WriteLine();
}
static void Main()
{
try
{
DemoArray a = new DemoArray(1, -4, 3, -5, 0);
int []mas1=a; //неявное преобразование типа DemoArray в int []
int []mas2=(int []) a; //явное преобразование типа DemoArray в int []
DemoArray b1 =mas1; //неявное преобразование типа int [] в DemoArray
DemoArray b2 =(DemoArray)mas2; //явное преобразование типа int [] в DemoArray
//изменение значений
mas1[0]=0; mas2[0]=-1;
b1[0]=100; b2[0]=-100;
//вывод на экран
a.Print("Массива a");
arrayPrint("Maccив mas1", mas1);
arrayPrint("Maccив mas2", mas2);
b1.Print("Массива b1");
b2.Print("Массива b2");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Main используется операция приведения типа DemoArray к int[] (и наоборот) в явном виде, хотя явная версия операции преобразования типа DemoArray к int[] не была определена. Объясните, почему возможно использование явного приведения типа.Point добавить:0 обращаться к полю x, по индексу 1 - к полю y, при других значениях индекса выдается сообщение об ошибке.++ ( -- ): одновременно увеличивает (уменьшает) значение полей х и у на 1 ;true и false: обращение к экземпляру класса дает значение true, если значение полей x и у совпадает, иначе false ;+: одновременно добавляет к полям х и у значение скаляра;Point в string (и наоборот).Triangle добавить:0 обращаться к полю a, по индексу 1 - к полю b, по индексу 2 - к полю c, при других значениях индекса выдается сообщение об ошибке.++ ( -- ): одновременно увеличивает (уменьшает) значение полей a, b и c на 1 ;true и false: обращение к экземпляру класса дает значение true, если треугольник с заданными длинами сторон существует, иначе false ;*: одновременно домножает поля a, b и c на скаляр;Triangle в string (и наоборот).Rectangle добавить:0 обращаться к полю a, по индексу 1 - к полю b, при других значениях индекса выдается сообщение об ошибке.++ ( -- ): одновременно увеличивает (уменьшает) значение полей a и b ;true и false: обращение к экземпляру класса дает значение true, если прямоугольник с заданными длинами сторон является квадратом, иначе false ;*: одновременно домножает поля a и b на скаляр;R ectangle в string (и наоборот).Money добавить:0 обращаться к полю first, по индексу 1 - к полю second, при других значениях индекса выдается сообщение об ошибке.++ ( -- ): одновременно увеличивает (уменьшает) значение полей first и second ;true, если поле second не нулевое, иначе false ;+: добавляет к значению поля second значение скаляра;Money в string (и наоборот).++ ( -- ): одновременно увеличивает (уменьшает) значение всех элементов массива на 1 ;true, если элементы массива не упорядочены по возрастанию, иначе false ;*: домножить все элементы массива на скаляр;++ ( -- ): одновременно увеличивает (уменьшает) значение всех элементов массива на 1 ;true и false: обращение к экземпляру класса дает значение true, если двумерный массив является квадратным;+: позволяющей сложить два массива соответсвующих размерностей;++ ( -- ): одновременно увеличивает (уменьшает) значение всех элементов массива на 1 ;true и false: обращение к экземпляру класса дает значение true, если каждая строка массива упорядоченна по возрастанию, иначе false.*: позволяющей умножить два массива соответствующих размерностей.+ ( - ): преобразующей строку к строчным (прописным) символам;true и false: обращение к экземпляру класса дает значение true, если строка не пустая, иначе false.: возвращает значение true, если строковые поля двух объектов посимвольно равны (без учета регистра), иначе false ;string (и наоборот).0 обращаться к полю r, по индексу 1 - к полю text, при других значениях индекса выдается сообщение об ошибке.-: удаляет из поля text все фрагменты, соответсвующие регулярному выражению поля r.true и false: обращение к экземпляру класса дает значение true, если поле text не пустое, иначе false ;+: дописывает в конец поля text строку.Regex в тип string (и наоборот).true, если установленная дата не является последним днем месяца, иначе false ;true и false: обращение к экземпляру класса дает значение true, если установленная дата является началом года, иначе false ;true, если поля двух объектов равны, иначе false ;DataTime в тип string (и наоборот).Используя дополнительную литературу и Интернет, рассмотрите следующие темы:
Класс - это обобщенное понятие, определяющие характеристики и поведение некоторого множества объектов, называемых экземплярами класса. "Классический" класс содержит данные, определяющие object.
Все программы, рассмотренные ранее, состояли из одного класса с одним методом Main и несколькими вспомогательными статическими методами. Теперь рассмотрим понятие "класс" более подробно.
Описание класса содержит ключевое слово class, за которым следует его имя, а далее в фигурных скобках - тело класса. Кроме того, для класса можно задать его базовые классы (предки) и ряд необязательных атрибутов и спецификаторов, определяющих различные характеристики класса:
[ атрибуты ] [ спецификаторы ] class имя_класса [ : предки ]
{тело_класса}
Простейший пример класса:
class Demo{}
Спецификаторы определяют свойства класса, а также доступность класса для других элементов программы. Возможные значения спецификаторов перечислены в следующей таблице:
| № | Спецификатор | Описание |
|---|---|---|
| 1 | new |
Задает новое описание класса взамен унаследованного от предка. Используется для вложения классов (в иерархии объектов). |
| 2 | public |
Доступ к классу не ограничен |
| 3 | protected |
Доступ только из данного или производного класса. Используется для вложенных классов. |
| 4 | internal |
Доступ только из данной программы (сборки). |
| 5 | protected internal |
Доступ только из данного и производного класса и из данной программы (сборки). |
| 6 | private |
Доступ только из элементов класса, внутри которых описан данный класс. Используется для вложенных классов. |
| 7 | static |
Статический класс. Позволяет обращатся к методам класса без создания экземпляра класса |
| 8 | sealed |
Бесплодный класс. Запрещает наследование данного класса. Применяется в иерархии объектов. |
| 9 | abstract |
Абстрактный класс. Применяется в иерархии объектов. |
Спецификаторы 2-6 называются спецификаторами доступа. Они определяют, откуда можно непосредственно обращаться к данному классу. Спецификаторы доступа могут комбинироваться с остальными спецификаторами.
Класс можно описывать непосредственно внутри пространства имен или внутри другого класса. В последнем случае класс называется вложенным. В зависимости от места описания класса некоторые из этих спецификаторов могут быть запрещены. В данном разделе мы рассмотрим классы, которые описываются непосредственно в пространстве имен. Для таких классов допускаются только два спецификатора: public и internal. По умолчанию, то есть если ни один спецификатор доступа не указан, подразумевается спецификатор internal.
Объекты создаются явным или неявным образом, то есть либо программистом, либо системой. Программист создает экземпляр класса с помощью операции new, например:
Demo a = new Demo (); // Создается экземпляр класса Demo
Если достаточный для хранения объекта объем памяти выделить не удалось, то генерируется исключение OutOfMemoryException.
Для каждого объекта при его создании в памяти выделяется отдельная область, в которой хранятся его данные. В классе могут присутствовать
До сих пор мы использовали в программах только данные (переменные и константы) и методы. В общем случае класс может содержать следующие функциональные элементы:
В данном разделе мы рассмотрим первые четыре категории элементов класса
Прежде чем приступить к проектированию классов, необходимо поговорить о присваивании и сравнении объектов. Механизм выполнения присваивания один и тот же для величин любого типа, как ссылочного, так и размерного, однако результаты различаются. При присваивании значения копируется значение, а при присваивании ссылки - ссылка, поэтому после присваивания одного объекта другому мы получим две ссылки, указывающие на одну и ту же область памяти:
(рис 13.1) 13_01
Пусть были созданы три объекта а, b и с, а затем выполнено присваивание b = с. Теперь ссылки b и с указывают на один и тот же объект. Старое значение b становится недоступным и очищается сборщиком мусора.
Аналогичная ситуация с операцией проверки на равенство. Величины b и с равны, т.к. они ссылаются на одну и ту же область памяти. Но а не равно b даже при равенстве их значений.
Данные, содержащиеся в классе, могут быть переменными или константами и задаются в соответствии с правилами, рассмотренными в теме "Идентификаторы". При описании данных также можно указывать атрибуты и спецификаторы, задающие различные характеристики элементов. Синтаксис описания элемента данных приведен ниже:
[атрибуты] [спецификаторы] [const] тип имя [ = начальное_значение ]
Рассмотрим возможные спецификаторы для данных:
| № | Спецификатор | Описание |
|---|---|---|
| 1 | new | Новое описание поля, скрывающее унаследованный элемент класса |
| 2 | public |
Доступ к элементу не ограничен |
| 3 | protected |
Доступ только из данного и производных классов |
| 4 | internal |
Доступ только из данной сборки |
| 5 | protected internal |
Доступ только из данного и производных классов и из данной сборки |
| 6 | private |
Доступ только из данного класса |
| 7 | static |
Одно поле для всех экземпляров класса |
| 8 | readonly |
Поле доступно только для чтения (значение таких полей можно установить либо при описании, либо в конструкторе) |
| 9 | |
Поле может изменяться другим процессом или системой |
Для констант можно использовать только спецификаторы 1-6.
По умолчанию элементы класса считаются закрытыми private. Для полей класса этот вид доступа является предпочтительным, поскольку поля определяют внутреннее строение класса, которое должно быть скрыто от пользователя. Все методы класса имеют непосредственный доступ к его закрытым полям.
Поля, описанные со спецификатором static, а также константы существуют в единственном экземпляре для всех объектов класса, поэтому к ним обращаются не через имя экземпляра, а через имя класса. Обращение к полю класса выполняется с помощью операции доступа (точка). Справа от точки задается имя поля, слева - имя экземпляра для обычных полей или имя класса для статических. Рассмотрим пример создания класса Demo и два способа обращения к его полям.
class Circle
{
public int x=0;
public int y=0;
public int radius=3;
public const double pi = 3.14;
public static string name = "Окружность";
double p;
double s;
}
class Program
{
static void Main()
{
Circle cr = new Circle(); //создание экземпляра класса
Console.WriteLine("pi=" + Circle.pi);// обращение к константе
Console.Write(Circle.name);// обращение к статическому полю
//обращение к обычным полям
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", cr.x, cr.y, cr.radius);
// Console.WriteLine(cr.p); - вызовет ошибку, т.к. поле p имеет тип private
Console.Write("Введите коэффициент= ");
int kof = int.Parse(Console.ReadLine());
cr.x -= kof; cr.y += kof; cr.radius *= kof;
Console.WriteLine(" Новая окружность с центром в точке ({0},{1}) и радиусом {2}",
cr.x, cr.y, cr.radius);
//cr.s = 2 * Circle.pi * cr.radius; - вызовет ошибку, т.к. поле s имеет тип private
}
}
Методы находятся в памяти в единственном экземпляре и используются всеми объектами одного класса совместно, поэтому необходимо обеспечить работу методов нестатических экземпляров с полями именно того объекта, для которого они были вызваны. Для этого в любой нестатический метод автоматически передается скрытый параметр this, в котором хранится ссылка на вызвавший функцию экземпляр.
В явном виде параметр this применяется для того, чтобы возвратить из метода ссылку на вызвавший объект, а также для идентификации поля в случае, если его имя совпадает с именем параметра метода, например:
class Circle
{
public int x=0;
public int y=0;
public int radius=3;
public const double pi = 3.14;
public static string name = "Окружность";
public Circle T() //метод возвращает ссылку на экземпляр класса
{
return this;
}
public void Set(int x, int y, int r)
{
this.x = x;
this.y = y;
radius=r;
}
}
class Program
{
static void Main()
{
Circle cr = new Circle(); //создание экземпляра класса
Console.WriteLine("pi=" + Circle.pi);// обращение к константе
Console.Write(Circle.name);// обращение к статическому полю
//обращение к обычным полям
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", cr.x, cr.y, cr.radius);
cr.Set(1, 1, 10);
Console.WriteLine("Новая окружность с центром в точке ({0},{1}) и радиусом {2}",
cr.x, cr.y, cr.radius);
Circle b=cr.T();//получаем ссылку на объект cr, аналог b=c
Console.WriteLine("Новая ссылка на окружность с центром в точке ({0},{1})
и радиусом {2}", b.x, b.y, b.radius);
}
}
Конструктор предназначен для инициализации объекта. Конструкторы делятся на конструкторы класса (для статических классов) и конструкторы экземпляра класса (всех остальных классов).
Конструктор экземпляра вызывается автоматически при создании объекта класса с помощью операции new. Имя конструктора совпадает с именем класса. Рассмотрим основные свойства конструкторов:
void.null.До сих пор мы задавали начальные значения полей класса при описании класса. Это удобно в том случае, когда для всех экземпляров класса начальные значения некоторого поля одинаковы. Если же при создании объектов требуется присваивать полю разные значения, это следует делать с помощью явного задания конструктора. В следующем примере добавлен конструктор и метод Print для вывода информации об объекте:
class Circle
{
public int x;
public int y;
public int radius;
public const double pi = 3.14;
public static string name = "Окружность";
public Circle(int x, int y, int r)//конструктор
{
this.x = x;
this.y = y;
radius = r;
}
public void Print()
{
Console.Write(name);
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", x, y, radius);
Console.WriteLine();
}
}
class Program
{
static void Main()
{
Circle a = new Circle(0, 0, 1); //вызов конструктора
a.Print();
Circle b=new Circle(10, 10, 5);//вызов конструктора
b.Print();
}
}
Часто бывает удобно задать в классе несколько конструкторов, чтобы обеспечить возможность инициализации объектов разными способами. Для этого конструкторы должны иметь разные сигнатуры. Рассмотрим это на примере:
class Circle
{
public int x;
public int y;
public int radius;
public const double pi = 3.14;
public static string name = "Окружность";
public Circle(int x, int y, int r)//конструктор 1
{
this.x = x;
this.y = y;
radius = r;
}
public Circle(int r)//конструктор 2
{
radius = r;
}
public void Print()
{
Console.Write(name);
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", x, y, radius);
Console.WriteLine();
}
}
class Program
{
static void Main()
{
Circle a = new Circle(0, 0, 1); //вызов конструктора 1
a.Print();
Circle b=new Circle(5);//вызов конструктора 2
b.Print();
}
}
Обратите внимание на то, что в конструктое 2 не были инициализированы поля x, y, поэтому им присваивается значение 0.
Если один из конструкторов выполняет какие-либо действия, а другой должен делать то же самое плюс еще что-нибудь, то удобно вызвать первый конструктор из второго. Для этого используется уже известное вам ключевое слово this в другом контексте, например:
public Circle(int x, int y, int r):this(r) //конструктор 1
{
this.x = x;
this.y = y;
}
public Circle(int r) //конструктор 2
{
radius = r;
}
Конструкция, находящаяся после двоеточия, называется
Статические классы содержат только статические члены, в том числе и конструктор, которые хранятся в памяти в единственном экземпляре. Поэтому создавать экземпляры класса нет смысла.
Чтобы подчеркнуть этот факт, в первой версии С# для статических классов создавали два конструктора, один - пустой закрытый (private) конструктор, второй - статический конструктор, не имеющий параметров. Первый конструктор предотвращал попытки создания экземпляров класса. Второй конструктор автоматически вызывается системой до первого обращения к любому элементу статического класса, выполняя необходимые действия по инициализации. Вышесказанное отражено в следующем примере:
class Demo
{
static int a;
static int b;
private Demo(){} //закрытый конструктор
static Demo() //статический конструктор
{
a=10;
b=2;
}
public static void Print ()
{
Console.WriteLine("{0}+{1}={2}",a,b,a+b);
Console.WriteLine("{0}*{1}={2}",a,b,a*b);
Console.WriteLine("{0}-{1}={2}",a,b,a-b);
}
}
class Program
{
static void Main()
{
//Demo S=new Demo(); //ошибка содать экземпляр класса нельзя
Demo.Print();
}
}
В версию 2.0 введена возможность описывать статический класс, то есть класс с модификатором static. Экземпляры такого класса создавать запрещено, и кроме того, от него запрещено наследовать. Все элементы такого класса должны явным образом объявляться с модификатором static (константы и
static class Demo
{
static int a=20;
static int b=10;
public static void Print ()
{
Console.WriteLine("{0}+{1}={2}",a,b,a+b);
Console.WriteLine("{0}*{1}={2}",a,b,a*b);
Console.WriteLine("{0}-{1}={2}",a,b,a-b);
}
}
class Program
{
static void Main()
{
Demo.Print();
}
}
Иногда требуется создать поле, которое с одной стороны, должно быть доступно для использования, с другой стороны, возможность что-то сделать с этим полем имеет ограничения. Например, полю нельзя присваивать произвольное значение, а только значения из какого-то диапазона. Свойство предлагает простой и удобный способ решения этой проблемы.
Синтаксис свойства:
[атрибуты] [спецификаторы] тип имя_свойства
{
[get код_доступа]
[set код_доступа]
}
Значения спецификаторов для свойств и методов аналогичны. Чаще всего свойства объявляются как открытые (со спецификатором public ).
Код доступа представляет собой блоки операторов, которые выполняются при получении ( get ) или установке ( set ) свойства. Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, то свойство доступно только для чтения. Если отсутствует часть get, то свойство доступно только для записи.
Рассмотрим пример работы со свойствами, обращая внимание на то, что синтаксически чтение и запись свойства выглядят почти как методы. При этом get должен содержать оператор return, возвращающий выражение, для типа которого должно существовать
class Circle
{
//закрытые поля
int x;
int y;
int radius;
public static string name = "Окружность";
public Circle(int x, int y, int r):this(r)//конструктор 1
{
this.x = x;
this.y = y;
}
public Circle(int r)//конструктор 2
{
radius = r;
}
public void Print()
{
Console.Write(name);
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", x, y, radius);
Console.WriteLine();
}
public int X //свойство для обращения к полю x
{
get
{
return x;
}
set
{
x = value;
}
}
public int Y //свойство для обращения к полю y
{
get
{
return y;
}
set
{
y = value;
}
}
public int R //свойство для обращения к полю radius
{
get
{
return radius;
}
set
{
radius = value;
}
}
public double P //свойство только для чтения
{
get
{
return 2* Math.PI *radius;
}
}
public double S //свойство только для чтения
{
get
{
return Math.PI *radius*radius;
}
}
}
class Program
{
static void Main()
{
Circle a = new Circle(0, 0, 1); //вызов конструктора
a.Print();
//установка новых значений
a.X=1;
a.Y=1;
a.R=10;
//a.S=100; //ошибка - свойство доступно только для чтения
Console.WriteLine("центр=({0},{1})
радиус={2}
периметр={3:f2}
площадь={4:f2}",
a.X, a.Y, a.R, a.P, a.S);
}
}
С добавлением новых классов в программу резко увеличивается ее размер, что затрудняет ее прочтение. Поэтому следует руководствоваться одним простым принципом "один класс - один файл". Для того чтобы создать новый файл для класса Circle выполним следующие действия:
(рис 13.2) 13_02
(рис 13.3) 13_03
В поле Name напишите Сircle.cs и нажмите кнопку Add.
Теперь окно Solution Explorer выглядит следующим образом:
(рис 13.4) 13_04
А файл Circle.cs выглядит следующим образом:
using System;
namespace ConsoleApplication1
{
public class Circle
{
public Circle()
{
//
// TODO: Add constructor logic here
//
}
}
}
namespace ConsoleApplication1 на namespace MyProgram, для того чтобы идентификаторы файлов Program.cs и Circle.cs были определены в одном пространстве имен.Circle из файла Program.cs в файл Circle.cs.Point, разработав следующие элементы класса:int x, y;(a, b).Triangle , разработав следующие элементы класса:int a, b, c;Rectangle, разработав следующие элементы класса:int a, b;Money, разработав следующие элементы класса:int first;//номинал купюрыint second; //количество купюрint [] IntArray;int n.int [,] IntArray;int n.nxn.i -того столбца.double [][] DoubelArray;int n, m.nxm.StringBuilder Line ;int n.n символов.Regex r ;string text ;DataTime data.1.01.2009Используя дополнительную литературу и Интернет, рассмотрите следующие темы:
В С# существует специальный вид метода, называемый деструктором, который вызывается сборщиком мусора непосредственно перед удалением объекта из памяти.
В деструкторе описываются действия, гарантирующие корректность последующего удаления объекта. Например, проверяется все ли ресурсы, используемые объектом, освобождены (файлы закрыты,
Синтаксис деструктора:
[атрибуты] [extern] ~имя_класса()
{тело_деструктора}
Деструктор не имеет параметров, не возвращает значения и не требует указания спецификаторов доступа. Его имя совпадает с именем класса и предваряется ~ ), символизирующей обратные по отношению к конструктору действия. Тело деструктора представляет собой блок или просто точку с запятой. Если деструктор определен как внешний, то используется спецификатор extern. Пример работы деструктора:
class DemoArray
{
int[] MyArray;//закрытый массив
string name; //закрытое поле
public DemoArray(int size,int x, string name)//конструктор
{
MyArray = new int[size];
this.name = name;
for (int i=0;i<size; ++i) MyArray[i]=x;
}
public void Print ()//метод
{
Console.Write(name+ " : ");
foreach (int a in MyArray) Console.Write(a+" ");
Console.WriteLine();
}
public int LengthN //свойство
{
get { return MyArray.Length; }
}
~DemoArray()//деструктор
{
Console.WriteLine("сработал деструктор для объекта "+this.name);
}
}
class Program
{
static void Main()
{
DemoArray a= new DemoArray(5,2, "один");
a.Print();
DemoArray b = new DemoArray(6,1, "два");
b.Print();
a = b;
a.Print();
}
}
В общем случае применение деструкторов замедляет процесс сборки мусора. Поэтому создавать деструкторы следует только тогда, когда необходимо освободить какие-то ресурсы перед удалением объекта.
Индексатор представляет собой разновидность свойства и обычно применяется для организации доступа к скрытым полям класса по индексу, например, так же, как мы обращаемся к элементу массива. Синтаксис индексатора аналогичен синтаксису свойства:
[атрибуты] [спецификаторы] тип this [список параметров] // последние [ ] являются элементами синтаксиса
{
[get код_доступа]
[set код_доступа]
}
Спецификаторы аналогичны спецификаторам свойств и методов. Индексаторы чаще всего объявляются со спецификатором public, поскольку они входят в
Код доступа представляет собой блоки операторов, которые выполняются при получении ( get ) или установке ( set ) значения некоторого элемента класса. Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, индексатор доступен только для чтения, если отсутствует часть get, индексатор доступен только для записи.
Список параметров содержит одно или несколько описаний индексов, по которым выполняется доступ к элементу. Чаще всего используется один индекс целого типа.
В качестве примера рассмотрим индексатор, который позволяет получить n-член последовательности Фиббоначи:
class DemoFib
{
public int this[int i] //индексатор, доступный только для чтения
{
get
{
if (i <=0) throw new Exception("недопустимое значение индекса");
else if (i==1 || i==2) return 1;
else
{int a=1, b=1, c;
for (int j=3; j<=i; ++j)
{
c=a+b;
a=b;
b=c;
}
return b;
}
}
}
}
class Program
{
static void Main()
{
Console.Write("n=");
int n=int.Parse(Console.ReadLine());
DemoFib a=new DemoFib();
try
{
Console.WriteLine("a[{0}]={1}",n,a[n]);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Индексаторы очень удобно применять для создания специализированных массивов, на работу с которыми накладываются какие-либо ограничения. Рассмотрим в качестве примера класс-массив, значения элементов которого находятся в диапазоне [0, 100]. Кроме того, при доступе к элементу проверяется, не вышел ли индекс за допустимые границы.
class DemoArray
{
int[] MyArray;//закрытый массив
public DemoArray(int size)//конструктор
{
MyArray = new int[size];
}
public int LengthArray //свойство, возвращающее размерность
{
get { return MyArray.Length; }
}
public int this[int i] //индексатор
{
get
{
if (i <0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
else return MyArray[i];
}
set
{
if (i <0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
else if (value >= 0 value <= 100) MyArray[i] = value;
else throw new Exception("присваивается недопустимое значение");
}
}
}
class Program
{
static void Main()
{
DemoArray a = new DemoArray(10);
for (int i=0; i<a.LengthArray; i++)
{
a[i] = i * i; // использование индексатора в режиме записи
Console.Write(a[i]+" ");// использование индексатора в режиме чтения
}
Console.WriteLine();
try
{
//a[10]=100;
//a[0]=200;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
try содержатся две закомментированные команды. Посмотрите, как проведет себя программа, если убрать комментарии: вначале один, затем другой.Язык С# допускает использование многомерных индексаторов. Они применяются для работы с многомерными массивами. Рассмотрим на примере предыдущую задачу при условии, что организуется двумерный массив.
class DemoArray
{
int[,] MyArray;//закрытый массив
int n, m;//закрытые поля: размерность массива
public DemoArray(int sizeN, int sizeM)//конструктор
{
MyArray = new int[sizeN, sizeM];
this.n = sizeN;
this.m = sizeM;
}
public int LengthN //свойство, возвращающее количество строк
{
get { return n; }
}
public int LengthM //свойство, возвращающее количество строк
{
get { return m; }
}
public int this[int i, int j] //индексатор
{
get
{
if (i < 0 || i >= n || j < 0 || j >= m) throw new Exception("выход за границы массива");
else return MyArray[i, j];
}
set
{
if (i < 0 || i >= n || j < 0 || j >= m) throw new Exception("выход за границы массива");
else if (value >= 0 value <= 100) MyArray[i, j] = value;
else throw new Exception("присваивается недопустимое значение");
}
}
}
class Program
{
static void Main()
{
DemoArray a = new DemoArray(3, 3);
for (int i = 0; i < a.LengthN; i++,Console.WriteLine())
{
for (int j = 0; j < a.LengthM; j++)
{
a[i, j] = i *j; // использование индексатора в режиме записи
Console.Write("{0,5}", a[i, j]);// использование индексатора в режиме чтения
}
}
Console.WriteLine();
try
{
//Console.WriteLine(a[3,3]);
//a[0,0]=200;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
try содержатся две закомментированные команды. Посмотрите, как проведет себя программа если убрать комментарии - вначале один, затем другой.С# позволяет переопределить большинство операций так, чтобы при использовании их объектами конкретного класса выполнялись действия, отличные от стандартных. Это дает возможность применять объекты собственных типов данных в составе выражений, например:
newObject x, y, z; ... z = x+y; // используется операция сложения, переопределенная для класса newObject
Определение собственных операций класса называют
Операции класса описываются с помощью методов специального вида, синтаксис которых выглядит следующим образом:
[ атрибуты] спецификаторы объявитель_операции
{тело}
В качестве спецификаторов одновременно используются ключевые слова public и static. Кроме того, операцию можно объявить как внешнюю - extern. Объявление операции может выглядеть по-разному, в зависимости от того, что мы перегружаем: унарную или бинарную операцию.
При описании операций необходимо соблюдать следующие правила:
public static );ref и out );В классе можно переопределять следующие унарные операции: + - ! ~ ++ --, а также константы true и false. При этом, если была перегружена константа true, то должна быть перегружена и константа false, и наоборот.
Синтаксис объявителя унарной операции:
тип operator унарная_операция (параметр)
Примеры заголовков унарных операций:
public static int operator + (DemoArray m) public static DemoArray operator --(DemoArray m) public static bool operator true (DemoArray m)
Параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. При этом операция должна возвращать:
+, -, !, ~ величину любого типа;++, -- величину типа класса, для которого она определяется;true и false величину типа bool.Операции не должны изменять значение передаваемого им операнда. Операция, возвращающая величину типа класса, для которого она определяется, должна создать новый объект этого класса, выполнить с ним необходимые действия и передать его в качестве результата.
В качестве примера рассмотрим класс DemoArray, реализующий одномерный массив, в котором содержатся следующие функциональные элементы:
true и false (при обращении к объекту будет возвращаться значение true, если все элементы массива положительные, в противном случае, будет возвращаться значение false).class DemoArray
{
int[] MyArray;//закрытый массив
public DemoArray(int size)//конструктор 1
{
MyArray = new int[size];
}
public DemoArray(params int[] arr)//конструктор 2
{
MyArray = new int[arr.Length];
for (int i = 0; i < MyArray.Length; i++) MyArray[i] = arr[i];
}
public int LengthArray //свойство, возвращающее размерность
{
get { return MyArray.Length; }
}
public int this[int i] //индексатор
{
get
{
if (i < 0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
return MyArray[i];
}
set
{
if (i < 0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
else MyArray[i] = value;
}
}
public static DemoArray operator -(DemoArray x) //перегрузка операции унарный минус
{
DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i] = -x[i];
return temp;
}
public static DemoArray operator ++(DemoArray x) //перегрузка операции инкремента
{
DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i] = x[i]+1;
return temp;
}
public static bool operator true(DemoArray a) //перегрузка константы true
{
foreach (int i in a.MyArray)
{
if (i<0)
{
return false;
}
}
return true;
}
public static bool operator false(DemoArray a)//перегрузка константы false
{
foreach (int i in a.MyArray)
{
if (i>0)
{
return true;
}
}
return false;
}
public void Print(string name) //метод - выводит поле-массив на экран
{
Console.WriteLine(name + ": ");
for (int i = 0; i < MyArray.Length; i++)
Console.Write(MyArray[i] + " ");
Console.WriteLine();
}
}
class Program
{
static void Main()
{
try
{
DemoArray Mas = new DemoArray(1, -4, 3, -5, 0); //вызов конструктора 2
Mas.Print("Исходный массив");
Console.WriteLine("\nУнарный минус");
DemoArray newMas=-Mas; //применение операции унарного минуса
Mas.Print("Mассив Mas"); //обратите внимание, что создается новый объект и знаки меняются
newMas.Print("Массив newMas"); //только у нового массива
Console.WriteLine("\nОперация префиксного инкремента");
DemoArray Mas1=++Mas;
Mas.Print("Mассив Mas");
Mas1.Print("Mассив Mas1=++Mas");
Console.WriteLine("\nОперация постфиксного инкремента");
DemoArray Mas2=Mas++;
Mas.Print("Mассив Mas");
Mas2.Print("Mассив Mas2=Mas++");
if (Mas)
Console.WriteLine("\nВ массиве все элементы положительные\n");
else Console.WriteLine("\nВ массиве есть не положительные элементы\n");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
DemoArray переопределение унарного плюса (все элементы массива преобразуются в положительные), унарного декремента (все элементы массива уменьшаются на единицу)При разработке класса можно перегрузить следующие бинарные операции: + - * / % | ^ << >> == != < > <= >=. Обратите внимание, операций присваивания в этом списке нет.
Синтаксис объявителя бинарной операции:
тип operator бинарная_операция (параметр1, параметр 2)
Примеры заголовков бинарных операций:
public static DemoArray operator + (DemoArray a, DemoArray b) public static bool operator == (DemoArray a, DemoArray b)
При переопределении бинарных операций нужно учитывать следующие правила:
В качестве примера вернемся к классу DemoArray, реализующему одномерный массив, и добавим в него две версии +:
class DemoArray
{
...
public static DemoArray operator +(DemoArray x, int a) //вариант 1
{
DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i]=x[i]+a;
return temp;
}
public static DemoArray operator +(DemoArray x, DemoArray y) //вариант 2
{
if (x.LengthArray == y.LengthArray)
{
DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i] = x[i] + y[i];
return temp;
}
else throw new Exception("несоответствие размерностей");
}
}
class Program
{
static void Main()
{
try
{
DemoArray a = new DemoArray(1, -4, 3, -5, 0);
a.Print("Массива a");
DemoArray b=a+10;
b.Print("\nМассива b");
DemoArray c = a+b;
c.Print("\nМассива c");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
DemoArray переопределение бинарного минуса (из всех элементов массива вычитается заданное число) и операции (поэлементно сравнивает два массива, если соответствующие элементы попарно совпадают, то операция возвращает значение true, иначе false ).Операции преобразования типов обеспечивают возможность явного и
explicit operator целевой_тип (параметр) //явное преобразование implicit operator целевой_тип (параметр) //неявное преобразование
Эти операции выполняют преобразование из типа параметра в тип, указанный в заголовке операции. Одним из этих типов должен быть класс, для которого выполняется преобразование.
Явное преобразование выполняется при использовании операции приведения типа.
При определении операции преобразования типа следует учитывать следующие особенности:
Следовательно, для одного и того класса нельзя определить одновременно и явную, и неявную версию. Однако, т.к.
В качестве примера вернемся к классу DemoArray, реализующему одномерный массив, и добавим в него неявную версию переопределения типа DemoArray в тип одномерный массив и наоборот:
class DemoArray
{
…
public static implicit operator DemoArray (int []a) //неявное преобразование типа int [] в DemoArray
{
return new DemoArray(a);
}
public static implicit operator int [](DemoArray a) //неявное преобразование типа DemoArray в int []
{
int []temp=new int[a.LengthArray];
for (int i = 0; i < a.LengthArray; ++i)
temp[i] = a[i];
return temp;
}
}
class Program
{
static void arrayPrint(string name, int[]a) //метод, который позволяет вывести на экран одномерный массив
{
Console.WriteLine(name + ": ");
for (int i = 0; i < a.Length; i++)
Console.Write(a[i] + " ");
Console.WriteLine();
}
static void Main()
{
try
{
DemoArray a = new DemoArray(1, -4, 3, -5, 0);
int []mas1=a; //неявное преобразование типа DemoArray в int []
int []mas2=(int []) a; //явное преобразование типа DemoArray в int []
DemoArray b1 =mas1; //неявное преобразование типа int [] в DemoArray
DemoArray b2 =(DemoArray)mas2; //явное преобразование типа int [] в DemoArray
//изменение значений
mas1[0]=0; mas2[0]=-1;
b1[0]=100; b2[0]=-100;
//вывод на экран
a.Print("Массива a");
arrayPrint("Maccив mas1", mas1);
arrayPrint("Maccив mas2", mas2);
b1.Print("Массива b1");
b2.Print("Массива b2");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Main используется операция приведения типа DemoArray к int[] (и наоборот) в явном виде, хотя явная версия операции преобразования типа DemoArray к int[] не была определена. Объясните, почему возможно использование явного приведения типа.Point добавить:0 обращаться к полю x, по индексу 1 - к полю y, при других значениях индекса выдается сообщение об ошибке.++ ( -- ): одновременно увеличивает (уменьшает) значение полей х и у на 1 ;true и false: обращение к экземпляру класса дает значение true, если значение полей x и у совпадает, иначе false ;+: одновременно добавляет к полям х и у значение скаляра;Point в string (и наоборот).Triangle добавить:0 обращаться к полю a, по индексу 1 - к полю b, по индексу 2 - к полю c, при других значениях индекса выдается сообщение об ошибке.++ ( -- ): одновременно увеличивает (уменьшает) значение полей a, b и c на 1 ;true и false: обращение к экземпляру класса дает значение true, если треугольник с заданными длинами сторон существует, иначе false ;*: одновременно домножает поля a, b и c на скаляр;Triangle в string (и наоборот).Rectangle добавить:0 обращаться к полю a, по индексу 1 - к полю b, при других значениях индекса выдается сообщение об ошибке.++ ( -- ): одновременно увеличивает (уменьшает) значение полей a и b ;true и false: обращение к экземпляру класса дает значение true, если прямоугольник с заданными длинами сторон является квадратом, иначе false ;*: одновременно домножает поля a и b на скаляр;R ectangle в string (и наоборот).Money добавить:0 обращаться к полю first, по индексу 1 - к полю second, при других значениях индекса выдается сообщение об ошибке.++ ( -- ): одновременно увеличивает (уменьшает) значение полей first и second ;true, если поле second не нулевое, иначе false ;+: добавляет к значению поля second значение скаляра;Money в string (и наоборот).++ ( -- ): одновременно увеличивает (уменьшает) значение всех элементов массива на 1 ;true, если элементы массива не упорядочены по возрастанию, иначе false ;*: домножить все элементы массива на скаляр;++ ( -- ): одновременно увеличивает (уменьшает) значение всех элементов массива на 1 ;true и false: обращение к экземпляру класса дает значение true, если двумерный массив является квадратным;+: позволяющей сложить два массива соответсвующих размерностей;++ ( -- ): одновременно увеличивает (уменьшает) значение всех элементов массива на 1 ;true и false: обращение к экземпляру класса дает значение true, если каждая строка массива упорядоченна по возрастанию, иначе false.*: позволяющей умножить два массива соответствующих размерностей.+ ( - ): преобразующей строку к строчным (прописным) символам;true и false: обращение к экземпляру класса дает значение true, если строка не пустая, иначе false.: возвращает значение true, если строковые поля двух объектов посимвольно равны (без учета регистра), иначе false ;string (и наоборот).0 обращаться к полю r, по индексу 1 - к полю text, при других значениях индекса выдается сообщение об ошибке.-: удаляет из поля text все фрагменты, соответсвующие регулярному выражению поля r.true и false: обращение к экземпляру класса дает значение true, если поле text не пустое, иначе false ;+: дописывает в конец поля text строку.Regex в тип string (и наоборот).true, если установленная дата не является последним днем месяца, иначе false ;true и false: обращение к экземпляру класса дает значение true, если установленная дата является началом года, иначе false ;true, если поля двух объектов равны, иначе false ;DataTime в тип string (и наоборот).Используя дополнительную литературу и Интернет, рассмотрите следующие темы:
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.