Система типів

Давайте розглянемо, як влаштована система типів в мові C#, але спочатку для порівняння приведемо класифікацію типів в стандарті мови C++.

Стандарт мови C++ включає наступний набір фундаментальних типів.

  1. Логічний тип — bool.
  2. Символьний тип — char.
  3. Цілі типи. Цілі типи можуть бути одного з трьох розмірів: short, int, long, що супроводжуються описувачем signed або unsigned, який указує, як інтерпретується значення — із знаком або без оного.
  4. Типи з плаваючою крапкою. Ці типи також можуть бути одного з трьох розмірів — float, double, long double.
  5. Тип void, використовуваний для вказівки на відсутність інформації.
  6. Покажчики (наприклад, int* — покажчик, що типізується, на змінну типу int).
  7. Посилання (наприклад, double& — посилання, що типізується, на змінну типу double).
  8. Масиви (наприклад, char[] — масив елементів типу char).
  9. Типи enum, що перераховують, для представлення значень з конкретної множини.
  10. Структури — struct.
  11. Класи.

Перші три види типів називаються інтегральними, або рахунковими. Значення їх перечислимы і впорядковані. Цілі типи і типи з плаваючою крапкою відносяться до арифметичного типу. Типи підрозділяються також на вбудованих і типи, визначені користувачем.

Ця схема типів збережена і в мові C#. Проте тут на верхньому рівні використовується і інша класифікація, що має для C# принциповий характер. Згідно цієї класифікації, всі типи можна розділити на чотири категорії:

  • Типи-значення — value, або значущі типи.
  • Посилальні — reference.
  • Покажчики — pointer.
  • Тип void.

Ця класифікація заснована на тому, де і як зберігаються значення типів. Для посилального типу значення задає посилання на область пам'яті в " купі ", де розташований відповідний об'єкт. Для значущого типу використовується пряма адресація, значення зберігає власне дані, і пам'ять для них відводиться, як правило, в стеку.

У окрему категорію виділені покажчики, що підкреслює їх особливу роль в мові. Покажчики мають обмежену область дії і можуть використовуватися тільки в небезпечних блоках, помічених як unsafe.

Особливий статус має і тип void, вказуючий на відсутність якого-небудь значення.

У мові C# жорстко визначено, які типи відносяться до посилальних, а які до значущих. До значущих типів відносяться: логічний, арифметичний, структури, перерахування. Масиви, рядки і класи відносяться до посилальних типів. На перший погляд, така класифікація може викликати деякий подив: чому це структури, які в C++ близькі до класів, відносяться до значущих типів, а масиви і рядки — до посилальних. Проте нічого дивовижного тут не немає. У C# масиви розглядаються як динамічні, їх розмір може визначатися на етапі обчислень, а не у момент трансляції. Рядки в C# також розглядаються як динамічні змінні, довжина яких може змінюватися. Тому рядки і масиви відносяться до посилальних типів, що вимагають розподілу пам'яті в " купі ".

Із структурами справа йде інакше. Структури C# представляють окремий випадок класу. Визначивши свій клас як структуру, програміст дістає можливість віднести клас до значущих типів, що іноді буває украй корисно. У C# тільки завдяки структурам з'являється можливість управляти віднесенням класу до значущих або посилальних типів. Правда, це неповноцінний засіб, оскільки на структури накладаються додаткові обмеження в порівнянні із звичайними класами.

Згідно прийнятої класифікації всі типи діляться на вбудованих і визначених користувачем. Всі вбудовані типи C# однозначно відображаються, а фактично співпадають з системними типами каркаса.NET Framework, розміщеними в просторі імен System. Тому усюди, де можна використовувати ім'я, наприклад int, з тим же успіхом можна використовувати і ім'я System.Int32.

Система вбудованих типів мови C# не тільки містить практично всі вбудовані типи (за винятком long double) стандарту мови C++, але і перекриває його розумним чином. Зокрема, тип String є вбудованим в мову, що цілком природно. В області збігу збережені імена типів, прийняті в C++, що полегшує життя тим, хто звик працювати на C++, але збирається по тих або інших причинах перейти на мову C#.

Мова C# більшою мірою, чим мова C++, є мовою об'єктного програмування. У мові C# згладжена відмінність між типом і класом. Всі типи — вбудовані і призначені для користувача — одночасно є класами, зв'язаними відношенням спадкоємства. Батьківським, базовим класом є клас Object. Решта всіх типів або, точніше, класів є його нащадками, успадковувавши методи цього класу. У класу Object є чотири успадковані методи:

  1. bool Equals (object obj) — перевіряє еквівалентність поточного об'єкту і об'єкту, переданого як аргумент;
  2. System.Type GetType() — повертає системний тип поточного об'єкту;
  3. String ToString() — повертає рядок, пов'язаний з об'єктом. Для арифметичних типів повертається значення, перетворене в рядок;
  4. int GetHashCode() — служить як хэш-функция у відповідних алгоритмах пошуку по ключу при зберіганні даних в хэш-таблицах.

Природно, що всі вбудовані типи потрібним чином перевизначають методи батька і додають власні методи і властивості. Враховуючи, що і типи, що створюються користувачем, також є нащадками класу Object, для них необхідно перевизначити методи батька, якщо передбачається використання цих методів; реалізація батька, що надається за умовчанням, не забезпечує потрібного ефекту.

Розглянемо декілька прикладів. Почнемо з цілком коректного в мові C# прикладу оголошення змінних і привласнення ним значень:

int x=11;int v = new Int32();v = 007;String s1 = "Agent";s1 = s1 + v.ToString() +x.ToString();

В даному прикладі змінна x оголошується як звичайна змінна типу int. В той же час для оголошення змінній v того ж типу int використовується стиль, прийнятий для об'єктів. У оголошенні застосовується конструкція new і виклик конструктора класу. У операторові привласнення, записаному в останньому рядку фрагмента, для обох змінних викликається метод ToString, як це робиться при роботі з об'єктами. Цей метод, успадкований від батьківського класу Object і перевизначений в класі int, повертає рядок із записом цілого. Відзначимо ще, що клас int не тільки успадковує методи батька - класу Object, — але і додатково визначає метод CompareTo, що виконує порівняння цілих, і метод GetTypeCode, що повертає системний код типу. Для класу Int визначені також статичні методи і поля, про які поговоримо трохи пізніше.

Так що ж таке після цього int, запитаєте ви: тип або клас? Адже раніше мовилося, що int відноситься до value-типам, отже, він зберігає в стеку значення своїх змінних, тоді як об'єкти повинні задаватися посиланнями. З іншого боку, створення екземпляра за допомогою конструктора, виклик методів, нарешті, існування батьківського класу Object, — все це указує на те, що int — це справжній клас. Правильна відповідь полягає в тому, що int — це і тип, і клас. Залежно від контексту x може сприйматися як змінна типу int або як об'єкт класу int. Це ж вірно і для решти всіх значущих типів. Варто відзначити, що всі значущі типи фактично реалізовані як структури, що представляють окремий випадок класу.

Така подвійність в мові C# обумовлена тим, що значущі типи ефективніше в реалізації, їм простіше відводити пам'ять, так що саме міркування ефективності реалізації змусили авторів мови зберегти значущі типи. Більш важливо, що часто необхідно оперувати значеннями, а не посиланнями на них, хоч би із-за відмінностей в семантиці привласнення для змінних посилальних і значущих типів.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: