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

 

11. ОБ’ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ

11.1 Опис та використання класів та об’єктів

11.2 Статичне та динамічне зв’язування об’єктів та методів. Конструктори

11.3 Динамічні об’єкти. Деструктори

11.4 Проект „Опукла оболонка”

 

11. ОБ’ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ

 

      Розглянуте раніше модульне програмування забезпечує високий рівень абстрації алгоритмів та даних. Але розвиток програмної індустрії виявив також обмеження, яким підпорядковані модулі. Зокрема, це необхідність часто багаторазово повторювати дуже схожі підпрограми або модулі для реалізації споріднених, але все ж різних, понять. Еволюція призвела до появи у середині 80-х років XX століття об’єктно-орієнтованого програмування. Об’єктно-орієнтоване програмування спочатку з’явилось як альтернатива звичайному процедурному програмуванню. Однак подальший розвиток показав більшу ефективність поєднання можливостей процедурного та об’єктно-орієнтованого програмування. Таким чином, розповсюджені мови програмування, наприклад Паскаль та Сі, набули нових об’єктно-орієнтованих можливостей.

      Взагалі, об’єктно-орієнтований підхід не обмежується тільки програмуванням. Він широко застосовується також при аналізі та проектуванні великих програмних систем. Але у даному курсі ми розглянемо саме об’єктно-орієнтоване програмування.

      Об’єктом називають деяку сутність, яка має визначені властивості, поведінку та стан. Множину об’єктів з однаковими властивостями та поведінкою називають класом. Об’єкти часом називають також екземплярами класу, хоча ці два поняття не є тотожніми. Кожен клас має атрибути (властивості) та операції (методи). Атрибути визначають та зберігають значення властивостей об’єктів класу, а операції визначають поведінку об’єктів класу.

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

      Ми будемо розглядати реалізацію об’єктно-орієнтованого програмування у мовах Паскаль та Сі. У Турбо-Паскалі класи та об’єкти з’явились, починаючи з версії 5.5 і далі об’єктно орієнтовані можливості майже не змінювались (треба зазначити, що мова Object Pascal для системи програмування Delphi має іншу реалізацію об’єктно-орієнтованого програмування). Для включення об’єктно-орієнтованих можливостей у мову Сі було розроблено нову мову програмування, яка носить назву C++.

 

11.1 Опис та використання класів та об’єктів

 

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

 

      type t = object (t)

                  a1: t1;

                  ...

                  am: tm;

                  def_N1;

                  ...

                  def_Nk;

               end;

 

де t – ім’я класу-предка; a1, ... amвластивості класу t з типами t1, ..., tm; def_N1, ..., def_Nk– методи - заголовки процедур та функцій виду:

 

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

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

або

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

 

Якщо класу-предка немає, то (t) не вказують. Наприклад, опис

 

      type Person = Object

                      Name,Surname: String;

                      BYear: Integer;

                      Procedure Input;

                      Procedure Output;

                    end;

 

описує клас Person з властивостями Name, Surname, Byear та методами Input та Output. А опис

 

      type Student = Object(Person)

                      Course: 1..MaxCourse;

                      Session: ARRAY [1..ExNum] OF Mark;

                      Procedure Input;

                      Procedure Output;

                      Function Average: Real;

                    end;

 

визначає клас Student, який є нащадком Person, зі своїми властивостями Course, Session та методами Input та Output.

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

      У нашому прикладі клас Student успадковує від класу Person всі властивості класу Person та додає свої властивості. Отже повний перелік властивостей класу Student: Name, Surname, Byear, Course, Session. Той же клас Student перевизначає методи Input та Output класу Person, замінивши їх власними методами.

      Класи можуть бути описані як у модулі, так і у основній програмі. У будь-якому випадку у тому ж модулі повинна бути наведена реалізація усіх методів (приблизно так, як ми наводили реалізацію інтерфейсних процедур та функцій модуля). У реалізації методу його імені передує префікс – ім’я класу, наприклад

 

      Procedure Person.Input;

 

