Студопедия — Наследование.
Студопедия Главная Случайная страница Обратная связь

Разделы: Автомобили Астрономия Биология География Дом и сад Другие языки Другое Информатика История Культура Литература Логика Математика Медицина Металлургия Механика Образование Охрана труда Педагогика Политика Право Психология Религия Риторика Социология Спорт Строительство Технология Туризм Физика Философия Финансы Химия Черчение Экология Экономика Электроника

Наследование.

 

Заявка

на участие в ХХI международной научно-технической конференции студентов и аспирантов

“Радиоэлектроника, электротехника и энергетика”

  фамилия, имя и отчество автора (соавторов)*  
  статус автора (соавторов) – студент, аспирант, соискатель  
  фамилия, имя, отчество научного руководителя (руководителей)  
  ученая степень и ученое звание (должность – при отсутствии ученых степеней и званий) научного руководителя  
  название доклада  
  научное направление  
  название секции  
  наименование учебного заведения (кафедры для МЭИ)  
  координаты для связи: – адрес электронной почты (E-mail)  
  для участников из внешних вузов и организаций: - планируется очное или заочное участие в конференции - нужна или нет гостиница  

 

* - в данной позиции научный руководитель (консультант) не указывается

Лекция № 9.

Наследование.

Наследование - это один из трех основных принципов объектно-ориентированного программирования. Главная задача наследования - обеспечить повторное использование кода. Существует два основных вида наследования:

классическое наследование (отношение «быть» - is-a) и

включение-делегирование (отношение «иметь» - has-a).

 

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

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

_________ class имя_производного_класса: имя_базового_класса

{ // тело производного класса }

 

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

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

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

Пример. В классе Shape, определяются атрибуты "обобщенной" двумерной геометрической фигуры (например, квадрата, прямоугольника, треугольника и т.д.). Класс Shape можно использовать в качестве базового для классов, которые описывают специфические типы двумерных объектов. Пусть производным будет класс Triangle, описывающий треугольник,

using System;

// Класс двумерных объектов,

class Shape {

public double width, height; public void Show()

{Console.WriteLine("Ширина и высота равны " +width + " и " + height);}

}

class Triangle: Shape

{ public double area() {return width*height/2;}}

class Program

{ static void Main(string[] args)

{Triangle tl = new Triangle();

tl.height=4.0;

tl.width=8.0;

Console.WriteLine("Информация o tl: “);

tl.Show();

Console.WriteLine("Площадь равна " + tl.area());

}

}

Класс Triangle содержит все элементы класса Shape и, кроме того, метод аrеа() для вычисления площади треугольника. Поскольку класс Triangle включает все члены базового класса Shape, он может обращаться к членам width и height внутри метода area(). Кроме того, внутри метода Main() объект t1 может прямо ссылаться на члены width и height, как если бы они были частью класса Triangle. Несмотря на то, что класс Shape является базовым для класса Triangle, это совершенно независимый и автономный класс. То, что его использует в качестве базового производный класс(классы), не означает невозможность использования его самого. Например, следующий фрагмент кода абсолютно верен:

Shape s1 = new Shape();

s1.height = 50;

s1.width = 2;

Console.WriteLine("Площадь равна " + s1.width*s1.height);

Объект класса Shape "ничего не знает" и не имеет права доступа к классу, производному от Shape.

 

Доступ к членам класса при наследовании.

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

1) использование открытых свойств и методов, позволяющих получить доступ к закрытым данным;

Новая версия класса Shape, в котором бывшие члены width и height стали свойствами.

class Shape {

double pri_width, pri_height; //закрытые члены

public double width {

get {return pri_width;}

set {pri_width = value;}}

public double height {

get {return pri_height;}

set {pri_height = value;}}

...}

 

2) использование защищенных членов.

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

 

using System;

class Shape // Класс двумерных объектов,

{protected double width, height;

public void Show() { Console.WriteLine("Ширина и высота равны " + width + " и " + height); }

public void set(double a, double b) { width = a; height = b;}

}

class Triangle: Shape

{ public double area() { return width * height/2;}

}

class Program

{ static void Main(string[] args) {

Triangle tl = new Triangle();

tl.set(4.0,8.0);

Console.WriteLine("Информация о tl:");

tl.Show();

Console.WriteLine("Площадь равна " + tl.area());

}

}

Поскольку класс Shape наследуется классом Triangle и члены width и height объявлены защищенными в базовом классе Shape (protected), метод area() может получить к ним доступ. Если бы эти члены были объявлены в классе Shape закрытыми, класс Triangle не имел бы к ним права доступа, и программа не скомпилировалась бы.

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

 

Порядок вызова конструкторов при наследовании.

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

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

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

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

