Fundamentals

Масиви

Вивчення масивів у C# - одновимірні, багатовимірні та зубчасті масиви, операції з масивами, методи класу Array, індекси та діапазони.

Масиви

Вступ та Контекст

Уявіть, що вам потрібно зберегти оцінки 30 студентів у курсі. Створювати 30 окремих змінних (grade1, grade2, ..., grade30) — це не тільки незручно, а й майже неможливо підтримувати. Саме для таких ситуацій існують масиви (Arrays).

Масиви — це фундаментальна структура даних, яка дозволяє зберігати декілька значень одного типу під одним ім'ям. Вони є будівельними блоками для більш складних колекцій та структур даних у C#.

Цікавий факт: Масиви в C# зберігаються в керованій купі (Managed Heap), навіть якщо вони містять типи значень. Це означає, що сам масив є reference type, хоча він може зберігати value types.

Що ви дізнаєтесь

У цьому розділі ви опануєте:

  • Різні типи масивів (одновимірні, багатовимірні, зубчасті)
  • Способи створення та ініціалізації масивів
  • Операції з масивами та методи класу Array
  • Сучасні можливості роботи з індексами та діапазонами (C# 8+)
  • Типові проблеми та їх вирішення

Передумови

Перед початком вивчення цієї теми рекомендується:

  • Розуміння змінних та типів даних
  • Знання про value types vs reference types
  • Базове розуміння циклів (for, foreach)

Фундаментальні Концепції

Що таке масив?

Масив (Array) — це структура даних фіксованого розміру, яка зберігає послідовність елементів одного типу під одним ім'ям. Кожен елемент доступний через числовий індекс (index).

Loading diagram...
graph LR
    A["int[] numbers"] --> B["Посилання в Stack"]
    B --> C["Об'єкт масиву в Heap"]
    C --> D["[0]: 10"]
    C --> E["[1]: 20"]
    C --> F["[2]: 30"]
    C --> G["[3]: 40"]
    C --> H["[4]: 50"]

    style B fill:#64748b,stroke:#334155,color:#ffffff
    style C fill:#f59e0b,stroke:#b45309,color:#ffffff
    style D fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style E fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style F fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style G fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style H fill:#3b82f6,stroke:#1d4ed8,color:#ffffff

Ключові характеристики масивів:

ХарактеристикаОпис
Фіксований розмірПісля створення розмір масиву не може бути змінений
ГомогенністьВсі елементи мають один і той самий тип
0-based indexingІндексація починається з 0 (перший елемент має індекс 0)
Reference typeСам масив зберігається в Heap, навіть якщо містить value types
Послідовне розміщенняЕлементи зберігаються в пам'яті послідовно (cache-friendly)
Чому індексація з 0? Історично індекс — це зміщення (offset) від початку масиву. Перший елемент має зміщення 0 байтів, другий — 1×(розмір елемента) байтів, тощо.

Типи Масивів

C# підтримує три типи масивів, кожен з яких призначений для різних сценаріїв.

Одновимірні масиви

Одновимірний масив (Single-dimensional array) — це найпростіший тип масиву, який представляє лінійну послідовність елементів.

Оголошення та ініціалізація

// Створення масиву на 5 елементів
int[] numbers = new int[5];

// Присвоювання значень
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;

Console.WriteLine(numbers[0]); // 10

Доступ до елементів

string[] fruits = ["Apple", "Banana", "Orange", "Grape"];

// Читання елементів
Console.WriteLine(fruits[0]);  // Apple
Console.WriteLine(fruits[2]);  // Orange

// Зміна елементів
fruits[1] = "Mango";
Console.WriteLine(fruits[1]);  // Mango

// Останній елемент
Console.WriteLine(fruits[fruits.Length - 1]); // Grape

// Index from end operator (^ - C# 8+)
Console.WriteLine(fruits[^1]); // Grape (останній)
Console.WriteLine(fruits[^2]); // Orange (передостанній)
Увага: Спроба доступу до неіснуючого індексу призведе до IndexOutOfRangeException:
int[] nums = [1, 2, 3];
int value = nums[10]; // ❌ IndexOutOfRangeException

Багатовимірні масиви

Багатовимірний масив (Multidimensional array) — це масив з двома або більше вимірами. Найпоширеніші — 2D масиви (матриці).

2D Масиви (Прямокутні масиви)

// Оголошення 2D масиву 3×4 (3 рядки, 4 стовпці)
int[,] matrix = new int[3, 4];

// Ініціалізація з значеннями
int[,] numbers = {
    { 1, 2, 3, 4 },
    { 5, 6, 7, 8 },
    { 9, 10, 11, 12 }
};

// Доступ до елементів
Console.WriteLine(numbers[0, 0]);  // 1
Console.WriteLine(numbers[1, 2]);  // 7
Console.WriteLine(numbers[2, 3]);  // 12

// Зміна елемента
numbers[1, 1] = 99;
Console.WriteLine(numbers[1, 1]);  // 99
Loading diagram...
graph TD
    A["int[,] matrix = new int[3, 4]"]
    A --> B["Рядок 0"]
    A --> C["Рядок 1"]
    A --> D["Рядок 2"]

    B --> B0["[0,0]"]
    B --> B1["[0,1]"]
    B --> B2["[0,2]"]
    B --> B3["[0,3]"]

    C --> C0["[1,0]"]
    C --> C1["[1,1]"]
    C --> C2["[1,2]"]
    C --> C3["[1,3]"]

    D --> D0["[2,0]"]
    D --> D1["[2,1]"]
    D --> D2["[2,2]"]
    D --> D3["[2,3]"]

    style A fill:#f59e0b,stroke:#b45309,color:#ffffff
    style B fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style C fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style D fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B0 fill:#64748b,stroke:#334155,color:#ffffff
    style B1 fill:#64748b,stroke:#334155,color:#ffffff
    style B2 fill:#64748b,stroke:#334155,color:#ffffff
    style B3 fill:#64748b,stroke:#334155,color:#ffffff
    style C0 fill:#64748b,stroke:#334155,color:#ffffff
    style C1 fill:#64748b,stroke:#334155,color:#ffffff
    style C2 fill:#64748b,stroke:#334155,color:#ffffff
    style C3 fill:#64748b,stroke:#334155,color:#ffffff
    style D0 fill:#64748b,stroke:#334155,color:#ffffff
    style D1 fill:#64748b,stroke:#334155,color:#ffffff
    style D2 fill:#64748b,stroke:#334155,color:#ffffff
    style D3 fill:#64748b,stroke:#334155,color:#ffffff

3D Масиви та вище

// 3D масив: 2×3×4 (2 "шари", 3 рядки, 4 стовпці)
int[,,] cube = new int[2, 3, 4];

// Ініціалізація
int[,,] data = {
    {
        { 1, 2, 3, 4 },
        { 5, 6, 7, 8 },
        { 9, 10, 11, 12 }
    },
    {
        { 13, 14, 15, 16 },
        { 17, 18, 19, 20 },
        { 21, 22, 23, 24 }
    }
};

// Доступ: [шар, рядок, стовпець]
Console.WriteLine(data[0, 1, 2]); // 7
Console.WriteLine(data[1, 2, 3]); // 24

Властивості багатовимірних масивів

int[,] matrix = new int[3, 4];

// Загальна кількість елементів
Console.WriteLine($"Length: {matrix.Length}"); // 12 (3×4)

// Кількість вимірів
Console.WriteLine($"Rank: {matrix.Rank}"); // 2

// Розмір конкретного виміру
Console.WriteLine($"Dimension 0: {matrix.GetLength(0)}"); // 3 (рядки)
Console.WriteLine($"Dimension 1: {matrix.GetLength(1)}"); // 4 (стовпці)

Зубчасті масиви

Зубчастий масив (Jagged array) — це масив масивів, де кожен внутрішній масив може мати різну довжину.

// Оголошення зубчастого масиву з 3 рядків
int[][] jaggedArray = new int[3][];

// Ініціалізація кожного внутрішнього масиву окремо
jaggedArray[0] = new int[] { 1, 2, 3 };       // 3 елементи
jaggedArray[1] = new int[] { 4, 5 };          // 2 елементи
jaggedArray[2] = new int[] { 6, 7, 8, 9 };    // 4 елементи

// Доступ
Console.WriteLine(jaggedArray[0][0]); // 1
Console.WriteLine(jaggedArray[1][1]); // 5
Console.WriteLine(jaggedArray[2][3]); // 9
Loading diagram...
graph TD
    A["int[][] jagged = new int[3][]"]
    A --> B["jagged[0]"]
    A --> C["jagged[1]"]
    A --> D["jagged[2]"]

    B --> B0["[0][0]: 1"]
    B --> B1["[0][1]: 2"]
    B --> B2["[0][2]: 3"]

    C --> C0["[1][0]: 4"]
    C --> C1["[1][1]: 5"]

    D --> D0["[2][0]: 6"]
    D --> D1["[2][1]: 7"]
    D --> D2["[2][2]: 8"]
    D --> D3["[2][3]: 9"]

    style A fill:#f59e0b,stroke:#b45309,color:#ffffff
    style B fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style C fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style D fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B0 fill:#64748b,stroke:#334155,color:#ffffff
    style B1 fill:#64748b,stroke:#334155,color:#ffffff
    style B2 fill:#64748b,stroke:#334155,color:#ffffff
    style C0 fill:#64748b,stroke:#334155,color:#ffffff
    style C1 fill:#64748b,stroke:#334155,color:#ffffff
    style D0 fill:#64748b,stroke:#334155,color:#ffffff
    style D1 fill:#64748b,stroke:#334155,color:#ffffff
    style D2 fill:#64748b,stroke:#334155,color:#ffffff
    style D3 fill:#64748b,stroke:#334155,color:#ffffff

Коли використовувати зубчасті масиви?

СценарійПриклад
Трикутна матрицяКожен рядок має різну кількість елементів
Дані з нерегулярною структуроюСписок студентів, де кожен має різну кількість оцінок
Економія пам'ятіКоли не всі комірки прямокутної матриці використовуються
Кращу продуктивністьJagged arrays швидші за multidimensional (менше arithmetic overhead)
Performance: Зубчасті масиви зазвичай швидші за багатовимірні, оскільки кожен внутрішній масив — це окремий об'єкт з послідовним розміщенням в пам'яті.

Порівняння: Багатовимірні vs Зубчасті

КритерійБагатовимірний int[,]Зубчастий int[][]
СтруктураПрямокутна матрицяМасив масивів
Розмір рядківВсі однаковіМожуть бути різні
Синтаксис доступуarray[i, j]array[i][j]
ПродуктивністьПовільнішеШвидше
Використання пам'ятіМенше overheadБільше overhead (кожен рядок — окремий об'єкт)
Випадок використанняМатриці, таблиціНерегулярні структури

