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

5.1 Класи і об'єкти, члени класів

Класи і об'єкти, члени класів: атрибути і (класні) функції (function member), конструктори, деструктори, позакласні функції, утиліти класів; область видимості і права доступу: відкриті і закриті члени, порушення прав доступу, дружні функції

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

кодом:

employee1.name[3]=’о’;

employee1.name[7]=’в’;

employee1.name[8]=’а’;

employee1.name[9]=’\0’;

Поки що структури зовсім прозорі. Вони інкапсулюють свої члени, але дозволяють

доступ до них усім, хто бачить самі структури. Використання, а ще гірше, несанкціонована

зміна атрибутів залишаються неконтрольованими. В найбільш послідовному виді

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

Як і структура, клас складається із членів: атрибутів і методів. Він ділиться

на відкриту ( public ) і закриту ( private )

частини. Головна відмінність від структур полягає у правилі замовчування. Відкриту

частину класу визначають явно. Все, що не позначено відкритим, закрите. Структури

звичайно відкриті, хоча можливе визначення закритої частини структури. Цим

користуються рідко.

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 ();

}

Одночасно ми доповнили визначення точки методом її ініціалізації. Створення

об'єкту — екземпляру класу ( class instance ) тепер інкапсулюється

у окрему програмну одиницю — метод, названий конструктором .

Point :: Point(double a, double b) {_x=a; _y=b;}

Визначення Point тепер необхідно доповнити оголошенням конструктора (зверніть

увагу на замовчувані значення його параметрів). Екземпляри точок створюються

тепер у такий спосіб

Point u(); //u._x == 0, u._y == 0

Point v(1.0, 2.0);

Point *p = new Point(1.0, -1.0);

Те ж саме стосується класу Employee

const short kMaxNameSize = 20;

class Employee

{

// Атрибути

private:

char name [kMaxNameSize];

unsigned int id;

float salary;

// Методи

public:

Employee(char*, unsigned int, float);

void printEmployee();

}

Тепер доступ зовні до атрибутів закрито, а ось реалізація конструктора

Employee::Employee( char *n, unsigned int i, float s )

{

strncpy(name, n, kMaxNameSize );

name[kMaxNameSize-1]='\0';

id = i;

salary = s;

cout << "Зареєстровано службовця” << "\n";

printEmployee();

}

Метод, симетричний конструктору, називається деструктором . Навіть

якщо у визначенні класу конструктори і деструктори відсутні (це можливо), система

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

Тому конструктор і деструктор є завжди. Щоб проблему стало краще видно, розглянемо

трохи складніший варіант структури EmployeeP , в якому передбачено виділення

динамічної пам'яті

class EmployeeP

{

// Атрибути

private:

char * name;

unsigned int id;

float salary;

// Методи

public:

EmployeeP(char*, unsigned int, float);

~EmployeeP();

void printEmployee();

};

Тепер пам'ять для прізвища виділяється в конструкторі

EmployeeP::EmployeeP( char *n, unsigned int i, float s )

{

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

strcpy(name, n);

id = i;

salary = s;

};

Знову розглянемо два визначення

EmployeeP employee1( "Клічко", 1, 300.0 );

EmployeeP *employee2;

employee2 = new EmployeeP( "Ребров", 2, 200.0 );

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

виході з блоку. Об'єкт employee2 динамічний, його необхідно знищувати явно

інструкцією delete . Але в обох випадках рядок name продовжує зберігатися в

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

неявно в момент закінчення життя відповідного об'єкта.

EmployeeP::~EmployeeP()

{

delete [] name;

}

Поява класів не ліквідує звичайних функцій, не інкапсульованих у класах. Образно

їх можна називати позакласними функціями ( non-member function ).

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

кінцевими точками:

double prod (Point u, Point v)

{

return u.modulus()*v.modulus()*cos(u.phi()-v.phi());

}

Позакласні функції, параметрами яких служать екземпляри певних класів, називають утилітами цих

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

клас трикутників використовує (агрегує) клас точок

class Triangle

{

Point a, b, c;

public:

Triangle (Point, Point, Point);

perimeter();

square();

}

Звичайно для атрибутів у класах передбачають методи доступу — парні функції

запису і читання атрибуту. Скажімо для класу точок це були б функції

void Point:: setX ( double );

double Point:: readX();

void Point:: setY ( double );

double Point:: readY();

або

double& Point:: X ( double );

double& Point:: Y ( double );

Загальнодоступність методів доступу корисна не завжди. Скажімо візьмемо задачу

нарахування зарплати. Відомість про зарплату кожного співробітника є конфіденційною

інформацією, а тому в клас не доцільно мати відкритий метод читання окладу,

не кажучи вже про метод встановлення окладу. Припустимо, що за нарахування

запрали відповідатиме метод printCheck класу Payroll

void Payroll:: printCheck( EmployeeP *payee )

{

cout << "Pay $" << payee->getSalary()

<< " to the order of "

<< payee->getName() << "...\n\n";

}

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

дружньої функції. Нею може бути метод іншого класу або навіть позакласна функція,

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

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

вибрати потрібну. Дружній об'єкт одержує винятковий доступ до закритої частини

класу. Друзів обирають обережно!

class EmployeeP

{

friend void Payroll :: printCheck(EmployeeP *);

// також:

friend class Employer;

// крім них:

friend bool operator>

(const EmployeeP&, const EmployeeP&);

// Атрибути

private:

char * name;

unsigned int id;

float salary;

// Методи

EmployeeP(char*, unsigned int, float);

float getSalary();

public:

~EmployeeP();

void printEmployee();

char* getName();

};

Ось операція порівняння службовців за окладами

bool operator>(const EmployeeP& e1, const EmployeeP& e2)

{

return e1.getSalary()>e2.getSalary();

}

Доповніть клас EmployeeP атрибутом, що зберігатиме дату зарахування на роботу

і визначіть порівняння службовців за стажем роботи.

Закритим може виявитися навіть конструктор. Можна домовитися, що створення

службовця є операцією, що вимагає особливих повноважень. Ось чому ми перенесли

конструктор у закриту частину та призначили дружнім класом клас Employer —

службовця на роботу приймає роботодавець

EmployeeP * Employer ::

createEmployee( char *n, unsigned int i, float s )

{

EmployeeP *anEmployee;

anEmployee = new EmployeeP (n, i, s);

return anEmployee ;

}

Тепер ми проаналізували поняття абстракції, інкапсуляції, включаючи концепцію приховання

інформації ( information hiding ). Клас Employee служить програмною

абстракцією службовця, клас Employer — працедавця, клас Payroll — відділу

зарплати. Кожен клас інкапсулює в собі дані і методи, приховуючи реалізацію

методів і зображення даних. Частина даних видима або доступна через відповідні

методи доступу всім, інша — лише привілейованим об'єктам.