[ЗМІСТ]      [Далі]       [Назад]      [Початок розділу]

 

6.3. Підпрограми у мовах програмування

 

      У мовах програмування, що розглядаються нами, всі підпрограми можна поділити на дві групи: вбудовані (стандартні) та визначені користувачем. Багато стандартних функцій та процедур ми розглянули в темі 5 "Прості типи даних". Інші вбудовані підпрограми будемо вивчати за потребою. Перейдемо тепер до підпрограм користувача.

      Мова Паскаль. Перш ніж перейти до розгляду правил використання підпрограм в мові Паскаль розглянемо загальну структуру програми.

      Програма складається з одного блоку, глобального, який може містити всередині декілька інших блоків, локальних. Кожний блок складається з заголовка, необов'язкової описової частини і обов'язкової частини реалізації, яка задає спосіб отримання множини вихідних даних за множиною вхідних даних даної програмної одиниці (одне з них може бути пустим). Глобальний блок - це головна програма, повинен існувати завжди. Для нього заголовок має вигляд

 

      program Name;

 

де Name - ім'я програми.

 

      Зауваження 6.1. Компілятор мови Турбо-Паскаль не відрізняє великі і малі латинські літери.

      Імена, що використовуються користувачем для ідентифікаторів не повинні

співпадати із зарезервованими словами мови

      Локальні блоки - це підпрограми користувача, їх присутність необов'язкова. Для функцій заголовок виглядає так

 

      function NameF [(х: q; у: r; ...; z: s)]: t;

 

де NameF - ім'я функції; х, у,..., z - можливо пустий список аргументів даної функції типів q, r,..., s відповідно; t - тип результату, що повертається функцією. В якості типу t може використовуватись будь-який тип даних з розглянутих в попередній частині курсу.

      У мові Паскаль реалізована друга система (Pv) визначення процедур. У зв'язку з цим, у процедури заголовок наступний

 

      procedure NameP [(х: q; у: r; ...; z: s;

                        var a: k; var b: l; ...; var c: m)];

 

де NameP - ім'я процедури; х, у,..., z, a, b,..., c - можливо пустий список формальних параметрів даної процедури типів q, r,..., s, k, l,..., m відповідно. Тут параметри х, у,..., z, що називають параметрами-значеннями, є аргументами, а параметри a, b,..., c - параметрами-змінними процедури.

      Описова частина будь-якого блоку має вигляд

 

       [const c= a; d= b;]             {опис констант}

       [type f= s; g= t; h= r;]        {опис типів}

       [var х: f; у: g; z: h; w: j;]   {опис змінних}

       [function. .. end;]             {опис функцій}

       [procedure. .. end;]            {опис процедур}

 

Тут c, d - символічні імена констант зі значеннями a, b відповідно; f, g, h - символічні імена типів користувача з множинами значень s, t, r відповідно; х, у, z, w - ідентифікатори змінних відповідно типів f, g,

h, j.

 

      Зауваження 6.2. У мові Турбо-Паскаль розділи описової частини можуть слідувати в будь-якому порядку. Необхідно тільки дотримуватись умови: кожна величина повинна бути описана перед тим, як вона буде використана.¦

      Частина реалізації глобального блоку має вигляд

 

            begin Р end.

 

Вона обов'язково завершується символом крапка. Тут ідентифікатори begin і end грають роль відкриваючої і закриваючої дужок, а Р - це тіло програми, що складається з операторів мови Паскаль розділених символом крапка з комою.

 

      Зауваження 6.3. Перед ідентифікатором end символ ";" можна не ставити.¦

      Для локального блоку частина реалізації виглядає так

 

            begin Q end;

 

Вона обов'язково завершується символом крапка з комою. Тут Q – тіло підпрограми.

 

      Об'єкти, які використовуються у програмі та містяться в описовій частини, в свою чергу, називаються відповідно глобальними та локальними. Областю дії будь-якого об'єкта є блок, в якому він описаний, і всі вкладені в нього блоки. Таким чином, якщо об'єкт (константа, змінна, підпрограма або тип) має значення тільки в межах деякої частини програми, то цей об'єкт називається локальним. Якщо ж деякий об'єкт визначений в головній програмі, то він називається глобальним по відношенню до програмних одиниць описаним в цій програмі (це правило розповсюджується і на підпрограми, що вміщують всередині себе інші підпрограми). Областю існування локального об'єкта є весь текст підпрограми.

      Проілюструємо вказані особливості об'єктів на простому прикладі.

      Приклад 6.13. Відмінності між локальними і глобальними змінними.

 

    program Prim1;

       var А, В: real;

       procedure Zovn;

          var C, D: real;

          procedure Vnut;

             var В, Е, F: real;

          begin

             Р(А, В, C, D, Е, F)

          end;

       begin

          Q(А, В, C, D, Vnut)

       end;

    begin

       R(А, В, Zovn)

    end.

 

