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


Класи і об'єкти, члени класів: атрибути і (класні) функції (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 — відділу

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

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

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

 

 

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’

 

 

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

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