Інших відмінностей від синтаксису процедур та функцій немає. У реалізації методу властивості класу доступні просто за іменем (Name, Course тощо). Якщо у реалізації методу класу викликають метод класу-предка, то імені метода передує ім’я класу, наприклад у реалізації методу Input класу Student виклик методу Input класу Person записують так: Person.Input.

 

      Об’єкти класів описують як змінні:

 

      var A, B: Person;

          C: Student;

 

      При використанні об’єктів для доступу до їх властивостей та виклику методів застосовують ім’я об’єкта та „.”, наприклад, A.Name, C.Course, B.Input. Під час виклику методу сам обєкт передається методу як додатковий неявний параметр. Тому всі зміни властивостей або виклики інших методів у тілі реалізації методу стосуються саме того об’єкта, для якого метод був викликаний.

      Для об’єктів також допустиме присвоєння. Причому, присвоєння можна виконати не тільки для об’єктів одного класу (наприклад, A:=B) але й у ієрархії класів, де об’єкту класу-предка можна присвоїти значення об’єкта класу-нащадка. Наприклад, оскільки клас Student є нащадком Person, ми можемо виконати присвоєння A:=C. При цьому буде виконано копіювання з C у A значень тільки тих властивостей та методів, які визначені для класу Person. Обернене присвоєння не допускається. Така трактовка присвоєння покликана не допустити появи в результаті присвоєння об’єктів невизначених властивостей та методів.

      Розглянемо приклад опису та використання об’єктів. Програма ObjPers описує класи Person – особа та Student – студент. Ці класи використовуються для обчислення стипендії за результатами останньої сесії. Вважаємо, що мінімальна стипендія – 50, а підвищена - 60. Вважаємо також, що стипендія нараховується з середнім балом не нижче 4 тим студентам, які не мають „двійок”; а підвищена стипендія – студентам з середнім балом 5.

      У реалізації методів Input та Output класу Student ми викликаємо відповідні методи класу Person, які вводять або виводять значення властивостей Name, Surname, BYear.

      Coeff та Ball – це масиви-константи типу ca для обчислення стипендії. Опис

 

      Coeff: ca = (0.00,1.00,1.20);

 

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

      Функція Stipend обчислює стипендію за середнім балом. Основна програма вводить масив об’єктів класу Student, обчислює стипендію кожного студента та показує результати.

 

Program ObjPers;

 

TYPE

  Person = Object

             Name,Surname: String;  {ім’я, прізвище}

             BYear: Integer;        {рік народження}

             Procedure Input;       {взяти дані про особу}

             Procedure Output;      {показати дані про особу}

           end;

 

  Procedure Person.Input;

    begin

      Write('Прізвище: '); Readln(Surname);

      Write('Ім”я: '); Readln(Name);

      Write('Рік народження: '); Readln(BYear);

    end;

 

  Procedure Person.Output;

    begin

      Write(Surname:15);

      Write(Name:10);

      Write(BYear:5);

    end;

 

 

Const

  MaxCourse = 5;                    {максимальний курс}

  ExNum = 4;                        {кількість іспитів у сесію}

 

