
Структури і ініціалізація полів
Друге серйозне обмеження пов'язане з процесом створення об'єктів і їх ініціалізацією.
Розглянемо оголошення:
int[] x;
int у = x[0];
Це оголошення коректне. А зараз розглянемо схоже оголошення:
int u;
int v = u;
Це оголошення некоректне, виникне помилка періоду компіляції, що повідомляє, що змінна v не ініціалізувала і не може бути використана в обчисленнях. У чому різниця? Об’єкт x – це масив і, отже, відноситься до посилальних типів. При створенні об'єктів посилального типу всі поля об'єктів ініціалізувалися деякими значеннями. У даному конкретному випадку всі елементи цілочисельного типу набудуть значення по замовчуванню, рівні нулю. Об’єкт u арифметичного типу відноситься до розгорненого типу. Всі поля об'єктів розгорненого типу повинні ініціалізувати явно або стандартним конструктором по замовчуванню у момент створення об'єкту. Різна семантика ініціалізації приводить до наступних обмежень на структури.
Якщо в класі поля можна оголошувати з ініціалізацією, то поля структури не допускають ініціалізацію при їх оголошенні. Оголошення з ініціалізацією полів означало б їх неявну ініціалізацію, а тому заборонено.
Стандартний конструктор по замовчуванню у структурах є, він в стеку створює об'єкт, ініціалізувавши поля створеного об'єкту значеннями за умовчанням. Цей конструктор не можна замінити власним конструктором без аргументів.
Клас Rational або структура Rational
Повернемося до класу Rational, спроектованого в теоретичних відомостях попередньої лабораторної роботи. Очевидно, що його цілком розумно представити у вигляді структури. Наслідування для нього необов'язкове. Семантика привласнення розгорненого типу більше підходить для раціональних чисел, чим посилальна семантика, адже раціональні числа – це ще один підклас арифметичного класу. Загалом, клас Rational – прямий кандидат в структури. Задамося питанням, наскільки просто оголошення класу перетворити на оголошення структури? Чи досить замінити слово class словом struct? В даному випадку заміною одного слова не обійтися. Потрібно справитися і з іншими вищепереліченими обмеженнями на структури. Зробити це не важко. Приведемо оголошення структури SRational, залишаючи можливість знайти всі відмінності між описами класу Rational і описом структури. Відмітимо, що, слідуючи правилам хорошого стилю, що вимагає давати полям змістовні імена, змінені імена полів.
/// <summary>
/// Структура SRational.
/// визначає новий тип даних - раціональні числа і основні
/// операції над ними - складання, множення, віднімання і ділення.
/// Раціональне число задається парою цілих чисел
/// (numerator,denominator) і зображається
/// зазвичай у вигляді дробу numerator/denominator.
/// Число numerator називається чисельником
/// denominator - знаменником. Для раціонального числа існує
/// безліч його уявлень, наприклад, 1/2, 2/4, 3/6, 6/12.
/// Серед всіх уявлень
/// можна виділити те, в якому чисельник і знаменник взаємно
/// нескорочувані. Такий представник зберігатиметься в полях класу.
/// </summary>
public struct SRational
{
const string NONE_EXIST =
"Не існує раціонального числа " +
"зі знаменником, рівним нулю!";
//Поля класу. Чисельник і знаменник раціонального числа.
int numerator, denominator;
/// <summary>
/// Конструктор класу. Створює раціональне число
/// еквівалентне numerator/denominator
/// але з взаємно нескоротним чисельником і знаменником.
/// Якщо denominator = 0, то викидається виключення
/// що повідомляє про неможливість створити
/// раціональне число із знаменником 0
/// </summary>
/// <param name="numerator">числитель</param>
/// <param name="denominator">знаменатель</param>
public SRational(int numerator, int denominator)
{
if (denominator == 0)
throw new SRationalException(NONE_EXIST);
if (numerator == 0)
{
this.numerator = 0;
this.denominator = 1;
return;
}
//приведення знаку
if (denominator < 0)
{
denominator = -denominator;
numerator = -numerator;
}
//приведення до нескоротного дробу
int m = numerator, n = denominator;
{
int p = 0;
m = Math.Abs(m); n = Math.Abs(n);
do
{
p = m % n; m = n; n = p;
} while (n != 0);
}//Nod
this.numerator = numerator / m;
this.denominator = denominator / m;
}
/// <summary>
/// Представлення раціонального числа
/// у вигляді рядка
/// </summary>
/// <returns>строка у форматі numerator/denominator
/// </returns>
public override string ToString()
{
return numerator + "/" + denominator;
}
public SRational Plus(SRational а)
{
int u, v;
u = numerator * а.denominator + denominator * а.numerator;
v = denominator * а.denominator;
return (new SRational(u, v));
}//Plus
public static SRational operator +(SRational r1, SRational r2)
{
return (r1.Plus(r2));
}
public SRational Minus(SRational а)
{
int u, v;
u = numerator * а.denominator - denominator * а.numerator;
v = denominator * а.denominator;
return (new SRational(u, v));
}//Minus
public static SRational operator -(SRational r1, SRational r2)
{
return (r1.Minus(r2));
}
public SRational Mult(SRational а)
{
int u, v;
u = numerator * а.numerator;
v = denominator * а.denominator;
return (new SRational(u, v));
}//Mult
public static SRational operator *(SRational r1, SRational r2)
{
return (r1.Mult(r2));
}
public SRational Divide(SRational а)
{
int u, v;
u = numerator * а.denominator;
v = denominator * а.numerator;
return (new SRational(u, v));
}//Divide
public static SRational operator /(SRational r1, SRational r2)
{
return (r1.Divide(r2));
}
//Константи класу 0 і 1 - ZERO і ONE
public static readonly SRational ZERO, ONE;
SRational(int num, int den, string t)
{
numerator = num; denominator = den;
}//Закритий конструктор
static SRational()
{
ZERO = new SRational(0, 1, "");
ONE = new SRational(1, 1, "");
}//Статичний конструктор
//Операції відношення
public static bool operator ==(SRational r1, SRational r2)
{
return ((r1.numerator == r2.numerator) &&
(r1.denominator == r2.denominator));
}
public static bool operator !=(SRational r1, SRational r2)
{
return ((r1.numerator != r2.numerator) ||
(r1.denominator != r2.denominator));
}
public static bool operator <(SRational r1, SRational r2)
{
return (r1.numerator * r2.denominator <
r2.numerator * r1.denominator);
}
public static bool operator >(SRational r1, SRational r2)
{
return (r1.numerator * r2.denominator >
r2.numerator * r1.denominator);
}
public static bool operator <(SRational r1, double r2)
{
return ((double)r1.numerator / r1.denominator < r2);
}
public static bool operator >(SRational r1, double r2)
{
return ((double)r1.numerator / r1.denominator > r2);
}
public override bool Equals(object obj)
{
return this == (SRational)obj;
}
public override int GetHashCode()
{
return numerator + denominator;
}
}
/// <summary>
/// Клас, що задає виключення при роботі
/// з раціональними числами.
/// </summary>
public class SRationalException:Exception
{
public SRationalException() { }
public SRationalException(string message) : base(message) { }
public SRationalException(string message, Exception e) : base(message, e) { }
}
Всі раніше побудовані приклади роботи з класом Rational застосовні і при роботі із структурою SRational і даватимуть еквівалентні результати за одним виключенням. При виклику конструктора без аргументів для класу Rational створюється коректне раціональне число 0/1. Для структури конструктор за умовчанням створює некоректне число 0/0. Цю ситуацію ніяк не можна виправити, оскільки цей конструктор не можна перевизначити. Єдиний вихід – не користуватися цим конструктором при роботі із структурами.
Побудуємо приклад роботи з об'єктами структури SRational:
public void TwoSemantics()
{
SRational sr1 = new SRational(1, 3);
SRational sr2 = new SRational(3, 5);
SRational sr3, sr4;
sr3 = sr1 + sr2; sr4 = sr3;
if (sr3 > 1) sr3 = sr1 + sr3 + SRational.ONE;
else sr3 = sr2 + sr3 - SRational.ONE;
Console.WriteLine("Структура SRational");
Console.WriteLine("sr1 = " + sr1.ToString());
Console.WriteLine("sr2 = " + sr2.ToString());
Console.WriteLine("sr3 = " + sr3.ToString());
Console.WriteLine("sr4 = " + sr4.ToString());
}
В даному прикладі використовуються константи, працює статичний конструктор, закритий конструктор, перевантажені операції порівняння, арифметичні вирази над раціональними числами. В результаті обчислень r3 набуде значення 8/15, r4 - 14/15. Відмітьте, аналогічний приклад для класу Rational дасть ті ж результати. Для класу Rational і структури Rational не можна виявити різницю між посилальним і розгорненим привласненням. Це пов'язано з особливістю класу Rational – він за побудовою відноситься до незмінних (immutable) класів, аналогічно класу string. Операції цього класу не змінюють поля об'єкту, а кожного разу створюють новий об'єкт. В цьому випадку можна вважати, що об'єкти класу володіють привласненням розгорненого типу.