3668

Процедури і функції — методи класу

Лекция

Информатика, кибернетика и программирование

Процедури і функції — методи класу Історично першим способом структуризації програм в мовах програмування високого рівня було використання процедур і функцій — щодо самостійних фрагментів програм, оформлених особливим чином і забезпечених ...

Украинкский

2012-11-05

64 KB

2 чел.

Процедури і функції — методи класу

Історично першим способом структуризації програм в мовах програмування високого рівня було використання процедур і функцій — щодо самостійних фрагментів програм, оформлених особливим чином і забезпечених ім'ям. Згадка цього імені в програмі називається викликом процедури (функції).

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

Структуризація програм необхідна по двох причинах. По-перше, неможливо написати більш менш складну програму як єдине ціле, не розчленовувавши її на окремих відносно самостійні частини — підпрограми. По-друге, підпрограми відкривають можливість повторного використання одного разу створеного коду і сприяють появі бібліотек підпрограм. У мові С# підпрограми не мають самостійного значення — вони можуть бути тільки методами класу.

Опис підпрограм

У С# немає спеціальних зарезервованих слів procedure і function для опису підпрограм. Оскільки вони є методами класу, ці слова надмірні. Синтаксис опису такий:

[модифікатори]   <Тип>  <Ім'я>   ([Формальниє_параметри>]) {<Тіло>}

У квадратних дужках вказані необов'язкові елементи. Модифікатори визначають область видимості підпрограми і детально розглядаються в розділі І..

Зараз поясню лише два модифікатори — private і public. Будь-які члени класу (зокрема методи-підпрограми), оголошені з модифікатором private, доступні тільки в методах даного класу. Модифікатор public робить метод (підпрограму) доступним в будь-якому місці програми. Якщо модифікатор не вказаний, вважається, що даний член класу помічений як закритий (з модифікатором private).

Формальні параметри можуть бути відсутніми, але і в цьому випадку круглі дужки за ім'ям підпрограми обов'язкові.

Тип підпрограми може бути будь-яким типом даних. В цьому випадку підпрограма є функцією, яка повертає результат вказаного типу. У тілі функції обов'язково указується оператор return, який привласнює функції потрібне значення. Як тип можна вказати зарезервоване слово void, яке означає відсутність типу. В цьому випадку підпрограма є процедурою і в ній не можна указувати оператора return.

Ім'я підпрограми повинне бути унікальним в поточній області видимості ідентифікатора.

Тіло підпрограми обов'язково реалізується у вигляді блоку операторів, тому за закриваючою круглою дужкою повинна слідувати відкриваюча фігурна, навіть якщо тіло підпрограми відсутній або містить єдиний оператор.

Приклади описів:

int A()  {...}

void В(...) {...}

public string С() { . . . }

Тут А і В — закриті члени класу, які доступні тільки в методах цього ж класу, причому А — цілочисельна функція, а В — процедура. С — відкрита функція рядкового типу, доступна в будь-якій точці програми.

Формальні параметри

Формальні параметри є засобом настройки підпрограми на виконання потрібної роботи. Наприклад, звичайна математична функція sin(х) має формальний параметр х, в тілі функції що трактує як кут, для якого слід обчислити значення синуса.

У С# розрізняють три різновиди (статусу) формальних параметрів: вхідні, вихідні і посилальні. За допомогою вхідних параметрів програма передає дані в тіло підпрограми. При цьому, фактично, в підпрограму передаються не дані, а їх копії, тому зміна вхідних параметрів в підпрограмі ніяк не передається програмі що визиває. Вихідні параметри оголошуються із зарезервованим словом out. Вони призначені для передачі даних з підпрограми в програму що визиває. У тілі підпрограми обов'язково повинен бути присутнім оператор привласнення цим параметрам нового значення — за цим стежить компілятор. Посилальні параметри передаються підпрограмі по посиланню, тобто в тіло підпрограми передається адреса параметра в пам’яті комп'ютера. Такі параметри позначаються зарезервованим словом ref. Вони дозволяють як передати дані в підпрограму, так і отримати з неї нові дані.