Тут запис S(X, Y,..., Z) вказує на можливість використання в тілі S об'єктів X, Y,..., Z.

      У програмі є один глобальний блок з ім'ям Prim і два локальних блоки з іменами Zovn і Vnut.

      Змінні А і В - глобальні, і з ними можна оперувати в будь-якому місці програми Prim.

      Змінні C і D описані в процедурі Zovn, отже вони є локальними по відношенню до програми Prim і підпрограми Zovn і їх можна використати в тілі Q. Але до них не можна звертатися в тілі R. Оскільки процедура Vnut вкладена в процедуру Zovn, змінні C і D є глобальними по відношенню до Vnut і вони можуть бути присутніми в тілі Р внутрішньої процедури.

      Змінні Е і F є локальними по відношенню до процедури Vnut, вони можуть використовуватися тільки в цій підпрограмі. Тут же описана і змінна В, ім'я якої співпадає з ім'ям глобальної змінної. Виникає питання: якщо в тілі Р буде бути присутнім ім'я B, то яка змінна мається на увазі - глобальна або локальна? Відповідь на нього дає правило: кожний об'єкт інтерпретується у відповідності з описом найбільш внутрішнього з вкладених блоків, що містять в даний момент цей об'єкт.

      Процедура Vnut локальна по відношенню до процедури Zovn і тому може бути викликана тільки з тіла цієї процедури, але не з головної програми.

 

      Розглянемо тепер деякі приклади, що демонструють прийоми роботи з підпрограмами. Зазначимо передусім, що текст програми складається з рядків. Максимальна довжина будь-якого рядка тексту не повинна перевищувати 127 символів. Якщо довжина програмного рядка більше, то всі зайві символи ігноруються.

 

      Приклад 1P. Корінь n-ої степені з кожного елемента послідовності, що передує недодатньому числу (задача 6.5).

 

    program SQRS;

       var A, Eps: real;

           K, N: byte;

       function SQRTN (N, K: byte; A, Eps: real): real;

          var X, Y, Z: real;

          function XN (X: real; N: byte): real;

             var Y: real; J: byte;

          begin

             Y:= 1;

             for J:= 1 to N do Y:= Y*X;

             XN:= Y

          end;

       begin

          Y:= 0.0; X:= (A+K)/2.0;

          while abs(X-Y)>=Eps do begin

             Y:= X; Z:= XN(X, K);

             X:= (K*X+A/Z)/N

          end;

          SQRTN:= X

       end;

    begin

       write('N, Eps=? '); readln(N, Eps);

       K:= N-1;

       while true do begin

          write(' A=? '); readln(A);

          if A<=0.0 then break;

          writeln(A,'^1/', N,'=', SQRTN(N, K, A, Eps));

       end;

    end.

 

      Приклад 2P. Сума гармонічного ряду (задача 6.12).

 

    program Sum_Harmonic;

       var J, N, Ch, Zn: integer;

       procedure Sl_Dr (Ch1, Zn1: integer; var Ch2, Zn2: integer);

       begin

          Ch2:= Ch1*Zn2+Ch2*Zn1;

          Zn2:= Zn1*Zn2

       end;

       procedure Soc_Dr (var Ch, Zn: integer);

          var K: integer;

          function Gcd (M, N: integer): integer;

          begin

             while M<>N do

                if M>N then M:= M-N

                else N:= N-M;

             Gcd:= M

          end;

       begin

          K:= Gcd(Ch, Zn);

          if K>1 then begin

             Ch:= Ch div K; Zn:= Zn div K

          end

       end;

    begin

       Ch:= 1; Zn:= 1;

       repeat

          write('n (n>1)=? '); readln(N)

       until N>1;

       for J:=2 to N do begin

          Sl_Dr(1, J, Ch, Zn); Soc_Dr(Ch, Zn);

          writeln('Н( ', J:3,' )= ', Ch,' / ', Zn)

       end

    end.

 

      Вправа 6.1. Проведіть в двох останніх прикладах класифікацію об'єктів, що використовуються в них на локальність і глобальність

      Відмітимо тепер особливості використання в процедурах змінних і параметрів.

 

      Приклад 3P.

 

    program Prim3;

       var A, B, C, D: integer;

       procedure Izm (C:integer; var D: integer);

          var B: integer;

       begin

          A:= 5; B:= 1; C:= 2; D:= 3;

          writeln(A, B, C, D)

       end;

    begin

       A:= 0; B:= 0; C:= 0; D:= 0;

       Izm(C, D); writeln(A, B, C, D)

    end.

 

      У підпрограмі Izm змінна А є глобальною змінною; B - локальна змінна; величина C - це параметр-значення, а змінна D - це параметр-змінна.

      Внаслідок виконання програми Prim3 отримаємо відповідь (доведіть!)

 

 5 1 2 3

 5 0 0 3.

 

      Сформулюємо правила використання параметрів процедури:

      1) якщо параметр є аргументом, а не результатом, то його потрібно визначити як параметр-значення;

      2) якщо ж параметр означає результат роботи процедури, то його потрібно визначити як параметр-змінну.

 

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

      Розглянемо наступну ситуацію. Програма використовує процедуру T1 що містить звернення до процедури T2, яка в свою чергу викликає T1. Це приклад так званої непрямої рекурсії. Для розв’язку даної колізії в мові Паскаль служить ідентифікатор forward.

 

      Приклад 6.14.

 

    program Prim3;

       ...

       procedure T2 [( форм. параметри )]; forward;

       procedure T1;

       begin

          ...

          T2 [( факт. параметри )];

          ...

       end;

       procedure T2; { параметри не повторюються ! }

       begin

          ...

          T1;

          ...

       end;

    begin

       ...

       T1;

       ...

    end.

 

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

 

      t f(t1 x1, ..., tn xn)

      {

       s1 y1; ...; sm ym;

     

       P

      }

 

