Введение в программирование на C# в .NET — КиберПедия 

История создания датчика движения: Первый прибор для обнаружения движения был изобретен немецким физиком Генрихом Герцем...

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

Введение в программирование на C# в .NET

2021-04-18 94
Введение в программирование на C# в .NET 0.00 из 5.00 0 оценок
Заказать работу

СОДЕРЖАНИЕ

Введение в программирование на C# в.NET. 5

Что такое.NET и зачем она нужна?. 5

Первая программа на C# и основные приемы работы в системе MS Visual Studio 5

Структура программы.. 9

Собственные пространства имен. 10

Особенности языка C#. 11

Полноценный логический тип данных. 11

Оператор switch. 11

Основные классы.. 12

Класс Console. 12

Класс Convert 13

Строковый тип данных string (класс String) 13

Метод Split 16

Enumeration – перечислимый тип. 17

Метод IndexOf() 18

Метод Format 18

Метод ToCharArray. 18

Объектно-ориентированное программирование. 19

Эволюция от структур к классам.. 19

Используем структуры.. 19

Структурный тип как параметр. 20

Помещаем метод в структурный тип. 20

Превращение в класс. 21

Классы и объекты.. 22

Значимые и ссылочные переменные. 22

Конструкторы класса. 26

Статические элементы.. 28

Генерация случайных чисел. 30

Массивы в языке C#. 31

Многомерные массивы.. 33

Класс ArrayList 34

Класс List<>. 36

Инкапсуляция. 36

Обработка ошибок. 38

Свойства класса. 41

Язык UML. 42

Связи между объектами. 43

Наследование (Inheritance) 44

Класс Object 47

Защищенные переменные. 48

Вызов базового конструктора. 49

Переопределение методов. Обращение к «затененным» элементам класса 49

Многоуровневое наследование. 50

Полиморфизм.. 50

Метод ToString. 54

Типичные ситуации проявления полиморфизма. 54

Абстрактные классы и полиморфизм.. 55

ЛИТЕРАТУРА.. 56


Введение в программирование на C# в.NET

Что такое.NET и зачем она нужна?

Многие книги, тематически относящиеся к.NET, начинаются с довольно пространной главы, подробно объясняющей роль.NET. Нам кажется, что подобный подход не совсем удачен. В данном курсе будем придерживаться другой стратегии – компоненты и свойства.NET будут описываться по мере того, как они будут проявляться в процессе создания программных приложений.

Пока остановимся на таком рабочем определении – среда.NET для программиста играет примерно ту же роль, что операционная система для пользователя, то есть приподнимает уровень средств программирования, делая их концепции более близкими к естественным (с точки зрения программиста) и, как следствие, более эффективными в процессе использования.

Благодаря такой роли,.NET прекрасно справляется с задачей обеспечения общего фундамента сразу для нескольких языков программирования. Среди них наиболее актуальными являются C++, Visual Basic, J# и C# (Си шарп). Именно C# будет использоваться нами далее для демонстрации возможностей.NET.

Следует четко отделять полезную функциональность, предоставляемую средой.NET и системой программирования. Существует несколько систем программирования на базе.NET – MS Visual Studio, Sharp Developer и др. Система программирования – это еще один слой, обеспечивающий удобство программирования. И хотя упомянутые системы программирования базируются на одинаковой платформе.NET, они все же отличаются количеством и уровнем услуг. К примеру, в разных системах с разной степенью могут быть реализованы средства автозавершения кода.

Структура программы

Сразу предупредим – в данной главе нет исчерпывающего изложения соответствующей информации. Целью главы является выделение основных структурных свойств программы на языке C#. Постепенно Вы будете узнавать другие подробности на эту тему.

Напомним, что главной структурной единицей программы на языке C++ была функция, а программа состояла из множества таких функций, среди которых обязательно должна быть функция main. Кроме этого на верхнем уровне можно было описывать глобальные переменные и собственные типы данных, такие как struct.

Идея создания программистом собственных типов данных в языке C# приобрела центральное место (как и в других объектно-ориентированных языках). По-прежнему это можно делать с помощью структурных типов struct. Однако еще более полноценную реализацию понятия тип удалось реализовать с помощью концепции классов.

