Справочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией
Скачать 5.05 Mb.
|
Вызов конструкторов базового класса Производный класс может вызывать конструктор, определенный в его базовом классе, используя расширенную форму объявления конструктора производного класса и ключевое слово base . Формат расширенного объявления таков: конструктор_производного_класса ( список_параметров ) : base( список_аргументов ) { // тело конструктора } Здесь с помощью элемента список_аргументов задаются аргументы, необходимые конструктору в базовом классе. Глава 11. Наследование 287 Чтобы понять, как используется ключевое слово 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); } } 288 Часть I. Язык C# class 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; } Глава 11. Наследование 289 // Свойства 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; // Закрытый член. /* Конструктор по умолчанию. Он автоматически вызывает конструктор по умолчанию класса TwoDShape. */ public Triangle() { style = "null"; } // Конструктор, который принимает три аргумента. public Triangle(string s, double w, double h) : base(w, h) { style = s; } // Создаем равнобедренный треугольник. public Triangle(double x) : base(x) { style = "равнобедренный"; } // Метод возвращает площадь треугольника. public double area() { return width * height / 2; } // Отображаем тип треугольника. public void showStyle() { Console.WriteLine("Треугольник " + style); } } class Shapes5 { public static void Main() { Triangle t1 = new Triangle(); Triangle t2 = new Triangle("прямоугольный", 8.0, 12.0); Triangle t3 = new Triangle(4.0); t1 = t2; Console.WriteLine("Информация о t1: "); t1.showStyle(); 290 Часть I. Язык C# t1. showDim(); Console.WriteLine("Площадь равна " + t1.area()); Console.WriteLine(); Console.WriteLine("Информация о t2: "); t2.showStyle(); t2.showDim(); Console.WriteLine("Площадь равна " + t2.area()); Console.WriteLine(); Console.WriteLine("Информация о t3: "); t3.showStyle(); t3. showDim(); Console.WriteLine("Площадь равна " + t3.area()); Console.WriteLine(); } } При выполнении этой версии программы получаем такие результаты: Информация о t1: Треугольник прямоугольный Ширина и высота равны 8 и 12 Площадь равна 48 Информация о t2: Треугольник прямоугольный Ширина и высота равны 8 и 12 Площадь равна 4 8 Информация о t3: Треугольник равнобедренный Ширина и высота равны 4 и 4 Площадь равна 8 Рассмотрим ключевые концепции base -механизма. При задании производным классом base -“метода” вызывается конструктор непосредственного базового класса. Таким образом, ключевое слово base всегда отсылает к базовому классу, стоящему в иерархии классов непосредственно над вызывающим классом. Это справедливо и для многоуровневой иерархии. Чтобы передать аргументы конструктору базового класса, достаточно указать их в качестве аргументов “метода” base() . При отсутствии ключевого слова base автоматически вызывается конструктор базового класса, действующий по умолчанию. Наследование и сокрытие имен Производный класс может определить член, имя которого совпадает с именем члена базового класса. В этом случае член базового класса становится скрытым в производном классе. Поскольку с точки зрения формального синтаксиса языка C# эта ситуация не является ошибочной, компилятор выразит свое “недоумение” всего лишь предупреждающим сообщением. Это предупреждение должно послужить напоминанием о факте сокрытия имени. Если вы действительно собирались скрыть член базового класса, то для предотвращения этого предупреждения перед членом производного Глава 11. Наследование 291 класса необходимо поставить ключевое слово new . Необходимо понимать, что эта функция слова new совершенно отличается от его использования при создании экземпляра объекта. Рассмотрим пример сокрытия имени. // Пример сокрытия имени в связи с наследованием. using System; class A { public int i = 0; } // Создаем производный класс. class В : A { new int i; // Этот член i скрывает член i класса A. public В(int b) { i = b; // Член i в классе В. } public void show() { Console.WriteLine( "Член i в производном классе: " + i); } } class NameHiding { public static void Main() { В ob = new В(2); ob.show(); } } Во-первых, обратите внимание на использование ключевого слова new при объявлении члена i в классе B . По сути, он сообщает компилятору о том, что вы знаете, что создается новая переменная с именем i , которая скрывает переменную i в базовом классе А . Если убрать слово new , компилятор сгенерирует предупреждающее сообщение. Результаты выполнения этой программы выглядят так: Член i в производном классе: 2 Поскольку в классе в определяется собственная переменная экземпляра с именем i , она скрывает переменную i , определенную в классе А . Следовательно, при вызове метода show() для объекта типа B , отображается значение переменной i , соответствующее ее определению в классе B , а не в классе А Использование ключевого слова base для доступа к скрытому имени Существует вторая форма использования ключевого слова base , которая действует подобно ссылке this , за исключением того, что ссылка base всегда указывает на базовый класс производного класса, в котором она используется. В этом случае формат ее записи такой: base. член 292 Часть I. Язык C# Здесь в качестве элемента член можно указывать либо метод, либо переменную экземпляра. Эта форма ссылки base наиболее применима в тех случаях, когда имя члена в производном классе скрывает член с таким же именем в базовом классе. Рассмотрим следующую версию иерархии классов из предыдущего примера: // Использование ссылки base для доступа к скрытому имени. using System; class A { public int i = 0; } // Создаем производный класс. class B : A { new int i; // Эта переменная i скрывает i класса А. public B(int a, int b) { base.i = a; // Так можно обратиться к i класса А. i = b; // Переменная i в классе B. } public void show() { // Эта инструкция отображает переменную i в классе А. Console.WriteLine("i в базовом классе: " + base.i); // Эта инструкция отображает переменную i в классе В. Console.WriteLine("i в производном классе: " + i); } } class UncoverName { public static void Main() { B ob = new B(1, 2); ob.show(); } } Результаты выполнения этой программы выглядят так: i в базовом классе: 1 i в производном классе: 2 Несмотря на то что переменная экземпляра i в классе в скрывает переменную i в классе А , ссылка base позволяет получить доступ к i в базовом классе. С помощью ссылки base также можно вызывать скрытые методы. Рассмотрим пример. // Вызов скрытого метода. using System; class A { public int i = 0; // Метод show() в классе А. public void show() { Console.WriteLine("i в базовом классе: " + i); } Глава 11. Наследование 293 } // Создаем производный класс. class B : A { new int i; // Эта переменная i скрывает // одноименную переменную класса A. public B(int a, int b) { base.i = a; // Так можно обратиться к // переменной i класса А. i = b; // Переменная i в классе B. } // Этот метод скрывает метод show(), определенный в // классе А. Обратите внимание на использование // ключевого слова new. new public void show() { base.show(); // Вызов метода show() класса А. // Отображаем значение переменной i класса B. Console.WriteLine("i в производном классе: " + i); } } class UncoverName { public static void Main() { B ob = new B(1, 2); ob.show(); } } Вот результаты выполнения этой программы: i в базовом классе: 1 i в производном классе: 2 Как видите, при вызове base.show() происходит обращение к версии метода show() , определенной в базовом классе. Обратите внимание на то, что назначение ключевого слова new в этой программе — сообщить компилятору о том, что вы сознательно создаете в классе в новый метод с именем show() , который скрывает метод show() , определенный в классе А Создание многоуровневой иерархии До сих пор мы использовали простые иерархии, состоящие только из базового и производного классов. Но можно построить иерархии, которые содержат любое количество уровней наследования. Как упоминалось выше, один производный класс вполне допустимо использовать в качестве базового для другого производного класса. Например, из трех классов ( А , B и C ) с может быть производным от B , который, в свою очередь, может быть производным от А . В подобной ситуации каждый производный класс наследует содержимое всех своих базовых классов. В данном случае класс C наследует все члены классов B и A Чтобы понять, какую пользу можно получить от многоуровневой иерархии, рассмотрим следующую программу. В ней производный класс Triangle используется в качестве базового для создания производного класса с именем ColorTriangle . Класс 294 Часть I. Язык C# ColorTriangle наследует все члены классов Triangle и TwoDShape и добавляет собственное поле color , которое содержит цвет треугольника. // Многоуровневая иерархия. 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; } // Свойства 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; // Закрытый член. /* Конструктор по умолчанию. Он вызывает конструктор по умолчании класса TwoDShape. */ public Triangle() { style = "null"; } // Конструктор с параметрами. public Triangle(string s, double w, double h) : base(w, h) { style = s; Глава 11. Наследование 295 } // Создаем равнобедренный треугольник. public Triangle(double x) : base(x) { style = "равнобедренный"; } // Метод возвращает значение площади треугольника. public double area() { return width * height / 2; } // Метод отображает тип треугольника. public void showStyle() { Console.WriteLine("Треугольник " + style); } } // Продолжаем иерархию классов треугольников. class ColorTriangle : Triangle { string color; public ColorTriangle( string c, string s, double w, double h) : base(s, w, h) { color = c; } // Метод отображает цвет треугольника. public void showColor() { Console.WriteLine("Цвет " + color); } } class Shapes6 { public static void Main() { ColorTriangle t1 = new ColorTriangle("синий", "прямоугольный", 8.0, 12.0); ColorTriangle t2 = new ColorTriangle("красный", "равнобедренный", 2.0, 2.0); Console.WriteLine("Информация о t1: "); t1.showStyle(); t1.showDim(); t1.showColor(); Console.WriteLine("Площадь равна " + t1.area()); Console.WriteLine(); Console.WriteLine("Информация о t2: "); t2.showStyle(); t2. showDim(); t2.showColor(); Console.WriteLine("Площадь равна " + t2.area()); } } 296 Часть I. Язык C# При выполнении этой программы получаем следующие результаты. Информация о t1: Треугольник прямоугольный Ширина и высота равны 8 и 12 Цвет синий Площадь равна 48 Информация о t2: Треугольник равнобедренный Ширина и высота равны 2 и 2 Цвет красный Площадь равна 2 Благодаря наследованию класс ColorTriangle может использовать ранее определенные классы Triangle и TwoDShape , добавляя только ту информацию, которая необходима для собственного (специального) применения. В этом и состоит ценность наследования: оно позволяет использовать код многократно. Этот пример иллюстрирует еще один важный момент: base всегда ссылается на конструктор “ближайшего” производного класса. Так, в классе ColorTriangle ссылка base вызывает конструктор, определенный в классе Triangle . В классе Triangle ссылка base вызывает конструктор, определенный в классе TwoDShape . Если в иерархии классов конструктору базового класса требуются параметры, все производные классы должны передавать эти параметры, независимо от того, нужны ли эти параметры самому производному классу. Последовательность вызова конструкторов У читателя может возникнуть вопрос: какой конструктор выполнится первым при создании объекта производного класса — определенный в производном классе или в базовом? Например, если класс B — производный от класса А , то конструктор класса А будет вызван до вызова конструктора класса B , или наоборот? Ответ звучит так. В иерархии классов конструкторы вызываются в порядке выведения классов, т.е. начиная с конструктора базового класса и заканчивая конструктором производного класса. Более того, этот порядок не нарушается, независимо от использования ссылки base . Если ссылка base не используется, будут выполнены конструкторы по умолчанию (т.е. конструкторы без параметров) всех базовых классов. Порядок выполнения конструкторов демонстрируется в следующей программе: // Демонстрация порядка выполнения конструкторов. using System; // Создаем базовый класс. class A { public A() { Console.WriteLine("Создание класса A."); } } // Создаем класс, производный от A. class B : A { public B() { Console.WriteLine("Создание класса B."); } |