Урок №134. Дружні функції і класи

  Юрій  | 

  Оновл. 13 Лют 2021  | 

 157

На цьому уроці ми розглянемо використання дружніх функцій і дружніх класів в мові С++.

Проблема

На попередніх уроках ми говорили про те, що дані вашого класу повинні бути private. Однак може виникнути ситуація, коли у вас є клас і функція, яка працює з цим класом, але яка не знаходиться у його тілі. Наприклад, є клас, в якому зберігаються дані, і функція (або інший клас), яка виводить ці дані на екран. Хоча код класу і код функції виводу розділені (для спрощення підтримки коду), код функції виводу тісно пов’язаний з даними класу. Отже, зробивши члени класу private, ми бажаного ефекту не отримаємо.

В таких ситуаціях є два варіанти:

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

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

Дружні функції

Дружня функція — це функція, яка має доступ до закритих членів класу, наче вона сама є членом цього класу. У всіх інших аспектах дружня функція є звичайною функцією. Нею може бути, як звичайна функція, так і метод іншого класу. Для оголошення дружньої функції використовується ключове слово friend перед прототипом функції, яку ви хочете зробити дружньою класу. Неважливо, оголошуєте ви її в public- чи в private-зоні класу. Наприклад:

Тут ми оголосили функцію reset(), яка приймає об’єкт класу Anything і встановлює m_value значення 0. Оскільки reset() не є членом класу Anything, то в звичайній ситуації функція reset() не мала б доступ до закритих членів Anything. Однак, оскільки ця функція є дружньою класу Anything, вона має доступ до закритих членів Anything.

Зверніть увагу, ми повинні передавати об’єкт Anything в функцію reset() в якості параметра. Це пов’язано з тим, що функція reset() не є методом класу. Вона не має вказівника *this і, крім як передачі об’єкта, вона не зможе взаємодіяти з класом.

Ще один приклад:

Тут ми оголосили функцію isEqual() дружньою класу Something. Функція isEqual() приймає в якості параметрів два об’єкти класу Something. Оскільки isEqual() є другом класу Something, то функція має доступ до всіх закритих членів об’єктів класу Something. Функція isEqual() порівнює значення змінних-членів двох об’єктів і повертає true, якщо вони рівні.

Дружні функції і кілька класів

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

Тут є дві речі, на які слід звернути увагу. По-перше, оскільки функція outWeather() є другом для обох класів, то вона має доступ до закритих членів обох класів. По-друге, зверніть увагу на наступний рядок у вищенаведеному прикладі:

Це прототип класу, який повідомляє компілятору, що ми визначимо клас Humidity трохи пізніше. Без цього рядка компілятор видав би помилку, що не знає, що таке Humidity при аналізі прототипу дружньої функції outWeather() всередині класу Temperature. Прототипи класів виконують ту ж роль, що і прототипи функцій: вони повідомляють компілятору про об’єкти, які пізніше будуть визначені, але які зараз потрібно використовувати. Однак, на відміну від функцій, класи не мають типу повернення або параметрів, тому їх прототипи лаконічні: ключове слово class + ім'я класу + ; (наприклад, class Anything;).

Дружні класи

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

Оскільки клас Display є другом класу Values, то будь-який з членів Display має доступ до private-членів Values. Результат виконання програми:

8.4 7

Примітки про дружні класи:

   По-перше, навіть незважаючи на те, що Display є другом Values, Display не має прямого доступу до вказівника *this об’єктів Values.

   По-друге, навіть якщо Display є другом Values, це не означає, що Values також є другом Display. Якщо ви хочете зробити обидва класи дружніми, то кожен з них повинен вказати в якості друга протилежний клас. Нарешті, якщо клас A є другом B, а B є другом C, то це не означає, що A є другом C.

Будьте уважні при використанні дружніх функцій і класів, оскільки це може порушувати принципи інкапсуляції. Якщо деталі одного класу зміняться, то деталі класу-друга також будуть змушені змінитися. Тому обмежуйте кількість і використання дружніх функцій і класів.

