'

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

Понравилась презентация – покажи это...





Слайд 0

Наследование Лекция №7


Слайд 1

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


Слайд 2

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


Слайд 3

Например, пусть имеется следующая иерархия классов: Для классов «Автотранспорт», «Самолеты» и «Поезда» класс «Транспортные средства» является прямым базовым классом, а для классов «Автобусы» и «Легковые автомобили» - косвенным.


Слайд 4

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


Слайд 5

Общая форма объявления класса, который наследует базовый класс: class <имя_произв._кл.> : <имя_базового_кл. > { <тело класса>} Например, class Chelovek { public string fam, dat_rog; public double rost, ves; public void info() { Console.WriteLine(fam +" " + dat_rog+ " " +rost+ " " +ves); } }


Слайд 6

Будем использовать описанный класс в качестве базового для класса «Студент»: class Student : Chelovek { public string vuz, gruppa; public int[ ] ocenki ; public void info_uch() { Console.Write(vuz + " " + gruppa+" оценки:" ); foreach (int x in ocenki) Console.Write(" "+x); Console.WriteLine(); } }


Слайд 7

Тогда в методе Main можно так работать с объектом класса: Student St1 = new Student(); //обращение к унаследованным полям от класса Chelovek: St1.fam = "Иванов"; St1.dat_rog = "12.07.89"; St1.rost = 185; St1.ves = 78; //обращение к собственным полям класса St1.ocenki = new int[] { 7, 2, 6 }; St1.vuz="ГГТУ им. П.О.Сухого"; St1.gruppa="ИТ-21"; St1.info( ); // от имени объекта st1вызван унаследованный метод St1.info_uch( ); // от имени объекта st1вызван собственный метод


Слайд 8

Схематично класс Student можно представить так: Chelovek


Слайд 9

Если класс используется в качестве базового производными классами, это не означает невозможность использования его самого. Например, Chelovek Man = new Chelovek( ); Man.fam = "Сидоров"; Man.rost = 178; Man.info( ) При этом нельзя использовать оператор Man.info_uch( ) Класс Сhelovek можно использовать в качестве базового и для других классов. Например,


Слайд 10

class Prepod:Chelovek { public int nagruzka, zarplata; public string kafedra; public void info_p() { Console.WriteLine(kafedra + " нагрузка " + nagruzka + " зарплата:" + zarplata); } }


Слайд 11

Тогда класс Prepod можно представить так: Chelovek


Слайд 12

В свою очередь, класс Student может быть базовым для другого класса: class SuperMan : Student { public int stip = 500;} Chelovek Student


Слайд 13

SuperMan Sm = new SuperMan( ); Sm.fam = "Кальчук"; Sm.ocenki = new int[] { 9, 9, 9 }; Sm.gruppa = "ИТ-22"; Console.WriteLine(Sm.fam + " " + Sm.stip); Sm.info_uch(); Доступ к элементам базового класса Производный класс наследует от базового класса ВСЕ, что он имеет. Но воспользоваться в производном классе можно не всем наследством. Производный класс не может получить доступ к тем из элементов, которые объявлены закрытыми (private). Косвенное влияние на такие элементы – лишь через public-функции базового класса.


Слайд 14

Например, пусть в классе Chelovek поле fam объявлено со спецификатором private: private string fam; Тогда следующий фрагмент будет ошибочным:


Слайд 15

class Prepod:Chelovek { public int nagruzka, zarplata; public string kafedra; public void info_p( ) { Console.WriteLine(fam); Console.WriteLine(kafedra + " нагрузка " + nagruzka + " зарплата:" + zarplata); } } Попытка обращения к закрытому полю базового класса Ошибки можно избежать, определив в базовом классе открытое свойство для доступа к закрытому полю fam.


Слайд 16

public string Fam { get { return fam; } set { fam = value; } } Тогда обращение к закрытому полю можно заменить именем свойства. С# позволяет создавать защищенные элементы класса. Защищенный член создается с помощью спецификатора доступа protected. Этот спецификатор обеспечивает открытый доступ к элементам базового класса, но только для производного класса. Для других классов доступ к защищенным элементам закрыт.