Type

  Mark = 2..5;                      {оцінка}

  Student = Object(Person)

              Course: 1..MaxCourse;             {курс}

              Session: ARRAY [1..ExNum] OF Mark;{оцінки у сесію}

              Procedure Input;                  {взяти дані про студента}

              Procedure Output;                 {показати дані про студента}

              Function Average: Real;           {підрахувати середній бал}

            end;

 

  Procedure Student.Input;

    var i: Integer;

    begin

      Person.Input;

      Write('Курс: '); Readln(Course);

      Write('Оцінки у сесію: ');

      for i:=1 to ExNum do Read(Session[i]);

      Readln;

    end;

 

  Procedure Student.Output;

    var i: Integer;

    begin

      Person.Output;

      Write(Course:2);

      for i:=1 to ExNum do Write(Session[i]:3);

    end;

 

  Function Student.Average: Real;

    var i: Integer; s: Real; b: Boolean;

    begin

      s:=0; b:=true;

      for i:=1 to ExNum do begin

        s:=s+Session[i];

        b:=b and (Session[i] <> 2)

      end;

      if b then Average:=s/ExNum else Average:=0

    end;

 

  const MaxStud = 4;          {кількість студентів}

        MaxCoeff = 3;         {кількість градацій для нарахування стипендії}

 

  type ca = array [1..MaxCoeff] of Real;

 

  const

    MinFee = 50;                    {мінімальна стипендія}

    Coeff: ca = (0.00,1.00,1.20);   {коефіцієнти для нарахування стипендії}

    Ball : ca = (0.00,4.00,5.00);   {відповідний середній бал}

 

  Function Stipend(av: Real): Real;

    var i: Integer; s: Real;

    begin

      i:=MaxCoeff+1;

      repeat

        i:=i-1

      until (av >= Ball[i]);

      Stipend:=MinFee*Coeff[i]

    end;

 

  var

    Stud: ARRAY [1..MaxStud] OF Student;  {масив студентів}

    i: Integer; av,stip: Real;

 

  begin

    writeln('Введіть список студентів');

    for i:=1 to MaxStud do begin

      Stud[i].Input;

    end;

    writeln('       Прізвище    Ім”я   Р.н.Курс  Оцінки    Бал  Стипендія');

    for i:=1 to MaxStud do begin

      av:=Stud[i].Average; stip:=Stipend(av);

      Stud[i].Output; write(av:6:2); write(stip:6:0);

      Writeln;

    end;

  end.

 

      Перейдемо тепер до C++. У мові C++ опис класу об’єктів виглядає так:

 

class t: public t

            {

             t1 a1;

             ...

             tm am;

            def_N1;

            ...

            def_Nk;

            };

 

де t – ім’я класу-предка; a1, ... amвластивості класу t з типами t1, ..., tm; def_N1, ..., def_Nk– методи - заголовки функцій виду:

 

      r Fi(r1 y1, ..., rf yf);

 

Якщо класу-предка немає, то „: public t” не вказують. Наприклад, опис

 

      class person {

                  public:

                    char name[30];

                    char surname[30];

                    int byear;

                    void input();

                    void output();

                   };

 

описує клас person з властивостями name, surname, byear та методами input та output. А опис

 

      class student: public person {

                 public:

                   unsigned course;

                   unsigned session[EX_NUM];

                   void input();

                   void output();

                   double average();

      };

 

визначає клас student, який є нащадком person, зі своїми властивостями course, session та методами input та output.

      У описах класів person та student ключове слово “public:” поділяє опис властивостей та методів на дві частини: невидиму та видиму для зовнішнього світу. Невидима частина розташована перед словом “public:”, а видима – після.

      Клас student успадковує від класу person всі властивості класу person та додає свої властивості course, session. Клас student перевизначає методи input та output класу person.

      Класи можуть бути описані як у модулі, так і у основній програмі. У будь-якому випадку у тому ж модулі повинна бути наведена реалізація усіх методів. У реалізації методу його імені передує префікс – ім’я класу та два символа „:”, наприклад

 

      void person::output()

 

Інших відмінностей від синтаксису функцій Сі немає. У реалізації методу атрибути класу доступні просто за іменем (name, course тощо). Якщо у реалізації методу класу викликають метод класу-предка, то імені методу передує ім’я класу, наприклад у реалізації методу input класу student виклик методу input класу person записують так: person::input();.

 

      Об’єкти класів описують як змінні:

 

      person a, b;

      student c;

 

      При використанні об’єктів для доступу до їх атрибутів та виклику операцій застосовують ім’я об’єкта та „.”, наприклад, a.name, c.course, b.input().

      Присвоєння для об’єктів підпорядковане тим же правилам, що і у Паскалі.

      Розглянемо опис та використання об’єктів у C++ на прикладі програми розрахунку стипендії. Реалізація програми у C++ аналогічна наведеній вище реалізації у Паскалі.

 

/* ObjPers */

 

#include <stdio.h>

 

class person {

            public:

              char name[30];        /* ім’я */

              char surname[30];     /* прізвище */

              int byear;            /* рік народження */

              void input();         /* взяти дані про особу */

              void output();        /* показати дані про особу */

             };

 

 

void person::input()

{

  printf("Прізвище: "); scanf("%s", surname);

  printf("Ім’я: ");   scanf("%s", name);

  printf("Рік народження: "); scanf("%d", &byear);

}

 