Дружні методи

Замість того, щоб робити дружнім цілий клас, ми можемо зробити дружніми тільки певні методи класу. Їх оголошення аналогічні оголошенням звичайних дружніх функцій, за винятком імені методу з префіксом ім'яКласу:: на початку (наприклад, Display::displayItem()).

Переробимо наш попередній приклад, щоб метод Display::displayItem() був дружнім класу Values. Ми могли б зробити наступне:

Однак це не спрацює. Щоб зробити метод дружнім класу, компілятор повинен побачити повне визначення класу, в якому дружній метод визначається (а не тільки його прототип). Оскільки компілятор послідовно “прочісуючи” рядки коду не побачив повного визначення класу Display, але встиг побачити прототип його методу, то він видасть помилку в рядку визначення цього методу дружнім класу Values (рядок №16).

Можна спробувати перемістити визначення класу Display вище визначення класу Values:

Однак тепер ми маємо іншу проблему. Оскільки метод Display::displayItem() використовує посилання на об’єкт класу Values в якості параметра, а ми тільки що перенесли визначення Display вище визначення Values, то компілятор буде скаржитися, що він не знає, що таке Values. Виходить замкнуте коло.

На щастя, це також можна дуже легко вирішити:

   По-перше, для класу Values використовуємо попереднє оголошення.

   По-друге, переносимо визначення методу Display::displayItem() за межі класу Display і розміщуємо його після повного визначення класу Values.

Ось як це виглядатиме:

Тепер все працюватиме правильно. Хоча це може здатися дещо складним, але цей “танець” з переміщенням класів і методів потрібен тільки тому, що ми намагаємося зробити все в одному файлі. Кращим рішенням було б помістити кожне визначення класу в окремий заголовок з визначеннями методів у відповідних файлах .cpp. Таким чином, всі визначення класів було б видно відразу у всіх файлах .cpp, і ніякого “танцю” з переміщеннями не знадобилося б!

Висновки

Дружня функція/клас — це функція/клас, яка має доступ до закритих членів іншого класу, наче вона сама є членом цього класу. Це дозволяє функції/класу працювати в тісному контакті з іншим класом, не змушуючи інший клас робити відкритими свої закриті члени.

Тест

Точка в геометрії — це позиція в просторі. Ми можемо визначити точку в 3D-просторі як набір координат x, y і z. Наприклад, Point(0.0, 1.0, 2.0) буде точкою в координатному просторі x = 0.0, y = 1.0 і z = 2.0.

Вектор в фізиці — це величина, яка має довжину і напрямок (але не положення). Ми можемо визначити вектор в 3D-просторі через значення x, y і z, що представляють напрям вектора вздовж осей x, y і z. Наприклад, Vector(1.0, 0.0, 0.0) буде вектором, що представляє напрямок тільки вздовж позитивної осі x довжиною 1.0.

Вектор може застосовуватися до точки для її переміщення на нову позицію. Це робиться шляхом додавання напрямку вектора до позиції точки. Наприклад, Point(0.0, 1.0, 2.0) + Vector(0.0, 2.0, 0.0) дасть точку (0.0, 3.0, 2.0).

Точки і вектори часто використовуються в комп’ютерній графіці (точка для представлення вершин фігури, а вектори — для переміщення фігури).

Виходячи з наступної програми:

a) Зробіть клас Point3D дружнім класу Vector3D і реалізуйте метод moveByVector() в класі Point3D.

Відповідь a)

b) Замість того, щоб клас Point3D був дружнім класу Vector3D, зробіть метод Point3D::moveByVector() дружнім класу Vector3D.

Відповідь b)

c) Переробіть свою відповідь із завдання b, використовуючи 5 окремих файлів: Point3D.h, Point3D.cpp, Vector3D.h, Vector3D.cpp і main.cpp.

Відповідь c)

Point3D.h:

Point3D.cpp:

Vector3D.h:

Vector3D.cpp:

main.cpp:

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (2 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *