программирование. Руководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт
Скачать 3.32 Mb.
|
Аналогично состоянию public и private, состояние protected сохраняется за членом класса независимо от количества уровней наследования. Поэтому когда производный класс используется в качестве базового для другого производного класса, любой защищенный член исходного базового класса, наследуемый первым производным классом, наследуется как защищенный и вторым производным классом. Несмотря на всю свою полезность, защищенный доступ пригоден далеко не для всех ситуаций. Так, в классе TwoDShape из приведенного ранее примера требовалось, чтобы значения его членов Width и Height были доступными открыто, поскольку нужно было управлять значениями, которые им присваивались, что было бы невозможно, если бы они были объявлены как protected. В данном случае более подходящим решением оказалось применение свойств, чтобы управлять доступом, а не предотвращать его. Таким образом, модификатор доступа protected следует применять в том случае, если требуется создать член класса, доступный для всей иерархии классов, но для остального кода он должен быть закрытым. А для управления доступом к значению члена класса лучше воспользоваться свойством. Конструкторы и наследование В иерархии классов допускается, чтобы у базовых и производных классов были свои собственные конструкторы. В связи с этим возникает следующий резонный вопрос: какой конструктор отвечает за построение объекта производного класса: конструктор базового класса, конструктор производного класса или же оба? На этот вопрос можно ответить так: конструктор базового класса конструирует базовую часть объекта, а конструктор производного класса – производную часть этого объекта. И в этом есть своя логика, поскольку базовому классу неизвестны и недоступны любые элементы производного класса, а значит, их конструирование должно происходить раздельно. В приведенных выше примерах данный вопрос не возникал, поскольку они опирались на автоматическое создание конструкторов, используемых в C# по умолчанию. Но на практике конструкторы определяются в большинстве классов. Ниже будет показано, каким образом разрешается подобная ситуация. Если конструктор определен только в производном классе, то все происходит очень просто: конструируется объект производного класса, а базовая часть объекта автоматически конструируется его конструктором, используемым по умолчанию. В качестве примера ниже приведен переработанный вариант класса Triangle, в котором определяется конструктор, а член Style делается закрытым, так как теперь он устанавливается конструктором. // Добавить конструктор в класс Triangle, using System; 11 Класс для двумерных объектов. • class TwoDShape { double pri_width; double pr.i_height; // Свойства ширины и длины объекта, public double Width { get { return pri_width; } set { pri_width = value < 0 ? ‑value : value; } } public double Height { get { return pri_height; } set { pri_height = value < 0 ? ‑value : 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; // инициализировать член базового класса Style = s; // инициализировать член производного класса } // Возвратить площадь треугольника, public double Area() { return Width * Height / 2; } // Показать тип треугольника, public void ShowStyle() { Console.WriteLine("Треугольник " + Style); } } class Shapes3 { static void Main() { Triangle tl = new Triangle("равнобедренный", 4.0, 4.0); Triangle t2 = new Triangle("прямоугольный", 8.0, 12.0); Console.WriteLine("Сведения об объекте tl: "); tl.ShowStyle(); tl.ShowDim(); Console . WriteLine ("Площадь равна " + tl.AreaO); Console.WriteLine (); Console.WriteLine("Сведения об объекте t2: "); t2.ShowStyle(); t2 .‑ShowDim () ; Console.WriteLine("Площадь равна " + t2.Area()); } } В данном примере конструктор класса Triangle инициализирует наследуемые члены класса TwoDShape вместе с его собственным полем Style. Когда конструкторы определяются как в базовом, так и в производном классе, процесс построения объекта несколько усложняется, поскольку должны выполняться конструкторы обоих классов. В данном случае приходится обращаться к еще одному ключевому слову языка С#: base, которое находит двоякое применение: во‑первых, для вызова конструктора базового класса; и во‑вторых, для доступа к члену базового класса, скрывающегося за членом производного класса. Ниже будет рассмотрено первое применение ключевого слова base. Вызов конструкторов базового класса С помощью формы расширенного объявления конструктора производного класса и ключевого слова base в производном классе может быть вызван конструктор, определенный в его базовом классе. Ниже приведена общая форма этого расширенного объявления: конструктор_производного_класса{список_параметров) : base (список_аргументов) { // тело конструктора } где список_аргументов обозначает любые аргументы, необходимые конструктору в базовом классе. Обратите внимание на местоположение двоеточия. Для того чтобы продемонстрировать применение ключевого слова base на конкретном примере, рассмотрим еще один вариант класса TwoDShape в приведенной ниже программе. В данном примере определяется конструктор, инициализирующий свойства Width и Height. Затем этот конструктор вызывается конструктором класса Triangle. // Добавить конструктор в класс TwoDShape. using System; // Класс для двумерных объектов, class TwoDShape { double pri_width; double pri_height; // Конструктор класса TwoDShape. public TwoDShape(double w, double h) { Width = w; Height = h; } public double Width { get { return pri_width; } set { pri_width = value < 0 ? ‑value : value; } } public double Height { get { return pri_height; } set { pri_height = value < 0 ? ‑value : 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 ShowStyleO { Console.WriteLine("Треугольник " + Style); } } class Shapes4 { static void Main() { Triangle tl = new Triangle("равнобедренный", 4.0, 4.0); Triangle t2 = new Triangle("прямоугольный", 8.0, 12.0); Console.WriteLine("Сведения об объекте tl: "); tl.ShowStyle(); tl.ShowDim(); Console.WriteLine("Площадь равна " + tl.AreaO); Console.WriteLine(); Console.WriteLine("Сведения об объекте t2: "); t2.ShowStyle(); t2.ShowDim(); Console.WriteLine("Площадь равна " + t2.Area()); Теперь конструктор класса Triangle объявляется следующим образом. public Triangle( string s, double w, double h) : base(w, h) { В данном варианте конструктор 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 х) { Width = Height = x; } '// Свойства ширины и высоты объекта, public double Width { get { return pri_width; } set { pri_width = value < 0 ? ‑value : value; } } public double Height { get { return pri_height; } set { pri_height = value < 0 ? ‑value : value; } } public void ShowDimO { 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 ShowStyleO { Console.WriteLine("Треугольник " + Style); } } class Shapes5 { static void Main() { Triangle tl = new Triangle(); Triangle t2 = new Triangle("прямоугольный", 8.0, 12.0); Triangle t3 = new Triangle(4.0); tl = t2; Console.WriteLine("Сведения об объекте tl: "); tl.ShowStyle(); tl.ShowDim(); Console.WriteLine("Площадь равна " + tl.AreaO); 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(); } } Вот к какому результату приводит выполнение этого кода. Сведения об объекте tl: Треугольник прямоугольный Ширина и высота равны 8 и 12 Площадь равна 48 Сведения об объекте t2: Треугольник прямоугольный Ширина и высота равны 8 и 12 Площадь равна 48 Сведения об объекте t3: Треугольник равнобедренный Ширина и высота равны 4 и 4 Площадь равна 8 А теперь рассмотрим вкратце основные принципы действия ключевого слова base. Когда в производном классе указывается ключевое слово base, вызывается конструктор из его непосредственного базового класса. Следовательно, ключевое слово base всегда обращается к базовому классу, стоящему в иерархии непосредственно над вызывающим классом. Это справедливо даже для многоуровневой иерархии классов. Аргументы передаются базовому конструктору в качестве аргументов метода base (). Если же ключевое слово отсутствует, то автоматически вызывается конструктор, используемый в базовом классе по умолчанию. Наследование и сокрытие имен В производном классе можно определить член с таким же именем, как и у члена его базового класса. В этом случае член базового класса скрывается в производном классе. И хотя формально в C# это не считается ошибкой, компилятор все же выдаст сообщение, предупреждающее о том, что имя скрывается. Если член базового класса требуется скрыть намеренно, то перед его именем следует указать ключевое слово new, чтобы избежать появления подобного предупреждающего сообщения. Следует, однако, иметь в виду, что это совершенно отдельное применение ключевого слова new, не похожее на его применение при создании экземпляра объекта. Ниже приведен пример сокрытия имени. // Пример сокрытия имени с наследственной связью. using System; class А { public int i = 0; } // Создать производный класс. j class В : A { * new int i; // этот член скрывает член i из класса А public В(int b) { i = Ь; // член i в классе В } public void Show() { Console.WriteLine("Член i в производном классе: " + i) ; } } class NameHiding { static void Main() { В ob = new В(2); ob.Show() ; } } Прежде всего обратите внимание на использование ключевого слова new в следующей строке кода. new int i; // этот член скрывает член i из класса А В этой строке компилятору, по существу, сообщается о том, что вновь создаваемая переменная i намеренно скрывает переменную i из базового класса А и что автору программы об этом известно. Если же опустить ключевое слово new в этой строке кода, то компилятор выдаст предупреждающее сообщение. Вот к какому результату приводит выполнение приведенного выше кода. Член i в производном классе: 2 В классе В определяется собственная переменная экземпляра i, которая скрывает переменную i из базового класса А. Поэтому при вызове метода Show () для объекта типа В выводится значение переменной i, определенной в классе В, а не той, что определена в классе А. Применение ключевого слова base для доступа к скрытому имени Имеется еще одна форма ключевого слова base, которая действует подобно ключевому слову this, за исключением того, что она всегда ссылается на базовый класс в том производном классе, в котором она используется. Ниже эта форма приведена в общем виде: base .член где член может обозначать метод или переменную экземпляра. Эта форма ключевого слова base чаще всего применяется в тех случаях, когда под именами членов производного класса скрываются члены базового класса с теми же самыми именами. В качестве примера ниже приведен другой вариант иерархии классов из предыдущего примера. // Применение ключевого слова base для преодоления // препятствия, связанного с сокрытием имен. using System; class А { public int i = 0; } // Ссзздать производный класс, class В : А { |