Операції з Масивами

Перебір елементів

int[] numbers = [10, 20, 30, 40, 50];

// Перебір з індексом
for (int i = 0; i < numbers.Length; i++)
{
    Console.WriteLine($"Index {i}: {numbers[i]}");
}

// Зворотний перебір
for (int i = numbers.Length - 1; i >= 0; i--)
{
    Console.WriteLine(numbers[i]);
}

Копіювання масивів

int[] original = [1, 2, 3, 4, 5];

// ❌ ПОМИЛКА: Копіювання посилання (shallow copy)
int[] reference = original;
reference[0] = 999;
Console.WriteLine(original[0]); // 999 (змінився оригінал!)

// ✅ Спосіб 1: Array.Copy
int[] copy1 = new int[5];
Array.Copy(original, copy1, original.Length);

// ✅ Спосіб 2: Clone (повертає object, потрібне приведення)
int[] copy2 = (int[])original.Clone();

// ✅ Спосіб 3: CopyTo
int[] copy3 = new int[5];
original.CopyTo(copy3, 0);

// ✅ Спосіб 4: Новий масив з Range (C# 8+)
int[] copy4 = original[..]; // Копіює весь масив

// Перевірка
copy1[0] = 100;
Console.WriteLine(original[0]); // 1 (оригінал не змінився)
Важливо: Всі наведені способи створюють shallow copy (поверхневу копію). Якщо масив містить reference types, копіюються лише посилання, а не самі об'єкти.
string[] original = ["Hello", "World"];
string[] copy = (string[])original.Clone();

