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

Domnin_Lab_9-12 / ЛАБ_9_C# / Наследование_Основы

.doc
Скачиваний:
16
Добавлен:
02.02.2015
Размер:
242.18 Кб
Скачать

НАСЛЕДОВАНИЕ. ОСНОВЫ.

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

Отметим особенности использования в C# средств защиты private, protectel и public, определяющие взаимный доступ между членами взаимонаследуемых классов:

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

2 Если члены класса защищены уровнем protected, то к таким полям данных могу непосредственно обратиться иетоды прямого (ближайшего) наследника.

3 Для снятия защиты все члены классов должны в своем описании иметь слово (уровень защиты) public.

Ниже приведена общая форма обявления классов при реализации механизма многоуровневого наследования. В качестве примера приводим структуру формы обявления для случая В ← С ← D ← Е

class B {} - // B - имя базового класса

class C : B {} - // C – имя производного (первого) класса

class D : В {} - // D – имя производного (второго) класса

class E : B {} - // E – имя производного (третьего) класса

class D : C {} - // D – имя производного (второго) класса

class E : D {} - // Е - имя производного (второго) класса

class E : D : C : B {} - // пример последовательного наследования

B

C

D

E

Рис 0.1 Примеры последовательного наследования

ib

B

C

D

E

jb

ic

jc

id

jd

ie

je

Рис 0.2 Пример доступа к параметрам соответствующих

класов при последовательном наследовании

Рис 0.2 Наглядно демонстрирует с какими полями данных взаимодействует соответствующий класс. Доступ к полям расширяется по мере увеличения их глубины наследования. Последний класс, класс Е (как видно из рисунка) взаимодействует со всеми полями всех предков. Ниже рассматривается пример взаимодействия классов В ← С при открытых (public) всех членов участвующих классов.

№ 1

// Полный доступ к членам классов при иерархии последовательного наследовании В←С

using System;

class B {

public int ib;

public void showb()

{ Console.WriteLine("\n ib = " + ib);

}

}

class C : B

{ public int ic;

public void showc()

{ Console.WriteLine(" ib+ic = " + (ib + ic));

}

}

class result

{ public static void Main()

{ C c1 = new C();

c1.ib = 1;

c1.ic = 2;

c1.showb();

c1.showc();

Console.WriteLine("\n\n\n ");

}

}

Рис 1 Результаты наследования при всех открытых членах классов

Рассмотрим подобное наследование для наибольшей длины последовательности объектов В ← С ← D ← Е

№ 2

// Наследование классов В ← С ← D ← Е при всех открытых полях

using System;

class B

{ public int ib;

public void showb()

{ Console.WriteLine("\n ib = " + ib);

}

}

class C : B

{ public int ic;

public void showc()

{ Console.WriteLine(" ib+ic = " + (ib + ic));

}

}

class D : C

{ public int id;

public void showd()

{ Console.WriteLine(" ib="+ib+" ic="+ic+" id="+id+" ibcd="+(ib + ic + id));

}

}

class E : D

{ public int ie;

public void showe()

{ Console.WriteLine(" ib="+ib+" ic="+ic+" id="+id+" ie="+ie+" ibcde="+(ib+ic+id+ie));

}

}

class result

{ public static void Main()

{ E e1 = new E();

e1.ib = 1;

e1.ic = 2;

e1.id = 3;

e1.ie = 4;

e1.showb();

e1.showc();

e1.showd();

e1.showe();

Console.WriteLine("\n\n\n ");

}

}

Рис 2 Реализация последовательного наследования при открытых полях

По приведенным примерам при отсутствии закрытых полей можно сделать такие выводы о особенностях наследовании в С#.

1 Множественное наследование (как в С++) не применяется.

2 Простой синтаксис описания наследования – на любом уровне между производным и базовым классами ставится двоеточие (“ : ”).

3 Все классы, в том числе и базовый (class B), могут использоваться самостоятельно.

4 Для каждого производного класса можно указать только один базовый класс.

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

6 Для кажвого производного класса доступны доступны кроме своих полей поля всех выше стоящих классов.

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

Доступ к закрытым членам класса

К закрытым членам класса относятся члены, которые описаны явно с указанием private или не описаны, что соответствует по умолчанию private. Рассмотрим пример №1 для этого случая ( №3 ).

3

