Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Podbelsky_V_V_C_Bazovy_kurs.pdf
Скачиваний:
69
Добавлен:
02.06.2015
Размер:
1.73 Mб
Скачать

252

Г л а в а 1 3

 

 

Если класс В является наследником (производным от) класса А, то для объекта класса В (objectB на рис. 13.5) доступны открытые члены классов А и В. В то же время для членов производного класса В доступны открытые и защищенные члены класса А (само собой доступны и все члены самого класса В). Объект класса А (objectА на схеме) никогда не имеет доступа к членам производного класса В.

13.5.Методы при наследовании

Оконструкторах. В приведенном примере наследования с классами «круг» и «кольцо» конструктор производного класса Ring явным образом обращается к конструктору базового класса Disk с помощью выражения base(Ri). Обращение происходит до выполнения операторов тела конструктора – в инициализаторе конструктора:

public Ring(double Ri, double ri) : base(Ri) { rad = ri; }

Так как в нашем примере поле rad базового класса Disk доступно для методов производного класса, то программист может попытаться так записать определение конструктора:

public Ring(double Ri, double ri) { base.rad = Ri; rad = ri; }

Внешне все выглядит правильно, но если не менять определения базового класса Disk, то компилятор выдаст следующее сообщение об ошибке:

'Disk' does not contain a constructor that takes '0' arguments

(Disk не содержит конструктора, который имеет '0' аргументов.)

Чтобы принять такое решение, компилятор использовал два правила. Первое из них мы уже приводили – если в определении класса присутствует объявление хотя бы одного конструктора, то конструктор без параметров автоматически в класс не добавляется. Правило второе относится к наследованию. Уже говорилось, что в отличие от других членов базового класса, конструкторы не наследуются. Конструктор базового класса необходимо

явно вызвать из инициализатора конструктора производного

Включение, вложение и наследование классов

253

 

 

класса. Если этого не сделано, то компилятор по умолчанию самостоятельно дополнит объявление конструктора (точнее его инициализатор) обращением к конструктору базового класса без параметров. Так как в классе Disk конструктор без параметров отсутствует, то компиляция завершается приведенным сообщением об ошибке.

Экранирование методов базового класса. Тот факт, что в производном классе могут быть определены новые методы, имена которых отличны от имен методов базового класса, наверное, нет необходимости пояснять. А вот на одноименных методах базового и производного классов остановимся подробно.

Во-первых, для методов возможна перегрузка (overload).

Вэтом случае одноименные методы базового и производного классов должны иметь разные спецификации параметров. Вовторых, разрешено переопределение, иначе экранирование или сокрытие (hiding), методом производного класса одноименного метода базового класса (спецификации параметров совпадают).

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

Вслучае перегрузки методов при наследовании никаких нововведений нет.

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

Покажем на примере переопределение (экранирование) методов. Определим класс «фигура на плоскости с заданными размерами вдоль координатных осей». Такой класс будет служить базовым для классов «прямоугольник», «эллипс», «треугольник» и т.д. Для простоты будем считать, что у прямоугольника стороны параллельны координатным осям, у эллипса оси параллельны координатным осям, у треугольника сторона и перпендикулярная ей высота параллельны координатным осям.

254

Г л а в а 1 3

 

 

При таком соглашении «габариты» базовой фигуры однозначно позволят вычислить площадь производной фигуры.

// 13_06.cs экранирование методов при наследовании class Figure // Базовый класс

{

protected double dx, dy; // Размеры вдоль осей public void print() {

Console.WriteLine("Габариты: dx={0:f2}, dy={1:f2}", dx, dy);

}

}

//Производный класс – прямоугольник: class Rectangle : Figure

{

public Rectangle(double xi, double yi) { dx = xi; dy = yi; }

public new void print()

{

Console.Write("Прямоугольник! \t"); base.print();

}

}

//Производный класс – треугольнмк: class Triangle : Figure

{

public Triangle(double xi, double yi) { dx = xi; dy = yi; }

public new void print()

{

Console.Write("Треугольник! \t"); base.print();

}

}

В каждом из производных классов есть свой метод print(), который экранирует одноименный метод базового класса. В методе print() производнного класса выполнено обращение к методу print() базового класса.

Применение классов иллюстрирует следующий код:

class Program

{

Включение, вложение и наследование классов

255

 

 

static void Main()

{

Rectangle rec = new Rectangle(3.0, 4.0); rec.print();

Triangle tre = new Triangle(5.0, 4.0); tre.print();

Figure fig = new Figure(); fig.print();

}

}

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

Прямоугольник! Габариты: dx=3,00, dy=4,00 Треугольник! Габариты: dx=5,00, dy=4,00

Габариты: dx=0,0, dy=0,0

Отметим, что при экранировании метод может иметь другой тип возвращаемого значения. Для тех, кто помнит, что в сигнатуру метода тип возвращаемого значения не входит, этот факт не окажется неожиданностью.

Виртуальные методы и полиморфизм. Ссылке с типом базового класса можно присвоить значение ссылки на объект производного класса. После такого присваивания ссылка не обеспечивает доступа к обычным (не виртуальным) методам производного класса. Рассмотрим следующий фрагмент программы с несколькими ссылками базового класса Figure, адресующими объекты и базового, и производных классов Triangle и Rectangle (программа 13_07.cs).

