- •Передмова
- •Розділ 1 об'єктний підхід у програмуванні
- •1.1.Причини виникнення ооп
- •1.1.1.Складність об'єкта дослідження
- •1.1.2.Складність процесу розробки програмного забезпечення
- •1.1.3.Складність опису окремих елементів
- •1.2.Парадигма ооп
- •1.3.Історія розвитку ооп
- •Розділ 2 об'єкти й класи: інкапсуляція
- •2.1.Структура об'єкта й класу
- •2.2.Особливості опису класів у мовах ооп
- •2.2.1.Опис класів в SmallTalk
- •2.2.3.Опис класів в Delphi
- •2.2.4.Опис класів в Java
- •2.3.Поля даних та їх ініціалізація
- •2.3.1.Визначення полів даних в SmallTalk
- •2.3.3.Визначення полів даних в Delphi
- •2.3.4.Визначення змінних в Java
- •2.4.Доступ до даних
- •2.4.1.Доступ до даних в SmallTalk
- •2.4.3.Доступ до даних в Delphi
- •2.4.4.Доступ до даних в Java
- •2.5.Спеціальні змінні
- •2.5.1.Спеціальні змінні в SmallTalk
- •2.5.3.Спеціальні змінні в Java
- •2.5.4.Спеціальні змінні в Delphi
- •2.6.Посилання
- •2.6.1.Визначення посилань в SmallTalk, Delphi і Java
- •2.7.Методи
- •2.7.1.Загальна схема визначення методу
- •2.7.2.Визначення методів в SmallTalk
- •2.7.4.Визначення методів в Delphi
- •2.7.5.Визначення методів в Java
- •2.8."Дружні" методи
- •2.8.2.Аналог дружніх функцій в Delphi
- •2.9.Конструктори й деструктори
- •2.9.1.Конструктори й деструктори в SmallTalk
- •2.9.3.Конструктори й деструктори в Delphi
- •2.9.4.Конструктори й деструктори в Java
- •2.10.Властивості
- •2.10.1.Властивості в Delphi
- •2.10.2.Властивості в Java
- •2.12.Абстрактні методи
- •Розділ 3 успадкування
- •3.1.Форми успадкування
- •3.2.Успадкування в SmallTalk
- •3.3.1.Віртуальне успадкування
- •3.3.2.Правило сумісності типів
- •3.3.3.Використання конструкторів і деструкторів при успадкуванні
- •3.4.Успадкування в Delphi
- •3.4.1.Ієрархія класів в Delphi
- •3.4.2.Створення нових компонентів
- •3.5.Успадкування в Java
- •3.5.1.Використання ключового слова super
- •3.5.2.Клас Object
- •Розділ 4 поліморфізм
- •4.1.Віртуальні методи
- •4.2.1.Механізм пізнього зв'язування
- •4.2.2.Таблиця віртуальних методів
- •4.3.Поліморфізм в Delphi
- •4.3.1.Заміщення віртуальних і динамічних методів
- •4.3.2.Приведення типів
- •4.4.Поліморфізм в Java
- •4.5.Поліморфізм в SmallTalk
- •5.1.Потокові класи
- •5.1.1.Ієрархія потокових класів
- •5.1.2.Форматоване введення/ виведення
- •5.1.3.Маніпулятори
- •5.1.4.Введення/виведення у файл
- •5.2.Контейнерні класи
- •5.2.1.Ітератори
- •5.2.2.Визначення контейнерних класів
- •5.2.3.Стандартні контейнерні класи
- •5.3.1.Параметиізовані класи (шаблони)
- •5.3.2.Ітератори stl
- •5.3.3.Узагальнені алгоритми
- •Література
- •Додатки лабораторна робота №1 об'єкти й повідомлення в smalltalk
- •Лабораторна робота №2 класи й методи в smalltalk
- •Листинг 3.1
- •Листинг 3.2
- •Листинг 3.3
- •Лабораторна робота 5 компоненти в delphi
- •Лабораторна робота 6 меню й вікна в delphi
- •Лабораторна робота 7 розробка меню в java
- •Лабораторна робота 8 робота з подіями в java
Листинг 3.1
//Інтерфейс класу Fraction
class Fraction {
private:
int num, denom;
Fraction reduced( void );// дріб з найменшим загальним знаменником
public:
Fraction( void ){ }
Fraction(int aNum, int aDenom ){num = aNum; denom = aDenom;} ~Fraction( void ){ }
//методи доступу
int numerator(void ){ return num; }
int denominator( void ) { return denom; }
//арифметичні методи
Fraction add( Fraction aFraction );
//підсумовування даного дробу й aFraction
Fraction substract( Fraction aFraction );
//віднімання даного дробу й aFraction
Fraction multiply( Fraction aFraction );
//добуток даного дробу й aFraction
Fraction divide (Fraction aFraction );
//частка від розподілу даного дробу на aFraction
//методи порівняння
int lessthan (Fraction aFraction );
//повертає ненульове значення, якщо даний дріб менше aFraction
int greaterthan (Fraction aFraction );
//повертає ненульове значення, якщо даний дріб більше aFraction
int equal( Fraction aFraction );
//повертає ненульове значення, якщо даний дріб дорівнює aFraction
//методи печатки void print( void );
//друкує даний дріб у форматі num/denom };
Листинг 3.2
//Pеалізація функцій-членів класу Fraction.
#include <iostream.h>
#include "fraction.h"
Fraction Fraction::reduced( void ){
int gcd = 1;
int min = num;
if( num > denom ) { min = denom; };
for( int i = 1; i <= min; i++ ) {
if( ( num%i == 0 ) && ( denom%i == 0 ) ) {gcd = i; }
}
num = num/gcd;
denom = denom/gcd;
return *this;
}
Fraction Fraction::add( Fraction aFraction ) {
Fraction tempt( num * aFraction.denom + denom * aFraction.num,
denom * aFraction.denom );
return temp.reduced() ;
}
Fraction Fraction::substract( Fraction aFraction ){}
Fraction Fraction::multiply( Fraction aFraction ){}
Fraction Fraction::divide( Fraction aFraction ){}
int Fraction::lessthan( Fraction aFraction ){}
int Fraction::greaterthan( Fraction aFraction ){}
int Fraction::equal( Fraction aFraction ){}
void Fraction::print( void ){}
Листинг 3.3
//Програма перевірки класу Fraction
#include <iostream.h>
#include "fraction.h"
main ( ) {
Fraction frac1( 3, 4 ), frac2( 20, 23 ), frac3, frас4;
cout<<"\n\nFraction test program results\n\n";
cout<<"frac1 = ";
fraс1.print();
cout<<"\n frac2 = ";
frac2.print() ;
frac3 = frac1.add( frac2 );
cout<<"\n Sum of frac1 and frac2 = ";
frac3.print();
cout<<" \n";
cout<<"Accessing methods test\n";
cout<<"frac3 numerator = "<<frac3.numerator()<<"\n";
cout<<"frac3 denominator = " <<frac3.denominator()<<"\n";
}
Конструктори
Конструктори не є обов'язковою частиною опису класу. Їхнє основне призначення - це ініціалізація полів знову створюваних об'єктів.
У листингу 3.4 представлений зміст файлу заголовка First.h, що містить прототипи й визначення всіх функцій-членів даного класу. Відкрита (public) частина класу містить два конструкторів, деструктор і вісім функцій-членів. Всі ці функції доступні в будь-якому файлі, що містить директиву #include "First.h". Другий параметр конструктора встановлює за замовчуванням для змінної екземпляра field3 значення, що дорівнює 0. Це значення встановлюється в тому випадку, якщо конструктор викликається з одним параметром замість двох.
Листинг 3.4.
//Інтерфейс класу First
#include <iostream.h>
#include <stdio.h>
class First {
private:
int field1; // підраховує число звертань до класу First
float field2, field3;
void setField1( int anlnt ) { field1=anlnt;}
public:
//Конструктори й деструктори
First (void ) {
cout<<"\n In void constructor for First";
setField1(0);
field2=0.0;
field3=0.0;
}
First (float aField2, float aField3 = 0.0 ) {
cout«"\n In parameter constructor for First";
setField1(0);
field2=aField2;
field3=aField3;
}
~First( void ) {
cout«"\n In destructor for First" ;
}
//Методи доступу
int getField1( void ){ return ++field1; }
float getField2( void ) {field1++; return field2; }
float getField3( void ){ field1++; return field3; }
void setField2 ( float aFloat ) { field1++; field2 = aFloat;}
void setField3( float aFloat ) { field1++; field3 = aFloat;}
void resetField1( void ){ setField1(0); }
//Операції над полями даних
int compareFields( void ){
field1++; return field2 == field3;
}
//Методи друку
void print( void ) {
field1++;
cout<<"\n Number of accesses to this object="<< field1;
printf( "\n Value of field2=%.2fValue of field3=%.2f",
field2, field3);
}
};
Листинг 3.5 містить невелику тестову програму, у якій оголошений вектор (масив об'єктів), що складається з двох екземплярів об'єктів класу First. Результати роботи тестової програми вказують на правильну обробку, як об'єкта first1, так і вектора first2. На результати роботи програми не впливає те, що при оголошенні вектора викликається конструктор без параметрів.
Листинг 3.5.
//Тестова програма для масиву об'єктів класу First
#include "First.h"
main () {
First first1, first2[2];
cout<<"\n\nTest program results for class First\n"; first1.resetField1();
cout<<"\nModify fields of first1 and verify\n"; first1.setField2( 15.8 ); first1.setField3( -8.0 );
firstl.print();
cout<<"\n\nSet and verify fields of Vector object\n";
for(int i = 0; i < 2; i++) {
first2 [i] .resetField1();
first2[i].setField2( (float)i );
first2 [i] .setField3( (float) ( 6*i+6 ) );
first2 [i] .print ();
}
cout<<"\n";
}
Виконайте цю програму й зрівняєте результати з наведеними нижче.
Результати роботи:
In void constructor for First
In void constructor for First
In void constructor for First
Test program results for class First
Modify fields of first1 and verify
Number of accesses to this object = 3
Value of field2 = 15.80 Value of field3 = -8.00
Set and verify fields of vector object
Number of accesses to this object = 3
Value of field2 = 0.00 Value of field3 = 6.00
Number of accesses to this object = 3
Value of field2 = 1.00 Value of field3 = 12.00
In destructor for First
In destructor for First
In destructor for First
Ініціалізація полів даних та статичних змінних
Існують чіткі правила ініціалізації полів усередині опису класу. Зокрема поле не може бути проініціалізоване при описі. Замість цього ініціалізація полів здійснюється за допомогою конструктора, або за допомогою методу доступу після створення екземпляра об'єкта. Листинг 3.6 ілюструє роботу зі звичайними (нестатичними) полями. Ініціалізація змінної anlnt викличе помилку при компіляції програми. Оператор присвоювання варто стерти. Конкретні значення можуть бути привласнені полям тільки при створенні екземпляра об'єкта, або після його створення.
Листинг 3.6.
//Ініціалізація полів об’єктів
class Second {
private:
int anInt = 6; // Помилка, така ініціалізація неприпустима
int otherInt;
public:
Second (void ){ anInt = 6; }
Second( int inInt3, int inInt4 ) {
anInt=inInt3;
otherInt=inInt4;}
void setAnInt( int inInt1 ){ anInt = inInt1; }
void setOtherInt( int inInt2 ){ otherInt = inInt2; }
};
main () {
Second aSecond; // виклик конструктора без параметрів
Second anotherSecond(25, 30);//виклик конструктора з
//двома параметрами
aSecond.setOtherInt( 50 );//використання методу доступу
}
Перевантаження операцій
Поведінку об'єкта визначають його функції-члени (методи), що визначені в описі класу. Розглянемо клас IntArray опис якого наведено в листингу 3.7.
Листинг 3.7.
//Клас IntArray
class IntArray {
private:
int *data;
unsigned size;
unsigned accessError;
public:
IntArray( void );
IntArray( unsigned mySize );
void putt( unsigned at, int value );
int get( unsigned at );
unsigned error ( void ){return accessError; };
};
IntArray::IntArray( void ) {
data = new int[ 100 ]; size = 100;
for( int index = 0; index < size; index ++ ) data[ index ] = 0;
}
IntArray::IntArray( unsigned mySize ) {
data = new int[ mySize ];
size = mySize;
accessError = 0;
for( int index = 0; index < size; index ++ ) data[ index ] = 0;
}
void IntArray::put( unsigned at, int value ) {
if( at > 0 && at <= size ) { data[ at -1 ] = value; accessError = 0; }
else {accessError = 1; }
}
int IntArray:: get( unsigned at ) {
if( at > 0 &s at <= size ){accessError = 0;return data[ at -1 ]; }
else {accessError = 1;return 0; }
}
#include <iostream.h>
main () {
IntArray myArray[ 200 ];
for( int index = 1; index <= 200; index ++)
myArray.put( index, index );
cout<<"myArray.get(25) = " << myArray.get(25)<<"\n";
cout<<"accessError = " " <<myArray.error()<<"\n\n";
cout<<"myArray.get(100 ) = " <<myArray.get(100)<<"\n";
cout<<"accessError " <<myArray. error () <<"\n\n" ;
cout<<"myArray.get ( 200 ) = " <<myArray.get(200)<<"\n";
cout<<"accessError = " << myArray.error()<<"\n\n";
cout<<"myArray.get(0) = " <<myArray.get(0 )<<"\n";
cout<<"accessError = "<<myArray.error()<<"\n\n";
}
Виконайте цю програму й порівняйте результати з наведеними нижче.
Результат роботи:
myArray.get (25) = 25
accessError =0
myArray.get(100) = 100
accessError =0
myArray.get(200 )= 200
accessError = 0
myArray.get(0) = 0
accessError = 1
Метод get з параметром at і метод put з параметрами at і value дозволяють здійснювати звичайний доступ до об'єктів масиву. При доступі здійснюється контроль діапазону.
Дружні функції
Головною метою використання дружніх функцій і класів є підвищення ефективності програми. Дружні функції й класи можуть здійснювати прямий доступ до закритих полів класу без використання функцій-членів цього класу.
Наприклад, припустимо, що ми хочемо вивести деяку множину значень, пов'язаних з об'єктом класу IntArray, що представлений у листингу 3.8. Ми можемо перевантажити операцію <<. Оголосивши перевантажений оператор дружнім класу IntArray, ми дозволяємо цій функції здійснювати прямий доступ до захищених полів цього класу. Необхідно підкреслити, що перевантажена функція << не є членом класу IntArray.
Листинг 3.8.
//Перевантажена функція, дружня класу IntArray
#include <iostream.h>
class IntArray {
/* Код з листингу 3.7 */
/* ... */
friend ostream& operator << ( osteram& OutStream, IntArray& anIntArray );
/* Код з листингу 3.7 */
/* ... */
ostream& operator << ( osteream& OutStream, IntArray& anIntArray ) {
for( int index = 1; index <= anIntArray.size; index ++ ) {
OutStream<<"The value at index position "<<index<<" = ";
OutStream<<anIntArray[ index ]<<"\n"; }
return OutStream;
}
Перевантажена операція << повертає посилання на ostream для того, щоб мати можливість послідовного запису декількох операцій << в одному виразі. Кожна з операцій << повертає адресу об'єкта ostream. Цей об'єкт потім передається наступній операції <<.
Збережіть визначення класу IntArray у файлі “intarray.h”.
Оператор присвоювання
В C++ є операція присвоювання за замовчуванням, що виконує рекурсивне по елементне копіювання одного об'єкта в іншій. У листингу 3.9 наведений приклад використання операції присвоювання за замовчуванням.
Листинг 3.9.
//Присвоювання за замовчуванням у класі IntArray.
#include "IntArray.h"
main () {
IntArray myArray( 200 );
for( int index : 1; index <= 200; index ++ )
myArray [ index ] = index;
IntArray secondArray( 200 );
secondArray = myArray;
cout<<secondArray;
myArrayt 200 ] = -50;
cout<<secondArray; }
Виконаєте цю програму. Виведене значення елемента secondArray[ 200 ] буде дорівнювати -50.
Завдання для самостійної роботи
Реалізуйте інші функції-члени класу Fraction і додайте в програму тестування перевірку методів порівняння й методів арифметичних операцій substract, multiply, divide.
Замініть методи add, substract, multiply, divide, greaterthen, lessthen, equal перевантаженими операціями +, -, *, /, >, <, ==. Перевірте роботу цих методів за допомогою тестової програми.
ЛАБОРАТОРНА РОБОТА 4 УСПАДКУВАННЯ Й ПОЛІМОРФІЗМ В C++
Відкриті й закриті похідні класи
У С++ класи, від яких будується ієрархія класів, називаються базовими, а їхні підкласи – похідними класами.
Правила доступу до об'єкта похідного класу залежать від того, чи є цей похідний клас відкритим (public) або закритим (private). Об'єкти відкритого похідного класу мають прямий доступ до відкритого (public) розділу свого класу й відкритому розділу базового класу. Об'єкти закритого похідного класу мають прямий доступ тільки до відкритої частини свого класу. Доступ до відкритої частини базового класу для таких об'єктів заблокований. Конструктори похідних класів автоматично викликають відповідний конструктор базового класу.
У листингу 4.1 представлений інтерфейс і опис ієрархії трьох класів. Ця ієрархія ілюструє поступову деталізацію властивостей у підкласах.
Листинг 4.1.
//Інтерфейс ієрархії класів Vehicle
#include <stdio.h>
#include <iostream.h>
class Vehicle {
private:
long weight; // вага у фунтах
int topSpeed; // швидкість - милі в годину
float price; // ціна в доларах
public:
Vehicle( void ){}
Vehicle( long aWeight, int aTopSpeed, float aPrice ) {
weight = aWeight; topSpeed = aTopSpeed; price = aPrice;
}
~Vehicle( void ){}
//методи доступу
long getWeight ( void ){ return weight; }
int getTopSpeedt void }{ return topSpeed; }
float getPrice ( void ) { return price; }
//метод печатки
void print( void ) {
cout<<"\n\nWeight: " << weight << "pounds";
cout<<"\nTop speed: " << topSpeed <<"mph";
printf("\nPrice: $%.2f", price);
}
};
class Car: publicVehicle{
private:
intnumberCylinders;
inthorsepower;
intdisplacement;
public:
Car(void){};
Car(longaWeight, intaTopSpeed, floataPrice,
intaNumberCylinders, intaHorsepower, intaDisplacement):
Vehicle(aWeight, aTopSpeed, aPrice) {
numberCylinders=aNumberCylinders;
horsepower=aHorsepower;
displacement=aDisplacement; }
~Car(void){}
//методи доступу
int getNumberCylinders( void ) { return numberCylinders; }
int getHorsepower ( void ){ return horsepower; }
int getDisplacement( void ){ return displacement; }
// метод друку
void print( void ) {
Vehicle::print();
cout<<"\nCylinders: " << numberCylinders ;
cout«"\nHorsepower: " << horsepower;
cout«"\nDisplacement: " << isplacement <<" cubic inches";
}
};
class SportsCar: public Car {
private:
float timeToSixty; // секунди
float timeQuarterMile; // секунди
public:
SportsCar( void ){}
SportsCar( long aWeight, int aTopSpeed, float aPrice,
int aNumberCylinders, int aHorsepower,
int aDisplacement, float aTimeToSixty,
float aTimeQuarterMile ):
Car ( aWeight, aTopSpeed, aPrice, aNumberCylinders,
aHorsepower, aDisplacement ){
timeToSixty = aTimeToSixty;
timeQuarterMile = aTimeQuarterMile;
}
// методи доступу
float getTimeToSixty( void ) { return timeToSixty; }
float getTimeQuarterMile( void ) { return timeQuarterMile; }
// метод друку
void print( void ) {
Car: : print() ;
printf ( "\nTimetosixty: %.2fseconds",timeToSixty);
printft"\nQuartermile: %.2fseconds",timeQuarterMile);
}
};
Збережіть цей код у файлі "vehicle.h".
У листингу 4.2 наведена тестова програма, у якій використовується клас Vehicle та похідні від нього класи Саr і SportsCar.
Листинг 4.2.
//Тестова програма для ієрархії класу Vehicle.
#include "vehicle.h"
main () {
Vehicle aVehicle ( 150000L, 60, 300000L );
Car aCar( 3500, 100, 12000, 6, 120, 150 );
SportsCar aSportsCar( 3000, 150, 22000, 8, 250, 300, 5.9, 11.3);
cout<<"\n\n Three Kinds of Vehicles";
cout«"\n\n A railroad boxcar";
aVehicle.print() ;
cout<<"\n\n An ordinary automobile";
aCar.print();
cout<<"\n\n A High Performance Sports Car"; aSportsCar.print();
}
Збережіть цей код у файлі "vehicle.срр" і виконайте його. Зрівняєте результати з наведеними нижче:
Three Kinds of Vehicles
A railroard boxcar
Weight: 150000 pounds Top Speed: 60 mph Price: $300000.00
An ordinary automobile
Weight: 3500 pounds Top Speed: 100 mph Price: $12000.00
Cylinders: 6
Horsepower: 120
Displacement: 150 cubic inches
A High Performance Sports Car
Weight: 3000 pounds
Top Speed: 150 mph
Price: $22000.00
Cylinders: 8
Horsepower: 250
Displacement: 300 cubic inches
Time to sixty: 5.90 seconds
Quarter mile: 11.30 seconds
Віртуальні функції й поліморфізм
У попередніх прикладах зв'язування повідомлення, що посилається об'єкту, з конкретним методом (функцією-членом), здійснювалося на етапі компіляції (тобто до запуску програми). Таке раннє зв'язування в багатьох випадках бажано, тому що це дозволяє одержувати найбільш оптимальний код.
Пізнє зв'язування дозволяє асоціювати повідомлення з методом під час виконання програми. В C++ пізнє зв'язування охоплює ряд функцій-членів (методів), що називаються віртуальними функціями. Віртуальна функція оголошується в базовому або похідному класі й потім перевизначається в наслідуваних класах. Сукупність класів (підкласів), у яких визначається й перевизначається віртуальна функція називається поліморфічним кластером, асоційованим з деякою віртуальною функцією. У межах поліморфічного кластера повідомлення зв'язується з конкретною віртуальної функцією-членом під час виконання програми.
Звичайну функцію-член також можна перевизначити в наслідуваних класах. Однак без атрибута virtual така функція-член буде зв'язана з повідомленням на етапі компіляції. Атрибут virtual гарантує пізнє зв'язування в межах поліморфічного кластера.
Щоб домогтися пізнього зв'язування для об'єкта, його потрібно оголосити як покажчик або посилання на об'єкт відповідного класу. Для відкритих похідних класів покажчики й посилання на об'єкти цих класів сумісні з покажчиками й посиланнями на об'єкти базового класу (тобто, до об'єкта похідного класу можна звертатися, начебто це об'єкт базового класу).
У листингу 4.3 проілюстрований механізм створення поліморфічного кластера.
Листинг 4.3.
//Поліморфічний кластер
#include <iostream.h>
#include <string.h>
class Parent {
protected:
char *lastName;
public:
Parent ( void ) {
lastName = new char[ 5 ];
strcpy( lastName, "None" );
}
Parent ( char *aLastName ) {
lastName = new char[ strlen( aLastName )+ 1 ];
strcpy( lastName, aLastName );
}
//Конструктор копіювання
Parent( Parents aParent ) {
lastName = new char[ strlen( aParent.lastName ) +1 ];
strcpy( lastName, aParent.lastName ) ;
}
//Методи доступу
char* getLastName( void ){ return lastName;}
void setLastName( char *aName ) {
lastName = new char[ strlen ( aName ) + 1 ] ;
strcpy ( lastName, aName ) ;
}
virtual void answerName( void ) {
cout<<"My last name is "<<lastName<<"\n";
}
//Деструктор
~Parent ( void ){ delete lastName; }
};
class Child: public Parent {
protected:
char *firstName;
public:
Child ( void ) {
firstName = new char[ 5 ];
strcpy( firstName, "None" );
}
Child ( char *aLastName, char *aFirstName ): Parent ( aLastName ) {
firstName = new char[ strlen ( aFirstName ) +1 ] ;
strcpy( firstName, aFirstName );
}
// Конструктор копіювання
Child ( Child& aChild ) {
setLastName ( aChild.getLastName() );
firstName = new char[ strlenl aChild.firstName )+1 ];
strcpy( firstName, aChild.firstName );
}
// Методи доступу
char* getFirstName( void ){ return firstName; }
void setFirstName( char *aName ){
firstName = new char[ strlen( aName ) +1 ] ;
strcpy( firstName, aName );
}
// Деструктор
~Child( void ){ delete firstName; }
virtual void answerName( void ) {
Parent::answerName();
cout<<"My first name is "<<firstName<<"\n";
}
};
class Grandchild: public Child {
private:
char *grandFatherName;
public:
Grandchild( char *aLastName, char *aFirstName,
char *aGrandFatherName ): Child ( aLastName, aFirstName ) { grandFatherName = new char[ strlen( aGrandFatherName )+ 1];
strcpy( grandFatherName, aGrandFatherName.);
}
~GrandChild( void ){ delete grandFatherName; }
virtual void answerName( void ) {
Child::answerName();
cout<<"My grandfather's name is " <<grandFatherName«"\n";
}
};
main () {
Parent *family[ 3 ];
Parent *p = new Parent( "Jones" );
Child *c = new Child( "Jones", "Henry" );
Grandchild *g = new Grandchild( "Jones", "Cyntia", "Murray" );
family[ 0 ] = p; family[ 1 ] = g; family[ 2 ] = c;
for( int index = 0; index < 3; index ++ ) family! index ]->answerName();
}
Збережіть цей код у файлі "parent.срр " і виконайте його. Зрівняйте результати з наведеними нижче.
My last name is Jones
My last name is Jones
My first name is Cynthia
My grandfather' s name is Murray
My last name is Jones
My first name is Henry
Якщо в похідних класах Child і Grandchild опустити ключове слово virtual перед функцією-членом answerName результати роботи програми не зміняться. Ключове слово virtual необхідно в поліморфічному кластері тільки перед віртуальною функцією самого верхнього рівня. Для всіх інших перевизначень цієї функції ключове слово virtual необов'язкове.
Якщо ключове слово virtual забрати перед всіма функціями-членами, то в результаті одержимо:
My last name is Jones
My last name is Jones
My last name is Jones
Якщо функції-члени answerName не є віртуальними, то з family[1] і family[2] будуть зв'язані тільки поля lastName покажчиків на об'єкти g і с. Це здійснюється за допомогою раннього зв'язування.
Віртуальні функції часто використовуються при побудові дерев розбору виразів. У листингу 4.4 наведений приклад побудови дерева розбору для функції із трьома змінними х, у и z.
Листинг 4.4.
// Інтерфейс класу Tree
#include <iostream.h>
#include "node.h"
class Tree {
friend ostreamb operator <<( ostreams, const Trees );
private: Node* nodePtr;'
public:
Tree( float n );
Tree( char* ch ) ;
Tree( char*, Tree t ) ;
Tree( Tree left, char*, Tree right );
Tree( const Tree& t );
~Tree ( void );
float value () ;
float operator () ( float x, float y, float z ) ;
void operator = ( const Tree& t );
};
Тут наведені прототипи п'яти конструкторів, за допомогою яких можна побудувати дерево розбору. За допомогою першого конструктора можна побудувати дерево для виразу, що складається тільки із числових констант. За допомогою другого – для виразу, що складається із змінних. У третьому конструкторі для побудови дерева розбору можна використати унарний оператор -. У четвертому конструкторі дозволяється використати бінарні операції +, -, * і /. П'ятий конструктор – конструктор копіювання.
Листинг 4.5 містить опис класу Node з файлу “node.h”.
Листинг 4.5.
//Інтерфейс класу Node
class Node {
friend class Tree;
private:
int use; //Кількість об'єктів дерева, які посилаються на екземпляр Node
protected: Node () { use = 1; }
virtual void print ( ostreams ) {}
virtual float nodeValue { return 9.0; }
virtual ~Node( void ){}
};
Node є абстрактний суперклас. Він містить три віртуальні функції: print, nodeValue і деструктор. Ці функції перевизначаються в кожному похідному класі. Віртуальний деструктор динамічно звільняє пам'ять, раніше розподілену під об'єкт відповідного похідного класу, при виході із блоку програми, що містить цей об'єкт.
Завдання на самостійну роботу
Розробіть ієрархію класів Person-Student, об'єкти якої можуть перевіряти й поповнювати рахунок (saving) класу Student.
Розробіть програму побудови дерев розбору виразів, що включають дійсні й комплексні аргументи. Перевірте результати роботи за допомогою тестової програми.
