[ЗМІСТ]      [Далі]       [Назад]

 

9. МОДУЛІ

 

9. МОДУЛІ

 

      Використання підпрограм дозволяє підняти рівень абстракції алгоритмів. Дійсно, за допомогою підпрограм ми можемо створювати нові операції та інструкції. Але підпрограми не дають можливості створювати принципово нові типи даних, які не передбачені мовою програмування. До того ж, для побудови великих програм підпрограми є все ж дуже маленькими „цеглинами”. Ці проблеми вирішуються введенням та використанням модулів.

      Модуль об’єднує підпрограми разом з описами констант, змінних, типів, які призначені для розв’язку певного класу задач. Характерною рисою модуля є інкапсуляція, тобто приховування певної частини модуля від використання ззовні. Взагалі, модуль поділяється на дві частини: видиму (інтерфейсну) та частину реалізації. Всі описи, які вказані у інтерфейсній частині модуля, доступні ззовні. Частина ж реалізації модуля є внутрішньою та недоступною для безпосереднього використання.

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

 

 

Визначимо синтаксис модуля.

 

      Модуль M це

        імпорт M1, M2, ..., Mn;

        експорт

            конст a1=c1; ...; ak=ck;

            тип t1=def_t1; ...; tl=def_tl;

            змін x1: t1; ...; xm: tm;

            def_N1;

            ...

            def_Nd;

        реалізація

            R

        ініціалізація

            P

      км.

 

У цьому записі M1, ..., Mn – список використовуваних модулів; a1, ..., ak – імена констант; c1, ..., ck – значення констант; t1, ..., tl – імена типів; def_t1, ..., def_tl – визначення типів; x1, ..., xm – змінні; t1, ..., tm – їх типи; def_N1, ..., def_Nd – заголовки процедур та функцій виду:

 

      процедура Ni(арг y1: r1; ...; yf: rf;

                  мод z1: s1; ..., zg: sg;

                  рез v1: u1; ..., vh: uh)

або

      функція Fi(y1: r1; ...; yf: rf): r;

 

      Частина реалізації модуля R включає такі ж описи констант, типів та змінних, як і інтерфейсна частина модуля. Частина реалізації включає також реалізацію процедур та функцій, заголовки яких описано у інтерфейсній частині, та реалізацію внтурішніх процедур та функцій модуля.

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

      До опису алгоритма, який використовує модулі, безпосередньо після заголовку алгоритма додається речення:

 

      імпорт M1, M2, ..., Mn;

 

де M1, ..., Mn – список використовуваних модулів.

 

      Якщо модуль M включено до списку імпорту алгоритма або деякого модуля, то всі константи, типи, змінні, процедури та функції, описані в інтерфейсній частині модуля M, доступні за їх іменами, наприклад, x1. Однак, у декількох модулях можуть бути описані однакові імена. Тоді їх використання спричиняє ситуацію, яку називають колізією імен. Щоб розв’язати колізію імен, треба вказувати ім’я модуля та крапку в якості префікса потрібного імені, наприклад, M.x1.

 

      Розглянемо приклад модуля.

      Приклад 9.1. Реалізувати модуль роботи з раціональними числами у вигляді пари (чисельник, знаменник).

      Розв’язок. Всі необхідні типи, процедури та функції опишемо у модулі Rational. Цей модуль реалізує такі дії над раціональними числами:

1.    операції: сума раціональних чисел;

2.    відношення: рівність раціональних чисел;

3.    інструкції: взяти раціональне число та показати раціональне число.

 

      Модуль Rational це

 

      експорт

        тип Rat = запис

                     Nom: ціл;            {чисельник}

                     Den: нат             {знаменник}

                   кз;

        змін RatError: нат;               {код останньої помилки}

 

        процедура ReadRat(мод r: Rat);    {взяти раціональне число}

        процедура WriteRat(r: Rat);       {показати раціональне число}

        функція AddRat(a, b: Rat): Rat;   {сума раціональних чисел}

        функція EqRat(a, b: Rat): бул;    {рівність раціональних чисел}

 

      реалізація

 

      {найбільший спільний дільник}

        функція GCD(m, n: нат): нат це

          поч

            поки m<>n повт

              якщо m>n то m <- m-n

              інакше n <- n-m кр;

            GCD <- m

          кф;

 

      {скорочення раціонального числа}

        процедура Reduce(мод r: Rat) це

          змін k: нат;

          поч

            якщо r.Nom=0 то r.Den <- 1

            інакше

              k <- GCD(abs(r.Nom),r.Den);

              r.Nom <- r.Nom div k;

              r.Den <- r.Den div k

            кр

          кп;

 

      {взяти раціональне число}

        процедура ReadRat(мод r: Rat) це

          поч

            якщо RatError<>0 то відмова кр;

            взяти(r.Nom, r.Den);

            якщо r.Den=0 то RatError <- 1 кр

          кп;

 

      {показати раціональне число}

        процедура WriteRat(r: Rat) це

          поч

            показати(r.Nom);

            якщо r.Den<>1 то

              показати('/',r.Den)

            кр

          кп;

 

      {сума раціональних чисел}

        функція AddRat(a, b: Rat): Rat це

          змін c: Rat;

          поч

            якщо RatError<>0 то відмова кр;

            c.Nom <- a.Nom*b.Den+b.Nom*a.Den;

            c.Den <- a.Den*b.Den;

            Reduce(c);

            AddRat <- c

          кф;

 

      {рівність раціональних чисел}

        функція EqRat(a, b: Rat): бул це

          поч

            EqRat <- a.Nom*b.Den=b.Nom*a.Den

          кф;

 

      ініціалізація

        RatError <- 0

      км.

 

      Можна реалізувати й інші арифметичні операції над раціональними числами: віднімання, множення ділення, а також відношення “<” або “>”. Вони реалізуються аналогічно розглянутим функціям AddRat та EqRat.

      У частині реалізації модуля Rational визначено дві внутрішні підпрограми, які використовуються для реалізації дій над раціональними числами: обчислення найбільшого спільного дільника GCD та скорочення раціонального числа Reduce. Ці підпрограми перешкоджають швидкому виходу чисельника та знаменника за межі інтервалу представлення для цілих або натуральних чисел під час виконання арифметичних операцій.

      Змінна RatError фіксує код останньої помилки. Можлива помилка під час роботи з раціональними числами – це рівність знаменника 0. Він може дорівнювати нулю після введення раціонального числа або ділення раціонального числа на 0. У цих випадках встановлюється ненульовий код помилки. Помилка повинна бути оброблена зовнішнім алгоритмом, який використовує модуль. Якщо помилку не оброблено, то подальше виконання дій над раціональними числами неможливе. Тому у піжпрограмах ReadRat та AddRat за ненульового коду помилки виникає відмова.

 

      Для перевірки роботи модуля використаємо алгоритм:

 

      Алг RatTst це

        імпорт Rational;

        змін a,b,c: Rat;

      поч

        цикл

          ReadRat(a);

          якщо Rational.RatError=0 то вихід;

          показати('знаменник не повинен дорівнювати 0');

          Rational.RatError <- 0

        кц;

        цикл

          ReadRat(b);

          якщо Rational.RatError=0 то вихід;

          показати('знаменник не повинен дорівнювати 0');

          Rational.RatError <- 0

        кц;

        c <- AddRat(a,b);

        WriteRat(c);

        показати('Числа ');

        якщо ØEqRat(a,b) то показати('не ') кр;

        показати('рівні')

      ка.

 

У цьому прикладі префікс Rational використовується при зверненні до змінної RatError. При виклику підпрограм модуля Rational префікс не використовується. Насправді, колізії імен немає і використання префікса необов’язкове.

 

      У стандарті мови Паскаль модулі не допускаються, але вони реалізовані у Турбо-Паскалі. Синтаксис модуля виглядає так:

 

      unit M;

        interface

            uses M1, M2, ..., Mn;

            const a1=c1; ...; ak=ck;

            type t1=def_t1; ...; tl=def_tl;

            var x1: t1; ...; xm: tm;

            def_N1;

            ...

            def_Nd;

        implementation

            uses M1’, M2’, ..., Me’;

            Pas(R)

      begin

        Pas(P)

      end.

 

У цьому записі M1, ..., Mn – список використовуваних модулів; a1, ..., ak – імена констант; c1, ..., ck – значення констант; t1, ..., tl – імена типів; def_t1, ..., def_tl – визначення типів; x1, ..., xm – змінні; t1, ..., tm – їх типи; def_N1, ..., def_Nd – заголовки процедур та функцій виду:

 

      procedure Ni(y1: r1; ...; yf: rf;

                  var z1: s1; ..., var zg: sg);