// Рядки незмінні, тому це безпечно
// Але для власних класів це може бути проблемою!

Клас Array

Всі масиви в C# успадковуються від класу System.Array, який надає багато корисних методів.

Властивості

int[] numbers = [10, 20, 30, 40, 50];
int[,] matrix = new int[3, 4];

// Length - загальна кількість елементів
Console.WriteLine(numbers.Length);    // 5
Console.WriteLine(matrix.Length);     // 12

// Rank - кількість вимірів
Console.WriteLine(numbers.Rank);      // 1
Console.WriteLine(matrix.Rank);       // 2

// GetLength(dimension) - розмір конкретного виміру
Console.WriteLine(matrix.GetLength(0)); // 3 (рядки)
Console.WriteLine(matrix.GetLength(1)); // 4 (стовпці)

// GetLowerBound / GetUpperBound
Console.WriteLine(numbers.GetLowerBound(0)); // 0
Console.WriteLine(numbers.GetUpperBound(0)); // 4 (останній індекс)

Методи сортування та реверсу

// Sort - сортування за зростанням
int[] numbers = [5, 2, 8, 1, 9];
Array.Sort(numbers);
Console.WriteLine(string.Join(", ", numbers));
// Output: 1, 2, 5, 8, 9

// Reverse - зворотний порядок
Array.Reverse(numbers);
Console.WriteLine(string.Join(", ", numbers));
// Output: 9, 8, 5, 2, 1

