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

CSBasicCourse2ndedPodbelsky / CSBasicCourse2ndedPodbelsky

.pdf
Скачиваний:
31
Добавлен:
22.03.2016
Размер:
2.08 Mб
Скачать

Хотя язык C# с помощью механизма сбора мусора (garbage collection) в

достаточной мере защищён от таких проблем как "утечка памяти" и "висячие ссылки", однако в C# остаётся, например, задача глубокого копирования и важным вопросом в ряде случаев является приведение типов, введённых пользовательскими классами. Зачастую для квалифицированного решения названных проблем можно

пользоваться конструкторами разных видов. В классе могут быть явно объявлены: конструктор умолчания "конструктор: умолчания" (конструктор без

параметров); конструктор копирования "конструктор: копирования" ;

конструктор приведения типов "конструктор: приведения типов" ; конструктор общего вида "конструктор: общего вида" .

При отсутствии в объявлении класса каких бы то ни было конструкторов,

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

либо конструктор умолчания просто необходим в классе наряду с другими конструкторами, – его требуется явно включать в объявления класса. Формат

объявления: class CLASS {

public CLASS() {

операторы_тела_конструктораopt

}

}

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

Примеры такой ситуации мы уже приводили, разбирая отличия ссылок от

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

конструктором копирования.

// 11_06.cs конструкторы using System;

class CL{

public int dom = 6;

public CL() { } // конструктор умолчания public CL(CL ob) { // конструктор копирования

dom = ob.dom;

}

}

class Program { static void Main() {

CL one = new CL(); CL two = new CL(one); two.dom = 5*one.dom;

Console.WriteLine("one.dom="+one.dom+", two.dom="+two.dom);

}

}

Результат выполнения программы: one.dom=6, two.dom=30

В классе CL конструктор без параметров объявлен явно. Для простоты примера в классе CL всего одно открытое поле int dom. В методе Main() ссылка one

связана с объектом, инициализированным конструктором без параметров. Объект,

адресуемый ссылкой two – копия объекта one, но эти объекты независимы, что иллюстрируют результаты выполнения программы.

Особенности обращений конструкторов одного класса друг к другу расмотрим на промере класса, особым образом представляющего вещественные числа. В

научной записи (в научной нотации) число записывается в виде произведения двух чисел: целой степени p числа 10 и числа m, удовлетворяющего условию 1.0 <= m < 10.0. Иногда p называют показателем, а m – мантиссой числа. Таким образом,

запись каждого числа выглядит так: m*10p. (При выводе значения числа в научной нотации возведение в степень будем обозначать символом ^.).

Примеры: постоянная Авогадро: 6.02486 * 1023 (г*моль)-1,

заряд электрона: -4.80286 * 10-10 СГСЭ.

Определим класс Real для представления чисел в научной нотации. В классе определим метод display() для вывода на консоль значения объекта и метод incrementM() для увеличения на 1 значения мантиссы числа. При этом значение числа увеличивается, конечно, не на 1, а на 10p. Для мантисс и показателей введем закрытые поля double m и int p. Определение класса может быть таким (программа

11_07.cs): using System;

class Real // Класс чисел в научной нотации { // Закрытые поля:

double m = 8.0; // мантисса - явная инициализация int p; // порядок - инициализация по умолчанию

//Метод - приращение мантиссы: public void incrementM()

{

m += 1;

if (m >= 10)

{

m /= 10; p++;

}

}

//Метод для вывода значения числа (объекта): public void display(string name)

{

string form = name + "\t = {0,8:F5}*10^{1,-3:D2}"; Console.WriteLine(form, m, p);

}

//Конструктор общего вида:

public Real(double mi, int pi)

{

m = mi; p = pi; reduce();

}

// конструктор приведения типов: public Real(double mi)

: this(mi, 0)

{ } // конструктор копирования:

public Real(Real re)

: this(re.m, re.p)

{ }

//конструктор умолчания: public Real()

{ }

//"Внутренний" для класса метод:

void reduce() // Приведение числа к каноническому виду.

{

double sign = 1; if (m < 0) {sign = -1; m = -m; } for (; m >= 10; m /= 10, p += 1) ;

for (; m < 1; m *= 10, p -= 1) ; m *= sign;

}

}

Среди методов класса нам сейчас важно рассмотреть явно

определенные в классе конструкторы.

Конструктор общего вида "конструктор: общего вида" : public Real(double mi, int pi)

{

m = mi; p = pi; reduce();

}

Параметры определяют значения мантиссы m и порядка p создаваемого объекта класса. В соответствии с правилами записи чисел в научной нотации для них необходимо соблюдение условия:

1.0 <= m < 10.0.

Так как значение аргумента mi при обращении к конструктору может не удовлетворять этому условию, то в теле конструктора вызывается закрытый для внешних обращений метод void reduce(). Его задача нужным образом преобразовать значения полей m и p.

Конструктор приведения типов "конструктор: приведения типов" : public Real(double mi) : this(mi, 0) { }

Это частный случай конструктора общего вида с одним параметром. В нашем примере он формирует объект класса Real по одному значению типа double,

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

преобразовать числовое значение в объект класса Real. В конструкторе применен инициализатор, содержащий обращение this(mi, 0) к конструктору с заголовком

Real(double mi, int pi). Значение второго аргумента, определяющего значение поля int p, задано нулевой константой, что соответствует естественному для математики

способу записи вещественного числа.

Констуктор копирования "конструктор: копирования" :

public Real(Real re) : this(re.m, re.p) { }

Позволяет создать копию объекта. Ещё раз обратим внимание на его отличие от операции присваивания, применение которой копирует только значение ссылки на объект. После присваивания ссылок на один объект начинают указывать несколько переменных (ссылок). Тело конструктора копирования в нашем примере не содержит операторов. Для присваивания значений полям создаваемого объекта используется инициализатор конструктора, содержащий обращение this(re.m, re.p) к

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

