C++. Класи. Частина 1. Поняття класу. Оголошення типу даних “клас”. Об’єкт класу. Інкапсуляція даних в класі

C++. Класи. Частина 1. Поняття класу. Оголошення класу. Об’єкт класу. Класи в середовищі CLR. Інкапсуляція даних в класі


Зміст



1. Поняття класу. Загальна форма оголошення класу

У мові програмування C++ поняття “клас” лежить в основі об’єктно-орієнтованого програмування (ООП). Об’єктно-орієнтоване програмування виникло як удосконалення процедурно-орієнтованого програмування.

В свій час, процедурно-орієнтоване програмування вже не забезпечувало необхідної якості написання великих програмних систем. Програми розбивались (структурувались) на модулі, виникала повторюваність програмного коду в різних модулях (частинах) програми, ускладнювалось тестування (пошук помилок), знижувалась надійність програми.

В основі ООП лежать поняття “об’єкт” та “клас”. У мові програмування об’єкт – це змінна типу “клас”. Клас описує дані та методи (функції), які будуть використовуватись об’єктом цього класу. Кожен клас описує логічно-завершену одиницю програми. Інкапсуляція даних та методів їх обробки в межах класу дозволяє покращити структурованість програмних систем. Це в свою чергу зменшує ризик виникнення “невидимих” логічних помилок. Використання спадковості та поліморфізму у класах дозволяє уникнути повторюваності програмного коду та зручно впорядкувати складні виклики методів, що об’єднані між собою в ланцюг.

Клас визначає формат (опис) деяких даних та роботу (поведінку) над цими даними. З оголошення класу можна отримати різну кількість об’єктів класу (змінних типу “клас”). Кожен об’єкт класу визначається конкретним (на даний момент) значенням внутрішніх даних, який називають станом об’єкту.

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

Клас може бути успадкований від інших класів верхнього рівня. Це означає, що клас може використовувати частину коду інших класів верхнього рівня.

Також клас може бути батьківським для інших класів, які успадковують його програмний код.

2. Які види класів мови C++ можна реалізувати в середовищі CLR?

У середовищі CLR підтримуються два види класів:

  • некеровані (unmanaged) класи. Для виділення пам‘яті під об‘єкти таких класів можуть бути використані некеровані покажчики (*) та операція new;
  • керовані (managed) класи. Для виділення пам‘яті в таких класах можуть бути використані керовані покажчики (^) та операція gcnew.

Дана тема висвітлює особливості використання некерованих (*) класів.

Приклади, що демонструють особливості використання та відмінності між керованими (^) та некерованими (*) класами детально описуються в темі:

3. Загальна форма оголошення unmanaged-класу. Ключове слово “class”

У найпростішому випадку (без спадковості) загальна форма оголошення unmanaged-класу має такий вигляд

class імя_класу
{
    private:
    // приховані дані та методи (функції)
    // ...

    public:
    // відкриті дані та методи
    // ...

    protected:
    // захищені дані та методи
    // ...
};

де

  • ім’я_класу – безпосередньо ім’я типу даних “клас”. Це ім’я використовується при створенні об’єктів класу.

Ключове слово class повідомляє про те, що оголошується новий клас (тип даних). Всередині класу оголошуються члени класу: дані та методи. Ключове слово private визначає члени класу, що мають бути приховані (закриті) від зовнішніх методів, оголошених за межами класу, а також об’єктів класу. Члени даних, оголошені з ключовим словом private, доступні тільки іншим членам цього класу.

Ключове слово public визначає дані (змінні) та методи (функції) класу, що є загальнодоступними.

Ключове слово protected визначає захищені дані та методи класу, які є:

  • доступні для методів успадкованих класів, якщо даний клас є батьківським по відношенню до інших класів;
  • недоступні для інших методів, що реалізовані в інших частинах програми;
  • недоступні для об’єктів (екземплярів) класу.

В межах опису класу секції (розділи) private, protected, public можуть слідувати в будь-якому порядку та в будь-якій кількості.

Наприклад:

class MyClass
{
    // секція (розділ) private за замовчуванням
    // ...

    public:
    // секція public
    // ...

    protected:
    // секція protected
    // ...

    private:
    // знову секція private
    // ...

    public:
    // знову секція public
    // ...
};

