Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатика
ИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханика
ОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторика
СоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансы
ХимияЧерчениеЭкологияЭкономикаЭлектроника

Властивості та індексатори

Читайте также:
  1. Властивості z-перетворення
  2. Властивості бетонної суміші
  3. Властивості бітумних і дьогтьових в'яжучих речовин
  4. Властивості визначеного інтеграла.
  5. Властивості випадкових похибок
  6. Властивості глин
  7. Властивості продуктів в лікувальному харчуванні

Нагадаємо, що концепція інкапсуляції стосується принципу, згідно якого всі внутрішні дані об’єкта не повинні бути доступним через екземпляр об’єкта. Замість цього, якщо є необхідність у зміні стану об’єкта, то це має робитися через методи доступу (accessor, або метод get) та зміни (mutator, або метод set). У С# інкапсуляція забезпечується на синтаксичному рівні з використанням модифікаторів доступу (вже згадувалися та використовувалися нами у попередній темі): ключових слів public, private, internal і protected. Значення цих ключових слів може бути різним в залежності від місця застосування: до типу або до члена.

Для цієї теми важливими є роль та застосування модифікаторів доступу public та private. Модифікатор public може бути застосованим до типів та до членів типів. Його призначення − робити тип або член загальнодоступним (необмеженим у доступі). Модифікатор private може бути застосованим тільки до членів типів. Його призначення − робити член типу приватним (доступним тільки членам цього ж типу). За замовчуванням члени типів є приватними (неявно приватнимі).

Тепер програмування чорного ящика (коли клас захищає та приховує подробиці своєї реалізації від зовнішнього світу) може бути реалізоване однією з двох технік:

• визначення пари методів доступу та зміни;

• визначення властивості.NET.

Більш гнучким для.NET-сумісних мов є друга техніка. Насамперед, потрібно мати на увазі, що властивість − це всього лише спрощене представлення тієї самої пари методів доступу й зміни.

Властивості та індексатори (властивості з параметрами) – це методи спеціального виду, з допомогою яких здійснюється контрольований доступ до даних. Контрольований доступ передбачає перевірку можливості доступу до значень або можливості його зміни.

Для визначення властивості класу використовується синтаксис:

[атрибути] [модифікатори доступу] тип_результату ім'я_властивості { get { //тіло функції доступу return результат; }   set { //тіло функції зміни } }

Метод зміни (set) неявно отримує параметр value типу тип_результату, у якому зберігається нове значення для зміни. Це означає, що тип результату властивості не може приймати значення void.