Слайд 17

Например, дополним класс Chelovek защищенным полем protected string status; Тогда можно получить доступ к этому полю в методах классов Student, Prepod и SuperMan, но нельзя обратиться к полю статус, например, в классе Program. При использовании производного класса в качестве базового для создания другого производного класса любой защищенный член исходного базового класса, который наследуется первым производным классом, также наследуется в статусе защищенного и вторым производным классом.


Слайд 18

class Student : Chelovek { public string vuz, gruppa; public int[ ] ocenki ; public void info_uch() { status = "студент"; Console.Write(status+" "+vuz + " " + gruppa+" оценки:" ); foreach (int x in ocenki) Console.Write(" "+x); Console.WriteLine(); } }


Слайд 19

Можно так: class SuperMan : Student { public int stip = 500; public void info_s() { status = "суперстудент"; Console.WriteLine(Fam+" - "+status); } } Но нельзя в классе Program, например, использовать код Chelovek Man = new Chelovek( ); Man.status = "человек";


Слайд 20

Использование конструкторов базового класса Если в базовом и производном классах не определены конструкторы, то при создании объекта производного класса используются конструкторы по умолчанию, предоставляемые системой программирования. Базовые и производные классы могут иметь собственные конструкторы. Конструктор базового класса инициализирует поля объекта, соответствующие базовому классу, а конструктор производного класса — поля объекта, соответствующие производному классу. Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор базового класса без параметров (при его отсутствии – конструктор по умолчанию).


Слайд 21

Например, определим в классе Student конструктор: public Student(string vz, string grp, int n) { vuz = vz; gruppa = grp; int[] ocenki = new int[n]; } Теперь при создании объекта класса Student Student St1 = new Student("ГГТУ им. П.О.Сухого","ИТ-21",3); так как конструктор в классе Chelovek отсутствует, будет вызван конструктор по умолчанию для полей, относящихся к базовому классу, а потом конструктор класса Student.


Слайд 22

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


Слайд 23

Конструктор производного класса с явным вызовом конструктора базового класса определяется следующим образом: <имя конструктора>(<список_параметров>): base (<список_аргументов>) { тело конструктора } Список аргументов содержит аргументы для конструктора базового класса. Если в базовом классе несколько конструкторов, то будет выбран конструктор, соответствующий количеству и типу аргументов в списке после слова base.


Слайд 24

Например, определим в классе SuperMan конструктор с вызовом конструктора, определенного в классе Student. public SuperMan(string vz, string grp, int n, int st) : base(vz, grp, n) { stip = st; } Тогда создание объекта класса с помощью этого конструктора может выглядеть следующим образом: SuperMan Sm = new SuperMan("ГГТУ им. П.О.Сухого", "ИТ-22",3,1000); Можно так: public SuperMan( int st): base("ГГТУ им. П.О.Сухого", "ИТ-22", 3) { stip = st; }


Слайд 25

Создание объекта с помощью этого конструктора: SuperMan Sm = new SuperMan(1000); В иерархии классов конструкторы базовых классов вызываются, начиная с самого верхнего уровня Для создания объектов в программе можно применять конструкторы трех степеней защиты: public – при создании объектов в рамках данного пространства имен, в методах любого класса — члена данного пространства имен; protected – при создании объектов в рамках производного класса, в том числе при построении объектов производного класса, а также для внутреннего использования классом — владельцем данного конструктора; private – применяется исключительно для внутреннего использования классом-владельцем данного конструктора.


Слайд 26

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


Слайд 27

По мнению создателей языка C#, тот факт, что ранее (до момента его переопределения) видимый элемент базового класса стал недоступен и невидим извне требует явного дополнительного подтверждения со стороны программиста. Например, переопределим в классе Student метод info( ) public void info() { Console.WriteLine(Fam + " " + dat_rog ); } Программа будет компилироваться и работать, но сначала будет выдано предупреждение. Чтобы оно не появлялось, лучше использовать спецификатор new: new public void info() { Console.WriteLine(Fam + " " + dat_rog ); }


