Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ооп теория

.pdf
Скачиваний:
19
Добавлен:
14.02.2015
Размер:
3.58 Mб
Скачать

Здесь rnd - это статическое поле класса Arrs, объявленное следующим образом:

private static Random rnd = new Random();

Процедура печати массива с именем name выглядит так:

public static void PrintAr1(string name,int[] A)

{

Console.WriteLine(name);

for(int i = 0; i<A.GetLength(0);i++)

Console.Write("\t" + name + "[{0}]={1}", i, A[i]); Console.WriteLine();

}//PrintAr1

На рис. 11.1 показан консольный вывод результатов работы процедуры

TestDeclarations.

Рис. 11.1. Результаты объявления и создания массивов

Особое внимание обратите на вывод, связанный с массивами u и v.

ДИНАМИЧЕСКИЕ МАССИВЫ

Во всех вышеприведенных примерах объявлялись статические массивы,

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

независимо от того, каким выражением описывается граница,

рассматриваются как динамические, и память для них распределяется в

"куче". Полагаю, что это отражение разумной точки зрения: ведь статические массивы, скорее исключение, а правилом является использование

191

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

Чисто синтаксически нет существенной разницы в объявлении статических и динамических массивов. Выражение, задающее границу изменения индексов, в динамическом случае содержит переменные.

Единственное требование - значения переменных должны быть определены в момент объявления. Это ограничение в C# выполняется автоматически,

поскольку хорошо известно, сколь требовательно C# контролирует инициализацию переменных.

Приведу пример, в котором описана работа с динамическим массивом:

public void TestDynAr()

{

//объявление динамического массива A1 Console.WriteLine("Введите число элементов массива A1"); int size = int.Parse(Console.ReadLine());

int[] A1 = new int[size]; Arrs.CreateOneDimAr(A1); Arrs.PrintAr1("A1",A1);

}//TestDynAr

В особых комментариях эта процедура не нуждается. Здесь верхняя граница массива определяется пользователем.

МНОГОМЕРНЫЕ МАССИВЫ

Уже объяснялось, что разделение массивов на одномерные и многомерные носит исторический характер. Никакой принципиальной разницы между ними нет. Одномерные массивы -это частный случай многомерных. Можно говорить и по-другому: многомерные массивы являются естественным обобщением одномерных. Одномерные массивы позволяют задавать такие математические структуры как векторы, двумерные - матрицы, трехмерные -

кубы данных, массивы большей размерности - многомерные кубы данных.

192

Замечу, что при работе с базами данных многомерные кубы, так называемые кубы OLAP, встречаются сплошь и рядом.

В чем особенность объявления многомерного массива? Как в типе указать размерность массива? Это делается достаточно просто, за счет использования запятых. Вот как выглядит объявление многомерного массива в общем случае:

<тип>[, ... ,] <объявители>;

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

справедливо и для многомерных. Можно лишь отметить, что хотя явная инициализация с использованием многомерных константных массивов возможна, но применяется редко из-за громоздкости такой структуры. Проще инициализацию реализовать программно, но иногда она все же применяется.

Вот пример:

public void TestMultiArr()

{

int[,]matrix = {{1,2},{3,4}}; Arrs.PrintAr2("matrix", matrix);

}//TestMultiArr

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

третья - печатать сами матрицы. Вот тестовый пример:

public void TestMultiMatr()

{

int n1, m1, n2, m2,n3, m3; Arrs.GetSizes("MatrA",out n1,out m1); Arrs.GetSizes("MatrB",out n2,out m2); Arrs.GetSizes("MatrC",out n3,out m3);

int[,]MatrA = new int[n1,m1], MatrB = new int[n2,m2]; int[,]MatrC = new int[n3,m3]; Arrs.CreateTwoDimAr(MatrA);Arrs.CreateTwoDimAr(MatrB);

193

Arrs.MultMatr(MatrA, MatrB, MatrC); Arrs.PrintAr2("MatrA",MatrA); Arrs.PrintAr2("MatrB",MatrB); Arrs.PrintAr2("MatrC",MatrC);

}//TestMultiMatr

Три матрицы - MatrA, MatrB и MatrC - имеют произвольные размеры,

выясняемые в диалоге с пользователем, и использование для их описания динамических массивов представляется совершенно естественным. Метод

CreateTwoDimAr заполняет случайными числами элементы матрицы,

переданной ему в качестве аргумента, метод PrintAr2 выводит матрицу на печать. Я не буду приводить их код, похожий на код их одномерных аналогов.

Метод MultMatr выполняет умножение прямоугольных матриц. Это классическая задача из набора задач, решаемых на первом курсе. Вот текст этого метода:

public void MultMatr(int[,]A, int[,]B, int[,]C)

{

if (A.GetLength(1) != B.GetLength(0)) Console.WriteLine("MultMatr: ошибка размерности!");

else

for(int i = 0; i < A.GetLength(0); i++) for(int j = 0; j < B.GetLength(1); j++)

{

int s=0;

for(int k = 0; k < A.GetLength(1); k++) s+= A[i,k]*B[k,j];

C[i,j] = s;

}

}//MultMatr

В особых комментариях эта процедура не нуждается. Замечу лишь, что прежде чем проводить вычисления, производится проверка корректности размерностей исходных матриц при их перемножении, - число столбцов первой матрицы должно быть равно числу строк второй матрицы.

Обратите внимание, как выглядят результаты консольного вывода на данном этапе работы (рис. 11.2).

194

Рис. 11.2. Умножение матриц МАССИВЫ МАССИВОВ

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

и так может продолжаться до некоторого уровня вложенности.

В каких ситуациях может возникать необходимость в таких структурах данных? Эти массивы могут применяться для представления деревьев, у

которых узлы могут иметь произвольное число потомков. Таковым может быть, например, генеалогическое дерево. Вершины первого уровня - Fathers,

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

Fathers[i] - это i-й отец. Вершины второго уровня представляются массивом массивов - Children, так что Children[i] - это массив детей i-го отца, а

Children[i][j] - это j-й ребенок i-го отца. Для представления внуков понадобится третий уровень, так что GrandChildren [i][j][k] будет представлять к-го внука j-го ребенка i-го отца.

195

Есть некоторые особенности в объявлении и инициализации таких массивов.

Если при объявлении типа многомерных массивов для указания размерности использовались запятые, то для изрезанных массивов применяется более ясная символика - совокупности пар квадратных скобок; например, 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

196

можно опустить и писать просто 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. А затем на нижнем

197

уровне конструктор создает настоящие массивы в динамической памяти, с

которыми и связываются ссылки.

Я не буду демонстрировать работу с генеалогическим деревом, ограничусь лишь печатью этого массива. Здесь есть несколько поучительных моментов.

В классе 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.

198

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

ПРОЦЕДУРЫ И МАССИВЫ

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

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

В процедуру достаточно передавать только сам объект - массив. Все его характеристики (размерность, границы) можно определить,

используя свойства и методы этого объекта.

Когда массив является выходным аргументом процедуры, как аргумент

C в процедуре MultMatr, выходной аргумент совсем не обязательно снабжать ключевым словом ref или out (хотя и допустимо). Передача аргумента по значению в таких ситуациях так же хороша, как и передача по ссылке. В результате вычислений меняется сам массив в динамической памяти, а ссылка на него остается постоянной.

Процедура и ее вызов без ключевых слов выглядит проще, поэтому обычно они опускаются. Заметьте, в процедуре GetSizes, где определялись границы массива, ключевое слово out, сопровождающее аргументы, совершенно необходимо.

Может ли процедура-функция возвращать массив в качестве результата? В C# ответ на этот вопрос положителен. В следующей лекции будет приведен пример подобной функции.

199

200