class Rational { ... //властивість доступу та зміни знаменника public int Numerator { get { return numerator; } set { numerator = value; Reduce(this);} } //властивість доступу та зміни знаменника public int Denominator { get { return denominator; } set { if (value > 0) {denominator = value; Reduce(this);} else Console.WriteLine("Рацiональне число::Помилка: хибне значення знаменника"); } } }

При інкапсуляції даних може виникнути необхідність у властивості, доступної тільки для читання. Для цього потрібно просто вилучити блок set. Аналогічно, якщо потрібно створити властивість, доступну тільки для запису, слід вилучити блок get.

У мові C# також підтримуються статичні властивості. Маніпулювати статичними властивостями можна так само, як і статичними методами.

З виходом платформи.NET 3.5 у мові С# з’явився ще один спосіб визначення простих служб інкапсуляції з мінімальним кодом, а саме — синтаксис автоматичних властивостей. Досить часто буває, що деякі властивості не роблять буквально нічого, крім простого присвоювання й повернення значень (як у наведеному вище коді класу Rational для властивості чисельника). У таких випадках було би занадто громіздко багаторазово визначати приватні поля та відповідні їм прості визначення властивостей. Наприклад, при побудові класу, якому потрібно 15 приватних полів даних необхідно реалізувати 15 пов’язаних з ними властивостей, які, по суті, являють собою не більш ніж тонкі оболонки для інкапсуляції. Щоб спростити процес простої інкапсуляції даних полів, можна застосовувати синтаксис автоматичних властивостей. Однак, слід пам’ятати, що на відміну від традиційних властивостей С#, створювати автоматичні властивості, призначені тільки для читання або тільки для запису, не можна. Наприклад у класі Rational можна було б не визначати приватне поле denominator, а визначити наступну автоматичну властивість:

//автоматична властивість доступу та зміни чисельника public int Numerator { get; set; }

Відмінністю індексаторів від властивостей є те, що індексатор здійснює доступ до окремого елемента з упорядкованого набору значень. Під упорядкуванням в цьому випадку розуміється відповідність кожного елемента певному набору індексів (індексів може бути декілька і вони можуть задаватися за допомогою різних типів даних – цілі числа, рядки, символи, об’єкти якогось класу, тощо). Іншими словами − індексатори дозволяють звертатися до полів в стилі масива. Для визначення індексатора класу використовується синтаксис:

[атрибути] [модифікатори доступу] тип_результату this[індекси] { get { //тіло функції одержання значень за індексами return результат; } set { //тіло функції присвоєння значень за індексами } }

Виклик індексатора здійснюється за правилом:

Ім'я_об'єкта[індекси];

Для індексаторів, як і для властивостей, обов’язковим є визначення, хоча б однієї частини – get або set.

class Rational { ... //індексатор для доступу до полів чисельника та знаменника за їх назвою public int this[string component] { get { if (string.Equals(component, "numerator")) return numerator; if (string.Equals(component, "denominator")) return denominator; Console.WriteLine("Рацiональне число::Помилка: хибне значення iндекса"); return 0; } set { if (string.Equals(component, "numerator")) Numerator=value; else if (string.Equals(component, "denominator")) Denominator=value; else Console.WriteLine("Рацiональне число::Помилка: хибне значення iндекса"); } } }

Конструктори

Згідно із загальними правилами мови програмування об’єкт-змінна повинен бути:

• створений − для нього повинна бути виділена пам’ять;

• ініціалізований − полям об’єкта повинні бути присвоєні значення;

• знищений – пам’ять, виділена під об’єкт, повинна бути звільнена.

Операція створення та ініціалізація полів об’єкта одержала назву конструювання об’єкта, а операція знищення об’єкта − деструкції об’єкта. Відповідні методи, якщо вони визначені в класі, одержали назву конструкторів і деструкторів.

На відміну від інших методів при описі конструктора не вказується тип значення, що повертається, а його ім’я співпадає з іменем класу. У мові C# загальний синтаксис опису конструктора наступний:

[модифікатори] ім'я_класу (список_параметрів) { тіло конструктора }

Мова С# підтримує перевантаження конструкторів. Це означає, що у класі можуть бути визначені декілька конструкторів (спеціальні конструктори), що відрізняються списком формальних параметрів. Якщо в класі явно не визначено жодного конструктора (і тільки в цьому випадку!), то він надається за замовчуванням (конструктор за замовчуванням), але при необхідності може бути перевизначений. За визначенням такий конструктор ніколи не приймає аргументів. Після розміщення нового об’єкта в пам’яті конструктор за замовчуванням гарантує ініціалізацію всіх полів у відповідні до значень за замовчуванням для типів даних С#: для посилальних типів null, для типів-значень − 0.

class Rational { ... //перевизначений конструктор за замовчуванням public Rational() { Count++; count++; ID = Count; } }

При визначенні конструкторів у якості модифікатору може використовуватися ключове слово static. У цьому випадку маємо статичний конструктор. Статичний конструктор служить для ініціалізації статичних полів класу (тобто статичних даних класу). Статичний конструктор не може мати параметрів. Він викликається один раз при створенні першого об’єкта класу в програмі.

class Rational { ... //статичний конструктор для ініціалізації статичних полів static Rational() { Count = count = 0; } }

Якщо в класі існують кілька конструкторів, то можна один конструктор викликати з іншого. Це робиться за допомогою ключового слова this та вказанням всіх параметрів конструктора, що викликається. Наприклад:

class Rational { ... //спеціальний конструтор з параметрами public Rational(int n, int d):this() { Numerator = n; Denominator = d; } }

Cтворення та ініціалізація об’єктів. Наявність конструкторів у класі дозволяє створювати та ініціалізувати об’єкти класу. Об’єкт створюється за допомогою оператора new:

Ім’я_класу ім’я_об’єкта = new ім’я_класу(список_параметрів);

Оскільки клас відноситься до посилальних типів даних, то у змінній ім’я_об’єкта буде міститися посилання на об’єкт класу ім’я_класу. Якщо в програмі проводиться присвоювання

Rational r1 = new Rational(); Rational r2 = r1;

то змінні r1 і r2 будуть посилатися на один і той самий об’єкт.

Щоб полегшити процес створення об’єктів та ініціалізації, у С# пропонується синтаксис ініціалізатора об’єкта. За допомогою цього механізму можна створити нову об’єктну змінну та присвоїти значення властивостям і/або загальнодоступним полям у декількох рядках коду. Синтаксично ініціалізатор об’єкта виглядає як список присвоєння значень полям чи властивостям, розділених комами та поміщений у фігурні дужки.

//тут ініціалізатор неявно викликає конструктор за замовчуванням (зокрема перевантажений) Rational ob = new Rational {numerator=1, denominator=3};

У наведеному прикладі спочатку неявно викликається конструктор за замовчуванням, після якого здійснюється установка значень зазначених полів чи властивостей. Таким чином, синтаксис ініціалізації об’єктів − це просто скорочена нотація синтаксису створення змінної класу за допомогою конструктора, із подальшою установкою значень полів даних. При бажані конструктор за замовчуванням (як і будь-який спеціальний конструктор) може бути викликаний і явно:

//тут ініціалізатор вже явно викликає конструктор за замовчуванням (зокрема перевантажений) Rational ob = new Rational() {numerator=2, denominator=3};

Деструктор

При використанні оператора new для створення об’єкта пам’ять для його зберігання виділяється динамічно. Якщо об’єкт більше не потрібний, пам’ять яку він займає, повинна бути звільнена. Мова С# володіє механізмом звільнення ресурсів пам’яті, що називається «збирач сміття». Якщо до об’єкта не відбуваються звернення (у програмі не залишилося посилань на об’єкт), цей механізм звільняє пам’ять автоматично. Збирач сміття викликається періодично в процесі роботи програми. Коли об’єкт використовує зовнішні ресурси (наприклад, файли, з’єднання з іншим обладнанням, підключення до баз даних і т.д.), для коректного звільнення цих ресурсів може використовуватися спеціальний метод, який називається деструктором. Загальний синтаксис опису наступний:

~ім’я_класу() { Тіло конструктора }

Він викликається збирачем сміття при знищенні об’єкта. Деструктор у класі може бути тільки один. Його ім’я співпадає з іменем класу та починається із символу ‘~’. У деструктора немає значення, що повертається, і немає параметрів. Він не має модифікаторів.

У нашому класі Rational перед знищенням об’єкта потрібно зменшити значення відповідного лічильника об’єктів.

class Rational { ... //деструктор ~Rational() { count--; } }

Перевантаження операцій

При проектуванні класів, що характеризують поведінку математичних об’єктів, зручно використовувати традиційні математичні знаки операцій для виконання відповідних дій. Наприклад, при додаванні двох раціональних чисел природніше було б використовувати операцію «+», а не викликати, припустимо, функцію Sum() (тим більше, що інший програміст може назвати цю функцію іншим іменем). Для таких ситуацій у мові С# є зручний засіб, що називається перевантаженням операцій.

У мові С# у програміста є можливість задавати для власних типів даних свої методи обробки, закріплені за позначенням тієї або іншої операції.

При перевантаженні операцій необов’язково дотримуватися стандартних процедур виконання операції. Наприклад, операція «*», що позначає звичайне множення двох об’єктів (чисел, матриць, векторів і т.д.), може бути перевантажена методом, який зовсім не здійснює множення. Але краще не змінювати змісту операцій, що перевантажуються.

Більшості операцій мови С# відповідають спеціальні операторні функції, що мають наступний прототип:

[модифікатори] тип_значення operator знак_операції (список_форм_параметрів) { тіло операторної функції }

У мові C# не всі операції можна перевантажувати. До заборонених для перевантаження операцій відносяться «.», «()», «[]», «||», «&&», «?:», «new», «is», «as», «sizeof», «typeof» і деякі інші. Існує кілька обмежень, які потрібно враховувати при перевантаженні операцій. По-перше, не можна міняти пріоритет операцій. По-друге, не можна змінювати число операндів операції. Наприклад, операція «!» має тільки один операнд, тому і її перевантажена реалізація повинна бути унарною. В іншому, правила перевантаження операцій збігаються із правилами перевантаження функцій. Перевантажені операції повинні відрізнятися списками параметрів. Наприклад, операція «*» для матриць може бути перевантажена як метод множення двох матриць або метод множення матриці на число.

У С# операторні функції повинні бути відкритими (public) та статичними, тобто вони є методами класу, але не методами об’єкта. Тому для бінарного оператора список параметрів повинен складатися із двох елементів, для унарного – з одного, при цьому хоча б один параметр повинен мати тип того класу, у якому він визначений. Також параметри не можуть передаватися з модифікаторами ref або out.

Перевантаження унарних та бінарних операцій. Приведемо приклад перевантаження операцій «+» та «-» у класі Rational:

class Rational { ... //перевантаження операції + public static Rational operator + (Rational arg1, Rational arg2) { Rational newR = new Rational(); newR.numerator = arg1.numerator * arg2.denominator + arg2.numerator * arg1.denominator; newR.denominator = arg1.denominator * arg2.denominator; Rational.Reduce(newR); return newR; } //перевантаження операції - public static Rational operator -(Rational arg1, Rational arg2) { Rational newR = new Rational(); newR.numerator = arg1.numerator * arg2.denominator - arg2.numerator * arg1.denominator; newR.denominator = arg1.denominator * arg2.denominator; Rational.Reduce(newR); return newR; } public static Rational operator -(Rational arg) { arg.numerator = -arg.numerator; return arg; } }

При перевантаженні окремих видів операцій існують особливості, про які потрібно сказати окремо.

Так, наприклад, слід пам’ятати, що операції скороченого присвоювання (+=, -=, і т.д.) не можуть перевантажуватися, але вони автоматично емалюються при перевантаженні відповідних бінарних операцій.

При перевантаженні операцій порівняння обов’язково в якості типу значення, що повертається, вказується тип bool. Крім того, такі операції перевантажуються попарно – операція «==» разом із «!=», «<» разом із «>», «<=» разом із «>=».

class Rational { ... //перевантаження операцій == та!= public static bool operator ==(Rational arg1, Rational arg2) { return arg1.numerator * arg2.denominator == arg2.numerator * arg1.denominator; } public static bool operator!=(Rational arg1, Rational arg2) { return arg1.numerator * arg2.denominator!= arg2.numerator * arg1.denominator; } }

Операції приведення до інших типів. Клас може містити методи для здійснення операції перетворення в інші типи даних або навпаки. Вони реалізуються за допомогою перевантаження спеціальних операторів. Оператор приведення до типу даних має вигляд:

[модифікатори] operator ім’я_типу(операнд);

Цей оператор не має типу, що повертається, оскільки він співпадає з іменем типу. Дана операція є унарною. Операція перетворення типу може бути явною (потрібне при спробі зберегти більше значення в меншому контейнері) або неявною (відбувається автоматично при спробі помістити менший тип у більшому). Це вказується за допомогою модифікаторів explicit або implicit. Клас може містити тільки одне із цих двох перетворень. Наприклад, визначимо в класі Rational неявне перетворення цілого числа в раціональне та явне перетворення раціонального числа у ціле.

class Rational { ... //неявне перетворення цілого числа у раціональне число public static implicit operator Rational(int n) { Rational newR = new Rational(){numerator=n}; return newR; }   //явне перетворення раціонального числа у ціле число public static explicit operator int(Rational arg) { return arg.numerator/arg.denominator; } }

Виклик даної операції у відповідних випадках здійснюється так:

int i1 = -9; Rational r1 = i1;   Rational r2 = new Rational(5, 1); int i2 = (int)r2;

Перетворення до логічного типу даних можна здійснити також за допомогою перевантаження операторів true та false, які перевантажуються обов’язково разом. Наприклад, у класі Rational ці операції можуть визначати, чи є дріб правильним.

class Rational { ... //операції перевірки правильності дробу public static bool operator true(Rational arg) { return Math.Abs(arg.numerator) < arg.denominator; } public static bool operator false(Rational arg) { return Math.Abs(arg.numerator) > arg.denominator; } }

Маючи визначеними у класі дані операції, можна об’єкти цього класу використовувати у логічних виразах:

Rational r = new Rational(-7, 3); if (r) Console.WriteLine("дріб − правильний"); else Console.WriteLine("дріб − не правильний");

Частинні випадки класу

Структура – це частинний випадок класу. У мовах C, Pascal вони були тільки сукупністю даних (полів класу), але не містили ані методів, ані подій. У мові C++ можливості структур були суттєво розширені − вони стали справжніми класами, хоча з деякими обмеженнями. У мові C# – нащадку С++ збережений саме такий підхід до структур.

Роблячи вибір між структурою та класом доцільно користуватися наступними правилами:

− Якщо необхідно віднести клас до типу значення − реалізовуйте його у вигляді структури.

− Якщо в класі кількість полів відносно невелика, а кількість можливих об’єктів відносно велика, то робіть його структурою. Це дозволить уникнути створюватися відносно великої кількості посилань, що дозволить підвищити ефективність роботи.

− В інших випадках реалізовуйте звичайні класи.

Розглянемо більш докладно питання опису структур, їх синтаксису, семантики та тих особливостей, що відрізняють їх від класів.

Синтаксис оголошення структури аналогічний синтаксису оголошення класу:

[атрибути] [модифікатори] struct ім'я_структури [:список_інтерфейсів] { тіло_структури }

Слід відмітити наступні зміни синтаксису опису структури по відношенню до класу:

− Ключове слово class змінене на слово struct.

− Для структур не може бути заданий базовий клас (клас або структура). Але так само як і клас структура може реалізовувати інтерфейси.

− Для структур не застосовні модифікатори abstract і sealed. Причиною є відсутність механізму успадкування.

Усе, що може бути вкладене в тіло класу, може бути вкладене й у тіло структури: поля, методи, конструктори, і все інше, включаючи класи та інтерфейси.

Аналогічно до класу структура може мати статичні та не статичні поля й методи, підтримує перевантаження конструкторів, зокрема може містити статичні й закриті конструктори.

Відмітимо обмеження, що накладаються на структури:

− У мові C# для структур не підтримується перевантаження конструктора за замовчуванням.

− У структурах не можна ініціалізувати поля екземпляра у основній частині структури. Можна ініціалізувати члени структури за допомогою спеціального конструктора або шляхом доступу до членів окремо після оголошення структури. Будь-які закриті поля можна ініціалізувати тільки у конструкторі.

− У спеціальних конструкторах не допускається виклик методів структури.

− На відміну від класів структури можна створювати без використання оператора new. У такому випадку конструктор не викликається, що робить виділення пам’яті більш ефективним. Однак поля залишаються без значень і об’єкт не можна використовувати допоки не будуть ініціалізовані всі поля.

− У випадку, коли структура містить член посилального типу, конструктор за замовчуванням члена повинен викликатися явно, а якщо ні, то член залишиться без значень і структура не зможе бути використаною (буде помилка компіляції).

Перелік – також частинний випадок класу, це клас, заданий без власних методів. Перелік задає скінченну множину можливих значень, які можуть приймати об’єкти класу переліку. Оскільки у переліків немає власних методів, то синтаксис оголошення цього класу спрощується до наступного загального правила:

[атрибути] [модифікатори] enum ім'я_переліку [:базовий клас] { список_можливих_значень }

Ключове слово enum говорить, що визначається окремий випадок класу – перелік. Список можливих значень, задає ті значення, які можуть приймати об’єкти цього класу. Можливі значення повинні бути ідентифікаторами, але при цьому допускається використовувати в їх описі і символи кирилиці.

Можна вказати також базовий клас для переліку, в якості якого допускається будь-який із вбудованих цілочислових. Справа в тому, що значення, задані переліком, проектуються на підмножину базового класу. Реально значення об’єктів переліку в пам’яті задаються значеннями базового класу. За замовчуванням, базовим класом для будь-якого переліку є клас int, а підмножина проекції починається з нуля. Але при бажанні можна змінити інтервал представлення та сам базовий клас. На базовий клас накладається обмеження: він повинен бути одним із вбудованих цілочислових типів, крім char.

Прклади оголошень переліків:

public enum MyEquationType { linear, quadratic, biquadratic }; public enum Angle2D { розгорнутий, повний, гострий, прямий, тупий }; public enum My2DShape { Point, Line, Angle, ConvexPolygon, Circle }; public enum My3DShape: long { Point, Line, Angle, Polyhedron, Sphere }; public enum MyShapeType: byte { _2DShape = 1, _3DShape };

Особливості, на які слід звернути увагу при оголошенні переліків:

− Як і інші класи переліки можуть бути оголошені безпосередньо в просторі імен проекту або можуть бути членом класу.

− Константи різних переліків можуть збігатися, як у переліках My2DShape і My3DShape. Ім’я константи завжди уточнюється іменем переліку.

− Константи можуть задаватися кирилицею, як у переліку Angle2DType.

− Дозволяється задавати базовий клас переліку. Для переліку My3DShape базовим класом задано клас long.

− Дозволяється задавати не тільки базовий клас, але й указувати початковий елемент підмножини, на яку проектується множина значень переліку. Для переліку MyShapeType у якості базового класу обрано клас byte, а підмножина значень починається з 1, так що збереженим значенням константи _2DShape є 1, а _3DShape – 2.

Приклад роботи з об’єктами – екземплярами різних переліків:

//My2DShape s = new My2DShape(My2DShape.Angle);   My2DShape s1 = My2DShape.Point; My3DShape s2; s2 = My3DShape.Point;   //if(s1!= s2) s2 = s1; if (s1.ToString()!= s2.ToString()) Console.WriteLine("Це рiзнi геометричнi фiгури."); else Console.WriteLine("Це спорiдненi геометричнi фiгури.");   My2DShape s3; s3 = (My2DShape)3; if (s3!= My2DShape.Angle) s3 = My2DShape.Angle; int num = (int)s3;

Даний приклад ілюструє наступні особливості роботи з об’єктами переліків:

− Об’єкти переліків не можна створювати в об’єктному стилі з використанням операції new, як у випадку об’єкту-змінної s, оскільки переліки не мають конструкторів.

− Об’єкти переліків можна оголошувати з явною ініціалізацією, як s1, або з неявною ініціалізацією, як s2. При оголошенні без явної ініціалізації об’єкт отримує значення першої константи переліку, так що s2 у момент оголошення отримує значення Point.

− Об’єкту можна привласнити значення, задане константою переліку, уточненої іменем переліку, як для s1.

− Не можна порівнювати об’єкти різних переліків, наприклад s1 і s2, але можна порівнювати рядки, що повертаються методом ToString, наприклад s1.ToSting() і s2.ToString(). Метод ToString, успадкований від класу Object, повертає рядок, що задає константу переліку.

− Існують явні взаємно обернені перетворення констант базового типу та констант переліку, як для s3.

Структура «Раціональне число»

Повернемося до класу Rational, спроектованому на протязі попередніх лекцій. Цей клас цілком розумно представити у вигляді структури. Дійсно. Успадкування для цього класу не потрібне. Семантика присвоювання типу значення більше підходить для раціональних чисел, аніж семантика посилального типу, адже раціональні числа – це ще один підклас арифметичного класу. А згадаймо, що у мові C# усі числові типи є саме структурами. Отже, клас Rational краще реалізувати у вигляді структури. Приклад такої реалізації наводиться у лістингу 1.

Лістинг 1. Реалізація класу «Раціональне число» у вигляді структури

public struct Rational { //чисельник long numerator; //знаменник long denominator;   //рекурсивний метод, що повертає НСД static long GCD(long a, long b) { if (b == 0) return a; else return GCD(b, a % b); }   //метод скорочення дробу void Reduce() { long gcd = Rational.GCD(Math.Abs(numerator), denominator); numerator /= gcd; denominator /= gcd; }   //властивість доступу та зміни чисельника та знаменника public long Numerator { get { return numerator; } set { numerator = value; if (denominator!= 0) Reduce(); } } public long Denominator { get { return denominator; } set { if (value > 0) { denominator = value; Reduce(); } else { denominator = 1; Console.WriteLine("Рацiональне число::Помилка: хибне значення знаменника"); } } }   //спеціальний ініціалізатор полів з параметрами public static Rational Init(int n, int d) { Rational newR = new Rational(); newR.Numerator = n; newR.Denominator = d; return newR; }   //метод виводу раціонального числа на екран public void Display() { if (denominator == 1) { Console.WriteLine(numerator); } else Console.WriteLine("{0}/{1}", numerator, denominator); }   //метод, що обмінює значеннями два раціональні числа public static void Swap(ref Rational arg1, ref Rational arg2) { Rational temp = arg1; arg1 = arg2; arg2 = temp; }   //методи Min та Мах, що повертають найменше та найбільше серед набору раціональних чисел public static Rational Min(params Rational[] arg) { if (arg.Length > 0) { Rational min = arg[0]; for (int i = 1; i < arg.Length; i++) if (arg[i] < min) min = arg[i]; return min; } else throw new ArgumentException("Не задано жодного аргументу!"); } public static Rational Max(params Rational[] arg) { if (arg.Length > 0) { Rational max = arg[0]; for (int i = 1; i < arg.Length; i++) if (arg[i] > max) max = arg[i]; return max; } else throw new ArgumentException("Не задано жодного аргументу!"); }   //індексатор для доступу до полів чисельника та знаменника за їх назвою public long this[string component] { get { if (string.Equals(component, "numerator")) return numerator; if (string.Equals(component, "denominator")) return denominator; Console.WriteLine("Рацiональне число::Помилка: хибне значення iндекса"); return 0; } set { if (string.Equals(component, "numerator")) Numerator = value; else if (string.Equals(component, "denominator")) Denominator = value; else Console.WriteLine("Рацiональне число::Помилка: хибне значення iндекса"); } }   //перевантаження арифметичних операцій public static Rational operator +(Rational arg1, Rational arg2) { Rational newR = new Rational(); newR.numerator = arg1.numerator * arg2.denominator + arg2.numerator * arg1.denominator; newR.denominator = arg1.denominator * arg2.denominator; newR.Reduce(); return newR; }   public static Rational operator -(Rational arg1, Rational arg2) { Rational newR = new Rational(); newR.numerator = arg1.numerator * arg2.denominator - arg2.numerator * arg1.denominator; newR.denominator = arg1.denominator * arg2.denominator; newR.Reduce(); return newR; }   public static Rational operator -(Rational arg) { arg.numerator = -arg.numerator; return arg; }   //перевантаження операцій порівняння public static bool operator >(Rational arg1, Rational arg2) { return arg1.numerator * arg2.denominator > arg2.numerator * arg1.denominator; } public static bool operator <(Rational arg1, Rational arg2) { return arg1.numerator * arg2.denominator < arg2.numerator * arg1.denominator; }   //неявне перетворення цілого числа у раціональне число public static implicit operator Rational(int n) { Rational newR = Rational.Init(n, 1); return newR; }   //явне перетворення раціонального числа у ціле число public static explicit operator long(Rational arg) { return arg.numerator / arg.denominator; }   //операції перевірки правильності дробу public static bool operator true(Rational arg) { return Math.Abs(arg.numerator) < arg.denominator; } public static bool operator false(Rational arg) { return Math.Abs(arg.numerator) > arg.denominator; } }

 


Тема 3

 

Основні засоби розробки класів у C#

Вступ

Однією із переваг ООП є те, що більшість класів для реалізації об’єктів не доводиться розробляти «з нуля». Зазвичай класи будують на основі вже існуючих, реалізовуючи тим самим третій важливий принцип ООП − ієрархічність.

Ієрархія − проранжована або впорядкована система абстракцій. Принцип ієрархічності передбачає використання ієрархій при розробці великих програмних систем.

В ООП при розробці класів використовуються два види ієрархії, які визначаються двома основними видами відношень між абстракціями: «загальний/окремий» та «ціле/частина».

Успадкування

Відношення «загальний/окремий» − вказує на те, що деяка абстракція є частинним випадком іншої більш загальної абстракції, наприклад, «точка − найпростіший випадок геометричної фігури» також «опуклий многокутник − окремий випадок геометричної фігури».

В свою чергу наявність між абстракціями зв’язку «загальний/окремий» визначає відношення успадкування між класами, яке передбачає можливість при описі класу абстракції зберігати та використовувати структурну або функціональну частину іншої або декількох інших абстракцій (відповідно просте та множинне успадкування).

Успадкуванням або узагальненням називається відношення між класами, при якому один клас будується на базі іншого шляхом додавання нових полів та визначення нових методів. При цьому вихідний клас, на базі якого виконується побудова, називається базовим (батьківським, предком, або суперкласом), а той, що будується − похідним (дочірнім, нащадком або підкласом).

Відношення між різними класами проекту прийнято ілюструвати діаграмою відношень класів, або просто діаграмою класів. На діаграмі клас зображується у вигляді прямокутника, який додатково може бути розділений горизонтальними лініями на одну, дві чи три секції. У окремих секціях може вказуватися ім’я класу, поля та методи класу. Відкритим членам класу передує знак «+», а закритим − «-». Обов’язковим елементом позначення класу є його ім’я. На початкових етапах розробки діаграми окремі класи можуть позначатися простим прямокутником із зазначенням тільки імені відповідного класу.

Якщо на діаграмі класів зображено тільки відношення успадкування, то таку діаграму називають ієрархією класів. На діаграмі класів успадкування зображують лінією із трикутною не зафарбованою стрілкою на кінці, яка направлена до базового класу (мал. 1). На діаграмах класів при необхідності допускається довільне розташування базових класів та похідних.

Мал. 1. Приклад ієрархії класів із двома похідними класами

 

У лістингу 1 наведено програмну реалізацію класу Shape у відповідності до наведеної діаграми класів.


Дата добавления: 2015-10-26; просмотров: 308 | Нарушение авторских прав


Читайте в этой же книге: Загальна характеристика платформи .Net Framework | Бібліотека базових типів BCL | Лістинг 2. Текст програми на мові C#, яка виводить на консоль привітання тільки тим користувачам, імена яких передаються як параметри командного рядка | Абстрактні класи | Недолік обмеження операцій | Деякі класи неузагальнених колекцій | Організація роботи із файлами даних стандарту XML | Делегати в якості параметрів | Події: створення та обробка |
<== предыдущая страница | следующая страница ==>
Синтаксис опису класу| Агрегація та композиція

mybiblioteka.su - 2015-2024 год. (0.027 сек.)