void person::output()

{

  printf("%s %s %d", surname, name, byear);

}

 

#define MAX_COURSE 5    /* максимальний курс */

#define EX_NUM 4        /* кількість іспитів у сесію */

#define TRUE 1

 

 

class student: public person {

           public:

             unsigned course;             /* курс */

             unsigned session[EX_NUM];    /* оцінки у сесію */

             void input();                /* взяти дані про студента */

             void output();               /* показати дані про студента */

             double average();            /* підрахувати середній бал */

};

 

void student::input()

{

 int i;

   

 person::input();

 printf("Курс: "); scanf("%u", &course);

 printf("Оцінки: ");

 for (i=0; i<EX_NUM; i++)

    scanf("%u", &session[i]);

}

 

void student::output()

{

 int i;

 

 person::output();

 printf(" %u", course);

 for (i=0; i<EX_NUM; i++)

   printf(" %u", session[i]);

}

 

double student::average()

{

 int i, b;

 double s;

 

 s=0; b=TRUE;

 for (i=0; i<EX_NUM; i++)

 {

  s+=session[i];

  b=b && (session[i]!=2);

 }

 if (b) return s/EX_NUM;

 else return 0;

}

 

#define MAX_STUD 4            /* кількість студентів */

#define MAX_COEFF 3           /* кількість градацій для нарахування стипендії */

 

#define MIN_FEE 50            /* мінімальна стипендія */

   

double coeff[] = {0.00,1.00,1.20};  /* коефіцієнти для нарахування стипендії */

double ball[] = {0.00,4.00,5.00};   /* відповідний середній бал */

 

double stipend(double av)

{

 int i;

 

 i=MAX_COEFF;

 do

   i--;

 while (av < ball[i]);

 return MIN_FEE*coeff[i];

}

 

main()

{

  student stud[MAX_STUD];     /* масив студентів */

  int  i;

  double av,stip;

 

  printf("Введіть студентів\n");

  for(i=0; i<MAX_STUD; i++)

    stud[i].input();

  printf("Результат\n");

  for(i=0; i<MAX_STUD; i++)

  {

   av=stud[i].average(); stip=stipend(av);

   stud[i].output(); printf(" %lf %lf\n", av, stip);

  }

}

 

11.2 Статичне та динамічне зв’язування об’єктів та методів. Конструктори

 

      Наявність наслідування породжує додаткові проблеми, пов’язані з викликом методів об’єктів.

      Розглянемо приклад. Нехай два класи A та B з однієї ієрархії мають одноіменний метод S (клас B перевизначає метод S класу A). Цей метод викликається з методу Q класу A. Метод Q, в свою чергу, наслідується класом B.

 

Type

      A = Object

            ...

            Procedure S(...);

            Procedure Q(...);

            ...

      end;

      B = Object(A)

            ...

            Procedure S(...);

            ...

      end;

...

{Реалізація}

 

Procedure A.Q(...);

      ...

      S(...);

      ...

end;

...

Var X: A; Y: B;

...

{Виклик}

      X.Q(...);

      ...

      Y.Q(...);

 

Запитання: метод S якого класу буде викликано після викликів X.Q(...) та Y.Q(...)?. Відповідь на це запитання залежить від того, коли ми визначаємось з переліком методів для об’єкта деякого класу, тобто зв’язуємо об’єкт з його методами. Існує два типи зв’язування об’єктів та методів: статичне та динамічне. При статичному зв’язування метод для об’єкта визначається до початку виконання програми, тобто під час компіляції. При динамічному зв’язуванні метод призначається під час виконання програми. Отже, якщо зв’язування статичне, то у обох викликах X.Q(...) та Y.Q(...) буде викликано метод S з класу A, тому що метод Q належить класу A. Якщо ж зв’язування динамічне, то у виклику X.Q(...) буде викликано метод S з класу A, а у виклику Y.Q(...) буде викликано метод S з класу B.

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

 

      Наявність віртуальних методів у класі (а отже, динамічного зв’язування) обумовлює необхідність ініціалізації всіх об’єктів цього класу. Така ініціалізація здійснюється спеціальними методами, які називають конструкторами. Конструктор повинен викликатись раніше, ніж будь які інші дії над об’єктом класу. Клас може мати декілька конструкторів, наприклад, клас File може мати конструктор Create для створення нового файлу та конструктор Open для відкриття існуючого.

 

      У мові Паскаль віртуальні методи позначаються словом virtual, яке йде після опису заголовку метода. Наприклад:

 

      procedure S (var x, y: real); virtual;

 

      Конструктори мають синтаксис, аналогічний синтаксису процедур, за виключенням того, що замість слова procedure використовують слово constructor. Наприклад:

 

      constructor Create(k: integer);

 

