Студент, викладач, персона, зав. кафедрою
ВСТУП
Даний лабораторний практикум складений відповідно до програми курсу “Об’єктно-орієнтоване програмування”, і призначений для студентів спеціальності 7.091401 — системи управління та автоматики, як денного, так і заочного відділень.
Мета практикуму — закріпити знання, отримані при вивченні теоретичної частини курсів і одержати практичні навички розробки об’єктно-орієнтованих програм. Практикум охоплює всі розділи об’єктно-орієнтованого програмування мовою С++ і включає виконання восьми лабораторних робіт. Перші чотири роботи пов'язані з базовими поняттями С++, такими як об'єкти і класи, спадкування, поліморфізм і віртуальні функції, обробка подій. Останні чотири роботи присвячені розвинутому програмуванню на С++ і охоплюють розділи професійного програмування, такі як перевантаження операцій, шаблони, потокові класи і стандартна бібліотека шаблонів.
У посібнику для кожної лабораторної роботи зазначені мета й основний зміст роботи. Наведено теоретичні відомості, необхідні для проведення роботи, порядок виконання роботи і методичних вказівок. Наприкінці наведені варіанти завдань і зміст звіту по роботі.
Лабораторні роботи виконуються:
№1 - №4 у середовищі Turbo C++ 3.0
№5 - №8 у середовищі Borland C++ 5.02.
Лабораторна робота №1.
Класи й об'єкти в С++
Мета. Одержати практичні навички реалізації класів на С++.
Основний зміст роботи.
Написати програму, в якій створюються і руйнуються об'єкти, визначеного користувачем класу. Виконати дослідження викликів конструкторів і деструкторів.
Короткі теоретичні зведення
Клас.
Клас — фундаментальне поняття С++ і лежить в основі багатьох властивостей С++. Клас надає механізм для створення об'єктів. У класі відбиті найважливіші концепції об’єктно-орієнтованого програмування: інкапсуляція, спадкування, поліморфізм.
З погляду синтаксису клас у С++ — це структурований тип, утворений на основі вже існуючих типів.
У цьому сенсі клас є розширенням поняття структури. У найпростішому випадку клас можна визначити за допомогою конструкції:
тип_класу ім'я_класу {список_членів_класу};
де
тип_класу – одне з службових слів class, struct, union;
ім'я_класу – ідентифікатор;
список_членів_класу – визначення й описи типізованих даних і приналежних класу функцій.
Функції – це методи класу, що визначають операції над об'єктом.
Дані – це поля об'єкта, що утворять його структуру. Значення полів визначає стан об'єкта.
Приклади
struct date //дата
{int month,day,year; // поля: місяць, день, рік
void set(int,int,int); // метод - установити дату
void get(int*,int*,int*); // метод- одержати дату
void next(); // метод- установить наступну дату
void print(); // метод - вивести дату
};
struct class complex // комплексне число
{double re,im;
double real(){return(re);}
double imag(){return(im);}
void set(double x,double y){re = x; im = y;}
void print(){cout<<”re = ”<<re; cout<<“im = ”<<im;}
};
Для опису об'єкта класу (екземпляра класу) використовується конструкція:
ім'я_класу ім'я_об'єкта;
date today,my_birthday;
date *point = &today; //покажчик на об'єкт типу date
date clim[30]; // масив об'єктів
date &name = my_birthday; //посилання на об'єкт
В обумовлені об'єкти входять дані, які відповідають членам-даним класу. Функції — члени класу дозволяють обробляти дані конкретних об'єктів класу. Звертатися до даних об'єкта і викликати функції для об'єкта можна двома способами. По-перше, за допомогою “кваліфікованих” імен:
ім'я_об'єкта. ім'я_даного
ім'я_об'єкта. ім'я_функції
Наприклад:
complex x1,x2;
x1.re = 1.24;
x1.im = 2.3;
x2.set(5.1,1.7);
x1.print();
Другий спосіб доступу використовує покажчик на об'єкт
покажчик_на_об'єкт–>ім'я_компонента
complex *point = &x1; // чи point = new complex;
point –>re = 1.24;
point –>im = 2.3;
point –>print();
Доступність компонентів класу
У розглянутих раніше прикладах класів компоненти класів є загальнодоступними. У будь-якому місці програми, де “видно” визначення класу, можна одержати доступ до компонентів об'єкта класу. Тим самим не виконується основний принцип абстракції даних – інкапсуляція (приховання) даних всередині об'єкта. Для зміни видимості компонент у визначенні класу можна використовувати специфікатори доступу : public, private, protected.
Загальнодоступні (public) компоненти доступні в будь-якій частині програми. Вони можуть використовуватися будь-якою функцією як всередині даного класу, так і поза ним. Доступ ззовні здійснюється через ім'я об'єкта :
ім'я_об'єкта.ім'я_члена_класу;
посилання_на_об’єкт.ім'я_члена_класу;
покажчик_на_об'єкт->ім'я_члена_класу;
Власні (private) компоненти локалізовані в класі і не доступні ззовні. Вони можуть використовуватися функціями-членами даного класу і функціями-“друзями” того класу, в якому вони описані.
Захищені (protected) компоненти доступні всередині класу і в похідних класах.
Змінити статус доступу до компонентів класу можна і за допомогою використання у визначенні класу ключового слова class. У цьому випадку усі компоненти класу за замовчуванням є власними.
Приклад.
class complex
{
double re, im; // private за замовчуванням
public:
double real(){return re;}
double imag(){return im;}
void set(double x,double y){re = x; im = y;}
};
Конструктор.
Недоліком розглянутих раніше класів є відсутність автоматичної ініціалізації створюваних об'єктів. Для кожного знову створюваного об'єкта необхідно було викликати функцію типу set (як для класу complex), або явно привласнювати значення даним об'єкта. Однак для ініціалізації об'єктів класу в його визначення можна явно включити спеціальну компонентну функцію, що називається конструктором. Формат визначення конструктора наступний
ім'я_класу(список_форм_параметрів){оператори_тіла_конструктора}
Ім'я цієї компонентної функції за правилами мови С++ повинно збігатися з ім'ям класу. Така функція автоматично викликається при визначенні чи розміщенні в пам'яті за допомогою оператора new кожного об'єкта класу.
Приклад
сomplex(double re1 = 0.0,double im1 = 0.0){re = re1; im = im1;}
Конструктор виділяє пам'ять для об'єкта та ініціалізує дані- члени класу.
Конструктор має ряд особливостей:
* Для конструктора не визначається тип значення, що повертається. Навіть тип void не припустимий.
* Покажчик на конструктор не може бути визначений і відповідно не можна одержати адресу конструктора.
* Конструктори не успадковуються.
* Конструктори не можуть бути описані з ключовими словами virtual, static, const, mutuable, valatile.
Конструктор завжди існує для будь-якого класу, причому, якщо він не визначений явно, він створюється автоматично. За замовчуванням створюється конструктор без параметрів і конструктор копіювання. Якщо конструктор описаний явно, то конструктор за замовчуванням не створюється. За замовчуванням конструктори створюються загальнодоступними (public).
Параметром конструктора не може бути його власний клас, але може бути посилання на нього (T&). Без явної вказівки програміста конструктор завжди автоматично викликається при визначенні (створенні) об'єкта. У цьому випадку викликається конструктор без параметрів. Для явного виклику конструктора використовуються дві форми:
ім'я_класу ім'я_об'єкта(фактичні_параметри);
ім'я_класу(фактичні_параметри);
Перша форма допускається тільки при не порожньому списку фактичних параметрів. Вона передбачає виклик конструктора при визначенні нового об'єкта даного класу:
complex ss(5.9,0.15);
Друга форма виклику приводить до створення об'єкта без імені:
complex ss = complex(5.9,0.15);
Існує два способи ініціалізації даних об'єкта за допомогою конструктора. Раніше ми розглядали перший спосіб, а саме передачу значень параметрів у тіло конструктора. Другий спосіб передбачає застосування списку ініціалізаторів даного класу. Цей список міститься між списком параметрів і тілом конструктора. Кожен ініціализатор списку відноситься до конкретного компонента і має вид
ім'я_даного(вираження)
Приклади.
class CLASS_A
{
int i; float e; char c;
public:
CLASS_A(int ii,float ee,char cc) : i(8),e( i * ee + ii ),з(сс){}
. . .
};
Клас “символьний рядок”.
#include <string.h>
#include <iostream.h>
class string
{
char *ch; // покажчик на текстовий рядок
int len; // довжина текстового рядка
public:
// конструктори
// створює об'єкт – порожній рядок
string(int N = 80): len(0){ch = new char[N+1]; ch[0] = '\0';}
// створює об'єкт по заданому рядку
string(const char *arch){len = strlen(arch);
ch = new char[len+1];
strcpy(ch,arch);}
// компонента-функції
// повертає посилання на довжину рядка
int& len_str(void){return len;}
// повертає покажчик на рядок
char *str(void){return ch;}
. . .};
Тут в класі string два конструктори – функції, що перевантажуються.
За замовчуванням створюється також конструктор копіювання виду T::T(const T&), де Т — ім'я класу. Конструктор копіювання викликається кожен раз, коли виконується копіювання об'єктів, що належать класу. Зокрема він викликається:
а)коли об'єкт передається функції за значенням;
б)при побудові тимчасового об'єкта значення функції, що повертається;
в)при використанні об'єкта для ініціалізації іншого об'єкта.
Якщо клас не містить явно визначеного конструктора копіювання, то при виникненні однієї з цих трьох ситуацій, виконується побітове копіювання об'єкта. Побітове копіювання не у всіх випадках є адекватним. Саме для таких випадків і необхідно визначити власний конструктор копіювання. Наприклад, у класі string:
string(const string& st)
{len=strlen(st.len);
ch=new char[len+1];
strcpy(ch,st.ch); }
Можна створювати масив об'єктів, однак при цьому відповідний клас повинний мати конструктор за замовчуванням(без параметрів).
Масив об'єктів може ініціюватися або автоматично конструктором за замовчуванням, або явним присвоюванням значень кожному елементу масиву.
class demo{
int x;
public:
demo(){x=0;}
demo(int i){x=i;}
};
void main(){
class demo a[20]; //виклик конструктора без параметрів(за замовчуванням)
class demo b[2]={demo(10),demo(100)};//явне присвоювання
Деструктор.
Динамічне виділення пам'яті для об'єкта створює необхідність звільнення цієї пам'яті при знищенні об'єкта. Наприклад, якщо об'єкт формується як локальний всередині блоку, то доцільно, щоб при виході з блоку, коли вже об'єкт перестає існувати, виділена для нього пам'ять була повернута. Бажано щоб звільнення пам'яті відбувалося автоматично. Таку можливість забезпечує спеціальний компонент класу – деструктор класу. Його формат:
~ім’я_класу( ){оператори_тіла_деструктора}
Ім'я деструктора збігається з ім'ям його класу, але добавляється префіксний символ “~” (тильда).
Деструктор не має параметрів і значення, що повертається. Виклик деструктора виконується не явно (автоматично), як тільки об'єкт класу знищується.
Наприклад, при виході за область визначення або при виклику оператора delete для покажчика на об'єкт.
string *p=new string(“рядок”);
delete p;
Якщо в класі деструктор не визначений явно, то компілятор генерує деструктор за замовчуванням, що просто звільняє пам'ять, зайняту даними об'єкта. У тих випадках, коли потрібно виконати звільнення й інші об'єкти пам'яті, наприклад область, на яку вказує ch в об'єкті string, необхідно визначити деструктор явно: ~string(){delete []ch;}
Також як і для конструктора, не може бути визначений покажчик на деструктор.
Покажчики на компоненти-функції.
Можна визначити покажчик на компоненти-функції.
тип_поверн_значення(ім'я_класу::*ім'я_покажчика_на_функцію)(специф_параметрів_функції);
Приклад .
double(complex : :*ptcom)(); // Визначення покажчика
ptcom = &complex : : real; // Настроювання покажчика
// Тепер для об'єкта А можна викликати його функцію
complex A(5.2,2.7);
cout<<(A.*ptcom)();
Можна визначити також тип покажчика на функцію
typedef double&(complex::*PF)();
а потім визначити і сам покажчик
PF ptcom=&complex::real;
Порядок виконання роботи.
1.Визначити клас користувача відповідно до варіанта завдання (дивись додаток).
2.Визначити в класі наступні конструктори: без параметрів, з параметрами, копіювання.
3.Визначити в класі деструктор.
4.Визначити в класі компонента-функції для перегляду і встановлення полів даних.
5.Визначити покажчик на компонент-функцію.
6.Визначити покажчик на екземпляр класу.
7.Написати демонстраційну програму, в якій створюються і руйнуються об'єкти класу користувача і кожен виклик конструктора і деструктора супроводжується видачею відповідного повідомлення (який об'єкт, який конструктор чи деструктор викликав).
8.Показати в програмі використання покажчика на об'єкт і покажчика на компонент-функцію.
Методичні вказівки.
1.Приклад визначення класу.
const int LNAME=25;
class STUDENT{
char name[LNAME]; // ім'я
int age; // вік
float grade; // рейтинг
public:
STUDENT(); // конструктор без параметрів
STUDENT(char*,int,float); // конструктор з параметрами
STUDENT(const STUDENT&); // конструктор копіювання
~STUDENT();
char * GetName() ;
int GetAge() const;
float GetGrade() const;
void SetName(char*);
void SetAge(int);
void SetGrade(float);
void Set(char*,int,float);
void Show(); };
Більш професійне визначення поля name типу покажчик: char* name. Однак у цьому випадку реалізація компонентів-функцій ускладнюється.
2.Приклад реалізації конструктора з видачею повідомлення.
STUDENT::STUDENT(char*NAME,int AGE,float GRADE)
{
strcpy(name,NAME); age=AGE; grade=GRADE;
cout<<”\nКонструктор з параметрами викликаний для об'єкта “<<this<<endl;
}
3.Варто передбачити в програмі всі можливі способи виклику конструктора копіювання. Нагадуємо, що конструктор копіювання викликається:
а) при використанні об'єкта для ініціалізації іншого об'єкта
Приклад
STUDENT a(“Іванов”,19,50), b=a;
б) коли об'єкт передається функції за значенням
Приклад
void View(STUDENT a){a.Show;}
в) при побудові тимчасового об'єкта значення функції, що повертається
Приклад
STUDENT NoName(STUDENT & student)
{STUDENT temp(student);
temp.SetName(“NoName”);
return temp;}
STUDENT c=NoName(a);
4.У програмі необхідно передбачити розміщення об'єктів як в статичній, так і в динамічній пам'яті, а також створення масивів об'єктів.
Приклади.
а)Масив студентів розміщається в статичній пам'яті
STUDENT gruppa[3];
gruppa[0].Set(“Іванов”,19,50);
і т.д.
або
STUDENT gruppa[3]={STUDENT(“Іванов”,19,50),
STUDENT(“Петрова”,18,25.5),
STUDENT(“Сидоров”,18,45.5)};
б)Масив студентів розміщається в динамічній пам'яті
STUDENT *p;
p=new STUDENT[3];
p-> Set(“Іванов”,19,50);
і т.д.
5.Приклад використання покажчика на компонентну функцію
void (STUDENT::*pf)();
pf=&STUDENT::Show;
(p[1].*pf)();
6.Програма використовує три файли
* заголовний h-файл із визначенням класу
* cpp-файл із реалізацією класу
* сpp-файл демонстраційної програми
Для запобігання багаторазового включення файлу-заголовка варто використовувати директиви препроцесора
#ifndef STUDENTH
#define STUDENTH
// модуль STUDENT.H
...
#endif
Зміст звіту.
1.Титульний лист: назва дисципліни, номер і найменування роботи, прізвище, ім'я, по батькові студента, дата виконання.
2.Постановка задачі. Варто дати конкретну постановку, тобто вказати який клас повинен бути реалізований, які повинні бути в ньому конструктори, компоненти-функції і т.д.
3.Визначення класу користувача з коментарями.
4.Реалізація конструкторів і деструктора.
5.Фрагмент програми, що показує використання покажчика на об'єкт і покажчика на функцію з поясненням.
6.Лістінг основної програми, де повинно бути зазначене в якому місці і який конструктор чи деструктор викликаються.
Додаток. Варіанти завдань
Опису членів-даних класів користувача
1.СТУДЕНТ | 2.СЛУЖБОВЕЦЬ | 3.КАДРИ |
ім'я – char* | ім'я – char* | ім'я – char* |
курс – int | вік – int | номер цеху-int |
стать - int(bool) | робітник стаж – int | розряд- int |
4.ВИРІБ | 5.БІБЛІОТЕКА | 6.ІСПИТ |
ім'я- char* | ім'я- char* | ім'я студента-char* |
шифр – char* | автор – char* | дата - int |
кількість- int | вартість- float | оцінка- int |
7.АДРЕСА | 8.ТОВАР | 9.КВИТАНЦІЯ |
ім'я- char* | ім'я- char* | номер- int |
вулиця- char* | кількість- int | дата- int |
номер будинку- int | вартість- float | сума- float |
10.ЦЕХ | 11.ПЕРСОНА | 12.АВТОМОБІЛЬ |
ім'я- char* | ім'я- char* | марка-char* |
начальник – char* | вік – int | потужність - int |
кількість працюючих- int | стать - int(bool) | ціна- float |
13.КРАЇНА | 14.ТВАРИНА | 15.КОРАБЕЛЬ |
ім'я- char* | ім'я- char* | ім'я-char* |
форма правління – char* | клас-char* | водотоннажність - int |
площа – float | середня вага – int | тип- char* |
Лабораторна робота №2.
Успадкування і віртуальні функції
Мета. Одержати практичні навички створення ієрархії класів і використання статичних компонентів класу.
Основний зміст роботи.
Написати програму, в якій створюється ієрархія класів. Включити поліморфні об'єкти в зв'язаний список, використовуючи статичні компоненти класу. Показати використання віртуальних функцій.
Короткі теоретичні відомості
Статичні члени класу
Такі компоненти повинні бути визначені в класі як статичні (static). Статичні дані класів не дублюються при створенні об'єктів, тобто кожен статичний компонент існує в єдиному екземплярі. Доступ до статичного компонента можливий тільки після його ініціалізації. Для ініціалізації використовується конструкція
тип ім'я_класу : : ім'я_даного ініціалізатор;
Наприклад, int complex : : count = 0;
Ця речення повинне бути розміщене в глобальній області після визначення класу. Тільки при ініціалізації статичне дане класу одержує пам'ять і стає доступним. Звертатися до статичного даного класу можна звичайним способом через ім'я об'єкта
ім'я_об'єкта.ім'я_компонента
Але до статичних компонентів можна звертатися і тоді, коли об'єкт класу ще не існує. Доступ до статичних компонентів можливий не тільки через ім'я об'єкта, але і через ім'я класу
ім'я_класу : : ім'я_компонента
Однак так можна звертатися тільки до public компонентів.
Для звертання до private статичного компонента ззовні можна за допомогою статичних компонентів-функцій. Ці функції можна викликати через ім'я класу.
ім'я_класу : : ім'я_статичної_функції
Приклад.
#include <iostream.h>
class TPoint
{
double x,y;
static int N; // статичний компонент- дане : кількість крапок
public:
TPoint(double x1 = 0.0,double y1 = 0.0){N++; x = x1; y = y1;}
static int& count(){return N;} // статичний компонент-функція
};
int TPoint : : N = 0; //ініціалізація статичного компонент-даного
void main(void)
{TPoint A(1.0,2.0);
TPoint B(4.0,5.0);
TPoint C(7.0,8.0);
cout<<“\nВизначені ”<<TPoint : : count()<<“крапки.”; }
Покажчик this
Коли функція-член класу викликається для обробки даних конкретного об'єкта, для цієї функції автоматично і неявно передається покажчик на той об'єкт, для якого функція викликана. Цей покажчик має ім'я this і неявно визначене в кожній функції класу в такий спосіб
ім'я_класу *const this = адреса_об'єкта
Покажчик this є додатковим схованим параметром кожної нестатичної компонентної функції. При вході в тіло приналежної класу функції this ініціюється значенням адреси того об'єкта, для якого викликана функція. В результаті цей об'єкт стає доступним всередині цієї функції.
В більшості випадків використання this є неявним. Зокрема, кожне звертання до нестатичного функції-члену класу неявно використовує this для доступу до члена відповідного об'єкта.
Прикладом широко розповсюдженого явного використання this є операції зі зв'язаними списками.
Успадкування
Успадкування — це механізм одержання нового класу на основі вже існуючого. Існуючий клас може бути доповнений чи змінений для створення нового класу.
Існуючі класи називаються базовими, а нові – похідними. Похідний клас успадковує опис базового класу; потім він може бути змінений додаванням нових членів, зміною існуючих функцій-членів і зміною прав доступу. За допомогою спадкування може бути створена ієрархія класів, що спільно використовують код і інтерфейси.
Наслідувані компоненти не переміщаються в похідний клас, а залишаються в базових класах.
В ієрархії похідний об'єкт успадковує дозволені для спадкування компонента всіх базових об'єктів (public, protected).
Допускається множинне спадкування – можливість для деякого класу успадковувати компоненти декількох ніяк не зв'язаних між собою базових класів. В ієрархії класів угода щодо приступності компонентів класу наступні:
private – Член класу може використовуватися тільки функціями-членами даного класу і функціями-“друзями” свого класу. У похідному класі він недоступний.
protected – Те ж, що і private, але додатково член класу з даним атрибутом доступу може використовуватися функціями-членами і функціями-“друзями” класів, похідних від даного.
public – Член класу може використовуватися будь-якою функцією, яка є членом даного чи похідного класу, а також до public - членів можливий доступ ззовні через ім'я об'єкта.
Варто мати на увазі, що оголошення friend не є атрибутом доступу і не успадковується.
Синтаксис визначення похідного класу :
class ім'я_класу : список_базових_класів
{список_компонентів_класу};
У похідному класі успадковані компоненти одержують статус доступу private, якщо новий клас визначений за допомогою ключового слова class, і статус public, якщо за допомогою struct
Явно змінити статус доступу, що замовчується, при спадкуванні можна за допомогою атрибутів доступу - private, protected і public, що вказуються безпосередньо перед іменами базових класів.
Конструктори і деструктори похідних класів
Оскільки конструктори не успадковуються, при створенні похідного класу наслідувані їм дані-члени повинні ініціюватися конструктором базового класу. Конструктор базового класу викликається автоматично і виконується до конструктора похідного класу. Параметри конструктора базового класу вказуються у визначенні конструктора похідного класу. У такий спосіб відбувається передача аргументів від конструктора похідного класу конструктору базового класу.
Наприклад.
class Basis
{ int a,b;
public:
Basis(int x,int y){a=x;b=y;}
};
class Inherit:public Basis
{int sum;
public:
Inherit(int x,int y, int s):Basis(x,y){sum=s;}
};
Об'єкти класу конструюються знизу-вверх: спочатку базовий, потім компоненти-об'єкти (якщо вони маються), а потім сам похідний клас. Таким чином об'єкт похідного класу містить у якості підоб’єкта об'єкт базового класу.
Знищуються об'єкти в зворотному порядку: спочатку похідний, потім його компоненти-об'єкти, а потім базовий об'єкт.
Таким чином, порядок знищення об'єкта протилежний стосовно порядку його конструювання.
Віртуальні функції.
До механізму віртуальних функцій звертаються в тих випадках, коли в кожному похідному класі потрібно свій варіант деякої компонентної функції. Класи, що включають такі функції, називаються поліморфними і відіграють особливу роль в ООП.
Віртуальні функції надають механізм пізнього (відкладеного) чи динамічного зв'язування. Будь-яка нестатична функція базового класу може бути зроблена віртуальною, для чого використовується ключове слово virtual.
Приклад.
class base
{
public:
virtual void print(){cout<<“\nbase”;}
. . .
};
class dir : public base
{
public:
void print(){cout<<“\ndir”;}
};
void main()
{
base B,*bp = &B;
dir D,*dp = &D;
base *p = &D;
bp –>print(); // base
dp –>print(); // dir
p –>print(); // dir
}
Таким чином інтерпретація кожного виклику віртуальної функції через покажчик на базовий клас залежить від значення цього покажчика, тобто від типу об'єкта, для якого виконується виклик.
Вибір того, яку віртуальну функцію викликати буде залежати від типу об'єкта, на який фактично (у момент виконання програми) спрямований покажчик, а не від типу покажчика.
Віртуальними можуть бути тільки нестатичні функції-члени.
Віртуальність успадковується. Після того як функція визначена як віртуальна, її повторне визначення в похідному класі (з тим же самим прототипом) створює в цьому класі нову віртуальну функцію, причому специфікатор virtual може не використовуватися.
Конструктори не можуть бути віртуальними, на відміну від деструкторів. Практично кожен клас, що має віртуальну функцію, повинний мати віртуальний деструктор.
Абстрактні класи
Абстрактним називається клас, у якому є хоча б одна чиста (порожня) віртуальна функція.
Чистою віртуальною функцією називається компонентна функція, що має наступне визначення:
virtual тип ім'я_функції(список_формальних_параметрів) = 0;
Чиста віртуальна функція нічого не робить і недоступна для викликів. Її призначення – бути основою для підмінюючих її функцій у похідних класах. Абстрактний клас може використовуватися тільки в якості базового для похідних класів.
Механізм абстрактних класів розроблений для представлення загальних понять, що надалі передбачається конкретизувати. При цьому побудова ієрархії класів виконується за наступною схемою. На чолі ієрархії стоїть абстрактний базовий клас. Він використовується для спадкування інтерфейсу. Похідні класи будуть конкретизувати і реалізувати цей інтерфейс. В абстрактному класі оголошені чисті віртуальні функції, що по суті є абстрактними методи.
Приклад .
class Base{
public:
Base(); // конструктор за замовчуванням
Base(const Base&); // конструктор копіювання
virtual ~Base(); // віртуальний деструктор
virtual void Show()=0; // чиста віртуальна функція
// інші чисті віртуальні функції
protected: // захищені члени класу
private:
// часто залишається порожнім, інакше буде заважати майбутнім розробкам
};
class Derived: virtual public Base{
public:
Derived(); // конструктор за замовчуванням
Derived(const Derived&); // конструктор копіювання
Derived(параметри); // конструктор з параметрами
virtual ~Derived(); // віртуальний деструктор
void Show(); // перевизначена віртуальна функція
// інші перевизначені віртуальні функції
// інші перевантажені операції
protected:
// використовується замість private, якщо очікується спадкування
private:
// використовується для деталей реалізації
};
Об'єкт абстрактного класу не може бути формальним параметром функції, однак формальним параметром може бути покажчик на абстрактний клас. У цьому випадку з'являється можливість передавати у функцію, що викликається, як фактичний параметр значення покажчика на похідний об'єкт, замінюючи ним покажчик на абстрактний базовий клас. У такий спосіб ми одержуємо поліморфні об'єкти.
Абстрактний метод може розглядатися як узагальнення перевизначення. В обох випадках поводження батьківського класу змінюється для нащадка. Для абстрактного методу, однак, поводження просто не визначене. Будь-яке поводження задається в похідному класі.
Одна з переваг абстрактного методу є чисто концептуальною: програміст може думкою наділити потрібною дією абстракцію як завгодно високого рівня. Наприклад, для геометричних фігур ми можемо визначити метод Draw, що їх малює: трикутник TTriangle, окружність TCircle, квадрат TSquare. Ми визначимо аналогічний метод і для абстрактного батьківського класу TGraphObject. Однак, такий метод не може виконувати корисну роботу, оскільки в класі TGraphObject просто немає достатньої інформації для малювання чого-небудь. Проте присутність методу Draw дозволяє зв'язати функціональність (малювання) тільки один раз із класом TGraphObject, а не вводити три незалежні концепції для підкласів TTriangle, TCircle, TSquare.
Є і друга, більш актуальна причина використання абстрактного методу. В об’єктно-орієнтованих мовах програмування зі статичними типами даних, до яких відноситься і С++, програміст може викликати метод класу, тільки якщо компілятор може визначити, що клас дійсно має такий метод. Припустимо, що програміст хоче визначити поліморфну змінну типу TGraphObject, що буде в різні моменти часу містити фігури різного типу. Це припустимо для поліморфних об'єктів. Проте компілятор дозволить використовувати метод Draw для перемінної, тільки якщо він зможе гарантувати, що в класі змінної є цей метод. Приєднання методу Draw до класу TGraphObject забезпечує таку гарантію, навіть якщо метод Draw для класу TGraphObject ніколи не виконується. Природно для того, щоб кожна фігура малювалася по своєму, метод Draw повинний бути віртуальним.
Порядок виконання роботи.
1.Визначити ієрархію класів (відповідно до варіанта).
2.Визначити в класі статичний компонент - покажчик на початок зв'язаного списку об'єктів і статичну функцію для перегляду списку.
3.Реалізувати класи.
4.Написати демонстраційну програму, у якій створюються об'єкти різних класів і поміщаються в список, після чого список проглядається.
5.Зробити відповідні методи не віртуальними і подивитися, що буде.
6.Реалізувати варіант, коли об'єкт додається в список при створенні, тобто в конструкторі (дивися пункт 6 наступного розділу).
Методичні вказівки.
1.Для визначення ієрархії класів зв'язати відношенням спадкування класи, приведені в додатку (для заданого варіанта). З перерахованих класів вибрати один, який буде стояти на чолі ієрархії. Це абстрактний клас.
2.Визначити у класах усі необхідні конструктори і деструктор.
3.Компонентні дані класу потрібно специфікувати як protected.
4.Приклад визначення статичних компонентів:
static person* begin; // покажчик на початок списку
static void print(void); // перегляд списку
5.Статичну компоненту-дане потрібно ініціювати поза визначенням класу, у глобальній області.
6.Для додавання об'єкта в список варто передбачити метод класу, тобто об'єкт сам додає себе в список. Наприклад, a.Add() - об'єкт a додає себе в список.
Включення об'єкта в список можна виконувати при створенні об'єкта, тобто помістити оператори включення в конструктор. У випадку ієрархії класів, включення об'єкта в список повинний виконувати тільки конструктор базового класу. Ви повинні продемонструвати обидва ці способи.
7.Список проглядається шляхом виклику віртуального методу Show кожного об'єкта.
8.Статичний метод перегляду списку викликайте не через об'єкт, а через клас.
9.Визначення класів, їхню реалізацію, демонстраційну програму помістити в окремі файли.
Зміст звіту.
1.Титульний лист: назва дисципліни, номер і найменування роботи, прізвище, ім'я, по батькові студента, дата виконання.
2.Постановка задачі. Варто дати конкретну постановку, тобто вказати які класи повинні бути реалізовані, які повинні бути в них конструктори, компоненти-функції і т.д.
3.Ієрархія класів у виді графа.
4.Визначення класів користувача з коментарями.
5.Реалізація конструкторів з параметрами і деструктора.
6.Реалізація методів для додавання об'єктів в список .
7. Реалізація методів для перегляду списку.
8.Лістінг демонстраційної програми.
9.Пояснення необхідності віртуальних функцій. Варто показати, які результати будуть у випадку віртуальних і не віртуальних функцій.
Додаток. Варіанти завдань
Перелік класів.
студент, викладач, персона, зав. кафедрою
2. службовець, персона, робітник, інженер
3. робітник, кадри, інженер, адміністрація
4. деталь, механізм, виріб, вузол
5. організація, страхова компанія, суднобудівна компанія, завод
6. журнал, книга, друковане видання, підручник
7. тест, іспит, випускний іспит, випробовування
8. місце, область, місто, мегаполіс