Такие классы, как Console и Convert называются встроенными только по той причине, что они написаны другими программистами и включены в стандартный набор классов. Однако программист может создавать свои собственные классы, обеспечивая их необходимыми ему функциями-методами.

В языке C# самостоятельных функций вообще нет – есть только методы классов, а вся программа – это просто несколько классов, описанных один за другим. Кроме того, пропали глобальные переменные – как убедились разработчики, глобальные переменные привносят в программу много опасных проблем.

Есть еще один важный вопрос: если структура программы однородна и представляет множество классов, то с какого места начинается выполнение программы. Для этого в одном (и только в одном) из классов обязательно доложен быть определен метод Main (с большой буквы!). Таким образом, ошибкой будет и отсутствие метода Main и наличие нескольких методов Main.

Наша первая программа удовлетворяет перечисленным требованиям. Вся она состоит из единственного класса Program. В этом классе содержится только один метод, и он называется Main – как говорится минимальный джентльменский набор.

Ответ на вопрос «что означает слово static в заголовке метода Main» вы узнаете позже, а на вопрос «зачем нужны параметры в методе Main» мы в нашем кратком курсе отвечать не будем – нельзя объять … сами знаете что.

Еще вопросы есть? По-крайней мере еще один важный вопрос должен остаться – что означает конструкция namespace в нашей программе.

Особенности языка C#

Ранее рассмотренные особенности программирования на C# не относились к самому языку, а лишь касались множества доступных для использования методов классов. В данной главе обратим внимание на некоторые особенности в наиболее стабильной части языка – в стандартных операторах. Большинство этих особенностей является результатом очищения языка C++ от своего тяжелого наследия в лице сравнительно низкоуровневого предшественника C.

Оператор switch

Отметим две особенности, отличающие оператор switch в языках C# и C++:

1. C# не поддерживает “провала” передачи управления, возникающего при отсутствии оператора break.

Фрагмент программы

int a; a=2;

switch (a)

{ case 1: cout<<”Один”;

case 2: cout<<”Два”;

case 3: cout<<”Три”;

case 4: cout<<”Четыре”;

}

приводил к выводу на экран строки «ДваТриЧетыре».

В C# аналогичный оператор будет считаться компилятором ошибочным – C# требует в конце каждого исполняемого блока указывать оператор break, либо goto, либо return. Таким образом, можно написать следующее:

int a; a=2;

switch (a)

{ case 1: Console.Write(”Один”); break;

case 2: Console.Write(”Два”); break;

case 3: Console.Write(”Три”); break;

case 4: Console.Write(”Четыре”); break;

}

В результате на экране появится только слово «Два»

2. В качестве выражения и констант, отмечающих варианты оператора switch можно использовать строковые данные (тип string)

string a; a=”Два”;

switch (a)

{ case ”Один”: Console.Write(1); break;

case ”Два”: Console.Write(2); break;

case ”Три”: Console.Write(3); break;

case ”Четыре”: Console.Write(4); break;

}

Такой фрагмент выводит на экран число 2.

Основные классы

Ниже рассмотрим использование наиболее популярных классов платформы.NET

Класс Math

Класс Console

Как вы уже успели заметить, этот класс используется для организации взаимодействия пользователя с компьютером с помощью двух устройств: клавиатура (для ввода информации) и монитор (для вывода). В предыдущих примерах были использованы методы ReadLine и WriteLine. Отметим ещё одну полезную особенность метода WriteLine.

Допустим, нужно вывести на экран значения трёх переменных x, y и z. это можно сделать следующим образом:

Console.WriteLine(“x=”+x+”, y=”+y+”, z=”+z);

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

Console.WriteLine(“x={0}, y={1}, z={2}”,x,y,z);

В этом случае метод WriteLine имеет несколько параметров. Первый обязательно является строкой, определяющей структуру выводимой информации. В этой строке присутствуют специальные элементы ({0}{1}{2}), вместо которых выводятся значения соответствующих других параметров (в нашем примере x, y и z).

