1   2   3   4
Ім'я файлу: prog_lecture_07.pdf
Розширення: pdf
Розмір: 205кб.
Дата: 29.09.2021
скачати

7.2.3.
Конструктор за замовчуванням
У розглянутих вище прикладах об'єкти класу
TComplex створювалися за допомогою конструктора, що має
один чи два параметри. Задамося питанням: «Що відбудеться, якщо в класі не визначений жодний конструктор?» У цьому випадку об'єкт класу створюється за допомогою конструкторазазамовчуванням, що,
природно, не має параметрів. Він заповнює всі поля глобального об'єкта нулями. Якщо об'єкт є локальним, його поля будуть містити випадкові значення. Усі функції-члени класу, створеного за допомогою конструктора за замовчуванням, працюють у відповідності зі своїм описом.
Конструктор за замовчуванням
#include
class TComplex

Лекція
7
Класи
15
{
int Re;
int Im;
public:
void print(){printf("Z = %d + i*%d \n",Re,Im);};
}Z;
int main()
{
Z.print();
return 0;
}
У підсумку одержуємо наступний результат.
Z = 0.0 + i*0.0
Якщо програміст визначить у класі хоча б один конструктор, навіть без параметрів, компілятор не буде генерувати автоматичний конструктор.
7.2.4.
Конструктор копіювання
Знову створений об'єкт можна ініціалізувати умістом раніше існував об'єкта. Для цього призначений конструкторкопіювання, що має наступний вид.
ім
'
я
_
класу
(
ім
'
я
_
класу
&)
Пояснимо необхідність цього конструктора на прикладі. Уявимо собі об'єкт класу
TArray
, що є масивом цілих чисел. Він задається двома параметрами — початковою адресою (вказівником) і розміром. У наступній програмі ми створюємо масив x
, а потім — масив y
, причому новий масив повинний бути копією старого.
Використання конструктора копіювання
#include
class TArray
{
int *p;
int size;
public:
TArray(long n, int x);
TArray(TArray&);
void view();
};
int main()
{
TArray x(10,1);
x.view();
TArray y = x;
y.view();
return 0;
}
TArray::TArray(long n, int x)
{
size = n;
p = new int[size];
for (long i=0; i printf("\nAddress x = %p \n",p);
}
TArray::TArray(TArray& X)
{
size=X.size;
p = new int[size]; //
Глибоке копіювання for (long i=0; i printf("\nAddress y = %p \n",p);
}
void TArray::view()
{

Лекція
7
Класи
16
for(long i=0; i }
У цій програмі реалізоване глибокекопіювання, що має на увазі створення абсолютно самостійної копії
раніше існував об'єкта. Зверніть увагу на те, що в конструкторі копіювання поле p
не дублюється. Замість цього йому привласнюється адреса знову виділеної пам'яті, у яку копіюються інші елементи масиву. У результаті ми побачимо на екрані наступні рядки.
Адреса x = 007803A0 1 1 1 1 1 1 1 1 1 1
Адреса x = 00780340 1 1 1 1 1 1 1 1 1 1
А що відбудеться, якщо програміст не визначив конструктор копіювання? У цьому випадку буде викликаний конструкторкопіюваннязазамовчуванням. Він виконує побітове, тобто поверхневекопіювання, і замість нової
копії ми одержимо два вказівники, що посилаються на той самий адресу.
Адреса x = 007803A0 1 1 1 1 1 1 1 1 1 1
Адреса x = 007803A0 1 1 1 1 1 1 1 1 1 1
7.2.5. «
Іменовані
конструктори
»
Конструктори, як і звичайні функції, можна перевантажувати, варіюючи типи і кількість параметрів. Однак бувають ситуації, коли об'єкт класу повинний створюватися двома різними способами, що реалізуються конструкторами, що мають однакову кількість і тип параметрів.
Яскравий приклад такої ситуації — ініціалізация комплексного числа. Як відомо, комплексні числа мають дві форми запису: у декартових і полярних координатах. У першому випадку дійсна і мнима частини числа задаються абсцисою й ординатою (z = x + iy), а в другому — радіусом і кутом, між радіусом-вектором і віссю абсцис (z = rcos(
α
)+ircos(
α
)). Обидві форми запису задаються парою дійсних чисел, тому прототипи конструкторів цілком збігаються, і розрізнити їхній виклик неможливо. Для того щоб розв’язати цю дилему,
слід скористатися статичними функціями-членами, що викликають закритий конструктор і повертають створений об'єкт класу. Лапки означають, що ці функції насправді конструкторами не є, хоча виконують зовсім аналогічну задачу. Ідея цієї ідіоми полягає в тім, що статичні функції-члени викликаються ще до створення об'єкта зазначеного класу. Отже, з їхньою допомогою можна викликати закритий конструктор, передавши йому відповідний набір аргументів — декартові координати або радіус і кут.
Використання
«
іменованих конструкторів
»
#include
#include
#define PI 3.141592
class TComplex
{
public:
static TComplex Dekart(double, double);
static TComplex Euler(double, double);
void view(){printf("Z = %lf +i*%lf \n",Re,Im); }
private:
double Re;
double Im;
TComplex(double x, double y):Re(x),Im(y){}
};
int main()
{
TComplex objDekart = TComplex::Dekart(1.0,0.0);
TComplex objEuler = TComplex::Euler(1.0,PI/2);
objDekart.view();
objEuler.view();
return 0;
}
TComplex TComplex::Dekart(double x, double y)
{
return TComplex(x,y);
}
TComplex TComplex::Euler(double r, double alpha)
{

Лекція
7
Класи
17
return TComplex(r*cos(alpha), r*sin(alpha));
}
Результат роботи цієї програми приведений нижче.
Z = 1.000000 +i*0.000000
Z = 0.000000 +i*1.000000
Відзначимо два моменти: 1) конструктор оголошений закритим, тому доступ до нього можливий лише через функції інтерфейсу; 2) інтерфейсні функції є статичними, тому їх можна викликати до створення нового об'єкта.
Якби функції
Dekart()
і
Euler()
не були статичними, їх виклик став би можливий тільки після створення об'єкта. Однак оскільки конструктор оголошений закритим, створити об'єкт було б неможливо. Ключове слово static розриває це замкнуте коло.
7.2.7.
Деструктор
Деструктор знищує об'єкти класу, звільняючи зайняті ресурси. Ця функція, як і конструктор, не має
значення, що повертається, однак, у відмінність він конструктора, вона не має одного параметра. Ім'я деструктора повинне збігатися з ім'ям класу, перед яким поставлена тильда.


ім
я
_
класу
() {
тіло деструктора
}
Чудовою особливістю деструктора є той факт, що він ніколи (за одним-єдиним виключенням) не викликається явно. Просто, коли об'єкт виходить з області видимості, програма сама викликає деструктор, що виконує запропоновані дії. Якщо програміст не визначив деструктор класу, компілятор згенерує деструкторза замовчуванням.
Використання деструктора
#include
class TClass2
{
public:
int a;
TClass2():a(1){cout << "Ctor TClass2" << endl;}
TClass2() {cout << "Dtor TClass2" << endl;}
};
class TClass1
{
public:
TClass2 *p;
TClass1()
{
p = new TClass2;
cout << "Ctor TClass1" << endl;
}
TClass1()
{
delete p;
cout << "Dtor TClass1" << endl;
}
};
int main()
{
TClass1 a;
return 0;
}
Наприклад, у даній програмі в модулі main()
визначений об'єкт a
класу
TClass1
. Цей об'єкт містить усередині себе вказівник на об'єкт класу
TClass2
. Отже, першим повинний викликатися конструктор класу
TClass2
, а потім — конструктор класу
TClass1
. Знищення об'єкта a
відбувається «зсередини»: спочатку знищується об'єкт класу
TClass2
, а потім — об'єкт класу
TClass1
Ctor TClass2
Ctor TClass1
Ctor TClass2
Ctor TClass1
Якби в об'єкті класу
TClass1
об'єкт класу
TClass2
не створювався, а просто був його членом,
деструктори викликалися б у зворотному порядку.
Зворотний порядок викликів деструкторів

Лекція
7
Класи
18
#include
class TClass2
{
public:
int a;
TClass2():a(1){cout << "Ctor TClass2" << endl;}
TClass2() {cout << "Dtor TClass2" << endl;}
};
class TClass1
{
public:
TClass2 p;
TClass1()
{
cout << "Ctor TClass1" << endl;
}
TClass1()
{
cout << "Dtor TClass1" << endl;
}
};
int main()
{
TClass1 a;
return 0;
}
Тепер повідомлення мають наступний вид.
Ctor TClass2
Ctor TClass1
Ctor TClass1
Ctor TClass2
Розглянемо ситуацію, у якій необхідно явно викликати деструктор.
Явний виклик деструктора
#include
#include
class TClass
{
public:
int a;
TClass() {cout << "Ctor TClass " << endl;}
TClass(){cout << "Dtor TClass " << endl;}
};
int main()
{
char memory[sizeof(TClass)];
void *p=memory;
TClass* q = new(p) TClass;
cout << q << endl;
q->TClass();
return 0;
}
Результат роботи цієї програми виглядає так.
Ctor TClass
0x0075fdc4
Dtor TClass
Тут використаний так називаний синтаксисрозміщення. Це — різновид оператора new
, що дозволяє
створювати і розміщати об'єкт по заздалегідь заданій адресі. Спочатку ми резервуємо пам'ять для об'єкта —
масив memory
, а потім створюємо об'єкт за допомогою синтаксису розміщення. Саме тому, що ми занадто глибоко втрутилися в механізм розподілу пам'яті для нових об'єктів, нам доведеться самим подбати про її
звільнення.

Лекція
7
Класи
19
7.2.7.
Передача аргументів
і
повернення значень
Конструктори, конструктори копіювання і деструктор дозволяють продемонструвати механізм передачі
аргументів і значень функцій, що повертаються.
Об'єкти, як і звичайні змінні, можуть передаватися за значенням і за посиланням. Як це відбувається,
ілюструється наступною програмою.
Передача об
'
єктів за значенням
#include
#include
class TComplex
{
double Re;
double Im;
public:
TComplex(double x, double y):Re(x),Im(y){ printf("Ctor\n");}
TComplex(TComplex& z){ Re = z.Re; Im = z.Im; printf("Copy ctor\n");}
TComplex(){printf("Dtor\n");}
double getRe() { return Re;}
double getIm() { return Im;}
};
double modul(TComplex);
int main()
{
printf("Start main\n");
TComplex z(1,1);
printf("
Модуль z = %lf\n",modul(z));
printf("End modul\n");
printf("End main\n");
return 0;
}
double modul(TComplex z)
{
printf("Start modul\n");
return sqrt(z.getRe()*z.getRe() + z.getIm()*z.getIm());
}
На екрані з'являться наступні рядки.
Start main
Ctor
Copy ctor
Start modul
Dtor
End modul
Модуль z = 1.414214
End main
Dtor
Отже, після запуску головного модуля створюється об'єкт z
класу
TComplex
. Про це свідчить повідомлення від конструктора —
Ctor
. Потім за допомогою конструктора копіювання створюється дублікат об'єкта z
Конструктор копіювання сповіщає про це, виводячи рядок
Copy ctor
. Потім починається виконання функції
modul()
, виводиться результат і знищується тимчасова копія параметра. Про це говорить рядок
Dtor
, що виводиться на екран після виконання оператора return
. Отже, на екрані з'являється результат обчислень.
Виконання функції main()
кінцю добігає кінця, і після виконання оператора return 0
викликається деструктор класу
TComplex
, що знищує об'єкт z
Основний висновок, якому необхідно зробити, — при передачі об'єкта за значенням його копія створюється конструкторомкопіювання.
Досить поставити після імені класу
TComplex у прототипі і заголовку функції modul()
символ амперсанду, що означає передачу параметра за посиланням, — і конструктор копіювання викликатися не буде.
На екрані з'явиться наступне повідомлення.
Start main
Ctor
Start modul
End modul

Лекція
7
Класи
20
Модуль z = 1.414214
End main
Розглянемо програму, що підсумовує два комплексних числа. У цьому випадку функція sum()
повертає
копію локального об'єкта, що зберігає результат. Після передачі ця копія й об'єкт знищуються.
Повернення об
'
єкта
#include
class TComplex
{
double Re;
double Im;
public:
static int counter;
TComplex(double x, double y):Re(x),Im(y)
{ counter++;printf("Ctor %d\n",counter); }
TComplex(TComplex& z)
{ Re = z.Re; Im = z.Im; counter++; printf("Copy %d\n",counter);}
TComplex(){printf("Dtor %d\n",counter);counter--;}
double getRe() { return Re;}
double getIm() { return Im;}
void putRe(double x) { Re =x;}
void putIm(double y) { Im = y;}
};
TComplex sum(TComplex,TComplex);
int TComplex::counter = 0;
int main()
{
printf("Start main\n");
TComplex z(1,1), w(2,2), u(0,0);
u = sum(z,w);
printf("End sum\n");
printf("w + u = %lf + i*%lf\n",u.getRe(),u.getIm());
printf("End main\n");
return 0;
}
TComplex sum(TComplex u, TComplex v)
{
printf("Start \n");
TComplex w(0,0);
w.putRe(u.getRe() + v.getRe());
w.putIm(u.getIm() + v.getIm());
return w;
}
От як виглядають повідомлення від конструкторів, конструкторів копіювання і деструкторів (коментарі
додані для більшої ясності).
Start main //
Начало функції
main()
Ctor 1 //
Створюється об '
єкт z
Ctor 2 //
Створюється об '
єкт w
Ctor 3 //
Створюється об '
єкт u
Copy 4 //
Створюється копія об '
єкта z
Copy 5 //
Створюється копія об '
єкта w
Start sum //
Починається виконання функції
sum()
Ctor 7 //
Створюється локальний об '
єкт s
Copy 7 //
Створюється копія локального об '
єкта
Dtor 7 //
Знищується копія локального об '
єкта
Dtor 7 //
Знищується копія локального об '
єкта
Dtor 5 //
Знищується копія об '
єкта w
Dtor 4 //
Знищується копія об '
єкта z
End sum //
Виконання функції
sum() довершене w + u = 3.000000 + i*3.000000 //
Результат
Dtor 3 //
Знищення об '
єкта u
Dtor 2 //
Знищення об '
єкта w
Dtor 1 //
Знищення об '
єкта z

Лекція
7
Класи
21
Отже, відзначимо, що конструктор копіювання викликається в наступних ситуаціях.
1. При ініціалізації об'єкта під час оголошення.
2. При передачі об'єкта як параметр функції.
3. При поверненні об'єкта як значення, що повертається
7.3.
ДРУЖНІ
ФУНКЦІЇ
І
КЛАСИ
У попередній програмі, коли нам знадобилося скласти два комплексних числа, довелося застосувати чотири
інтерфейсні функції: getRe()
, getIm()
, putRe()
, putIm()
. Програма стала б простіше, якби функція sum()
мала прямий доступ до полів класу
TComplex
. Для цього можна оголосити цю функцію дружньою,
використовуючи ключове слово friend
Використання дружніх функцій
#include
class TComplex
{
friend TComplex sum(TComplex, TComplex);
double Re;
double Im;
public:
static int counter;
TComplex(double x, double y):Re(x),Im(y)
{ counter++;printf("Ctor %d\n",counter);}
TComplex(TComplex& z)
{ Re = z.Re; Im = z.Im; counter++; printf("Copy %d\n",counter);}
TComplex(){printf("Dtor %d\n",counter);counter--;}
void print() {printf("Z = %lf + i*%lf\n",Re,Im);}
};
TComplex sum(TComplex,TComplex);
int TComplex::counter = 0;
int main()
{
printf("Start main\n");
TComplex z(1,1), w(2,2), u(0,0);
u = sum(z,w);
printf("End sum\n");
u.print();
printf("End main\n");
return 0;
}
TComplex sum(TComplex u, TComplex v)
{
printf("Start \n");
TComplex w(0,0);
w.Re= u.Re + v.Re;
w.Im=u.Im + v.Im;
return w;
}
Дружня функція має всі права члена класу. Отже, їй доступні всі поля — як відкриті, так і закриті. Для того щоб оголосити функцію дружньої, ключове слово friend слід помістити в оголошення класу. (Клас сам оголошує своїх друзів!)
Розділимо клас
TComplex на два:
TNumber
і
TFunction
. У першому класі залишимо змінні-члени, а до другого віднесемо усі функції.
Використання дружніх функцій
-
членів
#include
class TNumber;
class TFunction
{
public:
void print(TNumber z);

Лекція
7
Класи
22
};
class TNumber
{
double Re;
double Im;
public:
static int counter;
TNumber(double x, double y):Re(x),Im(y)
{ counter++;printf("Ctor %d\n",counter);}
TNumber(TNumber& z)
{ Re = z.Re; Im = z.Im; counter++; printf("Copy %d\n",counter);}
TNumber(){printf("Dtor %d\n",counter);counter--;}
friend void TFunction::print(TNumber);
};
int TNumber::counter = 0;
int main()
{
printf("Start main\n");
TNumber z(1,1);
TFunction a;
a.print(z);
printf("End main\n");
return 0;
}
void TFunction::print(TNumber z)
{
printf("z = %lf + i*%lf\n", z.Re, z.Im);
}
У результаті одержуємо наступні повідомлення.
Start main
Ctor 1
Copy 2
z = 1.000000 + i*1.000000
Dtor 2
End main
Dtor 1
Зверніть увагу на те, що функція print()
, що належить класу
TFunction
, одержує параметр типу
TNumber
. Отже, цей тип повинний бути визначений раніше. Однак клас
TNumber оголошує дружню функцію print()
, що належить класу
TFunction
. Для розв’язання цієї проблеми в мові С++ передбачений механізм неповногооголошеннякласу.
Працюючи з дружніми функціями, потрібно враховувати наступні обмеження.
1. Вони не успадковуються.
2. Вони не можуть мати специфікатори static
і extern
3. У класі, функція-член якого є дружньою до не цілком оголошеного класу, слід розміщати тільки прототип. Реалізація функції повинна знаходитися після повного оголошення класу.
Коли усі функції деякого класу є дружніми стосовно іншого класу, можна оголосити весь клас дружнім.
Наприклад, попередню програму можна переробити в такий спосіб.
Використання дружніх класів
#include
class TNumber; //
Неповне оголошення
TFunction
{
public:
void print(TNumber z); //
Апеляція до не цілком оголошеного класу
};
class TNumber
{

Лекція

1   2   3   4

скачати

© Усі права захищені
написати до нас