4. Що означає термін «інкапсуляція даних»?

Термін «інкапсуляція даних» означає, що для членів класу (даних та методів) можна встановлювати ступінь доступності з інших частин програмного коду (інших методів, об‘єктів класу). Таким чином, виникає поняття приховування даних (методів) у класі.

Інкапсуляція забезпечує покращення надійності зберігання даних в класі шляхом вводу додаткових методів перевірки цих даних на допустимі значення. Як правило, доступ до прихованих даних в класі відбувається не напряму, а через виклики спеціальних методів доступу чи властивостей. Безпосередньо дані розміщуються у прихованій частині класу, а методи доступу до цих даних розміщуються у загальнодоступній частині класу.

Класична мова C++ дозволяє встановлювати доступ до членів класу з допомогою трьох специфікаторів: private, protected, public.

5. Які типи доступу можуть мати члени класу? Які відмінності між членами класу, оголошеними з ключовими словами private, protected, public?

Члени класу можуть мати три основні типи доступу, які визначаються відповідними ключовими словами:

  • private – члени класу є прихованими. Це означає, що доступ до них мають тільки методи, що оголошені в класі. private-члени класу є недоступними з породжених класів та об’єктів цього класу;
  • protected – члени класу є захищеними. Це означає, що доступ до protected-членів мають методи класу, “дружні функції” та методи успадкованих класів. protected-члени класу є недоступними для об’єктів цього класу;
  • public – члени класу є відкритими (доступними) для усіх методів та об’єктів з усіх інших частин програмного коду.

6. Чи може клас містити тільки дані і не містити методів при його оголошенні?

Клас може бути оголошений без методів. Такі класи містять тільки дані. Щоб доступитися до даних у класі що не містить методів, потрібно ці дані оголосити в розділі public. Класи без методів майже не застосовуються. Якщо оголосити дані в розділі private, то доступитись до членів-даних класу буде неможливо.

Приклад. У даному прикладі оголошується клас без методів, що реалізує операції над датою. Клас містить внутрішні змінні (дані), що представляють собою число, місяць, рік.

class CMyDate
{
    public:
    int day; // число
    int month; // місяць
    int year; // рік
};

Фрагмент коду, що демонструє роботу з класом CMyDate

// оголосити об'єкт класу MyDate
CMyDate D;

// заповнити значеннями поля класу
D.day = 20;
D.month = 02;
D.year = 2002;

int t = D.year; // t = 2002
t = D.month; // t = 2

7. Приклад оголошення пустого класу

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

// клас не має ні даних ні методів
class NoData
{

};

Об‘єкт такого класу також створюється.

NoData nd; // об'єкт класу NoData

Зрозуміло, що такий клас має дуже обмежене використання. Пустий клас доцільно створювати у випадку, коли під час створення великого проекту потрібно протестувати його ранню (початкову) версію, в якій деякі класи ще не розроблені і не реалізовані. Замість реального класу вказується пустий клас – заглушка. Цей клас розроблений під потреби майбутнього проекту таким чином, щоб компілятор не видавав повідомлення про помилку і можна було протестувати вже написану частину коду. У цьому випадку ім‘я пустого класу вибирається таким, яким воно повинно бути в майбутньому проекті.

8. Приклад класу, що містить методи (функції)

Основні переваги класів виявляються при наявності методів – членів класу. З допомогою методів доступу до даних в класах можна зручно:

  • ініціалізувати дані початковими значеннями;
  • здійснювати перевірку на коректність внутрішніх даних при їх задаванні;
  • реалізовувати різні варіанти перетворення (конвертування) даних;
  • реалізовувати організацію виділення пам’яті для складених типів даних, що реалізовані у класах;
  • виконувати різноманітні види обчислень над даними.

Приклад. Модифікація класу CMyDate. Клас, що описує дату та операції над нею. Операції доступу до членів класу реалізовані з допомогою відповідних методів. Самі дані реалізовані у розділі private.

Програмний код класу

class CMyDate
{
    int day;
    int month;
    int year;

    public:
    void SetDate(int d, int m, int y); //
    int GetDay(void); // повертає номер дня
    int GetMonth(void); // повертає номер місяця
    int GetYear(void); // повертає
};

Реалізація методів класу SetDate(), GetDay(), GetMonth(), GetYear()

// Встановити нову дату
void CMyDate::SetDate(int d, int m, int y)
{
    day = d;
    month = m;
    year = y;
}

// зчитати номер дня
int CMyDate::GetDay(void)
{
    return day;
}

// зчитати номер місяця
int CMyDate::GetMonth(void)
{
    return month;
}

// зчитати рік
int CMyDate::GetYear(void)
{
    return year;
}

Використання методів класу з іншого програмного коду (наприклад, обробника події у додатках типу Windows Forms)

// об'єкт класу
CMyDate MD;

// встановити значення дати 20.02.2002
MD.SetDate(20, 2, 2002);

// зчитати дату
int t;
t = MD.GetDay(); // t = 20
t = MD.GetMonth(); // t = 2
t = MD.GetYear(); // t = 2002

9. В яких частинах класу та програми можна оголошувати реалізацію методів класу? Приклад

Реалізацію методів класу можна оголошувати в класі та за межами класу.

Наприклад. У наведеному нижче програмному коді оголошується клас СMyTime. Клас містить два методи SetTime1() та SetTime2(), які виконують ту саму роботу: встановлюють новий час. Тіло (реалізація) методу SetTime1() описується в класі MyTime. Реалізація методу SetTime2() описується за межами класу. В класі описується тільки прототип (декларація) методу SetTime2().

class CMyTime
{
    private:
    int sec; // секунда
    int min; // хвилина
    int hour; // година

    public:
    // реалізація методу всередині класу
    void SetTime1(int h, int m, int s)
    {
        // реалізація
        hour = h;
        min = m;
        sec = s;
    }

    // тільки декларація (прототип) методу (метод SetTime2 є членом класу)
    void SetTime2(int h, int m, int s); // реалізація методу - за межами класу
};

// реалізація методу SetTime2() за межами класу
void CMyTime::SetTime2(int h, int m, int s)
{
    hour = h;
    min = m;
    sec = s;
}

Тіло методу, що описується за межами класу, може бути описане в іншому модулі. Як правило, у системі Microsoft Visual Studio цей модуль має розширення *.cpp. Сам же клас описується в модулі з розширенням *.h.

10. Яке призначення має оператор розширення області видимості (доступу) ‘::’?

Програмний код методів-членів класу можна описувати в самому класі та за його межами. Якщо потрібно описати код методу, що є членом класу, то для цього використовується оператор розширення області видимості ::” . Оператор “::” визначає ім’я члену класу разом з іменем класу, в якому він реалізований.

11. Що таке об’єкт класу? Які відмінності між об’єктом класу та оголошенням класу? Оголошення об’єкту класу

Оголошення класу – це опис формату типу даних. Цей тип даних “клас” описує дані та методи, що оперують цими даними. Опис класу – це тільки опис (оголошення). У цьому випадку пам’ять для класу не виділяється.

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

Оголошення об’єкту класу (екземпляру) нічим не відрізняється від оголошення змінної:

Ім’я_класу ім’я_об’єкту;

З допомогою імені ім’я_об’єкту можна здійснити доступ до загальнодоступних (public) членів класу. Це здійснюється з допомогою символу ‘ . ‘ (крапка).

Можливий варіант оголошення покажчика на клас. Якщо це unmanaged-клас, то оголошення має вигляд:

Ім’я_класу * ім’я_об’єкту;

Після такого оголошення, потрібно виділяти пам’ять для об’єкту класу з допомогою оператора new. Доступ до даних за покажчиком здійснюється з допомогою комбінації символів ‘->’ так само як і у випадку зі структурами.

Наприклад. Оголошення класу Worker, що описує методи та дані про працівника підприємства.

class Worker
{
    private:
    // private-члени класу Worker
    // ...

    public:
    // public-члени класу Worker
    // ...
};

Об’єкт класу – це змінна типу “клас”. При оголошенні об’єкту класу виділяється пам’ять для цього об’єкту (змінної). Наприклад, для класу Worker можна написати наступний код

Worker w1; // об'єкт класу Worker, виділяється пам'ять для даних об'єкту

З об’єкту можна мати доступ тільки до public-членів класу. Це можна здійснювати з допомогою символу ‘ . ‘ (крапка) або доступу за покажчиком ‘->’;

w1.SetName("Johnson J."); // виклик public-методу
Worker * w2; // покажчик на клас Worker
w2 = new Worker(); // динамічне виділення пам'яті для класу
w2->SetName("Jackson M."); // виклик public-методу з класу за покажчиком

12. Який тип доступу за замовчуванням мають члени класу в C++?

За замовчуванням, члени класу мають доступ private. Тому, при оголошенні класу, якщо потрібно вказати private-члени класу, це слово можна опустити.

Наприклад, нехай задано оголошення класу, що описує піксель на екрані монітора.

class CMyPixel
{
    int color; // розділ private
    int x;
    int y;

    public:
    // загальнодоступні методи (розділ public)
    void SetXYColor(int nx, int ny, int ncolor)
    {
        x = nx; y = ny; color = ncolor;
    }

    int GetX(void) { return x; }
    int GetY(void) { return y; }
    int GetColor(void) { return color; }
};

Як видно, на початку оголошення класу розділ private відсутній. Це означає, що члени-дані класу color, x, y є прихованими. При створенні об’єкту класу та прямому доступі до них компілятор видасть помилку

CMyPixel MP; // оголосити об'єкт класу
// MP.x = 25; - помилка - "CMyPixel::x - cannot access private member declared in CMyPixel
MP.SetXYColor(25, 15, 2); // так правильно

13. Яким чином можна реалізувати доступ до private-членів класу?

Як правило, private-члени класу є закритими. Це є основна перевага інкапсуляції. Щоб змінювати значення private-членів класу, використовують методи класу, що оголошені в public-секції. У цих методах можна змінювати значення private-членів. Такий підхід використовується для забезпечення надійності збереження даних у private-членах. У public-методах, що мають доступ до private-членів, можна реалізувати додаткові перевірки на допустимість значень.

Наприклад. Нехай дано клас, що визначає масив з n дійсних чисел. Клас містить два приховані (private) члени даних:

  • n – кількість елементів масиву;
  • A – безпосередньо масив.

Судячи з логіки задачі, у такому класі кількість елементів масиву не може бути менше нуля (<0). Тому, в методі SetN(), що встановлює значення n доцільно проводити перевірку на коректність значення n.

Програмний код класу, що демонструє доступ до private-членів класу наведений нижче.

class CMyArray
{
    int n;
    float *A; // масив типу float, пам'ять для масиву виділяєтья динамічно

    public:
    CMyArray(); // конструктор класу, без нього ніяк
    void SetN(int nn); // встановити нове n, занулити масив A
    int GetN(void); // зчитати n
    void SetAi(int index, float value); // встановити нове значення в A[index]
    float GetAi(int index); // зчитати значення з A[index]
};

// конструктор класу, без нього ніяк
CMyArray::CMyArray()
{
    n = 0;
}

// встановити нове значення n, і встановити в 0 значення елементів масиву
void CMyArray::SetN(int nn)
{
    if (n>0) delete A; // звільнити пам'ять, виділену попередньо для масиву A
    n = nn;
    A = new float[n]; // виділити пам'ять для n елементів масиву

    // заповнити значеннями 0 елементи масиву
    for (int i=0; i<n; i++)
        A[i] = 0;
}

// зчитати n
int CMyArray::GetN(void)
{
    return n;
}

// встановити нове значення в A[index]
void CMyArray::SetAi(int index, float value)
{
    if (n<=0) return;
    if (index > n-1) return;
    A[index] = value;
}

// зчитати значення з A[index]
float CMyArray::GetAi(int index)
{
    return (float)A[index];
}

Як видно з програмного коду, у класі оголошується новий елемент – конструктор класу. Це спеціальний метод, що використовується для початкової ініціалізації членів даних класу. Більш детально про конструктори та деструктори описується в наступних темах:

Фрагмент використання класу CMyArray з іншого програмного коду:

CMyArray MA; // створити об'єкт класу CMyArray
int d;
double x;

MA.SetN(15);
d = MA.GetN(); // d = 15

MA.SetAi(3, 2.88);
x = MA.GetAi(3); // x = 2.88
x = MA.GetAi(10); // x = 0

MA.SetN(20); // перевизначення масиву
d = MA.GetN(); // d = 20
x = MA.GetAi(3); // x = 0


Зв’язані теми