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;
}
Як бачимо при ініціалізації та присвоєнні об'єктів відбувається тонка робота
з пам'яттю. Корисно уявляти собі місце розташування кожного об'єкту абсолютно
точно.