{ // тело конструктора}

Здесь с помощью элемента список_аргутентов задаются аргументы, необходимые конструктору в базовом классе.

В иерархии классов конструкторы вызываются в порядке выведения классов, т.е. начиная с конструктора базового класса и заканчивая конструктором производного класса. Более того, этот порядок не нарушается, независимо от использования ссылки base. Если ссылка base не используется, будут выполнены конструкторы по умолчанию (т.е. конструкторы без параметров) всех базовых классов.

 

using System;

class В

{ protected int х;

public В(int а){х = а;}

}

class Dl: В

{ protected int y;

public Dl(int a, int b):base(b)

{y = a;}

}

class D2: Dl

{ protected int z;

public D2(int a, int b, int c):base(b,c)

{z = a;}

public void Show()

{ Console.WriteLine("x=" + x + "y=" + у + "z=" + z);}

}

class Program {

static void Main(string[] args) {

В ob1 = new В(4);

Dl ob2 = new Dl(6,7);

D2 ob3 = new D2 (3,8,9);

ob3.Show();

}

}

Результаты работы: x=9y=8z=3

 

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

Рассмотрим ключевые концепции base-механизма. При задании производным классом base-"метода" вызывается конструктор непосредственного базового класса. Таким образом, ключевое слово base всегда отсылает к базовому классу, стоящему в иерархии классов непосредственно над вызывающим классом. Это справедливо и для многоуровневой иерархии. Чтобы передать аргументы конструктору базового класса, достаточно указать их в качестве аргументов "метода" base(). При отсутствии ключевого слова base автоматически вызывается конструктор базового класса, действующий по умолчанию.

 

using System;

class В

{ protected int х;

public В(int а) {х = а;}

public В(){}

}

class D: В

{protected int у;

public D(int а) {у = а;}

public D(int a, int b):base(b) {у = а; }

public void Show() {Console.WriteLine("x=" + x + "y=" + y); }

}

class Program {

static void Main(string[] args) {

D ob1 = new D(4);

D ob2 = new D(6,7);

ob1.Show();

ob2.Show();

}

}

Результат работы программы:

x=0y=4

x=7y=6

Скрытие имен базового класса при наследовании.

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

Эта функция слова new совершенно отличается от его использования при создании экземпляра объекта.

Для доступа к скрытому имени базового класса используется ключевое слово base. Ссылка base всегда указывает на базовый класс производного класса, в котором она используется. В этом случае формат ее записи такой:

base.члeн

Здесь в качестве элемента член можно указывать либо метод, либо переменную базового класса. Эта форма ссылки base наиболее применима в тех случаях, когда имя члена в производном классе скрывает член с таким же именем в базовом классе.

// Пример сокрытия имени в связи с наследованием.

using System;

class А { public int i = 0; }

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

{ new int i; // Этот член i скрывает член i класса А.

public В(int a, int b)

{ base.i = a; // Так можно обратиться к i класса А.

i = b; // Переменная i в классе В.

}

public void show() {Console.WriteLine("i в базовом классе: " + base.i+"i в производном классе: "+ i);}

}

class NameHiding

{ public static void Main()

{ B ob = new В(2,3); ob.show();

}

}

Результат работы программы:

i в базовом классе: 2i в производном классе: 3

Ключевое слово new при объявлении члена i в классе B сообщает компилятору о том, что вы знаете, что создается новая переменная с именем i, которая скрывает переменную i в базовом классе А. Если убрать слово new, компилятор сгенерирует предупреждающее сообщение. Несмотря на то что переменная экземпляра i в классе В скрывает переменную i в классе А, ссылка base позволяет получить доступ к i в базовом классе. Ссылка base всегда ссылается на конструктор "ближайшего" производного класса

 

Второй вид наследования включение-делегирование.

 

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

 

Пусть существуют два независимых класса Radio для автомобильного радиоприемника и Саr для легкового автомобиля. Класс Саr может содержать элемент типа Radio. В терминологии ООП контейнерный класс (в нашем случае Саr) называется родительским (parent), а внутренний класс, который помещен внутрь контейнерного (это, конечно, Radio), называется дочерним ( child).

 

using System;

class Radio

{bool state;

public Radio() { }

public void TurnOn(bool on) {state=on;}

}

class Car

{private Radio r;

string Marka;

public Car(string s){Marka = s; r = new Radio();}

public void SetRadio(bool a) { r.TurnOn (a); } // Передаем (делегируем) запрос внутреннему объекту

}

class Demo {

public static void Main()

{ Car carl = new Car ("Mersedes"); carl.SetRadio(true);

}

}

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

 

Произвести инициализацию средствами С# можно и так:

 

public class Car // Автомобиль «имеет» (has-a) радио

{private Radio r = new Radio(); } // Встроенное радио

 

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

Во внешний класс Car добавляется дополнительный открытый метод SetRadio(), который обеспечивает доступ к объекту внутреннего класса Radio.

 

Определение вложенных типов.

В С# возможно определить тип непосредственно внутри другого типа. Такие типы называются вложенными (nested):

// В С# возможно вкладывать друг в друга классы, интерфейсы и структуры

public class MyClass

{... // Члены внешнего класса

public class MyNestedClass

{... // Члены внутреннего класса

}

}

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

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

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

Пусть класс Radio будет вложенным для класса Саr. При этом к классу Radio из внешнего мира напрямую обратиться будет невозможно.

// За пределами класса Саr такая операция приведет только к ошибке!

Radio r= new Radio();

 

Ссылки на базовый класс и объекты производных классов.

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

Xx = new Х(10);

Х х2;

Y у = new Y(5);

х2 = х; // OК, обе переменные имеют одинаковый тип.

х2 = у; // Ошибка, здесь переменные разного типа.

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

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

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

using System;

class X

{ public int a;

public X (int i) {a = i;}

}

class Y: X

{ public int b;

publicY(int i, int j): base(j){b =i;} }

class Demo

{ public static void Main()

{ X x = new X(10);

X x2;

Y y = new Y(5,6);

x2 = x; // OK, обе переменные имеют одинаковый тип.

Console.WriteLine("х2.а: " + х2.а);

х2 = у; // Все равно ok, поскольку класс Y выведен из класса X.

Console.WriteLine("х2.а: " + х2.а);

// Х-ссылки "знают" только о членах класса X.

х2.а = 19; // OK

// x2.b = 27; // Ошибка, в классе X нет члена b

}

}

Класс Y - производный от класса X, поэтому допустимо ссылке х2 присвоить ссылку на объект класса Y.

 

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

Вот почему ссылка х2 не может получить доступ к члену b класса Y даже при условии, что она указывает на объект класса Y. И это вполне логично, поскольку базовый класс "не имеет понятия" о том, что добавил в свой состав производный класс. Поэтому последняя строка программы представлена как комментарий.

 

Виртуальные методы.

 

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

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

Следовательно, если имеются ссылки на различные объекты, будут выполняться различные версии виртуального метода. Другими словами, именно тип объекта, на который указывает ссылка (а не тип ссылки) определяет, какая версия виртуального метода будет выполнена. Таким образом, если базовый класс содержит виртуальный метод и из этого класса выведены производные классы, то при наличии ссылки на различные типы объектов (посредством ссылки на базовый класс) будут выполняться различные версии этого виртуального метода.

Чтобы объявить метод в базовом классе виртуальным, его объявление необходимо предварить ключевым словом virtual. При переопределении виртуального метода в производном классе используется модификатор override. Итак, процесс переопределения виртуального метода в производном классе иногда называется замещением метода (method overriding). При переопределении метода сигнатуры типа у виртуального и метода-заменителя должны совпадать. Кроме того, виртуальный метод нельзя определять как статический (static) или абстрактный (abstract).

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

// Демонстрация виртуального метода,

using System;

class В { // Создаем виртуальный метод в базовом классе,

public virtual void X(){Console.WriteLine("класс В");}

}

class Dl: В { // Переопределяем метод X() в производном классе,

public override void X() {Console.WriteLine("класс Dl");}

}

class D2: В { // Снова переопределяем метод X() в другом производном классе,

public override void X(){Console.WriteLine("класс D2");}

}

class OverrideDenso {

public static void Main() {

В Ob = new В();

Dl Ob1 = new Dl();

D2 Ob2 = new D2();

В baseRef; // Ссылка на базовый класс.

baseRef = Ob;

baseRef.X();

baseRef = Ob1;

baseRef.X();

baseRef = 0b2;

baseRef.X();

}

}

Результаты работы программы:

класс В

класс Dl

класс D2

 

В программе создается базовый класс В и два производных класса - D1 и D2. В классе В объявляется метод с именем Х(), а производные классы его переопределяют. В методе Main() объявляются объекты типа В, D1 и D, а также ссылка baseRef типа В. Затем программа поочередно присваивает ссылку на объект каждого типа ссылке baseRef и использует эту ссылку для вызова метода Х(). Нужная для выполнения версия определяется типом объекта, адресуемого в момент вызова, а не "классовым" типом ссылки baseRef.

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

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

 




<== предыдущая лекция | следующая лекция ==>
Первое информационное сообщение | Уфа, Тысячелистник 2014

Дата добавления: 2015-10-12; просмотров: 1257. Нарушение авторских прав; Мы поможем в написании вашей работы!



Функция спроса населения на данный товар Функция спроса населения на данный товар: Qd=7-Р. Функция предложения: Qs= -5+2Р,где...

Аальтернативная стоимость. Кривая производственных возможностей В экономике Буридании есть 100 ед. труда с производительностью 4 м ткани или 2 кг мяса...

Вычисление основной дактилоскопической формулы Вычислением основной дактоформулы обычно занимается следователь. Для этого все десять пальцев разбиваются на пять пар...

Расчетные и графические задания Равновесный объем - это объем, определяемый равенством спроса и предложения...

Мелоксикам (Мовалис) Групповая принадлежность · Нестероидное противовоспалительное средство, преимущественно селективный обратимый ингибитор циклооксигеназы (ЦОГ-2)...

Менадиона натрия бисульфит (Викасол) Групповая принадлежность •Синтетический аналог витамина K, жирорастворимый, коагулянт...

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

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

Психолого-педагогическая характеристика студенческой группы   Характеристика группы составляется по 407 группе очного отделения зооинженерного факультета, бакалавриата по направлению «Биология» РГАУ-МСХА имени К...

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

Studopedia.info - Студопедия - 2014-2024 год . (0.009 сек.) русская версия | украинская версия