Тверской государственный университет
Опубликован: 22.11.2005 | Доступ: свободный | Студентов: 30405 / 1848 | Оценка: 4.31 / 3.69 | Длительность: 28:26:00
ISBN: 978-5-9556-0050-5
Лекция 11:

Массивы языка C#

< Лекция 10 || Лекция 11: 1234 || Лекция 12 >

Массивы массивов

Еще одним видом массивов C# являются массивы массивов, называемые также изрезанными массивами (jagged arrays). Такой массив массивов можно рассматривать как одномерный массив, элементы которого являются массивами, элементы которых, в свою очередь, снова могут быть массивами, и так может продолжаться до некоторого уровня вложенности.

В каких ситуациях может возникать необходимость в таких структурах данных? Эти массивы могут применяться для представления деревьев, у которых узлы могут иметь произвольное число потомков. Таковым может быть, например, генеалогическое дерево. Вершины первого уровня - Fathers, представляющие отцов, могут задаваться одномерным массивом, так что Fathers[i] - это i -й отец. Вершины второго уровня представляются массивом массивов - Children, так что Children[i] - это массив детей i -го отца, а Children[i][j] - это j -й ребенок i -го отца. Для представления внуков понадобится третий уровень, так что GrandChildren [i][j][k] будет представлять к -го внука j -го ребенка i -го отца.

Есть некоторые особенности в объявлении и инициализации таких массивов. Если при объявлении типа многомерных массивов для указания размерности использовались запятые, то для изрезанных массивов применяется более ясная символика - совокупности пар квадратных скобок; например, int[][] задает массив, элементы которого - одномерные массивы элементов типа int.

Сложнее с созданием самих массивов и их инициализацией. Здесь нельзя вызвать конструктор new int[3][5], поскольку он не задает изрезанный массив. Фактически нужно вызывать конструктор для каждого массива на самом нижнем уровне. В этом и состоит сложность объявления таких массивов. Начну с формального примера:

//массив массивов - формальный пример
//объявление и инициализация
int[][] jagger = new int[3][]
{
	new int[] {5,7,9,11},
	new int[] {2,8},
	new int[] {6,12,4}
};

Массив jagger имеет всего два уровня. Можно считать, что у него три элемента, каждый из которых является массивом. Для каждого такого массива необходимо вызвать конструктор new, чтобы создать внутренний массив. В данном примере элементы внутренних массивов получают значение, будучи явно инициализированы константными массивами. Конечно, допустимо и такое объявление:

int[][] jagger1 = new int[3][]
{
	new int[4],
	new int[2],
	new int[3]
};

В этом случае элементы массива получат при инициализации нулевые значения. Реальную инициализацию нужно будет выполнять программным путем. Стоит заметить, что в конструкторе верхнего уровня константу 3 можно опустить и писать просто new int[][]. Самое забавное, что вызов этого конструктора можно вообще опустить - он будет подразумеваться:

int[][] jagger2 =
{
	new int[4],
	new int[2],
	new int[3]
};

А вот конструкторы нижнего уровня необходимы. Еще одно важное замечание - динамические массивы возможны и здесь. В общем случае, границы на любом уровне могут быть выражениями, зависящими от переменных. Более того, допустимо, чтобы массивы на нижнем уровне были многомерными. Но это уже "от лукавого" - вряд ли стоит пользоваться такими сложными структурами данных, ведь с ними предстоит еще и работать.

Приведу теперь чуть более реальный пример, описывающий простое генеалогическое дерево, которое условно назову "отцы и дети":

//массив массивов -"Отцы и дети"
int Fcount =3;
string[] Fathers = new string[Fcount];
Fathers[0] ="Николай"; Fathers[1] = "Сергей";
	Fathers[2] = "Петр";
string[][] Children = new string[Fcount][];
Children[0] = new string[] {"Ольга", "Федор"};
Children[1] = new string[]
	{"Сергей","Валентина","Ира","Дмитрий"};
Children[2] = new string[]{"Мария","Ирина","Надежда"};
myar.PrintAr3(Fathers,Children);

Здесь отцов описывает обычный динамический одномерный массив Fathers. Для описания детей этих отцов необходим уже массив массивов, который также является динамическим на верхнем уровне, поскольку число его элементов совпадает с числом элементов массива Fathers. Здесь показан еще один способ создания таких массивов. Вначале конструируется массив верхнего уровня, содержащий ссылки со значением void. А затем на нижнем уровне конструктор создает настоящие массивы в динамической памяти, с которыми и связываются ссылки.

Я не буду демонстрировать работу с генеалогическим деревом, ограничусь лишь печатью этого массива. Здесь есть несколько поучительных моментов. В классе Arrs для печати массива создан специальный метод PrintAr3, которому в качестве аргументов передаются массивы Fathers и Children. Вот текст данной процедуры:

public void PrintAr3(string [] Fathers, string[][] Children)
{
	for(int i = 0; i < Fathers.Length; i++)
	{
		Console.WriteLine("Отец : {0}; Его дети:", Fathers[i]);
		for(int j = 0; j < Children[i].Length; j++)
			Console.Write( Children[i][j] + " ");
		Console.WriteLine();
	}
}//PrintAr3

Приведу некоторые комментарии к этой процедуре:

  • Внешний цикл по i организован по числу элементов массива Fathers. Заметьте, здесь используется свойство Length, в отличие от ранее применяемого метода GetLength.
  • В этом цикле с тем же успехом можно было бы использовать и имя массива Children. Свойство Length для него возвращает число элементов верхнего уровня, совпадающее, как уже говорилось, с числом элементов массива Fathers.
  • Во внутреннем цикле свойство Length вызывается для каждого элемента Children[i], который является массивом.
  • Остальные детали, надеюсь, понятны.

Приведу вывод, полученный в результате работы процедуры PrintAr3.

Дерево "Отцы и дети"

Рис. 11.3. Дерево "Отцы и дети"

Процедуры и массивы

В наших примерах массивы неоднократно передавались процедурам в качестве входных аргументов и возвращались в качестве результатов.

В лекции 9 подробно описывались особенности передачи аргументов в процедуру. Остается подчеркнуть только некоторые детали:

  • В процедуру достаточно передавать только сам объект - массив. Все его характеристики ( размерность, границы) можно определить, используя свойства и методы этого объекта.
  • Когда массив является выходным аргументом процедуры, как аргумент C в процедуре MultMatr, выходной аргумент совсем не обязательно снабжать ключевым словом ref или out (хотя и допустимо). Передача аргумента по значению в таких ситуациях так же хороша, как и передача по ссылке. В результате вычислений меняется сам массив в динамической памяти, а ссылка на него остается постоянной. Процедура и ее вызов без ключевых слов выглядит проще, поэтому обычно они опускаются. Заметьте, в процедуре GetSizes, где определялись границы массива, ключевое слово out, сопровождающее аргументы, совершенно необходимо.
  • Может ли процедура-функция возвращать массив в качестве результата? В C# ответ на этот вопрос положителен. В следующей лекции будет приведен пример подобной функции.
< Лекция 10 || Лекция 11: 1234 || Лекция 12 >
Александр Галабудник
Александр Галабудник

Не обнаружил проекты, которые используются в примерах в лекции, также не увидел список задач.

Александра Гусева
Александра Гусева