Слайд 28

Константа, поле, свойство, объявленный в классе класс или структура скрывают все одноименные элементы базового класса. Например: class X { int d; public X(int x) { d = x; } public void f( ) { Console.WriteLine("d="+d); } public void f(int x) { Console.WriteLine("x=" + x); } }


Слайд 29

class Y : X { int s; new double f; public Y(int x) : base(x) { s = 10; f = 20.5; } public void show() { Console.WriteLine(" f=" + f); base.f( ); base.f(s); } } Попытка использовать обращение к методу базового класса f( ) или f(s) приведет к ошибке


Слайд 30

X x1 = new X(5); x1.f( ); Y y1 = new Y(3); y1.f( ); y1.show( ); Тогда можно так обращаться к элементам описанных классов: Итак, если элемент в производном классе скрывает нестатический элемент с таким же именем в базовом классе, то формат обращения к нестатическому элементу базового класса из производного следующий: base.<элемент базового класса> Если элемент в производном классе скрывает статический элемент с таким же именем в базовом классе, то формат обращения к элементу <имя класса>.< элемент базового класса > Если поле f в производном классе описано со спецификатором public, то методы базового класса будут скрыты для других классов и этот оператор будет ошибочным. Можно только обращаться к полю производного класса y1.f;


Слайд 31

