Подпрограммы: процедуры и функции - 1


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

Использование подпрограмм позволяет избежать дублирования кода, в случае если необходимо один и тот же код писать в разных местах программы. 
Библиотеки, которые импортируются в программу (например, System) состоят из подпрограмм, которые уже были кем-то составлены. Программистам не нужно думать о том, какие алгоритмы в них реализованы, они просто применяют их, задумываясь только о том, что именно они делают. Это большая экономия времени. Нет нужды писать алгоритм, который уже был кем-то написан.

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

Подпрограммы,или методы, бывают двух типов -  функции (те, которые возвращают результат работы) и процедуры (те, которые не возвращают).

Начнем со второго типа. Попробуем написать простой пример.
Предположим, что нам нужно выводить на экран строку "Error" каждый раз, когда в коде может возникнуть ошибка по вине пользователя (например, когда он вводит неверные данные).
Это можно сделать, написав оператор
Console.WriteLine("Error");
А теперь представим, что такую строчку нужно вставить во многих местах программы. Конечно, можно просто везде ее написать. Но это решение имеет два недостатка.
1) данная строка будет храниться в памяти много раз;
2) если мы захотим изменить вывод при ошибке, то придется менять эту строку по всей программе, что достаточно неудобно.

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

using System;
class Program {
    static void PrintError() {
        Console.WriteLine("Error");
    }
    static void Main() {
        PrintError();
    }
}

 

Процедура начинается со слова void. После имени процедуры записаны пустые скобки.
Все операторы, которые выполняются в процедуре, записываются с отступом. 
Модификатор Static означает, что данное поле, метод или свойство будет принадлежать не каждому объекту класса, а всем им вместе.
Методы и процедуры записываются до главного метода Main().

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

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

 

void printErrorZero()
{
    Console.WriteLine("Error. Division by zero!");
}

 

 

void printErrorInput()
{
    Console.WriteLine("Error in input!");
}

А если возможных ошибок будет намного больше? Тогда такое решение нам не подойдет.
Надо научиться управлять процедурой, указывая ей, какое сообщение на ошибку нужно вывести.
Для этого нам понадобятся параметры, которые мы будем записывать в круглых скобках, после имени процедуры
void printError(string s)
{
    Console.WriteLine(s);
}
В данной процедуре s - это параметр - специальная переменная, которая позволяет управлять процедурой.
Параметр - это переменная, от значения которой зависит работа подпрограммы. Имена параметров перечисляются через запятую в заголовке подпрограммы. Перед параметром записывается его тип.

Теперь при вызове процедуры нужно в скобках указывать фактическое значение, которое будет присвоено параметру (переменной s) внутри нашей процедуры
printError("Error! Division by zero!");
Такое значение называется аргументом.
Аргумент - это значение параметра, которое передается подпрограмме при ее вызове.
Аргументом может быть не только постоянное значение, но и переменная или арифметическое выражение.

Локальные и глобальные переменные
Часто необходимо использовать дополнительные переменные, которые будут использоваться только в подпрограмме. Такие переменные называются локальными (или местными), с ними можно работать только внутри той подпрограммы, в которой они созданы.
 
Область видимости локальной переменной - это блок, ограниченный фигурными скобками, внутри которого она объявлена.

Основная программа в С# - это тоже подпрограмма, поэтому все переменные объявленные внутри void Main(), - это локальные переменные. Остальные подпрограммы про локальные переменные других подпрограмм ничего не "знают".

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

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

В С# при старте программы все глобальные переменные автоматически обнуляются (логические переменные принимают значение false).

Проанализируйте три программы:
1) В этой программе переменная i локальная. Локальная переменная описывается внутри подпрограммы. 2) Здесь, даже если есть переменная i в основной программе (со значением 7), будет создана новая локальная переменная i со значением 5. 
При выполнении этой программы на экране появится значение 75.
3) В этой программе существует глобальная переменная i. Ее значение можно изменить внутри подпрограммы, и внутри основной программы.
Процедура будет работать с глобальной переменной i и ей будет присвоено новое значение, равное 2. На экран выводится значение 2.
static void test()
{
    int i = 5;
    Console.Write("i");
}
static void test()
{
    int i = 5; 
    Console.Write("i");
}

static void Main()
{
   int i = 7;
   Console.Write("i");
   test();
}
using System;
class Program 
{
    int i;
    static void test() 
    {
        i = 2;
    }
    static void Main()
    {
        test();
        Console.Write("i");
    }
}

Задача
Составить процедуру, которая меняет местами значения двух переменных.

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

Попробуем написать процедуру таким образом:
static void Swap (int a, int b)   // при таком описании параметров процедуры, 
{                                 // будет происходить копирование значений аргументов (x и y)
     int c;                       // переменные a и b - самостоятельные переменные, не связанные с x и y  
     c = a; a = b; b = c;
}
static void Main()
{
     int x = 1, y = 2;
     Swap (x, y);         //значения переменных x и y (аргументы) копируются в параметры a и b, x = 1, y = 2   
}                                       
Если запустить данную программу, то можно увидеть, что значения переменных x и y не изменилось. Для того чтобы параметры изменяли значения аргументов, необходимо использовать передачу данных по ссылке. Для этого перед названием типа данных в заголовке подпрограммы необходимо написать ref.
void Swap ( ref int a, ref int b )  // теперь переменные a и b получают адреса переменных x и y в памяти
{
      int c; 
      c = a; a = b; b = c;
}
static void Main() 
{ 
    int x = 1, y = 2; 
    Swap (ref x, ref y); 

Применение: если вы передаете аргумент по ссылке, то при вызове процедуры на этом месте может стоять только имя переменной (НЕ число и НЕ арифметическое выражение).

Нельзя вызвать процедуру таким образом:
Swap(x, 4);
Swap(5+x, y);