Меню сайта
Статистика
Онлайн всего: 1 Гостей: 1 Пользователей: 0 |
Лекция 12.СодержаниеКласс ArrayНельзя понять многие детали работы с массивами в C#, если не знать устройство класса Array из библиотеки FCL, потомками которого являются все классы-массивы. Рассмотрим следующие объявления: //Класс Array int[] ar1 = new int[5]; double[] ar2 ={5.5, 6.6, 7.7}; int[,] ar3 = new Int32[3,4]; Зададимся естественным вопросом: к какому или к каким классам принадлежат объекты ar1, ar2 и ar3? Ответ прост: все они принадлежат к разным классам. Переменная ar1 принадлежит к классу int[] - одномерному массиву значений типа int, ar2 - double[] - одномерному массиву значений типа double, ar3 - двумерному массиву значений типа int. Следующий закономерный вопрос: а что общего есть у этих трех объектов? Прежде всего, все три класса этих объектов, как и другие классы, являются потомками класса Object, а потому имеют общие методы, наследованные от класса Object и доступные объектам этих классов. У всех классов, являющихся массивами, много общего, поскольку все они являются потомками класса System.Array. Класс System.Array наследует ряд интерфейсов: ICloneable, IList, ICollection, IEnumerable, а, следовательно, обязан реализовать все их методы и свойства. Помимо наследования свойств и методов класса Object и вышеперечисленных интерфейсов, класс Array имеет довольно большое число собственных методов и свойств. Взгляните, как выглядит отношение наследования на семействе классов, определяющих массивы. Рис. 12.1. Отношение наследования на классах-массивах Благодаря такому мощному родителю, над массивами определены самые разнообразные операции - копирование, поиск, обращение, сортировка, получение различных характеристик. Массивы можно рассматривать как коллекции и устраивать циклы foreach для перебора всех элементов. Важно и то, что когда у семейства классов есть общий родитель, то можно иметь общие процедуры обработки различных потомков этого родителя. Для общих процедур работы с массивами характерно, что один или несколько формальных аргументов имеют родительский тип Array. Естественно, внутри такой процедуры может понадобиться анализ - какой реальный тип массива передан в процедуру. Рассмотрим пример подобной процедуры. Ранее я для печати элементов массива использовал различные процедуры PrintAr1, PrintAr2 и так далее, по одной для каждого класса массива. Теперь я приведу общую процедуру, формальный аргумент которой будет принадлежать родителю всех классов-массивов, что позволит передавать массив любого класса в качестве фактического аргумента: public static void PrintAr(string name, Array A) { Console.WriteLine(name); switch (A.Rank) { case 1: for(int i = 0; i<A.GetLength(0);i++) Console.Write("\t" + name + "[{0}]={1}", i, A.GetValue(i)); Console.WriteLine(); break; case 2: for(int i = 0; i<A.GetLength(0);i++) { for(int j = 0; j<A.GetLength(1);j++) Console.Write("\t" + name + "[{0},{1}]={2}", i,j, A.GetValue(i,j)); Console.WriteLine(); } break; default: break; } }//PrintAr Вот как выглядит создание массивов и вызов процедуры печати: public void TestCommonPrint() { //Класс Array int[] ar1 = new int[5]; double[] ar2 ={5.5, 6.6, 7.7}; int[,] ar3 = new Int32[3,4]; Arrs.CreateOneDimAr(ar1);Arrs.PrintAr("ar1", ar1); Arrs.PrintAr("ar2", ar2); Arrs.CreateTwoDimAr(ar3);Arrs.PrintAr("ar3", ar3); }//TestCommonPrint Вот результаты вывода массивов ar1, ar2 и ar3. Рис. 12.2. Печать массивов. Результаты работы процедуры PrintAr Приведу некоторые комментарии. Первое, на что следует обратить внимание: формальный аргумент процедуры принадлежит базовому классу Array, наследниками которого являются все массивы в CLR и, естественно, все массивы C#. Для того чтобы сохранить возможность работы с индексами, как в одномерном, так и в двумерном случае, пришлось организовать разбор случаев. Свойство Rank, возвращающее размерность массива, используется в этом разборе. К элементам массива A, имеющего класс Array, нет возможности прямого доступа в обычной манере - A [<индексы>], но зато есть специальные методы GetValue (<индексы>) и SetValue (<индексы>). Естественно, разбор случаев можно продолжить, придав процедуре большую функциональность. Заметьте, если разбор случаев вообще не делать, а использовать PrintAr только для печати одномерных массивов, то она будет столь же проста, как и процедура PrintAr1, но сможет печатать любые одномерные массивы, независимо от типа их элементов. Массивы как коллекцииВ ряде задач массивы C# целесообразно рассматривать как коллекции, не используя систему индексов для поиска элементов. Это, например, задачи, требующие однократного или многократного прохода по всему массиву - нахождение суммы элементов, нахождение максимального элемента, печать элементов. В таких задачах вместо циклов типа For по каждому измерению достаточно рассмотреть единый цикл For Each по всей коллекции. Эта возможность обеспечивается тем, что класс Array наследует интерфейс IEnumerable. Обратите внимание, этот интерфейс обеспечивает только возможность чтения элементов коллекции (массива), не допуская их изменения. Применим эту стратегию и построим еще одну версию процедуры печати. Эта версия будет самой короткой и самой универсальной, поскольку подходит для печати массива, независимо от его размерности и типа элементов. Вот ее код: public static void PrintCollection(string name,Array A) { Console.WriteLine(name); foreach (object item in A ) Console.Write("\t {0}", item); Console.WriteLine(); }//PrintCollection Конечно, за все нужно платить. Платой за универсальность процедуры печати является то, что многомерный массив печатается как одномерный без разделения элементов на строки. К сожалению, ситуация с чтением и записью элементов массива не симметрична. Приведу вариант процедуры CreateCollection: public static void CreateCollection(Array A) { int i=0; foreach (object item in A ) //item = rnd.Next(1,10); //item read only A.SetValue(rnd.Next(1,10), i++); }//CreateCollection Заметьте, эту процедуру сделать универсальной не удается, поскольку невозможно модифицировать элементы коллекции. Поэтому цикл For Each здесь ничего не дает, и разумнее использовать обычный цикл. Данная процедура не универсальна и позволяет создавать элементы только для одномерных массивов. Сортировка и поиск. Статические методы класса ArrayСтатические методы класса Array позволяют решать самые разнообразные задачи:
Все методы перегружены и имеют ряд модификаций. Большинство из этих методов применимо только к одномерным массивам. Приведу примеры различных операций, доступных при работе с массивами, благодаря наследованию от класса Array: public void TestCollection() { //операции над массивами int nc = 7; int[] col1 = new int[nc], col2 = new int[nc]; double[] col3 = new double[nc]; int[,] col4 = new int[2,2]; Arrs.CreateCollection(col1); Arrs.PrintCollection("col1",col1); Arrs.CreateCollection(col2); Arrs.PrintCollection("col2",col2); Arrs.CreateCollection(col3); Arrs.PrintCollection("col3",col3); Arrs.CreateTwoDimAr(col4); Arrs.PrintCollection("col4",col4); //сортировка, поиск, копирование // поиск элемента int first = Array.IndexOf(col1, 2); int last = Array.LastIndexOf(col1,2); if (first == -1) Console.WriteLine("Нет вхождений 2 в массив col1"); else if (first ==last) Console.WriteLine("Одно вхождение 2 в массив col1"); else Console.WriteLine("Несколько вхождений 2 в массив col1"); //first = Array.IndexOf(col4, 4); //только одномерный массив Array.Reverse(col1); Console.WriteLine("Обращение массива col1:"); Arrs.PrintCollection("col1",col1); //Копирование Array.Copy(col1, col3, col1.Length); Console.WriteLine(" Массив col3 после копирования массива col1:"); Arrs.PrintCollection("col3",col3); Array.Copy(col1,1,col2,1,2); Console.WriteLine("копирование двух элементов col1 в col2:"); Arrs.PrintCollection("col1",col1); Arrs.PrintCollection("col2",col2); //быстрая сортировка Хоара Array.Sort(col1); Console.WriteLine("Отсортированный массив col1:"); Arrs.PrintCollection("col1",col1); first = Array.BinarySearch(col1, 2); Console.WriteLine("Индекс вхождения 2 в col1: {0}",first); //Создание экземпляра (массива) Array my2Dar = Array.CreateInstance(typeof(double), 2,3); Arrs.PrintCollection("my2Dar",my2Dar); //клонирование my2Dar = (Array)col4.Clone(); Console.WriteLine("Массив my2Dar после клонирования col4:"); Arrs.PrintCollection("my2Dar",my2Dar); //копирование CopyTo col1.CopyTo(col2,0); Console.WriteLine("Массив col2 после копирования col1:"); Arrs.PrintCollection("col2",col2); } В этой процедуре продемонстрированы вызовы различных статических методов класса Array. Для метода Copy показан вызов двух реализаций этого метода, когда копируется весь массив и часть массива. Закомментированный оператор вызова метода IndexOf напоминает о невозможности использования методов поиска при работе с многомерными массивами. Приведу результаты вывода, порожденные этим кодом. Рис. 12.3. Результаты применения статических методов класса Array
Сводка свойств и методов класса ArrayМногие возможности, которыми можно пользоваться при работе с массивами, уже обсуждены. В завершение этой темы в таблицах 12.1-12.3 приведем сводку всех свойств и методов класса Array.
Класс Object и массивыДавайте обсудим допустимость преобразований между классами-массивами и классом Object. Понятно, что существует неявное преобразование объекта любого класса в объект класса Object, так что переменной типа оbject всегда можно присвоить переменную типа массив. Обратное такое преобразование также существует, но оно должно быть явным. Как всегда, при проведении явных преобразований не гарантируется успешность их выполнения. В этой лекции и ранее обсуждался вопрос о создании универсальных процедур, которые могли бы работать с данными разных типов. Серьезный разговор об универсализации классов еще предстоит, сейчас же лишь напомню, что уже рассматривался такой прием, как перегрузка метода. У клиента, использующего перегруженный метод, создается впечатление, что он вызывает универсальный метод, работающий с аргументами разного типа. Создатель перегруженного метода должен, конечно, написать множество реализаций для поддержки такой универсальности. Другой уже обсуждавшийся прием состоит в том, что формальный аргумент метода принадлежит родительскому классу, тогда методу при вызове может быть передан аргумент любого из потомков. Приведу в качестве примера многострадальную процедуру печати объектов, многократные варианты которой уже были рассмотрены. На этот раз формальный аргумент процедуры будет иметь тип оbject - прародителя всех классов. Разберем, как можно выяснить, что в процедуру передается массив, как определить его тип и работать с ним уже как с массивом, а не как с переменной класса Object. Вот текст этой процедуры, названной PrintObject: public static void PrintObj(object A) { Console.WriteLine("A.GetType()={0})", A.GetType()); if (A.GetType()==typeof(System.Int32[])) { int[] temp; temp = (int[])A; for(int i = 0; i<temp.GetLength(0);i++) Console.Write("\t temp[{0}]={1}", i,temp[i]); Console.WriteLine(); } else Console.WriteLine("Аргумент не является массивом целых"); }//PrintObject Несколько замечаний к реализации. Метод GetType, примененный к аргументу, возвращает не тип Object, а реальный тип фактического аргумента. Поэтому можно проанализировать, какому классу принадлежит объект, переданный в процедуру. На каждой ветви разбора можно создать временный объект нужного типа и скопировать в него переданный аргумент. В данном примере рассматривается только одна ветвь, в которой создается целочисленный одномерный массив temp. Заметьте, при присваивании значения переменной temp выполняется явное преобразование из класса Object в класс int[]. При наличии переменной temp, выполнение нужных действий над массивом не представляет никаких трудностей. Приведу два примера вызова этой процедуры: //работа с процедурой PrintObject //Корректный и некорректный вызовы Arrs.PrintObj(col1); Arrs.PrintObj(col3); Вот какой вывод порождается этим фрагментом кода: Рис. 12.4. Результаты работы процедуры PrintObj |
Поиск
Друзья сайта
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||