// В примере № 1 в классе В поле int ib по умолчанию закрыто

using System;

class B {

int ib;

public void showb()

{ Console.WriteLine("\n ib = " + ib); }

}

class C : B

{ public int ic;

public void showc()

{ Console.WriteLine(" ib+ic = " + (ib + ic)); }

}

class result

{ public static void Main()

{ C c1 = new C();

c1.ib = 1;

c1.ic = 2;

c1.showb();

c1.showc();

Console.WriteLine("\n\n\n ");

}

}

d:\dci\f75s\f75\CodeFile1.cs(17,42): error CS0122: 'B.ib' is inaccessible due to its protection level

d:\dci\f75s\f75\CodeFile1.cs(5,19): (Related location)

d:\dci\f75s\f75\CodeFile1.cs(25,12): error CS0122: 'B.ib' is inaccessible due to its protection level

d:\dci\f75s\f75\CodeFile1.cs(5,19): (Related location)

Compile complete -- 2 errors, 0 warnings

========== Build: 0 succeeded or up-to-date, 1 failed, 0 skipped ==========

Рис 3.1 Сообщение, что в строках (17,42) и (25,12) запрещенное

обращение к недоступному параметру “ ib ”

Выводы:

1 Закрытый член класса остается закрытым в пределах своего класса.

2 К закрытому члену класса нельзя получить доступ из вне класса. Доступ возможен только из собственного класса.

3 Следует различать закрытое поле (с уровнем защиты private) и защищенное поле (с уровнем защиты protected), при котором возможно обращение с другого класса (см пример далее).

4 В С# к закрытым членам можно обратиться, преобразовав их в свойства, для этого для каждого закрытого члена используются get- и set – аксессоры.

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

4

// В примере № 1 в классе В поле int ib по умолчанию закрыто

using System;

class B

{

int priib; //Это private-член, здесь имя любое.

//Удобно, чтобы оно напоминало исходное имя ib

// Свойство ib

public int ib // В дальнейшем имя ib везде остается,

// но оно заменяется свойством с темже именем

{ get { return priib; } // get-аксессор

set { priib = value; } // set-аксессор

}

public void showb()

{ Console.WriteLine("\n ib = " + ib);

}

}

class C : B

{ public int ic;

public void showc()

{ Console.WriteLine(" ib+ic = " + (ib + ic));

}

}

class result

{ public static void Main()

{

C c1 = new C();

c1.ib = 1;

c1.ic = 2;

c1.showb();

c1.showc();

Console.WriteLine("\n\n\n ");

}

}

Рис 4 Результат применения свойства для доступа к закрытому полю (см. рис 1)

Выводы:

1 Для доступа к закрытому полю ib это имя заменено свойством ib. Вместо него в объявлении закрытого имени можно было взять любое имя. Удобно в новом имени отразить имя исходного закрытого поля ( ib ). C учетом того, что новое имя всеравно отражает закрытое ( по умолчанию ) поле принято имя priib.

2 Новое имя применено при описании get- и set-аксессоров.

3 Как видно из текста программы и результатов все остальное осталось без изменения.

Организация защищенного доступа

Защищенный доступ обслуживает работу с защищенными членами, для которых применяется средство представление доступа protected. В обычной ситуации (при отсутствии наследования) protected и private одинаково. Однако при наличии наследования защищенный член базового класса становится защищенным членом производного класса, т.е. он становится доступным для производного класса и член производного класса может непосредственно обращаться к члену базового класса. Ниже пример демонстрирует это.

№ 5

// Работа с защищенными членами класса

using System;

class B {

protected int ib, jb; // ib, jb закрыты внутри базового класса,

// но доступны из производного класса одной иерархической структуры

public void set(int a, int b) {

ib = a;

jb = b;

Console.WriteLine("\n ib = " +ib + "\n\n jb = " +jb);

}

public void showb()

{ Console.WriteLine("\n ib+jb = " + ( ib + jb ));

}

}

class C : B

{ int ic; // Закрытый член

// Класс С получает доступ к членам ib и jb класса B

public void setc()

{ ic = ib * jb;

Console.WriteLine("\n ic = " + ib * jb);

}

public void showc()

{ Console.WriteLine("\n ib+jb+ic = " + (ib + jb + ic));

}

}

class result

