Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
387.doc
Скачиваний:
11
Добавлен:
30.04.2022
Размер:
3.51 Mб
Скачать

Теоретические сведения

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

Класс часто становятся модулем - единицей построения программной системы.

Абстракция данных, выделенная для конкретной предметной области реализуется в виде класса.

Синтаксис класса:

[<атрибуты>] class <имяКласса>[:<списокРодителей>]

{

<телоКласса>

}

Таблица 9

Атрибут

Значение

Отсутствует либо internal

Класс доступен только в рамках текущего проекта

Public

Класс доступен отовсюду

abstract или internal abstract

Класс доступен только в рамках текущего проекта, не допускает создания экземпляров, может наследоваться

sealed или internal sealed

Класс доступен только в рамках текущего проекта, допускает создания экземпляров, не может наследоваться

public abstract

Класс доступен отовсюду, не допускает создания экземпляров, может наследоваться

public sealed

Класс доступен отовсюду, допускает создания экземпляров, не может наследоваться

Обычно класс имеет атрибут доступа internal - значение по умолчанию.

class myClass

{ тело_класса }

В теле класса могут быть определены его члены:

  • константы;

  • поля и свойства;

  • методы, в том числе конструкторы и деструкторы;

  • события;

  • делегаты;

  • интерфейсы;

  • классы.

Каждый член имеет модификатор доступа, принимающий одно из четырех значений:

  • public - общий, член, доступный из любого кода;

  • private – частный, доступный только из того кода, который является составной частью данного класса;

  • protected – защищенный, доступный только из кода, являющегося составной частью данного класса или из производного класса;

  • internal – внутренний, доступный только внутри проекта (модуля), в котором он определен.

По умолчанию член класса имеете доступ private

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

Поля класса синтаксически являются обычными переменными (объектами) языка. Их описание удовлетворяет обычным правилам объявления переменных.

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

Для организации доступа к состоянию лучше использовать свойства, а не поля.

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

Если поля класса A должны быть доступны для методов класса B, являющегося потомком класса A, то эти поля следует снабдить атрибутом protected (защищенные).

Если поля должны быть доступны для методов классов B1, B2, …, дружественных к классу A, то эти поля следует снабдить атрибутом internal, а все дружественные классы поместить в один проект (assembly). Такие поля называются внутренними или дружественными.

Если некоторые поля должны быть доступны для методов любого класса B, которому доступен сам класс A, то эти поля снабжаются атрибутом public. Такие поля называются общедоступными или открытыми.

Для полей может использоваться ключевое слово readonly, означающее, что значение поля может быть задано только в процессе выполнения конструктора или при начальном присваивании:

class myClass

{

public readonly int MyInt=17;

}

Доступ к статическим полям может осуществляться из класса, в котором они описаны (MyClass.MyInt), но не через экземпляр объектов этого класса:

class myClass

{

public static int MyInt;

}

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

Основная структура свойства включает модификатор доступа, за которым идет имя типа, имя свойства и 1 или оба блока set и get, в которых содержится код обработки свойства.

public int MyIntProp

{

get

{

//код для получения значения свойства

}

set

{

//код для задания значения свойству

}

}

Блок get должен обладать возвращаемым значением того же типа, что и само свойство.

private int myInt; //частное поле

public int MyInt //открытое свойство

{

get

{

return myInt;

}

set

{ }

}

Блок set присваивает значение полю: используется ключевое слово value, чтобы сослаться на значение, полученное от пользователя

private int myInt;

public int MyInt

{

get

{

return myInt;

}

set

{ myInt=value; }

}

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

public int MyInt //свойство

{

get

{ return myInt;

}

set

{ if ( value >= 0 && value <= 10 )

myInt=value;

}

}

Методы класса синтаксически являются обычными процедурами и функциями языка. Содержательно методы определяют ту самую абстракцию данных, которую реализует класс.

class MyClass

{

public string GetString()

{

return “Это строка”;

}

}

Методы содержат описания операций, доступных над объектами класса. Два объекта одного класса имеют один и тот же набор методов.

Каждый метод имеет модификатор доступа, принимающий одно из четырех значений: public, private, protected, internal. Атрибутом доступа по умолчанию является атрибут private.

При определении метода можно задавать ключевые слова:

virtual – метод может быть переопределен;

