Конечно, такая простота в действительности делает программу менее понятной. Явное указание параметров позволяет увидеть, что именно подвергается изменениям, даже мельком взглянув на функцию. Если встречается вызов функции, имеющий вид myFunction(val1, out va12), то сразу становится ясно, что следует обратить самое пристальное внимание на переменные val1 и val2 и что переменной val2 по окончании выполнения функции будет присвоено новое значение. И наоборот, если у этой функции не имеется никаких параметров, то мы не сможем сделать предположения относительно того, над какими данными она выполняет манипуляции.
Наконец, не следует забывать о том, что использование глобальных данных не всегда оказывается возможным. Далее нам встретятся примеры программ, хранящихся в разных файлах и/или принадлежащих различным пространствам имен, которые взаимодействуют между собой посредством использования функций. В таких случаях код зачастую оказывается до такой степени разделенным, что непонятно, где именно следует хранить глобальные переменные.
Итак, оба способа обмена данными можно использовать с равным успехом. В большинстве случаев мы будем настаивать на применении параметров вместо глобальных данных, однако существуют отдельные ситуации, когда более подходящим может оказаться использование именно глобальных данных; и уж, во всяком случае, такой способ не является ошибкой.
4.5.Функция Main()
Теперь, когда вы познакомились почти со всеми простыми способами, применяемыми для создания и использования функций, давайте вернемся назад и более подробно рассмотрим функцию Main ().
Как было сказано ранее, функция Main() является точкой входа в приложение на C# и выполнение этой функции охватывает выполнение приложения. У этой функции имеется параметр – string[] args, однако до сих пор мы его не описывали. В данном разделе вы познакомитесь с этим параметром и узнаете, каким образом его можно применять. Существуют четыре различные сигнатуры, которые можно использовать для функции Main():
- static void Main();
- static void Main(string[] args);
- static int Main();
- static int Main(string[] args).
Естественно, следует обратить внимание, что никакие знаки препинания после функции Main() не ставятся, здесь они стоят исключительно вследствие требований грамматики русского языка.
При желании аргумент args можно опускать. Причина, по которой мы до сих пор пользовались вариантом с этим аргументом,– в том, что такая версия генерируется автоматически при создании консольного приложения в VS.
Третья и четвертая версии сигнатуры, приведенные выше, возвращают значение типа int, которое может быть использовано для указания на то, каким образом завершилось выполнение приложения, и которое часто используется для определения ошибки (хотя это в любом случае является обязательным). Обычно, возвращаемое значение равно 0, что означает "нормальное" окончание (т. e. приложение закончило свою работу и может быть завершено в безопасном режиме).
Параметр args функции Main() представляет собой метод, позволяющий приложению получать извне информацию, задаваемую в процессе его выполнения. Эта информация принимает форму параметров командной строки.
Параметры командной строки вам уже наверняка встречались. Когда производится запуск приложения из командной строки, то при этом очень часто имеется возможность непосредственно задать некоторую информацию, например, имя файла, загрузка которого требуется для выполнения приложения. В качестве примера давайте рассмотрим Windows-приложение Notepad. Запустить его можно, просто набрав notepad – либо в окне командного приглашения, либо в окне, которое появляется при выборе опции Run (Выполнить) из меню Start (Пуск). Однако в этих же окнах мы можем набрать нечто вроде: notepad myfile.txt. В результате Notepad либо загрузит файл myfile.txt, либо, если такого файла не существует, предложит его создать. В данном случае "myfile.txt" является аргументом командной строки. Мы можем сами написать консольное приложение, которое будет работать аналогичным образом, использовав параметр args.
Когда консольное приложение запускается, то все заданные параметры командной строки помещаются в массив
args. В дальнейшем эти параметры могут использоваться по мере необходимости.
4.6.Функции структур
В предшествующей лекции рассматривались типы структур, предназначенных для хранения в одном месте нескольких элементов информации. На самом деле возможностей у структур гораздо больше, и одной из них является способность содержать не только данные, но и функции. На первый взгляд подобное может показаться немного странным, однако на практике это приносит много пользы.
В качестве простого примера рассмотрим следующую структуру. struct customerName { public string firstName, lastName; }
Если у нас имеются переменные типа customerName и нам необходимо вывести на консоль полное имя, мы будем вынуждены конструировать это имя из его составных частей, например, для переменной типа customerName с именем customer можно использовать следующий синтаксис:
customerName myCustomer; | |||
myCustomer.firstName = "Jobn"; | |||
myCustomer.lastName = "Franklin"; | |||
Console.WriteLine("{0} {1}", customer.firstName, customer.lastName); |
Имея возможность включать в структуры функции, мы можем упростить эту процедуру за счет централизованного выполнения часто встречающихся задач. В данном случае это можно сделать следующим образом:
struct customerName { public string firstName, lastName;
public string Name () { return firstName + lastName; } }
Эта функция очень похожа на другие функции, рассматриваемые в этой лекции, за исключением того, что в ней отсутствует модификатор static. Причины этого отсутствия станут понятны позже, а пока достаточно просто запомнить, что это ключевое слово для функций структур не требуется. Мы можем воспользоваться этой функцией следующим образом:
customerName myCustomer; myCustomer.firstName = "John"; myCustomer.lastName = "Franklin";
Console.WriteLine(customer.Name());
Этот синтаксис гораздо проще и понятнее, чем предшествующий.
Необходимо отметить, что функция Name() имеет непосредственный доступ к полям структуры firstName и lastName. В рамках структуры customerName они могут считаться глобальными.
4.7.Перегрузка функций
В начале этой лекции описывалось как сравнивать сигнатуру функции при ее вызове. При этом необходимо иметь отдельные функции для работы с переменными различных типов. Наличие оператора перегрузки подтверждает, что последнее действительно является проблемой. Этот оператор предоставляет возможность создавать одновременно несколько функций, имеющих одинаковое имя, но работающих с параметрами различных типов.
Например, ранее мы использовали следующую программу, в которой содержалась функция с именем MaxValue (): class Classl { static intMaxValue(int[] intArray) { int maxVal = intArray[0];
for (int i = 1; i < intArray.Length; i++)
{
if (intArray[i] > maxVal)
maxVal = intArray[i];
}
return maxVal; }
static void Main(string[] args)
{
int[] myArray = {1, 8, 3, 6, 2, 5, 9, 3, 0, 2}; int maxVal = MaxValue(myArray);
Console.WriteLine("The maximum value in myArray is {0}", maxVal);
}
}
Эта функция может использоваться только для массивов значений типа int. Мы могли бы создать функции с другими именами, предназначенные для работы с параметрами других типов, переименовав вышеприведенную функцию как-нибудь вроде IntArrayMaxValue() и добавив функции наподобие DoubleArrayMaxValue() для работы с другими типами. В качестве альтернативы мы можем просто включить в нашу программу следующую функцию:
static double MaxValue(double[] doubleArray) { double maxVal = doubleArray[0]; for (int i = 1; i < doubleArray.Length; i++)
{
if (doubleArray[i] > maxVal) maxVal = doubleArray[i];
}
return maxVal; }
Разница между двумя функциями заключается в том, что эта функция работает со значениями типа double. Имя функции – MaxValue () – оказывается тем же самым, однако сигнатура (это принципиально) отличается. Было бы ошибкой описать две функции с одинаковым именем и одинаковой сигнатурой, однако поскольку в данном случае сигнатуры различны, то все нормально.
Теперь у нас имеются две версии функции MaxValue (), которые принимают массивы типа int и массивы типа double и возвращают максимальное значение типа int или типа double соответственно.
Красота такой формы программы в том, что не требуется явно указывать, которую из этих двух функций мы собираемся использовать. Мы просто задаем массив-параметр, и это приводит к выполнению того варианта, который соответствует типу используемого параметра.
На данном этапе стоит отметить еще одну высокоинтеллектуальную черту VS. Если в нашем приложении имеются две одноименные функции, описанные выше, и мы наберем это имя, например, в Main(), то VS выведет доступные варианты перегрузки данной функции. Если будет набрано следующее:
double result = MaxValue(
то VS выведет информацию по обеим версиям функции MaxValue (), которые мы можем просмотреть с помощью кнопок "стрелка вверх" и "стрелка вниз". При перегрузке функций учитываются все аспекты, касающиеся их сигнатур. Существует возможность, например, описать две различные функции, одна из которых принимает параметры по значению, а другая, соответственно, по ссылке:
static void showDouble(int val) {
}
static void showDouble(ref int val) {
}
Выбор используемой версии осуществляется исключительно на основании того, имеется ли в обращении к функции ключевое слово ref. При следующем вызове будет использован вариант, в котором параметр передается по ссылке:
showDouble(ref val);
А такой вызов позволит передать параметр по значению: showDouble(val);
Аналогичным образом можно описывать функции, отличающиеся числом требующихся им параметров и т. п. 4.8. Вопросы для повторения
1. Описание функций.
2. Параметры функций.
3. Возвращаемые значения.
4. Сигнатура функций.
5. Передача параметров по ссылке и по значению.
6. Область действия переменных.
7. Функция Main.
8. Перегрузка функций.
В предыдущих лекциях мы рассмотрели синтаксис языка С# и основы программирования на нем. Кроме того, мы научились собирать работоспособные консольные приложения. Однако для того, чтобы получить доступ ко всему потенциалу С# и .NET Framework, необходимо научиться использовать методы объектно-ориентированного программирования (ООП).