Например, если в предыдущем примере метод f с параметром класса Х сделать статическим: public static void f(int x) то оператор base.f(s); следует заменить на X.f(s); Пусть в программе определен класс, наследующий от класса Y class Z : Y { new int f; // переопределено поле f public Z( ) : base(5) { } public void sh() { f = 5; base.f( ); show( ); Console.WriteLine(" В Z f=" + f); } } Так как поле f класса Y закрыто, то ссылка base означает класс X и происходит обращение к методу класса X


Слайд 32

Если бы поле f класса Y было бы защищенным или открытым, то ключевое слово base обозначало бы класс Y, и оператор base.f( ); был бы ошибочным, а можно было бы использовать, например, оператор base.f = 2; Определенный в производном классе метод скрывает в базовом классе свойства, поля, типы, обозначенные тем же самым идентификатором, а также одноименные методы с той же сигнатурой; Например, пусть в классе Y элемент f не поле, а метод: public void f(int x) { Console.WriteLine(" В Y x=" + x); }


Слайд 33

Тогда внутри класса Y можно использовать оператор f( ), т.е. метод f без параметров класса Х остается видимым в классе Y и к нему можно обращаться без ключевого слова base. Оператор f(50); будет выполнять обращение к методу f с параметром класса Y. Чтобы вызвать метод с такой сигнатурой класса Х, нужно использовать оператор base.f(50) для нестатического метода или X.f(50) для статического. Объявляемые в производном классе индексаторы скрывают индексаторы в базовом классе с аналогичной сигнатурой.


Слайд 34

Например: class X { protected int[ ] x=new int[5]; public int this[int i] { get { return x[i]; } set { x[i] = value; } } } В классе Y переопределим индексатор:


Слайд 35

class Y:X { new public int this[int i] { get { return x[i]; } } } Тогда, если создан объект класса X X x1 = new X( ); то оператор x1[3] = 5; обращается к индексатору, определенному в классе X и ошибки нет.


Слайд 36

Для объекта класса Y y1 оператор y1[3] = 5; вызывает индексатор, переопределенный в классе Y, что приведет к ошибке, поскольку индексатор только для чтения. Если в производном классе переопределяется вложенный класс, то из производного класса виден только переопределенный класс, а для доступа к классу вложенному в базовый нужно использовать полное квалифицированное имя: <имя базового класса>.<имя вложенного класса> Например: class A { public class X { public int x = 100;} }


Слайд 37

class B:A { new class X // Вложенный класс базового класса скрывается { public int x = 10; public double y = 2.5; } static void Main(string[ ] args) { X x1 = new X( ); // объект класса Х из класса B Console.WriteLine(x1.x+" "+x1.y);


Слайд 38

Предотвращение наследования A.X x2 = new A.X( ); // объект класса Х из класса А Console.WriteLine(x2.x); Console.ReadKey( ); } } Для того чтобы запретить наследовать от класса или какой-то элемент класса, необходимо при определении использовать спецификатор sealed. Классы со спецификатором sealed называют бесплодными.


Слайд 39

sealed class A { . . . } // Следующий класс создать невозможно. class В : А {// ОШИБКА! Класс А не может иметь наследников. . . .} class X { sealed public void f0() { } } class Y:X { public void f0(){} // ОШИБКА! Переопределение f0 запрещено! }


Слайд 40

Абстрактные классы Если базовый класс используется исключительно как основа для создания классов — наследников, если он никогда не будет использован для создания объектов, если часть методов (возможно, что и все) базового класса обязательно переопределяется в производных классах, то класс объявляют как абстрактный со спецификатором abstract. Методы, которые будут обязательно переопределяться в потомках, также определяются со спецификатором abstract и не содержат тела. Объявление абстрактной функции завершается точкой с запятой.


Слайд 41

В производном классе соответствующая переопределяемая абстрактная функция обязательно должна включать в заголовок функции спецификатор override. На основе абстрактного класса невозможно создать объекты. Попытка создания соответствующего объекта — представителя абстрактного класса приводит к ошибке. Например, abstract class X { protected double xn,xk,dx; abstract protected double f(double x);


Слайд 42

public void tab( ) {Console.WriteLine("г=============T================¬"); Console.WriteLine("¦ Аргумент ¦ Функция ¦"); Console.WriteLine("¦=============+================¦"); double x = xn; while (x < xk + dx / 2) { Console.WriteLine("¦{0,12:f2} ¦{1,15:f3} ¦", x, f(x)); x = x + dx; } Console.WriteLine("L=============¦================-"); } }


Слайд 43

class Y : X { public Y(double xxn, double xxk, double dxx) { xn = xxn; xk = xxk; dx = dxx; } protected override double f(double x) { return Math.Sin(x); } } class Z : X { public Z() { xn = Double.Parse(Console.ReadLine()); xk = Double.Parse(Console.ReadLine()); dx = Double.Parse(Console.ReadLine()); }


Слайд 44

protected override double f(double x) { return Math.Cos(x); } } class Program { static void Main(string[ ] args) { Y f1 = new Y(2, 5, 0.5); f1.tab(); Z f2 = new Z( ); f2.tab( ); Console.ReadKey( ); } }


Слайд 45

В C# можно описать массив объектов базового класса и занести в него объекты производного класса. Но для объектов производного класса вызываются только методы и свойства, унаследованные от предка, т.е.определенные в базовом классе. Тем не менее это можно обойти. Рассмотрим пример: Создать класс «Транспорт» (элементы: поля – вид транспорта, время отправления, пункт отправления, пункт назначения; методы для ввода и вывода данных о рейсе) На его основе реализовать классы «Поезд» (доп. поля: номер поезда, массив свободных мест в каждом вагоне, свойство Количество свободных мест, метод ввода данных, метод вывода данных) и «Самолет» (доп. поля: название авиакомпании, время начала регистрации, количество свободных мест; метод ввода данных, метод вывода информации).


Слайд 46

В методе Main необходимо создать массив из элементов базового класса, заполненных ссылками на объекты производных классов. Требуется вывести список поездов и самолетов, которыми можно добраться из заданного пункта отправления до определенного пункта назначения с указанием времени отправления, количества свободных мест, а также для поезда номер поезда, а для самолета название авиакомпании и время начала регистрации. class Transport { public string vid; string P_nazn, P_otpr,vremja;


Слайд 47

protected void vvod() { Console.WriteLine("Пункт отправления?"); P_otpr = Console.ReadLine(); Console.WriteLine("Пункт назначения?"); P_nazn = Console.ReadLine(); Console.WriteLine("Время отправления?"); vremja = Console.ReadLine(); }


Слайд 48

protected void vivod( ) { Console.WriteLine(vid+" Пункт отправления: "+P_otpr+ " Пункт назначения: "+P_nazn+ " Время отправления:"+vremja); } public string Po { get { return P_otpr; } } public string Pn { get { return P_nazn; } }}


Слайд 49

class Poezd:Transport { int nomer; int[ ] kv; public Poezd(int n) { kv = new int[n]; } new public void vvod() { base.vvod(); Console.WriteLine("номер поезда?"); nomer = int.Parse(Console.ReadLine());


Слайд 50

for (int i=0; i < kv.Length; i++) { Console.WriteLine("количество свободных мест в "+I +" вагоне: "); kv[i] = int.Parse(Console.ReadLine()); }} public int Ksv { get { int S = 0; for (int i = 0; i < kv.Length; i++) S = S + kv[i]; return S; } }


Слайд 51

new public void vivod( ) {base.vivod( ); Console.WriteLine("№ "+nomer+ " количество свободных мест : "+Ksv+"\n"); }} class Samolet : Transport { string nazvanie, time; int ksv;


Слайд 52

new public void vvod() { base.vvod(); Console.WriteLine("авиакомпания?"); nazvanie = Console.ReadLine( ); Console.WriteLine("время регистрации?"); time = Console.ReadLine(); Console.WriteLine("количество свободных мест?"); ksv = int.Parse(Console.ReadLine()); }


Слайд 53

new public void vivod() { base.vivod(); Console.WriteLine("авиакомпания: " + nazvanie + " начало регистрации : " + time + " количество свободных мест : " + ksv + "\n"); } }


Слайд 54

class Program { static void Main(string[ ] args) { Console.WriteLine("сколько рейсов?"); int n=int.Parse(Console.ReadLine()); Transport[ ] t=new Transport[n]; for (int i = 0; i < n; i++) { Console.WriteLine("Вид транспорта?(1-поезд/2-самолет)"); int v = int.Parse(Console.ReadLine( ));


Слайд 55

if (v = = 1) { Poezd p = new Poezd(5); p.vvod( ); p.vid = "поезд"; t[i] = p; } else { Samolet s = new Samolet( ); s.vvod( ); s.vid = "самолет"; t[i] = s; } } Console.WriteLine("Задайте пункт отправления?"); string P_o = Console.ReadLine( ); Console.WriteLine("Задайте пункт назначения?"); string P_n = Console.ReadLine( );


Слайд 56

Console.WriteLine("Транспорт в заданном направлении:\n"); foreach (Transport tt in t) { if (tt.Pn == P_n && tt.Po == P_o) { if (tt.vid == "поезд") { Poezd p = (Poezd)tt; p.vivod( ); } else { Samolet s = (Samolet)tt; s.vivod( ); } } }


Слайд 57

Виртуальные методы Методы, которые в производных классах могут быть реализованы по другому, можно определять в качестве виртуальных. Для этого используется ключевое слово virtual. Например, virtual public void vyvod( ) Объявление метода виртуальным означает, что решение о том какой из одноименных методов иерархии будет вызван, принимается не на стадии компиляции, а на стадии выполнения программы в зависимости от вызывающего объекта.


Слайд 58

Если в производном классе нужно переопределить виртуальный метод, используется ключевое слово override. Переопределенный виртуальный метод должен иметь такой же набор параметров, как и соответствующий метод базового класса. class F1 { protected double x; public F1(double x1) { x = x1; }


Слайд 59

virtual public double f() { return x + 2; } public void vivod( ) { Console.WriteLine("x=" + x + " значение функции = " + f()); } } class F2 : F1 { public F2(double x1):base(x1) { } public override double f( ) { return x + 5; } }


Слайд 60

class Program { static void Main(string[] args) { F1 y = new F1(3); y.vivod(); F2 z = new F2(5); z.vivod(); Console.ReadKey(); } }


Слайд 61

Результаты работы: x=3 значение функции = 5 x=5 значение функции = 10 Если без override, то результаты работы: x=3 значение функции = 5 x=5 значение функции = 7


×

HTML:





Ссылка: