Глава 7. Массивы и индексаторы
Если программа должна работать с набором объектов одинакового типа, во многих случа­ях удобно образовать из этих объектов структуру данных, называемую массивом (array).
Каждый элемент массива имеет свой номер (индекс) и хранит один объект. Зная индекс элемента массива, программа сможет извлечь или обновить нужный ей объект.
Заметим, что в одном массиве могут храниться объекты базового класса и произ­водных классов. Ранее в нашей книге мы уже приводили подобные примеры, демонст­рирующие возможности полиморфизма в языке С#.
В языке программирования С# массивы обозначаются с помощью квадратных ско­бок, расположенных справа от обозначения типа объектов, составляющих массив. Ни­же мы привели пример определения массива из 10 целых чисел:
int[] bonus = new int[10];
Здесь объявлена ссылка bonus на одномерный массив, содержащий 10 ячеек для хранения целых чисел со знаком типа int.
Обратите внимание, что мы не просто объявили ссылку на массив, а сразу же создали массив оператором new, указав размер массива. Без этого программа не сможет использо­вать ссылку для работы с массивом, так как в ней будет храниться значение nul 1.
При определении массива не резервируется память, поэтому в объявлении ссылки размеры массива не указываются. После выполнения резервирования памяти операто­ром new размер массива становится фиксированным.
В том случае, когда программист заранее не знает размеры массива, можно указать эти размеры динамически во время работы программы, например:
int BonusArraySize = 5;
int[] bonus2 = new int[BonusArraySize] ;
Здесь создается массив, размеры которого предварительно записываются в пере­менную с именем BonusArraySize.
Типы массивов
Как и большинство языков программирования, язык С# позволяет создавать одномер­ные и многомерные массивы. Кроме того, возможно создание массивов, содержащих другие массивы. Рассмотрим способы объявления массивов перечисленных выше ти­пов и приемы работы с ними.
Одномерные массивы
Одномерный массив можно представить себе в виде линейной последовательности ячеек, каждая из которых имеет свой номер. Самая первая ячейка имеет номер 0, вто­рая — 1 и т. д.
ШКХ-пШп 225
8 Язык С# Самоучитель
На рис. 7.1 мы схематически показали одномерный массив с именем Array, со­держащий Size элементов. Эти элементы нумеруются от 0 до Size-1.
|~ Индекс
Array(0] Array[1] Array[2] Array[3] Array[4]
Array[Size-1] |
Рис. 7.1. Одномерный массив Array
Чтобы сослаться в программе на ячейку с заданным номером, необходимо указать этот номер в квадратных скобках, расположенных справа от имени массива:
int[] bonus = new int[10];
bonus[0 ] = 5,-bonus[1] = 7 ; bonus[2] = 10;
Console.WriteLine("Бонусы: bonus[0]={0}, bonus[1] = {1}, bonus[2]={2}", bonus[0], bonus[1], bonus[2]);
Здесь мы создали одномерный массив bonus, записали в 3 первые ячейки этого массива числа 5, 7 и 10, а затем вывели содержимое проинициализированных таким способом ячеек на консоль.
В данном случае была использована так называемая динамическая инициализация массива. Программа инициализирует массив, записывая объекты в его ячейки.
Возможна также статическая инициализация массива, когда содержимое его ячеек определяется в момент компиляции программы. Вот пример статической инициализа­ции массива:
int[] bonusl = {3, 2, 11};
Здесь после оператора присваивания мы указали список инициализации массива, заключенный в фигурные скобки.
Обратите внимание, что при статической инициализации нет никакой необходимо­сти указывать размер массива, так как он определяется автоматически исходя из количества элементов в списке инициализации.
Массивы С# удобны тем, что в них можно хранить объекты любого типа. Ниже мы объявили два строковых массива:
string!] Words = new string[3];
string!] HelloWords = {"Hello", "C#", "world!"}; Console.WriteLine("HelloWords: {0}, {1} {2}", HelloWords[0], HelloWords[1], HelloWords[2]);
Массив HelloWords проинициализирован статически, а его содержимое отобра­жается на консоли.
226
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Многомерные массивы
Многомерные массивы можно представить себе в виде многомерной матрицы, в узлах которой хранятся объекты.
На рис. 7.2 мы показали пример двумерного массива Array.
Столбцы
Аггау[0,0]
Array [0,1]
Array[0,2]
Аггау[1,0]
Array[1,1]
Array[1,2]
Аггау[2,0]
Array[2,1]
Array[2,2]
Аггау[3,0]
Array[3,1]
1 Array[3,2]
Аггау[4,0]
Array[4,1]
/ Array[4,2]
Ячейка Агтау[3,2] строка 3, столбец 2
/
Рис. 7.2. Двумерный массив Array
Такой массив можно представить себе в виде строк и столбцов. Для адресации ячейки двумерного массива нужно указывать два индекса — индекс строки и индекс столбца.
Чтобы создать двумерный массив, например, целых чисел, используйте конструк­цию следующего вида:
int[,] TwoDimArray = new int[2,3];
Здесь при объявлении массива мы использовали запятую для того, чтобы указать компилятору на необходимость создания ссылки TwoDimArray на двумерный мас­сив. Кроме того, в операторе new мы указали количество строк и столбцов создавае­мого двумерного массива.
Если нужно объявить многомерный массив, используйте несколько запятых:
int[,,,] MultiDimArray = new int[2,3,7,2];
Количество запятых должно быть равно размерности массива, уменьшенной на единицу. Таким образом, в предыдущем примере мы создали четырехмерный массив. Многомерные массивы можно инициализировать динамически или статически. Ниже приведен пример динамической инициализации двумерного массива:
int[,] TwoDimArray = new int[2,3];
TwoDimArray[0, 0] = 5;
TwoDimArray[1, 0] = 2 ;
TwoDimArray[0, 1] = 15;
TwoDimArray[1, 1] = 5;
TwoDimArray[0, 2] = 52;
TwoDimArray[1, 2] =32;
Глава 7. Массивы и индексаторы
227
Console.WriteLine("TwoDimArray:\n {0}, {1}",
TwoDimArray[0, 0], TwoDimArray[1, 0]); Console.WriteLine("{0}, {1}", TwoDimArray[0, Console.WriteLine("{0}, {1}", TwoDimArray[0,
1], TwoDimArray[1, 1; 2], TwoDimArray[1, 2\
После инициализации содержимое массива выводится на консоль.
Вот пример статической инициализации двумерного массива CrossZero, кото­рый можно использовать, например, для создания программы известной игры в крес­тики-нолики:
char[,] CrossZero = {
10', 'X'}, '0', 'X'}, 'X', '0'},
В этом массиве хранятся символы типа char.
Массивы массивов
В языке С# допускается создавать массивы массивов, называемые также несиммет­ричными массивами. Другие названия для несимметричных массивов, встречающиеся в литературе и в документации на язык С#, — ступенчатые или «рваные» (jagged) массивы.
На рис. 7.3 мы показали массив, содержащий 5 одномерных массивов разного размера:
Аггау[0,0]
Аггау[0,1]
Аггау[1,0]
Аггау[1,1]
Аггау[1,2]
Аггау[1,3]
Аггау[2,0]
Аггау[3,0]
Аггау[3,1]
Аггау[3,2]
Аггау[4,0]
Аггау[4,1]
Аггау[4,2]
Аггау[4,3]
Аггау[4,4]
Рис. 7.3. Массив массивов Array
Первый массив содержит 2 ячейки, второй — 4, третий — 1 и т. д. Изображение та­кого массива похоже на лестницу, отсюда, видимо, и произошел перевод слов jagged array как «ступенчатый массив».
При необходимости вы можете объединять в массивы не только одномерные, но и многомерные массивы. Однако работа с такими объектами потребует от вас не­дюжинного пространственного воображения.
Объявление массива массивов выполняется при помощи нескольких пар квадрат­ных скобок. Ниже, например, мы объявили массив массивов, содержащих текстовые строки:
string!][] JaggedArray = new string[2] L
228
А В Фролов, Г. В. Фролов. Язык С# Самоучитель
Обратите внимание, что мы указали размерность нашего массива массивов, равную двум. Соответственно нам необходимо инициализировать два массива строк:
JaggedArray[0] = new string[2]; JaggedArray[1] = new string[4];
После инициализации доступ к элементам массивов выполняется следующим образом:
JaggedArray[0][0] = "Hello, С#"; JaggedArray[0][1] = "World!";
JaggedArray[1][0] = "It";
JaggedArray[1][1] - "is";
JaggedArray[1][2] = "C#";
JaggedArray[1][3] = "JaggedArray";
При помощи первой пары квадратных скобок мы указываем индекс массива, а при помощи второй — индекс элемента в массиве. Язык С# допускает создание довольно сложных многомерных массивов, содержащих в себе другие многомерные массивы.
Например, ниже мы объявили ссылку на двумерный массив, содержащий четырехмер­ный массив, который, в свою очередь, содержит трехмерный массив текстовых строк:
string[, ] [,,,] [, ,] VeryComplexArray;
Мы, однако, не рекомендуем увлекаться созданием подобных конструкций без крайней на то необходимости. Они могут запутать программу и сделать ее исход­ный текст непонятным не только для посторонних людей, но и для самого разработчи­ка программы.
Пример программы
В листинге 7.1 мы привели исходный текст программы, демонстрирующей выполне­ние действий с одномерными, многомерными и несимметричными массивами, опи­санными ранее в этой главе.
Так как отдельные фрагменты этой программы уже были описаны, мы оставляем ее вам для самостоятельного изучения.
Листинг 7.1. Файл chOASampleArray\SampleArrayApp.cs
using System; namespace SampleArray {
class SampleArrayApp
{
static void Main(string[] args) {
int[] bonus = new int[10];
bonus(0] = 5; bonus [ 1 ] = 7 ,-bonus[2] = 10;
Глава 7. Массивы и индексаторы
229
Console.WriteLine("Бонусы: bonus[0]=CO}, bonus[1]={1}, bonus[2]={2}", bonus[0], bonus[1], bonus[2]);
int BonusArraySize = 5;
int[] bonus2 = new int[BonusArraySize];
int [] bonusl = {3, 2, lib-Console .WriteLine ("Бонусы: bonusl[0]={0}, bonusl[1]={1}, bonusl[2]={2}", bonusl[0], bonusl[l], bonusl[2]);
string[] Words = new string[3],-
string[] HelloWords = {"Hello", "C#", "world!"}; Console.WriteLine("HelloWords: {0}, {1} {2}", HelloWords[0], HelloWords[1], HelloWords[2]);
int[,] TwoDimArray = new int[2,3];
TwoDimArray[0, 0] = 5; TwoDimArray[1, 0] = 2; TwoDimArray[0, 1] = 15; TwoDimArray[1, 1] = 5; TwoDimArray[0, 2] =52; TwoDimArray[1, 2] = 32;
Console.WriteLine("TwoDimArray:\n {0}, {1}", TwoDimArray[0, 0], TwoDimArray[1, 0]);
Console.WriteLine("{0}, {1}",
TwoDimArray[0, 1], TwoDimArray[1, 1]);
Console.WriteLine("{0}, {1}",
TwoDimArray[0, 2], TwoDimArray[1, 2]);
chart,] CrossZero = {
{'X', '0•, 'X'}, {'0\ '0 , 'X'}, {'X', 'X', '01},
};
string!][] JaggedArray = new string[2][]; JaggedArray[0] = new string[2]; JaggedArray[1] = new string[4];
JaggedArray[0][0] = "Hello, C#"; JaggedArray[0][1] = "World!";
JaggedArray[1][0] = "It";
JaggedArray[1][1] = "is";
JaggedArray[1][2] = "C#";
JaggedArray[1][3] = "JaggedArray";
Console.ReadLine();
}
>
}
230
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Массивы и циклы
В предыдущих примерах программ мы обращались к отдельным ячейкам массивов, указывая их номер (индекс). Однако чаще всего обработка массивов выполняется в цикле с использованием таких операторов, как for, while, do и foreach. При этом программа последовательно обращается к ячейкам массива, записывая или извлекая данные.
Обработка одномерного массива чисел
Вначале мы рассмотрим самый простой прием циклической обработки одномерного массива, основанный на прменении оператора for (листинг 7.2). Этот прием исполь­зуется во многих языках программирования, в частности в языках С и С++.
Листинг 7.2. Файл ch07\ArrayLoop\ArrayLoopApp.cs
using System; namespace ArrayLoop {
class ArrayLoopApp {
static void Main(string[] args) {
int[] Numbers; Numbers = new int[10];
int i;
for(i =0; i < 10; i++) Numbers[i] = i;
Console.Write("Numbers: [" + Numbers[0]);
for(i =1; i < Numbers.Length; i++) Console.Write("," + Numbers[i]);
Console.WriteLine("]"); Console.ReadLine();
}
}
}
Внутри метода Main мы объявили массив Numbers, содержащий переменные ти­па int:
int[] Numbers;
Здесь мы не указываем размер массива, так как при объявлении массива память для него не резервируется.
Глава 7. Массивы и индексаторы
231
В следующей строке мы резервируем память для хранения 10 переменных типа int. Ссылку на эту память мы записываем в переменную Numbers, завершая созда­ние массива:
Numbers = new int[10];
Дополнительно мы объявляем переменную i, которая будет применяться для ин­дексации массива:
int i;
Запись значений в ячейки массива (т. е. инициализация массива) выполняется про­стым присваиванием в цикле:
for(i = 0; i < 10; i++) Numbers[i] = i;
После завершения этого процесса программа отображает на консоли значения, хранящиеся в массиве:
Console.Write("Numbers: [" + Numbers[0]);
for(i =1; i < Numbers.Length; i++) Console.Write("," + Numbers[i]);
Console.WriteLine("]");
Здесь мы вначале выводим на консоль содержимое первого элемента массива, а за­тем и всех остальных:
Numbers: [0,1,2,3,4,5,6,7,8,9]
Обратите внимание на то, как мы проверяем условие выхода за пределы массива. Параметр цикла i сравнивается со значением Numbers . Length. Что это за значение?
Все массивы в С# являются объектами библиотечного класса System. Array. По­ле Length этого класса содержит размер массива.
Обработка одномерного массива строк
В листинге 7.3 мы привели пример программы, отображающей на консоли содержи­мое одномерного массива строк, проинициализированного статически.
Листинг 7.3. Файл ch07\ArrayLoop1\ArrayLoop1App.cs
using System; namespace ArrayLoopl {
class ArrayLooplApp {
static void Madn(string[] args) (
svri ng [ ] Lines -
232
А В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
{
"This", "is" , "C#" , "string", "array"
};
int i ;
Console.Write(Lines[0]); for(i=l; i < Lines.Length; i++) Console.Write(" " + Lines[i]);
Console.ReadLine() ;
}
}
}
Отображение элементов массива на консоли выполняется в цикле и не имеет ника­ких особенностей:
Console.Write(Lines[0]); for(i=l; i < Lines.Length; i++) Console.Write(" " + Lines[i]);
Вначале на консоль выводится самый первый элемент массива с нулевым индек­сом, а затем все остальные:
This is С# string array
Параметр цикла изменяется от единицы до размера массива, равного значению Lines.Length.
Использование оператора foreach
Хотя для циклической обработки массивов можно использовать любые циклические операторы (и даже цикл, организованный с помощью нерекомендуемого к использо­ванию оператора goto), удобнее всего применять специализированный оператор foreach.
В листинге 7.4 мы привели второй вариант программы, описанной в предыдущем разделе и предназначенной для вывода на консоль содержимого массива, проинициа-лизированного статически.
Листинг 7.4. Файл ch07\ArrayForeach\ArrayForeachApp.cs
using System; namespace ArrayForeach
{
class ArrayForeachApp (
static void Main(string N args) {
Глава 7. Массивы и индексаторы
233
string[] Lines =
{
"This", "is", "C#", "string", "array"
};
foreach (string CurrentString in Lines) {
Console .Write (CurrentString) ,-Console.Write(" ");
}
Console.ReadLine();
}
}
}
Как видите, цикл обработки массива выглядит очень просто:
foreach (string CurrentString in Lines) {
Console.Write(CurrentString); Console.Write(" ") ;
)
В круглых скобках после оператора foreach мы объявили переменную CurrentString типа string, которой в процессе обработки массива Lines будет последовательно присваиваться содержимое каждой его ячейки.
Далее вы сможете использовать переменную CurrentString в теле цикла для про­смотра элементов массива. К сожалению, оператор foreach не позволяет изменять со­держимое элементов массива, но для просмотра массива (и, как вы узнаете позже, для про­смотра содержимого контейнеров объектов других типов) он очень удобен.
Многомерный массив объектов класса String
Для работы с многомерными массивами удобно использовать вложенные операторы цикла, такие, например, как for.
В листинге 7.5 мы привели исходный текст программы, которая создает двумерный массив текстовых строк, заполняет его, а затем выводит содержимое массива на консоль.
Листинг 7.5. Файл ch07\ArrayMultiDim\ArrayMultiDimApp.cs
using System; namespace ArrayMultiDim (
class ArrayMultiDimApp {
static void Main(string[] args)
234
А В. Фролов, Г. В. Фролов. Язык С#, Самоучитель
{
string[,] Colors = new string[2,4); int i, j;
for(i = 0; i < 2; i + +) {
for{j = 0; j < 3; j++) {
Colors[i,j] = String.Format("Color ({0}, {1})", i,j);
}
}
for(i =0; i < 2; i++) {
for(j = 0; j < 3; {
Console.WriteLine(Colors[i, j ] ) ;
}
)
Console.ReadLine() ;
}
}
}
Вначале мы объявляем двумерный массив объектов класса string и сразу резер­вируем для него память:
string!,] Colors = new string[2,4];
Таким образом, здесь создается массив из двух строк и четырех столбцов. Для работы со строками таблицы мы будем использовать переменные i (для пере­бора строк таблицы) и j (для перебора столбцов таблицы):
int i,j;
Массив Colors заполняется в двойном вложенном цикле:
for(i = 0; i < 2; i++) {
for(j = 0; j < 3; j++) {
Colors[i,j] = String.Format("Color ({0}, {1})", i,j);
}
}
В каждую ячейку массива записывается строка вида Color (х,у), где х и у — номер строки и столбца таблицы соответственно.
Вывод содержимого таблицы на консоль выполняется также в двойном вложенном цикле, как это показано ниже:
Глава 7. Массивы и индексаторы
235
for(i = 0; i < 2 ; i++) {
for(j = 0; j < 3; j ++) {
Console.WriteLine(Colors[i,j]);
}
>
Несимметричный массив объектов класса String
В нашей следующей программе (листинг 7.6) мы демонстрируем циклическую обра­ботку несимметричного массива текстовых строк.
Листинг 7.6. Файл ch07\AssymArrayLoop\AssymArrayLoopApp.cs
using System;
namespace AssymArrayLoop
{
class AssymArrayLoopApp {
static void Main(string[] args) {
string[][] Asymm; int i, j;
Asymm = new str ing [ 2 ] [ ] AsymmfO] = new string[3]; Asymmfl] = new string[4],-
for(i =0; i < Asymm.Length; i++) {
for(j = 0; j < Asymm[i].Length; j++)
{
Asymm[i][j] = String.Format("Asymm ({0}, {1})", i,j);
}
}
for(i = 0; i < Asymm.Length; i++)
{
for(j = 0; j < Asymm[i].Length; j++) {
Console .WriteLine (Asymm [ i ] [j ] ) ,-
}
}
Console.ReadLine();
>
}
}
236
А В Фролов, Г. В. Фролов Язык С# Самоучитель
Определяя несимметричный массив Asymm, мы сначала не указываем, сколько в нем столбцов и строк:
String!][] Asymm;
Далее при резервировании памяти для массива мы задаем только количество строк, равное двум:
Asymm = new string [2] []. ;
Затем в каждой строке задается разное количество столбцов: в первой строке — 3 столбца, а во второй — 4:
Asymm[0] = new string[3]; Asymm[l] = new string[4];
Инициализируя несимметричный массив в двойном вложенном цикле, мы не мо­жем полагаться на то, что каждая строка содержит одинаковое количество столбцов. Поэтому предельное значение переменной внутреннего цикла определяется как Asymm[i].Length:
for(i =0; i < Asymm.Length; i++) {
for(j = 0; j < Asymm[i].Length; j++) {
Asymm[i][j] = String.Format("Asymm ({0}, {1})", i,j);
)
)
Для первой строки оно равно трем, а для второй — четырем.
Содержимое несимметричного массива распечатывается на консоли следующим образом:
for(i =0; i < Asymm.Length; i++) {
for(j = 0; j < Asymm[i].Length; j++) {
Console.WriteLine(Asymm[i][j]);
}
}
Здесь границы переменных внешнего и внутреннего циклов указаны соответствен­но как Asyrtim. Length и Asymm [ i ] . Length.
Индексаторы
В предыдущей главе мы рассказывали о свойствах объектов (properties), с помощью которых можно создавать «умные» поля. Доступ к таким полям осуществляется толь­ко с помощью специальных процедур get и set. Напомним, что вы можете получать и изменять значение свойства с помощью обычного оператора присваивания, однако вместо простого копирования значения выполняется соответствующая процедура дос­тупа. Эта процедура может, например, сохранять значение свойства не в поле класса, а в базе данных или где-то еще, выполняя дополнительную обработку данных.
Глава 7. Массивы и индексаторы
237
В языке С# можно наделить «интеллектом» не только поля, но и массивы. Это де­лается с помощью конструкции, называемой индексатором (indexer).
Чтобы назначение индексатора было более понятным, рассмотрим практический пример.
Пусть нам нужно где-то хранить названия телевизионных каналов. При этом каж­дому номеру телевизионного канала должно ставиться в соответствие то или иное на­звание. Доступ к названиям каналов должен осуществляться по номеру канала, причем в том случае, если указан неправильный или несуществующий канал, вместо названия программа должна получить строку «Канал недоступен».
Безусловно, эту задачу можно решить с помощью обычного одномерного массива текстовых строк.
При инициализации массива в его ячейки следует записать названия каналов. По­лучая название канала по его номеру, программа должна проверять этот номер на до­пустимость. Если указан правильный номер, программа может извлечь название кана­ла из соответствующей ячейки массива, а если неправильный — вернуть строку «Ка­нал недоступен».
Для упрощения программы нам хотелось бы инкапсулировать алгоритм работы с названиями каналов в каком-либо классе, например в классе с названием ChannelNaraes. Снабдив этот класс индексатором, можно организовать доступ к на­званиям каналов, как будто бы они хранятся в обычном массиве:
ChannelNames ch = new ChannelNames(5);
ch[0] = "Спорт"; ch[l] = "Мир кино"; ch[2] = "Боевик"; ch[3] = "Наше кино"; ch[4] = "MTV";
for(uint i = 0; i < ch.Size; i++) {
string s = ch[i]; Console.WriteLine(s) ;
}
Как видите, здесь мы создали объект ch класса ChannelNames, передав конст­руктору количество каналов, равное пяти. Далее мы инициализируем список, последо­вательно записывая названия каналов в объект ch. При этом номер канала указывается таким же способом, как и индекс элемента массива, — с помощью квадратных скобок. Получение названия канала по его номеру выполняется тоже с применением квадрат­ных скобок.
Созданный таким способом объект ch ведет себя подобно массиву, однако он представляет собой нечто большее, чем обычный массив.
Рассмотрим исходный текст программы, в которой определен класс Channel-Names (листинг 7.7).
238
А. В. Фролов, Г, В. Фролов. Язык С#. Самоучитель
Листинг 7.7. Файл ch07\Arraylndexer\ArraylndexerApp.cs
using System; namespace Arraylndexer {
class ChannelNames {
private string[] Channels; private uint ArraySize;
public ChannelNames(uint Count) {
Channels = new string[Count]; ArraySize = Count;
}
public uint Size {
get {
return ArraySize;
}
}
public string this[uint index] {
get {
if(index >= 0 && index < Channels.Length)
return Channels[index]; else
return "Канал недоступен";
}
set {
if(index >= 0 && index < Channels.Length) Channels[index] = value;
}
}
}
class ArraylndexerApp {
static void Main(string[] args) {
ChannelNames ch = new ChannelNames(5);
ch[0] = "Спорт";
ch[l] = "Мир кино";
ch[2] = "Боевик";
ch[3] = "Наше кино";
ch[4] = "MTV";
Глава 7. Массивы и индексаторы
239
for(uint i = 0; i < ch.Size; i++)
(
string s = ch[i]; Console.WriteLine(s);
}
Console.ReadLine();
}
}
}
В классе ChannelNames мы объявили ссылку на массив текстовых строк Channels. Соответствующее поле имеет модификатор доступа private, поэтому доступ к нему возможен только для методов класса ChannelNames:
private string[] Channels;
Кроме того, в классе объявлено поле Size, хранящее размер массива названий ка­налов:
private uint ArraySize;
К этому полю тоже имеют доступ только члены класса ChannelNames. Конструктор класса ChannelNames создает массив заданного размера и сохраня­ет этот размер в поле ArraySize:
public ChannelNames(uint Count) {
Channels = new string[Count]; ArraySize = Count;
}
Чтобы программа, внешняя по отношению к классу ChannelNames, могла опреде­лять количество элементов в массиве Channels, мы объявили в классе свойство Size:
public uint Size {
get {
return ArraySize;
}
}
В этом свойстве предусмотрен только один метод доступа get, поэтому программа не сможет изменить содержимое поля ArraySize после создания объекта класса ChannelNames.
Объявление индексатора
Теперь мы переходим к самому интересному — к объявлению индексатора:
public string this[uint index] {
get
240
А В Фролов, Г. В. Фролов. Язык С#. Самоучитель
{
if(index >= 0 && index < Channels.Length)
return Channels[index]; else
return "Канал недоступен";
}
set
{
if(index >= 0 && index < Channels.Length) Channels[index] = value;
}
}
Как видите, объявление индексатора очень похоже на объявление свойства. В нем тоже могут быть процедуры доступа get и set, причем допускается объявлять либо обе эти процедуры, либо только какую-то одну из них. Процедуры доступа полностью управляют процессом записи данных в «умный» массив, а также извлечением данных из этого массива.
При объявлении индексатора, как и при объявлении свойства, мы должны указать модификатор доступа и тип. В нашем случае мы создали общедоступный индексатор, указав модификатор доступа public. Так как названия каналов представляют собой текстовые строки, тип индексатора задан как string.
Класс, содержащий индексатор, выступает в роли массива. Ссылка на такой массив выполняется либо с использованием имени объекта класса, либо через имя класса (ес­ли индексатор объявлен как статический). Поэтому для индексатора не требуется ука­зывать какое-то особенное имя. В качестве имени индексатора выступает ключевое слово this, обозначающее ссылку на объект данного класса.
После ключевого слова this в объявлении индексатора следует параметр, заклю­ченный в квадратные скобки. Этот параметр играет роль индекса и в нашем случае имеет целочисленный тип uint. При необходимости вы можете использовать для ин­дексации параметры любого типа, например текстовые строки.
В процедурах доступа get и set параметр индексатора применяется для получе­ния доступа к элементу массива. В нашем случае названия каналов хранятся в простом одномерном массиве текстовых строк Channels. Однако при необходимости проце­дура доступа могла бы извлекать эти названия из базы данных или получать через Ин­тернет, пользуясь для идентификации канала значением параметра index.
Процедура доступа set индексатора, так же как и аналогичная процедура доступа свойства, пользуется ключевым словом value для установки нового значения масси­ва. При этом нужный элемент массива идентифицируется при помощи параметра ин­дексатора.
Приведенная выше реализация процедуры доступа get выполняет необходимый нам алгоритм определения названия канала по его номеру. Если указан неправильный номер, она возвращает строку «Канал недоступен».
Что же касается процедуры доступа set, то она тоже выполняет некоторые про­верки. В частности, при указании недопустимого номера канала она игнорирует по­пытку записи в массив нового значения.
Глава 7. Массивы и индексаторы
241
Индексаторы многомерных массивов
В предыдущем разделе мы показали, как пользоваться индексаторами для получения «интеллектуального» доступа к одномерному массиву строк. При необходимости вы также можете создавать индексаторы и для многомерных массивов.
Теперь немного усовершенствуем программу, исходный текст которой был приве­ден в листинге 7.7. Мы потребуем, чтобы программа хранила для каждого номер кана­ла не одно, а два названия. Первое название пусть будет соответствовать, например, отечественному каналу, а второе — зарубежному.
Для реализации этой логики нам потребуется двумерный массив текстовых строк. В новом варианте программы (листинг 7.8) мы объявили такой массив, а также индек­сатор для организации к нему «интеллектуального» доступа.
Листинг 7.8. Файл ch07\ArraylndexerMulti\ArraylndexerMultiApp.cs
using System;
namespace ArraylndexerMulti {
class ChannelNames
{
private string[(] Channels; private uint ArraySize;
public ChannelNames(uint Count) {
Channels = new string[Count, 2]; ArraySize = Count;
}
public uint Size {
get {
return ArraySize;
}
}
public string this[uint index, uint language] {
get {
if(index >= 0 kk index < Channels.Length)
return Channels[index, language]; else
return "Канал недоступен";
}
242
А В. Фролов, Г. В Фролов. Язык С#. Самоучитель
set {
if(index >= 0 && index < Channels.Length) Channels[index, language] = value;
}
}
}
class ArraylndexerMultiApp {
static void Main(string[] args) {
ChannelNames ch = new ChannelNames(5);
ch[0,
0]
= "Спорт";
ch[l,
0]
= "Мир кино";
ch[2,
0]
= "Боевик";
ch[3,
0]
= "Наше кино"
ch[4,
0]
= "MTV";
ch[0,
1]
= "Eurosport"
ch[l,
1]
= "Discovery"
ch[2.
1]
_ «ту 5„.
ch[3.
1]
= "Fashion";
ch[4,
1]
= "Euronews";
for(uint i = 0; i < ch.Size; i++) {
string s = String.Format("{0} ({1})", ch[i, 0], ch[i, 1]); Console.WriteLine(s);
}
Console.ReadLine();
}
}
}
Ссылка на двумерный массив объявлена следующим образом: *
private string[,] Channels;
Она инициализируется в конструкторе, задающем количество строк и столбцов двумерного массива:
public ChannelNames(uint Count) {
Channels = new string[Count, 2]; ArraySize = Count;
}
Количество строк определяется параметром конструктора, а количество столбцов фиксировано и равно двум.
Для индексатора нам теперь потребуется два параметра, первый из которых будет за­давать номер канала, а второй — номер языка (0 для русского языка, и 1 для английского):
Глава 7. Массивы и индексаторы
243
public string this[uint index, uint language] (
get
{
if(index >= 0 && index < Channels.Length)
return Channels[index, language]; else
return "Канал недоступен";
}
set {
if(index >= 0 && index < Channels.Length) Channels[index, language] = value;
}
}
Здесь процедура доступа get возвращает название канала с заданным номером и на заданном языке или строку «Канал недоступен» в случае ошибки. Процедура дос­тупа set изменяет название заданного канала с учетом номера языка.
В теле метода Main, получающего управление сразу после запуска программы, про­грамма создает объект ch класса ChannelNames, передавая конструктору значение 5:
ChannelNames ch = new ChannelNames(5);
В результате этот объект сможет хранить информацию о пяти каналах на одном из двух языков.
Далее программа инициализирует список каналов, обращаясь неявным образом к индексатору класса ChannelNames:
ch[0,
0]
= "Спорт";
ch[l.
0]
= "Мир кино";
ch[2,
0]
= "Боевик";
ch[3,
0]
= "Наше кино"
ch[4,
0]
='"MTV";
ch[0,
1)
= "Eurosport"
ch[l,
1]
= "Discovery"
ch[2,
1]
= "TV 5";
ch[3,
1]
= "Fashion";
ch[4,
1)
= "Euronews";
Как видите, вначале инициализируются русские каналы, затем — зарубежные. После инициализации программа отображает на экране полный список каналов, также неявно обращаясь к индексатору:
for(uint i = 0; i < ch.Size; i++)
{
string s = String.Format("{0} ({1})", ch[i, 0], ch[i, 1]); Console.WriteLine(s);
}
244
А В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Вот что появится на консоли после запуска нашей программы:
Спорт (Eurosport) Мир кино (Discovery) Боевик (TV 5) Наше кино (Fashion) MTV (Euronews)
Дополнительные операции с массивами в С#
Рассказывая о таких типах данных, как int, char и т. п., мы обращали ваше внима­ние на то, что эти типы данных созданы на базе соответствующих классов. Что же ка­сается массивов, то они тоже созданы на базе класса System.Array. Функциональ­ность, инкапсулированная в этом классе, наделяет массивы С# дополнительными воз­можностями, недоступными в массивах, реализованных средствами других языков программирования. Массивы С# в отличие от массивов С++, можно, например, копи­ровать и сортировать.
В этом разделе мы рассмотрим наиболее полезные методы и свойства класса System. Array, которые вы можете применять при работе с массивами.
Определение размера массива
Ранее в наших программах мы уже определяли количество элементов, имеющихся в массиве, обращаясь для этого к свойству Length.
Существуют и другие средства, позволяющие получить информацию о размере массива.
Например, с помощью свойства Rank можно узнать размерность (ранг) масси­ва. Для одномерных массивов значение ранга равно единице, для двумерных — двум и т. д.
Методы GetLowerBound и GetUpperBound позволяют узнать соответственно минимальное и максимальное значение индекса элементов, хранящихся в массиве. В качестве параметров этим методам нужно передать значение ранга массива, умень­шенное на единицу.
Для изменения значения, хранящегося в ячейке массива, можно использовать не только квадратные скобки, но и метод SetValue. При работе с одномерными масси­вами в качестве первого параметра этому методу нужно передать изменяемое значе­ние, а в качестве второго — индекс соответствующей ячейки массива. На наш взгляд, однако, использование скобок представляет собой более наглядный способ работы с массивами.
В листинге 7.9 мы привели пример программы, демонстрирующей использование перечисленных выше свойств и методов.
Глава 7. Массивы и индексаторы
245
Листинг 7.9. Файл ch07\ArrayMore\ArrayMoreApp.cs
using System; namespace ArrayMore {
class ArrayMoreApp {
static void Main(string[] args) {
int[] arrayOfNumbers = {1, 3, 5, 7, 9, lib-Console .WriteLine ("Размер массива {0}", arrayOfNumbers.Length);
foreach(int i in arrayOfNumbers) {
Console.Write("{0} ", i);
}
Console.WriteLine("ХпПосле изменения элемента массива:");
arrayOfNumbers.SetValue(14, 5);
foreach(int i in arrayOfNumbers) {
Console.Write("{0} ", i);
}
Console.WriteLine("\пРанг массива: {0}",
arrayOfNumbers.Rank); Console.WriteLine("Нижняя граница массива: {0}",
arrayOfNumbers.GetLowerBound(0)); Console.WriteLine("Верхняя граница массива: {0}",
arrayOfNumbers.GetUpperBound(O));
Console.ReadLine();
}
}
}
Получив управление, программа создает массив целых чисел arrayOfNumbers, инициализируя его статически:
int[] arrayOfNumbers = {1, 3, 5, 7, 9, 11};
Далее программа отображает на консоли количество элементов (чисел), хранящих­ся в массиве, а также выводит все эти элементы на консоль:
Console.WriteLine("Размер массива {0}", arrayOfNumbers.Length); foreach(int i in arrayOfNumbers)
{
Console.Write("{0} ", i);
}
246
А В Фролов, Г. В. Фролов. Язык С#. Самоучитель
Количество элементов, хранящихся в массиве, извлекается из свойства Length, определенного в классе System .Array.
Далее мы записываем в ячейку массива с индексом 5 значение, равное 14, вызывая для этого метод SetValue:
arrayOfNumbers.SetValue{14, 5);
Аналогичный результат можно получить и с помощью квадратных скобок:
arrayOfNumbers[5] = 14;
Изменив значение элемента массива, программа вновь выводит его содержимое на консоль.
На следующем этапе своей работы программа выводит на консоль размерность массива, обращаясь для ее определения к свойству Rank:
*
Console.WriteLine("\пРанг массива: {0}", arrayOfNumbers.Rank);
И наконец, перед тем как завершить свое выполнение, программа выводит на кон­соль минимальное и максимальное значение индекса для элементов, хранящихся в массиве:
Console.WriteLine("Нижняя граница массива: (0)",
arrayOfNumbers.GetLowerBound(O)); Console.WriteLine("Верхняя граница массива: {0}",
arrayOfNumbers.GetUpperBound(O));
Обратите внимание, что у нас одномерный размер, ранг которого равен единице. Тем не менее мы передаем методам GetLowerBound и GetUpperBound нулевое значение, т. е. значение ранга, уменьшенное на единицу.
Сортировка и реверсирование массивов
С помощью статических методов Array. Sort и Array. Reverse можно соответст­венно отсортировать элементы массива и переставить их в обратном порядке. Исполь­зование этих методов демонстрируется в программе, исходный текст которой приве­ден в листинге 7.10.
Листинг 7.10. Файл ch07\SortReverse\SortReverseApp.c$
using System; namespace SortReverse {
class SortReverseApp {
static void Main(string[] args) {
int[] arrayOfNumbers = {21, 3, 51, 7, 29, lib-Console .Write ("Исходный массив: "); foreach(int i in arrayOfNumbers) Console.Write("{0} ", i);
Главв 7. Массивы и индексаторы
247
Array.Sort(arrayOfNumbers);
Console.Write("\пСортированный массив: "); foreach(int i in arrayOfNumbers) Console.Write("{0} ", i);
Array.Reverse(arrayOfNumbers);
Console.Write("\пРеверсированный массив: "); foreach(int i in arrayOfNumbers) Console.Write("{0} ", i);
Console.ReadLine() ;
}
}
}
В ней объявляется массив целых чисел с применением статической инициализации: int[) arrayOfNumbers = {21, 3, 51, 7, 29, 11};
Далее наша программа сортирует массив, вызывая метод Array. Sort:
Array.Sort(arrayOfNumbers);
Содержимое исходного и отсортированного массива выводится на консоль при по­мощи простейшего цикла:
foreach(int i in arrayOfNumbers) Console.Write("{0} ", i);
Этот же цикл применяется и для вывода содержимого массива, элементы которого были переставлены в обратном порядке при помощи метода Array. Reverse:
Array.Reverse(arrayOfNumbers);
Вот что наша программа выводит на консоль:
Исходный массив: 21 3 51 7 29 11 Сортированный массив: 3 7 11 21 29 51 Реверсированный массив: 51 29 21 11 7 3
Заметим, что оба описанных выше метода позволяют работать с массивами любых объектов, а не только чисел. При необходимости вы можете отсортировать методом Array. Sort, например, массив текстовых строк.
Поиск в массиве
С помощью статического метода Array. BinarySearch можно организовать поиск элементов в одномерном массиве. В качестве первого параметра этому методу нужно передать ссылку на массив, а в качестве второго — искомый элемент. При успехе ме­тод возвратит индекс найденного элемента, а в том случае, если элемент не найден - от­рицательное значение.
248
А В Фролов, Г В. Фролов. Язык С#. Самоучитель
Исходный текст программы, демонстрирующей применение метода Array.Bi-narySearch для поиска текстовой строки в массиве, приведен в листинге 7.11.
Листинг 7.11. Файл ch07\BinarySearch\BinarySearchApp.cs
using System; namespace BinarySearch {
class BinarySearchApp {
static void Main{string[] args) {
string[] arrayOfNumbers = {
"Каждый", "охотник", "желает", "знать", "где", "сидит", "фазан"
string searchString = "фазан";
int index=Array.BinarySearch(arrayOfNumbers, searchString);
if (index < 0 )
Console.WriteLine("Строка \"{0}\" не найдена.", searchString);
else
Console.WriteLine("Индекс строки \"{0}\" равен {1}.", searchString, index );
Console.ReadLine();
}
}
}
В программе объявлен и проинициализирован статически массив текстовых строк arrayOfNumbers.
Метод Main ищет в массиве слово «фазан», вызывая для этого статический метод Array .BinarySearch. Данный метод получает в качестве первого параметра ссыл­ку на массив, а в качестве второго — ссылку на искомую строку.
Если искомая строка не найдена, метод Array .BinarySearch возвращает отри­цательное значение. В случае успеха возвращается индекс найденной строки.
Наша программа отображает на консоли искомую строку и ее индекс.
Глава 7. Массивы и индексаторы
249
Сайт создан в системе uCoz