// Сортування рядків (alphabetically)
string[] names = ["Zoryana", "Anna", "Bohdan"];
Array.Sort(names);
Console.WriteLine(string.Join(", ", names));
// Output: Anna, Bohdan, Zoryana
Складність: Array.Sort() використовує IntroSort (гібрид QuickSort, HeapSort та InsertionSort) з часовою складністю O(n log n) у середньому випадку.

Методи пошуку

string[] fruits = ["Apple", "Banana", "Orange", "Banana"];

// IndexOf - перше входження
int index1 = Array.IndexOf(fruits, "Banana");
Console.WriteLine(index1); // 1

// LastIndexOf - останнє входження
int index2 = Array.LastIndexOf(fruits, "Banana");
Console.WriteLine(index2); // 3

// Якщо не знайдено - повертає -1
int index3 = Array.IndexOf(fruits, "Grape");
Console.WriteLine(index3); // -1

Інші корисні методи

int[] numbers = [1, 2, 3, 4, 5];

// Clear - обнулення діапазону елементів
Array.Clear(numbers, 1, 3); // Обнулити 3 елементи, починаючи з індексу 1
Console.WriteLine(string.Join(", ", numbers));
// Output: 1, 0, 0, 0, 5

// Fill - заповнення значенням (C# 10+)
int[] data = new int[5];
Array.Fill(data, 42);
Console.WriteLine(string.Join(", ", data));
// Output: 42, 42, 42, 42, 42

