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

ооп теория

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

 

ound,

 

границу по указанному измерению. Для

 

 

 

 

 

GetUpperBound

 

массивов нижняя граница всегда равна нулю.

 

 

 

 

 

 

 

 

 

 

GetValue,

 

Возвращает

или

устанавливает

 

 

 

 

 

SetValue

 

значение элемента массива с указанными

 

 

 

 

индексами.

 

 

 

 

 

 

 

 

 

 

 

 

 

Initialize

 

Может быть применен только к

 

 

 

 

 

 

 

массивам значимого типа. Инициализирует

 

 

 

 

 

 

 

 

элементы,

вызывая

соответствующий

 

 

 

 

конструктор. Как правило, не используется в

 

 

 

 

обычных программах.

 

 

 

 

КЛАСС Object И МАССИВЫ

 

 

 

 

Давайте обсудим допустимость преобразований между классами-массивами и классом Object. Понятно, что существует неявное преобразование объекта любого класса в объект класса Object, так что переменной типа оbject всегда можно присвоить переменную типа массив. Обратное такое преобразование также существует, но оно должно быть явным. Как всегда, при проведении явных преобразований не гарантируется успешность их выполнения.

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

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

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

Приведу в качестве примера многострадальную процедуру печати объектов,

многократные варианты которой уже были рассмотрены. На этот раз

211

формальный аргумент процедуры будет иметь тип о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

212

//Корректный и некорректный вызовы

Arrs.PrintObj(col1);

Arrs.PrintObj(col3);

Вот какой вывод порождается этим фрагментом кода:

Рис. 12.4. Результаты работы процедуры PrintObj Массивы объектов

Во всех рассмотренных примерах этой главы нам встречались массивы,

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

что компилятор не следит за инициализацией элементов массива и доверяет инициализации, выполненной конструктором массива по умолчанию. Но для массивов ссылочного типа инициализация по умолчанию присваивает ссылкам значение Null. Это означает, что создаются только ссылки, но не сами объекты. По этой причине, пока не будет проведена настоящая инициализация с созданием объектов и заданием ссылок на конкретные объекты, работать с массивом ссылочного типа будет невозможно.

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

///<summary>

///Класс победителей с именем и премией

///</summary>

public class Winners

213

