У цьому розділі ми розглянули перевантаження операторів, перевантаження операцій конвертації типів даних, а також кілька тем, пов’язаних з конструктором копіювання. Пора закріпити отримані знання.
Теорія
Перевантаження оператора — це специфічне перевантаження функції, яка дозволяє використовувати оператори з об’єктами користувацьких класів. При перевантаженні операторів їх функціонал і призначення слід зберігати максимально наближеним до їх початкового застосування. Якщо суть застосовуваного оператора з об’єктами користувацьких класів інтуїтивно незрозуміла, то краще використовувати функцію з ім’ям, замість перевантаження оператора.
Оператори можуть бути перевантажені через звичайні функції, через дружні функції і через методи класу. Наступні правила допоможуть зорієнтуватися, який спосіб перевантаження і коли слід використовувати:
Перевантаження операторів присвоювання (=
), індексу ([]
), виклику функції (()
) або вибору члена (->
) виконуйте через методи класу.
Перевантаження унарних операторів виконуйте через методи класу.
Перевантаження бінарних операторів, які змінюють свій лівий операнд (наприклад, оператор +=
) виконуйте через методи класу.
Перевантаження бінарних операторів, які не змінюють свій лівий операнд (наприклад, оператор +
) виконуйте через звичайні або дружні функції.
Перевантаження операцій конвертації типів даних використовується для явного або неявного перетворення об’єктів користувацького класу в інший тип даних.
Конструктор копіювання — це особливий тип конструктора, який використовується для ініціалізації об’єкта іншим об’єктом того ж класу. Конструктори копіювання використовуються в прямій/uniform-ініціалізації об’єктів об’єктами того ж типу, копіюючій ініціалізації (Fraction f = Fraction(7,4)
) і при передачі або поверненні параметрів по значенню.
Якщо ви не надасте свій конструктор копіювання, то компілятор надасть його автоматично. Конструктори копіювання за замовчуванням (які надаються компілятором) використовують почленну ініціалізацію. Це означає, що кожен член об’єкта копії ініціалізується відповідним членом вихідного об’єкта. Конструктор копіювання може бути проігнорований компілятором в цілях оптимізації, навіть якщо він має побічні ефекти, тому сильно не покладайтеся на свій конструктор копіювання.
Конструктори вважаються конструкторами конвертації за замовчуванням. Це означає, що компілятор використовуватиме їх для неявної конвертації об’єктів інших типів даних в об’єкти вашого класу. Ви можете уникнути цього, використовуючи ключове слово explicit. Ви також можете видалити функції всередині свого класу, включаючи конструктор копіювання і перевантажений оператор присвоювання, якщо це необхідно. І якщо пізніше в програмі викликатиметься видалена функція, то компілятор видасть помилку.
Оператор присвоювання можна перевантажити для виконання операцій присвоювання з об’єктами вашого класу. Якщо ви не надасте перевантажений оператор присвоювання самі, то компілятор створить його за вас. Перевантажені оператори присвоювання завжди повинні мати перевірку на самоприсвоювання.
За замовчуванням оператор присвоювання і конструктор копіювання, які надаються компілятором, виконують почленну ініціалізацію/присвоювання, що є поверхневим копіюванням. Якщо у вашому класі є динамічно виділені члени, то це, швидше за все, призведе до проблем (кілька об’єктів можуть вказувати на одну і ту ж виділену пам’ять). В такому випадку вам потрібно буде явно визначити свій конструктор копіювання і перевантаження оператора присвоювання для виконання глибокого копіювання.
Тест
Завдання №1
Припустимо, що Square — це клас, а square
— це об’єкт цього класу. Який спосіб перевантаження краще використати для наступних операторів?
square + square
-square
std::cout << square
square = 7;
Відповідь №1
Перевантаження бінарного оператора +
краще виконувати через звичайну/дружню функцію.
Перевантаження унарного оператора -
краще виконувати через метод класу.
Перевантаження оператора <<
повинне виконуватися через звичайну/дружню функцію.
Перевантаження оператора =
повинне виконуватися через метод класу.
Завдання №2
Напишіть клас Average, який обчислюватиме середнє значення всіх переданих йому цілих чисел. Використовуйте два члени: перший повинен бути типу int32_t і використовуватися для обчислення суми всіх переданих чисел, другий повинен бути типу int8_t і використовуватися для обчислення кількості переданих чисел. Щоб знайти середнє значення, потрібно розділити суму на кількість.
a) Наступний код функції main():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
int main() { Average avg; avg += 5; std::cout << avg << '\n'; // 5 / 1 = 5 avg += 9; std::cout << avg << '\n'; // (5 + 9) / 2 = 7 avg += 19; std::cout << avg << '\n'; // (5 + 9 + 19) / 3 = 11 avg += -9; std::cout << avg << '\n'; // (5 + 9 + 19 - 9) / 4 = 6 (avg += 7) += 11; // виконання "ланцюжка" операцій std::cout << avg << '\n'; // (5 + 9 + 19 - 9 + 7 + 11) / 6 = 7 Average copy = avg; std::cout << copy << '\n'; return 0; } |
Повинен видавати наступний результат:
5
7
11
6
7
7
Відповідь №2.а)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#include <iostream> #include <cstdint> // для цілочисельних значень фіксованого розміру class Average { private: int32_t m_total = 0; // сума всіх отриманих значень int8_t m_numbers = 0; // кількість всіх отриманих значень public: Average() { } friend std::ostream& operator<<(std::ostream &out, const Average &average) { // Середнє_значення = сума_всіх_отриманих_значень / кількість_всіх_отриманих_значень. // Слід пам'ятати, що тут повинне виконуватися ділення типу з плаваючою крапкою (а не типу int) out << static_cast<double>(average.m_total) / average.m_numbers; return out; } // Оскільки operator+=() змінює свій лівий операнд, то перевантаження слід виконувати через метод класу Average& operator+=(int num) { // Збільшуємо суму всіх отриманих значень новим значенням m_total += num; // І додаємо одиницю до загальної кількості отриманих чисел ++m_numbers; // Повертаємо поточний об'єкт, щоб мати можливість виконувати "ланцюжок" операцій з += return *this; } }; int main() { Average avg; avg += 5; std::cout << avg << '\n'; // 5 / 1 = 5 avg += 9; std::cout << avg << '\n'; // (5 + 9) / 2 = 7 avg += 19; std::cout << avg << '\n'; // (5 + 9 + 19) / 3 = 11 avg += -9; std::cout << avg << '\n'; // (5 + 9 + 19 - 9) / 4 = 6 (avg += 7) += 11; // виконання "ланцюжка" операцій std::cout << avg << '\n'; // (5 + 9 + 19 - 9 + 7 + 11) / 6 = 7 Average copy = avg; std::cout << copy << '\n'; return 0; } |
b) Чи потрібний цьому класу явний конструктор копіювання чи оператор присвоювання?
Відповідь №2.b)
Ні. Використання конструктора копіювання і перевантаженого оператора присвоювання, які надані компілятором за замовчуванням, тут буде достатньо.
Завдання №3
Напишіть свій власний клас-масив цілих чисел IntArray (не використовуйте std::array чи std::vector). Користувачі повинні передавати розмір масиву при створенні об’єкта цього класу, а сам масив (змінна-член) повинен виділятися динамічно. Використовуйте стейтменти assert для перевірки переданих значень, а також свій конструктор копіювання і перевантаження оператора присвоювання, якщо це необхідно, щоб наступний код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <iostream> IntArray fillArray() { IntArray a(6); a[0] = 6; a[1] = 7; a[2] = 3; a[3] = 4; a[4] = 5; a[5] = 8; return a; } int main() { IntArray a = fillArray(); std::cout << a << '\n'; IntArray b(1); a = a; b = a; std::cout << b << '\n'; return 0; } |
Видавав наступний результат:
6 7 3 4 5 8
6 7 3 4 5 8
Відповідь №3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
#include <iostream> #include <cassert> // для стейтментів assert class IntArray { private: int m_length = 0; int *m_array = nullptr; public: IntArray(int length): m_length(length) { assert(length > 0 && "IntArray length should be a positive integer"); m_array = new int[m_length] { 0 }; } // Конструктор копіювання, який виконує глибоке копіювання IntArray(const IntArray &array): m_length(array.m_length) { // Виділяємо новий масив m_array = new int[m_length]; // Копіюємо елементи з вихідного масиву в наш щойно виділений масив for (int count = 0; count < array.m_length; ++count) m_array[count] = array.m_array[count]; } ~IntArray() { delete[] m_array; } // Функція перевантаження оператора << friend std::ostream& operator<<(std::ostream &out, const IntArray &array) { for (int count = 0; count < array.m_length; ++count) { out << array.m_array[count] << ' '; } return out; } int& operator[] (const int index) { assert(index >= 0); assert(index < m_length); return m_array[index]; } // Перевантаження оператора присвоювання з виконанням глибокого копіювання IntArray& operator= (const IntArray &array) { // Перевірка на самоприсвоювання if (this == &array) return *this; // Якщо масив вже існує, то видаляємо його, щоб не відбувся витік пам'яті delete[] m_array; m_length = array.m_length; // Виділяємо новий масив m_array = new int[m_length]; // Копіюємо елементи з вихідного масиву в наш щойно виділений масив for (int count = 0; count < array.m_length; ++count) m_array[count] = array.m_array[count]; return *this; } }; IntArray fillArray() { IntArray a(6); a[0] = 6; a[1] = 7; a[2] = 3; a[3] = 4; a[4] = 5; a[5] = 8; return a; } int main() { IntArray a = fillArray(); // Якщо у вас тут виходить якась нісенітниця, то, швидше за все, ви забули виконати глибоке копіювання у вашому конструкторі копіювання std::cout << a << '\n'; IntArray b(1); a = a; b = a; // Якщо у вас тут виходить якась нісенітниця, то, швидше за все, ви забули виконати глибоке копіювання у вашій функції перевантаження оператора присвоювання, або забули про перевірку на самоприсвоювання std::cout << b << '\n'; return 0; } |
Завдання №4
Значення типу з плаваючою крапкою — це число з десятковим дробом, де кількість цифр після крапки (дробова частина) може змінюватися. Значення типу з фіксованою крапкою — це число з дробом, де дробова частина (після крапки) фіксована.
Вам потрібно написати клас для реалізації значень типу з фіксованою крапкою з двома цифрами після крапки (наприклад, 11.47
, 5.00
або 1465.78
). Діапазон класу повинен бути від -32768.99
до 32767.99
, в дробовій частині можуть бути будь-які дві цифри, але не допускайте проблем з точністю.
a) Якого типу даних змінну-член слід використовувати для реалізації значень типу з фіксованою крапкою з двома цифрами після крапки? (Обов’язково прочитайте відповідь, перш ніж приступати до виконання наступного завдання)
Відповідь №4.а)
Існує кілька способів реалізації значень типу з фіксованою крапкою. Оскільки це той же тип з плаваючою крапкою (крім того, що кількість цифр після крапки є фіксованою), то використання типу float або типу double може здатися очевидним рішенням. Але значення типу з плаваючою крапкою мають проблеми з точністю. З фіксованою крапкою ми можемо перебрати всі можливі числа, які можуть перебувати в дробовій частині значення (в нашому випадку, від .00
до .99
), тому використання типу даних з помилками в точності не є хорошим вибором.
Краще рішення: Використовуйте тип int16_t signed для зберігання цілої частини значення і int8_t signed для зберігання дробової частини значення.
b) Напишіть клас FixedPoint, який реалізує рекомендоване рішення з попереднього завдання. Якщо дробова або ціла частини значення є від’ємними, то число повинне розглядатися, як від’ємне. Реалізуйте перевантаження необхідних операторів і напишіть необхідні конструктори, щоб наступний код функції main():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
int main() { FixedPoint a(37, 58); std::cout << a << '\n'; FixedPoint b(-3, 9); std::cout << b << '\n'; FixedPoint c(4, -7); std::cout << c << '\n'; FixedPoint d(-5, -7); std::cout << d << '\n'; FixedPoint e(0, -3); std::cout << e << '\n'; std::cout << static_cast<double>(e) << '\n'; return 0; } |
Видавав наступний результат:
37.58
-3.09
-4.07
-5.07
-0.03
-0.03
Підказка: Для виводу значення конвертуйте його в тип double, використовуючи оператор static_cast.
Відповідь №4.b)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#include <iostream> #include <cstdint> // для цілочисельних значень фіксованого розміру class FixedPoint { private: std::int16_t m_base; // це ціла частина значення std::int8_t m_decimal; // це дробова частина значення public: FixedPoint(std::int16_t base = 0, std::int8_t decimal = 0) : m_base(base), m_decimal(decimal) { // Тут потрібно обробити випадок, коли дробова частина > 99 або < -99, але це ви повинні реалізувати самостійно // Якщо ціла або дробова частини від'ємні if (m_base < 0.0 || m_decimal < 0.0) { // Перевіряємо цілу частину if (m_base > 0.0) m_base = -m_base; // Перевіряємо дробову частину if (m_decimal > 0.0) m_decimal = -m_decimal; } } operator double() const { return m_base + static_cast<double>(m_decimal) / 100; } friend std::ostream& operator<<(std::ostream &out, const FixedPoint &fp) { out << static_cast<double>(fp); return out; } }; int main() { FixedPoint a(37, 58); std::cout << a << '\n'; FixedPoint b(-3, 9); std::cout << b << '\n'; FixedPoint c(4, -7); std::cout << c << '\n'; FixedPoint d(-5, -7); std::cout << d << '\n'; FixedPoint e(0, -3); std::cout << e << '\n'; std::cout << static_cast<double>(e) << '\n'; return 0; } |
c) Тепер додайте конструктор, який прийматиме значення типу double. Ви можете округлити цілу частину (зліва від крапки) за допомогою функції round() (яка знаходиться в заголовку cmath).
Підказки:
Ви можете отримати цілу частину від числа типу double шляхом конвертації числа типу double в число типу int.
Для переміщення однієї цифри вліво від крапки використовуйте множення на 10. Для переміщення двох цифр використовуйте множення на 100.
Наступний код функції main():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
int main() { FixedPoint a(0.03); std::cout << a << '\n'; FixedPoint b(-0.03); std::cout << b << '\n'; FixedPoint c(4.01); // збережеться, як 4.0099999..., тому нам потрібно це все округлити std::cout << c << '\n'; FixedPoint d(-4.01); // збережеться, як -4.0099999..., тому нам потрібно це все округлити std::cout << d << '\n'; return 0; } |
Повинен видавати наступний результат:
0.03
-0.03
4.01
-4.01
Відповідь №4.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
#include <iostream> #include <cstdint> // для цілочисельних значень фіксованого розміру #include <cmath> // для функції round() class FixedPoint { private: std::int16_t m_base; // це ціла частина нашого значення std::int8_t m_decimal; // це дробова частина нашого значення public: FixedPoint(std::int16_t base = 0, std::int8_t decimal = 0) : m_base(base), m_decimal(decimal) { // Тут потрібно обробити випадок, коли дробова частина > 99 або < -99, але це ви повинні реалізувати самостійно // Якщо ціла або дробова частини від'ємні if (m_base < 0.0 || m_decimal < 0.0) { // Перевіряємо цілу частину if (m_base > 0.0) m_base = -m_base; // Перевіряємо дробову частину if (m_decimal > 0.0) m_decimal = -m_decimal; } } FixedPoint(double d) { // Спочатку нам потрібно отримати цілу частину значення. // Ми можемо зробити це, виконавши конвертацію нашого числа типу double в число типу int m_base = static_cast<int16_t>(d); // відкидається дробова частина // Тепер нам потрібно отримати дробову частину нашого значення: // 1) d - m_base залишає тільки дробову частину, // 2) яку потім ми можемо помножити на 100, перемістивши дві цифри з дробової частини в цілу частину значення // 3) тепер ми можемо це діло округлити // 4) і, нарешті, конвертувати в тип int, щоб відкинути будь-який додатковий дріб m_decimal = static_cast<std::int8_t>(round((d - m_base) * 100)); } operator double() const { return m_base + static_cast<double>(m_decimal) / 100; } friend std::ostream& operator<<(std::ostream &out, const FixedPoint &fp) { out << static_cast<double>(fp); return out; } }; int main() { FixedPoint a(0.03); std::cout << a << '\n'; FixedPoint b(-0.03); std::cout << b << '\n'; FixedPoint c(4.01); // збережеться, як 4.0099999..., тому нам потрібно це все округлити std::cout << c << '\n'; FixedPoint d(-4.01); // збережеться, як -4.0099999..., тому нам потрібно це все округлити std::cout << d << '\n'; return 0; } |
d) Виконайте перевантаження наступних операторів: ==
, >>
, −
(унарний) і +
(бінарний).
Наступна програма:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void SomeTest() { std::cout << std::boolalpha; std::cout << (FixedPoint(0.75) + FixedPoint(1.23) == FixedPoint(1.98)) << '\n'; // обидва значення додатні, ніякого переповнення std::cout << (FixedPoint(0.75) + FixedPoint(1.50) == FixedPoint(2.25)) << '\n'; // обидва значення додатні, переповнення std::cout << (FixedPoint(-0.75) + FixedPoint(-1.23) == FixedPoint(-1.98)) << '\n'; // обидва значення від'ємні, ніякого переповнення std::cout << (FixedPoint(-0.75) + FixedPoint(-1.50) == FixedPoint(-2.25)) << '\n'; // обидва значення від'ємні, переповнення std::cout << (FixedPoint(0.75) + FixedPoint(-1.23) == FixedPoint(-0.48)) << '\n'; // друге значення від'ємне, ніякого переповнення std::cout << (FixedPoint(0.75) + FixedPoint(-1.50) == FixedPoint(-0.75)) << '\n'; // друге значення від'ємне, можливе переповнення std::cout << (FixedPoint(-0.75) + FixedPoint(1.23) == FixedPoint(0.48)) << '\n'; // перше значення від'ємне, ніякого переповнення std::cout << (FixedPoint(-0.75) + FixedPoint(1.50) == FixedPoint(0.75)) << '\n'; // перше значення від'ємне, можливе переповнення } int main() { SomeTest(); FixedPoint a(-0.48); std::cout << a << '\n'; std::cout << -a << '\n'; std::cout << "Enter a number: "; // введіть 5.678 std::cin >> a; std::cout << "You entered: " << a << '\n'; return 0; } |
Повинна видавати наступний результат:
true
true
true
true
true
true
true
true
-0.48
0.48
Enter a number: 5.678
You entered: 5.68
Підказка: Для виконання перевантаження оператора >>
використовуйте конструктор з параметром типу double для створення анонімного об’єкта класу FixedPoint, а потім присвойте цей об’єкт параметру функції перевантаження оператора >>
.
Відповідь №4.d)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
#include <iostream> #include <cstdint> // для цілочисельних значень фіксованого розміру #include <cmath> // для функції round() class FixedPoint { private: std::int16_t m_base; // це ціла частина нашого значення std::int8_t m_decimal; // це дробова частина нашого значення public: FixedPoint(std::int16_t base = 0, std::int8_t decimal = 0) : m_base(base), m_decimal(decimal) { // Тут потрібно обробити випадок, коли дробова частина > 99 або < -99, але це ви повинні реалізувати самостійно // Якщо дробова або ціла частини значення від'ємні if (m_base < 0.0 || m_decimal < 0.0) { // Перевіряємо цілу частину if (m_base > 0.0) m_base = -m_base; // Перевіряємо дробову частину if (m_decimal > 0.0) m_decimal = -m_decimal; } } FixedPoint(double d) { // Спочатку нам потрібно отримати цілу частину значення. // Ми можемо зробити це, виконавши конвертацію нашого числа типу double в число типу int m_base = static_cast<int16_t>(d); // відкидається дробова частина // Тепер нам потрібно отримати дробову частину нашого значення: // 1) d - m_base залишає тільки дробову частину, // 2) яку потім ми можемо помножити на 100, перемістивши дві цифри з дробової частини в цілу частину значення // 3) тепер ми можемо це діло округлити // 4) і, нарешті, конвертувати в тип int, щоб відкинути будь-який додатковий дріб m_decimal = static_cast<std::int8_t>(round((d - m_base) * 100)); } operator double() const { return m_base + static_cast<double>(m_decimal) / 100; } friend bool operator==(const FixedPoint &fp1, const FixedPoint &fp2) { return (fp1.m_base == fp2.m_base && fp1.m_decimal == fp2.m_decimal); } friend std::ostream& operator<<(std::ostream &out, const FixedPoint &fp) { out << static_cast<double>(fp); return out; } friend std::istream& operator >> (std::istream &in, FixedPoint &fp) { double d; in >> d; fp = FixedPoint(d); return in; } friend FixedPoint operator+(const FixedPoint &fp1, const FixedPoint &fp2) { return FixedPoint(static_cast<double>(fp1) + static_cast<double>(fp2)); } FixedPoint operator-() { return FixedPoint(-m_base, -m_decimal); } }; void SomeTest() { std::cout << std::boolalpha; std::cout << (FixedPoint(0.75) + FixedPoint(1.23) == FixedPoint(1.98)) << '\n'; // обидва значення додатні, ніякого переповнення std::cout << (FixedPoint(0.75) + FixedPoint(1.50) == FixedPoint(2.25)) << '\n'; // обидва значення додатні, переповнення std::cout << (FixedPoint(-0.75) + FixedPoint(-1.23) == FixedPoint(-1.98)) << '\n'; // обидва значення від'ємні, ніякого переповнення std::cout << (FixedPoint(-0.75) + FixedPoint(-1.50) == FixedPoint(-2.25)) << '\n'; // обидва значення від'ємні, переповнення std::cout << (FixedPoint(0.75) + FixedPoint(-1.23) == FixedPoint(-0.48)) << '\n'; // друге значення від'ємне, ніякого переповнення std::cout << (FixedPoint(0.75) + FixedPoint(-1.50) == FixedPoint(-0.75)) << '\n'; // друге значення від'ємне, можливе переповнення std::cout << (FixedPoint(-0.75) + FixedPoint(1.23) == FixedPoint(0.48)) << '\n'; // перше значення від'ємне, ніякого переповнення std::cout << (FixedPoint(-0.75) + FixedPoint(1.50) == FixedPoint(0.75)) << '\n'; // перше значення від'ємне, можливе переповнення } int main() { SomeTest(); FixedPoint a(-0.48); std::cout << a << '\n'; std::cout << -a << '\n'; std::cout << "Enter a number: "; // введіть 5.678 std::cin >> a; std::cout << "You entered: " << a << '\n'; return 0; } |