У Паскалі конструктори не можуть бути віртуальними методами.

 

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

 

      У програмі ObjVirtual ми використовуємо підпрограми зі стандартного модуля Graph:

·         DETECTфункція, визначає поточний графічний драйвер, тобто тип графічного адаптера комп’ютера;

·         InitGraph(gd,gm,'c:\tp7\bgi') – процедура, переводить екран у графічний режим gm драйвера gd; 'c:\tp7\bgi' вказує шлях до засобів підтримки різних графічних драйверів Турбо-Паскаля (звичайно вони знаходяться у підкаталозі BGI);

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

·         GetBkColor – функція, повертає поточний колір фону;

·         SetColor(c) – процедура, встановлює колір переднього плану c;

·         PutPixel(x,y,c) – процедура, зображує точку з координатами x, y кольором c;

·         Circle(x,y,r) – процедура, зображує коло з координатами центра x, y, радіусом r кольором переднього плану;

·         CloseGraph – процедура, завершує роботу у графічному режимі та переводить екран у текстовий режим.

 

      Клас GrPoint має властивості X,Y – координати точки та булівську властивість Visible, яка визначає, чи є точка видимою на екрані. Конструктор Create створює точку з координатами A,B. Методи GetX та GetY повертають координати точки. Метод OnScreen повертає значення істини або хибності в залежності від того, чи є точка видимою. Методи SwitchOn та SwitchOff визначені як віртуальні у ієрархії класів та виконують зображення або стирання точки з екрану. Нарешті, метод Move переміщує точку на відстань Dx, Dy по координатах x та y відповідно. У методі Move ми спочатку запам’ятовуємо, чи є точка видимою. Якщо так, то треба стерти точку (SwitchOff). Далі у будь-якому випадку треба змінити координати точки (X:=X+Dx; Y:=Y+Dy;). Нарешті, якщо точка була видимою, її треба знову зобразити (SwitchOn).

 

      Клас Circle наслідує від класу GrPoint всі властивості та методи GetX, GetY, OnScreen, Move. Властивості X,Y для класу Circle означають координати центра кола, а власна властивість Radius – радіус кола. Visible, як і для точки, визначає, чи є коло видимим на екрані. Конструктор Create створює коло з центром у точці (A,B) та радіусом R. Цей конструктор викликає конструктор Create батьківського класу GrPoint, що є розповсюдженою практикою для конструкторів у ієрархії класів. Метод GetR повертає радіус кола. Віртуальні методи SwitchOn, SwitchOff зображують або стирають коло з екрану. У методі SwitchOff ми спочатку запам’ятовуємо колір переднього плану (c:=GetColor), потім встановлюємо кольором переднього плану поточний колір фону (SetColor(GetBkColor)), зображуємо коло кольором фону (Graph.Circle(X,Y,Radius)) та відновлюємо колір переднього плану (SetColor(c)).

 

      У об’єктно-орієнтованому програмуванні вважається поганим стилем давати безпосередній доступ до властивостей об’єктів. Дійсно, у нашому прикладі ми могли б заборонити властивостям X, Y точки набувати значень поза межами екрану. Тому, як правило, доступ до властивостей відбувається опосередковано через методи, які змінюють або повертають значення цих властивостей. У програмі ObjVirtual це методи GetX, GetY, OnScreen, GetR. Для того, щоб приховати властивості (та можливо методи) використовують ключове слово private. Все, що йде після private до ключового слова public або до кінця опису класу, є прихованим та доступне тільки у поточному модулі. У нашому прикладі це властивості X, Y, Visible, R класів GrPoint та Circle. Якщо ключове слово private не вказане, то за угодою всі властивості та методи об’єктів класу є видимими ззовні.

 

      Основна програма нашого прикладу створює точку з координатами (100,100), показує її на екрані, а потім переміщує на 10 точок по горизонталі та на 20 по вертикалі. Потім програма створює коло з центром у точці (150,150) радіусом 30, показує його на екрані та переміщує на 10 точок по горизонталі та на 20 по вертикалі. Наявність Readln дозволяє слідкувати за цим процесом по кроках, оскільки для продовження треба натиснути клавішу <Enter>.

      Оскільки методи SwitchOn та SwitchOff є віртуальними, виклик p.Move(10,20) спричиняє виклик з метода Move методів SwitchOn та SwitchOff з класу GrPoint, а виклик c.Move(10,20) спричиняє виклик з метода Move методів SwitchOn та SwitchOff з класу Circle.

 

 

Program ObjVirtual;

 

Uses Graph;

 

Type

  GrPoint = Object                                  { точка екрану }

             private

              X,Y: Integer;                         { координати точки }

              Visible: Boolean;                     { ознака видимості }

             public

              Constructor Create(A,B: Integer);     { створити точку }

              Function GetX: Integer;               { координата x }

              Function GetY: Integer;               { координата y }

              Function OnScreen: Boolean;           { чи є точка видимою? }

              Procedure SwitchOn; Virtual;          { зобразити }

              Procedure SwitchOff; Virtual;         { стерти }

              Procedure Move(Dx,Dy: Integer);       { зсунути }

            end;

  Circle  = Object(GrPoint)                         { коло }

             private

              Radius: Integer;                      { радіус }

             public

              Constructor Create(A,B,R: Integer);   { створити коло }

              Function GetR: Integer;               { радіус }

              Procedure SwitchOn; Virtual;          { зобразити }

              Procedure SwitchOff; Virtual;         { стерти }

            end;

 

  Constructor GrPoint.Create;

    begin

      X:=A; Y:=B; Visible:=False

    end;

 

  Function GrPoint.GetX;

    begin

      GetX:=X

    end;

 

  Function GrPoint.GetY;

    begin

      GetY:=Y

    end;

 

  Function GrPoint.OnScreen;

    begin

      OnScreen:=Visible

    end;

 

  Procedure GrPoint.SwitchOn;

    begin

      Visible:=True; PutPixel(X,Y,GetColor)

    end;

 

  Procedure GrPoint.SwitchOff;

    begin

      Visible:=False; PutPixel(X,Y,GetBkColor)

    end;

 

  Procedure GrPoint.Move;

    var vis: Boolean;

    begin

      vis:=Visible;

      if vis then SwitchOff;

      X:=X+Dx; Y:=Y+Dy;

      if vis then SwitchOn

    end;

 

  Constructor Circle.Create;

    begin

      GrPoint.Create(A,B); Radius:=R

    end;

 

  Function Circle.GetR;

    begin

      GetR:=Radius

    end;

 

  Procedure Circle.SwitchOn;

    begin

      Visible:=True; Graph.Circle(X,Y,Radius)

    end;

 

  Procedure Circle.SwitchOff;

    var c: Integer;

    begin

      Visible:=False;

      c:=GetColor; SetColor(GetBkColor);

      Graph.Circle(X,Y,Radius); SetColor(c)

    end;

 

var p: GrPoint; c: Circle; gm,gd: Integer;

begin

  gd:=DETECT; InitGraph(gd,gm,'c:\tp7\bgi');

  p.Create(100,100);

  p.SwitchOn; Readln;

  p.Move(10,20); Readln;

  c.Create(150,150,30);

  c.SwitchOn; Readln;

  c.Move(10,20); Readln;

  CloseGraph;

end.

 

      У мові C++ віртуальні методи позначаються ключовим словом virtual, яке стоїть перед описом методу. Наприклад:

 

      virtual void s (double x, double y);

 

      Конструктори у C++ повинні мати ім’я, яке співпадає з ім’ям класу. Наприклад, для класу GrPoint, конструктор

 

      GrPoint(int a, int b);

 

Клас може мати декілька одноіменних конструкторів, які відрізняються кількістю та типами параметрів. Конструктори у C++ не можуть бути віртуальними. Для них також не можна вказати тип значення, що повертається. Не можна і викликати конструктор як звичайну функцію. У програмі ObjVirtual наведено приклад виклику конструктора класу GrPoint з конструктора класу Circle:

 

      Circle::Circle(int a, int b, int r) :GrPoint(a, b)

      {

            ...

      }

 

Конструктор класу GrPoint викликається відразу після заголовку конструктора класу Circle. Перед викликом ставиться символ “:”.

      Опис об’єктів класів, для яких визначено конструктори, повинен відразу включати виклик одного з конструкторів, а саме,

 

      GrPoint p = GrPoint(100,100);

 

або скорочено

 

      GrPoint p(100,100);

 

      Приклад програми ObjVirtual у C++ майже дослівно повторює аналогічний приклад у мові Паскакль. Тут ми також використовуємо модуль роботи з графікою (<graphics.h>), одноіменні підпрограми якого аналогічні розглянутим вище для Паскаля.

      Треба окремо розглянути приховування властивостей у C++, яке відрізняється від Паскаля. Ключове слово private (яке розуміють для опису класу за угодою) у C++ дозволяє використання властивостей або методів тільки у реалізації даного класу або у так-званих дружніх” (friend) класах. Для того, щоб дозволити використання властивостей або методів даного класу у його підкласах, треба застосовувати ключове слово protected.

      Зверніть також увагу на реалізацію методів getx, gety, on_screen, getr, яка наведена безпосередньо у описі класів. Такі функції називають inline-функціями, тому що їх реалізація, як правило, не вимагає більше, ніж одного рядка програми. Inline-функції не тільки скорочують текст програми. Під час компіляції вони просто підставляються на місце виклику замість виклику функції, що економить також час виконання.

      Символи „//” у C++ позначають коментар, який продовжується до кінця поточного рядка.

 

//ObjVirtual;

#include <stdio.h>

#include <graphics.h>

 

#define TRUE 1

#define FALSE 0

 

class GrPoint {                     // точка екрану

      protected:

            int x,y;                // координати точки

            int visible;            // ознака видимості

      public:

            GrPoint(int a, int b);              // створити точку

            int getx() { return x; };           // координата x

            int gety() { return y; };           // координата y

            int on_screen() { return visible; };// чи є точка видимою?

            virtual void switch_on();           // зобразити

            virtual void switch_off();          // стерти

            void move(int dx, int dy);          // зсунути

            };

 

class Circle: public GrPoint{       // коло

      protected:

            int radius;             // радіус

      public:

            Circle (int a, int b, int r);       // створити коло

            int getr() { return radius; };      // радіус

            virtual void switch_on();           // зобразити

            virtual void switch_off();          // стерти

            };

 

GrPoint::GrPoint(int a, int b)

{

      x=a; y=b; visible=FALSE;

};

 

void GrPoint::switch_on()

{

      visible=TRUE; putpixel(x,y,getcolor());

}

 

void GrPoint::switch_off()

{

      visible=FALSE; putpixel(x,y,getbkcolor());

}

 

void GrPoint::move(int dx, int dy)

{

      int vis;

 

      vis=visible;

      if (vis) switch_off();

      x+=dx; y+=dy;

      if (vis) switch_on();

}

 

Circle::Circle(int a, int b, int r) :GrPoint(a, b)

{

      radius=r;

}

 

void Circle::switch_on()

{

      visible=TRUE; circle(x, y, radius);

}

 

void Circle::switch_off()

{

      int c;

 

      visible=FALSE;

      c=getcolor(); setcolor(getbkcolor()) ;

      circle(x, y, radius); setcolor(c);

}

 

main()

{

      GrPoint p(100,100);

      Circle c(150,150,30);

      int gm, gd = DETECT;

 

      initgraph(&gd, &gm,"C:\\BC31\\BGI");

      p.switch_on();  getchar();

      p.move(10,20);  getchar();

      c.switch_on();  getchar();

      c.move(10,20);  getchar();

      closegraph();

}

 

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