Г. Шилдт - Полный справочник по C# (1160789), страница 55
Текст из файла (страница 55)
Он вызывает конструкторпо умолчанию класса TwoDShape. */public Triangle () {style = "null";// Конструктор с параметрами.public Triangle(string s, double w, double h) : base(w, h) {style = s;254Часть I. Язык С#// Создаем равнобедренный треугольник,public Triangle(double x) : base(x) {style = "равнобедренный";// Метод возвращает значение площади треугольника,public double area() {return width * height / 2;// Метод отображает тип треугольника,public void showStyleO {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 tl =new ColorTriangle("синий", "прямоугольный",8.0, 12.0);ColorTriangle t2 =new ColorTriangle("красный", "равнобедренный",2.0, 2.0);Console.WriteLine("Информация о tl: " ) ;tl.showStyle() ;tl.showDim();tl.showColor();Console .WriteLine ("Площадь равна " + tl.areaO);Console.WriteLine ();Console.WriteLine("Информация о t2: " ) ;t2.showStyle();t2.showDim();t2.showColor();Console.WriteLine("Площадь равна " + t2.area());Глава 11, Наследование295При выполнении этой профаммы получаем следующие результаты.Информация о tl:Треугольник прямоугольныйШирина и высота равны 8 и 12Цвет синийПлощадь равна 48Информация о t2:Треугольник равнобедренныйШирина и высота равны 2 и 2Цвет красныйПлощадь равна 2Благодаря наследованию класс C o l o r T r i a n g l e может использовать ранее определенные классы T r i a n g l e и TwoDShape, добавляя только ту информацию, которая необходима для собственного (специального) применения.
В этом и состоит ценностьнаследования: оно позволяет использовать код многократно.Этот пример иллюстрирует еще один важный момент: base всегда ссылается наконструктор "ближайшего" производного класса. Так, в классе C o l o r T r i a n g l e ссылка base вызывает конструктор, определенный в классе T r i a n g l e . В классе T r i a n g l eссылка base вызывает конструктор, определенный в классе TwoDShape. Если в иерархии классов конструктору базового класса требуются параметры, все производныеклассы должны передавать эти параметры, независимо от того, нужны ли эти параметры самому производному классу.Последовательность вызова конструкторовУ читателя может возникнуть вопрос: какой конструктор выполнится первым присоздании объекта производного класса — определенный в производном классе или вбазовом? Например, если класс в — производный от класса А, то конструктор классаА будет вызван до вызова конструктора класса в, или наоборот? Ответ звучит так.
Виерархии классов конструкторы вызываются в порядке выведения классов, т.е. начиная с конструктора базового класса и заканчивая конструктором производного класса.Более того, этот порядок не нарушается, независимо от использования ссылки base.Если ссылка base не используется, будут выполнены конструкторы по умолчанию(т.е. конструкторы без параметров) всех базовых классов. Порядок выполнения конст)укторов демонстрируется в следующей программе:// Демонстрация порядка выполнения конструкторов.ifeing System;// Создаем базовый класс,class A {public A() {Console.WriteLine("Создание класса А .
" ) ;// Создаем класс, производный от А.class В : А {public B() {Console.WriteLine("Создание класса В . " ) ;296Часть I. Язык С#// Создаем класс, производный от В.class С : В {public C() {Console.WriteLine("Создание класса С . " ) ;class!OrderOfConstruction {public static void Main()С с = new С();Вот результаты, сгенерированные программой:Создание класса А.Создание класса В.Создание класса С.Как видите, конструкторы вызываются в порядке выведения классов.И в этом есть логика. Поскольку базовый класс "ничего не знает" о производном,то действия по инициализации, которые он должен выполнить, никак не связаны ссуществованием производного класса. Более того, они (действия) могут быть необходимы как обязательное условие (предпосылка) инициализации, выполняемой производным классом в форме вызова его конструктора. Потому-то конструктор базовогокласса выполняется первым.Ссылки на базовый класс и объектыпроизводных классовКак вы знаете, С# — строго типизированный язык.
За исключением стандартногои автоматического преобразований, которые применяются к простым типам, совместимость типов строго соблюдается. Следовательно, ссылочная переменная одного"классового" типа обычно не может ссылаться на объект другого "классового" типа.Рассмотрим, например, следующую программу:Ф// Эта программа не скомпилируется.class X {int a;Шpublic X(int i){ а = i; }class Y {int a;public Y(int i)v{ a = i; }class IncompatibleRef {public s t a t i c void Main() {X x = new X(10);X x2;Глава 11. НаследованиеФ297Y у = new Y{5) ;x2 = x; // OK, обе переменные имеют одинаковый тип.х2 = у; // Ошибка, здесь переменные разного типа.Несмотря на то что здесь классы X и Y физически представляют собой одно и тоже, невозможно присвоить объект класса Y ссылочной переменной типа х, посколькуони имеют разные типы. В общем случае ссылочная переменная может ссылатьсятолько на объекты своего типа.Однако существует важное исключение из С#-требования строгой совместимоститипов.
Ссылочную переменную базового класса можно присвоить ссылке на объект^любого класса, выведенного из этого базового класса. Рассмотрим пример.// Ссылка на базовый класс может указывать на// объект производного класса.using System;class X {public int a;public X(int i) {a = i;class Y : X {public int b;public Y(int i, int j) : base(j) {b - i;class BaseRef {public static void Main() {X x = new X(10);X x2;Y у = new Y(5, 6) ;x2 = x; // OK, обе переменные имеют одинаковый тип.Console.WriteLine("x2.a: " + х2.а);х2 = у; // Все равно ok, поскольку класс Y// выведен из класса X.Console.WriteLine("х2.а: " + х2.а);//// Х-ссылки "знают" только о членах класса X.х2.а = 19; // ОКх2.Ь = 27; // Ошибка, в классе X нет члена Ь.На этот раз класс Y — производный от класса X, поэтому допустимо ссылке х2присвоить ссылку на объект класса Y.298Часть I.
Язык С#Важно понимать, что именно тип ссылочной переменной (а не тип объекта, на который она ссылается) определяет, какие члены могут быть доступны. Другими словами, когда ссылка на производный класс присваивается ссылочной переменной базового класса, вы получаете доступ только к тем частям объекта, которые определеныбазовым классом.
Вот почему ссылка х2 не может получить доступ к члену b класса Yдаже при условии, что она указывает на объект класса Y. И это вполне логично, поскольку базовый класс "не имеет понятия" о том, что добавил в свой состав производный класс. Поэтому последняя строка программы представлена как комментарий.И хотя последний абзац может показаться несколько "эзотерическим", он имеетряд важных практических приложений. Одно из них описано в этом разделе, а другое — ниже в этой главе при рассмотрении виртуальных методов.Важность присвоения ссылок на производный класс ссылочным переменным базового класса ощущается в случае, когда в иерархии классов вызываются конструкторы.
Как вы знаете, считается нормальным определить для класса конструктор, который в качестве параметра принимает объект своего класса. Это позволяет классу создать копию объекта. Классы, выведенные из такого класса, могут из этого фактаизвлечь определенную пользу. Рассмотрим, например, следующие версии классовTwoDShape и T r i a n g l e . В оба класса добавлены конструкторы, которые в качествегшраметра принимают объект.// Передача ссылки на производный класс// ссылке на базовый класс.u s i n g System;c l a s s TwoDShape {double p r i _ w i d t h ;double p r i _ h e i g h t ;// Закрытый член,// Закрытый член.// Конструктор по умолчанию,p u b l i c TwoDShape() {width = h e i g h t = 0.0;}// Конструктор класса TwoDShape.p u b l i c TwoDShape(double w, double h) {width = w;h e i g h t = h;}// Создаем объект, в котором ширина равна высоте,p u b l i c TwoDShape(double x) {width = h e i g h t = x;}// Создаем объект из объекта,p u b l i c TwoDShape(TwoDShape ob) {width = ob.width;height = ob.height;}// Свойства width и height,public double width {get { return pri_width; }set { pri__width = value; }Глава 11.
Наследование299public double height {get { return pri_height; }set { prijheight = value; }public void showDimO {Console.WriteLine("Ширина и высота равны " +width + " и " + height);// Класс треугольников, производный от класса TwoDShape.class Triangle : TwoDShape {string style; // Закрытый член.// Конструктор по умолчанию,public Triangle() {style = "null";// Конструктор класса Triangle,public Triangle(string s,double w,double h) : base(w, h) {style ~ s;// Создаем равнобедренный треугольник,public Triangle(double x) : base(x) {style = "равнобедренный";// Создаем объект из объекта,public Triangle(Triangle ob) : base(ob) {style « ob.style;// Метод возвращает площадь треугольника,public double area() {return width * height / 2;// Метод отображает тип треугольника,public void showStyle() {Console.WriteLine("Треугольник " + style);class Shapes7 {public static void Main() {Triangle tl = new Triangle("прямоугольный", 8.0, 12.0);// Создаем копию объекта tl.Triangle t2 = new Triangle(tl);Console.WriteLine("Информация о tl: " ) ;tl.showStyleO ;tl.showDimO ;500Часть I.
Язык С#Console. WriteLine( "Площадь равна " + tl. area O b Console .WriteLine();Console.WriteLine("Информация о t2: " ) ;t2.showStyle();t2.showDim();Console.WriteLine("Площадь равна " + t2.area());В этой программе объект t 2 создается из объекта t l и является идентичным ему.JBOT результаты выполнения этой программы:Информация о tl:Треугольник прямоугольныйШирина и высота равны 8 и 12Площадь равна 48Информация о t2:Треугольник прямоугольныйШирина и высота равны 8 и 12Площадь равна 4 8Обратите внимание на этот конструктор класса T r i a n g l e :// Создаем объект из объекта.public Triangle(Triangle ob) : base(ob) {style = ob.style;}Он принимает объект типа T r i a n g l e и передаетjviexaHH3Ma) этому конструктору класса TwoDShape:// Создаем объект из объекта.public TwoDShape(TwoDShape ob) {width = ob.width;height = ob.height;*его (посредствомbase-Ключевым моментом здесь является то, что конструктор TwoDShape () ожидаетобъект класса TwoDShape.
Однако конструктор T r i a n g l e () передает ему объект класса T r i a n g l e . Как разъяснялось выше, такой "номер проходит" благодаря тому, чтоссылка на базовый класс может указывать на объект производного класса. Следовательно, вполне допустимо передать конструктору TwoDShape () ссылку на объекткласса, выведенного из класса TwoDShape. Поскольку конструктор TwoDShape () инициализирует только те части объекта производного класса, которые являются членамикласса TwoDShape, не имеет значения, что объект может содержать и другие члены,добавленные производным классом.Виртуальные методы и их переопределениеВиртуальным называется метод, объявляемый с помощью ключевого словаv i r t u a l в базовом классе и переопределяемый в одном или нескольких производныхклассах.
Таким образом, каждый производный класс может иметь собственную версию виртуального метода. Виртуальные методы представляют интерес с такой позиции: что произойдет, если виртуальный метод будет вызван посредством ссылки набазовый класс. Какую именно версию метода нужно вызвать, С# определяет по типуГлава 11.
Наследование301объекта, на который указывает эта ссылка, причем решение принимается динамически, во время выполнения программы. Следовательно, если имеются ссылки на различные объекты, будут выполняться различные версии виртуального метода. Другими словами, именно тип объекта, на который указывает ссылка (а не тип ссылки) определяет, какая версия виртуального метода будет выполнена. Таким образом, если базовыйкласс содержит виртуальный метод и из этого класса выведены производные классы,то при наличии ссылки на различные типы объектов (посредством ссылки на базовыйкласс) будут выполняться различные версии этого виртуального метода.Чтобы объявить метод в базовом классе виртуальным, его объявление необходимопредварить ключевым словом v i r t u a l .