Конструктор умолчания "конструктор: умолчания" , т.е. конструктор без параметров:

public Real() { }

Отсутствие параметров, отсутствие (в данном примере) инициализатора конструктора и пустое тело конструктора вызывает вопрос. А зачем нужен такой конструктор? Ведь в предыдущем варианте класса Real объявления такого конструктора не было.

При наличии явно определенных конструкторов (хотя бы одного) компилятор не встраивает в определение класса конструктор с пустым списком параметров. При необходимости такой конструктор нужно объявлять явно, что и сделано.

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

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

значения.

Пример применения конструкторов класса (программа 11_07.cs): static void Main()

{

Real number = new Real(303.0, 1); // конструктор общего вида number.display("number");

Real number1 = new Real(0.000321);// констр. приведения типов number1.display("number1");

Real numCopy = new Real(number);// конструктор копирования

number1 = number;

// присваивание ссылок

number.incrementM();

// изменение объекта

number.display("number");

 

number1.display("number1");

 

numCopy.display("numCopy"); // копия сохранила значение

Real numb = new Real();

// конструктор умолчания

numb.display("numb");

 

}

 

 

 

 

Результат выполнения программы:

number

=

3,03000

* 10^03

 

number1

=

3,21000

* 10^-04

number

=

4,03000

* 10^03

 

number1 =

4,03000

* 10^03

 

numCopy = 3,03000 * 10^03

numb

= 8,00000 * 10^00

 

11.7. Деструкторы и финализаторы

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

Объявление деструктора: extern opt ~имя_класса( )

тело_деструктора

Как показано в формате, имя_деструктора "деструктор:имя_деструктора"

это имя класса с префиксом ~ (тильда). Других имён у деструкторов не бывает.

Деструктор в классе может быть только один. Параметров у деструктора нет. Нет и

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

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

необходимые для уничтожения объекта. Практически тело деструктора аналогично телу метода без параметров с возвращаемым значением типа void.

Деструктор выполняется после того как соответствующий объект класса перестаёт использоваться в программе. Вызов деструктора выполняется автоматически. Момент вызова конкретно не определён. Явно вызвать деструктор из кода программы нельзя.

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

иллюстрирует результат следующей программы (11_08.cs).

// 11_08.cs деструктор using System;

class A { ~A() {

Console.WriteLine("Destructor!");

}

}

class Test

{

static void Main()

{

A b = new A(); b = null;

}

}

Результат выполнения программы:

Destructor!

В архитектуре .NET деструкторы реализуются с помощью метода с названием

Finalize(). Этот метод, называемый финализатором "финализатор" , подменяет в сборке реально использованный в коде деструктор. Программируя на уровне языка

C#, можно не обращать на это внимание, но это важно понимать, если исследовать

код на языке IL.

Деструктор нужен только в том классе, который требует для создаваемого

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

связывает создаваемый объект с дескриптором файла или устанавливает сетевое

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

освобождению дескриптора файла или сетевого соединения. Именно такие действия

должен выполнять деструктор.

Контрольные вопросы

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

Какие элементы являются обязательными в объявлении нестатического поля. Когда выполняется инициализация нестатических полей?

Каков статус доступа нестатического поля при отсутствии в его объявлении модификаторов доступа?

Можно ли объявить статическое поле с типом класса, которому оно принадлежит?

Вкаком случае в классе могут одновременно присутствовать одноименные статический и нестатический методы?

Вкаких случаях телом нестатического метода может быть пустой оператор?

Вкаком случае конструктор умолчания (конструктор без параметров) создаётся автоматически?

Назовите возможные применения ссылки this. В каких методах ссылка this отсутствует?

Опишите формат объявления нестатического конструктора. Перечислите модификаторы конструктора.

Объясните назначение инициализатора конструктора. Перечислите виды конструкторов.

Каков статус доступа у конструктора умолчания, встраиваемого в класс автоматически?

Что такое конструктор копирования?

Каким образом конструктор может обратиться к другому конструктору своего класса?

Объясните назначение деструктора.

Сколько деструкторов может быть в одном классе? Что такое финализатор?

Глава 12. Средства взаимодействия с объектами

12.1. Принцип инкапсуляции и методы объектов

Объектно-ориентированное программирование базируется на трех принципах:

полиморфизм, инкапсуляция и наследование . С одним из проявлений полиморфизма "полиморфизм" , а именно с перегрузкой методов, мы уже познакомились.

Инкапсуляцию "инкапсуляция" можно рассматривать как сокрытие особенностей реализации того или иного фрагмента программы от внешнего пользователя. Фрагмент должен быть доступен для обращений к нему только через внешний интерфейс фрагмента. Описания внешнего интерфейса должно быть достаточно для использования фрагмента. Если таким фрагментом является процедура (или функция), то нужно знать, как следует обратиться к процедуре, как передать ей необходимые входные данные и как получить результаты выполнения процедуры. Подробности внутренней реализации процедуры не должны интересовать того программиста, который использует процедуру. Именно принцип инкапсуляции "инкапсуляция:принцип инкапсуляции" лежит в основе запрета на использование в процедурах и функциях глобальных переменных. Ведь глобальная переменная определена вне процедуры и доступна внешним изменениям, не зависящим от обработки данных в её теле.

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

отделить их от механизмов реализации (внутреннего построения) класса. При этом нужно стремиться к достижению следующих трех целей:

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

возможность модифицировать внутреннюю реализацию класса без изменения тех

Соседние файлы в папке CSBasicCourse2ndedPodbelsky