Кроме ReadLine и WriteLine класс Console содержит дополнительные методы для управления клавиатурой и выводом информации на монитор. Например, с помощью метода SetCursorPosition можно управлять позицией курсора.

Класс Convert

Мы уже знаем о существовании метода ToInt32, осуществляющего преобразование в целое число. Кроме того, в этом классе имеется ещё ряд методов преобразования: ToBoolean, ToByte, ToChar, ToDateTime, ToDecimal, ToDouble, ToSingle, ToString. Этот (неполный!) перечень методов позволяет сделать следующие выводы:

1. Система основных типов языка в основном унаследована от языка С++, однако, в ней появились и новшества.

Как уже говорилось, в C# имеется полноценный логический тип данных Boolean.

К числу основных типов в C# добавился тип DateTime, позволяющий оперировать календарными датами и временем.

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

Два основных вещественных типа языка C++ (float и double) в C# «поменялись местами» - стандартным теперь является Double. Это означает, что вещественные константы, записываемые обычным образом, относятся к типу Double. Поэтому оператор  a = 0.5  будет признан компилятором ошибочным (несовместимость типов в присваивании), если переменная a имеет тип float.    

Преобразованием в тип float занимается метод  ToSingle.

2. Каждый из методов преобразования является перегруженным. Это позволяет применять метод с одним и тем же именем для преобразования разнотипной исходной информации. Например:

Convert.ToInt32(3.2);

Convert.ToInt32(false);

Convert.ToInt32(“Hello”);

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

Метод Split

Английское слово split означает «разбить на части». Одним вызовом этого метода можно решить популярную задачу разбиения текста на некоторые фрагменты.

Рассмотрим следующую постановку задачи:

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

Очевидно, что для решения этой задачи нужно выделять в тексте отдельные слова. Это достаточно сложный циклический процесс, поскольку слова отделяются друг от друга множеством разных символов-разделителей. Именно эту задачу решает метод Split.

Исходную строку можно получить путём ввода с клавиатуры.

string text = Console.ReadLine();

Для успешной работы методу Split нужно передать параметр – массив символов-разделителей.

char[] separators = new char[] {' ', ',', '.'};

