Лекція 4. Типи даних та змінні у .NET


Table of Contents

4.1. Поняття типу даних. Система типів .NET.

Тип (загальне поняття) — це стійка сукупність елементів зі спільними характеристиками, яку можна виділити серед множини інших об’єктів.

Тип даних як у математиці, так і у програмуванні має обмеження на значення, що дозволяє зменшити невизначеність у записах та уникнути частини помилок. Тип даних вказує яким чином об’єкти цього типу можуть взаємодіяти з іншими елементами мови програмування або між собою (Джерело: Cardelli & Wegner, 1985).

Системи типів у мовах програмування мають ряд властивостей та діляться за різними класифікаціями: статичні і динамічні, строго типізовані і не строго типізовані тощо. Також на сьогодні існують елементи мов програмування, що перемішують дані поняття або підміняють їх (наприклад var у C#).

Система типів .NET називається Common Type System (далі CTS). CTS визначає спосіб оголошення використання та керування типами у середовищі CLR, а також є важливою частиною підтримки міжмовної інтеграції у середовищі виконання (MSDN, n.d.).

Усі типи на платформі .NET Framework поділяються на типи-значення (Value Type) та типи-посилання (Rerence Type). Типи-значення представлені фактичними значеннями об’єкта, тобто під час присвоєння змінної відбувається створення нової копії об’єкта. Для типів посилань створення нової копії не відбувається. Типи посилання вказують на «комірку пам’яті» де розміщено значення.

У спрощеному вигляді система типів .NET має такий вигляд:

  • типи-значення:

    • прості типи;
    • структури;
    • перелічувані типи;
  • типи-посилання:

    • класи;
    • інтерфейси;
    • масиви;
Рис. 4.1. Система типів .NET

Варто також згадати, що типи даних, які підтримуються компілятором напряму і знаходяться у .NET Framework Class Library (FCL) називаються примітивними. Оскільки типи повинні бути універсальними між різними мовами, то розрізняються FCL типи та типи конкретної мови програмування (Також варто відмітити, що існує загальномовна специфікація типів (Common Language Specification), проте відповідність їй не обов’язкова). По суті типи конкретної мови програмування є псевдонімами FCL типів. Наприлад, int у C# — це Int32 у FCL, проте записати можна обидва варіанти:

int a = 0;
Int32 b = 0;

Розглянемо примітивні типи даних C# та їх відмінності (табл. 4.1).

Таблиця 4.1. Примітивні типи C# та їх характеристики (Биллиг, 2005)

Цілочисельні типи
Назва FLC-тип Значення Розмір, біт
sbyte System.SByte $$[-128;127]$$ 8
byteSystem.Byte$$[0;255]$$8
short System.Short$$[-32768;32767]$$ 16
ushort System.UShort$$[0;65535]$$ 16
int System.Int32$$≈(-2*10^9;2*10^9)$$ 32
uint System.UInt32$$≈(0;4*10^9)$$ 32
long System.Int64$$≈(-9*10^{18};9*10^{18})$$ 64
ulong System.UInt64$$≈(0;18*10^{18})$$64
Числові типи з дробовою частиною
Назва FLC-тип Значення Точність, знаків після коми
floatSystem.Single$$≈(+1.5*10^{-45}; +3.4*10^{38})$$7
doubleSystem.Double$$≈(+5.0*10^{-324}; +1.7*10^{308})$$15-16
decimalSystem.Decimal$$≈(+1.0*10^{-28}; +7.9*10^{28})$$28-29
Логічний тип
Назва FLC-тип Значення Розмір, біт
boolSystem.Booleantrue/false8
Символи та рядки
Назва FLC-тип Значення Розмір, біт
charSystem.CharU+0000 - U+ffff16
stringSystem.StringРядок символів Unicode
Об'єктний тип
Назва FLC-тип Притмітки
objectSystem.ObjectБазовий тип для усіх інших

Розглянемо коротко структури, класи та перелічувані типи даних.

Структури. Структура є першим визначеним користувачем типом, який ми розглянемо. Схожі UDT (User Defined Types) є і у мовах Pascal, C тощо. По суті структури у C# є спрощеною версією класів із мінімальними відмінностями та наслідуєть не напряму від System.Object, а від System.ValueType. Структури в С# створюються за допомогою ключового слова struct (Лістинг 4.1).

Лістинг 4.1. Структура Point

public struct Point
{
    // Поле структури
    public int x, y;
  
    //Параметризований конструктор
    public Point(int xPosition, int yPosition) {
        x = xPosition;
        y = yPosition;
    }
    
    //Метод структури
    public void Display() {
        System.Console.WriteLine("({0}, {1})", x, y);
    }
}

Класи. Клас є посилальним типом даних, що неявно наслідується від System.Object. У класі оголошуються поля, методи, властивості, конструктори, події та інші елементи, що визначають його дані та можливості. Оголошення класу відбувається із використанням ключовго слова class (лістинг 4.2).

Лістинг 4.2. Клас Student

public class Student
{
    private int _age;
    public int GetAge() 
    {
      return _age;
    }
}

Перелічувані типи. Перелічуваний тип (enum) – це програмна конструкція, що дозволяє у межах типу даних визначити альтернативні значення для базового примітивного типу. Перелічувані типи наслідують напряму System.Enum`. Перелічувані типи мають значні обмеження у можливостях порівняно з класами або структурами. Оголошення перелічуваного типу даних відбувається за допомогою ключового слова enum (лістинг 4.3).

Лістинг 4.3. Перелічуваний тип Character

public enum Character
{
    Student = 0,
    Teacher = 1,
    Dekan = 2,
    Rector = 3
}

Розглянемо відмінності між типами-посиланнями та типами значеннями (Albahari & Albahari, 2012). Примітка. Детальніша інформація про значимі та посилальні типи даних подана у питанні 4.6

Таблиця 4.2. Порівняння типів-значень та типів-посилань у C#

1
Тип-значення
Тип-посилання
2
public struct Point
{
    public int X { get;set; }
    public int Y { get;set; }
}
public class Point
{
    public int X { get;set; }
    public int Y { get;set; }
}
3
var p1 = new Point { X = 5, Y = 7 };
var p2 = p1;
p2.X = 10;
Console.WriteLine("p1: X = {0}", p1.X);
Console.WriteLine("p2: X = {0}", p2.X);
  
4
p1: X = 5
p2: X = 10
p1: X = 10
p2: X = 10
5
graph TD; p1-->X1[X=5]; p1-->Y1[Y=7]; p2-->X2[X=10]; p2-->Y2[Y=7];
graph TD; p1-->REF[object_ref]; p2-->REF[object_ref]; REF-->X3[X=10]; REF-->Y3[Y=7];

Як видно із табл. 4.2 тип-значення створює копію елемента у пам’яті і працює з копією, не впливаючи на початкові дані. Тип-посилання вказує на ділянку пам’яті і під час зміни значення поля впливає на усі об’єкти, що «прив’язані» до цієї змінної.


4.2. Оголошення та ініціалізація змінних. Константи

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

Для оголошення змінної використовується запис у вигляді:

тип ім’я_змінної;

Правила іменування змінних у C#:

  • Ім’я змінної може починатися із латинської букви або знаку «_».
  • Ім’я змінної може складатися із латинських букв, цифр або знаку «_».
  • Якщо ім’я змінної співпадає із ключовим словом мови програмування, то потрібне використовувати його із знаком @ (Наприклад: int @class = 1; ).

Лістинг 4.4. Приклад оголошення змінних у C#.

  int a;
  float myNumber;
  double _salary;
  string name;
  bool isPresent;
  decimal discount;

Під час оголошення змінної створюється екземпляр вказаного типу. Таким чином, можна сказати, що можливості конкретної змінної визначаються можливостями типу даних. Наприклад, у змінну цілочисельного типу не можна записати рядок (string). Також важливо відмітити, що у C# не можна змінити тип даних під час існування змінної. До того як змінна буде використана у коді програми вона повинна бути оголошеною. Компілятор перевіряє наявність оголошення змінної у момент її використання.

Будь-яка змінна до використання повинна бути ініціалізованою. Ініціалізація змінних – процес виділення у «пам’яті» під конкретний тип даних із записом значення. Також ініціалізацію змінних можна пояснити як надання конкретного значення змінній (лістинг 4.5).

Ініціалізацію змінної можна провести одразу під час оголошення або у процесі виконання програми (динамічна ініціалізація). Синтаксис ініціалізації змінної під час оголошення:

тип ім’я_зміннної = значення;

Оператор = у записі означає «присвоїти». «Дорівнює» записується як == і є оператором відношення.

Лістинг 4.5. Ініціалізація змінних.

  int a = 10;
  float myNumber = 0.2;
  double _salary = 100.9;
  string name = "Ivan";
  bool isPresent = true;
  decimal discount = 0.02;
  char c = 'R';

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

Лістинг 4.6. Оголошення та ініціалізація змінних.

  int a = 10, b, c = 20;
  char d, f = 'F', s = '3';

Динамічна ініціалізація змінних передбачає присвоєння значення у процесі виконання програми, наприклад, за результатами обчислень (лістинг 4.7).

Лістинг 4.7. Динамічна ініціалізація змінних.

  double a = 5, b = 7; //Катети
  double c; // гіпотенуза
  c = Math.Sqrt(a * a + b * b);
  Console.WriteLine("Гіпотенуза дорівнює - {0:F}", c);

У C# є можливість оголошення та ініціалізації констант. Константа у математиці є величиною, значення якої не змінюється. Константа є протиставленням до змінної. У програмуванні константа – це спосіб запису інформації, зміна якої заборонена. Значення констати повинно бути відомим у момент компіляції програми. По суті константу потрібно ініціалізувати у момент оголошення (лістинг 4.8).

Лістинг 4.8. Оголошення та ініціалізація констант.

  const string spec = "економічна кібернетика';
  const double PI = 3.14;
  const bool Truth = true;
  43 з 111
  const char _iSymbol = 'i';

Область видимості та час існування змінної. Усі змінні до цього часу ми оголошували у межах функції Main(). Насправді змінна може бути оголошеною у межах різних блоків програми. Блоки програми відкремлються «фігурними» дужками ({}). Блок коду визначає область видимості та час існування змінної. За межами цього блоку змінної не існує і доступу до неї немає. Розглянемо приклад коду (лістинг 4.9).

Лістинг 4.9. Область видимості змінної.

static void Main(string[] args)
{
   int x; // Змінна відома у межах метода Main()
   x = 10;
   if (x == 10)
   {
     // Початок нової області видимості
     int у = 20; // Змінна відома лише цьому блоку
     Console.WriteLine("х та у: " + x + " " + у);
     x = у * 2;
   }
   // y = 100; //- помилка "y" - не існує
   Console.WriteLine("х : " + x);
}

Якщо видалити коментування у рядку y = 100, то компілятор проінформує про помилку ~ «Змінна ‘y’ не існує у даному контексті», тобто вона поза областю видимості і доступу немає.

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

Лістинг 4.10. Область видимості змінних.

  int x = 10;
  
  if (x == 10) {
     x = x + 1;
     int a = x;
  }
  
  if (x == 11) {
     x = x + 1;
     int a = x;
  }

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


4.3. Оператори та вирази

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

  • арифметичні;
  • порозрядні;
  • логічні;
  • оператори відношення.

4.3.1. Арифметичні оператори

Таблиця 4.3. Арифметичні оператори

Оператор Дія
+ Додавання
- Віднімання, унарний мінус
* Множення
/ Ділення
% Ділення по модулю
-- Декремент
++ Інкремент

Дія С#-операторів +, -, * і / збігається з дією аналогічних операторів в будь-якій іншій мові програмування (і в алгебрі). Їх можна застосовувати до даних будь-якого вбудованого числового типу.

Перш за все хочу нагадати, що після застосування оператора ділення (/) до цілого числа залишок буде відкинутий. Наприклад, результат цілочисельного ділення 10/3 дорівнюватиме 3.

Залишок від ділення можна отримати за допомогою оператора ділення по модулю (%). Цей оператор працює практично так само, як в інших мовах програмування: повертає залишок від ділення без остачі. Наприклад, 10%3 рівне 1. У С# оператор % можна застосувати як до цілочисельних типів, так і типам з плаваючою крапкою. Наприклад, 10,0%3,0 також рівне 1. (У мовах C/C++ операції ділення по модулю застосовні лише до цілочисельних типів.)

4.3.2. Інкремент і декремент

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

х = х + 1;

аналогічна такій інструкції:

х++.

Так само інструкція

х = х - 1;

аналогічна такій інструкції:

х--.

Оператори інкремента і декремента можуть стояти як перед своїм операндом, так і після нього. Наприклад, інструкцію х = х - 1; можна переписати у вигляді префіксної форми

++х; // Префіксна форма оператора інкремента

або у вигляді постфіксної форми:

х++; // Постфіксна форма оператора інкремента

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

Якщо такий оператор застосований в префіксній формі, то С# спочатку виконає цю операцію, аби операнд набув нового значення, яке потім буде використано останньою частиною вираження. Якщо ж оператор застосований в постфіксній формі, то С# використовує у вираженні його старе значення, а потім виконає операцію, в результаті якої операнд знайде нове значення. Розглянемо наступний фрагмент коду:

х = 10; 
int a = ++х;

Результат: a = 11;

х = 10; 
int a = х++;

Результат: a = 10;

4.3.3. Логічні оператори. Оператори відношення

Результат виконання операторів відношення і логічних операторів має тип bool.

Таблиця 4.4. Оператори відношення і логічні оператори

Оператор Дія
== Рівно
!= Не рівно
> Більше
< Менше
>= Більше або рівно
<= Менше або рівно
& І
| Або
^ Виключаюче або
|| Скорочене або
&& Скорочене і
! Не

У С# на рівність або нерівність можна порівнювати (відповідно, за допомогою операторів == та !=) всі об’єкти. Але такі оператори порівняння, як <, >, <= або >=, можна застосовувати лише до типів, які підтримують відношення впорядкування. Це означає, що всі оператори відношення можна застосовувати до всіх числових типів. Проте значення типу bool можна порівнювати лише на рівність або нерівність, оскільки значення true і false не упорядковуються. Наприклад, в С# порівняння true > false не має сенсу.

Що стосується логічних операторів, то їх операнди повинні мати тип bool, і результат логічної операції завжди матиме тип bool. Логічні оператори &, |, ^ і ! виконують базові логічні операції І, АБО, взаємовиключення і НЕ.

Лістинг 4.11. Оператори відношення та логічні оператори.

static void Main(string[] args)
{
    int a = 10, b = 11;
    bool abool = true, bbool = false;

    if(a > b) Console.WriteLine("a > b");                             //false
    if(a < b) Console.WriteLine("a < b");                             //true

    if(a == b) Console.WriteLine("a == b");                           //false
    if(a != b) Console.WriteLine("a != b");                           //true
    if (abool || bbool) Console.WriteLine("abool || bbool - true");   //true
    if (abool && bbool) Console.WriteLine("abool && bbool - true");   //false
    if (abool ^ bbool) Console.WriteLine("abool ^ bbool - true");     //true
    if (!abool) Console.WriteLine("!abool - true");                   //false
} 

4.3.4. Інші оператори

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

Таблиця 4.5. Інші оператори

Дія Оператор
Доступ до членів .
Індексація **[]**
Приведення типів ()
Тернарний оператор ?:
Створення нового об’єкта new()
Інформація про тип даних is sizeof typeof
Управління виключеннями переповнення checked unchecked

Є також і інші оператори у C#. Ми розглянули лише основні, які будемо використовувати у нашому курсі.

При присворенні виразів аналогічно до алгебри враховується пріоритет операцій:

Таблиця 4.6. Пріоритет операцій

Пріор. Оператор
1 () [] . (постфікс)++ (постфікс)–– new sizeof typeof unchecked
2 ! ~ (ім’я типу) +(унарний) –(унарний) ++(префікс) ––(префікс)
3 * / %
4 +
5 << >>
6 < > <= => is
7 == !=
8 &
9 ^
10 |
11 &&
12 ||
13 ?:
14 = += –= *= /= %= &= |= ^= <<= >>=

Оператор присвоєння має додаткові можливості.

Присвоєння можна проводити наступним чином:

  int a, b, c; 
  int d = c = b = c = 3.

У результаті виконання коду усі змінні будуть ініціалізовані значенням 3.

У C#, так само як і у С++, є складений оператор присвоєння. Для виразу x = x + 10 складений оператор матиме вигляд: x += 10. Складений оператор означає додавання до поточного значення певного числа. Аналогічні операції можна застосовувати і до інших операторів: -=, *=, /=, %=.

4.4. Літерали

У програмах на мовах високого рівня (у тому числі C#) літералами називають послідовність символів, що входять у алфавіт мови програмування, які забезпечують явне представлення значень, які використовуються для позначення початкових значень в оголошенні членів класів, змінних і констант в методах класу. Розрізняються літерали арифметичні (різних типів), логічні, символьні (включаючи Escape-послідовності), рядкові.

4.4.1. Арифметичні літерали

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

  • стандартним зовнішнім виглядом

    Значення цілочисельного типу зазвичай кодується інтуїтивно зрозумілою послідовністю символів $1, 2, 3, … N$.

    Значення чисел з дробовою частиною також передбачає стандартний вигляд (крапка-роздільник між цілою і дробовою частиною, або наукова або експоненціальна нотація - $1.2500E+052$).

    Шістнадцяткове представлення цілочисельного значення кодується шістнадцятковим літералом, що складається з символів $0, …, 9$, а також $‘a’ …, ‘f’$, або $‘A’ …, ‘F’$ з префіксом '0x'.

  • власне значенням

    Так, накприклад, 32768 ніяк не може бути значенням типу short.

  • додатковим суфіксом

    Суфікси l, L відповідають типові long; ul, UL - unsigned long; f, F - float; d, D - double, M, m - decimal. Значення типу double кодуються також без префікса з вказанням розділювача дробової частини, наприклад, $25.0$.

4.4.2. Логічні літерали

До логічних літералів відносяться наступні послідовності символів: true і false.

4.4.3. Символьні літерали

Символьні літерали - це взяті в одинарні лапки символи, що вводяться з клавіатури, наприклад, 'X', 'p', 'Q', '7', а також цілочисельні значення в діапазоні від $0$ до $65535$, перед якими розташовується конструкція вигляду (char) - операція явного приведення до типу char.

Наступні взяті в одинарні лапки послідовності символів є Escape-послідовностями. Ця категорія літералів використовується для створення додаткових ефектів (дзвінок), простого форматування інформації, що виводиться, і кодування символів при виводі і порівнянні (у виразах порівняння).

Таблиця 4.7.Символьні Escape-послідовності

Команда Пояснення
\a Звуковий сигнал
\b Повернення на одну позицію назад
\f Перехід на нову сторінку
\n Перехід на новий рядок
\r Повернення каретки
\t Горизонтальна табуляція
\v Вертикальна табуляція
\0 Нуль
\' Одинарна лапка
\" Подвійна лапка
\\ Зворотна коса лінія

Рядкові літерали - це послідовність символів і символьних Escape-послідовностей, взятих у подвійні лапки.

Verbatim string - рядковий літерал, що інтерпретується компілятором так, як він записаний. Escape-послідовності сприймаються строго як послідовності символів.

Verbatim string представляється за допомогою символу @, який розташовується безпосередньо перед текстом, взятим в парні подвійні лапки. Представлення подвійних лапок в Verbatim string забезпечується їх дублюванням.

Пара літералів (другий - Verbatim string)

  ..."c:\\My Documents\\sample.txt"...
  ...@"c:\My Documents\sample.txt"...

мають одне і те ж значення: c:\My Documents\sample.txt.

Рядкові літерали є об’єктами типу string.

4.5. Приведення типів даних

Приведення типів - один з аспектів безпеки будь-якої мови програмування.

Використовувані в програмі типи характеризуються власними діапазонами значень, які визначаються властивостями типів, у тому числі і розміром області пам’яті, призначеної для кодування значень відповідного типу. При цьому області значень різних типів перетинаються.

Багато значень можна виразити більш ніж одним типом. Наприклад, значення 4 можна представити як значення типу sbyte, byte, short, ushort, int, uint, long, ulong. При цьому в програмі все повинно бути влаштовано так, щоб логіка перетворення значень одного типу до іншого була зрозумілою, а результати цих перетворень - передбачувані.

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

Але у ряді випадків перетворення вимагає додаткової уваги з боку програміста, який повинен явним чином вказувати необхідність перетворення, використовуючи вирази приведення типу або звертаючись до спеціальних методів перетворення, визначеним в класі System.Convert, які забезпечують перетворення значення одного типу до значення іншого.

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

Розрізняють розширююче та звужуюче перетворення.

Розширююче перетворення - значення одного типу перетвориться до значення іншого типу, яке має такий же або більший розмір. Наприклад, значення, представлене у вигляді 32-розрядного цілого числа із знаком, може бути перетворене в 64-розрядне ціле число із знаком. Розширене перетворення вважається безпечним, оскільки вихідна інформація при такому перетворенні не спотворюється.

Можливість розширеного перетворення представлено у таблиці нижче.

Таблиця 4.8. Можливість розширеного перетворення типів даних

Тип, що перетворюється У який тип перетворюється
Byte UInt16, Int16, UInt32, Int32, UInt64, Int64, Single, Double, Decimal
SByte Int16, Int32, Int64, Single, Double, Decimal
Int16 Int32, Int64, Single, Double, Decimal
UInt16 UInt32, Int32, UInt64, Int64, Single, Double, Decimal
Char UInt16, UInt32, Int32, UInt64, Int64, Single, Double, Decimal
Int32 Int64, Double, Decimal
UInt32 Int64, Double, Decimal
Int64 Decimal
UInt64 Decimal
Single Double

Звужуюче перетворення - значення одного типу перетвориться до значення іншого типу, яке має менший розмір (з 64-розрядного в 32-розрядне). Таке перетворення потенційне небезпечне втратою значення. Звужуючі перетворення можуть призводити до втрати інформації.

Якщо тип, до якого здійснюється перетворення, не може правильно передати значення джерела, то результат перетворення виявляється дорівнює константі PositiveInfinity або NegativeInfinity. Примітка. Перевірити інформацію! При цьому значення PositiveInfinity інтерпретується як результат ділення додатнього числа на нуль, а значення NegativeInfinity - як результат ділення відємного числа на нуль. Якщо звужуюче перетворення забезпечується методами класу System.Convert, то втрата інформації супроводиться генерацією виключення (про виключення пізніше).

Таблиця 4.9. Можливість звужуючого перетворення типів даних

Тип, що перетворюється У який тип перетворюється
Byte Sbyte
SByte Byte, UInt16, UInt32, UInt64
Int16 Byte, SByte, UInt16
UInt16 Byte, SByte, Int16
Int32 Byte, SByte, Int16, UInt16, UInt32
UInt32 Byte, SByte, Int16, UInt16, Int32
Int64 Byte, SByte, Int16, UInt16, Int32, UInt32, UInt64
UInt64 Byte, SByte, Int16, UInt16, Int32, UInt32, Int64
Decimal Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64
Single Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64
Double Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt6

Лістинг 4.12. Звужуюче перетворення

public static void Main()
{
    int i = 1234567;
    short s = (short)i;
    Console.WriteLine("int - {0}\n short - {1}", i,s);
}

Результат виконання:

  int - 1234567
  short - -10617

4.6. Робота з памяттю у С#

4.6.1. “Стек” і “купа”

Звершити розділ

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

Посилальні типи (наприклад, об’єкти) розташовуються в купі (Heap). Купа — це оперативна пам’ять вашого комп’ютера. Доступ до неї здійснюється повільніше, ніж до стека. Коли об’єкт розташовується в купі, то змінна зберігає лише адресу об’єкта. Ця адреса зберігається в стеку. За адресою програма має доступ до самого об’єкту, всі дані якого зберігаються в загальній пам’яті (купі).

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

4.6.2. Типи-посилання та типи-значення

Усі типи даних у C# поділяються на значимі (ValueType) та типи-посилання (ReferenceType).

Значимі типи:

  • містять у собі об’єкти даних;
  • не можуть бути пустими.

Типи-посилання:

  • містять у собі посилання на об’єкт даних;
  • можуть бути пустими (null).

Які типи даних відносяться до значимих, а які до типів-посилань було розглянуто у питанні 4.1.

Запишемо простий приклад:

int a = 1000;
int b = a;
b = 2000;

Логічно, що після виконання цього коду a = 1000, b = 2000 - це принцип роботи значимих типів - у змінну копіюється значення і не привязується до змінної з якої воно було взяте.

Тепер давайте створимо просту структуру Point.

Лістинг 4.13. Використання структури (значимий тип).

struct Point
{
    public int x;
    public int y;
} 

static void Main(string[] args)
{
    Point point1 = new Point();
    point1.x = 111;
    point1.y = 111;

    //Виведемо на екран змінну point1
    Console.WriteLine("point1.x = {0}; point1.y = {1}", point1.x, point1.y);

    // Створимо змінну point2
    Point point2 = point1;

    //Виведемо на екран змінну point2
    Console.WriteLine("point2.x = {0}; point2.y = {1}", point2.x, point2.y);

    Console.WriteLine("\t---- Change point 2-----");
    //Змінимо значення параметрів у point2
    point2.x = 777;
    point2.y = 777;
    //Виведемо point1 та point2
    Console.WriteLine("point1.x = {0}; point1.y = {1}", point1.x, point1.y);
    Console.WriteLine("point2.x = {0}; point2.y = {1}", point2.x, point2.y);
}

Результат виконання коду:

point1.x = 111; point1.y = 111
point2.x = 111; point2.y = 111
        ---- Change point 2-----
point1.x = 111; point1.y = 111
point2.x = 777; point2.y = 777

Як бачимо після зміни значень х та у для змінної point2 значення змінної рoint1 не змінилося! Все правильно, адже структура є значимим типом і тому відбувається копіювання значень при присвоєнні.

Тепер давайте замінимо структуру Point на клас.

Приклад 12. Використання класу (посилальний тип). Лістинг 4.14. Використання класу (посилальний тип)

class Point
{
  public int x;
  public int y;
}

Результат виконання коду:

point1.x = 111; point1.y = 111
point2.x = 111; point2.y = 111
        ---- Change point 2-----
point1.x = 777; point1.y = 777
point2.x = 777; point2.y = 777

Як бачимо значення point1 змінилося. Посилальні типи даних вказують лише на ділянку пам’яті, у якій знаходиться значення, а не на саме значення. При присвоєнні не відбувається копіювання значень, а копіювання посилання.

Для узагальнення опишемо все це у таблиці:

Таблиця 4.10. Можливість звужуючого перетворення типів даних

Характеристика Значимий тип Тип-посилання
Змінна містить Значення Посилання на значення
Змінна зберігається У стеку У кучі
Значення по замовчуванню 0, false '\0', null
Оператор присвоєння Копіює значення Копіює посилання

Коментар до таблиці:

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

Значенням, яким змінна ініціалізувалася за умовчанням (необхідність виконання цієї вимоги диктується ідеологією безпеки Microsoft .NET) в разі визначення за допомогою типу-значення є 0 (для чисельного типу даних), false (для логічного типу даних), '\0' (для символьного типу даних), а в разі визначення за допомогою типу-посилання – значення порожнього посилання null.


Задачі

Задача 4.1

Виведіть на екран наступний текст:

"Саме знання нікому їсти не дасть."
                      \І. Я. Франко\

Задача 4.2

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


Задача 4.3

Напишіть програму, яка конвертує суму грн. у євро.


Задача 4.4

Напишіть попередню програму з умовою, що суму у грн. та курс валюти задає користувач.


Задача 4.5

Написати програму, яка обчислює квадрат введеного числа.


Задача 4.6*

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


Задача 4.7

Обрахувати ціну покупки, якщо відомо:

  • ціну за одиницю товару;
  • кількість товару;
  • знижка (у % від загальної суми).

Задача 4.8

Дано катети прямокутного трикутника (вводяться з клавіатури користувачем). Визначити довжину гіпотенузи.


Задача 4.9

У C# немає функції, яка обчислює квадрат числа. Написати програму, яка обчислює квадрат введеного числа.


Контрольні запитання

  1. Що таке тип даних?
  2. Дайте класифікацію типів даних .NET.
  3. Що таке змінна? Який синтаксис оголошення змінної.
  4. Що таке констаната? Який синтаксис оголошення констант?
  5. Що таке область видимості змінної?
  6. Які види операторів існують у C#?
  7. Що таке інкремент та декремент? Яка різниця між постфіксним та прфіксним записом цих операторів?
  8. Що таке літерал? Який синтаксис запису літералів для різних типів даних.
  9. Що таке привдення типів даних? 10.Яка різниця та синтаксис явного та неявного привдення типів даних? 11.Опишіть основні характеристики значимих типів даних. 12.Опишіть основні характеристики посилальних типів даних.

Список використаних джерел

  1. Albahari, J., & Albahari, B. (2012). C# 5.0 in a Nutshell, Fifth Edition. 1005 Gravenstein Highway North, Sebastopol, USA: O’Reilly Media, Inc.
  2. Cardelli, L., & Wegner, P. (December 1985 p.). On Understanding Types, Data Abstraction, and Polymorphism. ACM Computing 1. Surveys, 17(4), сс. 149-154. Отримано з http://lucacardelli.name/Papers/OnUnderstanding.A4.pdf
  3. Kort, W. d. (2013). Exam Ref 70-483: Programming in C#. Sebastopol, California 95472: O’Reilly Media, Inc.
  4. MSDN. (без дати). Отримано з Microsoft Developer Network: http://msdn.microsoft.com/
  5. Биллиг, А. (2005). Основы программирования на C#. Москва, Россия: НОУ ИНТУИТ.
  6. Нейгел, К. И. (2011). С# 4.0 и платформа .NET 4 для профессионалов. Москва, Россия: ООО “И.Д. Вильямс”.
  7. Павловская, Т. (2009). С#. Программирование на языке высокого уровня. Учебник для вузов. СПб, Россия: Питер.

Додаткові матеріали для завантаження

  1. Лекція у форматі DOCX Завантажити (у розробці)
  2. Лекція у форматі PDF Завантажити (у розробці)
  3. Діаграми до лекції у форматі draw.io на github Перейти

Дорогі друзі, якщо Ви помітили, що для написання матеріалів використані джерела, які я не вказав - прошу надіслати мені інформацію на пошту. Дякую.
Previous
Next