Cтатичні члени класів і статичні класні функції


Створення і ініціалізація об'єктів, довизначення конструкторів, замовчуваний конструктор, копіювання, поверхневе і глибоке копіювання, ініціалізація і присвоєння, копіювальний конструктор

Створення і ініціалізація об'єктів, довизначення конструкторів, замовчуваний конструктор, копіювання, поверхневе і глибоке копіювання, ініціалізація і присвоєння, копіювальний конструктор

 

Упродовж цього підрозділу ми будемо переписувати визначення декількох класів,

наводячи в них все більше лоску. Один з цих класів

 

class Date

{

int _day, _month, _year;

public:

void showDate();

}

void Date::showDate()

{

cout<<_day<<':'<<_month<<':'<<_year<<endl;

}

 

Поки що з цим класом можна зробити небагато чого, наприклад, в результаті

виконання програми

 

int main()

{

Date t;

t.showDate();

return 0;

}

 

 

одержимо, швидше всього, три незрозумілих числа. Компілятор для проформи доповнить

клас замовчуваним конструктором з порожнім тілом. Визначенням Date t конструктор

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

і методами. Корисно було б подумати, як проініціалізувати атрибути. Ясно, що

звичайна ініціалізація виду int _day=1; не підходить. Ініціалізацією атрибутів

займається конструктор, оскільки створення об'єкту відбувається динамічно і

тому не підконтрольне компілятору. Доповнимо клас конструктором

 

Date :: Date (int d,int m, int y)

{

_day=d; _month=m; _year=y;

}

 

 

або трохи іншим, у якому значення атрибутів задаються ініціалізацією, а не

присвоєнням,

 

Date :: Date (int d, int m, int y) :

_day(d), _month(m), _year(y)

{

};

 

але не обома одночасно. та відповідно інтерфейс

 

class Date

{

int _day, _month, _year;

public:

Date (int d, int m, int y);

void showDate();

}

 

 

Тепер визначення Date t; стане неможливим. Клас містить власний конструктор,

а тому компілятор не стане генерувати замовчуваний конструктор. Приклад вживання

класу доведеться переписати на зразок

 

int main()

{

Date t(31,1,2003);

t.showDate();

return 0;

}

 

Але ж визначення Date t; не було таким вже безглуздим. В такий спосіб можна

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

Date може з'явитися ще один конструктор

 

 

Date :: Date ()

{

// Деталі реалізації системного часу в додатку TIMES.C

struct tm * today =new tm;

time_t timer;

 

time( &timer );

today = gmtime(&timer);

 

_day = today->tm_mday;

_month = ++(today->tm_mon);

_year = today->tm_year+=1900;

 

cout<<”Сьогодні ”

<<today->tm_mday<<':'

<<today->tm_mon<<':'

<<today->tm_year<<endl;

}

 

 

Можливі інші варіанти: Date (int d, int m) — дата в поточному році; Date (int d)

— день в поточному році і поточному місяці; Date (const char *cdate) —

дата, задана у символьному форматі, наприклад, May 03 2003. Подумайте,

як реалізувати останній конструктор.

 

Тепер ми можемо створювати дати різними способами

 

Date today;

Date t(31, 1, 2003);

Date s(31, 1);

Date u(31);

Date v(”Jan 31 2003”);

Date w = u;

Date x (w);

Date y; y = x;

 

 

Дві останні ініціалізації та присвоєння реалізуються компілятором шляхом поверхневого

поатрибутного копіювання об'єктів. Його можна запрограмувати явно, використовуючи

ще один тип конструктора, що зветься конструктором копіювання ( copy

constructor )

 

Date::Date (const Date& baseDate)

{

cout<<"Copy constructor Date ";

_day = baseDate._day;

_month = baseDate._month;

_year = baseDate._year;

}

 

 

Для нашого випадку досить поатрибутного конструктора копіювання, але наступний

приклад покаже проблеми, що виникають при поверхневому копіюванні. Поки що

підведемо попередні підсумки, ще раз переписавши інтерфейс

 

class Date

{

int _day, _month, _year;

public:

Date (int d, int m, int y);

Date (int d, int m);

Date (int d);

Date ();

Date (const char *cdate);

Date (const Date&);

void showDate();

}

 

 

Розглянемо приклад, для якого поверхневого копіювання недостатньо

 

class RandomVector

{

int size;

int *v;

int position;

public:

RandomVector(int s) ;

RandomVector(const RandomVector&);

~RandomVector() ;

};

 

 

Клас вектору випадкових величин RandomVector призначається для підтримки статистичних

експериментів. Генерується випадкова вибірка v заданої довжини size , в якій

потім атрибут position використовується як індекс поточного елементу.

 

RandomVector::RandomVector(int s) :

size(s), position(0)

{

v= new int[size];

for(int i=0; i< } *(v+i)="rand();" i++)>

Оскільки в класі використовується динамічна пам'ять, потрібен явний деструктор

 

RandomVector::~RandomVector ()

{

delete [] v;

}

 

 

Копіювальний конструктор повинен забезпечувати глибоке копіювання, яке знову

ж потребує динамічної пам'яті

 

RandomVector :: RandomVector (const RandomVector& base):

 

size(base.size), position(base.position)

{

v=new int[size];

for(int i=0; i< } *(v+i)="*(base.v+i);" i++)>

Ось приклади використання випадкових векторів

 

 

RandomVector u1(100); //Звичайний конструктор

RandomVector u2=u1; //Конструктор копіювання

 

 

А от із спроби присвоїти значення одного вектора іншому нічого доброго не

вийде

 

RandomVector u3(100);

u3 = u2; //Error

 

 

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

(детальніше про довизначення операторів йтиметься у підрозділі 5.8) та відповідно

поповнивши інтерфейс оголошенням присвоєння

 

RandomVector&

RandomVector::operator=(const RandomVector& t)

{

if (size!=t.size)

{

delete[]v;

size=t.size;

v=new int[size];

}

position=t.position;

for(int i=0; i<size; i++)

*(v+i)=*(t.v+i);

return *this;

}

 

 

Як бачимо при ініціалізації та присвоєнні об'єктів відбувається тонка робота

з пам'яттю. Корисно уявляти собі місце розташування кожного об'єкту абсолютно

точно.