(читатели, знакомые с массивами в языке С++ заметят, что описание и инициализация массивов в C# выглядит несколько иначе; об этом подробнее будет рассказано далее).

Метод Split в результате своей работы возвращает массив строк, полученных в результате выделения их из всего текста на основе информации о символах-разделителях.

string[] words = text.Split(separators);

Вот и всё! Однако, в этой короткой строчке содержится очень важное новшество, непосредственно относящее к объектно-ориентированному программированию. Метод Split вызывается в форме text.Split(…),  то есть метод Split как будто принадлежит строковой переменной text. Это действительно правильная точка зрения, которую Вы должны усвоить в ходе этого курса. Будьте внимательны, поскольку некоторые методы  класса String вызываются «от имени» класса, например, String.Format(…). Такие методы называются статическими. Методы, подобные Split называются нестатическими или методами экземпляра класса.

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

int L = 0;

for(int i = 0; i<words.Length; i++) L += words[i].Length;

double aL = Convert.ToDouble(L) / words.Length;

 Здесь мы видим, что по своей форме цикл for в C# такой же, как и в С++. Новшеством является удобная возможность задать границу цикла (количество элементов массива!) с помощью свойства Length массива words, то есть массив сам содержит информацию о своём размере. Аналогично можно определить и длину (количество символов) одной строки: words[i].Length. Приведение Convert.ToDouble понадобилось для того, чтобы операция «деления» выдала вещественный, а не целый результат.

В C# имеется возможность несколько улучшить последний цикл, используя новую разновидность оператора цикла – foreach.

foreach(string w in words) L += w.Length;

Для читателя, знающего английский язык, этот цикл выглядит очень естественно, поскольку хорошо «переводится» на «человеческий» язык. В переводе на русский это звучит примерно так: «Для каждой строки w из words выполнить некоторое действие». Формальный синтаксис этого оператора следующий:

foreach ( <тип> <переменная> in <массив> ) <тело цикла>;

Далее Вы узнаете, что кроме массивов в цикле foreach можно использовать и другие «контейнерные» типы.

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

foreach(…) { Console.WriteLine(w); L += w.Length; }

Выполнение программы с входной строкой " yes, no hello." покажет, что метод Split разбил эту строку не на три, а на шесть строк: пустая строка, “yes”, пустая строка, “no”,“hello”, пустая строка. Это произошло потому, что метод Split считает «словом» любую строку, расположенную между символами-разделителями. Чтобы избавиться от этой проблемы можно «заставить» метод Split работать несколько иначе – не выделять в качестве «слов» пустые строки между соседними символами-разделителями. Для этого в метод Split нужно передать дополнительный параметр - StringSplitOptions.RemoveEmptyEntries. Теперь вызов метода выглядит так:

string[] words=

 text.Split(separators, StringSplitOptions.RemoveEmptyEntries);

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

Ещё больший интерес представляет форма записи и тип этого параметра. Тип параметра является перечислением (подробнее об этом в следующем параграфе).

Метод IndexOf()

Этот метод возвращает позицию первого вхождения строки, в строке, вызывающей этот метод. Если подстроки в строке нет, метод возвращает -1.

String s=”Hello, Helen!”;

Console.WriteLine(s.IndexOf(“He”); //выводит 0

Console.WriteLine(s.IndexOf(“he”); //выводит -1

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

int k=0;

while (k!=-1)

{

k= s.IndexOf(“He”,k);

Console.WriteLine(k); //выводит 0

k++;

}

Метод Format

Синтаксис и действие этого метода похожи на метод Console.WriteLine. Отличие в том, что WriteLine выводит сформированную строку на экран, а Format возвращает эту строку для дальнейшего использования в программе.

Метод Format является статическим и вызывается от имени класса:

String s=String.Format(“{0}x{1}={2} и {2}={1}x{0}”,2,3,2*3);

В результате сформирована строка “2x3=6 и 6=3*2”

Метод ToCharArray

Набор методов класса String не может быть идеальным средством для решения всех задач обработки текстов. Существует немало даже простых задач, которые «неудобно» решать этими методами. В этом случае остается последнее средство – решать задачу, рассматривая строку как массив символов. Однако, как уже было сказано ранее, индексированный доступ к символам строки возможен только для чтения. Именно в этом случае Вам понадобится метод ToCharArray, который «разбирает» целостный объект-строку на массив символов. В следующем примере решается простая задача инвертирования символов строки:

String s=”телефон”;

char ch;

char chAr=s.ToCharArray();

for(int i=0; i<char.Length/2; i++)

{ ch=chAr[i]; char[i]=chAr[char.Length-i-1]; chAr[char.Length-i-1]=ch; }

s=new String(char);

Пример демонстрирует, что метод ToCharArray возвращает массив символов. Последняя строка показывает, как можно создать объект-строку из массива символов.

Используем структуры

Напомним, как использовать традиционные уже в языке Си структурные типы и их переменные – структуры.

01 struct Person

02 { public string Name;

03   public double Height;

04   public double Weight;

05 }

06 class Program

07 { static void Main(string[] args)

08   { Person me, you;

09     me.Name="Это я"; me.Height=190.0; me.Weight=85;

10     you.Name="Это ты"; you.Height=140.0; you.Weight=85;

11     PersonAnalyze(me.Height,me.Weight,me.Name);

12     PersonAnalyze(you.Height,you.Weight,you.Name);

13   }

14   static void PersonAnalyze(double h,double w,string n)

15   { if (h - w > 100.0) Console.WriteLine(n + " худой");

16     else          Console.WriteLine(n + " полный");

17   }

18 }

В определении структурного типа Person (стр.01-05) новым является только использование слова public в описании переменных. Его роль мы выясним позже.

Класс Program условно можно назвать главным классом, поскольку он содержит метод Main, с которого и начнется выполнение программы.

В методе Main описываются две структуры (переменные структурного типа). В отличие от языка Си, в описании структур ключевое слово struct не указывается. Таким образом, переменные me и you являются переменными типа Person.

Далее с помощью операции доступа к полю (операции точка) и операторов присваивания происходит заполнение переменных me и you информационным содержимым (стр.09-10).

Наконец, в стр. 11 и 12 вызывается метод PersonAnalyze класса Program сначала с данными структуры me, а затем с данными структуры you. Заметим, что метод PersonAnalyze, как и метод Main описан как static. На экране должен появиться следующий результат:

Это я полный

Это ты худой

Превращение в класс

Наконец, рассмотрим последнюю модификацию программы.

01 class Person

02 { public string Name;

03   public double Height;

04   public double Weight;

05   public void PersonAnalyze()

06   { if (Height-Weight>100.0) Console.WriteLine(Name+" худой ");

07     else Console.WriteLine(Name + " полный ");

08   }

09 }

10 class Program

11 { static void Main(string[] args)

12   { Person me;

13     me = new Person();

14     Person you = new Person();

15     me.Name="Это я"; me.Height=190.0; me.Weight=85;

16     you.Name="Это ты"; you.Height=140.0; you.Weight=85;

17     me.PersonAnalyze();

18     you.PersonAnalyze();

19   }

20 }

Во-первых, в заголовке структурного типа слово struct заменено ключевым словом class. Как следствие, в методе Main уже недостаточно только описать переменые. Переменные, порождаемые на основании класса, называются объектами и требуют обязательного создания с помощью операции new. Мы видим, что эту операцию можно выполнить в отдельном операторе присваивания (стр.13) и в момент описания переменной с инициализацией (стр.14).

Пока разница между структурными типами и классами не очень заметна. После прочтения этого пособия Вы сможете аргументировано оценить эту разницу самостоятельно.

Классы и объекты

Теперь мы можем дать предварительное определение понятия класс. Класс – это программная конструкция, определяющая новый тип данных. Для этого в классе определяются переменные и методы. Класс является «правилом» для создания объектов (экземпляров этого класса). Все объекты имеют одинаковый набор переменных. Однако соответствующие переменные различных объектов независимы друг от друга.

Далее можно обращаться к переменным объекта и вызывать методы объекта. Обратите внимание на словосочетание «переменные объекта» - действительно, каждый объект класса имеет свой собственный комплект переменных, описанных в его классе. Множество значений переменных объекта можно называть состоянием объекта. Иначе обстоит дело с методами – они существуют в одном экземпляре и все объекты класса пользуются методами «сообща». Множество методов определяет поведение объектов класса.

Конструкторы класса

Рассмотрим подробнее, как создается объект класса Person:

newMe = new Person();

Сначала в правой части присваивания выполняется операция new, которая резервирует в памяти участок, способный хранить все переменные класса. Однако назвать это действие полноценным созданием объекта нельзя. Здесь не хватает того, что происходит и в реальной жизни – при рождении объект не только занимает место в пространстве, но и получает полный набор значений своих характеристик (начальное состояние объекта). Это необходимо выполнить и в момент создания объекту. Вот почему после операции new указывается не просто тип Person, а вызывается специальный метод, имя которого совпадает с именем класса. Такой метод называется конструктором. Исходя из такой роли конструктора, он должен быть определен в каждом классе.

Конструктор класса имеет несколько синтаксических особенностей:

1. Обычно (но не всегда!) конструктор описывается как public.

2. При определении конструктора в заголовке не указывается тип возвращаемого значения. Конструктор в принципе ничего не может возвращать, поэтому даже ключевое слов void здесь будет неуместно.

3. Имя конструктора всегда совпадает с именем класса.

Обсудим теперь список параметров конструктора. Логично, что через фактические параметры при вызове конструктора должны быть указаны данные, позволяющие определить состояние объекта. Поэтому, например, для создания полноценного объекта класса Person можно указать параметры для имени, веса и роста:

public Person(string n, double h, double w)

{ name=n; Height=h; Weight=w; }

При желании мы можем использовать имена параметров, совпадающие с именами переменных классов. Однако в этом случае придется использовать ключевое слово this для решения проблемы коллизии имен (совпадения имен двух переменных в одной области видимости):

public Person(string Name, double Height, double Weight)

{ this.Name= Name; this.Height= Height; this.Weight= Weight; }

Слово this указателем на текущий объект. Это слово обозначает объект данного класса, который вызвал метод.

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

public Person(int age)

{ Height=table[age].height;Weight= table[age].weight; }

Заметим, что этот конструктор не обеспечивает назначение объекту имени

Person p = new Person(10);

p.PersonAnalyze();

Такой эксперимент покажет, что объект, на который ссылается переменная p, имеет имя “”, то есть пустую строку. Это стандартное «нулевое» значение, которое автоматически присваивается строковым переменным, если это инициализация не была выполнена явно. Для переменных числовых типов таким стандартным значением является 0, а для логических переменных - false.

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

В классе можно объявить статический конструктор с атрибутом static. Он вызывается автоматически - его не нужно вызывать стандартным образом. Точный момент вызова не определен, но гарантируется, что вызов произойдет до создания первого объекта класса. Такой конструктор может выполнять некоторую предварительную работу, которую нужно выполнить один раз, например, связаться с базой данных, заполнить значения статических полей класса, создать константы класса, выполнить другие подобные действия. Статический конструктор, вызываемый автоматически, не должен иметь модификаторов доступа. Вот пример объявления такого конструктора в классе Person:

static Person()

{ Console.WriteLine("Выполняется статический конструктор!"); }

Особую роль играет конструктор без параметров. Его называют конструктором по умолчанию, поскольку он может использовать для инициализации переменных объекта некоторые стандартные значения. Мы можем определить, например, такой конструктор:

public Person(){ name=”noname”; Height=50; Weight=4; }

Если в классе явно не определен ни один конструктор, то конструктор по умолчанию генерируется компилятором. Однако ничего интересного такой конструктор не выполняет – он инициализирует переменные объекта стандартными «нулевыми» значениями. Если в составе класса имеется переменная, являющаяся объектом некоторого класса, то в этом случае она будет иметь значение null – специальное слово, обозначающее отсутствие ссылки на объект в памяти.

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

Вызов перегруженного конструктора

Статические элементы

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

1. Сначала создайте классы – полезные правила для создания объектов.

2. Далее создавайте объекты – экземпляры этих классов и заставляйте их выполнять нужную Вам работу.

Таким образом в процессе выполнения программы непосредственно действуют объекты. Однако в некоторых случаях естественнее считать, что действия выполняются самим классом. Хорошим примером является класс Math – все его методы вызываются от имени класса:

Console.WriteLine(Math.Sin(0.5)+Math.Cos(0.5));

Было бы довольно странно для вычисления математических функций сначала создавать объект-«математику»:

Math m1=new Math();

Console.WriteLine(m1.Sin(0.5)+m2.Cos(0.5));

Это означало бы, что можно создавать несколько «математик». Но ведь они все должны действовать абсолютно одинаково.

Аналогично дело обстоит и с переменными – бывают случаи, когда некоторые данные естественнее представлять не как порцию информации, принадлежащую объекту, а классу в целом. В классе Math эта ситуация встречается при доступе к тригонометрической константе p - Math.PI.

Элементы класса (переменные и методы), которые соотносятся не с объектами, а с классом, называются статическими. В описании статических элементов используется ключевое слово static. В отличие от статических членов обычные элементы класса называются элементами экземпляров класса – методы экземпляров и переменные экземпляров.

Переменные экземпляра описываются в классе и хранятся в объектах класса. Статические переменные описываются и хранятся (в единственном числе) в классе, как показано на следующем рисунке:

 

Рассмотрим следующий пример. Класс Person моделирует ситуацию, когда множество людей располагают некоторым общим запасом пищи и делят его поровну. Количество пищи FoodQuantity и количество людей PeopleCount – характеристики всей совокупности людей. Поэтому они описаны как статические переменные. Метод Description описывает состояние всего множества людей и также является статическим методом.

class Person

{ public static double FoodQuantity = 100.0;

public static int PeopleCount = 0;

public double Weight;

public Person()

{ Weight = 10.0; PeopleCount++; }

public void ToEat()

{ double t = FoodQuantity / 2.0 / PeopleCount;

Weight += t; FoodQuantity -= t;

}

public static string Description()

 { return String.Format("Людей - {0} Еды - {1}",

PeopleCount,FoodQuantity);

  }

}

class Program

{ static void Main(string[] args)

{ Person p1 = new Person(); p1.ToEat();

Console.WriteLine("Вес p1 - {0}", p1.Weight);

Console.WriteLine(Person.Description());

Person p2 = new Person(); Person p3 = new Person();

p1.ToEat(); Console.WriteLine("Вес p1 - {0}",p1.Weight);

Console.WriteLine(Person.Description());

}

}

Статические методы могут использовать только статические переменные  и другие статические методы класса. Попытка использовать в методе Description переменную Weight привела бы к ошибке компилятора.

Для инициализации статических переменных можно использовать статический конструктор:

static Person()

{ FoodQuantity = 100.0; PeopleCount = 0; }

Если в начале изучения языка C# не сразу используются его объектно-ориентированные возможности, структура программы выглядит примерно следующим образом:

class Program

{ static void Main(string[] args)

{ Do1();

Console.WriteLine(Do2());

}

static void Do1() { Console.WriteLine("метод Do1"); }

static string Do2() { return "метод Do2"; }

}

Таким образом, можно не обращать внимания на наличие в программе класса Program. Программа сстоит из нескольких статических методов, которые вызываются в «главном» методе Main.

Генерация случайных чисел

Необходимость в создании последовательности случайных чисел возникает в программировании довольно часто. Кроме того, способ, которым это делается в C#, характерен с точки зрения объектно-ориентированного подхода. В других языках программирования для генерации случайных чисел имеется некоторая функция (например, Random в языке Pascal).  Следует учитывать детерминированную природу алгоритма генерации случайных чисел. Обычно такие алгоритмы основаны на некотором перемешивании цифр начального числа («зерна»). Для того чтобы серия случайных чисел каждый раз была другой, в качестве начального зерна алгоритму следует передавать различные значения начального зерна. Для этого в языке Pascal имеется функция Randomize, использующая в качестве начального зерна текущее системное время.

Однако в C# нет самостоятельных функций, а только методы классов. В качестве ближайшей аналогии функциям Random и Randomize можно использовать статические методы некоторого класса. Однако на самом деле в C# имеется класс Random c набором методов для генерации случайных чисел. Таким образом, фрагмент программы, генерирующий два случайных числа, может выглядеть так:

Random rnd=new Random();

//целое случайное число в диапазоне от 1 до 6

int i=rnd.Next(1,7);

//целое вещественное число в диапазоне от 0 до1

double d=rnd.NextDouble();

Таким способом решается и проблема уникального начального зерна – при создании нового объекта в качестве начального зерна неявно используется текущее системное время. Тем не менее, класс Random следует использовать внимательно. Следующий фрагмент продемонстрирует две одинаковые серии случайных чисел – i11 будет равно i21, а i12 – равно i22.

Random rnd1=new Random();

Random rnd2=new Random();

int i11=rnd.Next(1,7);

int i12=rnd.Next(1,7);

int i21=rnd.Next(1,7);

int i22=rnd.Next(1,7);

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

Массивы в языке C#

Хотя основные приемы использования массивов C# унаследовал от C++, следует обратить внимание на ряд важных особенностей.

Каждый массив является объектом класса System.Array. Поэтому в жизненном цикле массива имеется стадия описания массива и стадия создания массива. Внимание – в описании массива не указывается размер (количество элементов):

int [] Arr1;

Person [] Arr2;

Как и раньше, массив – это коллекция однотипных элементов. Массив Arr1 будет содержать целые числа, а массив Arr2 – объекты класса Person. Еще отметим «перемещение» пары квадратных скобок – они в C# указываются перед именем массива. Таким образом, конструкция «int []» является полноценным описателем типа массива.

Далее массив можно создать и на этой стадии нам понадобится операция new:

Arr1 = new int[10];

Использование new почти не изменилось – после new нужно указать тип объекта, а у нас это int[]. Только теперь в квадратных скобках нужно указать количество элементов массива. Кроме того, отсутствуют скобки со списком параметров.

Дальнейшее использование массива может происходить обычным образомю Например:

for (int i=0; i<10; i++) Arr1[i] = i*2;

Как видите, нумерация, по прежнему начинается с 0.

Аналогично поступим с массивом Arr2:

Arr2 = new Person[Arr1[3]];

Здесь проявилась замечательная особенность массивов в C# - их размер может задаваться выражением, значение которого определится только во время выполнения программы. Более того, Вы можете заново создать массив:

Arr2 = new string[Arr2.Length + 2];

Здесь мы воспользовались свойством Length класса System.Array. В результате размер нового массива больше на 2 элемента размера старого. Но учтите, что «новый» массив не содержит элементов старого массива – ведь это совсем новый объект в новом месте памяти.

Отметим, что в примере мы не инициализировали массив. В отношении массива требование инициализации не действует. Дело в том, что при создании массива с помощью new создается множество ссылок, каждая из которых содержит «пустой» указатель null. Этого достаточно, чтобы C# позволил приступить к использованию массива. Однако здесь появляется возможность для ошибки во время выполнения массива – если Вы попытаетесь использовать объект, на который ссылается такая null-ссылка. Поэтому нужно выполнить что-то в таком духе:

for (int i=0; i<Arr2.Length; i++) Arr2[i]=new Person();

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

static int[] ModifyArray(int[] inArr)

{ int [] rArr = new int[inArr.Length * 2];

for (int i = 0; i < inArr.Length; i++)

{ rArr[i] = inArr[i]; rArr[inArr.Length + i] = 0; }

return rArr;

}

Этот метод можно использовать следующим образом:

int[] Arr3 = ModifyArray(Arr1);

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

Следует отметить, что кроме обычных приемов работы с массивами (обращение к элементам с помощью индекса, циклы и т.д.) класс System.Array предоставляет ряд дополнительных и весьма полезных методов. Некоторые из них перечислены в следующей таблице:

Метод Описание
static int IndexOf (Array array, Object value) Возвращает первое вхождение значения value в массив array. Если array не содержит заданного значения, метод возвращает отрицательное целое число.
public static void Sort (Array array) Сортирует элементы во всем одномерном массиве array.
static int BinarySearch (Array array, Object value) Быстрый поиск методом половинного деления позиции значения value в объекте array. Перед вызовом этого метода объект array необходимо отсортировать. Если array не содержит заданного значения, метод возвращает отрицательное целое число.

Многомерные массивы

Рассмотрим только 2-х мерные массивы. Использование массивов большей размерности принципиально не отличается. Существуют два вида таких массивов – прямоугольные и «рваные» (ступенчатые).

У прямоугольного массива все строки имеют одинаковое количество элементов (также как и столбцы). Такая структура в математике называется двумерной матрицей.

Описание двумерного массива выглядит следующим образом:

int [, ] matrix;

При создании такого массива, как обычно, используется операция new:

matrix = new int[3,4];

Здесь создается прямоугольный массив из 3-х строк и 4-х столбцов. Дальнейшее использование такого массива вполне традиционно. Например, можно заполнить весь массив содержимым следующим образом:

for(int i = 0; i<matrix.GetLength(0); i++)

for (int j = 0; j< matrix.GetLength(1); j++)

matrix[i, j]=i*j;

Обратим внимание, что для определения количества шагов в циклах перебора вместо свойства Length (общее количество элементов), нужно использовать метод GetLength с параметром – номером измерения.

Другой тип многомерных массивов – рваные массивы – следует представлять как одномерный массив, элементами которого являются в свою очередь, массивы. Описание такого массива несколько отличается:

int [][] jag


Поделиться с друзьями:

Двойное оплодотворение у цветковых растений: Оплодотворение - это процесс слияния мужской и женской половых клеток с образованием зиготы...

Семя – орган полового размножения и расселения растений: наружи у семян имеется плотный покров – кожура...

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

История развития пистолетов-пулеметов: Предпосылкой для возникновения пистолетов-пулеметов послужила давняя тенденция тяготения винтовок...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.221 с.