{ public static void Main()

{ C c1 = new C();

c1.set(10, 50); // Класс С ”видит” В-члены ib и jb

c1.showb(); // Класс С ”видит” В-члены ib и jb

c1.setc(); // Этот метод пртнадлежит самому классу С

c1.showc(); // Этот метод пртнадлежит самому классу С

Console.WriteLine("\n\n\n ");

}

}

Рис 5 Результаты работы с защищенными членами базового класса

Работа с конструкторами при наследовании

Конструктор это специальный метод, задача которого состоит в создании объекта своего класса. Конструктор можек функционировать явно, когда он создан пользователем, или неявно, когда он выполняет свои функции по умолчанию. Режим работы по умолчанию позволяет обойтись без явного создания констуктора. При наследовании, так как каждый класс может иметь свой конструктор, возникает вопрос, какой из конструкторов создает объект для производного класса – конструктор базового класса или собственный конструктор производного класса. При организации работы иерархии классов следует уяснить как совместно работают конструкторы разных классов. Рассмотрим такие состояния:

- конструктор явно определен только в производном классе;

- конструкторы определены и в базовом и в производном классах.

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

№ 6

// Наследование.Конструкторы: производного класса + по умолчанию базового

using System;

class B{

int priib; //Это private-член

int prijb; // Это private-член

// Свойство ib

public int ib

{ get { return priib; } // get-аксессор

set { priib = value; } // set-аксессор

}

// Свойство jb

public int jb

{ get { return prijb; } // get-аксессор

set { prijb = value; } // set-аксессор

}

public void showb()

{ Console.WriteLine("\n ib = " + ib + " jb = " + jb);

}

}

// Класс С производный от класса В

class C : B

{ int ic; // Закрытое поле

// Конструктор производного класса С

public C(int i, int j, int k)

{ ib = i; // Инициализация поля базового класса ib (через свойство)

jb = j; // Инициализация поля базового класса jb (через свойство)

ic = k; // Инициализация поля производного класса (своего класса)

Console.WriteLine("\n ic = " +ic);

}

// Определение результата деления jb/ib - метод divide

public double divide()

{ double d;

d = (double)jb / ib;

Console.WriteLine("\n d = jb/ib = " + d);

return d;

}

public void showc()

{ Console.WriteLine("\n ib+ic = " + (ib + ic));

}

}

class result

{ public static void Main()

{ C c1 = new C( 25, 5, 100);

c1.showb();

c1.showc();

Console.WriteLine("\n c1.divide = " + c1.divide() + "\n\n\n " );

}

}

Рис 6 Применение явного конструктора производного класса

Выводы:

1 Так как поля базового класса закрыты ( private ), то используемые имена ib и jb заменяются свойствами (см. пример № 4).

2 Конструктор производного класса ( public C(int i, int j, int k) ) инициализирует наследуемые им поля базового класса ib и jb и собственное поле ic (в одном классе допускается своему методу обращаться к своему закрытому полю).

3 Инициализация данных выполнена в строке создания ссылочного имени с1 и выделения памяти для решения примера ( C c1 = new C( 25, 5, 100); )

4 Обратите внимание (для приобретения опыта при работе с разными типами данных) на вычисление d = int ib / intjb.

Вызов конструкторов базового класса

У любого класса может быть несколько конструкторов. Рассматривается организация вызова со стороны производного класса конструктора базового класса. Для этого применяется формат расширенного объявления производного класса с применением ключевого слова base:

имя_конструктора_произвводного класса ( список_параметров) :

base ( список аргументов ) {

// Тело конструктора

}

где: base –ключевое слово, определяющее вызов конструктора базового класса

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

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

№ 7

// Наследование.Конструкторы: добавление конструктора в базовый класс В

using System;

class B{

int priib; //Это private-член

int prijb; // Это private-член

// Конструктор класса В (добавлено в правнении с № 6)

public B(int i, int j)

{ ib = i;

jb = j;

}

// Свойство ib

public int ib

{ get { return priib; } // get-аксессор

set { priib = value; } // set-аксессор

}

// Свойство jb

public int jb

{ get { return prijb; } // get-аксессор

set { prijb = value; } // set-аксессор

}

public void showb()

{ Console.WriteLine("\n ib = " + ib + " и jb = " + jb);

}

}

// Класс С производный от класса В

class C : B

