Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 2376 / 262 | Оценка: 4.47 / 4.24 | Длительность: 14:45:00
Лекция 2:

Классы

< Лекция 1 || Лекция 2: 123456 || Лекция 3 >
Операции

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

Конструкторы класса Person

Для полноты картины добавим в класс Person множество конструкторов класса.

//Конструкторы класса
public Person(){}
public Person( string fam)
{
    this.fam = fam;
}
public Person(string fam, int age)
{
    this.fam = fam; this.age = age;
}
public Person(string fam, int age,
    string health, int zarplata, Status s)
{
    this.fam = fam; this.age = age; status = s;
    this.health = health; salary = zarplata;
}

Заметьте, правильный стиль требует, чтобы среди конструкторов класса всегда присутствовал конструктор по умолчанию - конструктор без аргументов. В нашем случае тело этого конструктора пусто, поскольку все поля объявлены с инициализацией и конструктору нет смысла изменять эти значения. Часто имена формальных аргументов конструктора совпадают с соответствующими именами полей класса. Поэтому при присваивании полю объекта значения аргумента необходимо уточнить имя поля именем объекта. В данном случае речь идет о текущем объекте, имя которого фиксировано - this. Обратите внимание, кроме конструктора без аргументов и полного конструктора, заданы конструкторы для частных случаев, когда в момент создания объекта известны только фамилия или фамилия и возраст личности.

Класс как модуль

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

Утверждение о том, что для статического класса не создаются объекты, требует уточнения. Один объект создается. Более того, этот статический объект создается всегда, создается автоматически, не требуя выполнения операции new, как это делается для динамически создаваемых объектов. Этот объект получает имя, совпадающее с именем класса. Статический объект, рассматриваемый как модуль класса, создается статическим конструктором, который может быть задан явно, но в случае отсутствия явного задания всегда автоматически добавляется к статическому классу. Конструктор этот вызывается неявно, точный момент его вызова не определен, но гарантируется, что он будет вызван еще до того, как над классом будут выполняться какие-либо операции. Статический конструктор работает точно так же, как и обычный конструктор. Полями создаваемого конструктором объекта будут статические поля и константы класса, которые рассматриваются как статические константы, не требующие задания модификатора static. Информация в полях статического объекта независимо от модификатора доступа открыта для всех статических методов класса, а если эти поля объявлены с модификатором public, то поля доступны и клиентам класса. Так, например, константы PI и E из статического класса Math доступны всем клиентам класса Math.

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

Класс со статическими полями и методами

Рассмотрим теперь общую для C# ситуацию, когда класс, представляющий тип данных, объявленный без модификатора static, имеет как обычные, так и статические поля и методы. Такой класс в полной мере играет обе роли типа данных и модуля. Клиент класса, вызвав обычный конструктор класса, может создать объект класса, содержащий набор обычных полей. Используя этот объект, как цель вызова, можно вызвать обычный метод класса, который и будет работать с информацией, хранимой в полях объекта. Но параллельно с этим динамическим процессом создания объектов класса статический конструктор класса, автоматически вызванный, создаст единственный статический объект с именем класса, и этот объект доступен всем клиентам наравне с создаваемыми ими динамическими объектами.

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

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

Напомню, что всякий вызов метода в объектных вычислениях имеет вид x.F(...) ; где x - цель вызова. Обычно целью вызова является динамически созданный объект x, вызывающий обычные методы класса, называемые динамическими или экземплярными методами. В этом случае поля целевого объекта доступны методу и служат глобальным источником информации. Если же необходимо вызвать статический метод (поле), то целью является статический объект, имя которого задается именем класса. Этот объект и его методы доступны и тогда, когда ни один другой динамический объект класса еще не создан.

Проектирование класса Rational

В заключение этой лекции займемся проектированием класса Rational, описывающего известный в математике тип данных - рациональные числа. По ходу проектирования будут вводиться новые детали, связанные с описанием класса. Начнем проектирование, как обычно, с задания тэга summary, описывающего назначение класса, его свойства и поведение. Вот этот текст:

/// <summary>
/// Класс Rational.
/// определяет новый тип данных - рациональные числа и основные
/// операции над ними - сложение, умножение, вычитание и деление.
/// Рациональное число задается парой целых чисел (m,n) и изображается
/// обычно в виде дроби m/n. Число m называется числителем,
/// n - знаменателем. Для каждого рационального числа существует
/// множество его представлений, например, 1/2, 2/4, 3/6, 6/12.
/// Среди всех представлений
/// можно выделить то, в котором числитель и знаменатель взаимно
/// несократимы. Такой представитель будет храниться в полях класса.   
/// </summary>
public class Rational
{
      // Описание тела класса Rational
}//Rational

Свойства класса Rational

