Операції, для доступу до елементів класу, аналогічні операціям, для доступу до елементів структури. Операція вибору елемента крапка () комбінується для доступу до елементів об'єкта з ім'ям об'єкта або з посиланням на об'єкт. Операція вибору елемента стрілка (->) комбінується для доступу до елементів об'єкта з вказівником на об'єкт.
Програма на мал.3 використає простий клас, названий Count, з відкритим елементом даних х типу int і відкритої функцією-елементом print, щоб проілюструвати доступ до елементів класу за допомогою операції вибору елемента. Програма створює три екземпляри змінних типу Count - counter, counterRef (посилання на об'єкт типу Count) і counterPtr (покажчик на об'єкт типу Count). Змінна counterRef об’явлена, щоб
посилатися на counter, змінна counterPtr об’явлена, щоб указувати на counter. Важливо відзначити, що тут елемент даних х зроблений відкритим просто для того, щоб продемонструвати способи доступу до відкритих елементів. Як ми вже встановили, дані звичайно робляться закритими (private).// FIG6_4. CPP
// Демонстрація операцій доступу до елементів класу. і - >
#include <iostream. h>
// Простий клас Count class Count { public:
int x;
void print () { cout << x " endl; } };
main ()
{
Count counter, // створюється об'єкт counter
*counterPtr = &counter, // покажчик на counter &counterRef = counter; // посиланя на counter
cout " "Присвоювання х значення 7 і друк по імені об'єкта: ";
counter. х =7; // присвоювання 7 елементу даних х
counter. print (); // виклик функції-елемента для друку
cout << "Присвоювання х значення 8 і друк по посиланню: ";
counterRef. x = 8; // присвоювання 8 елементу даних х
counterRef. print (); // виклик функції-елемента для друку
cout << "Присвоювання х значення 10 і друк по покажчику: "; counterPtr->x = 10; // присвоювання 10 елементу даних х counterPtr->print (); // виклик функції-елемента для друку
return 0;
}
Мал.3. Доступ до даних-елементів об'єкта й функціям-елементам за допомогою імені об'єкта, посилання й вказівника на об'єкт
Присвоювання х значення 7 і друк по імені об'єкта: 7
Присвоювання х значення 8 і друк по посиланню: 8
Присвоювання х значення 10 і друк по покажчику: 10
Серед інших функцій-членів конструктор виділяється тим, що його ім'я збігається з ім'ям класу. Для оголошення конструктора за замовчуванням ми пишемо:
class Account {
public:
// конструктор за замовчуванням...
Account ();
// ...
private:
char *_name;
unsigned int _acct_nmbr;
double _balance;
};
Єдине синтаксичне обмеження, що накладає на конструктор, полягає в тому, що він не повинен мати тип значення, що повертає, навіть void.
Кількість конструкторів в одного класу може бути будь-яким, аби тільки всі вони мали різні списки формальних параметрів.
Звідки ми знаємо, скільки і які конструктори визначити? Як мінімум, необхідно присвоїти початкове значення кожному члену, що це потребує. Наприклад, номер рахунку або задається явно, або генерується автоматично таким чином, щоб гарантувати його унікальність. Припустимо, що він буде створюватися автоматично. Тоді ми повинні дозволити ініціализувати два члени, що залишилися _name і _balance:
Account (const char *name, double open_balance);
Об'єкт класу Account, ініціалізуємий конструктором, можна об’явити в такий спосіб:
Account newAcct ("Mikey Matz", 0);
Якщо ж є багато рахунків, для яких початковий баланс дорівнює 0, то корисно мати конструктор, що задає тільки ім'я власника й автоматично ініцілізує _balance нулем. Один зі способів зробити це - надати конструктор виду:
Account (const char *name);
Інший спосіб - включити в конструктор із двома параметрами значення за замовчуванням, рівне нулю:
Account (const char *name, double open_balance = 0.0);
Обоє конструктора володіють необхідної користувачеві функціональністю, тому обоє рішення прийнятні. Ми воліємо використати аргумент за замовчуванням, оскільки в такій ситуації загальне число конструкторів класу скорочується.
Потрібно чи підтримувати також завдання одного лише початкового балансу без вказівки імені клієнта? У цьому випадку специфікація класу явно забороняє це. Наш конструктор із двома параметрами, з яких другий має значення за замовчуванням, надає повний інтерфейс для задання початкових значень тих членів класу Account, які можуть бути ініціалізовані користувачем:
class Account {
public:
// конструктор за замовчуванням...
Account ();
// імена параметрів в оголошенні вказувати необов'язково
Account (const char*, double=0.0);
const char* name () { return name; }
// ...
private:
// ...
};
Нижче наведені два приклади правильного визначення об'єкта класу Account, де конструкторові передається один або два аргументи:
int main ()
{
// правильно: в обох випадках викликається конструктор
// с двома параметрами
Account acct ("Ethan Stern");
Account *pact = new Account ("Michael Lieberman", 5000);
if (strcmp (acct. name (), pact->name ()))
// ...
}
C++ вимагає, щоб конструктор застосовувався до певного об'єкта до його першого використання. Це означає, що як для acct, так і для об'єкта, на який указує pact, конструктор буде викликаний перед перевіркою в інструкції if.
Компілятор перебудовує нашу програму, вставляючи виклики конструкторів.
От як, цілком ймовірно, буде модифіковане визначення acct усередині main ():
// псевдокод на C++,
// іллюструючий внутрішню вставку конструктора
int main ()
{
Account acct;
acct. Account:: Account ("Ethan Stern", 0.0);
// ...
}
Звичайно, якщо конструктор визначений як вбудований, то він підставляється в точці виклику.
Обробка оператора new трохи складніше. Конструктор викликається тільки тоді, коли він успішно виділив пам'ять. Модифікація визначення pact у трохи спрощеному виді виглядає так:
// псевдокод на C++,
// іллюструючий внутрішню вставку конструктора при обробці new
int main ()
{
// ...
Account *pact;
try {
pact = _new (sizeof (Account));
pact->Acct. Account:: Account (
"Michael Liebarman", 5000.0);
}
catch (std:: bad_alloc) {
// оператор new закінчився невдачею:
// конструктор не викликається
}
// ...
}
Існує три в загальному випадку еквівалентні форми завдання аргументів конструктора:
// загалом ці конструктори еквівалентні
Account acct1 ("Anna Press");
Account acct2 = Account ("Anna Press");
Account acct3 = "Anna Press";
Форма acct3 може використовуватися тільки при завданні єдиного аргументу. Якщо аргументів два або більше, рекомендовано користуватися формою acct1, хоча припустимо й acct2.
// рекомендує форма, що, виклику конструктора
Account acct1 ("Anna Press");
Визначати об'єкт класу, не вказуючи списку фактичних аргументів, можна в тому випадку, якщо в ньому або об’явлений конструктор за замовчуванням, або взагалі немає об’яв конструкторів. Якщо в класі об’явлений хоча б один конструктор, то не дозволяється визначати об'єкт класу, не викликаючи жодного з них. Зокрема, якщо в класі визначений конструктор, що приймає один або більше параметрів, але не визначений конструктор за замовчуванням, то в кожному визначенні об'єкта такого класу повинні бути присутнім необхідні аргументи. Можна заперечити, що не має змісту визначати конструктор за замовчуванням для класу Account, оскільки не буває рахунків без імені власника. У переглянутій версії класу Account такий конструктор виключений:
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 {