на тему рефераты Информационно-образоательный портал
Рефераты, курсовые, дипломы, научные работы,
на тему рефераты
на тему рефераты
МЕНЮ|
на тему рефераты
поиск
Розробка власного класу STRING
i>class Account {

public:

// імена параметрів в оголошенні вказувати необов'язково

Account (const char*, double=0.0);

const char* name () { return name; }

// ...

private:

// ...

};

Тепер при оголошенні кожного об'єкта Account у конструкторі обов'язково треба вказати як мінімум аргумент типу C-рядка, але це швидше за все безглуздо. Чому? Контейнерні класи (наприклад, vector) вимагають, щоб для класу елементів, що поміщають у них, був або заданий конструктор за замовчуванням, або взагалі ніяких конструкторів. Аналогічна ситуація має місце при виділенні динамічного масиву об'єктів класу. Так, що інструкція викликала б помилку компіляції для нової версії Account:

// помилка: потрібен конструктор за замовчуванням для класу

Account *pact = new Account [new_client_cnt];

На практиці часто потрібно задавати конструктор за замовчуванням, якщо є які-небудь інші конструктори.

А якщо для класу немає розумних значень за замовчуванням? Наприклад, клас Account вимагає задавати для будь-якого об'єкта прізвище власника рахунку.

У такому випадку найкраще встановити стан об'єкта так, щоб було видно, що він ще не ініціалізований коректними значеннями:

// конструктор за замовчуванням для класу Account

inline Account:: Account () {

_name = 0;

_balance = 0.0;

_acct_nmbr = 0;

}

Однак у функції-члени класу Account прийдеться включити перевірку цілісності об'єкта перед його використанням.

Існує й альтернативний синтаксис: список ініціалізації членів, у якому через кому вказуються імена й початкові значення. Наприклад, конструктор за замовчуванням можна переписати в такий спосіб:

// конструктор за замовчуванням класу Account з використанням

// списку ініціалізації членів

inline Account::

Account ()

: _name (0),

_balance (0.0), _acct_nmbr (0)

{}

Такий список допустимо тільки у визначенні, але не в оголошенні конструктора. Він міститься між списком параметрів і тілом конструктора й відділяється двокрапкою. От як виглядає наш конструктор із двома параметрами при частковому використанні списку ініціалізації членів:

inline Account::

Account (const char* name, double opening_bal)

: _balance (opening_bal)

{

_name = new char [strlen (name) +1];

strcpy (_name, name);

_acct_nmbr = get_unique_acct_nmbr ();

}

Конструктор не можна об'являти із ключовими словами const або volatile, тому наведені записи невірні:

class Account {

public:

Account () const; // помилка

Account () volatile; // помилка

// ...

};

Це не означає, що об'єкти класу з такими специфікаторами заборонено ініціалізувати конструктором. Просто до об'єкта застосовується підходящий конструктор, причому без обліку специфікаторів в оголошенні об'єкта. Константність об'єкта класу встановлюється після того, як робота з його ініціалізації завершена, і пропадає в момент виклику деструктора. Таким чином, об'єкт класу зі специфікатором const уважається константним з моменту завершення роботи конструктора до моменту запуску деструктора. Те ж саме ставиться й до специфікатора volatile.

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

// у якімсь заголовному файлі

extern void print (const Account &acct);

// ...

int main ()

{

// перетворить рядок "oops" в об'єкт класу Account

// за допомогою конструктора Account:: Account ("oops", 0.0)

print ("oops");

// ...

}

За замовчуванням конструктор з одним параметром (або з декількома - за умови, що всі параметри, крім першого, мають значення за замовчуванням) відіграє роль оператора перетворення. У цьому фрагменті програми конструктор Account неявно застосовується компілятором для трансформації літерального рядка в об'єкт класу Account при виклику print (), хоча в даній ситуації таке перетворення не потрібно.

Ненавмисні неявні перетворення класів, наприклад трансформація "oops" в об'єкт класу Account, виявилися джерелом помилок, що виявляють важко. Тому в стандарт C++ було додано ключове слово explicit, що говорить компіляторові, що такі перетворення не потрібні:

class Account {

public:

explicit Account (const char*, double=0.0);

};

Даний модифікатор застосуємо тільки до конструктора.

1.8 Конструктор копіювання

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

Часто почленна ініціалізація не забезпечує коректну дію класу. Тому ми явно визначаємо конструктор копіювання. У нашому класі Account це необхідно, інакше два об'єкти будуть мати однакові номери рахунків, що заборонено специфікацією класу.

Конструктор копіювання приймає як формальний параметр посилання на об'єкт класу (рекомендовано зі специфікатором const). Його реалізація:

inline Account::

Account (const Account &rhs)

: _balance (rhs. _balance)

{

_name = new char [strlen (rhs. _name) + 1];

strcpy (_name, rhs. _name);

// копіювати rhs. _acct_nmbr не можна

_acct_nmbr = get_unique_acct_nmbr ();

}

Коли ми пишемо:

Account acct2 (acct1);

компілятор визначає, чи оголошений явний конструктор копіювання для класу Account. Якщо він оголошений і доступний, то він і викликається; а якщо недоступний, то визначення acct2 вважається помилкою. У випадку, що коли конструктор копіювання не об'явлений, виконується почленна ініціалізація за замовчуванням. Якщо згодом об'явлення конструктор копіювання буде додане або вилучене, ніяких змін у програми користувачів вносити не прийдеться. Однак перекомпілювати їх все-таки необхідно.

1.9 Деструктор класу

