JavaRush /Курси /C# SELF /Поглиблена робота з масивами

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

C# SELF
Рівень 7 , Лекція 2
Відкрита

1. Масив типу string

Коротко розповімо про масив типу string.

Як ми вже зазначали, масив може бути будь‑якого типу. А отже, можна створити масив типу string. Ось як виглядав би код, якби потрібно було написати програму, що «зчитує з клавіатури 10 рядків і виводить їх на екран у зворотному порядку».

string[] array = new string[10];	// Створюємо обʼєкт-масив на 10 елементів
for (int i = 0; i < 10; i++)		// Цикл від 0 до 9
{
   array[i] = Console.ReadLine();	// Зчитуємо рядок з клавіатури й зберігаємо його в комірку масиву
}
for (int i = 9; i >= 0; i--)		// Цикл від 9 до 0
{
    Console.WriteLine(array[i]);	// Виводимо на екран чергову комірку масиву
}

Код практично не змінився! Потрібно лише під час створення масиву замінити тип int на string. Решта — без змін.

2. Масив типу string у пам’яті

І ще один корисний факт. Розгляньмо три ілюстрації:

Ілюстрація 1. Як обʼєкт string розміщується в пам’яті:

Масив типу string у пам'яті

Зверніть увагу: текст рядка не зберігається прямо у змінній — для нього виділяється окремий блок пам’яті. А у змінній типу string зберігається адреса (посилання) на об’єкт із текстом.

Ілюстрація 2. Як масив цілих чисел розміщується в пам’яті:

Масив int у пам'яті

Це зображення ви також бачили.

Ілюстрація 3. Як у пам’яті розміщується масив рядків:

Як у пам'яті розташовується string масив

Ліворуч бачимо змінну‑масив типу string[] (вона зберігає адресу об’єкта‑масиву).
Посередині — об’єкт‑масив типу string.
Праворуч — об’єкти‑рядки, які зберігають певні тексти.

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

3. Швидка ініціалізація масиву у C#

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

Часто, окрім даних, які програма зчитує, їй для роботи потрібні також свої внутрішні дані. Наприклад, потрібно зберігати в масиві довжини всіх місяців. Як може виглядати цей код:

int[] months = new int[12];
months[0] = 31; // січень
months[1] = 28; // лютий
months[2] = 31; // березень
months[3] = 30; // квітень
months[4] = 31; // травень
months[5] = 30; // червень
months[6] = 31; // липень
months[7] = 31; // серпень
months[8] = 30; // вересень
months[9] = 31; // жовтень
months[10] = 30; // листопад
months[11] = 31; // грудень

Але є спосіб записати його коротше:

// довжини місяців року
int[] months = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

Можна просто перелічити через кому всі значення масиву!

Зручно, чи не так? Та й це ще не все.

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

Тому цей код можна записати ще коротше:

// довжини місяців року
int[] months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

Хіба не гарно?

Такий запис називається «швидка ініціалізація масиву». Вона, до речі, працює не тільки для типу int

// назви місяців року
string[] months = { "Січень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"};

4. Цикл foreach

Прохід усіма елементами масиву — настільки часта операція, що для неї створили окремий цикл. Він називається foreach і записується так:


foreach (int score in scores)
{
    Console.WriteLine($"Бали: {score}");
}
        
Перебирання елементів масиву за допомогою foreach

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

Компілятор перетворить код циклу foreach на код нижче:


for (int i = 0; i < scores.Length; i++)
{
    int score = scores[i];
    Console.WriteLine($"Бали: {score}");
}
        
Як працює foreach «під капотом»

Жодної магії. Тепер зрозуміло, чому ви не можете змінити елементи масиву через змінну score.

Напишімо ще один приклад: знайдемо суму всіх балів.


int sum = 0;
foreach (int score in scores)
{
    sum += score;
}
Console.WriteLine($"Сума всіх балів: {sum}");
        
Сумування елементів масиву за допомогою foreach

Коли варто використовувати який цикл?

  • Якщо потрібен індекс або потрібно змінювати елементи — цикл for.
  • Якщо просто пройтися значеннями — foreach швидше й чистіше.

5. Індексування з кінця: оператор ^

У сучасних версіях C# можна отримувати елементи з кінця масиву. Раніше було так: потрібен останній елемент масиву — пишете щось на кшталт:


int lastScore = scores[scores.Length - 1];
        
Отримання останнього елемента масиву по-старому

Тепер усе набагато простіше. Достатньо перед індексом написати оператор ^, і відлік піде з кінця:


int lastScore = scores[^1];        // Останній елемент
int penultimate = scores[^2];      // Передостанній елемент
        
Індексування з кінця за допомогою ^
  • ^1 — це перший із кінця (останній).
  • ^2 — другий із кінця.

Візуалізація

Індекс Значення в масиві scores Звичайний індекс Індекс з кінця
0 10 0 ^5
1 15 1 ^4
2 8 2 ^3
3 22 3 ^2
4 17 4 ^1

scores[^1] = 17, scores[^2] = 22 — легко й зрозуміло.

Важливо! Зверніть увагу, що індекси з кінця нумеруються з 1, а не з нуля.

6. Діапазони: оператор .. (range)

Іноді потрібно отримати підмасив із вихідного масиву. У C# для цього можна використовувати спеціальний оператор діапазону .. — лаконічний і дуже зручний спосіб отримати потрібний фрагмент масиву.

Такий підмасив іноді називають зрізом (від англ. slice), і він визначається двома межами: початком і кінцем.
Важливий момент: права межа не включається в результат!

Як це виглядає


int[] scores = { 10, 15, 8, 22, 17 };
int[] top3 = scores[0..3]; // Беремо елементи від 0 до 2: {10, 15, 8}
        
Отримання зрізу масиву за допомогою ..

  • scores[0..3] — беремо елементи з індексу 0 (включно) до індексу 3 (НЕ включно).
  • Підсумковий масив міститиме елементи з індексами 0, 1 і 2.

Скорочення

Якщо ліва межа не вказана, беремо з самого початку:


int[] firstTwo = scores[..2]; // те саме, що scores[0..2], тобто {10, 15}
        

Якщо права межа не вказана, беремо до самого кінця:


int[] fromThird = scores[2..]; // {8, 22, 17}
        

Від’ємні індекси

За допомогою символу ^ можна відраховувати індекси з кінця:


int[] lastTwo = scores[^2..]; // {22, 17}
        

Тут ^2 — це «другий елемент із кінця».

Можна також брати діапазон «від» і «до», використовуючи від’ємні індекси з обох боків:


int[] mid = scores[1..^1]; // {15, 8, 22} (від другого до передостаннього)
        

Що важливо пам’ятати

  • Правий край завжди не включається — тобто якщо пишемо a..b, отримуємо всі елементи від a до b-1.
  • Якщо вказати межі, що виходять за межі масиву, буде згенеровано виняток IndexOutOfRangeException.
  • Зріз — це новий масив, оригінал не змінюється. Будь‑які зміни в новому масиві не зачеплять вихідний.

Практичні приклади

int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };

int[] start = arr[..3];      // {1, 2, 3}
int[] end = arr[^2..];       // {6, 7}
int[] middle = arr[2..5];    // {3, 4, 5}
int[] all = arr[..];         // {1, 2, 3, 4, 5, 6, 7} (повна копія)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