або

      function Fi(y1: r1; ...; yf: rf): r;

 

M1’, M2’, ..., Me’ – список модулів, що використовуються виключно у частині реалізації модуля.

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

      procedure Ni;

або

      function Fi;

 

      Необов’язкова частина ініціалізації, яка починається словом begin, складається з ланцюга команд Pas(P). Якщо ініціалізація не потрібна, слово begin та ланцюг Pas(P) можна опустити.

      Основним модулем є програма (Program), у якій виколристовувані моудлі перерахуваються у реченні uses, яке повинно бути розташоване раніше всіх описів:

 

      uses M1, M2, ..., Mn;

 

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

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

 

      Systemпідпрограми введення/виведення роботи з рядками, математичні функції тощо;

      Crt – засоби роботи з екраном у текстовому режимі та клавіатурою;

      Dos – заоби доступу до функцій операційної системи;

      Graph – засоби роботи з екраном у графічному режимі.

 

      Всі стандартні модулі, що використовуються, окрім модуля System треба явно вказувати у реченні uses.

 

      Приклад 9P. Реалізувати модуль роботи з раціональними числами у вигляді пари (чисельник, знаменник) (задача 9.1).

 

unit Rational;

 

interface

 

  type Rat = record

               Nom: integer;

               Den: word

             end;

  var RatError: word;

 

  procedure ReadRat(var r: Rat);

  procedure WriteRat(r: Rat);

  procedure AddRat(a, b: Rat; var c: Rat);

  function EqRat(a, b: Rat): boolean;

 

implementation

 

  function GCD(m, n: word): word;

    begin

      while m<>n do

        if m>n then m:=m-n

        else n:=n-m;

      GCD:=m

    end;

 

  procedure Reduce(var r: Rat);

    var k: word;

    begin

      if r.Nom=0 then r.Den:=1

      else begin

        k:=GCD(abs(r.Nom),r.Den);

        r.Nom:=r.Nom div k;

        r.Den:=r.Den div k

      end

    end;

 

  procedure ReadRat(var r: Rat);

    begin

      if RatError<>0 then exit;

      readln(r.Nom, r.Den);

      if r.Den=0 then RatError:=1

    end;

 

  procedure WriteRat(r: Rat);

    begin

      write(r.Nom);

      if r.Den<>1 then begin

        write('/');

        writeln(r.Den)

        end

      else writeln

    end;

 

  procedure AddRat(a, b: Rat; var c: Rat);

    begin

      if RatError<>0 then exit;

      c.Nom:=a.Nom*b.Den+b.Nom*a.Den;

      c.Den:=a.Den*b.Den;

      Reduce(c)

    end;

 

  function EqRat(a, b: Rat): boolean;

    begin

      EqRat:=a.Nom*b.Den=b.Nom*a.Den

    end;

 

 

begin

  RatError:=0

end.

 

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

 

Program RatTst;

 

  uses Rational;

 

  var a,b,c: Rat;

 

begin

  while true do begin

    writeln('Введіть перше раціональне число');

    ReadRat(a);

    if RatError=0 then break;

    writeln('знаменник не повинен дорівнювати 0');

    RatError:=0

  end;

  while true do begin

    writeln('Введіть друге раціональне число');

    ReadRat(b);

    if RatError=0 then break;

    writeln('знаменник не повинен дорівнювати 0');

    RatError:=0

  end;

  AddRat(a,b,c);

  write('Сума='); WriteRat(c);

  write('Числа ');

  if not EqRat(a,b) then write('не ');

  writeln('рівні')

end.

 

      У мові Сі модулі на рівні синтаксису не виділяються. Реалізація модулів можлива з використанням роздільної компіляції та файлів заголовків. Програма у Сі може складатись з декількох файлів, кожний з яких вміщує підпрограми та описи. Головним файлом вважається той, що вміщує функцію main(). Роздільна компіляція – це обробка компілятором кожного файла програми окремо. Файли заголовків мають розширення імені .H та вміщують описи глобальних констант, типів, змінних, підпрограм. Описи констант, типів, змінних не відрізняються від розглянутих у попередніх розділах. Для підпрограм у фалах заголовків наводять тільки заголовок, після якого ставлять „;”:

 

      extern void n(t1 x1, ..., tn xn);

або

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

 

Ключове слово extern вказує на те, що підпрограма реалізована у зовнішньому файлі.

      Файли заголовків підключають до інших файлів за допомогою директиви #include. Наприклад,

 

#includemyfile.h

 

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

 

#include <stdio.h>

 

Директива #include приписує включити текст відповідного файла заголовка на місце #include та потім виконати компіляцію.

      При реалізації модулів для кожного модуля у Сі створюють два файли з однаковим іменем та з розширеннями .H та .C. Файл заголовку з розширенням .H грає роль інтерфейсної частини модуля, а файл з розширенням .C – частини реалізації модуля. Для використання модуля треба підключити відповідний файл заголовка за допомогою #include.

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

      Примітка: У мові Сі немає засобів розв’язку колізії імен, тому всі глобальні імена повинні бути унікальними.

 

      Приклад 9C. Реалізувати модуль роботи з раціональними числами у вигляді пари (чисельник, знаменник) (задача 9.1).

      Розв’язок. Програму представимо у складі 3 файлів: модуль раціональних чисел – rat.h та rat.c – та тестуюча програма rattst.c.

 

/* Раціональні числа rat.h*/

 

typedef struct {

                int nom;

                unsigned den;

               } rat;

 

extern int rat_error;

 

extern void add_rat(rat a, rat b, rat* pc);

 

extern int eq_rat(rat a, rat b);

 

extern void read_rat(rat* pr);

 

extern void write_rat(rat r);

 

 

/* Раціональні числа rat.c*/

 

#include <stdio.h>

#include <stdlib.h>

#include "rat.h"

 

int rat_error=0;

 

static unsigned gcd(unsigned n, unsigned m)

  {

   while (n != m)

     if (n > m) n -= m;

     else m -= n;

   return m;

  }

 

static void reduce(rat* pr)

  {

   unsigned k;

 

   if ((*pr).nom == 0) (*pr).den = 1;

   else

     {

      k = gcd(abs((*pr).nom),(*pr).den);

      (*pr).nom /= k; (*pr).den /= k;

     }

  }

 

void add_rat(rat a, rat b, rat* pc)

  {

   if (rat_error != 0) exit(1);

   (*pc).nom = a.nom*b.den+b.nom*a.den;

   (*pc).den = a.den*b.den;

   reduce(pc);

  }

 

int eq_rat(rat a, rat b)

  {

   return a.nom*b.den == b.nom*a.den;

  }

 

void read_rat(rat* pr)

  {

   if (rat_error != 0) exit(1);

   scanf("%d/%u",&(*pr).nom,&(*pr).den);

   if ((*pr).den == 0) rat_error = 1;

  }

 

void write_rat(rat r)

  {

   if (r.den == 1) printf("%d\n",r.nom);

   else printf("%d/%u\n",r.nom, r.den);

  }

 

Ключове слово static перед підпрограмами gcd та reduce вказує на те, що ці підпрограми є внутрішніми для файла rat.c та недоступні ззовні.

 

/* rattst.c */

 

#include <stdio.h>

#include "rat.h"

 

 

main()

  {

   rat a, b, c;

   while(1)

     {

      printf("Введіть 1 раціональне число\n");

      read_rat(&a);

      if (rat_error == 0) break;

      printf("знаменник не повинен дорівнювати 0\n");

      rat_error = 0;

     }

   while(1)

     {

      printf("Введіть 2 раціональне число\n");

      read_rat(&b);

      if (rat_error == 0) break;

      printf("знаменник не повинен дорівнювати 0\n");

      rat_error = 0;

     }

   printf("Сума=");

   add_rat(a,b,&c); write_rat(c);

   if (eq_rat(a,b)) printf("Числа рівні\n");

   else printf("Числа не рівні\n");

  }

 

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

 

      Вправа 9.1. Доповнити модуль роботи з раціональними числами підпрограмами для реалізації операцій віднімання, множення та ділення а також відношення “<”.

 

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

 

     ¥    i   z2i+1

     S (-1) -------

    i=0     (2i+1)!

 

де zкомплексне число.

 

[ЗМІСТ]      [Далі]       [Назад]