Урок C# №10: Массивы и цикл foreach
Ну вот мы и подбираемся к фундаменту C#. Сегодня мы поговорим с вами о такой структуре данных, как массив и завершим серию уроков по циклам последним их представителем — циклом для каждого (foreach).
Если вы практиковались в программировании, то наверняка могли обратить внимание на то, что порой несколько переменных одного типа гораздо удобнее было бы объединить в некоторое множество. Например, в программе нужно обрабатывать фамилии класса, группы или коллег по работе.
Безусловно, можно спокойно объявлять переменные в таком количестве, сколько они понадобятся. У вас тридцать сотрудников? Сделаем тридцать переменных, что нам это стоит. 300 сотрудников? Трудно, но не беда. 3000? Какого хрена я пришел на эту дурацкую работу, скажете вы и бросите этот код :).
Я не случайно предварил тему о массивах циклами. Сейчас вы поймете для чего я это сделал.
Итак, вернемся к нашей многострадальной задаче о сотрудниках. Допустим, нам нужно будет вести список всех сотрудников по фамилиям. Зная о циклах, мы легко можем автоматизировать ввод этих фамилий. Нам просто нужно будет объявлять переменные для их хранения. Однако тут мы столкнемся с одним но. Цикл всего лишь автоматизирует участок кода, а нам нужно будет сделать так, чтобы фамилии хранились в разных переменных, иначе мы рискуем переписывать их многократно и не справится с задачей. Мы могли бы поступить так:
string sotr1,sotr2,sotr3,sotr4,sotr5; Console.WriteLine (“Введите фамилию сотрудника”); sotr1=Console.ReadLine (); Console.WriteLine (“Введите фамилию сотрудника”); sotr2=Console.ReadLine (); Console.WriteLine (“Введите фамилию сотрудника”); sotr3=Console.ReadLine (); Console.WriteLine (“Введите фамилию сотрудника”); sotr4=Console.ReadLine (); Console.WriteLine (“Введите фамилию сотрудника”); sotr5=Console.ReadLine ();
Как видите, цикл сам напрашивается собой. Мы могли бы занести в тело цикла ввод фамилии и вывод надписи о том, что нужно ввести эту самую фамилию. Однако в нашем примере у нас есть пять переменных. Если мы будем ставить одну из этих переменных в цикл, то ничего у нас не выйдет (попробуйте сами).
Не кажется ли вам, что гораздо проще будет сделать некую абстрактную переменную, допустим sotr, и приращивать к ней по единице? Ведь у нас вопрос сразу решится.
Увы, теми средствами, что мы сейчас знаем, это нельзя сделать. В этом случае нам придется воспользоваться массивами.
Что такое массив? Это набор элементов одного типа, имеющий общее имя. Так как это набор элементов, то к каждому элементу, входящий в массив можно обратиться по его индексу.
Синтаксис массива в C# несколько отличается от C или C++. Дело в том, что на C# нужно не только объявить массив, но и выделить для него память.
Давайте я поясню, что это означает. Помните, я говорил, что переменные в C# хранятся в стеке, а объекты — в куче (или хипе, кто как привык). Стек удобный и быстрый, но за это удобство нужно расплачиваться. Такой платой является ограниченный размер стека. Если мы запихнем в стек большие структуры данных, мы получим любимый хакерами «stack overflow» или переполнение стека. Чтобы этого не произошло, массивы и объекты помещают в кучу, представляющую собой нечто иное как оперативную память компьютера.
В C++ (равно как и в C) массив почти тоже самое, что и указатель. Это означает, что массив автоматически размещается в куче, хотя там также можно создавать и динамические массивы.
В C# указатели хотя и имеются, но код с ними считается небезопасным и сопряжен с некоторыми трудностями компиляции (нужно будет «убедить» вашу среду разработки в том, что код можно запускать. Об этом мы поговорим в отдельном уроке). Поэтому для массива по указанным выше причинам необходимо выделять память.
Итак, мы знаем, что массив — это набор переменных одного типа, имеющий общее имя. Поэтому его объявление выглядит так:
тип_данных [] имя_массива;
Обратите внимание на квадратные скобки — они как раз и указывают C# о том, что это массив. В качестве типов данных могут выступать и пользовательские, такие как структуры и объекты, но о них поговорим в других уроках.
Теперь, применяя эти знания в задаче с сотрудниками, у нас получится такое объявление:
string [] sotr;
Мы объявили массив, но пользоваться им еще не можем. Чтобы начать его использовать, нам нужно выделить память. Делается это так:
new string [размер_массива];
В квадратных скобках указывается размер в цифрах. Чтобы сэкономить место в коде, обычно в C# (равно как и в Java) объявление массива объединяют вместе с выделением памяти для него:
string [] sotr=new string [5];
Не хочу этот урок делать слишком длинным, поэтому могу сказать, что это — одномерный массив. Есть еще и двумерный, и трехмерный, и так называемый зубчатый массив, разреженный массив и т. д. О некоторых мы поговорим в другом уроке. Но обычно применяются максимум двумерные массивы.
Доступ к элементам массива осуществляется по индексу, причем его отсчет начинается с 0. Однако прежде чем обратиться к элементу массива, его нужно туда поместить. Сделать это можно двумя способами: при инициализации и при явном присвоении элементу массива нужного значения. Давайте посмотрим, как это делается.
Массив можно объявлять при инициализации. Этот способ весьма удобен, когда мы знаем наперед количество элементов массива и его состав. Например, наш список сотрудников будет выглядеть примерно таким образом:
string [] sotr={“Иванов”,”Петров”,”Сидоров”,”Борисов”,”Сергеев”};
Мы получим массив из пяти элементов, содержащих фамилии сотрудников. Таким образом часто полезно создавать массивы при написании методов преобразования чисел в строчные эквиваленты (особенно это актуально при написании бухгалтерских программ) и т.п.
Тем не менее, достаточно часто массивы бывают просто огромными по своей структуре. Представьте себе инициализацию массива, в который нужно внести 1000 или 10000 фамилий. Представляете себе размер кода?
Гораздо проще будет вносить данные в массив традиционным способом – поэлементно. В этом случае нам нужно будет создать массив и вносить в него нужные данные.
string [] sotr=new string [10]; sotr[0]=”Иванов”; ... sotr [9]=”Михайлов”;
Хорошо, скажете вы, но в чем же выгода массива? Ведь мы могли 10 раз написать различные переменные с нужными фамилиями и задача бы была выполнена. Кроме того, забегая несколько вперед, скажу, что предыдущий фрагмент можно смело назвать китайским кодом. Нормальные программисты так не пишут.
Что дает нам массив? Теперь мы можем манипулировать его элементами в цикле, обращаясь по индексу. А это означает, что теперь мы можем и отсортировать содержимое массива и т.д.
Смотрите, как теперь можно ввести данные о сотрудниках в массив, используя цикл.
for (int i=0;i<sotr.Length;i++) { Console.WriteLine (“Введите фамилию сотрудника”); sotr[i]=Console.ReadLine (); } Console.ReadKey();
Вставьте этот код в метод Main и запустите его на компиляцию. Видите, насколько более гибок данный подход? Мы перекладываем задачу по наполнению массива уже на пользователя программы и он может вносить туда буквально все (особенно, в нашей программе, в которой напрочь отсутствуют проверки ввода данных :)) Потом мы можем написать метод, который запишет наш массив в файл и потом уже из него его считывать при запуске программы. Но об этом в других уроках.
Так как мы знаем количество элементов массива, то нам идеально подходит цикл с параметром, чем я и не преминул воспользоваться. Наверняка вас обескуражила запись условия i<sotr.Length. Дело в том, что массив – это объект. И у него есть куча своих методов и свойств. Length возвращает количество элементов массива. Мы могли бы написать i<10 и код бы выполнился, но здесь чревато ошибиться с вводом диапазона. Поэтому гораздо удобнее работать именно с методами массива.
Однако предостерегу вас от возможных ошибок. У меня в этом коде перебирается всего 10 элементов, поэтому я могу вольготно вызывать метод (точнее, это свойство). Однако часто в программах нужно будет манипулировать с тысячами элементов. Если писать так, как в этом участке кода, вы столкнетесь с неизбежной проблемой производительности, так как постоянно придется вызывать это свойство. Чтобы избежать этого, нужно просто считать количество элементов массива в отдельную переменную и работать уже с ней:
long len=sotr.Length; int i=0; while (i<len) { sotr[i]=Console.ReadLine (); i++; }
Не пугайтесь, это просто разновидность предыдущего кода.
Пока мы оставим в покое массивы до следующего урока, а сейчас рассмотрим последний цикл foreach.
Цикл foreach
В C# имеется замечательный цикл ля перебора элементов любого множества значений. Вы видели, что я использовал циклы с пред условием и с параметром для перебора элементов массива. Например, для вывода элементов массива мне бы пришлось писать примерно такой код:
for (int i=0;i<sotr.Length;i++) Console.WriteLine (sotr[i]);
Однако такой код может показаться несколько громоздким. Поэтому гораздо проще использовать цикл foreach (при переборе коллекций он вообще незаменим):
foreach(string s in sotr) Console.WriteLine(s);
Синтаксис такого цикла выглядит так:
foreach (Тип_переменной-цикла имя_переменной in Набор_значений)
Часто его не любят применять новички, однако при работе с другими наборами значений вы поймете всю прелесть это цикла.
Напоследок давайте рассмотрим еще один оператор прерывания цикла –continue. Если break полностью прерывает цикл, то continue вызывает очередную итерацию. Несмотря на то, что в примере я буду использовать цикл Для каждого, continue применяется во всех типах циклов, учтите это.
Итак, у нас стоит задача найти количество однофамильцев Сидорова среди сотрудников. Мы будем решать ее таким образом (учтите, это далеко не самый правильный способ, просто он пришел мне в голову в качестве примера использования continue):
string []sotr={"Иванов","Петров","Сидоров","Борисов","Сергеев","Сидоров"}; int k=0; //счетчик однофамильцев foreach(string s in sotr) { if (s=="Сидоров") { k++; continue; } Console.WriteLine (s); } Console.WriteLine ("Сидоров"); Console.WriteLine("У вас {0} Сидорова",k);
Я сильно упрости задачу, но тем не менее, она показывает работу оператора continue. В цикле мы проверяем на соответствие элемента массива заданной фамилии и если она совпадает, то мы увеличиваем счетчик и переходим к следующей итерации. В остальных случаях мы выводим фамилию сотрудника.
Вообще, в жизни подобные ситуации встречаются часто, но решаются они несколько иным способом. Этот просто служит в качестве примера.
На этом пока все. До следующего урока!