Справочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией
Скачать 5.05 Mb.
|
Глава 7 Массивы и строки Глава 7. Массивы и строки 155 этой главе мы возвращаемся к теме типов данных языка C# (познакомимся с типом данных string ). Помимо массивов здесь будет уделено внимание использованию цикла foreach Массивы Массив (array) — это коллекция переменных одинакового типа, обращение к которым происходит с использованием общего для всех имени. В C# массивы могут быть одномерными или многомерными, хотя в основном используются одномерные массивы. Массивы представляют собой удобное средство группирования связанных переменных. Например, массив можно использовать для хранения значений максимальных дневных температур за месяц, списка цен на акции или названий книг по программированию из домашней библиотеки. Массив организует данные таким способом, который позволяет легко ими манипулировать. Например, если у вас есть массив, содержащий дивиденды, выплачиваемые по выбранной группе акций, то, построив цикл опроса всего массива, нетрудно вычислить средний доход от этих акций. Кроме того, организация данных в форме массива позволяет легко их сортировать в нужном направлении. Хотя массивы в C# можно использовать по аналогии с тем, как они используются в других языках программирования, C#-массивы имеют один специальный атрибут, а именно: они реализованы как объекты. Этот факт и стал причиной того, что рассмотрение массивов в этой книге было отложено до введения понятия объекта Реализация массивов в виде объектов позволила получить ряд преимуществ, причем одно из них (и к тому же немаловажное) состоит в том, что неиспользуемые массивы могут автоматически утилизироваться системой сбора мусора. Одномерные массивы Одномерный массив — это список связанных переменных. Такие списки широко распространены в программировании Например, один одномерный массив можно использовать для хранения номеров счетов активных пользователей сети. В другом — можно хранить количество мячей, забитых в турнире бейсбольной командой. Для объявления одномерного массива используется следующая форма записи. тип [] имя_массива = new тип [ размер ]; Здесь с помощью элемента записи тип объявляется базовый тип массива. Базовый тип определяет тип данных каждого элемента, составляющего массив. Обратите внимание на одну пару квадратных скобок за элементом записи тип . Это означает, что определяется одномерный массив. Количество элементов, которые будут храниться в массиве, определяется элементом записи размер . Поскольку массивы реализуются как объекты, их создание представляет собой двухступенчатый процесс. Сначала объявляется ссылочная переменная на массив, а затем для него выделяется память, и переменной массива присваивается ссылка на эту область памяти. Таким образом, в C# массивы динамически размешаются в памяти с помощью оператора new На заметку Если вы уже знакомы с языками С или C++, обратите внимание на объявление массивов в C# Квадратные скобки располагаются за именем типа, а не за именем массива В 156 Часть I. Язык C# Рассмотрим пример. При выполнении приведенной ниже инструкции создается int - массив (состоящий из 10 элементов), который связывается со ссылочной переменной массива sample. int[] sample = new int[10]; Это объявление работает подобно любому объявлению объекта. Переменная sample содержит ссылку на область памяти, выделенную оператором new Доступ к отдельному элементу массива осуществляется посредством индекса. Индекс описывает позицию элемента внутри массива. В C# первый элемент массива имеет нулевой индекс. Поскольку массив sample содержит 10 элементов, его индексы изменяются от 0 до 9. Чтобы получить доступ к элементу массива по индексу, достаточно указать нужный номер элемента в квадратных скобках. Так, первым элементом массива sample является sample[0] , а последним — sample[9] . Например, следующая программа помещает в массив sample числа от 0 до 9. // Демонстрация использования одномерного массива. using System; class ArrayDemo { public static void Main() { int[] sample = new int[10]; int i; for(i = 0; i < 10; i = i+1) sample[i] = i; for(i = 0; i < 10; i = i+1) Console.WriteLine("sample[" + i + "]: " + sample[i]); } } Результаты выполнения этой программы имеют такой вид: sample[0]: 0 samp1е[1]: 1 sample[2]: 2 sample[3]: 3 sample[4]: 4 sample[5]: 5 sample[6]: 6 sample[7]: 7 sample[8]: 8 sample[9]: 9 Схематично массив sample можно представить в таком виде: Массивы широко применяются в программировании, поскольку позволяют легко обрабатывать большое количество связанных переменных. Например, следующая программа вычисляет среднее арифметическое от множества значений, хранимых в массиве nums , опрашивая в цикле for все его элементы. 0 sample[0] 1 2 3 4 5 6 7 8 9 sample[1] sample[2] sample[3] sample[4] sample[5] sample[6] sample[7] sample[8] sample[9] Глава 7. Массивы и строки 157 // Вычисление среднего арифметического от // множества значений. using System; class Average { public static void Main() { int[] nums = new int[10]; int avg = 0; nums[0] = 99; nums[1] = 10; nums[2] = 100; nums[3] = 18; nums[4] = 78; nums[5] = 23; nums[6] = 63; nums[7] = 9; nums[8] = 87; nums[9] = 49; for(int i=0; i < 10; i++) avg = avg + nums[i]; avg = avg / 10; Console.WriteLine("Среднее: " + avg); } } Вот результат выполнения программы: Среднее: 53 Инициализация массива В предыдущей программе значения массиву nums были присвоены вручную, т.е. с помощью десяти отдельных инструкций присваивания. Существует более простой путь достижения той же цели: массивы можно инициализировать в момент их создания. Формат инициализации одномерного массива имеет следующий вид: тип [] имя_массива = [ val1 , val2 , ..., valN ]; Здесь начальные значения, присваиваемые элементам массива, задаются с помощью последовательности val1—valN . Значения присваиваются слева направо, в порядке возрастания индекса элементов массива. C# автоматически выделяет для массива область памяти достаточно большого размера, чтобы хранить заданные значения инициализации (инициализаторы). В этом случае нет необходимости использовать в явном виде оператор new . Теперь рассмотрим более удачный вариант программы Average // Вычисление среднего арифметического от // множества значений. using System; class Average { public static void Main() { int[] nums = { 99, 10, 100, 18, 78, 23, 63, 9, 87, 49 }; 158 Часть I. Язык C# int avg = 0; for(int i=0; i < 10; i++) avg = avg + nums[i]; avg = avg / 10; Console.WriteLine("Среднее: " + avg); } } Хотя, как уже было отмечено выше, в этом нет необходимости, при инициализации массива все же можно использовать оператор new . Например, массив nums из предыдущей программы можно инициализировать и таким способом, хотя он и несет в себе некоторую избыточность. int[] nums = new int[] { 99, 10, 100, 18, 78, 23, 63, 9, 87, 49 }; Несмотря на избыточность new -форма инициализации массива оказывается полезной в том случае, когда уже существующей ссылочной переменной массива присваивается новый массив. Например: int[] nums; nums = new int[] { 99, 10, 100, 18, 78, 23, 63, 9, 87, 49 }; Здесь массив nums объявляется в первой инструкции и инициализируется во второй. И еще. При инициализации массива допустимо также явно указывать его размер, но размер в этом случае должен соответствовать количеству инициализаторов. Вот, например, еще один способ инициализации массива nums int[] nums = new int[10] { 99, 10, 100, 18, 78, 23, 63, 9, 87, 49 }; В этом объявлении размер массива nums явно задан равным 10. Соблюдение "пограничного режима" Границы массивов в C# строго "охраняются законом". Выход за границы расценивается как динамическая ошибка. Чтобы убедиться в этом, попытайтесь выполнить следующую программу, в которой намеренно делается попытка нарушения границ массива. // Демонстрация выхода за границу массива. using System; class ArrayErr { public static void Main() { int[] sample = new int[10]; int i; // Организуем нарушение границы массива. for(i = 0; i < 100; i = i+1) sample[i] = i; } } Как только i примет значение 10 , будет сгенерирована исключительная ситуация типа IndexOutOfRangeException , и программа прекратит выполнение. Глава 7. Массивы и строки 159 Многомерные массивы Несмотря на то что в программировании чаше всего используются одномерные массивы, их многомерные "собратья" — также не редкость. Многомерным называется такой массив, который характеризуется двумя или более измерениями, а доступ к отдельному элементу осуществляется посредством двух или более индексов. Двумерные массивы Простейший многомерный массив — двумерный. В двумерном массиве позиция любого элемента определяется двумя индексами. Если представить двумерный массив в виде таблицы данных, то один индекс означает строку, а второй — столбец. Чтобы объявить двумерный массив целочисленных значений размером 10x20 с именем table , достаточно записать следующее: int[,] table = new int[10, 20]; Обратите особое внимание на то, что значения размерностей отделяются запятой. Синтаксис первой части этого объявления [,] означает, что создается ссылочная переменная двумерного массива. Для реального выделения памяти для этого массива с помощью оператора new используется более конкретный синтаксис: int[10, 20] Тем самым обеспечивается создание массива размером 10x20, причем значения размерностей также отделяются запятой. Чтобы получить доступ к элементу двумерного массива, необходимо указать оба индекса, разделив их запятой. Например, чтобы присвоить число 10 элементу массива table , позиция которого определяется координатами 3 и 5, можно использовать следующую инструкцию: table[3, 5] = 10; Рассмотрим пример программы, которая заполняет двумерный массив числами от 1 до 12, а затем отображает содержимое этого массива. // Демонстрация использования двумерного массива. using System; class TwoD { public static void Main() { int t, i; int[,] table = new int[3, 4]; for(t=0; t < 3; ++t) { for(i=0; i < 4; ++i) { table[t,i] = (t*4)+i+1; Console.Write(table[t,i] + " "); } Console.WriteLine(); } } } 160 Часть I. Язык C# В этом примере элемент массива table[0,0] получит число 1, элемент table[0,1] — число 2, элемент table[0,2] — число 3 и т.д. Значение элемента table[2,3] будет равно 12. Схематически этот массив можно представить, как показано на рис. 7.1. Рис. 7.1. Схематическое представление массива table , созданное программой TwoD На заметку Если вы уже знакомы с языками C или C++, обратите внимание на объявление многомерных массивов в C# и доступ к их элементам. В языках C или C++ значения размерностей массивов и индексы указываются в отдельных парах квадратных скобок В C# значения размерностей отделяются запятыми. Массивы трех и более измерений В C# можно определять массивы трех и более измерений. Вот как объявляется многомерный массив: тип [, .. ,] имя = new тип [ размер1 , ..., размерN ]; Например, с помощью следующего объявления создается трехмерный целочисленный массив размером 4x10x3: int[, ,] multidim = new int[4, 10, 3]; Чтобы присвоить число 100 элементу массива multidim , занимающему позицию с координатами 2,4,1, используйте такую инструкцию: multidim[2, 4, 1] = 100; Рассмотрим программу, в которой используется трехмерный массив, содержащий 3x3x3-матрицу значений. // Программа суммирует значения, расположенные // на диагонали 3x3x3-матрицы. using System; class ThreeDMatrix { public static void Main() { int[,,] m = new int[3, 3, 3]; int sum = 0; int n = 1; for(int x=0; x < 3; x++) for(int y=0; y < 3; y++) for(int z=0; z < 3; z++) m[x, y, z] = n++; 1 2 3 4 5 6 7 8 9 10 11 12 Левый индекс 0 1 2 2 0 1 3 table[1,2] Правый индекс Глава 7. Массивы и строки 161 sum = m[0,0,0] + m[1,1,1] + m[2, 2, 2]; Console.WriteLine("Сумма первой диагонали: " + sum); } } Вот результаты выполнения этой программы: Сумма первой диагонали: 42 Инициализация многомерных массивов Многомерный массив можно инициализировать, заключив список инициализаторов каждой размерности в собственный набор фигурных скобок. Например, вот каков формат инициализации двумерного массива: тип [,] имя_массива = { { val , val , val , ..., val } { val , val , val , ..., val } { val , val , val , ..., val ) }; Здесь элемент val — значение инициализации. Каждый внутренний блок означает строку. В каждой строке первое значение будет сохранено в первой позиции массива, второе значение — во второй и т.д. Обратите внимание на то, что блоки инициализаторов отделяются запятыми, а точка с запятой становится только после закрывающей фигурной скобки. Например, следующая программа инициализирует массив sqrs числами от 1 до 10 и квадратами этих чисел. // Инициализация двумерного массива. using System; class Squares { public static void Main() { int[,] sqrs = { { 1, 1 }, { 2, 4 }, { 3, 9 }, { 4, 16 }, { 5, 25 }, { 6, 36 }, { 7, 49 }, { 8, 64 }, { 9, 81 }, { 10, 100 } }; int i, j; for(i=0; i < 10; i++) { for(j=0; j < 2; j++) Console.Write(sqrs[i,j] + " "); Console.WriteLine(); } } } 162 Часть I. Язык C# Результаты выполнения этой программы: 1 1 2 4 3 9 4 16 5 25 6 36 7 49 8 64 9 81 10 100 Рваные массивы В предыдущем примере мы создавали двумерный массив, который в C# называется прямоугольным. Если двумерный массив можно представить в виде таблицы, то прямоугольный массив можно определить как массив, строки которого имеют одинаковую длину. Однако C# позволяет создавать двумерный массив специального типа, именуемый рваным, или с рваными краями. У такого массива строки могут иметь различную длину. Следовательно, рваный массив можно использовать для создания таблицы со строками разной длины. Рваные массивы объявляются с помощью наборов квадратных скобок, обозначающих размерности массива. Например, чтобы объявить двумерный рваный массив, используется следующий формат записи: тип [][] имя = new тип [ размер ][]; Здесь элемент размер означает количество строк в массиве. Для самих строк память выделяется индивидуально, что позволяет строкам иметь разную длину. Например, следующий фрагмент программы при объявлении массива jagged выделяет память для его первой размерности, а память для его второй размерности выделяется “вручную”. int[][] jagged = new int[3][]; jagged[0] = new int[4]; jagged[1] = new int[3]; jagged[2] = new int[5]; После выполнения этого фрагмента кода массив jagged выглядит так: jagged[0][0] jagged[0][1] jagged[0][2] jagged[0][3] jagged[l][0] jagged[l][1] jagged[l][2] jagged[2][0] jagged[2][1] jagged[2][2] jagged[2][3] jagged[2][4] Теперь вам, вероятно, понятно, откуда у рваных массивов такое название. После создания рваного массива доступ к элементу осуществляется посредством задания индекса внутри собственного набора квадратных скобок. Например, чтобы присвоить число 10 элементу массива jagged с координатами 2 и 1, используйте такую инструкцию: jagged[2][1] = 10; Обратите внимание на то, что этот синтаксис отличается от того, который используется для доступа к элементам прямоугольного массива. Следующая программа демонстрирует создание рваного двумерного массива. Глава 7. Массивы и строки 163 // Демонстрация рваных массивов. using System; class Jagged { public static void Main() { int[][] jagged = new int[3][]; jagged[0] = new int[4]; jagged[1] = new int[3]; jagged[2] = new int[5]; int i; // Сохраняем значения в первом массиве. for(i=0; i < 4; i++) jagged[0][i] = i; // Сохраняем значения во втором массиве. for(i=0; i < 3; i++) jagged[1][i] = i; // Сохраняем значения в третьем массиве. for(i=0; i < 5; i++) jagged[2][i] = i; // Отображаем значения первого массива. for(i=0; i < 4; i++) Console.Write(jagged[0][i] + " "); Console.WriteLine(); // Отображаем значения второго массива. for(i=0; i < 3; i++) Console.Write(jagged[1][i] + " "); Console.WriteLine(); // Отображаем значения третьего массива. for(i=0; i < 5; i++) Console.Write(jagged[2][i] + " "); Console.WriteLine(); } } Вот как выглядят результаты выполнения этой программы: 0 1 2 3 0 1 2 0 1 2 3 4 Рваные массивы используются нечасто, но в некоторых ситуациях они могут оказаться весьма эффективными. Например, если вам нужен очень большой двумерный массив с неполным заполнением (т.е. массив, в котором используются не все его элементы), то идеальным решением может оказаться массив именно такой, неправильной формы. 164 Часть I. Язык C# И еще. Поскольку рваные массивы — это по сути массивы массивов, то совсем не обязательно, чтобы “внутренние” массивы имели одинаковый тип. Например, эта инструкция создает массив двумерных массивов: int[][,] jagged = new int[3][,]; А эта инструкция присваивает элементу jagged[0] ссылку на массив размером 4x2: jagged[0] = new int[4][2]; Следующая инструкция присваивает значение переменной i элементу jagged[0][1,0] jagged[0][1,0] = i; Присвоение значений ссылочным переменным массивов Как и в случае других объектов, присваивая одной ссылочной переменной массива другую, вы просто изменяете объект, на который ссылается эта переменная. При этом не делается копия массива и не копируется содержимое одного массива в другой. Рассмотрим, например, следующую программу: // Присвоение значений ссылочным переменным массивов. using System; class AssignARef { public static void Main() { int i; int[] nums1 = new int[10]; int[] nums2 = new int[10]; for(i=0; i < 10; i++) nums1[i] = i; for(i=0; i < 10; i++) nums2[i] = -i; Console.Write("Содержимое массива nums1: "); for(i=0; i < 10; i++) Console.Write(nums1[i] + " "); Console.WriteLine(); Console.Write("Содержимое массива nums2: "); for(i=0; i < 10; i++) Console.Write(nums2[i] + " "); Console.WriteLine(); nums2 = nums1; // Теперь nums2 ссылается на nums1. Console.Write( "Содержимое массива nums2 после \nприсваивания; "); for(i=0; i < 10; i++) Console.Write(nums2[i] + " "); Console.WriteLine(); // Теперь воздействуем на массив nums1 Глава 7. Массивы и строки 165 // посредством переменной nums 2. nums2[3] = 99; Console.Write("Содержимое массива nums1 после\n" + "его изменения посредством nums2: "); for(i=0; i < 10; i++) Console.Write(nums1[i] + " "); Console.WriteLine(); } } Вот результаты выполнения этой программы: Содержимое массива nums1: 0 1 2 3 4 5 6 7 8 9 Содержимое массива nums2: 0 -1 -2 -3 -4 -5 -6 -7 -8 -9 Содержимое массива nums2 после присваивания: 0 1 2 3 4 5 6 7 8 9 Содержимое массива nums1 после его изменения посредством nums2: 0 1 2 99 4 5 6 7 8 9 Как видно по результатам выполнения этой программы, после присвоения содержимого переменной nums1 переменной nums2 обе они ссылаются на один и тот же объект. Использование свойства Length Из того факта, что в C# массивы реализованы как объекты, программисты могут извлечь много пользы. Например, с каждым массивом связано свойство Length , содержащее количество элементов, которое может хранить массив. Использование этого свойства демонстрируется в следующей программе. // Использование свойства Length. using System; class LengthDemo { public static void Main() { int[] nums = new int[10]; Console.WriteLine("Длина массива nums равна " + nums.Length); // Используем Length для инициализации массива nums. for(int i=0; i < nums.Length; i++) nums[i] = i * i; // Теперь используем Length для отображения nums. Console.Write("Содержимое массива nums: "); for(int i=0; i < nums.Length; i++) Console.Write(nums[i] + " "); Console.WriteLine(); } } При выполнении этой программы получаются следующие результаты: Длина массива nums равна 10 Содержимое массива nums: 0 1 4 9 16 25 36 49 64 81 166 Часть I. Язык C# Обратите внимание на то, как в классе LengthDemo цикл for использует свойство nums.Length для управления количеством итераций. Поскольку каждый массив сопровождается информацией о собственной длине, можно использовать эту информацию, а не вручную отслеживать размер массива. При этом следует иметь в виду, что свойство Length никак не связано с реально используемым количеством элементов массива. Оно содержит количество элементов, которое массив способен хранить. При получении длины многомерного массива возвращается общее количество элементов, обусловленное “врожденной” способностью массива хранить данные. Например: // Использование свойства Length для 3-х-мерного массива. using System; class LengthDemo3D { public static void Main() { int[,,] nums = new int[10, 5, 6]; Console.WriteLine("Длина массива равна " + nums.Length); } } Вот результаты выполнения этой программы: Длина массива равна 300 Как подтверждает результат запуска предыдущей программы, свойство Length содержит количество элементов, которое может хранить массив nums и которое в данном случае равно 300 (10 x 5 x 6). При этом у нас нет возможности получить длину массива по конкретному измерению (координате). Использование свойства Length упрощает многие алгоритмы за счет выполнения более простых операций над массивами. Например, следующая программа использует свойство Length для реверсирования содержимого массива посредством его копирования в другой массив в направлении от конца к началу. // Реверсирование массива. using System; class RevCopy { public static void Main() { int i,j; int[] nums1 = new int[10]; int[] nums2 = new int[10]; for(i=0; i < nums1.Length; i++) nums1[i] = i; Console.Write("Исходное содержимое массива: "); for(i=0; i < nums2.Length; i++) Console.Write(nums1[i] + " "); Console.WriteLine(); // Копируем массив nums1 в массив nums2 с // изменением порядка следования элементов. if(nums2.Length >= nums1.Length) // Необходимо // убедиться, что массив nums2 // достаточно велик. Глава 7. Массивы и строки 167 for(i=0, j=nums1.Length-1; i < nums1.Length; i++, j--) nums2[j] = nums1[i]; Console.Write("Содержимое массива в обратном порядке: "); for(i=0; i < nums2.Length; i++) Console.Write(nums2[i] + " "); Console.WriteLine(); } } Вот как выглядят результаты выполнения этой программы: Исходное содержимое массива: 0 1 2 3 4 5 6 7 8 9 Содержимое массива в обратном порядке: 9 8 7 6 5 4 3 2 1 0 Здесь свойство Length выполняет две важных функции. Во-первых, оно подтверждает, что массив-приемник имеет размер, достаточный для хранения содержимого исходного массива. Во-вторых, оно обеспечивает условие завершения цикла for , который выполняет реверсное копирование. Безусловно, это очень простой пример, и размер массива легко узнать и без свойства Length , но аналогичный подход можно применить к широкому диапазону более сложных ситуаций. Использование свойства Length при работе с рваными массивами При работе с рваными массивами использование свойства Length приобретает особое значение, поскольку позволяет получить длину каждого отдельного (“строчного”) массива. Рассмотрим, например, следующую программу, которая имитирует работу центрального процессора (ЦП) в сети с четырьмя узлами. // Демонстрация использования свойства Length при // работе с рваными массивами. using System; class Jagged { public static void Main() { int[][] network_nodes = new int[4][]; network_nodes[0] = new int[3]; network_nodes[1] = new int[7]; network_nodes[2] = new int[2]; network_nodes[3] = new int[5]; int i, j; // Создаем фиктивные данные по // использованию ЦП. for(i=0; i < network_nodes.Length; i++) for(j=0; j < network_nodes[i].Length; j++) network_nodes[i][j] = i * j + 70; Console.WriteLine("Общее количество сетевых узлов: " + network_nodes.Length + "\n"); for(i=0; i < network_nodes.Length; i++) { for(j=0; j < network_nodes[i].Length; j++) { 168 Часть I. Язык C# Console.Write("Использование ЦП на узле " + i + " для ЦП " + j + ": "); Console.Write(network_nodes[i][j] + "% "); Console.WriteLine(); } Console.WriteLine(); } } } Результаты выполнения этой программы выглядят так: Общее количество сетевых узлов: 4 Использование ЦП на узле 0 для ЦП 0: 70% Использование ЦП на узле 0 для ЦП 1: 70% Использование ЦП на узле 0 для ЦП 2: 70% Использование ЦП на узле 1 для ЦП 0: 70% Использование ЦП на узле 1 для ЦП 1: 71% Использование ЦП на узле 1 для ЦП 2: 72% Использование ЦП на узле 1 для ЦП 3: 73% Использование ЦП на узле 1 для ЦП 4: 74% Использование ЦП на узле 1 для ЦП 5: 75% Использование ЦП на узле 1 для ЦП 6: 76% Использование ЦП на узле 2 для ЦП 0: 70% Использование ЦП на узле 2 для ЦП 1: 72% Использование ЦП на узле 3 для ЦП 0: 70% Использование ЦП на узле 3 для ЦП 1: 73% Использование ЦП на узле 3 для ЦП 2: 76% Использование ЦП на узле 3 для ЦП 3: 79% Использование ЦП на узле 3 для ЦП 4: 82% Обратите внимание на то, как свойство Length используется для рваного массива network_nodes . Вспомните, что двумерный рваный массив — это массив массивов. Поэтому выражение network_nodes.Length возвращает количество массивов, хранимых в массиве network_nodes (в данном случае это значение равно 4). Чтобы получить длину отдельного массива во “внешнем” рваном массиве, используйте, например, такое выражение: network_nodes[0].Length Это выражение возвращает длину первого массива. Цикл foreach В главе 5 упоминалось о том, что в языке C# определен цикл foreach , но подробное его рассмотрение было отложено на “потом”. По заголовку этого раздела нетрудно догадаться, что время для этого наступило. Цикл foreach используется для опроса элементов коллекции. Коллекция — это группа объектов. C# определяет несколько типов коллекций, и одним из них является массив. Формат записи цикла foreach имеет такой вид: foreach( тип имя_переменной in коллекция ) инструкция ; Глава 7. Массивы и строки 169 Здесь элементы тип и имя_переменной задают тип и имя итерационной переменной, которая при функционировании цикла fоreach будет получать значения элементов из коллекции. Элемент коллекция служит для указания опрашиваемой коллекции (в данном случае в качестве коллекции мы рассматриваем массив). Таким образом, элемент тип должен совпадать (или быть совместимым) с базовым типом массива. Здесь важно запомнить, что итерационную переменную применительно к массиву можно использовать только для чтения. Следовательно, невозможно изменить содержимое массива, присвоив итерационной переменной новое значение. Рассмотрим простой пример использования цикла foreach . Приведенная ниже программа создает массив для хранения целых чисел и присваивает его элементам начальные значения. Затем она отображает элементы массива, попутно вычисляя их сумму. // использование цикла foreach. using System; class ForeachDemo { public static void Main() { int sum = 0; int[] nums = new int[10]; // Присваиваем элементам массива nums значения. for(int i = 0; i < 10; i++) nums[i] = i; // Используем цикл foreach для отображения значений // элементов массива и их суммирования. foreach(int x in nums) { Console.WriteLine("Значение элемента равно: " + x); sum += x; } Console.WriteLine("Сумма равна: " + sum); } } При выполнении этой программы получим следующие результаты: Значение элемента равно: 0 Значение элемента равно: 1 Значение элемента равно: 2 Значение элемента равно: 3 Значение элемента равно: 4 Значение элемента равно: 5 Значение элемента равно: б Значение элемента равно: 7 Значение элемента равно: 8 Значение элемента равно: 9 Сумма равна: 45 Как видно из приведенных выше результатов, цикл foreach последовательно опрашивает элементы массива в направлении от наименьшего индекса к наибольшему. Несмотря на то что цикл foreach работает до тех пор, пока не будут опрошены все элементы массива, существует возможность досрочного его останова с помощью инструкции break . Например, следующая программа суммирует только пять первых элементов массива nums // Использование инструкции break в цикле foreach. using System; 170 Часть I. Язык C# class ForeachDemo { public static void Main() { int sum = 0; int[] nums = new int[10]; // Присваиваем элементам массива nums значения. for(int i = 0; i < 10; i++) nums[i] = i; // Используем цикл foreach для отображения значений // элементов массива и их суммирования. foreach(int x in nums) { Console.WriteLine("Значение элемента равно: " + x); sum += x; if(x == 4) break; // Останов цикла, когда x равен 4. } Console.WriteLine("Сумма первых 5 элементов: " + sum); } } Вот как выглядят результаты выполнения этой программы: Значение элемента равно: 0 Значение элемента равно: 1 Значение элемента равно: 2 Значение элемента равно: 3 Значение элемента равно: 4 Сумма первых 5 элементов: 10 Очевидно, что цикл foreach останавливается после получения пятого элемента массива. Цикл foreach работает и с многомерными массивами. В этом случае он возвращает элементы в порядке следования строк: от первой до последней. // Использование цикла foreach с двумерным массивом. using System; class ForeachDemo2 { public static void Main() { int sum =0; int[,] nums = new int[3,5]; // Присваиваем элементам массива nums значения. for(int i = 0; i < 3; i++) for(int j=0; j < 5; j++) nums[i,j] = (i+1)*(j+1); // Используем цикл foreach для отображения значений // элементов массива и их суммирования. foreach(int x in nums) { Console.WriteLine("Значение элемента равно: " + x); sum += x; } Console.WriteLine("Сумма равна: " + sum); } } Глава 7. Массивы и строки 171 Вот результаты выполнения этой программы: Значение элемента равно: 1 Значение элемента равно: 2 Значение элемента равно: 3 Значение элемента равно: 4 Значение элемента равно: 5 Значение элемента равно: 2 Значение элемента равно: 4 Значение элемента равно: 6 Значение элемента равно: 8 Значение элемента равно: 10 Значение элемента равно: 3 Значение элемента равно: 6 Значение элемента равно: 9 Значение элемента равно: 12 Значение элемента равно: 15 Сумма равна: 90 Поскольку цикл fоreach может опрашивать массив последовательно (от начала к концу), может сложиться впечатление, что его использование носит весьма ограниченный характер. Однако это не так. Для функционирования широкого круга алгоритмов требуется именно такой механизм. Одним из них является алгоритм поиска. Например, следующая программа использует цикл foreach для поиска в массиве заданного значения. Когда значение найдено, цикл останавливается. // Поиск значения в массиве с помощью цикла foreach. using System; class Search { public static void Main() { int[] nums = new int[10]; int val; bool found = false; // Присваиваем элементам массива nums значения. for(int i = 0; i < 10; i++) nums[i] = i; val = 5; // Используем цикл foreach для поиска в массиве nums // заданного значения. foreach(int x in nums) { if(x == val) { found = true; break; } } if(found) Console.WriteLine("Значение найдено!"); } } Цикл foreach также используется для вычисления среднего значения, определения минимального или максимально числа в наборе чисел, поиска дубликатов и т.д. Как будет показано далее, цикл foreach особенно полезен при работе с другими типами коллекций. 172 Часть I. Язык C# Строки С точки зрения ежедневного программирования одним из самых важных типов данных C# является тип string . Он предназначен для определения и поддержки символьных строк. Во многих других языках программирования строка представляет собой массив символов. В C# дело обстоит иначе: здесь строки являются объектами. Таким образом, string — это ссылочный тип. Несмотря на то что string — встроенный тип данных, для его рассмотрения необходимо иметь представление о классах и объектах. На самом деле мы негласно используем класс string , начиная с главы 2, но вы попросту об этом не знали. При создании строкового литерала в действительности создается объект класса string . Например, в инструкции Console.WriteLine("В C# строки являются объектами."); строка "В C# строки являются объектами." средствами языка C# автоматически превращена в объект класса string . Таким образом, в предыдущих программах мы подспудно использовали класс string . В этом разделе мы научимся работать с ними в явном виде. Создание строк Самый простой способ создать объект типа string — использовать строковый литерал. Например, после выполнения приведенной ниже инструкции str будет объявлена ссылочной переменной типа string , которой присваивается ссылка на строковый литерал. string str = "C#-строки - это мощная сила."; В данном случае переменная str инициализируется последовательностью символов “C#-строки - это мощная сила.”. Можно также создать string -объект из массива типа char . Вот пример: char[] charray = ('t', 'e', 's', 't'); string str = new string(charray); После создания string -объект можно использовать везде, где разрешается использование строки символов, заключенной в кавычки. Например, string -объект можно использовать в качестве аргумента функции WriteLine() , как показано в следующем примере. // Знакомство со строками. using System; class StringDemo { public static void Main() { char[] charray = {'A', ' ', 's', 't', 'r', 'i', 'n', 'g', '.' }; string str1 = new string(charray); string str2 = "Еще один string-объект."; Console.WriteLine(str1); Console.WriteLine(str2); } } Глава 7. Массивы и строки 173 Результаты выполнения этой программы таковы: A string. Еще один string -объект. Работа со строками Класс string содержит ряд методов, которые предназначены для обработки строк (некоторые из них показаны в табл. 7.1). Тип string также включает свойство Length , которое содержит длину строки. Чтобы получить значение отдельного символа строки, достаточно использовать индекс. Например: string str = "test"; Console.WriteLine(str[0]); При выполнении этого фрагмента программы на экран будет выведен символ t (первый символ слова "test" ). Как и у массивов, индексация строк начинается с нуля. Однако здесь важно отметить, что с помощью индекса нельзя присвоить символу внутри строки новое значение. Индекс можно использовать только для получения символа. Таблица 7.1. Наиболее часто используемые методы обработки строк static string Copy( string str ) Возвращает копию строки str int CompareTo(string str ) Возвращает отрицательное значение, если вызывающая строка меньше строки str , положительное значение, если вызывающая строка больше строки str , и нуль, если сравниваемые строки равны int IndexOf(string str ) Выполняет в вызывающей строке поиск подстроки, заданной параметром str . возвращает индекс первого вхождения искомой подстроки или -1, если она не будет обнаружена int LastIndexOf(string str ) Выполняет в вызывающей строке поиск подстроки, заданной параметром str . Возвращает индекс последнего вхождения искомой подстроки или -1, если она не будет обнаружена string ToLower() Возвращает строчную версию вызывающей строки string ToUpper() Возвращает прописную версию вызывающей строки Чтобы узнать, равны ли две строки, необходимо использовать оператор "==" Обычно, когда оператор "==" применяется к ссылочным объектам, он определяет, относятся ли обе ссылки к одному и тому же объекту. Но применительно к объектам типа string дело обстоит иначе. В этом случае проверяется равенство содержимого двух строк. То же справедливо и в отношении оператора "!=" . Что касается остальных операторов отношения (например, ">" или ">=" ), то они сравнивают ссылки так же, как и объекты других типов. Рассмотрим программу, которая демонстрирует выполнение ряда операций над строками. // Демонстрация выполнения некоторых операций над строками. using System; class StrOps { public static void Main() { string str1 = "В .NET-программировании без C# не обойтись."; string str2 = string.Copy(str1); 174 Часть I. Язык C# string str3 = "C#-строки — могучая сила."; string strUp, strLow; int result, idx; Console.WriteLine("str1: " + str1); Console.WriteLine("Длина строки str1: " + str1.Length); // Создаем прописную и строчную версии строки str1. strLow = str1.ToLower(); strUp = str1.ToUpper(); Console.WriteLine("Строчная версия строки str1:\n " + strLow); Console.WriteLine("Прописная версия строки str1:\n " + strUp); Console.WriteLine(); // Отображаем str1 в символьном режиме. Console.WriteLine("Отображаем str1 посимвольно."); for(int i=0; i < str1.Length; i++) Console.Write(str1[i]); Console.WriteLine("\n"); // Сравниваем строки. if(str1 == str2) Console.WriteLine("str1 == str2"); else Console.WriteLine("str1 != str2"); if(str1 == str3) Console.WriteLine("str1 == str3"); else Console.WriteLine("str1 != str3"); result = str1.CompareTo(str3); if(result == 0) Console.WriteLine("str1 и str3 равны."); else if(result < 0) Console.WriteLine("str1 меньше, чем str3"); else Console.WriteLine("str1 больше, чем str3"); Console.WriteLine(); // Присваиваем str2 новую строку. str2 = "Один Два Три Один"; // Поиск строк. idx = str2.IndexOf("Один"); Console.WriteLine( "Индекс первого вхождения подстроки Один: " + idx); idx = str2.LastIndexOf("Один"); Console.WriteLine( "Индекс последнего вхождения подстроки Один: " + idx); } } Глава 7. Массивы и строки 175 При выполнении этой программы получаем следующие результаты: str1: В .NET-программировании без C# не обойтись. Длина строки str1: 43 Строчная версия строки str1: в .net-программировании без C# не обойтись. Прописная версия строки str1: В .NET-ПРОГРАММИРОВАНИИ БЕЗ C# НЕ ОБОЙТИСЬ. Отображаем str1 посимвольно. В .NET-программировании без C# не обойтись. str1 == str2 str1 != str3 str1 больше, чем str3 Индекс первого вхождения подстроки Один: 0 Индекс последнего вхождения подстроки Один: 13 С помощью оператора "+" можно конкатенировать (объединить) несколько строк. Например, при выполнении этого фрагмента кода string str1 = "Один"; string str2 = "Два"; string str3 = "Три"; string str4 = str1 + str2 + str3; переменная str4 инициализируется строкой "ОдинДваТри". И еще. Ключевое слово string представляет собой псевдоним для класса System. String , определенного библиотекой классов среды .NET Framework. Таким образом, поля и методы, определяемые типом string , по сути являются полями и методами класса System.String (здесь были представлены только некоторые из них). Подробно класс System.String рассматривается в части П. Массивы строк Подобно другим типам данных, строки могут быть собраны в массивы. Рассмотрим пример. // Демонстрация использования массивов строк. using System; class StringArrays { public static void Main() { string[] str = { "Это", "очень", "простой", "тест." }; Console.WriteLine("Исходный массив: "); for(int i=0; i < str.Length; i++) Console.Write(str[i] + " "); Console.WriteLine("\n"); // Изменяем строку. str[1] = "тоже"; str[3] = "тест, не правда ли?"; Console.WriteLine("Модифицированный массив: "); for(int i=0; i < str.Length; i++) Console.Write(str[i] + " "); } } 176 Часть I. Язык C# После выполнения этой программы получаем такие результаты: Исходный массив: Это очень простой тест. Модифицированный массив: Это тоже простой тест, не правда ли? А вот пример поинтереснее. Следующая программа отображает целочисленное значение с помощью слов. Например, значение 19 будет отображено как словосочетание “один девять”. // Отображение цифр целого числа с помощью слов. using System; class ConvertDigitsToWords { public static void Main() { int num; int nextdigit; int numdigits; int[] n = new int[20]; string[] digits = { "нуль", "один", "два", "три", "четыре", "пять", "шесть", "семь", "восемь", "девять" }; num = 1908; Console.WriteLine("Число: " + num); Console.Write("Число в словах: "); nextdigit = 0; numdigits = 0; /* Получаем отдельные цифры и сохраняем их в массиве п. Эти цифры хранятся в обратном порядке. */ do { nextdigit = num % 10; n[numdigits] = nextdigit; numdigits++; num = num / 10; } while(num > 0); numdigits--; // Отображаем слова. for(; numdigits >= 0; numdigits--) Console.Write(digits[n[numdigits]] + " "); Console.WriteLine(); } } Вот результаты выполнения этой программы: Число: 1908 Число в словах: один девять нуль восемь Глава 7. Массивы и строки 177 В этой программе string -массив digits хранит в порядке возрастания словесные эквиваленты цифр от нуля до девяти. Чтобы заданное целочисленное значение преобразовать в слова, сначала выделяется каждая цифра этого значения, начиная с крайней правой, и полученные цифры запоминаются в обратном порядке в int -массиве с именем n Затем этот массив опрашивается от конца к началу, и каждое целое значение массива n используется в качестве индекса для доступа к элементам массива digits , чтобы отобразить на экране соответствующую строку. Постоянство строк Следующее утверждение, вероятно, вас удивит: содержимое string -объектов неизменно. Другими словами, последовательность символов, составляющих строку, изменить нельзя. Хотя это кажется серьезным недостатком, на самом деле это не так. Это ограничение позволяет C# эффективно использовать строки. Если вам понадобится строка, которая должна представлять собой “вариацию на тему” уже существующей строки, создайте новую строку, которая содержит желаемые изменения. Поскольку неиспользуемые строковые объекты автоматически утилизируются системой сбора мусора, даже не нужно беспокоиться о “брошенных” строках. При этом необходимо понимать, что ссылочные переменные типа string могут менять объекты, на которые они ссылаются. А содержимое созданного string -объекта изменить уже невозможно. Чтобы до конца понять, почему неизменяемые строки не являются препятствием для программиста, воспользуемся еще одним методом класса string : Substring() . Этот метод возвращает новую строку, которая содержит заданную часть вызывающей строки. Поскольку создается новый string -объект, содержащий подстроку, исходная строка остается неизменной, и правило постоянства строк не нарушается. Вот формат вызова метода Substring(): string Substring(int start , int len ) Здесь параметр start означает индекс начала, а параметр len задает длину подстроки. Рассмотрим программу, которая демонстрирует метод Substring() и принцип постоянства строк. // Использование метода Substring(). using System; class SubStr { public static void Main() { string orgstr = "C# упрощает работу со строками."; // Создание подстроки. string substr = orgstr.Substring(4, 14); Console.WriteLine("orgstr: " + orgstr); Console.WriteLine("substr: " + substr); } } А вот как выглядят результаты работы этой программы: orgstr: C# упрощает работу со строками. substr: прощает работу 178 Часть I. Язык C# Как видите, исходная строка orgstr не изменена, а строка substr содержит нужную подстроку. И хотя постоянство string -объектов обычно не является ограничением, не исключены ситуации, когда возможность модифицировать строки могла бы оказаться весьма кстати. Для таких случаев в C# предусмотрен класс StringBuilder , который определен в пространстве имен System.Text . Этот класс создает объекты, которые можно изменять. Однако в большинстве случаев все же используются string -объекты, а не объекты класса StringBuilder Использование строк в switch-инструкциях Для управления switch -инструкциями можно использовать string -объекты, причем это единственный тип, который там допускается, помимо типа int . Эта возможность в некоторых случаях облегчает обработку. Например, следующая программа отображает цифровой эквивалент слов “один”, “два” и “три”. // Демонстрация возможности строкового управления // инструкцией switch. using System; class StringSwitch { public static void Main() { string[] strs = { "один", "два", "три", "два", "один" }; foreach(string s in strs) { switch(s) { case "один": Console.Write(1); break; case "два": Console.Write(2); break; case "три": Console.Write(3); break; } } Console.WriteLine(); } } Вот результат выполнения этой программы: 12321 |