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

5.2 Інтерфейс класу, реалізація класу; визначення і оголошення класу

Інтерфейс класу, реалізація класу; визначення і оголошення класу

Традиційно інтерфейс і реалізацію класу розміщують у різних файлах, як це

було з оголошеннями і визначеннями об'єктів. Інтерфейс записують у заголовні

файли. Ось приклад заголовного файлу для класу точок

//Point.h

#ifndef POINT_H

#define POINT_H

class Point

{

// Атрибути

private:

double _x;

double _y;

// Методи

public:

Point (double a=0, double b=0);

Point operator+(Point);

bool operator==(Point);

double modulus ();

double phi ();

}

#endif

Ось файл реалізації

//Point.cpp

# include ”Point.h”

#include <cmath>

Point :: Point(double a, double b)

{

_x=a; _y=b;

}

Point Point::operator+(Point v)

{

Point w;

w._x = _x + v._x;

w._y = _y + v._y;

return w;

}

bool operator== (Point v)

{

return (_x == v._x) && (_y == v._y);

}

double modulus ()

{

return sgrt(_x*_x+_y*_y);

}

double Point::phi ()

{

return atan(_y/_x);

}

//Кінець Point.cpp

Буває зовсім коротке оголошення класу, наприклад,

//Payroll.h

#ifndef PAYROLL_H

@define PAYROLL_H

class EmployeeP;

class Payroll

{

…………………………………

void printCheck( EmployeeP * )

}

#endif

Без оголошення класу EmployeeP компілятор не зміг би відкомпілювати оголошення

функції printCheck , де EmployeeP використовується як тип параметру. Інтерфейс

класу EmployeeP знадобиться лише у файлі Payroll.cpp , де буде визначатися

реалізація. Туди і помістимо #include

//Payroll.cpp

#include ”Payroll.h”

#include ”EmployeeP.h’

З цієї причини в заголовному файлі не розміщують визначень, які можуть потребувати

включення додаткових файлів.

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

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

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

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

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;

}

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

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

точно.