Рациональное число представимо парой целых чисел - m и n. Они и становятся полями класса. Совершенно естественно сделать эти поля закрытыми. Разумная стратегия доступа к ним - "ни чтения, ни записи", поскольку пользователь не должен знать, как представлено рациональное число в классе, и не должен иметь доступа к составляющим рационального числа. Поэтому для таких закрытых полей не будут определяться методы-свойства. Вот объявление полей и констант класса:

//Поля класса. Числитель и знаменатель рационального числа.
const string NONE_EXIST =
     "Не существует рационального  числа " +
     "со знаменателем, равным нулю!";      
int m,n;

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

Зададим два конструктора - конструктор без аргументов, создающий рациональное число 0 и полный конструктор, которому передаются числитель и знаменатель рационального числа. Чуть позже добавим в класс закрытый конструктор и статический конструктор, позволяющий создать константы нашего класса. Вот определение конструкторов:

//Констукторы класса
/// <summary>
/// Конструктор по умолчанию.
/// Создает рациональный 0 - пару (0/1)
/// </summary>
public Rational()
{
    m = 0; n = 1;
}
/// <summary>
/// Конструктор класса. Создает рациональное число
/// m/n, эквивалентное a/b, но с взаимно несократимыми
/// числителем и знаменателем. 
/// Если b = 0, то выбрасывается исключение, 
/// сообщающее о невозможности создать
/// рациональное число со знаменателем 0
/// </summary>
/// <param name="a">числитель</param>
/// <param name="b">знаменатель</param>
public Rational(int a, int b)
{
    if (b == 0) throw new RationalException(NONE_EXIST);
    //приведение знака
    if (b < 0) { b = -b; a = -a; }
    //приведение к несократимой дроби
    int d = Nod(a, b);
    m = a / d; n = b / d;            
}

Как видите, конструктор класса может быть довольно сложным. В конструкторе с аргументами проверяется корректность задаваемых аргументов. Если данные заданы корректно, то пара чисел приводится к эквивалентному рациональному числу с несократимыми числителем и знаменателем. По ходу дела вызывается закрытый метод класса, вычисляющий значение НОД(a,b) - наибольшего общего делителя чисел a и b.

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

Методы класса Rational

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

Закрытый метод НОД

Метод, вычисляющий наибольший общий делитель пары чисел, понадобится не только конструктору класса, но и всем операциям над рациональными числами. Алгоритм нахождения общего делителя хорошо известен со времен Эвклида. Я приведу программный код метода без особых пояснений:

/// <summary>
/// Закрытый метод класса.
/// Возвращает наибольший общий делитель чисел a,b
/// </summary>
/// <param name="a">первое число</param>
/// <param name="b">второе число</param>
/// <returns>НОД(a,b)</returns>
int Nod(int m, int n)
{
   int p=0;
   m=Math.Abs(m); n =Math.Abs(n);
   do
   {
      p = m%n; m=n; n=p;
   }while (n!=0);
   return(m);
}//Nod
Печать рациональных чисел

Любой класс содержит метод ToString, наследованный от родительского класса object. Переопределим этот метод для класса Rational так, чтобы он задавал строковое представление рационального числа в формате m/n. Тогда печать рациональных чисел ничем не будет отличаться от печати чисел арифметического типа. Вот код метода ToString для класса Rational:

/// <summary>
/// Представление рационального числа 
/// в виде строки
/// </summary>
/// <returns>строка в формате m/n</returns>
public override string ToString()
{
    return m + "/" + n;
}
Тестирование создания рациональных чисел

В классе Testing, предназначенном для тестирования нашей работы и являющегося клиентом класса Rational, создадим процедуру, позволяющую проверить корректность создания рациональных чисел. Вот эта процедура:

public void TestCreateRational()
{
    try
    {                 
         Rational r1 = new Rational(-5, -25);
         Console.WriteLine("r1 = " + r1.ToString());
         Rational r2 = new Rational();
         Console.WriteLine("r2 = " + r2.ToString());
         Rational r3 = new Rational(4, -8);
         Console.WriteLine("r3 = " + r3.ToString());  
         Rational r4 = new Rational(2, 6); 
         Console.WriteLine("r4 = " + r4.ToString()); 
         Rational r5 = new Rational(0, 0);
         Console.WriteLine("r5 = " + r5.ToString());
    }
    catch (RationalException e)
    {
        Console.WriteLine(e.Message);
    }
}

Вот как выглядят результаты ее работы:

Создание и печать рациональных чисел

Рис. 1.1. Создание и печать рациональных чисел
< Лекция 1 || Лекция 2: 123456 || Лекция 3 >
Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дарья Федотова
Дарья Федотова
Александр Чихалин
Александр Чихалин
Россия