C# — Функции в c#


Урок 4 Функции и процедуры.

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

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

Что нам предстоит сделать в этом уроке?! Познакомится с функциями и процедурами в C#.

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

Функция – это такая же процедура, только ей свойственно явное возвращение результат ее работы.

Чтобы стало ясно, приведу пример:

static void CopyString(string a, ref string b)
<
b = a+b;
>

static string ReturnString(string a, string b)
<
return a + b;
>

Из анализа приведенного кода видно, что процедура CopyString – не возвращает значений с помощью слова return, те на прямую. Она возвращает значения через ref – указатель. Подробнее о нем, вы можете почитать в книгах по C#, а можете не запариватся и просто запомнить, что если вам нужно поменять значение какой-либо переменной в процедуре и возвратить это значение не явно используйте ref.

Да чуть не забыл, ref можно применять как в функциях так и в процедурах и не один раз. Например:

static string ReturnString(string a, string b,ref string c)
<
c=a+b+” Test”;
return a + b;
>

Что будет если мы засунем переменные a,b,c в функцию?! Например

a=Hello
b=Artem
e = ReturnString(a,b,c);

e = HelloArtem
c = HelloArtem Test

Как определить что перед вами процедура или функция и как вообще их писать?!

Запомните процедура всегда начинается со слова void, а функция с названия класса или типа, который она возвращает. Напрмер string в нашем случае.

Пишется процеру просто:

Void (параметры)
<
тело процедуры
>
(параметры)
<
Тело функции
Return ;
>

: позволяет видеть или скрывать процедуры для их вызова, может быть: public private. Так же существуют можификатор static.

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

Так с процедурами и функциями пока все). Хотя наверно стоит сказать, что процедуры и функции нужны для того, чтобы оптимизировать код программы:

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

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

Лекция 1.5 Процедуры и функции – методы класса

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

Процедуры и функции – методы класса

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

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

Процедуры и функции связываются теперь с классом, они обеспечивают требуемую функциональность класса и называются методами класса. Поскольку класс в объектно-ориентированном программировании рассматривается как некоторый тип данных, то главную роль в классе начинают играть его данные – поля класса, задающие свойства объектов класса. Методы класса «служат» данным, занимаясь их обработкой. Помните, в C# процедуры и функции существуют только как методы некоторого класса, они не существуют вне класса.

В данном контексте понятие класс распространяется и на все его частные случаи – структуры, интерфейсы, делегаты.

В языке C# нет специальных ключевых слов – method, procedure, function, но сами понятия конечно же присутствуют. Синтаксис объявления метода позволяет однозначно определить, чем является метод – процедурой или функцией.

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

Процедуры и функции. Отличия

Функция отличается от процедуры двумя особенностями:

  • всегда вычисляет некоторое значение, возвращаемое в качестве результата функции;
  • вызывается в выражениях.

Процедура C# имеет свои особенности:

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

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

Описание методов (процедур и функций). Синтаксис

Синтаксически в описании метода различают две части – описание заголовка и описание тела метода:

Рассмотрим синтаксис заголовка метода:

Имя метода и список формальных аргументов составляют сигнатуру метода. Заметьте, в сигнатуру не входят имена формальных аргументов, здесь важны типы аргументов. В сигнатуру не входит и тип возвращаемого результата.

Квадратные скобки (метасимволы синтаксической формулы) показывают, что атрибуты и модификаторы могут быть опущены при описании метода. Подробное их рассмотрение будет дано в лекциях, посвященных описанию классов. Сейчас же упомяну только об одном из модификаторов – модификаторе доступа. У него четыре возможных значения, из которых пока рассмотрим только два – public и private. Модификатор public показывает, что метод открыт и доступен для вызова клиентами и потомками класса. Модификатор private говорит, что метод предназначен для внутреннего использования в классе и доступен для вызова только в теле методов самого класса. Заметьте, если модификатор доступа опущен, то по умолчанию предполагается, что он имеет значение private и метод является закрытым для клиентов и потомков класса.

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

Методы A и B являются закрытыми, а метод С – открыт. Методы A и С реализованы процедурами, а метод B – функцией, возвращающей целое значение.

Список формальных аргументов

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

Рассмотрим теперь синтаксис объявления одного формального аргумента:

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

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

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

Для иллюстрации давайте рассмотрим группу методов класса Testing из проекта ProcAndFun, сопровождающего эту лекцию:

/// Группа перегруженных методов Cube()

/// первый аргумент — результат

/// представляет сумму кубов

/// произвольного числа оставшихся аргументов

/// Аргументы могут быть разного типа.


void Cube(out long p2, int p1)

p2 = (long)Math.Pow(p1, 3);

void Cube(out long p2, params int[] p)

p2 = 0; for (int i = 0; i

/// Функция с побочным эффектом

Увеличивается на 1

/// значение a на входе

Четыре перегруженных метода с именем Cube и метод F будут использоваться при объяснении перегрузки и побочного эффекта. Сейчас проанализируем только их заголовки. Все методы закрыты, поскольку объявлены без модификатора доступа. Перегруженные методы с именем Cube являются процедурами, метод F – функцией. Все четыре перегруженных метода имеют разную сигнатуру. Хотя имена и число аргументов у всех методов одинаковы, но типы и ключевые слова, предшествующие аргументам, различны. Первый аргумент у всех четырех перегруженных методов является выходным и сопровождается ключевым словом out, в теле метода этому аргументу присваивается значение. Аргумент функции F является обновляемым, он снабжен ключевым словом ref, в теле функции используется его значение для получения результата функции, но и само значение аргумента изменяется в теле функции. Два метода из группы перегруженных методов используют ключевое слово params для своего последнего аргумента. Позже мы увидим, что при вызове этих методов этому аргументу будет соответствовать несколько фактических аргументов, число которых может быть произвольным.

Тело метода

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

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

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

Вызов метода. Синтаксис

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

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

Сам вызов метода, независимо от того, процедура это или функция, имеет один и тот же синтаксис:

Если это оператор, то вызов завершается точкой с запятой.

Формальный аргумент, задаваемый при описании метода, синтаксически является идентификатором – именем аргумента. Фактический аргумент – представляет собой «выражение», значительно более сложную синтаксическую конструкцию. Вот точный синтаксис фактического аргумента:

О соответствии списков формальных и фактических аргументов

Между списком формальных и списком фактических аргументов должно выполняться определенное соответствие по числу, порядку следования, типу и статусу аргументов. Если в первом списке n формальных аргументов, то фактических аргументов должно быть не меньше n (соответствие по числу). Каждому i-му формальному аргументу (для всех i от 1 до n-1) ставится в соответствие i-й фактический аргумент. Последнему формальному аргументу при условии, что он объявлен с ключевым словом params, ставятся в соответствие все оставшиеся фактические аргументы (соответствие по порядку). Если формальный аргумент объявлен с ключевым словом ref или out, то фактический аргумент должен сопровождаться таким же ключевым словом в точке вызова (соответствие по статусу).

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

Если T – тип формального аргумента, то выражение, задающее фактический аргумент, должно быть согласовано по типу с типом T. Это означает, что вычисленный тип выражения совпадает c типом T или допускает неявное преобразование к типу T, или является потомком типа T (соответствие по типу).

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

Вызов метода. Семантика

Что происходит в момент вызова метода? Выполнение начинается с вычисления фактических аргументов, которые, как мы знаем, являются выражениями. Вычисление этих выражений может приводить в свою очередь к вызову других методов. Так что первый этап может быть довольно сложным и требовать больших временных затрат. В чисто функциональном программировании все вычисление по программе сводится к вызову одной функции, фактические аргументы которой содержат вызовы функций, фактические аргументы которых содержат вызовы функций, и так далее и так далее. И на языке C# можно организовать вычисления подобным образом.

Для простоты понимания семантики вызова можно полагать, что в точке вызова создается блок, соответствующий телу метода (реально все происходит значительно эффективнее). В этом блоке происходит замена имен формальных аргументов фактическими аргументами. Для выходных (ref и out) аргументов, для которых фактические аргументы также являются именами, эта замена или передача аргументов происходит по ссылке, означая замену формального аргумента ссылкой на реально существующий объект, заданный фактическим аргументом. Чуть более сложную семантику имеет вызов по значению, применяемый к формальным аргументам, объявленным без ключевых слов ref и out. При вычислении выражений, заданных такими фактическими аргументами, их значения присваиваются специально создаваемым временным переменным, локализованным в теле исполняемого блока. Имена этих локализованных переменных и подставляются вместо имен формальных аргументов. Понятно, что тип локализованных переменных определяется типом соответствующего формального аргумента.

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

Каково следствие семантики вызова по значению? Если вы забыли указать ключевое слово ref или out для аргумента, фактически являющегося выходным, то к нему будет применяться вызов по значению. Даже если в теле метода происходит изменение значения этого аргумента, то оно действует только на время выполнения тела метода. Как только метод заканчивает свою работу (завершается блок) все локальные переменные (в том числе созданные для замены формальных аргументов) оканчивают свое существование, так что изменения не затронут фактических аргументов, и они сохранят свое значение, бывшее у них до вызова. Отсюда вывод: все выходные аргументы значимых типов, значения которых предполагается изменить в процессе работы, должны иметь ключевое слово ref или out.

Говоря о семантике вызова по ссылке и по значению, следует сделать важное уточнение. В объектном программировании, каковым является и программирование на C#, основную роль играют ссылочные типы – мы работаем с классами и объектами. Когда методу передается объект ссылочного типа, то все поля этого объекта могут в методе меняться самым беззастенчивым образом. И это несмотря на то, что объект формально не является выходным, не имеет ключевых слов ref или out, использует семантику вызова по значению. Сама ссылка на объект при этом, как и положено, остается неизменной, но состояние объекта, его поля могут полностью обновиться. Такая ситуация типична и представляет один из основных способов изменения состояния объектов. Именно поэтому ref или out не столь часто появляются при описании аргументов метода.

Что нужно знать о методах?

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

Почему у методов мало аргументов?

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

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

Поля класса или функции без аргументов?

Поля хранят информацию о состоянии объектов класса. Состояние объекта динамически изменяется в ходе вычислений – обновляются значения полей. Часто возникающая дилемма при проектировании класса: что лучше – создать ли поле, хранящее информацию, или создать функцию без аргументов, вычисляющую значение этого поля всякий раз, когда это значение понадобится. Решение дилеммы – это вечный для программистов выбор между памятью и временем, Если предпочесть поле, то это приводит к дополнительным расходам памяти. Они могут быть значительными, когда создается большое число объектов, ведь поле должен иметь каждый объект. Если предпочесть функцию, то это потребует временных затрат на вычисление значения, они могут быть значительными, если функция вызывается многократно, а ее вычисление требует значительно больших затрат в сравнении с выбором текущего значения поля.

Если бы синтаксис описания метода допускал отсутствие скобок у функции (метода), в случае, когда список аргументов отсутствует, то клиент класса мог бы и не знать, обращается он к полю или к методу. Такой синтаксис принят, например, в языке Eiffel. Преимущество такого подхода в том, что изменение реализации никак не сказывается на клиентах класса. В языке C# это не так. Когда мы хотим получить длину строки, то пишем s.Length, точно зная, что Length – это поле, а не метод класса string. Если бы по каким-либо причинам разработчики класса string решили изменить реализацию и заменить поле Length соответствующей функцией, то ее вызов имел бы вид s.Length().

Пример: Две версии класса Account

Продемонстрируем рассмотренные выше вопросы на примере проектирования классов Account и Account1, описывающих такую абстракцию данных, как банковский счет. Определим на этих данных две основные операции – занесение денег на счет и снятие денег. В первом варианте – классе Account – будем активно использовать поля класса. Помимо двух основных полей credit и debit, хранящих приход и расход счета, введем поле balance, задающее текущее состояние счета и два поля, связанных с последней выполняемой операцией. Поле sum будет хранить сумму денег текущей операции, а поле result – результат выполнения операции. Увеличение числа полей класса приведет, как следствие, к уменьшению числа аргументов у методов класса. Вот описание нашего класса:

/// Класс Account определяет банковский счет.

/// простейший вариант с возможностью трех операций:

/// положить деньги на счет, снять со счета, узнать баланс.

public class Account

int debit=0, credit=0, balance =0;

int sum =0, result=0;

/// Зачисление на счет с проверкой

public void putMoney(int sum)

credit += sum; balance = credit — debit; result =1;

/// Снятие со счета с проверкой

public void getMoney(int sum)

/// Уведомление о выполнении операции

Console.WriteLine(«Операция зачисления денег прошла успешно!»);

Console.WriteLine(«Cумма=<0>, Ваш текущий баланс=<1>»,

Console.WriteLine(«Операция снятия денег прошла успешно!»);

Console.WriteLine(«Cумма=<0>, Ваш текущий баланс=<1>»,

Console.WriteLine(«Операция зачисления денег не выполнена!»);

Console.WriteLine(«Сумма должна быть больше нуля!»);

Console.WriteLine(«Cумма=<0>, Ваш текущий баланс=<1>»,

Console.WriteLine(«Операция снятия денег не выполнена!»);

Console.WriteLine(«Сумма должна быть не больше баланса!»);

Console.WriteLine(«Cумма=<0>, Ваш текущий баланс=<1>»,


Как можно видеть, только у методов getMoney и putMoney имеется один входной аргумент. Это тот аргумент, который нужен по сути дела, поскольку только клиент может решить какую сумму он хочет снять или положить на счет. Других аргументов у методов класса нет, вся информация передается через поля класса. Уменьшение числа аргументов приводит к повышению эффективности работы с методами, так как исчезают затраты на передачу фактических аргументов. Но за все надо платить. В данном случае усложняются сами операции работы со вкладом, поскольку нужно в момент выполнения операции обновлять значения многих полей класса. Закрытый метод Mes вызывается после выполнения каждой операции, сообщая о том, как прошла операция и информируя клиента о текущем состоянии его баланса.

А теперь спроектируем аналогичный класс Account1, отличающийся только тем, что у него будет меньше полей. Вместо поля balance в классе появится соответствующая функция с этим же именем, вместо полей sum и result появятся аргументы у методов, обеспечивающие необходимую передачу информации. Вот как выглядит этот класс:

/// Класс Account1 определяет банковский счет.

/// Вариант с аргументами и функциями

public class Account1

int debit=0, credit=0;

/// Зачисление на счет с проверкой

public void putMoney(int sum)

if (sum >0)credit += sum;

/// Снятие со счета с проверкой

public void getMoney(int sum)

/// Уведомление о выполнении операции

void Mes(int result, int sum)

Console.WriteLine(«Операция зачисления денег прошла успешно!»);

Console.WriteLine(«Cумма=<0>, Ваш текущий баланс=<1>»,

Console.WriteLine(«Операция снятия денег прошла успешно!»);

Console.WriteLine(«Cумма=<0>, Ваш текущий баланс=<1>»,

Console.WriteLine(«Операция зачисления денег не выполнена!»);

Console.WriteLine(«Сумма должна быть больше нуля!»);

Console.WriteLine(«Cумма=<0>, Ваш текущий баланс=<1>»,

Console.WriteLine(«Операция снятия денег не выполнена!»);

Console.WriteLine(«Сумма должна быть не больше баланса!»);

Console.WriteLine(«Cумма=<0>, Ваш текущий баланс=<1>»,

Сравнивая этот класс с классом Account, можно видеть, что число полей сократилось с 5 до двух, упростились основные методы getMoney и putMoney. Но в качестве платы у класса появился дополнительный метод balance(), у метода Mes теперь появились два аргумента. Какой класс лучше? Однозначно сказать нельзя, все зависит от контекста, приоритетов, заданных при создании конкретной системы.

Приведу процедуру класса Testing, тестирующую работу с классами Account и Account1:

public void TestAccounts()

Account myAccount = new Account();

//Аналогичная работа с классом Account1

Console.WriteLine(«Новый класс и новый счет!»);

Account1 myAccount1 = new Account1();

На рис. 1_5.1 показаны результаты работы этой процедуры.

Рис. 1_5.1. Тестирование классов Account и Account1

Функции с побочным эффектом

Функция называется функцией с побочным эффектом, если помимо результата, вычисляемого функцией и возвращаемого ей в операторе return, она имеет выходные аргументы с ключевыми словами ref и out. В языках C/C++ функции с побочным эффектом применяются сплошь и рядом. Хороший стиль ОО-программирования не рекомендует использование таких функций. Выражения, использующие функции с побочным эффектом, могут потерять свои прекрасные свойства, присущие им в математике. Если F(a) – функция с побочным эффектом, то a + F(a) может быть не равно F(a) + a, так что теряется коммутативность операции сложения.

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

/// тестирование побочного эффекта

public void TestSideEffect()

a = 1; b = a + F(ref a);

a = 1; c = F(ref a) + a;

Console.WriteLine(«a=<0>, b=<1>, c=<2>»,a, b, c);

На рис. 1_5.2 показаны результаты работы этого метода.

Рис. 1_5.2. Демонстрация вызова функции с побочным эффектом

Обратите внимание на полезность указания ключевого слова ref в момент вызова. Его появление хоть как-то оправдывает некоммутативность сложения.

Напомню, что и выражения с побочным эффектом также приводят к потере коммутативности сложения и умножения. Выражение x + ++x не эквивалентно выражению ++x + x.

Методы. Перегрузка

Должно ли быть уникальным имя метода в классе? Нет, это не требуется. Более того, проектирование методов с одним и тем же именем является частью стиля программирования на С++ и стиля C#. Существование в классе методов с одним и тем же именем называется перегрузкой, а сами одноименные методы называются перегруженными.

Перегрузка методов полезна, когда требуется решать подобные задачи с разным набором аргументов. Типичный пример – это нахождение площади треугольника. Площадь можно вычислить по трем сторонам, по двум углам и стороне, по двум сторонам и углу между ними и при многих других наборах аргументов. СчиПродемонстрируем рассмотренные выше вопросы на примере проектирования классов Account и Account1, описывающих такую абстракцию данных, как банковский счет. Определим на этих данных две основные операции – занесение денег на счет и снятие денег. В первом варианте – классе Account – будем активно использовать поля класса. Помимо двух основных полей credit и debit, хранящих приход и расход счета, введем поле balance, задающее текущее состояние счета и два поля, связанных с последней выполняемой операцией. Поле sum будет хранить сумму денег текущей операции, а поле result – результат выполнения операции. Увеличение числа полей класса приведет, как следствие, к уменьшению числа аргументов у методов класса. Вот описание нашего класса:тается удобным во всех случаях иметь для метода одно имя, например Square, и всегда, когда нужно вычислить площадь, не задумываясь вызывать метод Square, передавая ему известные в данный момент аргументы.

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

Перегрузка характерна и для знаков операций. В зависимости от типов аргументов один и тот же знак может выполнять фактически разные операции. Классическим примером является знак операции сложения +, который играет роль операции сложения не только для арифметических данных разных типов, но и выполняет конкатенацию строк.

Перегрузка требует уточнения семантики вызова метода. Когда встречается вызов не перегруженного метода, то имя метода в вызове однозначно определяет, тело какого метода должно выполняться в точке вызова. Когда же метод перегружен, то знания имени недостаточно – оно не уникально. Уникальной характеристикой перегруженных методов является их сигнатура. Перегруженные методы, имея одинаковое имя, должны отличаться либо числом аргументов, либо их типами, либо ключевыми словами (заметьте, с точки зрения сигнатуры ключевые слова ref и out не отличаются). Уникальность сигнатуры позволяет вызвать требуемый перегруженный метод.

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

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

Есть ситуации, где перегрузка полезна, недаром она широко используется при построении библиотеки FCL. Возьмем, например класс Convert, у которого 16 методов с разными именами, зависящими от целевого типа преобразования. Каждый из этих 16 методов перегружен и в свою очередь имеет примерно 16 реализаций в зависимости от типа источника. Согласитесь, что-то неразумное было бы иметь в классе Convert 256 методов вместо 16 перегруженных методов. Впрочем, также неразумно было бы иметь один перегруженный метод, имеющий 256 реализаций. Перегрузка – это инструмент, которым следует пользоваться с осторожностью и обоснованно.

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

/// Тестирование перегруженных методов Cube()

public void TestLoadMethods()

long u = 0; double v = 0;

Cube(out u, 7); Cube(out v, 7.5);

Console.WriteLine(«u= <0>, v= <1>», u, v);

Cube(out u, 7, 11, 13);

Cube(out v, 7.5, Math.Sin(11.5) + Math.Cos(13.5), 15.5);


Console.WriteLine(«u= <0>, v= <1>», u, v);

На рис. 1_5.3 показаны результаты этого тестирования.

Рис. 1_5.3 Тестирование перегрузки методов

Архитектура проекта

Как обычно для поддержки примеров этой главы создано Решение с именем Ch5, содержащее консольный проект ProcAndFun. Помимо автоматически созданного класса Program в проект добавлены три класса – Testing, Account, Account1. В Main процедуре класса Program создается объект testObject класса Testing, вызывающий методы этого класса. Каждый из методов представляет собой тест, позволяющий на примере пояснить излагаемый материал.

Задачи и алгоритмы

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

Числа

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

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

Цифры. Системы счисления

Для записи чисел привычным способом, знакомым еще с первых классов школы, является их запись в позиционной системе счисления. Напомним некоторые факты. В позиционной системе счисления всегда есть цифра 1. Считается, что единицу создал бог, а остальные цифры придуманы человеком. Если так, то наиболее замечательной из человеческих придумок в этой области является введение цифры 0. Цифры позиционной системы упорядочены и каждая получатся из предыдущей прибавлением единицы. Число различных цифр в позиционной системе счисления задает основание системы счисления – p. В привычной для нас десятичной системе счисления p = 10 и цифрами являются знакомые всем символы: 0, 1, 2, … 9. В двоичной системе счисления цифр всего две – 0 и 1 и p = 2. В шестнадцатеричной системе счисления p =16 и привычных символов для обозначения цифр не хватает, так что дополнительно используются большие буквы латинского алфавита: 0, 1, 2, … 9, A, B, C, D, E, F, где A задает 10, а F – цифру 15. Поскольку в любой позиционной системе счисления цифры задают числа от 0 до p-1, то для числа p уже нет специального символа. Как следствие, в любой позиционной системе счисления основание системы счисления представляется числом 10, так что справедливы следующие соотношения: 2(10) = 10(2); 16(10) = 10(16); p(10) = 10(p). Здесь и в дальнейшем при записи числа при необходимости будем указывать в круглых скобках и систему счисления. В обыденной жизни непреложным фактом является утверждение «2*2=4». Мы понимаем, что столь же верным является утверждение «2*2 = 11» (в троичной системе счисления), или, если хотите «2*2 = 10», – все зависит от системы счисления, в которой ведутся вычисления.

Еще Эйлер занимался записью чисел в различных системах счисления. В его записных книжках можно найти запись числа π в двоичной системе счисления и запись чисел в системе с основанием 24, цифры которой он обозначал буквами латиницы.

Целые числа в позиционных системах счисления записываются в виде последовательности подряд идущих цифр: N=cncn-1…c.Эта запись стала настолько естественной, что иногда теряется ее исконный смысл: N = cn10 n + cn-110 n-1 + … + c110 1 + c10 0 , при котором цифры в записи числа представляют собой коэффициенты разложения числа N по степеням основания, так что вклад каждой цифры в число определяется как самой цифрой, так и ее позицией k и равен ck10 k . По причине того, что вклад каждой цифры в число зависит от ее позиции, система счисления и называется позиционной.

Запись чисел в позиционной системе легко обобщается и на числа с дробной частью:
N=cncn-1…c,d1…dm, где дробная часть отделяется от целой символом запятой или точки. И в этом случае остается справедливым разложение числа N по степеням основания, в котором цифры дробной части задают коэффициенты для отрицательных степеней основания:
N = cn10 n + cn-110 n-1 + … + c110 1 + c10 0 + d110 -1 + d210 -2 + … + dm10 — m (1)

Понимание соотношения (1) достаточно для решения большинства задач, рассматриваемых в этом разделе, являющихся частными случаями следующей задачи: дано представление числа в системе с основанием p, требуется найти его представление в системе с основанием q. (Пример: найти запись числа N=2BA37,5F(16) в троичной системе счисления).

Рассмотрим возможную схему решения подобных задач. Зная основание системы счисления p и цифры в записи числа в этой системе счисления, нетрудно вычислить значение числа N в десятичной системе счисления. Для этого достаточно воспользоваться соотношением (1), в котором значения цифр и основание системы счисления – p задаются в десятичной системе и в ней же ведутся вычисления. Например:

N=2BA37,5F(16) = 2*16 4 + 11*16 3 +10*16 2 + 3*16 1 +7 + 5*16 -1 + 15*16 -2

Сложнее, но достаточно просто решается и обратная задача. Зная значение числа N в десятичной системе, нетрудно получить цифры, задающие его запись в системе с основанием q. Представим число N в виде суммы целой и дробной частей N= C+D. Рассмотрим схему получения цифр отдельно для целой части C и дробной – D. Пусть в системе с основанием q число C представимо в виде C = cncn-1…c. Тогда нетрудно получить его последнюю цифру c и число M = cncn-1…c1, полученное отбрасыванием последней цифры в записи числа C. Для этого достаточно воспользоваться операциями деления нацело и получения остатка при делении нацело: c = C%p; M = C/p;

Применяя этот прием n раз, получим все цифры в записи числа C. Заметьте, имеет место соотношение: N= (N/p)*p + N%p, где в соответствии с языком C# операция / означает деление нацело, а % – остаток от деления нацело. Чтобы получить все цифры и сохранить их в массиве, достаточно эту схему вставить в соответствующий цикл, что схематично можно представить следующим почти программным текстом:

Для получения цифр дробной части применяется та же схема, но с некоторой модификацией. Если цифры целой части вычисляются, начиная с последней, младшей цифры числа, то цифры дробной части вычисляются, начиная с первой цифры после запятой. Для ее получения достаточно имеющуюся дробь умножить на основание системы счисления и в полученном результате взять целую часть. Для получения последующих цифр этот процесс следует применять к числу M, представляющему дробь, из которой удалена первая цифра:
d1 =[D*p]; m=. Здесь [x] и означают соответственно взятие целой и дробной части числа x. Хотя напрямую этих операций нет в языке C#, но их достаточно просто выразить имеющимися средствами этого языка.

Чтобы перейти от системы счисления p к системе счисления q, всегда ли следует использовать десятичную систему в качестве промежуточной: p→ 10 → q? Нет, не всегда. В ряде случаев удобнее использовать прием, основанный на следующем утверждении:

Если основания систем счисления связаны соотношением p = q k , то для перехода от записи числа в системе с основанием p к записи числа в системе с основанием q достаточно каждую цифру системы p представить группой из k цифр системы q.

Для обратного перехода из q в p достаточно сгруппировать цифры системы q и каждую группу из k цифр заменить одной цифрой системы p. Доказательство этих утверждений оставляю читателю.

Для программистов особую важность представляют три системы счисления – двоичная, восьмеричная и шестнадцатеричная. Основания этих систем счисления связаны упомянутым соотношением: 16 = 2 4 ; 8 = 2 3 . Рассмотрим число N, записанное в шестнадцатеричной системе N = 2A,3E(16). Чтобы получить его запись в двоичной системе, каждую цифру запишем в двоичной системе, представив ее группой из четырех двоичных цифр. Нетрудно видеть, что N = 00101010,00111110(2). Незначащие нули слева и справа могут быть отброшены, так что окончательно имеем: N = 101010,0011111(2). Переведем это число в восьмеричную систему. Для этого достаточно провести группирование по три цифры влево и вправо от запятой соответственно для целой и дробной части, так что получим: N = 52,174(8).

Римская система счисления

Еще сегодня для записи целых чисел, в особенности дат, используется римская система счисления. Эта система записи чисел не является позиционной. В ее основе лежит понятие человеческих рук с их пятью и десятью пальцами. Поэтому в этой системе есть цифры 1, 5 и 10, записываемые с помощью символов I, V, X. Помимо этого есть еще четыре цифры – 50, 100, 500, 1000, задаваемые символами L, C, D, M. В этой системе нет цифры 0 и она не является позиционной. С помощью цифр римской системы обычно записывают целые числа, не превышающие 4000. Запись числа представляет собой последовательность подряд идущих цифр, а значением числа является сумма цифр в его записи. Например число III означает I + I + I = 3. В записи числа старшие цифры предшествуют младшим, например CVI = C+V+I = 100+5+1=106. Из этого правила есть одно исключение. Младшая цифра может предшествовать старшей цифре и тогда вместо сложения применяется вычитание, например CIX = C+X-I = 100+10-1=109.

Задачи

  1. Дано целое число N = cn-1…c, где ci – это цифры. Получить n – число цифр в записи числа и целочисленный массив DigitsN такой, что DigitsN[i] = ci.
  2. Дано целое число N = cn-1…c, где ci – это цифры. Получить строку strN, задающую запись числа N.
    Указание: конечно, можно воспользоваться стандартным методом ToString, но в задаче требуется дать собственную реализацию этого метода.
  3. Дано дробное число N = 0.dm-1…d, где di – это цифры. Получить m – число цифр в записи числа и целочисленный массив FractN такой, что FractN[i] = di.
  4. Дано дробное число N = 0. dm-1…d, где di – это цифры. Получить строку strN, задающую запись числа N.
    Указание: конечно, можно воспользоваться стандартным методом ToString, но в задаче требуется дать собственную реализацию этого метода.
  5. Дано вещественное число с целой и дробной частью N = cn-1…c.dm-1…d, где ci, dj – это цифры. Получить n и m – число цифр в записи целой и дробной части числа и целочисленный массив DigitsN из n+m элементов такой, что первые его n элементов содержат цифры целой части, а последние m элементов – дробной.
  6. Дано вещественное число с целой и дробной частью N = cn-1…c.dm-1…d, где ci, dj – это цифры. Получить строку strN, задающую запись числа N.
  7. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в двоичную систему счисления N = bk-1…b, получить k – число цифр и целочисленный массив DigitsN такой, что DigitsN[i] = bi, где bi – это цифры в записи числа N в двоичной системе счисления.
    Пример: N = 17(10) = 10001(2)
  8. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в троичную систему счисления N = bk-1…b, получить k – число цифр и целочисленный массив DigitsN такой, что DigitsN[i] = bi, где bi – это цифры в записи числа N в троичной системе счисления.
    Пример: N = 17(10) = 122(3)
  9. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в четверичную систему счисления N = bk-1…b, получить k – число цифр и целочисленный массив DigitsN такой, что DigitsN[i] = bi, где bi – это цифры в записи числа N в четверичной системе счисления.
    Пример: N = 17(10) = 101(4)
  10. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в восьмеричную систему счисления N = bk-1…b, получить k – число цифр и целочисленный массив DigitsN такой, что DigitsN[i] = bi, где bi – это цифры в записи числа N в восьмеричной системе счисления.
    Пример: N = 17(10) = 21(8)
  11. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в шестнадцатеричную систему счисления N = bk-1…b, получить k – число цифр и целочисленный массив DigitsN такой, что DigitsN[i] = bi, где bi – это цифры в записи числа N в шестнадцатеричной системе счисления.
    Пример: N = 17(10) = 11(16)
  12. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в двоичную систему счисления N = bk-1…b. Получить строку strN, задающую запись числа N.
    Пример: N = 17(10) = 10001(2)
  13. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в троичную систему счисления N = bk-1…b. Получить строку strN, задающую запись числа N.
    Пример: N = 17(10) = 122(3)
  14. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в четверичную систему счисления N = bk-1…b. Получить строку strN, задающую запись числа N.
    Пример: N = 17(10) = 101(4)
  15. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в восьмеричную систему счисления N = bk-1…b. Получить строку strN, задающую запись числа N.
    Пример: N = 17(10) = 21(8)
  16. Дано целое число N = cn-1…c, где ci – это цифры десятичной системы счисления. Перевести число N в шестнадцатеричную систему счисления N = bk-1…b. Получить строку strN, задающую запись числа N.
    Пример: N = 17(10) = 11(16)
  17. Дано дробное число N = 0.dm-1…d, где di – это цифры десятичной системы счисления. Перевести число N в двоичную систему счисления N = 0.bk-1…b, вычислив k цифр в его записи, сохраняя их в целочисленном массиве DigitsN таком, что DigitsN[i] = bi, где bi – это цифры в записи числа N в двоичной системе счисления.
    Пример: N = 0.17(10) = 0.001010111(2) при k=9.
  18. Дано дробное число N = 0.dm-1…d, где di – это цифры десятичной системы счисления. Перевести число N в троичную систему счисления N = bk-1…b, вычислив k цифр в его записи, сохраняя их в целочисленном массиве DigitsN таком, что DigitsN[i] = bi, где bi – это цифры в записи числа N в троичной системе счисления.
    Пример: N = 0.17(10) = 0.01112(3) при k=5.
  19. Дано дробное число N = 0.dm-1…d, где di – это цифры десятичной системы счисления. Перевести число N в четверичную систему счисления N = bk-1…b, вычислив k цифр в его записи, сохраняя их в целочисленном массиве DigitsN таком, что DigitsN[i] = bi, где bi – это цифры в записи числа N в четверичной системе счисления.
    Пример: N = 0.17(10) = 0.02232(4) при k=5.
  20. Дано дробное число N = 0.dm-1…d, где di – это цифры десятичной системы счисления. Перевести число N в восьмеричную систему счисления N = bk-1…b, вычислив k цифр в его записи, сохраняя их в целочисленном массиве DigitsN таком, что DigitsN[i] = bi, где bi – это цифры в записи числа N в восьмеричной системе счисления.
    Пример: N = 0.17(10) = 0.127(8) при k=3.
  21. Дано дробное число N = 0.dm-1…d, где di – это цифры десятичной системы счисления. Перевести число N в шестнадцатеричную систему счисления N = bk-1…b, вычислив k цифр в его записи, сохраняя их в целочисленном массиве DigitsN таком, что DigitsN[i] = bi, где bi – это цифры в записи числа N в шестнадцатеричной системе счисления.
    Пример: N = 0.17(10) = 0.1B(16) при k=5.
  22. Дано вещественное число с целой и дробной частью N = cn-1…c.dm-1…d, где ci, dj – это цифры десятичной системы счисления. Перевести число N в двоичную систему счисления с заданной точностью, вычислив k цифр дробной части числа. Получить строку strN, задающую запись числа N в этой системе счисления.
    Пример: N = 17.17(10) = 10001. 001010111 (2)
  23. Дано вещественное число с целой и дробной частью N = cn-1…c.dm-1…d, где ci, dj – это цифры десятичной системы счисления. Перевести число N в троичную систему счисления с заданной точностью, вычислив k цифр дробной части числа. Получить строку strN, задающую запись числа N в этой системе счисления.
    Пример: N = 17.17(10) = 122. 01112 (3)
  24. Дано вещественное число с целой и дробной частью N = cn-1…c.dm-1…d, где ci, dj – это цифры десятичной системы счисления. Перевести число N в четверичную систему счисления с заданной точностью, вычислив k цифр дробной части числа. Получить строку strN, задающую запись числа N в этой системе счисления.
    Пример: N = 17.17(10) = 101. 02232 (4)
  25. Дано вещественное число с целой и дробной частью N = cn-1…c.dm-1…d, где ci, dj – это цифры десятичной системы счисления. Перевести число N в восьмеричную систему счисления с заданной точностью, вычислив k цифр дробной части числа. Получить строку strN, задающую запись числа N в этой системе счисления.
    Пример: N = 17.17(10) = 21. 127 (8)
  26. Дано вещественное число с целой и дробной частью N = cn-1…c.dm-1…d, где ci, dj – это цифры десятичной системы счисления. Перевести число N в шестнадцатеричную систему счисления с заданной точностью, вычислив k цифр дробной части числа. Получить строку strN, задающую запись числа N в этой системе счисления.
    Пример: N = 17.17(10) = 11. 1B (16)
  27. Дана строка, задающая представление целого числа N = cn-1…c, где ci – это цифры десятичной системы счисления. Получить число N.
    Эту задачу можно сформулировать и так: задайте собственную реализацию методаToInt32 классаConvert.
  28. Дана строка, задающая представление вещественного числа с целой и дробной частью: N = cn-1…c.dm-1…d, где ci, dj – это цифры десятичной системы счисления. Получить число N.
    Эту задачу можно сформулировать и так: задайте собственную реализацию методаToDouble классаConvert.
  29. Дана строка, задающая в двоичной системе счисления представление целого числа N = cn-1…c, где ci – это цифры двоичной системы счисления. Получить число N.
    Эту задачу можно рассматривать, как расширение классаConvert: добавление методаFromBinaryToInt32.
  30. Дана строка, задающая в двоичной системе счисления представление вещественного числа с целой и дробной частью: N = cn-1…c.dm-1…d, где ci, dj – это цифры двоичной системы счисления. Получить число N.
    Эту задачу можно рассматривать, как расширение классаConvert: добавление методаFromBinaryToDouble.
  31. Дана строка, задающая в шестнадцатеричной системе счисления представление целого числа N = cn-1…c, где ci – это цифры шестнадцатеричной системы счисления. Получить число N.
    Эту задачу можно рассматривать, как расширение классаConvert: добавление методаFromHexToInt32.
  32. Дана строка, задающая в двоичной системе счисления представление вещественного числа с целой и дробной частью: N = cn-1…c.dm-1…d, где ci, dj – это цифры двоичной системы счисления. Получить число N.
    Эту задачу можно рассматривать, как расширение классаConvert: добавление методаFromHexToDouble.
  33. Дана строка, задающая в двоичной системе счисления представление целого числа N = cn-1…c, где ci – это цифры двоичной системы счисления. Получить строки str4N, str8N, str16N, задающие представление числа N в системах счисления: четверичной, восьмеричной, шестнадцатеричной.
    Указание. Используйте группирование цифр при переводе из одной системы счисления в другую.
  34. Дана строка, задающая в двоичной системе счисления представление вещественного числа с целой и дробной частью: N = cn-1…c.dm-1…d, где ci, dj – это цифры двоичной системы счисления. Получить строки str4N, str8N, str16N, задающие представление числа N в системах счисления: четверичной, восьмеричной, шестнадцатеричной.
    Указание. Используйте группирование цифр при переводе из одной системы счисления в другую.
  35. Дана строка, задающая в восьмеричной системе счисления представление целого числа N = cn-1…c, где ci – это цифры восьмеричной системы счисления. Получить строки str4N, str2N, str16N, задающие представление числа N в системах счисления: четверичной, двоичной, шестнадцатеричной.
    Указание. Используйте группирование цифр при переводе из одной системы счисления в другую.
  36. Дана строка, задающая в восьмеричной системе счисления представление вещественного числа с целой и дробной частью: N = cn-1…c.dm-1…d, где ci, dj – это цифры восьмеричной системы счисления. Получить строки str4N, str2N, str16N, задающие представление числа N в системах счисления: четверичной, двоичной, шестнадцатеричной.
    Указание. Используйте группирование цифр при переводе из одной системы счисления в другую.
  37. Дана строка, задающая в шестнадцатеричной системе счисления представление целого числа N = cn-1…c, где ci – это цифры шестнадцатеричной системы счисления. Получить строки str4N, str8N, str2N, задающие представление числа N в системах счисления: четверичной, восьмеричной, двоичной.
    Указание. Используйте группирование цифр при переводе из одной системы счисления в другую.
  38. Дана строка, задающая в шестнадцатеричной системе счисления представление вещественного числа с целой и дробной частью: N = cn-1…c.dm-1…d, где ci, dj – это цифры шестнадцатеричной системы счисления. Получить строки str4N, str8N, str2N, задающие представление числа N в системах счисления: четверичной, восьмеричной, двоичной.
    Указание. Используйте группирование цифр при переводе из одной системы счисления в другую.
  39. (*) Заданы p и q – основания двух систем счисления, strN – строка, задающая представление вещественного числа N в системе с основанием p. Получить строку, задающую представление числа N в системе с основанием q, возможно с некоторой точностью, заданной параметром k – числом цифр дробной части числа N при его записи в системе с основанием q.
  40. (*) Дано число N и основание системы счисления p. Получить ci – коэффициенты разложения числа N по степеням основания с заданной точностью Eps.
    Указание: В данной задаче предполагается, чтоN,p,ci являются вещественными числами и дляci выполняется условие (ci r1 * p2 r2 * … * pk rk , где pi – простые числа и pk 5 * 3 1 .

Если d | m и d | n, то d является общим делителем чисел m и n. Среди всех общих делителей можно выделить наибольший общий делитель, обозначаемый как НОД(m, n). Если НОД(m, n) = 1, то числа m и n называются взаимно простыми. Простые числа взаимно просты, так что НОД(q, p) =1, если q и p – простые числа.

Если m | A и n | A, то A является общим кратным чисел m и n. Среди всех общих кратных можно выделить наименьшее общее кратное, обозначаемое как НОК(m, n). Если НОК(m, n) = m*n, то числа m и n являются взаимно простыми. НОК(q, p) =q*p, если q и p – простые числа.

Если через Pm и Qn обозначить множества всех простых множителей чисел m и n, то

Если получено разложение чисел m и n на простые множители, то, используя приведенные соотношения, нетрудно вычислить НОД(m,n) и НОК(m,n). Существуют и более эффективные алгоритмы, не требующие разложения числа на множители.

Алгоритм Эвклида

Эффективный алгоритм вычисления НОД(m,n) предложен еще Эвклидом. Он основывается на следующих свойствах НОД(m,n), доказательство которых предоставляется читателю:

Если m > n, то по третьему свойству его можно уменьшить на величину n. Если же m n) m = m — n;

Создание функций

Создание функций (методов) в языке программирования C#

Дата изменения: 01.08.2015

В этом уроке разберем что такое функции. Создание функции состоит из двух этапов, это

  1. создание самой функции
  2. и её вызов.

Создание функции

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

  1. Вначале пишется уровень доступа, который у нас будет публичный (public)
  2. затем тип возвращаемых данных (double)
  3. и имя самой функции. (STriangle)
  4. во входных параметрах будет две переменные, это основание треугольника (a) и высота (h):

Ключевое слово return определяет, что вернет наша функция после её вызова. Для того чтобы передать комментарии нашей функции, воспользуемся метаданными. Для их создания достаточно написать три символа слеша подряд: /// появится следующая конструкция, которую необходимо наполнить данными:

Вызов функции

Чтобы воспользоваться методом STriangle необходимо создать экземпляр класса Program и затем через него вызвать созданную нами функцию:

Процедура в C#
Основное отличие процедуры от функции в том, что процедура ничего нам не возвращает.
Создадим ещё одну функцию, которая не будет ничего возвращать, а выведет в консоль заданную строку:

В методе Main вызовем данный метод и посмотрим результат:

Результат выполнения функции

p/s: С помощью ToString() мы преобразовали выходной тип double к string.

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

C# — Функции в c#

Доброго времени суток. Прошу подсказки в создании указателя на функцию. Именно что указателя, а не делегата.

В общем примерно так: есть функция:

Ответы

  • Помечено в качестве ответа Abolmasov Dmitry 24 октября 2011 г. 8:38


По-моему, в C# невозможно получить небезопасный указатель на функцию. Для вызовов таких функций из неуправляемого кода всегда создаётся управляемая обёртка.

Думал, думал, вот до чего додумался:

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

C#: асинхронные функции

В C# 5.0 добавлены ключевые слова await и async для поддержки асинхронного программирования — стиля программирования, когда длительные по времени выполнения функции проделывают всю или большую часть своей работы после возврата контроля в точку вызова. В противоположность этому при обычном — синхронном — программировании длительные функции блокируют вызвавший их код пока не завершат свое выполнение. Асинхронное программирование предполагает параллелизм (параллельное выполнение нескольких операций), когда длительные операции выполняются параллельно с вызвавшим их кодом. Параллельное выполнение асинхронных функций реализуется либо через многопоточность (для операций, зависящих только от быстродействия процессора — compute-bound), либо через механизм обратного вызова (для операций, ограниченных скоростью ввода/вывода данных — I/O-bound).

Например, следующий метод выполняется синхронно, является длительным по времени выполнения и зависит от быстродействия процессора (compute-bound):

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

В CLR предусмотрен класс Task (в System.Threading.Tasks ) для реализации концепции операций, завершающихся в будущем. Для операций, зависящих от быстродействия процессора, можно создать экземпляр Task вызвав Task.Run и передав ему в качестве параметра делегат. CLR запустит этот делегат в отдельном потоке параллельно с вызвавшим кодом:

Этот метод асинхронный, так как возвращает контроль над выполнением в точку вызова сразу же пока сам выполняется параллельно. Однако необходим специальный механизм, позволяющий вызвавшему коду определить, что должно случиться когда операция завершиться и результат станет доступным. Класс Task реализует это с помощью метода GetAwaiter , который позволяет добавить в точку вызова отложенное продолжение:

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

Ключевые слова await и async

Ключевое слово await упрощает синтаксис добавления отложенного продолжения:

Соответственно пример из предыдущего раздела можно переписать так:

Также необходимо добавить родительскому методу модификатор async :

Модификатор async заставляет компилятор воспринимать await как ключевое слово, а не как идентификатор типа (это сделано с цель обратной совместимости с кодом, написанным до появления C# 5.0). Модификатор async может быть применен только к методу (или лямбда выражению) возвращаемому void либо Task или Task . Модификатор async (также как модификатор unsafe ) не является частью сигнатуры метода или его метаданных.

Методы с модификатором async называются асинхронными функциями, поскольку в действительности выполняются асинхронно. Встретив выражение с ключевым словом await , выполнение возвращается в точку вызова (также как при встрече yield return в итераторе). Но перед возвращением создается отложенное продолжение для ожидания завершения задания. Когда отложенное задание завершиться, выполнение вернется назад в метод и продолжит его выполнение с того момента, на котором оно остановилось. Если отложенное задание завершиться с ошибкой, будет выброшено исключение, в противном случае возвращенное значение будет считаться результатом выполнения выражения с ключевым словом await .

В пределах асинхронной функции компилятор ожидает встретить либо выражение с ключевым слово await , которое и рассматривается как отложенное задание, либо любой объект с методом GetAwaiter , возвращающим отложенный объект (awaitable object) — объект, реализующий интерфейс INotifyCompletion.OnCompleted и содержащий метод GetResult (возвращающий соответствующий результат), а также свойство bool IsCompleted , проверяющее завершенность синхронного выполнения.

Помимо обобщенного класса Task можно использовать и не обобщенный класс Task , который не возвращает никакого значения:

Обучение C#

Формула программиста

Работая с этим сайтом, Вы даете согласие на использование файлов Cookie.

Класс Math для работы с математическими функциями

Методы определения модуля и получения знака — Abs , Sign

Методы определения минимума и максимума — Min , Max

Методы округления

Методы тригонометрических функций

  • Sin(угол) — вычисление синуса угла.
  • Cos(угол) — вычисление косинуса угла.
  • Tan(угол) — вычисление тангенса угла.
  • Asin(значение) — вычисление арксинуса значения из диапазона [‑1; 1], возвращаемое значение лежит в диапазоне [‑π/2; π/2].
  • Acos(значение) — вычисление арккосинуса значения из диапазона [‑1; 1], возвращаемое значение лежит в диапазоне [0; π].
  • Atan(значение) — вычисление арктангенса значения, возвращаемое значение лежит в диапазоне [‑π/2; π/2].
  • Sinh(угол) — вычисление гиперболического синуса угла.
  • Cosh(угол) — вычисление гиперболического косинуса угла.
  • Tanh(угол) — вычисление гиперболического тангенса угла.

Логарифмические функции

Возведение в степень и извлечение квадратного корня

Для возведения числа в степень предусмотрен метод Pow( double , double ) , в качестве первого аргумента которого указывается число, возводимое в степень, а в качестве второго аргумента — показатель степени.
Для извлечения квадратного корня из числа типа double можно также использовать метод Sqrt( double ) .

Автор: Вставская Елена Владимировна

Начинаем практику по языку C#

Чтобы стать хорошим программистом — нужно писать программы. На нашем сайте очень много практических упражнений.

После заполнения формы ты будешь подписан на рассылку «C# Вебинары и Видеоуроки», у тебя появится доступ к видеоурокам и консольным задачам.

Несколько раз в неделю тебе будут приходить письма — приглашения на вебинары, информация об акциях и скидках, полезная информация по C#.

Ты в любой момент сможешь отписаться от рассылки.

Встроенные функции в C#?

Как вы делаете «встроенные функции» в C#? Я не думаю, что понимаю концепцию. Они похожи на анонимные методы? Как лямбда-функции?

Примечание: ответы почти полностью касаются способности встроенные функции, т. е. » оптимизация вручную или компилятора, которая заменяет сайт вызова функции телом вызываемого абонента.- Если вас интересует . —7anonymous (a.к. a. lambda) функции см. @jalf это!—6 или что это за «лямбда», о которой все говорят?.

14 ответов

наконец, в .NET 4.5 CLR позволяет намекать / предлагать 1 метод встраивания с помощью MethodImplOptions.AggressiveInlining значение. Он также доступен в багажнике Mono (совершается сегодня).

1. Раньше здесь применялась» сила». Поскольку было несколько downvotes, я попытаюсь уточнить этот термин. Как и в комментариях и документации, The method should be inlined if possible. особенно учитывая моно (который открыт), есть некоторые моно-специфические технические ограничения рассматривая inlining или более общий (например, виртуальные функции). В целом, да, это намек на компилятор, но я думаю, что это то, о чем просили.

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

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

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

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

лучше оставить все в покое и позволить компилятору делать свою работу, а затем профилировать и выяснить, является ли inline лучшим решением для вас. Конечно, некоторые вещи просто имеют смысл быть встроенными (особенно математические операторы), но позволить компилятору обрабатывать это, как правило, лучшая практика.

обновление: Per Конрад.ответ кручинского, следующее верно для версий .NET до 4.0 и включая 4.0.

можно использовать класс MethodImplAttribute to запретить метод от быть inlined.


. но нет никакого способа сделать наоборот и силу он должен быть встроен.

вы смешиваете два разных понятия. Функция встраивание-это оптимизация компилятора, которая не влияет на семантику. Функция ведет себя одинаково независимо от того, является ли она встроенной или нет.

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

в C# нет встроенного ключевого слова, потому что это оптимизация, которую обычно можно оставить компилятору, особенно на языках JIT’Ed. Компилятор JIT имеет доступ к статистике времени выполнения, которая позволяет ему решать, что встроить гораздо эффективнее, чем вы можете при написании кода. Функция будет встроена, если компилятор решит, и вы ничего не можете с этим поделать. :)

Вы имеете в виду встроенные функции в C++ смысл? В котором содержимое обычной функции автоматически копируется встроенным в callsite? Конечный эффект заключается в том, что вызов функции фактически не происходит при вызове функции.

Если это так, то нет, нет эквивалента C#.

или вы имеете в виду функции, которые объявлены в другой функции? Если да, то да, C# поддерживает это с помощью анонимных методов или лямбда выражения.

Коди имеет это право, но я хочу привести пример того, что такое встроенная функция.

скажем, у вас есть этот код:

на компиляторJust-In-Time optimizer может выбрать изменение кода, чтобы избежать повторного размещения вызова OutputItem () в стеке, так что это было бы так, как если бы вы написали код следующим образом:

в этом случае мы бы сказали, что функция OutputItem() была встроена. Обратите внимание, что это может сделайте это, даже если OutputItem() вызывается из других мест.

отредактировано, чтобы показать сценарий более-вероятно, будет встроен.

Да точно, единственное различие заключается в том, что он возвращает значение.

упрощение (без использования выражений):

List .ForEach выполняет действие, он не ожидает возврата результата.

так Action делегата было бы достаточно.. скажи:

это то же самое, что сказать:

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

List .Where принимает функцию, ожидая результата.

так Function можно было бы ожидать:

что то же самое, что:

вы также можете объявить эти методы inline и asign их переменным IE:

надеюсь, это поможет.

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

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

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

оператор » лучше всего оставить эти вещи в покое и позволить компилятору выполнять работу..»(Коди Бросиус) — полный Рубиш. Я программирую высокопроизводительный игровой код в течение 20 лет, и мне еще предстоит встретить компилятор, который «достаточно умен», чтобы знать, какой код должен быть встроен (функции) или нет. Было бы полезно иметь» встроенную » инструкцию в C#, правда в том, что компилятор просто не имеет всей информации, необходимой для определения, какая функция должна быть всегда inlined или нет без подсказки «inline». Конечно, если функция мала (accessor), то она может быть автоматически встроена, но что, если это несколько строк кода? Nonesense, компилятор не может знать, вы не можете просто оставить это компилятору для оптимизированного кода (за пределами алгоритмов).

нет, в C# нет такой конструкции, но компилятор .NET JIT может решить выполнить встроенные вызовы функций в JIT time. Но я на самом деле не знаю, действительно ли он делает такие оптимизации.
(Я думаю, что это необходимо :-))

в случае, если ваши сборки будут ngen-ed, вы можете взглянуть на TargetedPatchingOptOut. Это поможет ngen решить, следует ли использовать встроенные методы. MSDN reference

Это все еще только декларативный намек на оптимизацию, а не императивная команда.

Я знаю, что этот вопрос касается C#. Однако вы можете писать встроенные функции в .NET с помощью F#. см.: использование ‘ inline` в F#

C# не поддерживает встроенные методы (или функции) так, как это делают динамические языки, такие как python. Однако анонимные методы и lambdas могут использоваться для аналогичных целей, в том числе, когда вам нужно получить доступ к переменной в содержащем методе, как в примере ниже.

лямбда-выражения являются встроенными функциями! Я думаю, что у C# нет дополнительного атрибута, такого как inline или что-то в этом роде!

Как передать функцию в c#?

Delegate (с большой буквы) — это статический класс, которые содержит методы для работы с делегатами. Сам же делегат нужно объявить через ключевое слово delegate (с маленькой буквы), как класс или интерфейс. Например:
public delegate void TestDelegate(string message);
При этом использовать вы можете только конкретный тип делегата, точно также как вы используете конкретный MyClass и т.п., а не само ключевое слово >TestDelegate . Чтобы передать с помощью делегата ссылку на функцию, сигнатуры функции и делегата должны полностью совпадать. В случае приведенного примера TestDelegate , функция должна также возвращать void и принимать string .

В вашем случае тип делегата должен быть такой: public delegate void MyDelegate(); Этому делегату вы можете присвоить ссылку на ваш метод void init() .
Но свой тип делегаты в данном случае объявлять нет необходимости. В Net. Framework уже есть целый ряд делегатов, как раз для таких целей. Это Func и Action .
Вы можете просто заменить слово Delegate на Action в вашем коде, и всё должно работать.

C# для начинающих

C# — это язык с функциональностью C++, стилем программирования Java и моделью быстрой разработки приложений, позаимоствованной из BASIC. Если вы уже знаете C++, освоение синтаксиса C# займет у вас менее часа. Знакомство с Java даст вам существенное преимущество, так C# позаимствовал у Java структуру программы, концепции пакетов и «сборки мусора». Кроме того, при обсуждении конструкций языка C#, я буду считать, что вы знаете, C++.

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

В статье обсуждаются следующие аспекты языка C#:

  • Структура программы
  • Пространства имен
  • Переменные
  • Типы данных
  • Перечисления
  • Классы и структуры
  • Свойства
  • Модификаторы
  • Интерфейсы
  • Массивы
  • Индексаторы
  • Упаковка и распаковка
  • Параметры функции
  • Операторы
  • Делегаты
  • Наследование и полиморфизм
Структура программы

Как и C++, C# чувствителен к регистру. Точка с запятой (;) является разделителем операторов. В отличие от C++ в C#, к сожалению, нет отдельных файлов объявления (заголовчных файлов) и файлов реализации (cpp). Весь код (объявление класса и его реализация) помещается в один файл с расширением cs.

Вот, традиционная реализация ‘Hello world!’ на C#:
using System;

Все в C# помещается в класс, а классы в C # должны находится в каком-либо пространстве имен (так же, как файлы в папке). Как и C++, метод Main является точкой входа вашей программы. В C++ основная функция вашей программы называется main, тогда как C# основная функцияначинается с заглавной буквы М и назвается Main.
Нет необходимости ставить точку с запятой после блока, класса или определения структуры. Это было необходимо в C++, в C# это не требуется.

Пространства имен

Каждый класс находится в каком-либо пространстве имен. Термин пространство имен имеет в C# точно такое же значние, как b в C ++, но в C# мы используем пространства имен чаще, чем в C++. Вы можете получить доступ к классу в пространстве имен с использованием точки (.). В приведенной выше программе MyNameSpace — это пространство имен.

Теперь рассмотрим, как получить доступ к классу HelloWorld из какого-то другого класса в другом пространстве имен.

Теперь из вашего класса HelloWorld вы можете получить доступ к классу AnotherClass, вот таким образом:

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

Можно также определить вложенные пространства имен.

Ключевое слово Using

Директива #include заменена использованием ключевого слова using, за которым следует имя пространства имен. Так же, как это было продемонстрированно выше для пространства имен System. System является пространством имен базового уровня, в котором находятся все другие пространства имен и классы. Базовым класс для всех объектов в C# является класс Object из пространства имен System.

Переменные

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

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

Все типы C# являются производными от базового класса Object. Существуют два типа типов данных:

  • Основные / встроенные типы
  • Определяемые пользователем типы

Ниже приводится таблица, в которой перечислены встроенные типы C#:

Тип Размер в байтах Описание Диапазон значений
byte 1 беззнаковый целочисленный тип/td> 0 : 255
sbyte 1 целочисленный тип со знаком -128 : 127
short 2 короткий целочисленный тип со знаком -32768 : 32767
ushort 2 короткий беззнаковый целочисленный тип 0 : 65535
int 4 целочисленный тип со знаком -2147483648 : 2147483647
uint 4 беззнаковый целочисленный тип 0 : 4294967295
long 8 длинныйй целочисленный тип со знаком -9223372036854775808 : 9223372036854775807
ulong 8 длинныйй беззнаковый целочисленный тип 0 : 18446744073709551615
float 4 число с плавающей точкой
double 8 число с плавающей точкой двойной точности
decimal 8 десятичный тип фиксированной точности
string Строка в кодировке Unicode
char Символ в кодировке Unicode
bool Целочисленный тип, который может иметь одно из двух значений: true или false.

Определяемые пользователем типы включает в себя:

Все эти типы данных можно разделить на типы значений, еще называемые значимыми типами, (value types) и ссылочные типы (reference types). Важно понимать между ними различия.
К типам значений относятся типа данных, для котороых память выделяется на стеке. Это:

  • Целочисленные типы (byte, sbyte, char, short, ushort, int, uint, long, ulong)
  • Типы с плавающей точкой (float, double)
  • Тип decimal
  • Тип bool
  • Перечисления enum
  • Структуры (struct)

К ссылочным типам относятся типы, для которых память выделяется в «куче». Экземпляры ссылочных типов создаются с помощью оператора new, однако, в отличие от C++, в C# не существует оператор delete для ссылочных типо. В C#, память выделенная для ссылочных типов, автоматически освобождается сборщиком мусора, когда экземпляры этих типов выходят из области видимости. К таким типам относятся:

  • Тип object
  • Тип string
  • Классы (class)
  • Интерфейсы (interface)
  • Делегаты (delegate)
Перечисления

Перечисления в C# выглядят точно так же, как и в C++. Перечисление определяется с помощью ключевого слова enum. Например:

Цукерберг рекомендует:  Одним словом. Нейминг для мобильных приложений
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих