Класи і об'єкти, члени класів
Класи і об'єкти, члени класів: атрибути і (класні) функції (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’
З цієї причини в заголовному файлі не розміщують визначень, які можуть потребувати
включення додаткових файлів.