{

//поля класса string name; int price;

//статическое или динамическое поле rnd?

//static Random rnd = new Random(); Random rnd = new Random();

// динамические методы

public void SetVals(string name)

{

this.name = name;

this.price = rnd.Next(5,10)* 1000; }//SetVals

public void PrintWinner(Winners win)

{

Console.WriteLine("Имя победителя: {0}," +

" его премия - {1}", win.name, win.price);

}//PrintWinner }//class Winners

Коротко прокомментирую этот текст.

1.Свойство name описывает имя победителя, а свойство price - величину его премии.

2.Свойство rnd необходимо при работе со случайными числами.

3.Метод SetVals выполняет инициализацию. Он присваивает полю name

значение, переданное в качестве аргумента, и полю price - случайное значение.

4.Метод PrintWinner - метод печати свойств класса. Без подобного метода не обходится ни один класс.

5.В классе появится еще один статический метод InitAr, но о нем скажу чуть позже.

Пусть теперь в одном из методов нашего тестирующего класса Testing

предполагается работа с классом Winners, начинающаяся с описания победителей. Естественно, задается массив, элементы которого имеют тип

Winners. Приведу начало тестирующего метода, в котором дано соответствующее объявление:

public void TestWinners()

{

//массивы объектов

214

int nwin = 3;

Winners[] wins = new Winners[nwin];

string[] winames = {"Т. Хоар", "Н. Вирт", "Э. Дейкстра"};

В результате создан массив wins, состоящий из объектов класса Winners. Что произойдет, если попытаться задать значения полей объектов, вызвав специально созданный для этих целей метод SetVals? Рассмотрим фрагмент кода, осуществляющий этот вызов:

//создание значений элементов массива

for(int i=0; i < wins.Length; i++) wins[i].SetVals(winames[i]);

На этапе выполнения будет сгенерировано исключение - нулевая ссылка.

Причина понятна: хотя массив wins и создан, но это массив ссылок,

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

И там необходим явный вызов конструктора для создания каждого массива на внутреннем уровне.

Как же создавать эти объекты? Конечно, можно возложить эту обязанность на пользователя, объявившего массив wins, - пусть он и создаст экземпляры для каждого элемента массива. Правильнее все-таки иметь в классе соответствующий метод. Метод должен быть статическим, чтобы его можно было вызывать еще до того, как созданы экземпляры класса, поскольку метод предназначен для создания этих самых экземпляров. Так в нашем классе появился статический метод InitAr:

//статический метод

public static Winners[] InitAr(Winners[] Winar)

{

for(int i=0; i < Winar.Length; i++) Winar[i] = new Winners();

return(Winar);

}//InitAr

215

Методу передается массив объектов, возможно, с нулевыми ссылками. Он возвращает тот же массив, но уже с явно определенными ссылками на реально созданные объекты. Теперь достаточно вызвать этот метод, после чего можно спокойно вызывать и метод SetVals. Вот как выглядит правильная последовательность вызовов методов класса Winners:

Winners.InitAr(wins);

//создание значений элементов массива for(int i=0; i < wins.Length; i++)

wins[i].SetVals(winames[i]); //печать значений элементов массива for(int i=0; i < wins.Length; i++)

wins[i].PrintWinner(wins[i]);

}//TestWinners

Теперь все корректно, массивы создаются, элементы заполняются нужными значениями, их можно распечатать:

Рис. 12.5. Печать элементов массива wins

Обратите внимание, что всем победителям назначена одна и та же премия.

Хотя понятно, что дело в программной ошибке, но в ней можно видеть и знак свыше. Коль скоро для победителей выбраны такие имена, почитаемые всеми программистами, то негоже пытаться расставить их по ранжиру даже в примере.

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

МАССИВЫ. СЕМАНТИКА ПРИСВАИВАНИЯ

Преобразования между классами массивов и родительскими классами Array

и Object уже рассматривались. А существуют ли другие преобразования

216

между классами массивов? Что происходит при присваивании x=e; (передаче аргументов в процедуру), если x и e - это массивы разных классов? Возможно ли присваивание? Ответ на этот вопрос положительный, хотя накладываются довольно жесткие ограничения на условия, когда такие преобразования допустимы. Известно, например, что между классами Int и Object

существуют взаимные преобразования - в одну сторону явное, в другую неявное. А вот между классами Int[] и Object[] нет ни явных, ни неявных преобразований. С другой стороны, такое преобразование существует между классами String[] и Object[]. В чем же тут дело, и где логика? Запомните,

главное ограничение на возможность таких преобразований состоит в том,

что элементы массивов должны иметь ссылочный тип. А теперь притянем сюда логику. Крайне желательно обеспечить возможность проведения преобразований между массивами, элементы которых принадлежат одному семейству классов, связанных отношением наследования. Такая возможность и была реализована. А вот для массивов с элементами значимых типов подобную же возможность не захотели или не смогли реализовать.

Сформулируем теперь точные правила, справедливые для присваивания и передачи аргументов в процедуру. Для того, чтобы было возможным неявное преобразование массива с элементами класса S в массив с элементами класса

T, необходимо выполнение следующих условий:

классы S и T должны быть ссылочного типа;

размерности массивов должны совпадать;

должно существовать неявное преобразование элементов класса S в

элементы класса T.

Заметьте, если S - это родительский класс, а T - его потомок, то для массивов одной размерности остальные условия выполняются. Вернемся теперь к примеру с классами Int[], String[] и Object[]. Класс Int не относится к

217

ссылочным классам, и потому преобразования класса Int[] в Object[] не существует. Класс string является ссылочным классом и потомком класса

Object, а потому существует неявное преобразование между классами String[]

и Object[].

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

Если существует неявное преобразование массива с элементами класса S в

массив с элементами класса T, то существует явное преобразование массива с элементами класса T в массив с элементами класса S.

Для демонстрации преобразований между массивами написана еще одна процедура печати. Вот ее текст:

public static void PrintArObj(string name,object[] A)

{

Console.WriteLine(name); foreach (object item in A )

Console.Write("\t {0}", item); Console.WriteLine();

}//PrintArObj

Как видите, формальный аргумент этой процедуры принадлежит классу

Object[]. При ее вызове фактическими аргументами могут быть массивы,

удовлетворяющие выше указанным условиям. Вот пример кода, в котором вызывается эта процедура. В этом же фрагменте показаны и присваивания массива одного класса другому, где выполняются явные и неявные преобразования массивов.

public void TestMas()

{

string[] winames = {"Т. Хоар", "Н. Вирт", "Э. Дейкстра"}; Arrs.PrintArObj("winames", winames);

object[] cur = new object[5]; cur = winames; Arrs.PrintArObj("cur", cur); winames = (string[])cur;

Arrs.PrintArObj("winames", winames); }//TestMas

Взгляните на результаты работы этой процедуры.

218

Рис. 12.6. Семантика присваивания и преобразования массивов

Приступая к описаниям массивов, я полагал, что 10 страниц одной лекции будет вполне достаточно. Оказалось, что массивы C# более интересны.

Надеюсь, с этим согласятся и читатели.

219

Тема 13. СИМВОЛЫ И СТРОКИ ПОСТОЯННОЙ ДЛИНЫ В C#

СОДЕРЖАНИЕ ЛЕКЦИИ

ОБЩИЙ ВЗГЛЯД o СТРОКИ С++

СТРОКИ С#

o КЛАСС CHAR

o КЛАСС CHAR[] - МАССИВ СИМВОЛОВ o СУЩЕСТВУЕТ ЛИ В C# ТИП CHAR*

220