
CSBasicCourse2ndedPodbelsky / CSBasicCourse2ndedPodbelsky
.pdfмассивов, то есть двумерные (матрицы), трехмерные («параллелепипеды») и т.д.
Именно такие массивы традиционно принято называть многомерными массивами.
Примеры:
int [,] dots; |
//ссылка на двумерный массив |
byte [,,] bits; |
//ссылка на трёхмерный массив |
Для определения объекта – конкретного экземпляра типа многомерных массивов используется выражение с операцией new
new тип_не_массива [d1,d2, d3 …] инициализатор
Здесь di – размер - количество элементов по соответствующему измерению.
Инициализатор представляет собой вложение конструкций
{список_инициализаторов}.
Элементами такого списка в свою очередь служат заключенные в фигурные скобки списки инициализаторов. Глубина вложения соответствует размерности массива. Размерность массива "массив:размерность массива" (его ранг) можно получить с помощью нестатического свойства Rank.
Пример определения с использованием инициализации матрицы (двумерного массива) с размерами 4 на 2:
int [,] matr = new int[4,2] {{1,2},{3,4},{5,6},{7,8}};
Как и для одномерных массивов при наличии инициализатора конструкцию new int[4,2] можно опустить.
Так как размеры объектов-массивов задаются выражениями, в которые могут входить переменные, то допустимы многомерные массивы с динамически определяемыми размерами. В качестве примера рассмотрим программу,
формирующую единичную матрицу, размеры которой вводит пользователь с
клавиатуры:
// 07_05.cs - двумерный массив - единичная матрица using System;
class Program
{
static void Main()
{
int size;
do Console.Write("size = ");
while (!int.TryParse(Console.ReadLine(), out size) || size < 1); int[,] one = new int[size, size];
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");
|
|
} |
|
|
|
foreach (int mem in one) |
|||
|
|
Console.Write(mem+" "); |
||
|
Console.WriteLine(); |
|||
|
Console.WriteLine("one.Length = " + one.Length); |
|||
|
Console.WriteLine("one.Rank = " + one.Rank); |
|||
} |
} |
|
|
|
Результат выполнения программы: |
||||
|
||||
size = 4<ENTER> |
||||
1 |
0 |
0 |
0 |
|
0 |
1 |
0 |
0 |
|
0 |
0 |
1 |
0 |
|
0 |
0 |
0 |
1 |
|
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 |
||||
one.Length = 16 |
||||
one.Rank = 2 |
|
|||
|
В программе формируется двумерный массив (экземпляр массивов типа int[,]), |
размеры которого определены переменной size. Значение size вводит пользователь.
По умолчанию все элементы массива получают нулевое значение. Во вложенных
циклах диагональным элементам присвоены единичные значения, и выводятся |
|
|
значения всех элементов. Обратите внимание, как в заголовке внешнего цикла |
for |
|
используется обращение к методу Console.WriteLine( ) для перехода на новую |
||
строку при выводе. Далее иллюстрируется применение цикла |
foreach к |
|
многомерному массиву. Перебор значений массива, представляющего матрицу, |
||
выполняется по строкам. (Быстрее изменяется правый индекс.) |
|
|
В конце программы выведено значение свойства one.Length |
– это |
общее |
количество элементов в массиве и значение ранга массива "массив:ранг массива" –
ранг равен двум.
7.4. Массивы массивов и непрямоугольные массивы
Как следует из синтаксического определения, при объявлении типа массива
можно задавать несколько спецификаторов размерностей. В стандарте приведён пример такого типа массивов (с тремя спецификаторами размерностей):
int [ ] [,,][,] – одномерный массив, элементы которого - трёхмерные массивы,
каждый с элементами типа "двумерный массив с элементами типа |
int". Такой |
||
массив можно рассматривать как массив массивов. |
|
||
Так как размеры массивов, входящих как элементы в другой массив, могут |
|||
быть разными, то массив массивов |
"массив:массив массивов" в общем случае не |
||
является |
"прямоугольным" |
. Такие непрямоугольные массивы |
|
"массив:непрямоугольные массивы" |
в стандарте C# названы |
jagged array |
(зубчатые массивы). Пример из стандарта:
int[ ][ ] j2 = new int[3][ ]; j2[0] = new int[ ] { 1, 2, 3 };
j2[1] = new int[ ] { 1, 2, 3, 4, 5, 6 };
j2[2] = new int[ ] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Обратите внимание, что с помощью одной операции new невозможно создать массив массивов. В примере j2 – ссылка на объект типа массив массивов с элементами типа int. Выражение new int[3][ ] создаёт объект-массив с тремя элементами типа "ссылка на одномерный массив с элементами типа int". Каждая из этих ссылок доступна с помощью соответствующего выражения j2[0], j2[1], j2[2].
Однако, вначале значения этих ссылок не определены. Только присвоив каждой из них результат выражения new int[ ] инициализатор, мы связываем ссылки с
конкретными одномерными массивами, память для которых за счёт выполнения операции new будет выделена в куче.
Приведённые четыре объявления можно заменить одним, используя правила
инициализации (количество операций new при этом не изменится): int[ ][ ] j3 = new int[3][ ] {
new int[ ] { 1, 2, 3 },
new int[ ] { 1, 2, 3, 4, 5, 6 },
new int[ ] { 1, 2, 3, 4, 5, 6, 7, 8, 9} };
При объявлении массива массивов как и для других видов массивов можно не применять инициализаторы, но тогда придётся явно указать размеры массивов, и их элементы получат умалчиваемые значения.
Применяя инициализатор, разрешено опускать для массива массивов
выражение с операцией new, например, таким образом: int[ ][ ] j4 = {
new int[3], new int[6], new int[9] };
В этом объявлении размер массива верхнего уровня задан списком |
|
инициализаторов. Размеры массивов нижнего уровня с элементами типа |
int здесь |
заданы явно, и значения элементов определяются по умолчанию как нулевые.
В качестве примера с непрямоугольным (зубчатым) массивом рассмотрим
следующую программу, формирующую нижнюю нулевую треугольную матрицу.
Элементам её диагонали присвоим номера строк (нумерацию будем выполнять,
начиная от 1). Как в примере с единичной матрицей размер матрицы (число её
строк) будет вводить пользователь, как значение переменной |
int size. Текст |
программы: |
|
// 07_06.cs - нижняя треугольная матрица |
|
using System; |
|
class Program |
|
{ |
|
static void Main() |
|
{ |
|
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;
}
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); Console.WriteLine("tre.Rank = " + tre.Rank);
}
}
Результат выполнения программы: size = 4<ENTER>
1 |
2 |
|
|
0 |
3 |
|
|
0 |
0 |
4 |
|
0 |
0 |
0 |
tre.Length = 4 tre.Rank = 1
В программе объявлена ссылка tre на массив массивов. Операцией new
определён массив из size элементов – ссылок на массивы. Каждый элемент tre[j] –
ссылка на ещё не существующий одномерный массив с элементами типа int. Эти
массивы – реальные строки треугольной матрицы – формируются в цикле. Длина j–
го массива равна j+1.
В цикле печати массива для определения числа элементов используется свойство Length. Выражение tre.Length возвращает число строк матрицы. Обратите внимание, что в отличие от многомерного массива свойство Length равно числу элементов только "верхнего" уровня массива массивов. tri[j].Length позволяет определить длину j-й строки. Свойство Rank, относящееся к объекту типа int[ ][ ],
равно 1, т.к. это одномерный массив ссылок на массивы "массив:массив ссылок на массивы" . Остальное очевидно из результатов выполнения программы.
Вводя ссылку на массив и объявляя конкретный объект – экземпляр массива,
программист каждый раз определяет некоторый тип именно таких массивов,
которые ему нужны. Синтаксис объявления этих типов мы уже разобрали и
объяснили с помощью примеров. Следует обратить внимание, что имена этих типов массивов и синтаксис определения типов массивов не похожи на те конструкции,
которые применяются для определения пользовательских классов как таковых
(вводимых с помощью служебного слова class). Однако, каждый декларируемый в программе тип массивов является настоящим классом и создаётся как производный
(как наследник) системного класса Array. Будучи наследником, каждый тип массивов получает или по-своему реализует методы и свойства класса Array.
Следующая программа иллюстрирует возможности некоторых методов, о которых
мы ещё не говорили.
// 07_07.cs – методы и свойства класса Array using System;
class Program
{
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. = " + 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 |
7 |
|
|
10 -7 |
0 |
3,901 |
|
3 |
2,099 |
6 |
|
5 |
-1 |
5 |
6 |
В программе определён и инициализирован двумерный массив с элементами
типа double. Результаты выполнения программы поясняют особенности свойств и
методов типа массивов, производного от класса Array. Обратите внимание, что
GetUpperBound(1) – верхняя граница второго индекса, а не количество значений
этого индекса.
7.5. Массивы массивов и поверхностное копирование
Итак, массив массивов "массив:массив массивов" представляет собой одномерный массив, элементами которого являются ссылки на массивы следующего (подчинённого) уровня. Этот факт требует особого внимания, так как затрагивает фундаментальные вопросы копирования ссылок "ссылка:копирование ссылок" и тех объектов, которые ими адресуются.
Независимо от того, какого вида массив мы рассматриваем, присваивание ссылке на массив значения другой ссылки на уже существующий массив (на объект с типом массива) приводит к появлению двух ссылок на один массив. Это мы уже иллюстрировали и разобрали.
Метод Clone() позволяет создать новый экземпляр массива. В программе
07_04.cs показано, что изменяя один из одномерных массивов-копий, мы не изменяем второй. Следующая программа иллюстрирует применение копирования к
многомерному массиву:
// 07_08.cs - двумерный массив - полное клонирование using System;
class Program
{
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("Массив 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("Массив 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("Массив 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: |
0 |
||
1 |
0 |
0 |
|
0 |
1 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
one.Length = 16 |
|||
Массив two: |
0 |
||
-4 |
0 |
0 |
|
0 |
1 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
Массив one: |
0 |
||
1 |
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 - непрямоугольный массив –
//клонирование поверхностное!
using System; class Program
{
static void Main( )
{
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 |
2 |
|
|
0 |
3 |
|
|
0 |
0 |
4 |
|
0 |
0 |
0 |
|
tre.Length = 4 |
|
||
Массив two: |
|
||
-4 |
2 |
|
|
0 |
3 |
|
|
0 |
0 |
4 |
|
0 |
0 |
0 |
|
Массив tre: |
|
||
-4 |
2 |
|
|
0 |
3 |
|
|
0 |
0 |
4 |
|
0 |
0 |
0 |
В программе определена ссылка two типа int[ ][ ] и ей присвоен результат копирования (клонирования) "треугольного" массива, адресованного ссылкой tre,
имеющей тип int[ ][ ]. С помощью оператора two[0][0] = - size;
изменён один целочисленный элемент "нижнего уровня" массива массивов. После присваивания изменилось значение, соответствующее выражению tre[0][0].