При виборі того або іншого різновиду формальних параметрів потрібно керуватися наступними міркуваннями:

  •  Якщо параметр визначений як вхідний, програміст може бути упевнений в тому, що виклик підпрограми не буде пов'язаний з яким-небудь побічним ефектом, оскільки в підпрограму передається не сам параметр, а лише його копія. На місці цього параметра може розташовуватися довільний вираз відповідного типу. Проте програма витрачатиме додаткові ресурси комп'ютера (пам'ять і час), особливо якщо в підпрограму передається великий масив даних.
  •  Якщо параметр помічений як вихідний (out) або посилальний (ref), в підпрограму передається сам параметр (його адреса), а не його копія, що мінімізує витрати пам'яті і часу в точці виклику. Він може (ref) або повинен (out) змінитися в підпрограмі, тому на його місці слід указувати не вираз, а змінну відповідного типу.
  •  У підпрограмі можна оголосити не один, а декілька параметрів. В цьому випадку сусідні параметри розділяються комами, утворюючи список параметрів Останнім (або єдиним) параметром списку може оголошуватися масив будь-якого типу із зарезервованим словом params. В цьому випадку на місці даного параметра при виклику підпрограми може стояти скільки завгодно параметрів такого типу.

Програма з лістингу 19.1 ілюструє використання функції А з довільною кількістю формальних параметрів.

Лістинг 18.1. Функція з довільною кількістю параметрів

using System;

class Program

{

   static int A(params int[] I)

   // Ця функція знаходить суму довільної кількості  цілочисельних параметрів звернення

   {

       int Sum = 0;

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

           Sum += I[i]; ;

       return Sum;

   }

   static void Main()

   {

       Console.WriteLine("{0} {1}", A(1, 2, 3), A(4 , 5, 6, 7, 8, 9));

       Console.ReadLine();

   }

}

Виклик підпрограм

Для виклику підпрограми потрібно вказати її ім'я і список фактичних параметрів (якщо в описі підпрограми є непорожній список формальних параметрів). Список фактичних параметрів повинен відповідати за типом, кількістю, порядком проходження і статусом списку формальних параметрів.

Якщо в списку п формальних параметрів, то фактичних параметрів повинно бути не менше п (відповідність по кількості). Кожному і-му формальному параметру ставиться у відповідність і-й фактичний параметр. Останньому формальному параметру за умови, що він оголошений із зарезервованим словом params, ставляться у відповідність фактичні параметри, що все залишилися (відповідність по порядку).

Якщо формальний параметр оголошений із зарезервованим словом ref або out, то фактичний параметр повинен супроводжуватися таким же зарезервованим словом в точці виклику (відповідність по статусу).

Відповідність за типом означає, що фактичний параметр повинен мати такий же (або сумісний з ним) тип, який має формальний параметр.

Відповідність по статусу, тобто поява зарезервованих слів ref і out в точці виклику підпрограми, особливість мови С#, що відрізняє його від всіх інших мов програмування. Цю особливість слід всіляко вітати, оскільки вона нагадує програмістові про те, що даний фактичний параметр є вихідним і його значення може змінитися (у разі ref) або напевно зміниться (у разі out) після виклику підпрограми. Проте із-за незвичності синтаксису ці слова часто забувають указувати, що приводить до синтаксичних помилок, що супроводжуються повідомленнями типу «Argument N: cannot convert from 'Type' to 'Out Type'» («Параметр N: неможливо перетворити 'Type' в 'Out Type'»).

Функції з побічним ефектом

Функція називається функцією з побічним ефектом, якщо крім результату, що обчислюється функцією і що повертається нею в операторові return, вона має вихідні параметри із зарезервованими словами ref або out.

Деяким програмістам, можливо, такі функції сподобаються, проте хороший стиль програмування не рекомендує їх використання. Річ у тому, що функція може викликатися не тільки в операторові привласнення, але і у виразах. Функція з побічним ефектом може порушити комутативність операцій.

Наприклад, якщо int f (out int а) — функція з побічним ефектом, то вираз а + f (out а) в загальному випадку не рівно виразу f (out а) + а.

Якщо по яких-небудь причинах функція повинна повертати декілька значень, логічніше оформити її у вигляді процедури з відповідним набором вихідних параметрів.

Рекурсія

Рекурсія — це такий спосіб організації обчислювального процесу, при якому підпрограма в ході виконання складових її операторів звертається сама до себе.

Розглянемо класичний приклад — обчислення факторіалу. Програма з лістингу 18.2  читає ціле число N і виводить на екран значення N!, яке обчислюється за допомогою рекурсивної функції Fac().

Лістинг 18.2. Обчислення факторіалу за допомогою рекурсивної функції

using System;

class Factorial 

{

static long Fac(int N)     // Рекурсивна функція

{

   if (N <= 1) // Рішення тривіальне?

       return 1; // -Да. Вихід з рекурсії

   else // -Нет. Рекурсія

       return N * Fac(N - 1);

}

static void Main()

{

   Console.WriteLine("Обчислення факториала\n");

Console.Write("Введіть ціле число: ");

string S = Console.ReadLine();

int N = int.Parse(S);

Console.WriteLine("{0}! = {1:n}", N, Fac(N));

Console.ReadLine();

}

}

При виконанні правильно організованої рекурсивної підпрограми здійснюється багатократний перехід від деякого поточного рівня організації алгоритму до нижнього рівня послідовно до тих пір, поки не буде нарешті отримано тривіальне рішення поставленої задачі. У нашому випадку рішення при N = 1 тривіально (факторіал 1 рівний 1) і використовується для зупинки рекурсії.

Рекурсивні алгоритми зазвичай виглядають витонченішими, ніж нерекурсивні, але вони мають два істотні недоліки: по-перше, при великій глибині вкладення рекурсій може виникнути переповнювання стека, оскільки кожен рекурсивний виклик залишає в стеку параметри виклику; по-друге, при вході в рекурсивну підпрограму і при виході з неї витрачається додатковий час для заштовхування параметрів виклику в стек і для витягання їх із стека. У зв'язку з цим рекомендується, якщо це можливо, використовувати нерекурсивні («плоскі») алгоритми. Наприклад, обчислити факторіал можна і за допомогою такої нерекурсивної функции:

static long Fac(int N)

{

long Res = 1;

for (int i = 1; i <= N; i++) Res = Res * i;

return Res;

}

Проте в деяких випадках обійтися без рекурсії неможливо. Наприклад, мені так і не вдалося придумати «плоский» алгоритм обходу всіх гілок дерева каталогів.

При реалізації рекурсивного алгоритму слід по можливості мінімізувати розмір даних, що передаються в рекурсивному виклику.

Лістинг 18.3. Структурізація програми з допомогою процедур

Приклад 1.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

//

   class Program

   {//все методы должны быть объявлены в каком либо классе

       //методы, имеющие обозначение static не обязаны быть привязаны к какому либо объекту

       static void Procedure1()

       {

           Console.WriteLine("Hello from Procedure 1");

       }

       static void Procedure2()

       {

           Console.WriteLine("Hello from Procedure 2");

       }

       static int Procedure3(int i)

       {

           return (int)Math.Pow(i,2);

       }

       static string Procedure4()

       {

           return "Hello World (from Procedure 4)";

       }

       static void Main(string[] args)

       {

           Procedure1();

           Procedure2();

           int result1 = Procedure3(7);

           string result2 = Procedure4();

           Console.WriteLine("результат метода Procedure3:"+result1);

           Console.WriteLine("результат метода Procedure4:"+result2);

       }

   }

 

Приклад 2.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

class A

{

   public void Procedure1()

   {

       Console.WriteLine("Hello from Procedure 1");

   }

   public void Procedure2()

   {

       Console.WriteLine("Hello from Procedure 2");

   }

   public int Procedure3(int i)

   {

       return (int)Math.Pow(i, 2);

   }

   public string Procedure4()

   {

       return "Hello World (from Procedure 4)";

   }

   public static void StaticProcedure()

   {

       Console.WriteLine("Hello from static procedure");

   }

}

   class Program

   {

       static void Main(string[] args)

       {//для использования методов класса А необходимо создать объект даного класса

           A objA = new A();

           objA.Procedure1();

           objA.Procedure2();

           int result1=objA.Procedure3(7);

           string result2 = objA.Procedure4();

           Console.WriteLine("Результат выполнения метода Procedure3:"+result1);

           Console.WriteLine("Результат выполнения метода Procedure4:" + result2);

           //также остаеться возможность использовать статические (static) методы даного класса А

           //для этого необходимо указать какому классу принадлежит статический метод

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

           A.StaticProcedure();

           

       }

   }


 

А также другие работы, которые могут Вас заинтересовать

33360. Система прерываний КР1816ВУ51 48 KB
  Система развивается с появлением новых типов микроконтроллеров этой серии число источников прерываний постоянно увеличивается и достигло в некоторых пятнадцати. Рассмотрим систему прерываний МК51. Из пяти источников прерываний внешними являются входы INT0 и INT1 а внутренними два счетчика таймера и последовательный порт.
33361. Система команд КР1816ВУ51 33 KB
  Всего в системе команд семейства MК51 можно выделить 5 групп: команды арифметических операций команды логических операций команды пересылки данных команды операций с битами и команды передачи управления. Команды операций с битами Эти команды устанавливают в 1 SETB или 0 CLR прямоадресуемый бит внутренней памяти данных изменяют его значение на противоположное CLR выполняют операции ND и OR над флагом переноса С и прямоадресуемым битом ND и ORL осуществляют пересылку значения между флагом С и прямоадресуемым битом MOV...
33362. Типовая схема СУ на базе КР1816ВУ51 27 KB
  В случае если производительность процессора микроконтроллера достаточна для решения поставленной задачи эту проблему можно решить организацией системы шин к которым и подключаются все необходимые устройства. Кроме достаточной производительности микроконтроллер должен иметь возможность подключения внешней памяти данных. Микроконтроллер МК51 обладает такой возможностью.
33363. Состав и назначение элементов процессорного ядра, характеристика ОМК АТ90S8515 31 KB
  Организация памяти микроконтроллера Память микроконтроллеров VR семейства Clssic выполнена по Гарвардской архитектуре в которой разделены не только адресные пространства памяти программ и памяти данных но также и шины доступа к ним. В связи с тем что регистровая память находится в адресном пространстве ОЗУ об этих двух областях памяти обычно говорят как об одной. 6 регистров общего назначения R26 R31 X Y Z используется в качестве указателей при косвенной адресации памяти данных. Каждый регистр файла имеет свой собственный адрес в...
33364. Структура памяти ОМК АТ90S8515 30.5 KB
  Причем память данных состоит из трех областей: регистровая память статическое ОЗУ и память на основе EEPROM. В связи с тем что регистровая память находится в адресном пространстве ОЗУ об этих двух областях памяти обычно говорят как об одной. Память программ Память программ ёмкостью 4 К 16разрядных слов предназначена для хранения команд управляющих функционированием микроконтроллера.
33365. Порты ввода-вывода ОМК АТ90S8515 31.5 KB
  Конфигурирование каждой линии порта задание направления передачи данных может быть произведено программно в любой момент времени. Обращение к портам ввода вывода Обращение к портам производится через регистры ввода вывода причем под каждый порт в адресном пространстве ввода вывода зарезервировано по 3 адреса. По этим адресам размещаются три регистра: регистр данных порта PORTx регистр направления данных DDRx и регистр выводов порта PINx. Действительные названия регистров и их разрядов получаются подстановкой названия порта вместо...
33366. Таймер/счётчики ОМК АТ90S8515 38 KB
  Как правило эти выводы линии портов ввода вывода общего назначения а функции реализуемые этими выводами при работе совместно с таймерами счетчиками являются их альтернативными функциями. Выводы используемые таймерами счетчиками общего назначения Название T90S8515 Описание T0 PB0 Вход внешнего сигнала таймера T0 T1 PB1 Вход внешнего сигнала таймера T1 ICP ICP Вход захвата таймера T1 OC1 Выход схемы сравнения таймера T1 OC1 PD5 То же OC1B OC1B То же TOSC1 Вход для подключения резонатора TOSC2 Выход для подключения резонатора ...
33367. Универсальный асинхронный приемопередатчик ОМК АТ90S8515 38.5 KB
  Управление работой приемопередатчика осуществляется с помощью регистра управления UCR. Текущее состояние приемопередатчика определяется с помощью регистра состояния USR. При чтении регистра UDR выполняется обращение к регистру приемника при записи к регистру передатчика. Работа передатчика разрешается установкой в 1 разряда TXEN регистра UCR UCSRB.
33368. Система прерываний ОМК AT90S8515 63 KB
  При возникновении прерывания микроконтроллер сохраняет в стеке содержимое счетчика команд PC и загружает в него адрес соответствующего вектора прерывания. По этому адресу должна находиться команда относительного перехода к подпрограмме обработки прерывания. Кроме того последней командой подпрограммы обработки прерывания должна быть команда RETI которая обеспечивает возврат в основную программу и восстановление предварительно сохранённого счетчика команд. Младшие адреса памяти программ начиная с адреса 001 отведены под таблицу векторов...