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 в памяти
И еще один полезный факт. Рассмотрим 3 картинки:
Картинка 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; // декабрь
Но есть способ записать его короче — спасибо создателям C#:
// длины месяцев года
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 |
score[^1] = 17, score[^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} (со 2-го до предпоследнего)
Что важно помнить
- Правый край всегда не включается — то есть если пишем 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} (полная копия)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