Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курс лекцій.doc
Скачиваний:
15
Добавлен:
03.11.2018
Размер:
1.12 Mб
Скачать

4. Конструктори та деструктори.

4.1 Поняття про конструктори.

Розглянемо описаний вище клас Ttime. Оголосимо екземпляр класу v:

TTime v;

При такому оголошеннi відбувається лише виділення пам’яті для відповідного екземпляра класу. Програміст повинен сам подбати про ініціалізацію полів. Якщо уявити собі наявність кількох класів, що мають складну структуру, велику кількість полів даних, то задача акуратної та правильної ініціалізації полів є досить громіздкою та помилконебезпечною. Адже в такому випадку необхідно тримати в пам’яті всю структуру цих класів. В деяких випадках при створенні об’єкту необхідним є і виділення пам’яті. У зв’язку з цим в С++ введені функції-члени, що мають особливий синтаксис , завдання та назву. Це конструктори. Вони служать для ініціалізації полів даних, виділення пам’яті, якщо це необхідно, та виконання інших дій, які можуть бути потрібними при створенні екземпляра об’єкту. Саме слово конструктор - від англійського слова constuct -будувати, конструювати.

Специфіка синтаксису задання конструктора полягає в тому, що його ім’я обов’язково повинне співпадати з іменем класу, в якому він оголошується. При оголошенні конструктора не вказується тип значення, яке він вертає (ключове слово типу результату відсутнє). Конструктор може мати довільну кількість параметрів ( навіть не мати їх взагалі). Його тіло може бути описаним як в межах формального опису класу, так і за його межами (як і для звичайної функції-члена). Конструктор викликається автоматично системою С++ у випадку, коли, виконуються умови його виклику. Для того, щоб викликався конструктор при оголошенні екземпляра об’єкту, необхідно після ідентифікатора змінної-екземпляра в круглих дужках вказати список фактичних параметрів конструктора (так, ніби викликається функція з іменем змінної-екземпляра та параметрами, які повинен мати конструктор). Якщо параметри відсутні, після ідентифікатора нічого не ставиться.

Приклад:

clas TT {

private:

long dt;

char*dts;

publiс:

TT(int, int);

};

TT ::TT(int a, int n)

{dt=a;

dts=(char*)malloc(n*sizeof(char));

}

main()

{

TT t2(1,15);

// . . .

}

При оголошенні TT t2(1,15); зразу буде викликаний конструктор класу, який ініціалізує поле dt та виділяє пам’ять. Умовою його виклику є відповідність параметрів, вказаних в дужках, сигнатурі конструктора. Якщо в нашому прикладі оголосити змінну так: TT t2(1,1); , то буде видане повідомлення про помилку. Адже конструктора з відповідною сигнатурою не існує.

В класах може бути оголошено кiлька конструкторiв в межах правил перевантаження функцій, допускається використання параметрів по замовчуванню. Cпецифiчну роль вiдiграють конструктори, якi мають порожнiй список параметрiв. Такi конструктори називаються конструкторами по замовчуванню. Якщо такий конструктор в класi є, то вiн завжди автоматично викликається при входi екземплера класу в область видимостi.

Приклад:

class TT {

private:

long dt;

char*dts;

publiс:

TT();

};

TT ::TT(void)

{

dt=0;

dts=(char*)malloc(10*sizeof(char));

}

main()

{

TT v;

// . . . .

}

В момент оголошення змінної-екземпляра класу v відбувається виклик конструктора по замовчуванню та відбуваються всі дії, які він виконує.

Відмітимо, що відповідні конструктори класів викликаються і в ситуаціях, коли необхідним є неявне перетворення типів (див. п.12.2).

Якщо конструктор вiдсутнiй, то такий об'єкт можна iнiцiалiзувати шляхом присвоєння йому об'єкта цього ж класу.

TTime today;

.

:

TTime d=today;

При цьому вiдбувається побiтове (до версії 2.0) чи почленне рекурсивне копiювання. Слід обережно працювати з класами, які мають конструктори, оскільки при присвою-ванні екземплярів таких класів можуть виникати помилкові ситуації. Розглянемо наступний приклад:

class IntArray

{

private:

int *data;

unsigned size;

public:

IntArray(void);

IntArray(unsigned mySize);

void put(unsigned index, int value);

int get(unsigned index);

};

int IntArray::get(unsigned index)

{

if (index<size) return data[index];

else return 0;

}

void IntArray:: put(unsigned index, int value)

{ if (index<size) data[index]=value; }

IntArray:: IntArray(void)

{

data=new int[100];

size=100;

for(int i=0;i<size;i++) data[i]=0;

}

IntArray IntArray(unsigned mySize)

{

data=new int[mySize];

size=mySize;

for(int i=0;i<size;i++) data[i]=0;

}

main()

{

IntArray myArray(200);

IntArray second(200);

second=myArray;

myArray.put(10,-50);

cout<<second.get(10);

}

В результаті буде надруковане значення 10-того елемента масиву -50. Тобто для даних об’єктів виділяється одна і та ж область пам’яті. Цю проблему можна легко вирішити, якщо перевантажити оператор присвоювання (див. розділ “Перевантаження операторів” ).

Розглянемо наступний фрагмент програми:

Ttime t1;

Ttime t2(1,2,1998);

t1=t2;

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

Відмітимо, що якщо в класі немає жодного конструктора, компілятор сам створює функцію-конструктор класу. Цей конструктор викликається в момент створення екземпляра класу (виділення для нього пам’яті) і виконує ініціалізацію вказівника this.