де f – ім’я функції; t – тип результату; x1, ..., xn – формальні параметри; t1, ..., tn – їх типи; y1, ..., ym – локальні змінні; s1, ..., sm – їх типи; P – тіло функції. Процедури у Сі розглядаються як функції, що не повертають результат. В такому випадку тип функції позначається ідентифікатором void або взагалі може не вказуватись. Для повернення результату функції у тілі функції P повинен бути оператор

 

            return e;

 

де eвираз типу t. Цей оператор завершує виконання функції та повертає значення e у місце виклику.

      Виклик функції у мові Сі може мати вигляд:

 

      z = f(e1, ..., en);

або

      f(e1, ..., en);

 

де e1, ..., en – вирази типів t1, . .., tn. У другому випадку результат функції ігнорується. Такий виклик застосовується, як правило, для функцій типу void.

      При виклику функції всі параметри передаються за значенням, тобто як аргументи. Для поверненя результатів підпрограм використовуються вказівники. Вказівник задає посилання на деяку змінну, або, як ще кажуть, адресу змінної. Опис вказівника p на змінну типу t у Сі виглядає так:

 

      t *p;

 

Наприклад,

      int *pn;

 

pnвказівник на змінну цілого типу. Більш докладно робота з вказівниками буде розглянута далі. Зараз тільки відмітимо, що для отримання змінної, на яку вказує вказівник, використовується операція розіменування вказівника “*”. Наприклад, *pn = 1; присвоює змінній, на яку вказує pn, значення 1. Операція “&” повертає вказівник на змінну (адресу змінної). Наприклад, &x є вказівником на змінну x.

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

 

      swap(double *px, double *py)

      {

       double z;

     

       z = *px; *px = *py; *py = z;

      }

 

та її виклик

 

      swap(&x, &y);

 

у підпрограмі swap px та py – вказівники, яким під час виклику присвоюються значення адрес змінних x та y, що забезпечує повернення результатів підпрограми.

      Зауваження 6.5. Рядки у мові Сі реалізовано як вказівники, тому рядки завжди передаються у підпрограми як модифіковані параметри. Символ “*” перед іменем рядка вказувати не потрібно. Наприклад,

 

      t f(..., char s[], ...)

 

