
0 2
0 0 3
0 0 0 4
tre.Length = 4
tre.Rank = 1
В программе объявлена ссылка tre на массив массивов. Операцией new определён массив из size элементов - ссылок на массивы. Каждый элемент tre[j] – ссылка на ещё не существующий одномерный массив с элементами типа int. Эти массивы - реальные строки треугольной матрицы -формируются в цикле. Длина j-ro массива равна j+1.
В цикле печати массива для определения числа элементов используется свойство Length. Выражение tre.Length возвращает число строк матрицы. Обратите внимание, что в отличие от многомерного массива свойство Length равно числу элементов только "верхнего" уровня массива массивов. tri[j].Length позволяет определить длину j-й строки. Свойство Rank, относящееся к объекту типа int[][], равно 1, т.к. это одномерный массив ссылок на массивы. Остальное очевидно из результатов выполнения программы.
Вводя ссылку на массив и объявляя конкретный объект - экземпляр массива, программист каждый раз определяет некоторый тип именно таких массивов, которые ему нужны. Синтаксис объявления этих типов мы уже разобрали и объяснили с помощью примеров. Следует обратить внимание, что имена этих типов массивов и синтаксис определения типов массивов не похожи на те конструкции, которые применяются для определения пользовательских классов как таковых (вводимых с помощью служебного слова class). Однако каждый декларируемый в программе тип массивов является настоящим классом и создаётся как производный (как наследник) системного класса Array. Будучи наследником, каждый тип массивов получает или по-своему реализует методы и свойства класса Array. Следующая программа иллюстрирует возможности некоторых методов, о которых мы ещё не говорили.
// 07_07.cs массивы - методы и свойства класса Array
static void Main()
{
double[,] ar = {
{ 10, -7, 0, 7},
{ -3, 2.099, 6, 3.901},
{ 5, -1, 5, 6},
};
Console.WriteLine("ar.Rank = " + ar.Rank);
Console.WriteLine("ar.GetUpperBound(1) = " + ar.GetUpperBound(1));
Console.WriteLine("ar.GetLength(1) = " + ar.GetLength(1));
for (int i = 0; i < ar.GetLength(0); i++, Console.WriteLine())
for (int j = 0; j <= ar.GetUpperBound(1); j++)
Console.Write("\t"+ ar[i, j]);
}
Результат выполнения программы: ar.Rank = 2
ar.GetUpperBound(1) = 3 ar.GetLength(1) = 4 10 -7 0 7
3 2,099 6 3,901
5 -1 5 6
В программе определён и инициализирован двумерный массив с элементами типа double. Результаты выполнения программы поясняют особенности свойств и методов типа массивов, производного от класса Array. Обратите внимание, что GetUpperBound(1) - верхняя граница второго индекса, а не количество его значений.
7.5. Массивы массивов и поверхностное копирование
Итак, массив массивов представляет собой одномерный массив, элементами которого являются ссылки на массивы следующего (подчинённого) уровня. Этот факт требует особого внимания, так как затрагивает фундаментальные вопросы копирования ссылок и тех объектов, которые ими адресуются.
Независимо от того, какого вида массив мы рассматриваем, присваивание ссылке на массив значения другой ссылки на уже существующий массив (на объект с типом массива) приводит к появлению двух ссылок на один массив. Это мы уже иллюстрировали и разобрали.
Метод Clone() позволяет создать новый экземпляр массива. В программе 07_04.cs показано, что изменяя один из одномерных массивов-копий мы не изменяем второй. Следующая программа иллюстрирует применение копирования к многомерному массиву:
// 07_08.cs - двумерный массив - полное клонирование
static void Main()
{
int size;
do
Console.Write("size = ");
while (!int.TryParse(Console.ReadLine(), out size) || size < 1);
int[,] one = new int[size, size];
Console.WriteLine("Maccив one:");
for (int i = 0; i < size; i++, Console.WriteLine())
for (int j = 0; j < size; j++)
{
if (i == j)
one[i, j] = 1;
Console.Write(one[i, j] + "\t");
}
Console.WriteLine("one. Length = " + one.Length);
int[,] two = (int[,])one.Clone(); // клонирование
two[0, 0] = -size;
Console.WriteLine("Maccив two:");
for (int i = 0; i < size; i++, Console.WriteLine())
for (int j = 0; j < size; j++)
Console.Write(two[i, j] + "\t");
Console.WriteLine("Maccив one:");
for (int i = 0; i < size; i++, Console.WriteLine())
for (int j = 0; j < size; j++)
Console.Write(one[i, j] + "\t");
}
Результат выполнения программы: size = 4<ENTER> Массив one:
1 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
one. |
Length |
= 16 |
|
Массив two |
t |
| |
-4 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
Массив one |
; |
| |
1 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
В программе определена ссылка two типа int[,] и ей присвоен результат копирования массива, связанного со ссылкой one, имеющей тот же тип int[,]. Выведена единичная матрица, адресованная ссылкой one, затем изменён обычным присваиванием один элемент массива-копии:
two[0,0]=-size;
Выведенные на консоль значения элементов массивов иллюстрируют независимость массива-оригинала от массива-копии.
Программы 07_04.cs и 07_08.cs работают с массивами, у которых по одному спецификатору размерности. В первом случае массив одномерный, во второй программе клонируется двумерный массив. Применяя метод Clone() к массиву массивов, мы сталкиваемся с очень важной особенностью. Строго говоря, действия метода остаются прежними - он создаёт массив-копию и присваивает его элементам значения элементов массива-оригинала. Однако в этом случае копирования тех подчинённых массивов, на которые "смотрят" ссылки-элементы массива-оригинала, не происходит. Выполняется так называемое поверхностное или поразрядное копирование. Иначе и быть не должно - "не знает" метод Clone(), что код, который является значением элемента массива, представляет собой ссылку и по этой ссылке нужно ещё что-то "доставать".
Таким образом, копируя с помощью метода Clone() массив массивов, мы получаем два экземпляра массива верхнего уровня, элементы которых адресуют одни и те же участки памяти, выделенные для подчинённых массивов объекта-оригинала.
В качестве иллюстрации указанной ситуации приведём следующую программу, построенную на основе 07_06.cs:
// 07_09.cs - непрямоугольный массив - клонирование поверхностное!
static void Main09()
{
int size;
do
Console.Write("size = ");
while (!int.TryParse(Console.ReadLine(), out size) || size<1);
int[][] tre = new int[size][];
for (int j = 0; j < size; j++)
{
tre[j] = new int[j + 1];
tre[j][j]=j + 1;
}
Console.WriteLine("Массив tre:");
for (int i = 0; i < tre.Length; i++, Console.WriteLine())
for (int j = 0; j < tre[i].Length; j++)
Console. Write(tre[i][j] + "\t");
Console.WriteLine("tre.Length = " + tre.Length);
int[][] two = (int[][])tre.Clone();
two[0][0] = - size;
Console.WriteLine("Массив two:");
for (int i = 0; i < two.Length; i++, Console.WriteLine())
for (int j = 0; j < two[i].Length; j++)
Console.Write(two[i][j] + "\t");
Console.WriteLine("Массив tre:");
for (int i = 0; i < tre.Length; i++, Console.WriteLine())
for (int j = 0; j < tre[i].Length; j++)
Console.Write(tre[i][j] + "\t");
}
Результат выполнения программы:
size = 4<ENTER>
Массив tre:
1
0 2
0 0 3
0 0 0 4
tre.Length = 4
Массив two:
-4
0 2
0 0 3
0 0 0 4
Массив tre:
-4
0 2
0 0 3
0 0 0 4
В программе определена ссылка two типа int[][] и ей присвоен результат копирования (клонирования) "треугольного" массива, адресованного ссылкой tre, имеющей тип int[][]. С помощью оператора two[0][0] = - size; изменён один целочисленный элемент "нижнего уровня" массива массивов. После присваивания изменилось значение, соответствующее выражению tre[0][0].