1629295407-c61bfe4caba98380ea3e7cdae6295416 (846200), страница 52
Текст из файла (страница 52)
Вероятно, вам приходилосьслышать эти термины, но мы будем придерживаться стандартных C#-терминов. В C++также используются термины “базовый класс/производный класс”.Использование защищенного доступаКак упоминалось выше, закрытый член базового класса недоступен для производногокласса. Казалось бы, это означает, что, если производный класс должен иметь доступ кчлену базового класса, его нужно сделать открытым. При этом придется смириться с тем,что открытый член будет доступным для любого другого кода, что иногда нежелательно. Ксчастью, таких ситуации можно избежать, поскольку C# позволяет создавать защищенныечлены. Защищенным является член, который открыт для своей иерархии классов, но закрытвне этой иерархии.Защищенный член создается с помощью модификатора доступа protected.
Приобъявлении protected-члена он по сути является закрытым, но с одним исключением.Это исключение вступает в силу, когда защищенный член наследуется. В этом случаезащищенный член базового класса становится защищенным членом производного класса, аследовательно, и доступным для производного класса. Таким образом, используямодификатор доступа protected, можно создавать закрытые (для “внешнегоГлава 11. Наследование283мира”) члены класса, но вместе с тем они будут наследоваться с возможностью доступа состороны производных классов.Рассмотрим простой пример использования защищенных членов класса.// Демонстрация использования защищенных членов класса.using System;class В {protected int i, j;// Закрыт внутри класса В,//но доступен для класса D.public void set(int a, int b) {i = a;j = b;}}public void show() {Console.WriteLine(i + " " + j);}class D : В {int k; // Закрытый член.// Класс D получает доступ к членам i и j класса В.public void setk() {k = i * j;}}public void showk() {Console.WriteLine(k);}class ProtectedDemo {public static void Main() {D ob = new D();}}ob.set(2, 3); // OK, так как D "видит" В-члены i и j.ob.show(); // OK, так как D "видит" В-члены i и j.ob.setk(); // OK, так как это часть самого класса D.ob.showk(); // OK, так как это часть самого класса D.Поскольку в этом примере класс в наследуется классом D и члены i и j объявленызащищенными в классе в (т.е.
с использованием модификатора доступа protected), методsetk() может получить к ним доступ. Если бы члены i и j были объявлены в классе взакрытыми, класс D не имел бы к ним права доступа, и программа не скомпилировалась бы.Подобно модификаторам public и private модификатор protected остается сосвоим членом независимо от реализуемого количества уровней наследования.Следовательно, при использовании производного класса в качестве базового для созданиядругого производного класса любой защищенный член исходного базового класса, которыйнаследуется первым производным классом, также наследуется в статусе защищенного ивторым производным классом.284Часть I.
Язык C#Конструкторы и наследованиеВ иерархии классов как базовые, так и производные классы могут иметь собственныеконструкторы. При этом возникает важный вопрос: какой конструктор отвечает за созданиеобъекта производного класса? Конструктор базового или конструктор производного класса,или оба одновременно? Ответ таков: конструктор базового класса создает часть объекта,соответствующую базовому классу, а конструктор производного класса — часть объекта,соответствующую производному классу. И это вполне логично, потому что базовый класс“не видит” или не имеет доступа к элементам производного класса.
Поэтому ихконструкции должны быть раздельными. В предыдущих примерах классы опирались наконструкторы по умолчанию, создаваемые автоматически средствами C#, и поэтому мы несталкивались с подобной проблемой. Но на практике большинство классов имеетконструкторы, и вы должны знать, как справляться с подобной ситуацией.Если конструктор определяется только в производном классе, процесс созданияобъекта несложен: просто создается объект производного класса. Часть объекта,соответствующая базовому классу, создается автоматически с помощью конструктора поумолчанию. Например, рассмотрим переработанную версию класса Triangle, в которойопределяется конструктор. Здесь член style объявлен private-членом, поскольку теперьон устанавливается конструктором.// Добавление конструктора в класс Triangle.using System;// Класс двумерных объектов.class TwoDShape {double pri_width; // Закрытый член.double pri_height; // Закрытый член.}// Свойства width и height.public double width {get { return pri_width; }set { pri_width = value; }}public double height {get { return pri_height; }set { pri_height = value; }}public void showDim() {Console.WriteLine("Ширина и высота равны " +width + " и " + height);}// Класс треугольников - производный от класса TwoDShape.class Triangle : TwoDShape {string style; // Закрытый член.// Конструктор.public Triangle(string s, double w, double h) {width = w; // Инициализирует член базового класса.height = h; // Инициализирует член базового класса.Глава 11.
Наследование285}style = s; // Инициализирует член своего класса.}// Метод возвращает значение площади треугольника.public double area() {return width * height / 2;}// Отображаем тип треугольника.public void showStyle() {Console.WriteLine("Треугольник " + style);}class Shapes3 {public static void Main() {Triangle t1 = new Triangle("равнобедренный",4.0, 4.0);Triangle t2 = new Triangle("прямоугольный",8.0, 12.0);Console. WriteLine("Информация о t1: ");t1.showStyle();t1.showDim();Console.WriteLine("Площадь равна " + t1.area());Console.WriteLine();}}Console.WriteLine("Информация о t2: ");t2.showStyle();t2.showDim();Console.WriteLine("Площадь равна " + t2.area());Здесь конструктор класса Triangle инициализирует наследуемые им члены классаTwoDShape, а также собственное поле style.Если конструкторы определены и в базовом, и в производном классе, процесссоздания объектов несколько усложняется, поскольку должны выполниться конструкторыобоих классов.
В этом случае необходимо использовать еще одно ключевое слово C# base,которое имеет два назначения: вызвать конструктор базового класса и получить доступ кчлену базового класса, который скрыт “за” членом производного класса. Сначаларассмотрим первое назначение слова base.Вызов конструкторов базового классаПроизводный класс может вызывать конструктор, определенный в его базовомклассе, используя расширенную форму объявления конструктора производного класса иключевое слово base.
Формат расширенного объявления таков:конструктор_производного_класса(список_параметров) : base(список_аргументов) {// тело конструктора}Здесь с помощью элемента список_аргументов задаются аргументы,необходимые конструктору в базовом классе.286Часть I. Язык C#Чтобы понять, как используется ключевое слово base, рассмотрим в следующейпрограмме еще одну версию класса TwoDShape.
В ней определяется конструктор, которыйинициализирует свойства width и height.// Добавление конструкторов в класс TwoDShape.using System;// Класс двумерных объектов.class TwoDShape {double pri_width; // Закрытый член.double pri_height; // Закрытый член.// Конструктор класса TwoDShape.public TwoDShape(double w, double h) {width = w;height = h;}// Свойства width и height.public double width {get { return pri_width; }set { pri_width = value; }}public double height {get { return pri_height; }set { pri_height = value; }}}public void showDim() {Console.WriteLine("Ширина и высота равны " +width + " и " + height);}// Класс треугольников, производный от класса TwoDShape.class Triangle : TwoDShape {string style; // Закрытый член.// Вызываем конструктор базового класса.public Triangle(string s,double w,double h) : base(w, h) {style = s;}// Метод возвращает площадь треугольника.public double area() {return width * height / 2;}}// Отображаем тип треугольника.public void showStyle() {Console.WriteLine("Треугольник " + style);}Глава 11.
Наследование287class Shapes4 {public static void Main() {Triangle t1 = new Triangle("равнобедренный", 4.0, 4.0);Triangle t2 = new Triangle("прямоугольный", 8.0, 12.0);Console.WriteLine("Информация о t1: ");t1.showStyle();t1.showDim();Console. WriteLine("Площадь равна " + t1.area());Console.WriteLine();}}Console.WriteLine("Информация о t2: ");t2.showStyle();t2.showDim();Console.WriteLine("Площадь равна " + t2.area());Здесь конструктор Triangle() вызывает “метод” base() с параметрами w и h, чтов действительности означает вызов конструктора TwoDShape(), который инициализируетсвойства width и height значениями w и h, соответственно. Класс Triangle больше неинициализирует эти значения сам. Ему остается инициализировать только одно значение,уникальное для класса треугольников, а именно член style (тип треугольника).
Такойподход дает классу TwoDShape свободу выбора среди возможных способов построенияподобъектов. Более того, со временем класс TwoDShape может расширять свои функции,но об этом расширении ранее созданные производные классы не будут “знать”, чтопредотвратит существующий код от разрушения.С помощью ключевого слова base можно вызвать конструктор любой формы,определенный в базовом классе. Реально же выполнится тот конструктор, параметрыкоторого будут соответствовать переданным при вызове аргументам.
Например, вот каквыглядят расширенные версии классов TwoDShape и Triangle, которые включаютконструкторы по умолчанию и конструкторы, принимающие один аргумент:// Добавляем в класс TwoDShape конструкторы.using System;class TwoDShape {double pri_width; // Закрытый член.double pri_height; // Закрытый член.// Конструктор по умолчанию.public TwoDShape() {width = height = 0.0;}// Конструктор класса TwoDShape с параметрами.public TwoDShape(double w, double h) {width = w;height = h;}// Создаем объект, у которого ширина равна высоте.public TwoDShape(double x) {width = height = x;}288Часть I.