Одна із цілей, що ставляться перед конструктором, - забезпечити автоматичне виділення ресурсу. Ми вже бачили в прикладі із класом Account конструктор, де за допомогою оператора new виділяється пам'ять для масиву символів і привласнюється унікальний номер рахунку. Можна також представити ситуацію, коли потрібно одержати монопольний доступ до поділюваної пам'яті або до критичної секції потоку. Для цього необхідна симетрична операція, що забезпечує автоматичне звільнення пам'яті або повернення ресурсу після завершення часу життя об'єкта, - деструктор. Деструктор - це спеціальна обумовлена користувачем функція-член, що автоматично викликається, коли об'єкт виходить із області видимості або коли до покажчика на об'єкт застосовується операція delete. Ім'я цієї функції створено з імені класу з попереднім символом “тильда" (~). Деструктор не повертає значення й не приймає ніяких параметрів, а отже, не може бути перевантажений.

Хоча дозволяється визначати кілька таких функцій-членів, лише одна з них буде застосовуватися до всіх об'єктів класу. От, наприклад, деструктор для нашого класу Account:

class Account {

public:

Account ();

explicit Account (const char*, double=0.0);

Account (const Account&);

~Account ();

// ...

private:

char *_name;

unsigned int _acct_nmbr;

double _balance;

};

inline

Account:: ~Account ()

{

delete [] _name;

return_acct_number (_acct_nnmbr);

}

Зверніть увагу, що в нашому деструкторі не скидаються значення членів:

inline Account:: ~Account ()

{

// необхідно

delete [] _name;

return_acct_number (_acct_nnmbr);

// необов'язково

_name = 0;

_balance = 0.0;

_acct_nmbr = 0;

}

Робити це необов'язково, оскільки відведена під члени об'єкта пам'ять однаково буде звільнена. Розглянемо наступний клас:

class Point3d {

public:

// ...

private:

float x, y, z;

};

Конструктор тут необхідний для ініціалізації членів, що представляють координати точки. Чи потрібний деструктор? Немає. Для об'єкта класу Point3d не потрібно звільняти ресурси: пам'ять виділяється й звільняється компілятором автоматично на початку й наприкінці його життя.

В загальному випадку, якщо члени класу мають прості значення, скажімо, координати точки, то деструктор не потрібний. Не для кожного класу необхідний деструктор, навіть якщо в нього є один або більше конструкторів. Основною метою деструктора є звільнення ресурсів, виділених або в конструкторі, або під час життя об'єкта, наприклад звільнення пам'яті, виділеної оператором new.

Але функції деструктора не обмежені тільки звільненням ресурсів. Він може реалізовувати будь-яку операцію, що за задумом проектувальника класу повинна бути виконана відразу по закінченні використання об'єкта. Так, широко розповсюдженим прийомом для виміру продуктивності програми є визначення класу Timer, у конструкторі якого запускається та або інша форма програмного таймера. Деструктор зупиняє таймер і виводить результати вимірів. Об'єкт даного класу можна умовно визначати в критичних ділянках програми, які ми хочемо профілювати, у такий спосіб:

{

// початок критичної ділянки програми

#ifdef PROFILE

Timer t;

#endif

// критична ділянка

// t знищується автоматично

// відображається витрачений час...

}

Щоб переконатися в тім, що ми розуміємо поводження деструктора (та й конструктора теж), розберемо наступний приклад:

(1) #include "Account. h"

(2) Account global ("James Joyce");

(3) int main ()

(4) {

(5) Account local ("Anna Livia Plurabelle", 10000);

(6) Account &loc_ref = global;

(7) Account *pact = 0;

(8)

(9) {

(10) Account local_too ("Stephen Hero");

(11) pact = new Account ("Stephen Dedalus");

(12) }

(13)

(14) delete pact;

(15) }

Скільки тут викликається конструкторів? Чотири: один для глобального об'єкта global у рядку (2); по одному для кожного з локальних об'єктів local і local_too у рядках (5) і (10) відповідно, і один для об'єкта, розподіленого в купі, у рядку (11). Ні об'явлення посилання loc_ref на об'єкт у рядку (6), ні об'явлення вказівника pact у рядку (7) не приводять до виклику конструктора. Посилання - це псевдонім для вже сконструйованого об'єкта, у цьому випадку для global. Вказівника також лише адресує об'єкт, створений раніше (у цьому випадку розподілений у купі, рядок (11)), або не адресує ніякого об'єкта (рядок (7)).

Аналогічно викликаються чотири деструктори: для глобального об'єкта global, об'явленого в рядку (2), для двох локальних об'єктів і для об'єкта в купі при виклику delete у рядку (14). Однак у програмі немає інструкції, з якої можна зв'язати виклик деструктора. Компілятор просто вставляє ці виклики за останнім використанням об'єкта, але перед закриттям відповідної області видимості.

Конструктори й деструктори глобальних об'єктів викликаються на стадіях ініціалізації й завершення виконання програми. Хоча такі об'єкти нормально поводяться при використанні в тім файлі, де вони визначені, але їхнє застосування в ситуації, коли виробляються посилання через границі файлів, стає в C++ серйозною проблемою.

Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15



© 2003-2013
Рефераты бесплатно, курсовые, рефераты биология, большая бибилиотека рефератов, дипломы, научные работы, рефераты право, рефераты, рефераты скачать, рефераты литература, курсовые работы, реферат, доклады, рефераты медицина, рефераты на тему, сочинения, реферат бесплатно, рефераты авиация, рефераты психология, рефераты математика, рефераты кулинария, рефераты логистика, рефераты анатомия, рефераты маркетинг, рефераты релиния, рефераты социология, рефераты менеджемент.