// Resize - зміна розміру (створює НОВИЙ масив!)
int[] original = [1, 2, 3];
Array.Resize(ref original, 5);
Console.WriteLine(string.Join(", ", original));
// Output: 1, 2, 3, 0, 0
Важливо про Array.Resize: Цей метод НЕ змінює існуючий масив. Він створює новий масив та оновлює посилання. Якщо є інші посилання на старий масив, вони залишаться незмінними.
int[] arr1 = [1, 2, 3];
int[] arr2 = arr1; // Додаткове посилання

Array.Resize(ref arr1, 5);

Console.WriteLine(arr1.Length); // 5 (новий масив)
Console.WriteLine(arr2.Length); // 3 (старий масив)

Індекси та Діапазони (C# 8+)

C# 8 додав потужні можливості для роботи з масивами: Index (^) та Range (..).

Index from End (^)

string[] fruits = ["Apple", "Banana", "Orange", "Grape", "Mango"];

// Зворотна індексація
Console.WriteLine(fruits[^1]); // Mango (останній)
Console.WriteLine(fruits[^2]); // Grape (передостанній)
Console.WriteLine(fruits[^5]); // Apple (перший, якщо рахувати з кінця)

// Еквівалент старому способу
Console.WriteLine(fruits[fruits.Length - 1]); // Mango
Console.WriteLine(fruits[fruits.Length - 2]); // Grape

// Можна використовувати в змінних
Index lastIndex = ^1;
Console.WriteLine(fruits[lastIndex]); // Mango
Loading diagram...
graph LR
    A["Array indices"]
    A --> B["Forward: [0] [1] [2] [3] [4]"]
    A --> C["Backward: [^5] [^4] [^3] [^2] [^1]"]

    B --> D["Apple"]
    B --> E["Banana"]
    B --> F["Orange"]
    B --> G["Grape"]
    B --> H["Mango"]

    C --> D
    C --> E
    C --> F
    C --> G
    C --> H

    style A fill:#f59e0b,stroke:#b45309,color:#ffffff
    style B fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style C fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style D fill:#64748b,stroke:#334155,color:#ffffff
    style E fill:#64748b,stroke:#334155,color:#ffffff
    style F fill:#64748b,stroke:#334155,color:#ffffff
    style G fill:#64748b,stroke:#334155,color:#ffffff
    style H fill:#64748b,stroke:#334155,color:#ffffff

Range Operator (..)

int[] numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

// Range: [start..end] (end не включається)
int[] slice1 = numbers[2..5];   // [2, 3, 4]
int[] slice2 = numbers[0..3];   // [0, 1, 2]
int[] slice3 = numbers[5..];    // [5, 6, 7, 8, 9] (до кінця)
int[] slice4 = numbers[..4];    // [0, 1, 2, 3] (з початку)
int[] slice5 = numbers[..];     // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (весь масив)

// Комбінація з Index from End
int[] slice6 = numbers[^5..^1]; // [5, 6, 7, 8]
int[] slice7 = numbers[2..^2];  // [2, 3, 4, 5, 6, 7]

// Зберігання range у змінній
Range middle = 3..7;
int[] slice8 = numbers[middle]; // [3, 4, 5, 6]

Console.WriteLine(string.Join(", ", slice1)); // 2, 3, 4
Console.WriteLine(string.Join(", ", slice6)); // 5, 6, 7, 8
Візуалізація Range:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
                 ↑     ↑
       [2..5] виділяє індекси 2, 3, 4 (5 не включається)

Практична Реалізація

Типові сценарії використання

int[] scores = [85, 92, 78, 95, 88, 76];

// Сума
int sum = 0;
foreach (int score in scores)
{
    sum += score;
}
Console.WriteLine($"Sum: {sum}"); // 514

// Середнє значення
double average = (double)sum / scores.Length;
Console.WriteLine($"Average: {average:F2}"); // 85.67

// Максимум та мінімум
int max = scores[0];
int min = scores[0];
foreach (int score in scores)
{
    if (score > max) max = score;
    if (score < min) min = score;
}
Console.WriteLine($"Max: {max}, Min: {min}"); // Max: 95, Min: 76

Передача масивів у методи

// Метод для виведення масиву
static void DisplayArray(int[] arr)
{
    Console.WriteLine($"[{string.Join(", ", arr)}]");
}

// Метод для зміни елементів
static void MultiplyByTwo(int[] arr)
{
    for (int i = 0; i < arr.Length; i++)
    {
        arr[i] *= 2;
    }
}

// Використання
int[] numbers = [1, 2, 3, 4, 5];

DisplayArray(numbers); // [1, 2, 3, 4, 5]

MultiplyByTwo(numbers); // Змінює оригінальний масив!

DisplayArray(numbers); // [2, 4, 6, 8, 10]
Важливо: Масиви завжди передаються за посиланням (навіть без ключового слова ref). Зміни всередині методу відображаються на оригінальному масиві.

Troubleshooting

Типові помилки та їх вирішення

IndexOutOfRangeException

int[] numbers = [1, 2, 3, 4, 5];

// Помилка: індекс виходить за межі
for (int i = 0; i <= numbers.Length; i++) // <=  замість <
{
    Console.WriteLine(numbers[i]);
    // ❌ Exception при i = 5
}

NullReferenceException

int[] numbers = null;

// ❌ NullReferenceException
Console.WriteLine(numbers.Length);

Проблема з багатовимірними масивами

int[,] matrix = new int[3, 4];

// ❌ Неправильно: використання Length
for (int i = 0; i < matrix.Length; i++) // Length = 12!
{
    // matrix[i] - синтаксична помилка
}

Зміна розміру масиву

// ❌ Неможливо змінити розмір існуючого масиву
int[] numbers = [1, 2, 3];
// numbers.Length = 5; // Помилка: Length is read-only

// ✅ Створення нового масиву більшого розміру
int[] oldArray = [1, 2, 3];
int[] newArray = new int[5];
Array.Copy(oldArray, newArray, oldArray.Length);
// newArray: [1, 2, 3, 0, 0]

// ✅ Або використання Array.Resize
int[] data = [1, 2, 3];
Array.Resize(ref data, 5);
// data: [1, 2, 3, 0, 0]
Best Practice: Якщо вам часто потрібно змінювати розмір колекції, використовуйте List<T> замість масиву (це буде розглянуто в наступних розділах).

Практичні Завдання

Рівень 1: Базові операції

Завдання 1.1: Сума парних чисел

Створіть масив з 10 цілих чисел. Обчисліть суму всіх парних чисел у масиві.

Підказка: Використовуйте оператор % для перевірки парності.

Завдання 1.2: Реверс масиву

Напишіть код, який реверсує масив без використання Array.Reverse().

Завдання 1.3: Пошук максимального елемента

Знайдіть максимальний елемент у масиві та його індекс.

Рівень 2: Робота з багатовимірними масивами

Завдання 2.1: Транспонування матриці

Створіть функцію, яка транспонує матрицю (міняє рядки та стовпці місцями).

Завдання 2.2: Діагональна сума

Обчисліть суму елементів на головній діагоналі квадратної матриці.

Рівень 3: Складні алгоритми

Завдання 3.1: Bubble Sort

Реалізуйте алгоритм сортування бульбашкою (Bubble Sort).

Завдання 3.2: Зубчастий масив студентів

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

Завдання 3.3: Спіраль матриці

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

Резюме

У цьому розділі ви дізналися про:

  • Три типи масивів: одновимірні, багатовимірні та зубчасті
  • Операції з масивами: ініціалізація, доступ, перебір, копіювання
  • Клас Array: потужні методи для сортування, пошуку та маніпуляції
  • Сучасний синтаксис: індекси ^ та діапазони .. (C# 8+)
  • Практичні сценарії: від простої статистики до складних алгоритмів
Наступний крок: Масиви — це основа для розуміння колекцій. У наступних розділах ви дізнаєтеся про List<T>, Dictionary<TKey, TValue> та інші динамічні структури даних, які базуються на концепціях, вивчених тут.