
Начальные сведения о массивах
Все типы переменных, изученные ранее – т.н. скалярные (простые) типы.
Массив – это агрегированная (структурированная) переменная. Массив можно уподобить таким понятиям алгебры, как вектор, матрица.
Массив – это упорядоченная совокупность данных одного типа. Для пояснения того, как именно упорядочивается совокупность, существует и другое определение:
Массив – это переменная с индексом (со списком индексов).
<Объявление одномерного массива> :: =
<Имя типа>[] <Имя массива>;
Квадратные (причём, пустые) скобки в данном случае являются элементом языка C#, а не признаком необязательности их содержимого. Пустые скобки означают, что переменная является одномерным массивом. Доступ к элементам такого массива будет осуществляться с помощью одного индекса. Количество элементов массива не определено, память под массив не выделена. Работать с массивом можно будет только после явного выделения памяти под него:
<Имя массива> = new <Имя типа>[<Выражение числа элементов>];
<Имя типа> должно совпадать с тем, которое участвовало в объявлении массива.
<Выражение> должно быть целочисленным. Оно назначает количество элементов в массиве. Элементы получают место в памяти, но значение их не определено.
Объяснять далее тему «массивы» с помощью формул Бэкуса–Наура означало бы сделать объяснение неоправданно громоздким. Более уместным представляется пояснить работу с массивами на примерах.
Примеры объявления массивов:
double[] mD1; bool[,] mB2; int[,,] mI3;
Первый из массивов – одномерный, второй и третий, соответственно, двумерный и трёхмерный. Память под элементы массивов в результате такого объявления не выделяется. Правда, память (8 байт) выделяется под указатель на элементы будущего массива, и он указывает «никуда» (имеет значение null). Однако, слово «указатель» не рекомендуется применять в языке C# до поры до времени, а в языках Java и VB – никогда. Разрешено слово «ссылка», означающее примерно то же.
Примеры размещения массивов в памяти:
mD1 = new double[7]; mB2 = new bool[4,5]; mI3 = new int[2,2,2];
В выражениях в квадратных скобках указана «протяжённость» массива по каждому из его измерений, причём, эти выражения могут зависеть от переменных. Левая граница диапазона изменения индексов есть ноль, правая на единицу меньше «протяжённости». Количество элементов массива есть произведение всех его «протяжённостей». Массив mD1 состоит из 7 элементов, обращение к первому и последнему из них имеет вид соответственно mD1[0], mD1[6]. Массивы mB2 и mI3 содержат соответственно 20 и 8 элементов.
Оператор размещения массива в памяти применяется столько раз, сколько угодно программисту. При каждом получении «нового» участка памяти под массив программист должен осознавать, что произойдёт со «старым» участком, который был выделен под массив ранее.
Пример 1.
double[] mD2, mD3; mD2 = new double[7]; mD3 = mD2; mD2 = new double[8];
«Новый» участок памяти для 8 элементов предоставлен массиву mD2. «Старый» участок, содержащий 7 элементов, передан под управление переменной mD3. Оператор mD3 = mD2 не означает, что элементы массива mD2 копируются в массив mD3. Он означает, что ссылка на данные mD3, перед тем указывающая «никуда», отныне ссылается на элементы массива mD2.
Пример 2.
double[] mD2; mD2 = new double[7]; … mD2 = new double[8];
«Новый» участок памяти для 8 элементов управляется переменной mD2. Ссылка на «старый» участок, содержащий 7 элементов, для программиста потеряна, а сам участок не может быть использован для размещения других переменных. К счастью, в C# есть сборщик мусора, он эту ссылку хранит до момента, когда сочтёт нужным высвободить память.
Примеры обращения к отдельным элементам массивов:
mD1[6] = 2.2; mB2[3,4] = false; mB3[1,1,1] = 3;
Объявление массива и предоставление ему памяти можно совместить в одном операторе. Примеры:
nD1 = 7; int[] mD1 = new double[nD1];
В одном операторе можно совместить и объявление, и размещение в памяти, и заполнение (инициализацию) массива или нескольких массивов. Примеры:
double[] mD1, mE1, mF1 = new double[] {2.1, 4.2, 6.3, 8.4};
int[,] mI2 = new int[,] { {111,112}, {121,122}};
int[,,] mI3 = new int[,,] {{{111,112},{121,122}},{{211,212},{221,222}}};
В C# предусмотрена работа с т.н. зазубренными массивами (Jagged arrays). В простейшем случае, двумерные зазубренные массивы можно уподобить двумерным таблицам с непостоянным числом элементов в строках. Чаще для таких массивов используется название «массив массивов».
Пример 3. Создать «нижнюю треугольную матрицу» из N строк. Число элементов строки равно её номеру плюс единица (нумерация начинается с нуля).
Сначала создаётся массив ссылок на строки:
int[][] JaggedArray = new int[N][];
Затем проявляется отдельная забота об элементах каждой строки:
for (int i=0; i<N; i++) JaggedArray[i] = new int[i+1];
Пример 4. Переразместить в памяти «нижнюю треугольную матрицу» из M строк так, чтобы «старое» содержимое матрицы не было потеряно.
Сначала изменяется размер массива, содержащего ссылки на строки:
System.Array.Resize(ref JaggedArray, M);
Теперь массив JaggedArray наверняка занимает в памяти иное место, чем прежде. Независимо от того, что больше, N или M, все «старые» (по своим номерам) элементы массива ссылаются на те же элементы строк, что и раньше. Если M<N, ссылки на «лишние» строки для программиста теряются. Далее ими пользуется сборщик мусора.
Затем в памяти «доразмещаются» строки матрицы, если их стало больше:
for (int i=N; i<M; i++) JaggedArray[i] = new int[i+1];