
- •Ключове слово this
- •Ключове слово static
- •Статичний конструктор
- •Інкапсуляція з використанням методів get і set
- •Інкапсуляція з використанням властивостей
- •Організація робіт при описі класу. Атрибут partial
- •Спадкоємство
- •Int point; // поле
- •Додавання до класу запечатаного класу
- •Вкладеність класів
- •Поліморфізм
- •Абстрактні класи
- •Приховування членів класу
- •Оператори as і is
- •Структури
Спадкоємство
Спадкоємство - це можливість використання існуючих класів для створення нових класів. Нові класи можуть створюватися на основі існуючих класів, успадковуючи їх функціональність, тобто те що уміють робити старі класи. При цьому старі класи називають батьками або базовими класами, а нові - нащадками або дочірніми класами, або похідними класами. Нові класи фактично розширюють функціональність старих, тому що уміють робити те, що уміють старі, але і ще дещо, чого немає в старих.
При спадкуванні в дочірньому класі стають доступними всі члени батьківського класу з атрибутом public або protected.
Задамо базовий клас у вигляді, показаному в лістингу 8.8.
Лістинг 8.8
class car // Автомобіль
{
const int maxSpeed = 90; // Макс. швидкість автомобіля
int carSpeed; // Поле
public int Speed // Властивість (повинна бути загальнодоступною)
{
get {return carSpeed;}
set
{
carSpeed = value; // Одна з форм синтаксису задання властивості
if(carSpeed > maxSpeed) // Контроль задання швидкості автомобіля користувачем
carSpeed = maxspeed;
}
} // Speed
} // car
Тут заданий клас car (автомобіль) всього з одним полем - швидкість автомобіля (carSpeed). На його основі створена властивість Speed, в якій у методі встановлення властивості (set) перевіряється, чи не введе користувач неприпустиму величину максимальної швидкості, яка задана константою maxSpeed. Якщо таке відбудеться, то метод get візьме за введену швидкість величину максимальної швидкості. Допустимо, ви хочете побудувати інший клас автомобіля (My_car) і такий, у якого були б ті ж поля і властивості, що і у car, але щоб новий клас ще містив у собі марку автомобіля. Хай це нове поле буде називатися carname. Зрозуміло, що нелогічно знову створювати клас з тими ж даними що вже розроблені, і додавати в нього нові дані. Краще б було скористатися існуючим, а потім додати свої. Тобто бажано успадкувати вже готову функціональність і додати свою. Це і дозволяє робити принцип спадкоємства, закладений в C#. Точніше, в його компілятор. У нашому випадку ми можемо спокійно написати синтаксичну конструкцію, представлену в лістингу 8.9.
Лістинг 8.9
class My_car : car
{
const int maxName = 20;
string carName; // Поле - марка автомобіля
public string Name // Властивість (повинна бути загальнодоступною)
{
get {return carName;}
set
{
carName = value;
if(carName.Length > maxName)
carName="помилка: марка повинна мати " + "не більше 20-ти символів";
}
}
}
Конструкція схожа на ту, що показана в лістингу 8.8, тому пояснимо тільки її перший рядок: так задається клас-спадкоємець - через двокрапку від його імені пишеться клас-батько.
Зведемо тепер дані лістингів 8.8 і 8.9 в одну програму, додавши до цього метод Main(), щоб перевірити, чи працює знов створений клас My_car. Ми повинні переконатися, чи можна з цього класу отримати властивість Speed з класу-батька, чи можна змінювати його в дочірньому класі і працювати з маркою автомобіля в дочірньому класі. Вся програма повністю приведена в лістингу 8.10, а результат її роботи показаний на рис. 8.7.
Лістинг 8.10
using System;
namespace app27_inherit
{
class car // Автомобіль
{
const int maxSpeed = 90; // Макс. швидкість автомобіля
int carSpeed; // Поле
public int Speed // Властивість
{
get {return carSpeed;}
set
{
carSpeed = value; // Одна із форм
// Задання властивості
if(carSpeed > maxSpeed) // Контроль задання швидкості автомобіля корисувачем
carSpeed = maxSpeed;
}
} // Speed
} // car
class My_car : car
{
// Тут немає конструктора: він береться за замовчуванням
const int maxName = 20;
string carName; // Поле — марка автомобіля
public string Name // Властивість
{
get {return carName;}
set
{
carName = value; if(carName.Length > maxName)
carName="Помилка: марка повинна мати " + "не більше 20-ти символів";
}
}
}
class Program
{
public static void Main()
{
Console.WriteLine("Дані щодо класу-нащадку:");
car obj_car = new car {Speed = 85}; // Ініціалізація об'єкта — базового класу
Console.WriteLine("Встановлена швидкість в " + "батьківському класі: {0}",
obj_car.Speed);
My_car cr = new My_car {Name = "Volvo"};
// Ініціалізація об'єкта
cr.Speed=88;
Console.WriteLine("Марка автомобіля: " + "{0}\nШвидкість в дочірньому класі: {1}",
cr.Name, cr.Speed);
Console.Write("Press any key to continue... ");
Console.Read();
}
}
}
Рис. 8.7. Результати роботи з успадкованими і батьківськими властивостями
По-перше, треба не забувати, що коли ви створюєте властивості, ті повинні мати атрибут public, тобто бути загальнодоступними. Якщо атрибут public не зазначений, компілятор такому членові класу автоматично присвоїть атрибут private. Але якщо властивості - автоматичні, тоді їм присвоюється public за замовчуванням. У нашому випадку властивості оголошені не автоматичні, а традиційним способом, правда, з удосконаленим синтаксисом.
По-друге, коли створюється об'єкт похідного класу, всі його члени повинні отримати значення, зокрема, природно, і ті, що перейдуть від батька, через конструктор або шляхом доступу безпосередньо (див. cr.Speed=88;).
Заборона на спадкоємство
У C# існує спеціальне ключове слово. Якщо їм помітити клас, то від цього класу не можна буде успадковувати. Це слово - sealed (запечатаний). Якщо ви оголосили sealed class car { члени класу}, то при спробі компіляції конструкції class My_car : car { члени класу} компілятор видасть помилку. Наприклад, багато системних класів запечатані, тобто не дозволяють себе розширювати за рахунок спадкоємства. Конструкція
My_string : string { }
видасть помилку компіляції, бо клас string запечатаний: користуватися його членами - будь ласка, але свої не додавайте.
Конструктори і спадкоємство
В ієрархії класів допускається, щоб у базових і похідних класів були власні конструктори. У зв'язку з цим виникає наступне резонне питання: який конструктор відповідає за побудову об'єкта похідного класу: конструктор базового класу, конструктор похідного класу або ж обидва? Виявляється, що конструктор базового класу конструює базову частину об'єкта, а конструктор похідного класу - похідну частину цього об'єкта. І в цьому є своя логіка, бо базовому класу невідомі і недоступні будь-які елементи похідного класу, а значить, їх конструювання повинне відбуватися окремо.
Якщо конструктор визначений тільки в похідному класі (так званий спеціалізований конструктор, тобто з параметрами), то все відбувається дуже просто: конструюється об'єкт похідного класу а базова частина об'єкта автоматично конструюється його конструктором, використовуваним за замовчуванням (у базовому класі конструктора немає, як ми припустили, отже, береться конструктор за замовчуванням). Коли конструктори визначаються як в базовому, так і в похідному класі, процес побудови об'єкта дещо ускладнюється, бо повинні виконуватися конструктори обох класів. А в базовому класі може бути багато конструкторів. Який треба виконувати у даному конкретному випадку? Тут доводиться використовувати ключове слово base, яке знаходить потрібний конструктор базового класу і виконує його. Існує форма оголошення конструктора похідного класу, за допомогою якого може бути викликаний конструктор, визначений в його базовому класі:
конструктор похідного_класу(список_параметрів): base (список_параметрів)
{
// тіло конструктора
}
де base (список_параметрів) - це параметри, потрібні якомусь конструктору в базовому класі для його запуску. Який конструктор саме повинен запускатися, компілятор визначає за числом і типом параметрів, заданих в методі (а це саме метод) base. Після цього ініціалізовані поля, стануть відомі в похідному об'єкті.
Приклад програми приведений в лістингу 8.11. Результат роботи представлено на рис. 8.8.
Лістинг 8.11
using System;
namespace app28_base
{
class Myclass
{
public int x, y, z; // Поля
// Конструктор базового класу
public Myclass(int x, int y, int z)
{
/* тут використано this, бо імена полів та імена параметрів в конструкторі співпадають.
this.x означає, що х відноситься до поля х */
this.x = x;
this.y = y;
this.z = z;
}
}
class inherite_class : Myclass
{