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 зберігається адреса (посилання) на об’єкт із текстом.
Ілюстрація 2. Як масив цілих чисел розміщується в пам’яті:
Це зображення ви також бачили.
Ілюстрація 3. Як у пам’яті розміщується масив рядків:
Ліворуч бачимо змінну‑масив типу 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 на код нижче:
for (int i = 0; i < scores.Length; i++)
{
int score = scores[i];
Console.WriteLine($"Бали: {score}");
}
Жодної магії. Тепер зрозуміло, чому ви не можете змінити елементи масиву через змінну score.
Напишімо ще один приклад: знайдемо суму всіх балів.
int sum = 0;
foreach (int score in scores)
{
sum += score;
}
Console.WriteLine($"Сума всіх балів: {sum}");
Коли варто використовувати який цикл?
- Якщо потрібен індекс або потрібно змінювати елементи — цикл 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} (повна копія)
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