P1 = Pol. мужской;
P2 = P1;
C1 = Cvet.синий;
Palitra = RGB.Green;
При выводе на экран значений переменных отображаются значения именованных констант.
Есть связь между целочисленным типом и перечислением. Дело в том, что все значения, входящие в список именованных констант, нумеруются системой, начиная с 0. Так, в перечислении Cvet значение желтый имеет номер 2, а значение фиолетовый – номер 6. При необходимости можно использовать явное преобразование целого числа в перечисление. Например, если целочисленная переменная k имеет значение 2, то оператор
Palitra = (RGB) k;
присвоит переменной Palitra значение Blue.
12.2. СТРУКТУРЫ
Допустим, что в программе предполагается обрабатывать данные, связанные с информацией о студентах, и о каждом студенте известно следующее: ФИО, пол, год рождения, домашний адрес, академическая группа, оценки по предметам за прошедшую сессию. Все данные имеют разный тип: ФИО – это строка, год рождения – целое число, оценки по предметам – это массив целых чисел. Разумеется, значения этих характеристик у всех студентов различаются, но структурно данные по разным студентам одинаковы. Было бы очень удобно работать не только с конкретными значениями (ФИО, группа, курс и т. п.), но и с общей совокупностью данных. Вполне логично объявить новый тип данных – Студент, который включал бы в себя все перечисленные данные. Эти данные в состав нового типа могли бы входить в качестве отдельных полей (характеристик). Тогда, используя общие правила языка, можно с помощью нового типа создавать переменные и массивы типа Студент.
Например, можно объявить массив студентов (типа Студент) и назвать его группой, причем представить это образование как еще один новый тип данных Группа. Тогда объединение в массив нескольких групп может дать новое понятие – факультет. И так можно агрегировать данные (объединять в смысловые совокупности) до бесконечности – в разумных пределах, конечно.
Подобное объединение разнотипных данных в единое целое принято называть агрегированием данных. В языке C# для этой цели используются структуры. Структура является значимым типом, т. е. переменная-структура содержит значение, а не ссылку на него. Рассмотрим упрощенный синтаксис структуры (подробное описание можно найти в [1]).
struct Имя
{
поля структуры
}
Зарезервированное слово struct как раз и означает, что создаваемый новый тип является структурой. Имя является именем нового типа данных. Поля структуры содержат описание данных, которые агрегированы внутри структуры как единое целое. Каждое поле содержит информацию об одном виде данных. Поле описывается следующим образом:
public Тип_поля Имя_поля;
Зарезервированное слово public указывает, что поле структуры является доступным для выбора значений и изменений. Тип_поля – это тип значения, которое содержит поле. Имя_поля – это идентификатор, позволяющий обращаться к конкретному значению внутри структуры.
Рассмотрим, как может выглядеть, например, описание структуры Student с включением в нее ранее перечисленных данных.
struct Student
{
public string FIO;
public Pol Pol_stud;
public int God;
public string Adres;
public string Gruppa;
public int Ocenka[] ;
}
Данное описание составлено в предположении, что кроме него в программе имеется описание перечисления Pol со значениями мужской и женский. Наличие такого описания обеспечивает использование в программе типа данных Student при объявлении переменных и массивов. Например:
Student A, B;
Student [] gruppa = new Student[30];
Как видим, объявление переменных и массива выполнено по обычным правилам. Каждая переменная и каждый элемент массива – это уже не одно значение, а объединение разнотипных значений. И A, и B, и элементы массива от gruppa[0] до gruppa[29] – все содержат перечисленные в структуре поля. Однако значения полей могут быть различными.
Доступ к полям структуры для получения значения или для его изменения обеспечивается специфической операцией, которую мы уже ранее неоднократно использовали. Она обозначается символом «точка». Например:
A.FIO = ““;
A.Gruppa = “КВТ091“;
B. Gruppa = A. Gruppa;
Console.WriteLine(A.FIO);
Поле Ocenka является массивом, поэтому при объявлении переменной-структуры оно содержит пустую ссылку null. Чтобы поле можно было использовать, память для массива нужно выделить. Например:
A.Ocenka = new int[5];
И только после этого можно работать с элементами массива. Например:
A. Ocenka[0] = 4;
При желании присваивать можно не только поля, но и структуры целиком. Например:
B = A;
gruppa[0] = B;
Полем структуры может быть другая структура. В этом случае доступ к полям внутренней структуры осуществляется уже через две операции «точка».
Отметим один очевидный факт. Значением переменной gruppa является ссылка на массив в динамической памяти. Сами значения размещены в динамической памяти. Каждый элемент массива в момент выделения памяти инициализируется: каждое поле получает значение, равное нулю соответствующего типа.
То же можно сказать и про объявление переменных A и B. Например, поле FIO получает значение ”” (пусто), God – значение 0, Ocenka – значение null (отсутствует ссылка на массив). Следовательно, при объявлении переменной-структуры или выделении места под массив операцией new можно быть уверенными, что соответствующие поля «очищены нулями».
13. ФУНКЦИИ В C#
13.1. ПОДПРОГРАММЫ В ЯЗЫКАХ ВЫСОКОГО УРОВНЯ
Программа, составленная в виде консольного приложения для решения несложной задачи, имеет программный код, размещенный в пределах главной функции программы (Main). Если программный код располагается в пределах одной или двух страниц экранного текста, то такой текст обычно прост для понимания. При разработке могут быть допущены какие-то ошибки, но они легко будут устранены при тестировании и отладке программы. Внесение изменений в такую программу при необходимости также не составит большого труда, даже если это придется делать по прошествии значительного периода времени – вспомнить суть программного алгоритма и разобраться с программным кодом, записанным на одной или двух экранных страничках, будет несложно.
Представим себе, однако, ситуацию, когда задача достаточно сложна и алгоритм занимает, скажем, 200 или 300 страниц экранного текста. Даже хорошие комментарии делают понимание такой программы весьма затруднительным. Особенно, если прошло определенное время с момента окончания разработки. Да и сама разработка становится проблемной. Большой программный код, записанный «в линию», трудно воспринимать. Ведь решение всегда состоит из множества алгоритмов, а программный код включает в себя всевозможные детали реализации этих алгоритмов. Наконец, отдельные алгоритмы могут повторяться в различных частях программы, что делает текст громоздким для понимания.
Перечисленные неудобства могут быть устранены, если текст программы оформлять не «сплошь в линию», а применять механизм структурирования. Под структурированием понимается выделение в самостоятельные подпрограммы каких-то целостных алгоритмов, отдельное описание их и размещение этих подпрограмм в тексте программы под некоторыми именами. Тогда основной алгоритм можно описать значительно короче, используя обращение к ранее созданным подпрограммам как к вспомогательным алгоритмам – это называется вызов подпрограммы. Текст главной части программы становится более понятным, т. к. остается, по сути, описание основного алгоритма, а все детали вспомогательных алгоритмов вынесены в отдельные программные блоки. Такая программа называется структурированной программой.
Итак, структурированная программа содержит подпрограммы. Каждая подпрограмма имеет начало (первый оператор) и конец (последний оператор). Выполнение подпрограммы начинается с первого оператора и заканчивается последним оператором при любом вызове подпрограммы. Очевидно также, что при любом вызове для нормальной работы подпрограммы ей могут потребоваться некоторые исходные данные, а в результате ее выполнения могут получиться какие-то результаты, которые должны использоваться в следующих после вызова операторах. Например, если подпрограмма «умеет» вычислять сумму элементов целочисленного массива, то для нормальной работы вместе с вызовом ей нужно передать этот массив (адрес конкретного массива), а после завершения получить от нее вычисленную сумму.
Для того чтобы описанные действия могли выполняться, предусмотрен специальный механизм вызова подпрограмм. Однако, в современных языках термин «подпрограмма» сейчас не используется. Вместо него применяют существительные процедура и функция. Описание процедуры и функции различается, но не слишком сильно. В некоторых языках в описании нет вообще никаких отличий – различия чисто функциональные. Процедура выполняет заложенный в ней алгоритм и завершает работу. Результат ее выполнения – изменения в переменных, массивах и т. п. Функция делает то же самое, но, кроме того, после завершения выполнения некоторое вычисленное значение передаётся (говорят: возвращается) в то место программы, из которого была вызвана функция. Отличие и в размещении вызова. Вызов процедуры – это всегда отдельный оператор. Вызов функции – это выражение в составе некоторого оператора, т. е. операнд. Значение, возвращенное функцией, сразу же используется. Оно может присваиваться (в операторе присваивания), выводиться на экран (в операторе вывода), быть операндом в некоторой операции (например, быть слагаемым) и т. п.
Таким образом, если программист хочет использовать процедуру или функцию, он должен:
- описать процедуру (функцию); сделать вызов процедуры (функции).
Заглянем чуть-чуть вперед. В объектно-ориентированных языках, в том числе и в C#, вместо слов «процедура» и «функция» используется термин метод, который более точно отражает суть дела. Отметим еще раз: процедура, функция, метод – это синонимы, отражающие один и тот же факт использования подпрограмм. Вместе с тем, слово «метод» подчеркивает, что речь идет о такой подпрограмме, которая связана с некоторым объектом некоторого класса. Можно было бы сказать: «процедура (функция) вызывается для выполнения каких-либо действий над объектом (переменной)». Говорят проще: «для объекта использован метод». Но всякий объект относится (говорят: принадлежит) к некоторому классу. Следовательно, к объекту можно применять методы только того класса, к которому он принадлежит. Последнее утверждение не вполне точно, но пока будем считать так.
Отметим также, что методы могут быть статическими и динамическими. Динамический метод как раз и предназначен для обслуживания конкретного объекта класса, в то время как статический метод – это метод класса вообще, исполнение которого не связанно с конкретным объектом, и для такого метода больше подходит термин «процедура» или «функция».
Порядок организации классов, объектов и методов мы рассмотрим в разделе 14. Далее в текущем разделе вместо термина «метод», общепринятого в языке C#, мы очень часто будем использовать слово «функция». Смысл такого выбора в том, чтобы сосредоточиться именно на правилах описания и использования алгоритма как отдельной структурной единицы программы.
В момент вызова функции программист знает, что именно должно быть сделано, а именно – то, что описано в алгоритме функции. Вместе с тем, до начала выполнения функции должно быть точно известно, что нужно использовать в качестве данных. Ведь функция обеспечивает одну и ту же работу с разными исходными данными – в полном соответствии с принципом массовости алгоритма. Существует механизм, который обеспечивает передачу в функцию нужных данных или сведений о них. В описании функции до начала описания алгоритма могут быть указаны параметры. Параметры – это переменные особого вида, назначение которых состоит в том, чтобы принять в момент начала выполнения функции сведения об исходных данных. Исполнение функции начинается с уже имеющимися значениями в параметрах, т. е. исполнение алгоритма функции осуществляется с конкретными исходными данными.
А попадают значения в параметры в тот момент, когда осуществляется вызов функции. В операторе вызова указываются (в виде переменных или значений) исходные данные для выполнения функции. Список переменных (значений) при вызове функции обычно точно соответствует списку параметров в описании функции. Сколько указано параметров, столько и должно быть передано значений в функцию до начала её исполнения. Переменные (значения), указанные при вызове, называются аргументами.
В литературе по программированию встречается и иная терминология. Параметры, используемые в описании функции, называют формальными параметрами, а аргументы при вызове – фактическим параметрами. Получается, что в момент вызова функции осуществляется подстановка фактических параметров (аргументов) в формальные параметры и с подставленными значениями исполняется алгоритм функции. Следует отметить, что в ряде случаев функция может не иметь параметров и тогда при вызове не указываются никакие аргументы. Это бывает тогда, когда исполнение алгоритма не зависит от исходных данных.
Порядок описания параметров (формальных и фактических) в разных языках программирования определяется правилами конкретного языка. Вместе с тем, существует общепринятое деление параметров на виды. Различают параметры следующих видов:
· Входной параметр – это переменная, значение которой определяется соответствующим аргументом. В момент вызова значение аргумента копируется в параметр, после чего оба (и аргумент, и параметр) начинают жить каждый своей жизнью. Изменение параметра никак не влияет на аргумент, а изменение аргумента – на параметр.
· Выходной параметр – это переменная, значение которой после выполнения функции присваивается соответствующему аргументу. Это значит, что в момент вызова в параметр ничего не записывается, т. е. при вызове не нужно заботиться о том, чтобы в аргументе было какое-то конкретное значение. Имя аргумента должно присутствовать, а значение в нем может быть какое угодно – не требуется даже инициализация. Однако функция в процессе выполнения должна что-то сформировать в параметре, чтобы после ее завершения в аргументе было какое-то значение.
· Универсальный параметр – это переменная, обладающая обоими предыдущими свойствами. Для таких параметров в некоторых языках (например, в C#) требуется, чтобы аргумент в момент вызова уже имел явное значение, т. е. был инициализирован. Это значение может быть изменено в процессе выполнения функции, что приведет к изменению и соответствующего аргумента.
Получив значения в свои параметры, функция начинает выполняться. В процессе исполнения могут получаться промежуточные результаты, которые где-то необходимо хранить. Для этого внутри функции объявляются переменные (объекты) – точно так же, как это делается при объявлении переменных в функции Main. Особенность этих переменных – в том, что область их «жизни» ограничена данной функцией. Говорят, что область существования переменных ограничена, локализована телом той функции, в которой они объявлены. Для этого существует специальный термин: локальные переменные. Каждая функция может иметь собственные локальные переменные, причем имена переменных в разных функциях могут повторяться. При использовании переменной внутри функции работа идет именно с переменной, описанной в данной функции – к локальной переменной из другой функции доступа нет.
В различных операторах и выражениях внутри функции могут использоваться и локальные переменные, и параметры. Параметры также являются локальными переменными. Но при этом надо помнить об их особенностях, определяемых видом параметра. Любое изменение локальной переменной, соответствующей выходному или универсальному параметру, приведет к изменению соответствующего аргумента. Аргумент, соответствующий выходному параметру, изменится после завершения функции. Аргумент, соответствующий универсальному параметру, изменится в момент изменения соответствующего параметра.
В момент завершения выполнения функции значения локальных переменных теряются. Правда, значение выходного параметра успеет переместиться в соответствующий аргумент. Кроме того, при завершении функции может быть выполнена передача некоторого значения в точку вызова функции – функция вернет некоторое значение, если она именно функция, а не процедура. Для этого в алгоритме функции должно быть предусмотрено формирование некоторой переменной (объекта). Значение, полученное в этой переменной (объекте), как раз и будет возвращено. Это сделает последний (по исполнению, не по написанию) оператор функции – специальный оператор возврата. В подавляющем большинстве языков он имеет имя Return. Структура оператора такова, что в нем указывается имя переменной (а иногда и само значение), значение которой должно быть возвращено.
13.2. МЕТОД Main
Попробуем посмотреть с точки зрения организации функций на метод Main, который мы до сих пор использовали при написании наших программ на языке С#. При создании проекта программная среда заготавливает этот метод внутри класса Program в следующем виде:
static void Main(string[] args)
{
}
Имя метода говорит о том, что он главный, с него начинается исполнение приложения. Слово static говорит о том, что это статический метод класса. Он является методом вообще, общим методом класса. Фактически – это просто процедура или функция.
А вот что это на самом деле: процедура или функция – зависит от типа метода. В нашем случае тип определяется словом void (пустой). Это значит, что метод не возвращает никакого значения, т. е. является процедурой. Но мы могли бы изменить тип. Например, написать вместо void тип int. В этом случае метод стал бы функцией, и после завершения должен был бы возвращать некоторое целое значение.
Допустим, что в процессе исполнения метода (то есть решения нашей задачи) некоторая переменная PriznakRezultata имеет тип int и получит какое-то значение. Предположим также, что это значение показывает, нормально или ненормально прошел процесс решения. Пусть в качестве типа в методе Main исполь-зуется тип int. Тогда в качестве последнего исполняемого опера-тора можно записать следующее выражение:
return PriznakRezultata;
Это значит, что при завершении выполнения функции Main значение из названной переменной будет возвращено туда, откуда был вызван метод Main. Но этот метод начал работать тогда, когда произошел запуск нашей программы. Значит, фактически метод был вызван операционной системой – ведь именно она запускала нашу программу. Получается, что возвращаемое значение вернет-ся в операционную систему и уже она, анализируя это значение, будет решать, что ей делать дальше.
Маловерояно, однако, что операционная система знает о нашей программе что-либо особенное. Скорее всего, она знает только одно: эту программу надо выполнить – и все. А что она там вернет… Да мало что она там возвращает – операционной системе нет до этого дела. И кажется, что нет накокого смысла что-то возвращать при завершении программы. Не потому ли компилятор делает заготовку с типом void?
Посмотрим, однако, чуть более внимательно. Да, вряд ли имеет смысл что-то возвращать в операционную систему, когда она запускает нашу программу. А что, если нашу программу будет запускать другая программа? Ведь в каждом языке программиро-вания есть способ запуска другой программы из тела исполняемой. Представим себе, что в другой программе имеется оператор запус-ка, используемый как функция. Тогда результатом выполнения этой функции как раз и будет значение, возвращенное нашей программой!
Теперь посмотрим, что приготовила среда в круглых скобках после имени метода. Параметр args – это массив строк. При вызове нашей программы в этот массив будут скопированы аргументы из оператора вызова функции Main. Но опять-таки вспомним, что эту функцию вызывает или другая программа, или операционная система. Тогда, если операционная система запуска-ет нашу программу из командной строки (а пользователь может и так запускать программу), те парамеры, которые будут указаны в командной строке, также попадут в этот массив. И они также могут использоваться в нашей программе как исходные данные!
Все описанные особенности можно хорошо прочувствовать, если ознакомиться с лабораторной работой № 9, описанной в [4].
13.3. ПРАВИЛА ОПИСАНИЯ И ВЫЗОВА ФУНКЦИЙ
Будем пока ориентироваться на описания функций в пределах одного класса – класса Program, который автоматически образуется средой при создании проекта. Именно поэтому будем использовать не полный, а упрощенный вариант описания. Более полно синтаксис описания будет рассмотрен чуть позже в разделе 14. Итак, будем считать, что синтаксис описания подпрограммы имеет следующий вид:
static Тип_результата Имя (список_формальных_параметров)
{тело}
Начнем с самого простого. Тело содержит операторы, реализующие алгоритм. В теле не могут присутствовать описания других функций, а также структур и классов. Если функция возвращает значение, то последним исполняемым оператором в теле должен быть оператор return.
Слово static в начале описания в нашем случае обязательно. Оно означает, что мы определяем внутри класса Program функцию, которая не связана с каким-либо объектом и может вызываться для выполнения просто по имени. Позднее мы увидим, что возможны и другие ключевые слова в начале описания.
Каждая функция обязательно имеет Имя, которое используется при вызове. Имя составляется по правилам определения идентификаторов. Лучше использовать имя, состоящее из одного слова, но если требуется использовать несколько слов, то между ними следует поставить знак «подчеркивание». Можно и сплошь записать несколько слов, но для того чтобы было удобно читать и понимать, каждое слово лучше начать с заглавной буквы. Можно использовать русские буквы, причем как в именах функций, так и в именах переменных. Это следствие использования системы кодирования Unicode.
Тип_результата перед именем определяет тип возвращаемого значения. Здесь можно указать любой тип, как предопределенный (встроенный в язык), так и тип, созданный программистом. Возвращаемое значение должно быть сформировано при выполнении функции. Как правило, это значение присваивается какой-то переменной (такого же типа) и указывается в операторе возврата return. Есть исключение. Если в качестве типа используется слово void, то формировать возвращаемое значение не нужно и оператор возврата не требуется – это описание процедуры, а не функции. В дальнейшем мы будем в основном использовать термин «функция», а слово «процедура» – только тогда, когда это будет принципиально.
В круглых скобках после имени функции может размещаться список параметров. Он может присутствовать, а может его и не быть – все зависит от алгоритма функции. Если параметры присутствуют, то при вызове функции в операторе вызова необходимо указать аргументы – точно в том же порядке и в том же количестве, что и параметры описания. Список параметров представляет собой описание отдельных параметров, разделенных запятой. Описание каждого параметра в общем случае состоит из имени параметра (локальная переменная) и типа этого параметра, при этом сначала записывается тип, а потом имя параметра. Тип параметра может быть любым.
Имя функции вместе со списком параметров образует так называемую сигнатуру функции. В сигнатуру из списка параметров включаются только типы параметров. Функции считаются разными, если они имеют разные сигнатуры. Двух функций с одинаковыми сигнатурами в одном классе (в нашем случае – в классе Program) быть не может. А вот две одноименные функции использовать можно – важно только, чтобы они различались хотя бы одним типом одного параметра. Такой прием (использование одноименных функций) называется перегрузка функции.
В языке C# поддерживаются параметры трех видов:
- Параметр-значение. Задается обычным образом: тип и имя переменной. Параметр-ссылка. Это универсальный параметр. Имя параметра является псевдонимом (вторым именем) для аргумента. Здесь перед типом указывается ключевое слово ref. При вызове слово ref указывается также перед аргументом. Выходной параметр. Имя параметра также является псевдонимом аргумента. Здесь перед типом указывается ключевое слово out. Это же слово указывается в операторе вызова перед соответствующим аргументом.
Вызов метода, независимо от того, процедура это или функция, имеет один и тот же синтаксис:
Имя (список аргументов)
Если это оператор (описан вызов процедуры), то вызов завершается точкой с запятой.
Выполнение функции при вызове осуществляется следующим образом:
- Вычисляются выражения, указанные как аргументы в операторе вызова. Тип значения, который получается после вычисления, должен совпадать с типом формального параметра в описании функции. Запоминается точка вызова функции, т. е. то место программы, с которого продолжится выполнение программы после завершения функции. Выделяется место в памяти под формальные параметры в соответствии с их типом. Для ссылок и выходных параметров место не выделяется, т. к. их имена являются псевдонимами аргументов. Каждому параметру сопоставляется соответствующий аргумент. Смысл сопоставления – установить между ними соответствие, т. е. определить, какой аргумент какому параметру соответствует. При этом в параметр-значение копируется значение из аргумента. Выполняются операторы тела функции. Если среди параметров имеются выходные параметры, то в теле функции они обязательно должны получить какие-то значения. Значения параметров-ссылок могут использоваться внутри тела. Они могут также получать новые значения (но не обязательно). Если функция возвращает значение (то есть не является процедурой), то после выполнения оператора return возвращаемое значение передается в точку вызова функции, где и может быть немедленно использовано. Если тип функции void (то есть это процедура), то управление передается оператору, который следует после оператора вызова.
Рассмотрим несколько примеров.
Пример 1
static double ВычислениеСуммы(double a, double b)
{
double s; // служебная переменная для вычисления суммы
s = a + b; // вычисление суммы
return s; // возврат вычисленного значения
}
Данная функция в качестве исходных данных получает два вещественных числа. Значения при вызове функции копируются в параметры а и b. Сумма вычисляется и сохраняется в переменной s вещественного типа. Обратите внимание, что тип функции тоже вещественный. Значение из этой переменной возвращается в точку вызова. Предположим, что в главной функции имеются следующие операторы:
double z, z1, x2;
z1 = 15.8; z2 = 4.2;
z = ВычислениеСуммы(z1, z2); // 1
При выполнении оператора 1 вызывается функция, значения аргументов z1 и z2 копируются соответственно в параметры a и b, после чего выполняется алгоритм функции. Результат сложения = 20. Это значение возвращается в точку вызова и присваивается переменной z. Отметим, что подобное использование функции (вызов для выполнения) возможно не только в главной функции, но и в любом месте программы, причем не один, а много раз. Одно описание, а вызовов может быть много.
Пример 2
static void Сумма(double a, double b, out double z)
{
z = a + b;
}
Здесь функция не возвращает никакого значения, о чем свидетельcтвует тип void. Однако сумма значений все равно вычисляется и сохраняется в параметре z. Этот параметр выходной (out), при завершении работы функции его значение не будет потеряно, а переместится в соответствующий аргумент. Пусть имеется следующий текст:
double R;
Сумма(15.8, 4.2, R); // 2
При выполнении оператора 2 вызывается функция. Оператор вызова имеет три аргумента. Два первых копируются в параметры a и b. В третий параметр ничего не копируется – просто устанавливается соответствие между z и R. При выполнении функции параметр z является псевдонимом аргумента R. Фактически сумма, записанная в z, сохраняется в R. Обратите внимание на то, как вызывается функция. Это именно отдельный оператор, т. е. функция фактически является процедурой.
Пример 3
static void sum(double a, double b, ref double t)
{
t = t + a + b;
}
Функция похожа на предыдущую, но третий параметр – универсальный, т. е. параметр-ссылка. Значение суммы также сохраняется в нем, но это сумма трех чисел: к тому, что было в t, прибавляются значения a и b. Пусть имеется текст:
double d;
d = 10.0;
sum(15.8, 4.2, ref d); // 3
При выполнении оператора 3 вызывается функция. Значения двух первых аргументов копируются в соответствующие параметры, а между аргументом d и параметром t устанавливается соответствие: они псевдонимы, т. е. фактически указывают на одно и то же место памяти (на одно и то же значение). Перед началом выполнения функции параметр t получает значение 10. При выполнении функции параметр t получает значение 30, и соответствующий аргумент d также имеет то же значение. Фактически вид параметра ref обеспечивает передачу значения в функцию и получение обратно измененного значения.
Пример 4
static void Добавить(student[] m, ref int k, student R)
{
m[k] = R;
k++;
}
Функция обеспечивает добавление в массив структур нового значения типа student. Нам сейчас неважно, что именно содержит структура student. Если предположить, что параметр k содержит количество занятых элементов массива, то новое значение размещается в первом свободном элементе массива (вспомним, что счет элементов идет с 0). После этого значение k увеличивается на 1, т. е. тем самым указывается, что в массиве стало больше на одно значение. Учитывая, что параметр k – это параметр-ссылка, можно считать, что это значение сохранится и в аргументе. Следующий текст представляет собой схему использования вышеприведенной функции.
struct student
{ … }
student S1, S2;
student [ ] massiv = new student[200];
int kol = 0;
…
// предположим, что значение S1 инициализировано,
// т. е. поля этой структуры уже имеют значения
Добавить(massiv, ref kol, S1); // 4
…
// предположим также, что значение S2 тоже инициализировано,
// т. е. поля этой структуры тоже имеют значения
Добавить(massiv, ref kol, S2); // 5
Оператор 4 вызывает функцию и передает ей ссылку на массив – вспомним, что переменная massiv содержит ссылку. Кроме того, аргумент kol также является ссылкой. После выполнения оператора 4 в массиве заполнен нулевой элемент значением переменной S1, а переменная kol стала равна 1. При выполнении оператора 5 значение переменной S2 становится первым элементом массива, а переменная kol получает значение 2.
Другие примеры организации функций приведены в [4] в лабораторных работах 10 и 11.
Принято считать, что число аргументов при вызове функции должно точно соответствовать числу параметров в описании функции. Это действительно так, если параметры описаны по ранее изложенным правилам. Однако есть возможность при вызове функции передавать ей произвольное число аргументов. Для реализации этой возможности необходимо задать ключевое слово params в последнем параметре. В этом случае последний параметр считается массивом того типа, который указан как тип параметра. В операторе вызова для этого параметра можно указать больше, чем один аргумент. Все эти аргументы будут переданы в функцию через последний параметр-массив.
|
Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 |