abstract – метод должен быть переопределен (допускается только в абстрактных классах;

override – метод переопределяет метод базового класса,

extern – определение метода где-то в другом месте.

public class MyBaseClass

{

public virtual void DoSomething()

{

//базовая реализация

}

}

public class MyDerivClass : MyBaseClass

{

public override void DoSomething()

{

//переопределение базовой реализации

}

}

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

У класса могут быть поля, связанные не с объектами, а с самим классом. Эти поля объявляются как статические с модификатором static. Статические поля доступны всем методам класса. Независимо от того, какой объект вызвал метод, используются одни и те же статические поля, позволяя методу использовать информацию, созданную другими объектами класса.

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

В классе могут быть объявлены константы.

Константы фактически являются статическими полями, доступными только для чтения, значения которых задаются при инициализации. Однако задавать модификатор static для констант запрещено.

Конструктор - неотъемлемый компонент класса. Нет классов без конструкторов. Конструктор позволяет создавать объекты класса. Его имя совпадает с именем класса.

Если программист не задал конструктор класса, то к классу автоматически добавляется конструктор по умолчанию - конструктор без аргументов.

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

В большинстве случаев нет необходимости описывать соответствующие классу конструкторы и деструкторы, поскольку объект базового класса System.Object обеспечивает их реализацию по умолчанию.

Добавить в класс конструктор можно так:

class MyClass

{

public MyClass()

{ //код конструктора

}

}

Конструктор обладает тем же именем, что и класс, не имеет параметров (это конструктор по умолчанию) и является общим, что позволяет создавать экземпляры класса.

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

class MyClass

{

private MyClass()

{ //код конструктора

}

}

Можно задать конструктор с параметрами

class MyClass

{

public MyClass()

{ //код конструктора

}

public MyClass(int myInt)

{ //код конструктора не по умолчанию

}

}

Количество задаваемых конструкторов не ограничено.

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

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

В классе можно объявить статический конструктор с атрибутом static. Он вызывается автоматически - его не нужно вызывать стандартным образом. Точный момент вызова не определен, но гарантируется, что вызов произойдет до создания первого объекта класса. Такой конструктор может выполнять работу, которую нужно выполнить один раз: связаться с базой данных, заполнить значения статических полей класса, создать константы класса, выполнить другие подобные действия. Статический конструктор, вызываемый автоматически, не должен иметь модификаторов доступа.

Деструктор, используемый в .NET Framework и предоставляемый классом System.Object, называется Finalize(). Но для объявления деструктора используется другое имя. Имя деструктора строится из имени класса с предшествующим ему символом ~ (тильда). Как и у статического конструктора, у деструктора не указывается модификатор доступа.

class MyClass

{

~MyClass()

{ //код деструктора

}

}

Код, заключенный в деструкторе, будет выполняться при сборке мусора, освобождая занятые ресурсы. После вызова деструктора происходят явные вызовы деструкторов базовых классов, включая метод Finalize() в корневом классе System.Object.

В .NET удаление объектов, после того, как они стали не нужными, производит сборщик мусора.

В языке C# y класса может быть деструктор, но он не занимается удалением объектов и не вызывается нормальным образом в ходе выполнения программы. Так же, как и статический конструктор, деструктор класса, если он есть, вызывается автоматически в процессе сборки мусора. Его роль - в освобождении ресурсов, например, файлов, открытых объектом.

Рассмотрим пример проектирования класса Matr, позволяющего хранить двумерную матрицу размером m*n, сравнивать 2 матрицы по признакам: матрицы равны, если равны соответствующие суммы строк матриц; матрица меньше второй матрицы, если сумма элементов главной диагонали первой матрицы больше соответствующей суммы второй матрицы. Следует заметить, что для получения главной диагонали матрицы необходимо, чтобы матрица была квадратной.

Удобнее всего начать разработку программы с построения диаграммы классов, выполняемого с помощью специального средства Visual Studio – средства автоматизированного проектирования классов. Средство позволяет визуально представить класс, наполнить его полями, свойствами и т.п., а затем автоматически генерирует код для спроектированных классов.

Для вызова построителя диаграммы надо открыть вкладку ClassDiagram1.cd, например, с помощью команды View Class Diagram контекстного меню для текущего проекта (рис. 30).

В верхней части окна в рабочей области с помощью команд контекстного меню добавляются новые классы, интерфейсы, перечисления, делегаты, структуры, абстрактные классы, комментарии, а также их содержимое. Каждый новый компонент принято помещать в отдельный файл, имя файла обычно совпадает с именем компонента. Такая стратегия вполне естественна в ООП и согласуется с его принципами.

В нижней части окна на панели Class Details можно откорректировать характеристики всех членов классов, интерфейсов и др. Автоматически Visual Studio создаст код – заготовку спроектированных компонентов (рис. 30).

В примере с классом Matr создадим закрытые поля:

int rowCount – число строк матрицы;

int colCount – число столбцов матрицы;

int[,] mas – элементы матрицы.

Эти поля добавляются в класс на диаграмме классов. Активизируется прямоугольник – класс Matr, через команду контекстного меню Add/Fields вводится имя поля, а в нижней части окна построителя диаграмм корректируются тип и атрибут доступа поля.

Рис. 30. Окно построения диаграммы классов

Доступ клиента к закрытым полям, естественно, запрещен. Для закрытых полей, если это необходимо, для записи/чтения создают открытые свойства. Свойство RowCount позволит только читать закрытое поле rowCount:

public int RowCount

{

get

{

return rowCount;

}

}

Свойство ColCount позволит только читать закрытое поле сolCount:

public int ColCount

{

get { return colCount; }

}

Свойство ColCount позволит только записывать значения элементов матрицы в закрытое поле:

public int[,] Mas

{

set

{

mas = value;

}

}

Таким образом, имея матрицу соответствующей размерности, можно ее элементами заполнить поле mas класса Matr.

Конечно же, не забудем и о конструкторах. Обойтись лишь одним конструктором по умолчанию сложно, т.к. матрица нулевой размерности – это скорее отсутствие матрицы. Вспомним, что по умолчанию все поля заполняются нулевыми значениями своего типа. Поэтому оперделим свой конструктор по умолчанию, который создает единичную матрицу размерностью 3 на 3. На диаграмме классов в класс Matr добавим 2 конструктора: по умолчанию и с параметрами – размерностью матрицы. Поскольку конструкторы отличаются списком параметров, то их наличие правомочно, и они являются перегруженными.

Сразу же можно перейти в автоматически сгенерированный код класса и откорректировать конструкторы.

public Matr()

{

rowCount = 3;

colCount = 3;

mas = new int[rowCount, colCount];

for (int i = 0; i < rowCount; i++)

for (int j = 0; j < colCount; j++)

mas[i, j] = 1;

}

Конструктор с параметрами создает матрицу размерности rowCount*colCount с элементами, получающими случайные значения из промежутка [10; 100).

public Matr(int rowCount, int colCount)

{

this.rowCount = rowCount;

this.colCount = colCount;

mas = new int[rowCount, colCount];

Random myRand = new Random();

for (int i = 0; i < rowCount; i++)

for (int j = 0; j < colCount; j++)

mas[i, j] = myRand.Next(10, 100);

}

Отметим, что конструктор класса может быть достаточно сложным, и следовало бы добавить код проверки корректности передаваемых пераметров – размерности матрицы.

Определим методы нашего класса.

Если поля класса почти всегда закрываются, чтобы скрыть от пользователя представление данных класса, то методы класса всегда имеют открытую часть – сервисы, которые класс предоставляет своим клиентам и наследникам. Но не все методы открываются. Большая часть методов класса может быть закрытой, скрывая от клиентов детали реализации, необходимые для внутреннего использования.

Сокрытие представления и реализации делается не по соображениям утаивания того, как реализована система. Чаще всего, ничто не мешает клиентам ознакомиться с полным текстом класса. Сокрытие делается в интересах самих клиентов. При сопровождении программной системы изменения в ней неизбежны. Клиенты не почувствуют на себе негативные последствия изменений, если они делаются в закрытой части класса. Чем больше закрытая часть класса, тем меньше влияние изменений на клиентов класса.

Метод SumRows() будет рассчитывать суммы строк матрицы. Добавим его в диаграмму класса Matr и сразу же откорректируем код метода. Крнечно, методу не нужны параметры, ему и без того доступны все поля и методы класса. Метод возвращает массив вычисленных сумм строк матрицы.

public int[] SumRows()

{

int[] sumRows = new int[rowCount];

for (int i = 0; i < rowCount; i++)

{

sumRows[i] = 0;

for (int j = 0; j < colCount; j++)

sumRows[i] += mas[i, j];

}

return sumRows;

}

Второй метод рассчитывает сумму диагональных элементов матрицы.

//Возвращает сумму диагональных элементов

public int SumDiag()

{

int sumDiag = 0;

for (int i = 0; i < rowCount; i++)

{

sumDiag += mas[i, i];

}

return sumDiag;

}

При работе с объектом возникает задача вывода на экран его содержимого в приемлимом для клиента виде. Клиенту удобно (в консольном приложении) воспользоваться консольным выводом в виде Console.WriteLine(<объект>), следовательно, надо в классе переопределить метод ToString(). Добавим в диаграмму класса Matr этот метод и отредактируем его тело. Метод возвращает строку «красивого» представления матрицы:

public override string ToString()

{

StringBuilder sb = new StringBuilder();

sb.Append("Матрица размерности " + this.rowCount +

"*" + this.colCount+"\n");

for (int i = 0; i < rowCount; i++)

{

for (int j = 0; j < colCount; j++)

sb.Append(mas[i, j] + " ");

sb.Append("\n");

}

return sb.ToString();

}

Теперь следует задать операции сравнения матриц, т.е. операции «равно», «не равно», «больше», «меньше», чтобы клиенту было легко сравнивать объекты этого класса, как простые переменные.

Язык C# допускает определение операций, заданных указанными символами. Этот процесс называется перегрузкой операций.

Операция класса оформляется в виде метода, именем которого является сам знак операции, которому предшествует ключевое слово operator. Важно, что операция является статическим методом класса с атрибутом static.

Операции сравнения – бинарные, их результат истина или ложь. Поэтому все 4 метода возвращают значение типа bool, имеют по 2 параметра – сравниваемые объекты класса. Обратим внимание на то, что операции должны перегружаться попарно.

Определяем метод сравнения на равенство двух матриц.

static public bool operator ==(Matr a, Matr b)

{

if (a.RowCount != b.RowCount)

return false;

else

{

int[] sumB = new int[b.RowCount];

int[] sumA = new int[a.RowCount];

for (int i = 0; i < a.RowCount; i++)

if (a.SumRows()[i] != b.SumRows()[i])

return false;

}

return true;

}

Определяем метод сравнения «не равно» двух матриц.

static public bool operator !=(Matr a, Matr b)

{

if (a.RowCount != b.RowCount)

return true;

else

{

int[] sumB = new int[b.RowCount];

int[] sumA = new int[a.RowCount];

for (int i = 0; i < a.RowCount; i++)

if (a.SumRows()[i] != b.SumRows()[i])

return true;

}

return false;

}

Определяем метод сравнения «больше» двух матриц.

static public bool operator >(Matr a, Matr b)

{

if (a.SumDiag() > b.SumDiag())

return true;

else return false;

}

Определяем метод сравнения «меньше» двух матриц.

public static bool operator <(Matr a, Matr b)

{

if (a.SumDiag() < b.SumDiag())

return true;

else return false;

}

}

Для проверки работы программы создадим в диаграмме классов новый класс Test – добавим к диаграмме новый класс, для которого будет создаваться новый файл test.cs. В класс добавим только 1 метод Testing(), в нем будут создаваться матрицы – объекты класса Matr, сравниваться между собой и выводиться на экран.

В классе Program будет присутствовать только функция Main(). В Main() создадим объект класса Test и для него вызовем метод Testing().

В результате получим диаграмму классов (рис. 31).

Рис. 31. Диаграмма классов

После редактирования коды классов могут выглядеть так:

class Test

{

public void Testing()

{

Program.Matr a = new Program.Matr();

Program.Matr b = new Program.Matr();

Program.Matr d = new Program.Matr(4, 4);

Console.WriteLine("A: "+a);

Console.WriteLine("B: "+b);

Console.WriteLine("a=b? " + ((a == b) ? ("- это так") : ("- нет")));

Console.WriteLine("a>b? " + ((a > b) ? ("- это так") : ("- нет")));

Program.Matr c = new Program.Matr(4,4);

int [,] myMas = { {10,22,36,47}, {13,52,20,34},

{90,45,23,48}, {30,32,14,56} };

c.Mas = myMas;

Console.WriteLine("C: "+c);

Console.WriteLine("D: "+d);

Console.WriteLine("c=d? " + ((c == d) ? ("- это так") : ("- нет")));

Console.WriteLine("c>d? " + ((c > d) ? ("- это так") : ("- нет")));

}

}

static void Main(string[] args)

{

Test myTest = new Test();

myTest.Testing();

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]