static void Main()

{

Figure fig1 = new Rectangle(3.0, 4.0); Figure fig2 = new Triangle(5.0, 4.0); Figure fig3 = new Figure(); fig1.print();

fig2.print();

fig3.print();

}

}

Три ссылки, имеющие тип Figure, ассоциированы с объектами разных классов. Затем с помощью этих ссылок

256

Г л а в а 1 3

 

 

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

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

Габариты: dx=3,00, dy=4,00 Габариты: dx=5,00, dy=4,00 Габариты: dx=0,00, dy=0,00

Адресаты обращений в выражениях fig1.print(), fig2.print(), fig3.print() определяются объявленным типом ссылок, а не типом значения, которое ассоциировано со ссылкой «присвоено». Связано это с невиртуальностью методов print().

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

В нашем примере изменения будут минимальными. Заголовок метода в базовом классе примет вид (программа 13_08.cs):

public virtual void print()

В каждом из производных классов заголовок переопределяющего метода примет вид:

public override void print()

Обратите внимание, что модификатор new заменен для виртуального метода модификатором override.

Результат выполнения того же фрагмента кода с определением и использованием ссылок fig1, fig2, fig3 будет таким:

Прямоугольник! Габариты: dx=3,00, dy=4,00 Треугольник! Габариты: dx=5,00, dy=4,00

Габариты: dx=0,00, dy=0,00

Здесь дважды выполнено обращение к методам print() производных классов, и последним выполнен вызов виртуального метода print() базового класса.

Включение, вложение и наследование классов

257

 

 

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

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

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

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

// 13_09.cs массив ссылок с типом базового класса using System;

class A

{

public virtual string record() { return "Базовый класс!"; }

}

class B : A

{

public override string record() { return "Производный В!"; }

}

class C : A

{

public override string record() { return "Производный C!"; }

}

class Program

{

static void Main()

{

A[] arrA = new A[] {new A(), new B(), new C(), new

B()};

foreach (A rec in arrA)

258

Г л а в а 1 3

 

 

Console.WriteLine(rec.record());

}

}

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

Базовый класс! Производный В! Производный C! Производный В!

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

// 13_10.cs ссылка с типом базового класса как

параметр using System;

class Aclass { }

class Bclass : Aclass { } class Cclass : Aclass { } class Program

{

static void type(Aclass par) { Console.WriteLine(par.ToString());

}

static void Main()

{

type(new Aclass()); type(new Bclass()); type(new Cclass());

}

}

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

Aclass

Bclass

Cclass

Включение, вложение и наследование классов

259

 

 

Пример (программа 13_11.cs):

static Aclass type(int m)

{

if (m == 0) return new Bclass(); if (m == 1) return new Cclass(); return new Aclass();

}

static void Main()

{

for (int i = 0; i < 3; i++) Console.WriteLine(type(i).GetType());

}

}

Результат:

Bclass

Cclass

Aclass

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

13.6. Абстрактные методы и абстрактные классы

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

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

260

Г л а в а 1 3

 

 

ного в примерах класса фигур вряд ли пригодны для метода, который вычисляет площадь или периметр фигуры. Эти характеристики нельзя определить, если о фигуре известны только ее габариты. Однако, в базовом классе можно «запланировать» проведение этих вычислений в производных классах. Для этого в базовый класс добавляют так называемые абстрактные методы, и класс объявляют абстрактным. Абстрактные методы задают сигнатуру реальных методов, которые должны быть реализованы в классах, производных от абстрактного.

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

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

Продемонстрируем особенности использования абстрактных классов и абстрактных методов на примере классов, производных от класса “фигура на плоскости”. Превратим этот базовый класс в абстрактный, добавив в него абстрактный метод для вычисления площади фигуры. Метод вывода сведений об объекте класса также сделаем абстрактным. Для иллюстрации объявим в этом классе не виртуальный метод, выполняющий сжатие (или увеличение) габаритных размеров фигуры в заданное число раз. Текст программы с таким классом:

// 13_12.cs абстрактные методы в абстрактном

классе

Включение, вложение и наследование классов

261

 

 

 

using System;

 

 

abstract class Figure // Абстрактный базовый класс

 

{

 

 

protected double dx, dy; // Размеры вдоль осей

 

public abstract void print();

 

public void compress(double r) { dx *= r; dy *= r; }

 

abstract public double square();

 

}

 

 

class Rectangle : Figure

{

 

public Rectangle(double xi, double yi)

 

{ dx = xi; dy = yi; }

 

 

public override void print()

{

 

Console.Write("Площадь прямоугольника={0:f2}. \t", square());

Console.WriteLine("Габариты: dx={0:f2}, dy={1:f2}", dx, dy);

}

public override double square() { return dx * dy; }

}

class Triangle : Figure {

public Triangle(double xi, double yi)

{dx = xi; dy = yi; } public override void print(){

Console.Write("Площадь треугольника={0:f2}. \t", square());

Console.WriteLine("Габариты: dx={0:f2}, dy={1:f2}", dx, dy);

}

public override double square()

{return dx * dy / 2; }

}

class Program

{

static void Main()

{

Figure fig = new Rectangle(3.0, 4.0); fig.print();

fig = new Triangle(5.0, 4.0); fig.print(); fig.compress(2.0); fig.print();

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]