Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ПРИС_шпоры.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
3.46 Mб
Скачать

11. C#. Наследование: правила, синтаксис. Сокрытие имен.

Наследование — один из трех фундаментальных принципов объектно-ориентиро-

ванного программирования, поскольку именно благодаря ему возможно созда-

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

класс, который определяет характеристики, присущие множеству связанных элемен-

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

классами с добавлением в каждый из них своих, уникальных особенностей.

В языке С# класс, который наследуется, называется базовым. Класс, который на-

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

это специализированная версия базового класса. В производный класс, наследующий

все переменные, методы, свойства, операторы и индексаторы, определенные в базо-

вом классе, могут быть добавлены уникальные элементы.

Основы наследования

С# поддерживает наследование, позволяя в объявление класса встраивать другой

класс. Это реализуется посредством задания базового класса при объявлении произ-

водного. Лучше всего начать с примера. Рассмотрим класс TwoDShape, в котором оп-

ределяются атрибуты "обобщенной" двумерной геометрической фигуры (например,

^квадрата, прямоугольника, треугольника и т.д.).

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

class TwoDShape {

public double width;

public double height;

public void showDimO {

Console.WriteLine("Ширина и высота равны " +

width + " и " + height);

Класс TwoDShape можно использовать в качестве базового (т.е. как стартовую

площадку) для классов, которые описывают специфические типы двумерных объек-

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

класса Triangle. Обратите внимание на то, как объявляется класс Triangle.

// Простая иерархия классов.

using System;

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

class TwoDShape {

public double width;

public double height;

public void showDimO {

Console.WriteLine("Ширина и высота равны " +

width + " и " + height);

// Класс Triangle выводится из класса TwoDShape.

class Triangle : TwoDShape {

public string style; // Тип треугольника.

// Метод возвращает площадь треугольника,

public double area() {

return width * height / 2;

// Отображаем тип треугольника,

public void showStyleO {

Console.WriteLine("Треугольник " + style);

class Shapes {

public s t a t i c void MainO {

Triangle t l = new Triangle();

Triangle t2 = new T r i a n g l e ( ) ;

t l . w i d t h = 4.0;

t l . h e i g h t - 4.0;

t l . s t y l e = "равнобедренный";

t2.width = 8.0;

t2.height ≪ 12.0;

t 2 . s t y l e = "прямоугольный";

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

tl.showStyle();

tl.showDim();

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

Console.WriteLine();

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

t2. showStyleO ;

t2.showDim();

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

Вот результаты работы этой программы.

Информация о tl:

Треугольник равнобедренный

Ширина и высота равны 4 и 4

Площадь равна 8

Информация о t2:

Треугольник прямоугольный

Ширина и высота равны 8 и 12

Площадь равна 4 8

В классе Triangle создается специфический тип объекта класса TwoDShape, в

данном случае треугольник. Класс Triangle содержит все элементы класса

TwoDShape и, кроме того, поле s t y l e , метод area О и метод showStyleO. В пере-

менной s t y l e хранится описание типа треугольника, метод area () вычисляет и воз-

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

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

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

class Triangle : TwoDShape {

Этот синтаксис можно обобщить. Если один класс наследует другой, то имя базо-

вого класса указывается после имени производного, причем имена классов разделяют-

ся двоеточием. В С# синтаксис наследования класса очень прост для запоминания и

использования.

Поскольку класс Triangle включает все члены базового класса, TwoDShape, он

может обращаться к членам width и height внутри метода агеа(). Кроме того,

внутри метода MainO объекты t l и t2 могут прямо ссылаться на члены width и

height, как если бы они были частью класса Triangle. Включение класса

TwoDShape в класс Triangle схематически показано на рис. 11.1.

TwoDShape

width

height

showDim()

style

area

showStyle()

Triangle

Рис. 11.1. Схематическое представление класса Triangle

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

В С# (в отличие от C++) не поддерживается наследование нескольких базовых клас-

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

С++-кода на С#. Однако можно создать иерархию наследования, в которой один

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

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

Основное достоинство наследования состоит в том, что, создав базовый класс, ко-

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

для создания любого числа более специализированных производных классов. В каж-

дом производном классе можно затем точно "настроить" собственную классифика-

цию.

Закрытый член класса остается закрытым в рамках этого класса. К нему

нельзя получить доступ из кода, определенного вне этого класса, включая

производные классы. Базовый и производный классы иногда называют суперклассом и подклассом. Эти термины пришли из программирования на языке Java. Суперкласс в Java — это базовый класс в С#. Подкласс в Java — это производный класс в С#. Вероятно, вам при-

ходилось слышать эти термины, но мы будем придерживаться стандартных С#-

терминов. В C++ также используются термины "базовый класс/производный класс".

Использование защищенного доступа

Как упоминалось выше, закрытый член базового класса недоступен для производ-

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

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

смириться с тем, что открытый член будет доступным для любого другого кода, что

иногда нежелательно. К счастью, таких ситуаций можно избежать, поскольку С# по-

зволяет создавать защищенные члены. Защищенным является член, который открыт для

своей иерархии классов, но закрыт вне этой иерархии.

Защищенный член создается с помощью модификатора доступа protected. При

объявлении protected-члена он по сути является закрытым, но с одним исключени-

ем. Это исключение вступает в силу, когда защищенный член наследуется. В этом

случае защищенный член базового класса становится защищенным членом производ-

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

используя модификатор доступа protected, можно создавать закрытые (для "внеш-

него мира") члены класса, но вместе с тем они будут наследоваться с возможностью

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

Конструкторы и наследование

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

ные конструкторы. При этом возникает важный вопрос: какой конструктор отвечает

за создание объекта производного класса? Конструктор базового или конструктор

производного класса, или оба одновременно? Ответ таков: конструктор базового клас-

са создает часть объекта, соответствующую базовому классу, а конструктор производ-

ного класса — часть объекта, соответствующую производному классу. И это вполне

логично, потому что базовый класс "не видит" или не имеет доступа к элементам

производного класса. Поэтому их конструкции должны быть раздельными. В преды-

дущих примерах классы опирались на конструкторы по умолчанию, создаваемые ав-

томатически средствами С#, и поэтому мы не сталкивались с подобной проблемой.

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

справляться с подобной ситуацией.

Если конструктор определяется только в производном классе, процесс создания

объекта несложен: просто создается объект производного класса. Часть объекта, соот-

ветствующая базовому классу, создается автоматически с помощью конструктора по

умолчанию.

Наследование и сокрытие имен

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

члена базового класса. В этом случае член базового класса становится скрытым в про-

изводном классе. Поскольку с точки зрения формального синтаксиса языка С# эта

ситуация не является ошибочной, компилятор выразит свое "недоумение" всего лишь

предупреждающим сообщением. Это предупреждение должно послужить напомина-

нием о факте сокрытия имени. Если вы действительно собирались скрыть член базо-

вого класса, то для предотвращения этого предупреждения перед членом произвол-

ного класса необходимо поставить ключевое слово new. Необходимо понимать, что

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

экземпляра объекта.

Рассмотрим пример сокрытия имени.

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

using System;

class A {

public int i = 0;

}

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

class В : А {

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

public В(int b) {

i = b; // Член i в классе В.

public void show() {

Console.WriteLine(

"Член i в производном классе: " + i);

class NameHiding {

public s t a t i c void Main() {

В ob = new В(2);

ob.show();

Во-первых, обратите внимание на использование ключевого слова new при объяв-

лении члена i в классе в. По сути, он сообщает компилятору о том, что вы знаете,

что создается новая переменная с именем i, которая скрывает переменную i в базо-

вом классе А. Если убрать слово new, компилятор сгенерирует предупреждающее со-

общение.

Результаты выполнения этой программы выглядят так: /

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

Поскольку в классе в определяется собственная переменная экземпляра с именем

i , она скрывает переменную i, определенную в классе А. Следовательно, при вызове

метода show() для объекта типа в, отображается значение переменной i, соответст-

вующее ее определению в классе В, а не в классе А.

Использование ключевого слова base для доступа к скрытому имени

Существует вторая форма использования ключевого слова base, которая действует

подобно ссылке t h i s , за исключением того, что ссылка base всегда указывает на ба-

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

ее записи такой:

base.член

Здесь в качестве элемента член можно указывать либо метод, либо переменную

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

члена в производном классе скрывает член с таким же именем в базовом классе. Рас-

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

// Использование ссылки base для доступа к скрытому имени.

using System;

class A {

public int i = 0;

}

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

class В : А {

new int i; // Эта переменная i скрывает i класса А.

public В(int a, int b) { !

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

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

}

public void show() {

// Эта инструкция отображает переменную i в классе А.

Console.WriteLine("i в базовом классе: " + base.i);

// Эта инструкция отображает переменную i в классе В.

Console.WriteLine("i в производном классе: " + i );

class UncoverName {

public static void Main() {

В ob = new В(1f 2 );

ob.show();

Результаты выполнения этой программы выглядят так: Ii в базовом классе: 1

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

Несмотря на то что переменная экземпляра i в классе В скрывает переменную i в

классе А, ссылка base позволяет получить доступ к i в базовом классе.

С помощью ссылки base также можно вызывать скрытые методы.