Массив – именованная структура данных, фиксированного размера, которая позволяет хранить, последовательность однотипных элементов, к которым можно обращаться с помощью индекса.
С# поддерживает следующие типы массивов:
- одномерные;
- ступенчатые или зубчатые;
- многомерные.
Одномерные массивы
Одномерный, или линейный массив – это конструкция фиксированной длины из набора элементов наперед заданного типа, доступ к которым осуществляется с использование одного индекса. Его можно рассматривать как вектор:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
Синтаксис объявления массива, похожий на создание переменной, только после типа указываются квадратные скобки:
тип[] имя_массива;
Пример объявления целочисленного массива:
int[] array;
В языке C#, сам массив имеет ссылочный тип данных, это значит, что после объявления переменной типа массив, выделяется память, только для хранения ссылки на экземпляр массива, при этом значение переменной равно null. Для создания экземпляра массива, и выделения памяти для хранения элементов, необходимо использовать оператор new.
Для создания массива используется следующий синтаксис:
тип[] имя_массива = new тип[длина_массива];
Присвоим, предварительно созданной, переменной массив из десяти элементов:
array = new int[10];
При этом все элементы массива будут иметь тип int и значения по умолчанию для этого типа данных - 0.
Массивы, подобно переменным, поддерживают инициализацию, для этого используется массив-литерал. В C#, можно инициализировать массив несколькими вариантами, которые равнозначны для компилятора:
int[] arr1 = new int[5] { 0, 2, 4, 6, 8 };
int[] arr2 = new int[] { 0, 2, 4, 6, 8 };
int[] arr3 = new[] { 0, 2, 4, 6, 8 };
var arr4 = new[] { 0, 2, 4, 6, 8 };
int[] arr5 = { 0, 2, 4, 6, 8 };
В первых трех вариантах, можно использовать var вместо int[].
Для доступа к элементам массива, используются индексы, при этом начальный индекс равен нулю, соответственно последний индекс на единицу меньше длины массива.
var a = new[] { 10, 20, 30 };
int t = a[2]; //t = 30, поскольку элемент с индексом 2 имеет значение 30
a[0] = 15; // 15, 20, 30
a[1] = 25; // 15, 25, 30
Длину одномерного массива, можно получить из поля Length.
Обычно для работы с массивами, используются циклы. Рассмотрим пример заполнения и вывода, на экран консоли, данных массива:
byte[] bytes = new byte[7];
//заполнение массива
for (byte i = 0; i < bytes.Length; i++)
{
bytes[i] = i;
}
//вывод значений элементов массива в консоль
//во время каждой итерации цикла foreach, переменная b принимает следующее значение из массива
foreach (var b in bytes)
{
Console.WriteLine(b);
}
Как было описано выше массив в C#, имеет ссылочный тип(reference type), не путать с типом элементов. Это значит, что в переменной с названием массива, хранится ссылка на экземпляр, а не сами данные. Это нужно учитывать при работе с массивами.
Рассмотрим ситуацию, когда нужно скопировать данные одного целочисленного массива в другой.
В случае с переменными примитивных типов данных, мы могли записать копирование следующим образом:
var k = 9;
var p = k; // k = 9; p = 9;
k = 7; // k = 7; p = 9;
p = 2; // k = 7; p = 2;
Если применить эту логику работы к массивам, то получим:
var a = new[] { 1, 2, 3 };
var b = a; //a == {1, 2, 3}; b == {1, 2, 3}
a[0] = 9; //a == {9, 2, 3}; b == {9, 2, 3}
b[2] = 8; //a == {9, 2, 8}; b == {9, 2, 8}
Во второй строке кода, мы присваиваем переменной b ссылку на те самые данные, на которые указывает переменная a, то есть по факту у нас есть один набор элементов, а на него ссылаются две переменные. Если не учитывать этот факт, то в итоге мы получим ошибки в данных, которые иногда трудно отследить.
Для создания физической копии массива, можно воспользоваться следующим синтаксисом:
int[] A = new[] { 10, 20, 30 };
int[] B = new int[A.Length];
for (int i = 0; i < B.Length; i++)
{
B[i] = A[i];
}
Кроме этого класс Array предоставляет нам возможность создать копию массива, вызовом метода Copy:
int[] a = { 11, 12, 13, 14 };
int[] b = new int[a.Length];
Array.Copy(a, b, a.Length);
Ступенчатый массив
Тип данных массива может быть любым, по этому мы можем создать массив массивов, разной размерности, именно такая структура носит название – ступенчатый или зубчатый массив.
Для создания ступенчатого массива используется такой синтаксис:
byte[][] arr = new byte[3][];
arr[0] = new byte[4] { 1, 2, 3, 4 }; //создаем 1-й подмассив
arr[1] = new byte[5] { 1, 2, 3, 4, 5 }; //создаем 2-й подмассив
arr[2] = new byte[8] { 1, 2, 3, 4, 5, 6, 7, 8 }; //создаем 3-й подмассив
Он имеет следующий вид:
1 | 2 | 3 | 4 | ||||
1 | 2 | 3 | 4 | 5 | |||
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
Для обращения к элементу, используются два индекса:
arr[1][2] = 0;
Console.WriteLine(arr[0][3]);
Используя вложенные циклы, можно осуществлять обход всех элементов зубчатого массива:
char[][] symbolTable = new char[][]
{
new char[]{ 'a','b', 'c'},
new char[]{ 'f','g', 'h', 'j','k'},
new char[]{ 'w','x', 'y', 'z'}
};
foreach (var symbolRow in symbolTable)
{
foreach (var symbol in symbolRow)
{
Console.Write("{0}\t", symbol);
}
Console.WriteLine();
}
Многомерные массивы
Для многомерных массивов характерным есть ранг(rank), или количество измерений. Двумерный массив – это таблица, трехмерный – куб…
string[,] table = new string[4, 5]; //двухмерный массив
string[,,] cube = new string[3, 3, 3]; //трехмерный массив
Поскольку на практике мало используются массивы размерностью больше двух, дальше мы будем рассматривать только двухмерные массивы.
Создание двумерного массива можно записать следующим образом(все варианты равнозначны)::
int[,] table1 = new int[3, 3] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
int[,] table2 = new int[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
int[,] table3 = new [,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
int[,] table4 = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
В первых трех вариантах, вместо int[,] можно использовать var.
Каждый из созданных двумерных массивов имеет вид:
1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 |
Доступ к элементам осуществляется посредством двух индексов:
int[,] tbl = new int[2, 3];
tbl[0, 0] = 10;
tbl[1, 1] = 20;
Цикл foreach может обходить все элементы двумерного массива, а использование других циклов немного усложняется, из за того, что поле Length содержит в себе общую длину массива.
Для получения количества строк и столбцов, используют метод GetUpperBound, который возвращает значение последнего индекса, для переданного в качестве аргумента, измерения. Поскольку как результат мы получаем индекс, для получения длину, значение нужно увеличить на единицу:
- Количество строк – GetUpperBound(0) + 1;
- Количество столбцов – GetUpperBound(1) + 1.
Рассмотрим пример заполнения и вывода значений двумерного массива:
var table = new int[3, 4];
//количество строк
var rowCount = table.GetUpperBound(0) + 1;
//количество столбцов
var columnCount = table.GetUpperBound(1) + 1;
//заполнение таблицы
for (int i = 0; i < rowCount; i++)
for (int j = 0; j < columnCount; j++)
table[i, j] = i + j;
//вывод элементов на экран
Console.WriteLine("Результат работы цикла foreach");
foreach (var element in table)
{
Console.Write("{0}\t", element);
}
Console.WriteLine("Вывод в виде таблицы");
var r = 0;
while (r < rowCount)
{
var c = 0;
while (c < columnCount)
{
Console.Write("{0}\t", table[r, c]);
c++;
}
r++;
Console.WriteLine();
}
Как видим, цикл foreach выводит все элементы двумерного массива.
Многомерные массивы могут быть подмассивами одномерных, или многомерных массивов, однако такие конструкции усложняют работу с данными, по этому практически не используются.
Итоги
К основным характеристикам массивов относятся:
- Размерность или ранг – количество индексов, с помощью которых обращаются к элементу(одномерные, двумерные, трехмерные…);
- Размер или длина массива – общее количество элементов в массиве;
- Размерность каждого измерения – длина отдельно взятого измерения массива(для двухмерного – количество строк и столбцов).
В большинстве случаев используются одномерные и двумерные массивы. Переменная массива, не зависимо от типа данных её элементов, имеет ссылочный тип, помните это при работе с массивами.