Наследование Лекция 7. Основы наследования. Создание производных классов. Наследование является одной из основных особенностей объектно-ориентированного.

Презентация:



Advertisements
Похожие презентации
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
Advertisements

Полиморфизм. Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Учебный курс Объектно-ориентированный анализ и программирование Лекция 7 Методы как средство реализации операций Лекции читает кандидат технических наук.
Наследование. Наследование – это свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся.
Основы информатики Классы Заикин Олег Сергеевич zaikin.all24.org
1 Классы в Java Ключевое слово class означает: Я говорю тебе, как выглядит новый тип объекта. Класс является базовым элементом объектно-ориентированного.
Индексаторы и операции классов. Лекция 5. Индексаторы. Если в классе есть скрытое поле, представляющее собой набор элементов, например, массив, то в нем.
Полиморфизм Полиморфизм (polymorphism) - последний из трех "китов", на которых держится объектно-ориентированное программирование Слово это можно перевести.
Объектно-ориентированное программирование С++. Лекция 6 Карпов В.Э.
Классы в С#. Перечисления С# Перечисление задает конечное множество возможных значений, которые могут получать объекты класса перечисление. [атрибуты][модификаторы]
1 Java 6. ИНТЕРФЕЙСЫ И ВНУТРЕННИЕ КЛАССЫ. 2 Интерфейсы Не являются классами Ни один из объявленных методов не может быть реализован внутри интерфейса.
1 ©Павловская Т.А. (СПбГУ ИТМО) Курс «С#. Программирование на языке высокого уровня» Павловская Т.А.
Наследование Полиморфизм ВЫЗОВ КОНСТРУКТОРОВ И ДЕСТРУКТОРОВ ПРИ НАСЛЕДОВАНИИ.
Кафедра ОСУ, Java 2004 Слайд 1 Наследование Наследование позволяет использовать существующий класс для определения новых классов, т.е. способствует.
Практическое занятие 6. Функции. Большинство языков программирования используют понятия функции и процедуры. C++ формально не поддерживает понятие процедуры,
НаследованиеНаследование2 class Point { double x; double y; Color color; }; class Radius { Point center; double radius; };
Наследование и полиморфизм. «Быть» или «Иметь» а так же «Точно» или «Как получится»
Лекция 2: Описание класса 1. Поля 2. Методы 3. Конструкторы.
С# и ООП Формальное определение класса с C# Класс в C# - это пользовательский тип данных (user defined type), который состоит из данных (часто называемых.
Множественное наследование class A {... }; class B {... }; class C : public A, protected B {... }; !!! Спецификатор доступа распространяется только на.
Транксрипт:

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

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

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

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

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

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

Будем использовать описанный класс в качестве базового для класса «Студент»: 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(); }

Тогда в методе Main можно так работать с объектом класса: Student St1 = new Student(); //обращение к унаследованным полям от класса Chelovek: St1.fam = "Иванов"; St1.dat_rog = " "; 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вызван собственный метод

fam dat_rog rost ves info( ) vuz gruppa ocenki info_uch( ) Схематично класс Student можно представить так: Chelovek

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

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

fam dat_rog rost ves info( ) nagruzka zarplata kafedra info_p( ) Тогда класс Prepod можно представить так: Chelovek

В свою очередь, класс Student может быть базовым для другого класса: class SuperMan : Student { public int stip = 500;} fam dat_rog rost ves info( ) vuz gruppa ocenki info_uch( ) stip Chelovek Student

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- функции базового класса.

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

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

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

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

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(); }

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

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

Например, определим в классе 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.

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

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

Например, определим в классе 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; }

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

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

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

Константа, поле, свойство, объявленный в классе класс или структура скрывают все одноименные элементы базового класса. Например: 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); } }

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) приведет к ошибке

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

Например, если в предыдущем примере метод 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

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

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

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

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

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

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);

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

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

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

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

public void tab( ) { Console.WriteLine(""); 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(""); }

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()); }

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( ); }

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

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

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

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; } }}

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());

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; } }

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

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

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

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( ));

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( );

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( ); } }

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

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

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; } }

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

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