11. ОБ’ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ
11.1 Опис та використання класів та об’єктів
11.2 Статичне та динамічне зв’язування об’єктів та
методів. Конструктори
11.3 Динамічні об’єкти. Деструктори
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();
}