{ int ic; // Закрытое поле

// Вызывается конструктор базового класса В (согласно с форматом расширенного объявления)

public C(int i, int j, int k) : base( i, j)

{ ic = k; // Инициализация поля производного класса (своего класса)

Console.WriteLine("\n ic = " + ic);

}

// Определение результата деления jb/ib - метод divide

public double divide()

{ double d;

d = (double)jb / ib;

Console.WriteLine("\n d = jb/ib = " + d);

return d;

}

public void showc()

{ Console.WriteLine("\n ib+ic = " + (ib + ic));

}

}

class result

{

public static void Main()

{

C c1 = new C(25, 5, 100);

c1.showb();

c1.showc();

Console.WriteLine("\n c1.divide = " + c1.divide() + "\n\n\n ");

}

}

Рис 7 Применение конструктора производного класса в формате расширенного объявления ( полное совпадение с рис 6 )

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

8

/* Наследование. Добавление конструктора в базовый класс В

// Для дополнительных методов по сравнению с примером № 7 в начале комментарий

// добавляем (+)

using System;

class B

{

int priib; //Это private-член

int prijb; // Это private-член

// (+) Добавление (далее обозначаем (+)) конструктора по умолчанию в класс В

public B()

{

ib = jb = 0;

}

// Конструктор класса В c параметрами(добавлено в сравнении с № 6)

public B(int i, int j)

{

ib = i;

jb = j;

}

// (+)Создание дополнительного объекта класса В с ib = jb = const

public B(int x)

{

ib = jb = x;

}

// Свойство ib

public int ib

{

get { return priib; } // get-аксессор

set { priib = value; } // set-аксессор

}

// Свойство jb

public int jb

{

get { return prijb; } // get-аксессор

set { prijb = value; } // set-аксессор

}

public void showb()

{

Console.WriteLine(" ib = " + ib + " и jb = " + jb);

}

}

// Класс С производный от класса В

class C : B

{

int ic; // Закрытое поле

// (+)Конструктор по умолчанию для класса С, который

// автоматически вызывает конструктор по умолчанию класса В

public C()

{

ic = 0;

}

// Вызывается конструктор базового класса В (с форматом расширенного объявления )

public C(int i, int j, int k) : base(i, j)

{

ic = k; // Инициализация поля производного класса (своего класса)

Console.WriteLine(" ic = " + ic);

}

// (+)Определяем значения ib и jb из условия ib = jb = x

public C(int x) : base(x)

{

ic = 100;

}

// Определение результата деления jb/ib - метод divide

public double divide()

{

double d;

d = (double)jb / ib;

Console.WriteLine(" d = jb/ib = " + d);

return d;

}

public void showc()

{

Console.WriteLine(" ib+ic = " + (ib + ic));

}

}

class result

{ public static void Main()

{ C c1 = new C();

C c2 = new C(25, 5, 1000);

C c3 = new C(125);

Console.WriteLine("\n Сообщение о с1: ");

c1.showb();

c1.showc();

Console.WriteLine(" c1.divide = " + c1.divide());

c1 = c2;

Console.WriteLine("\n Повторное сообщение о с1 при с1 = с2");

Console.WriteLine(" Сообщение о с1: ");

c1.showb();

c1.showc();

Console.WriteLine(" c1.divide = " + c1.divide());

Console.WriteLine("\n Сообщение о с2: ");

c2.showb();

c2.showc();

Console.WriteLine(" c2.divide = " + c2.divide());

Console.WriteLine("\n Сообщение о с3: ");

c3.showb();

c3.showc();

Console.WriteLine(" c3.divide = " + c3.divide());

Console.WriteLine("\n\n\n");

}

}

Рис 8 Результаты применения расширенных версий классов

В (базовый класс) и С (производный класс)

Выводы:

1 Ключевое слово base всегла отсылает к базовому классу.

2 Базовый класс всегда распологается вначале (вверху) иерархии классав.

3 Чтобы передать аргументы конструктору базового класса следует указать их как аргументы методу base().

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

5 Ссылочное имя с1 вызывалось дважды. Первый раз оно использовалось самостоятельно и все переменные были обнулены. При этом была обнаружена недопустимая операция – деление на ноль. Получены сообщения d =ib/jb=NaN и с1.divide = NaN. Во второй раз, при выполнении равенства между ссылочными именам с1 = с2 все числовые показатели были одинаковыми.

6 При ссылочном имени с2 конструктор производного класса С инициализировал значения всех переменных и поэтому получено ic = 1000.

7 При ссылочном имени с3 конструктор базового класса инициализировал параметр ib=125 и ib = jb, при этом базовым конструктором был определено значение ic = 100.

Работа конструкторов в иерархической структуре классав

Так как в C# любая иерархическая структура классов представляет собой линейную последовательность классов от базового до самого последнего производного класса и при создании базового класса (предыдущего класса) он ничего не знает о последующем классе (следующем производном классе) естественно предположить, что последовательность вызова конструкторов совпадает с последовательностью создания классов. Составим простейшую структуру программы создания иерархии классов и выполним инициализацию этой структуры с последнего производного класса. Пусть такая иерархия будет включать 7 классав: A ←B ←C ←D ←E ←F ←G, A – базовый класс, а G – последний производный класс.

№ 9

// Демонстрация порядка обращения к конструкторам

using System;

// Создаем базовый класс А

class A {

public A() {

Console.WriteLine("\n Создание класса А.");

}

}

// Создаем класс В, производный от А

class B : A {

public B() {

Console.WriteLine(" Создание класса B.");

}

}

// Создаем класс C, производный от B

class C : B {

public C() {

Console.WriteLine(" Создание класса C.");

}

}

// Создаем класс D, производный от C

class D : C {

public D() {

Console.WriteLine(" Создание класса D.");

}

}

class E : D {

public E() {

Console.WriteLine(" Создание класса E.");

}

}

// Создаем класс F, производный от E

class F : E {

public F() {

Console.WriteLine(" Создание класса F.");

}

}

class G : F {

public G() {

Console.WriteLine(" Создание класса G. \n\n\n");

}

}

class ControlOfOrder

{ public static void Main()

{ G g = new G();

}

}

Рис 9 Последовательность работы с конструкторами в иерархии классов

Организация работы с ссылками (с ссылочными данными)

Исходя из того, что язык С# -строго типизированный язык ссылочная переменная одного классового типа не может ссылаться на объект другого классового типа, т.е. недопустимый ниже приводимый пример

………..

№ 10

class Error {

public static void Main() {

X x = new X(100);

X x1;

Y y = new Y(500);

x1 = x; //

x1 = y; // Ошибка, ниже приведен ее текст

}

}

d:\dci\f81s\f81\CodeFile1.cs(23,12): error CS0029: Cannot implicitly convert type 'Y' to 'X'

Compile complete -- 1 errors, 0 warnings

========== Build: 0 succeeded or up-to-date, 1 failed, 0 skipped ==========

Рис 10 Ошибка при присвоении объекта класса У ссылочной

переменной типа Х

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

Ниже приводится пример для иерархии В←С с добавлением конструкторов.

№ 11

// Наследование. Передача ссылки на производный класс ссылке на базовый класс

using System;

class B

{ int priib; //Это private-член

int prijb; // Это private-член

// Добавление конструктора по умолчанию в класс В

public B()

{

ib = jb = 0;

}

// Конструктор класса В c параметрами(добавлено в сравнении с № 6)

public B(int i, int j)

{

ib = i;

jb = j;

}

// Создание дополнительного объекта класса В с ib = jb

public B(int x)

{

ib = jb = x;

}

// Создается объект из объекта

public B(B ob)

{

ib = ob.ib;

jb = ob.jb;

}

// Свойство ib

public int ib

{

get { return priib; } // get-аксессор

set { priib = value; } // set-аксессор

}

// Свойство jb

public int jb

{

get { return prijb; } // get-аксессор

set { prijb = value; } // set-аксессор

}

public void showb()

{

Console.WriteLine(" ib = " + ib + " и jb = " + jb);

}

}

// Класс С производный от класса В

class C : B

{

int ic; // Закрытое поле

// (+)Конструктор по умолчанию для класса С, который

// автоматически вызывает конструктор по умолчанию класса В

public C()

{

ic = 0;

}

// Вызывается конструктор базового класса В (согласно с форматом расширенного объявления)

public C(int i, int j, int k) : base(i, j)

{

ic = k; // Инициализация поля производного класса (своего класса)

Console.WriteLine(" ic = " + ic);

}

// Определяем значения ib и jb из условия ib = jb = x

public C( int x ) : base( x )

{

ic = 100;

}