де sпараметр типу рядок.

 

      Повернімось до загального вигляду програму і Сі. Серед усіх функцій виділяється одна функція main(), яка обов’язково повинна бути описана, тому що з неї починається виконання програми. Як і в Паскалі, кожний об’єкт повинен бути спочатку описаний, а потім використаний. Функції можуть бути розташовані в довільному порядку, але якщо деяка функція використовується раніше, ніж вказано її повний опис, треба перед використанням надавати так званий прототип функції, який є заголовком функції, наприклад,

 

      swap(double *px, double *py);

 

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

 

      Приклад 1C. Корінь n-ої степені з кожного елемента послідовності, що передує недодатньому числу (задача 6.5).

 

#include <stdio.h>

#include <math.h>

 

/* SQRS */

 

double a, eps;

unsigned k, n;

 

double xn (double x, unsigned n)

{

  double y; unsigned j;

 

  y = 1;

  for(j=1; j<=n; j++)

    y *= x;

  return y;

}

 

double sqrtn (unsigned n, unsigned k, double a, double eps)

{

  double x, y, z;

 

  y = 0.0; x = (a+k)/2.0;

  while (fabs(x-y)>=eps)

    {

     y = x; z = xn(x, k);

     x = (k*x+a/z)/n;

    }

  return x;

}

 

main()

{

 

  printf(N, Eps=?); scanf(“%u %lf”, &n, &eps);

  k = n-1;

  while (1)

    {

     printf(A=?); scanf(“%lf”, &a);

     if (a<=0.0) break;

     printf(“%lf^1/%u=%lf\n”, a, n ,sqrtn(n, k, a, eps));

    }

}

 

      Приклад 2C. Сума гармонічного ряду (задача 6.12).

 

#include <stdio.h>

 

/* Sum_Harmonic */

 

int j, n, ch, zn;

 

sl_dr (int ch1, int zn1, int *pch2, int *pzn2)

{

 

  *pch2 = ch1* *pzn2 + *pch2*zn1;

  *pzn2 = zn1* *pzn2;

}

 

int gcd(int m, int n)

{

 

  while (m!=n)

    if (m>n) m -= n;

    else n -= m;

  return m;

}

 

soc_dr (int *pch, int *pzn)

{

  int k;

 

  k = gcd(*pch, *pzn);

  if (k>1)

    {

     *pch /= k; *pzn /= k;

    }

}

 

main()

{

 

  ch = 1; zn = 1;

  do

    {

     printf(“n (n>1)=? ”); scanf(“%d”, &n);

    }

  while (n<=1);

  for(j=2; j<=n; j++)

    {

     sl_dr(1, j, &ch, &zn);

     soc_dr(&ch, &zn);

     printf(“Н(%d)= %d/%d\n”, j, ch, zn);

    }

}

 

У прикладах 1C та 2C використання глобальних змінних a, eps, k, n та j, n, ch, zn обумовлене тільки дотриманням аналогії з прикладами на Паскалі. Насправді можна було використати локальні змінні у функції main.

 

      Приклад 3C.

 

#include <stdio.h>

 

/* Prim3 */

 

int a, b, c, d;

 

izm (int c, int *d)

{

  int b;

 

  a = 5; b = 1; c = 2; *d = 3;

  printf(“%d %d %d %d\n”, a, b, c, *d);

}

 

main()

{

 

  a = 0; b = 0; c = 0; d = 0;

  izm(c, &d);

  printf(“%d %d %d %d\n”, a, b, c, d);

}

 

      Внаслідок виконання програми Prim3 отримаємо відповідь

 5 1 2 3

 5 0 0 3.

 

Задачі та вправи

 

      Вправа 6.2. Користуючись правилом виклику, обчисліть вирази

 

      a) Мн(Мн(2,4), 3);      b) Степ(2, Мн(2,3)).

 

      Вправа 6.3. Чи Можна визначити функцію f(х), що задовольняє твердженням

 

            { х=а } у <- f(х)+f(х) { у=y1 };

            { х=а } у <- 2*f(х)    { у=y2 }

 

так, щоб y1<>y2?

 

[ЗМІСТ]      [Далі]       [Назад]      [Початок розділу]