Namespace CounterNameSpace 10 страница

У мові програмування C++ також передбачено двобайтові (16-бітові) символьні версії стандартних потоків, що іменуються wcin, wcout, wcerr і wclog. Вони призначені для підтримки таких мов як китайська, японська та інших східно-азіатських, для представлення яких потрібні великі символьні набори. У цьому навчальному посібнику двобайтові стандартні потоки не розглядаються.

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

19.2.3. Класи потоків

Як було зазначено в розд. 2, С++-система введення-виведення використовує заголовок <iostream>, у якому для підтримки операцій введення-виведення даних визначена достатньо складна ієрархія класів. Ця ієрархія починається з системи шаблонних класів. Як наголошувалося в розд. 17, шаблонний клас визначає зміст виконуваних дій, не задаючи у повному обсязі типи даних, які він повинен обробляти. Маючи шаблонний клас, можна створювати його конкретні примірники. Для бібліотеки введення-виведення стандарт мови програмування C++ створює дві спеціалізації шаблонних класів: одну для 8-, а іншу для 16-бітових ("широких") символів. У цьому навчальному посібнику описуються шаблонні класи тільки для 8-бітових символів, оскільки вони найчастіше використовуються.

С++-система введення-виведення побудована на двох взаємопов'язаних, але різних ієрархіях шаблонних класів. Перша ієрархія виведена з класу низькорівневого введення-виведення basic_streambuf. Цей клас підтримує базові низькорівневі операції введення та виведення і забезпечує підтримку всієї С++-системи введення-виведення. Якщо Ви не плануєте займатися програмуванням спеціалізованих операцій введення-виведення даних, то Вам навряд чи доведеться використовувати безпосередньо клас basic_streambuf. Друга ієрархія класів, з якою С++-програмістам доводиться працювати безпосередньо, виведена з класу basic_ios. Це –| клас високорівневого введення-виведення, який забезпечує форматування даних, контроль помилок і надає статусну інформацію, пов'язану з потоками введення-виведення[72]. Клас basic_ios використовується як базовий для декількох похідних від нього класів, в т.ч. класи basic_istream, basic_ostream і basic_iostream. Ці класи використовуються для створення потоків, призначених для окремого введення та виведення даних і їх одночасного введення-виведення.

Як ми вже зазначали вище, бібліотека введення-виведення даних створює дві спеціалізовані ієрархії шаблонних класів: одну для 8-, а іншу для 16-бітових символів. У табл. 19.1 наведено список імен шаблонних класів і відповідних їм "символьних" версій.

Табл. 19.1. Список імен шаблонних класів і відповідних їм "символьних" версій

Шаблонні класи Символьні класи
Базові низькорівневі операції введення-виведення
basic_streambuf streambuf
Висорівневі операції введення-виведення
basic_ios ios
basic_istream istream
basic_ostream ostream
basic_iostream iostream
basic_fstream fstream
basic_ifstream ifstream
basic_ofstream ofstream

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

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

Необхідно пам'ятати! Якщо помістити у програму заголовок <iostream>, то вона матиме доступ до цього важливого класу.

19.3. Перевантаження операторів введення-виведення даних

У розглянутих раніше прикладах програм для виконання операції введення або виведення "класових" даних створювалися функції-члени класів, призначення яких полягало тільки у тому, щоб ввести або вивести ці дані. Незважаючи на те, що у такому вирішенні цих питань немає нічого неправильного, проте у мові програмування C++ передбачено вдаліший спосіб виконання операцій введення-виведення даних "класових" даних – шляхом перевантаження операторів введення-виведення даних "<<" і ">>".

Оператор "<<" виводить інформацію у потік, а оператор ">>" вводить інформацію з потоку.

У мові C++ оператор "<<" називається оператором виведення або вставки, оскільки він вставляє символи у потік. Аналогічно оператор ">>" називається оператором введення або вилучення, оскільки він вилучає символи з потоку.

Як уже зазначалося вище, оператори введення-виведення вже перевантажені| (у заголовку <iostream>) для того, щоб вони могли виконувати операції потокового введення або виведення даних будь-яких вбудованих С++-типів. У цьому підрозділі можна буде дізнатися про те, як визначити ці оператори для створення власних класів.

19.3.1. Створення перевантажених операторів виведення даних

Як простий приклад розглянемо механізм створення оператора виведення даних для уже відомої нам з попередніх розділів (див. поч. у розд. 14.1) такої версії класу kooClass:

class kooClass { // Оголошення класового типу

public:

int x, y, z; // Тривимірні координати

kooClass(int a, int b, int c) { x = a; y = b; z = c;}

};

Щоб створити операторну функцію виведення даних для об'єктів типу kooClass, необхідно перевантажити оператор виведення даних "<<". Один з можливих способів його реалізації має такий вигляд:

// Відображення тривимірних координат x, y, z

// Перевантажений оператор виведення даних для класу kooClass

ostream &operator<<(ostream &stream, kooClass obj)

{

stream << obj.x << ", ";

stream << obj.y << ", ";

stream << obj.z << "\n";

 

return stream; // Повертає посилання на параметр stream

}

Розглянемо уважно цю операторну функцію, оскільки її вміст характерний для багатьох операторних функцій виведення даних. По-перше, зверніть увагу на те, що, згідно з оголошенням, вона повертає посилання на об'єкт типу ostream. Це дає змогу декілька звичайних операторів виведення даних об'єднати в одному складеному виразі. По-друге, зверніть увагу також на те, що ця функція має два параметри. Перший є посиланням на потік, який використовується в лівій частині оператора "<<". Другим є об'єкт, який знаходиться у правій частині цього оператора[73]. Саме тіло операторної функції складається з настанов виведення трьох значень координат, що містяться в об'єкті типу kooClass, і настанови повернення потоку stream. Нижче наведено коротку програму, у якій продемонстровано механізм використання перевантаженого оператора виведення даних.

Код програми 19.1. Демонстрація механізму реалізації перевантаженого оператора виведення даних

#include <iostream>// Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

class kooClass { // Оголошення класового типу

public:

int x, y, z; // Тривимірні координати

kooClass(int a, int b, int c) { x = a; y = b; z = c;}

};

 

// Відображення тривимірних координат x, y, z

// Перевантажений оператор виведення даних для класу kooClass

ostream &operator<<(ostream &stream, kooClass obj)

{

stream << obj.x << ", ";

stream << obj.y << ", ";

stream << obj.z << "\n";

 

return stream; // Повертає посилання на параметр stream

}

 

int main()

{

kooClass A_ob(1, 2, 3), B_ob(3, 4, 5), C_ob(5, 6, 7);

 

// Перевантажений оператор виведення даних

cout << A_ob << B_ob << C_ob;

 

getch(); return 0;

}

У процесі виконання ця програма виводить на монітор такі результати:

1, 2, 3

3, 4, 5

5, 6, 7

Якщо видалити програмний код, що стосується звичайних операторів виведення даних конкретного класу kooClass, то залишиться "скелет", що відповідає будь-якій операторній функції виведення даних:

ostream &operator<<(ostream &stream, class_type obj)

{

// Код операторної функції виведення даних

 

return stream; // Повертає посилання на параметр stream

}

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

Загалом конкретні дії, які має виконувати операторна функція виведення даних, визначаються програмістом. Але, якщо у Вас виникає бажання дотримуватися професійного стилю програмування, то створена програмістом операторна функція виведення даних повинна тільки виводити інформацію. І потім, завжди незайве бути впевненому в тому, що вона повертає тільки параметр stream.

Перш ніж переходити до наступного розділу, подумайте, чому операторна функція виведення даних для класу kooClass не була запрограмована так:

// Версія обмеженого застосування (використанню не підлягає).

ostream &operator<<(ostream &stream, kooClass obj)

{

cout << obj.x << ", ";

cout << obj.y << ", ";

cout << obj.z << "\n";

 

return stream; // Повертає посилання на параметр stream

}

Не важко здогадатися, що у такій версії операторної функції жорстко закодований потік cout. Це обмежує перелік ситуацій, в яких її можна використовувати.

Необхідно пам'ятати! Перевантажений оператор "<<" можна застосувати для виведення будь-якого потоку. Потік даних, який використовується у "<<"-виразі, передається параметру stream.

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

19.3.2. Використання функцій-"друзів" класу для перевантаження операторів виведення даних

У попередній програмі операторна функція виведення даних не була визначена як член класу kooClass. Насправді ні будь-яка операторна функція виведення даних, ні функція їх введення не можуть бути членами класу. Справа тут полягає ось в чому. Якщо операторна функція є членом класу, то лівий операнд (що опосередковано передається за допомогою покажчика this) повинен бути об'єктом класу, який генерує звернення до цієї операторної функції. І це змінити не можна. Проте під час перевантаження операторів виведення даних лівий операнд повинен бути потоком, а правий – об'єктом класу, дані якого підлягають виведенню. Отже, перевантажені оператори виведення даних не можуть бути функціями-членами класу.

У зв'язку з тим, що операторні функції виведення даних не можуть бути членами класу, для якого вони визначаються, то виникає серйозне запитання: як перевантажений оператор виведення даних може отримати доступ до закритих членів класу? У попередній програмі (див. код програми 19.1) змінні х, y і z були визначені як відкриті, тому оператор виведення даних без перешкод міг отримати до них доступ. Водночас закриття даних – важлива частина об'єктно-орієнтованого програмування, то вимагати, щоб усі дані були відкритими, просто нелогічно. Проте існує просте вирішення цього питання – оператор виведення даних можна зробити "другом" класу. Якщо функція є "другом" деякого класу, то вона отримує легальний доступ до його private-даних. Оголошення "другом" класу операторної функції виведення даних продемонструємо на прикладі класу kooClass.

Код програми 19.2. Демонстрація механізму використання функцій-"друзів" класу для перевантаження оператора виведення даних

#include <iostream>// Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

class kooClass { // Оголошення класового типу

int x, y, z; // Тривимірні координати (тепер це private-члени)

public:

kooClass(int a, int b, int c) { x = a; y = b; z = c;}

friend ostream &operator<<(ostream &stream, kooClass obj);

};

 

// Відображення тривимірних координат x, y, z

// Перевантажений оператор виведення даних для класу kooClass

ostream &operator<<(ostream &stream, kooClass obj)

{

stream << obj.x << ", ";

stream << obj.y << ", ";

stream << obj.z << "\n";

 

return stream; // Повертає посилання на параметр stream

}

 

int main()

{

kooClass A_ob(1, 2, 3), B_ob(3, 4, 5), C_ob(5, 6, 7);

 

// Перевантажений оператор виведення даних

cout << A_ob << B_ob << C_ob;

 

getch(); return 0;

}

Звернемо Вашу увагу на те, що змінні х, y і z у цій версії програми є закритими у класі kooClass, проте, операторна функція виведення даних звертається до них безпосередньо. У наведеному прикладі якраз і виявляється велика перевага "друзів" класу: оголошуючи операторні функції введення та виведення даних "друзями" класу, для якого вони визначаються, ми тим самим підтримуємо принцип інкапсуляції об'єктно-орієнтованого програмування.

19.3.3. Створення перевантажених операторів введення даних

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

// Прийняття тривимірних координат x, y, z

// Перевантажений оператор введення даних для класу kooClass

istream &operator>>(istream &stream, kooClass &obj)

{

cout << "Введіть координати х, y і z: ";

 

// Перевантажений оператор введення даних

stream >> obj.x >> obj.y >> obj.z;

 

return stream; // Повертає посилання на параметр stream

}

Перевантажений оператор введення даних повинен повертати посилання на об'єкт типу istream. Окрім того, перший параметр повинен бути посиланням на об'єкт типу istream. Цей тип належить потоку, що вказується зліва від оператора ">>". Другий параметр є посиланням на об'єкт, який приймає значення, що вводяться. Оскільки другий параметр – посилання на об'єкт, то його можна модифікувати під час введення інформації.

Загальний формат перевантаженого оператора введення даних має такий вигляд:

istream &operator>>(istream &stream, objectType &obj)

{

// код операторної функції введення даних

&operator<<(ostream &stream, Matrycya obj)

return stream; // Повертає посилання на параметр stream

}

Особливості використання операторних функцій введення та виведення даних для об'єктів типу kooClass продемонстровано в такій програмі.

Код програми 19.3. Демонстрація механізму реалізації перевантажених операторів введення/виведення потокової інформації

#include <vcl>

#include <iostream>// Для потокового введення-виведення

#include <conio>// Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

 

class kooClass { // Оголошення класового типу

int x, y, z; // Тривимірні координати

public:

kooClass(int a, int b, int c) { x = a; y = b; z = c;}

friend ostream &operator<<(ostream &stream, kooClass obj);

friend istream &operator>>(istream &stream, kooClass &obj);

};

 

// Відображення тривимірних координат x, y, z

// Перевантажений оператор виведення даних для класу kooClass

ostream &operator<<(ostream &stream, kooClass obj)

{

stream << obj.x << ", ";

stream << obj.y << ", ";

stream << obj.z << "\n";

 

return stream; // Повертає посилання на параметр stream

}

 

// Прийняття тривимірних координат x, y, z

// Перевантажений оператор введення даних для класу kooClass

istream &operator>>(istream &stream, kooClass &obj)

{

cout << "Введіть координати x, y і z: ";

 

// Перевантажений оператор введення даних

stream >> obj.x >> obj.y >> obj.z;

 

return stream; // Повертає посилання на параметр stream

}

 

int main()

{

kooClass A_ob(1, 2, 3);

 

cout << A_ob; // Перевантажений оператор виведення даних

 

cin >> A_ob; // Перевантажений оператор введення даних

cout << A_ob; // Перевантажений оператор виведення даних

 

getch(); return 0;

}

Ось як виглядає| результат виконання цієї програми:

1, 2, 3

Введіть координати x, y і z: 5 6 7

5, 6, 7

Подібно до операторних функцій виведення даних, операторні функції їх введення не можуть бути членами класу, для оброблення даних якого вони призначені. Вони може бути тільки "друзями" цього класу або просто незалежними операторними функціями.

За винятком того, що операторна функція введення даних повертає посилання на об'єкт типу istream, то тіло цієї функції може містити все те, що програміст вважає за потрібне у неї помістити. Але логічніше використовувати оператори введення все ж таки за прямим призначенням, тобто для виконання операцій введення даних.

19.3.4. Порівняння С- і С++-систем введення-виведення

Як уже зазначалося вище, попередниця мови програмування C++, мова С оснащена однією з найгнучкіших (серед структурованих мов) і водночас дуже могутньою системою введення-виведення[74]. Однак виникає логічне запитання: чому ж тоді у мові програмування C++ визначається власна система введення-виведення, якщо в ній продубльована велика частина того, що міститься у мові С (маємо на увазі могутній набір С-функцій введення-виведення)? Відповісти на це запитання неважко. Йдеться про те, що С-система введення-виведення не забезпечує ніякої підтримки для об'єктів, що визначаються користувачем. Наприклад, якщо створити таку структуру

struct myStruct { // Оголошення типу структури

int count;

char sMas[80];

double balance;

} oust;,

то наявну у мові C систему введення-виведення неможливо налаштувати так, щоб вона могла виконувати операції введення-виведення даних безпосередньо над об'єктами типу myStruct. Але, оскільки центром об'єктно-орієнтованого програмування є саме об'єкти, то виникає потреба у тому, щоб у мові програмування C++ функціонувала така система введення-виведення, яку можна було б динамічно "навчати" поводженню з будь-якими об'єктами, що створюються програмістом. Саме тому для мови програмування C++ і була винайдена нова об'єктно-орієнтована система введення-виведення. Як Вам уже довелося переконатися вище, С++-підхід до введення-виведення даних дає змогу перевантажувати оператори "<<" і ">>" так, що вони можуть працювати з класами, які створюються програмістами.

Варто знати! Оскільки мова програмування C++ є надбудовою мови С, тому увесь вміст засобів С-системи введення-виведення включено у мову C++[75]. Тому у процесі перекладу С-програм на мову C++ програмісту не потрібно підряд змінювати усі настанови введення-виведення даних. Працюючі С-настанови скомпілюються і успішно працюватимуть у новому С++-середовищі. Просто програміст повинен врахувати те, що стара С-система введення-виведення не володіє об'єктно-орієн­това­ними можливостями.

19.4. Форматоване введення-виведення даних

Дотепер під час введення або виведення інформації в наведених вище прикладах програм діяли параметри форматування, які за замовчуванням використовує С++-система введення-виведення. Але програміст може сам керувати форматом представлення даних, причому двома способами. Перший спосіб передбачає використання функцій-членів класу ios, а другий –| функцій спеціального типу, що іменуються маніпуляторами (manipulator). У цьому підрозділі почнемо засвоєння можливостей форматування даних з функцій-членів класу ios, а завершимо – створенням власних маніпуляторних функцій.

19.4.1. Форматування даних з використанням функцій-членів класу ios

У C++-системі введення-виведення кожен потік пов'язаний з набором прапорців, які керують процесом форматування даних. У класі ios оголошується перерахунок fmtflags, у якому визначено стандартні значення прапорців форматування[76] (табл. 19.2).

Табл. 19.2. Стандартні С++-значення прапорців форматування

adjustfield floatfield right skipws
basefield hex scientific unitbuf
boolalpha internal showbase uppercase
dec left showpoint  
fixed oct showpos  

Ці значення використовуються для встановлення або скидання прапорців форматування за допомогою таких функцій: setf() і unsetf(). Під час використання старого компілятора може так статися, що він не зможе визначити тип перерахунку fmtflags. У цьому випадку прапорці форматування кодуватимуться як цілочисельні long-значення.

1. Якщо прапорець skipws встановлено, то при потоковому введенні даних провідні "пропускні" символи, або символи пропуску (тобто пропуски, символи табуляції та нового рядка), відкидаються. Якщо ж прапорець skipws скинуто, то пропускні символи не відкидаються.

2. Якщо встановлено прапорець left, то дані, що виводяться, вирівнюються по лівому краю, а якщо встановлено прапорець right – по правому. Якщо встановлено прапорець internal, то числове значення доповнюється пропусками, якими заповнюється поле між ним і знаком числа або символом основи системи числення. Якщо жоден з цих прапорців не встановлено, то результат вирівнюється по правому краю за замовчуванням.

3. За замовчуванням числові значення виводяться у десятковій системі числення. Проте основу системи числення можна змінити. Встановлення прапорця oct приведе до виведення результату у вісімковому представленні, а встановлення прапорця hex – в шістнадцятковому. Щоб під час відображення результату повернутися до десяткової системи числення, достатньо встановити прапорець dec.

4. Встановлення прапорця showbase приводить до відображення позначення основи системи числення, у якій представляються числові значення. Наприклад, якщо використовується шістнадцяткове представлення, то значення 1F буде відображено як 0x1F.

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

6. Встановлення прапорця showpos викликає відображення провідного знаку "плюс" перед позитивними значеннями.

7. Встановлення прапорця showpoint приводить до відображення десяткової крапки і хвостових нулів для всіх чисел з плинною крапкою – потрібні вони чи ні.

8. Після встановлення прапорця scientific числові значення з плинною крапкою відображаються в експоненціальному представленні. Якщо встановлено прапорець fixed, то дійсні значення відображаються у звичайному представленні. Якщо не встановлено жоден з цих прапорців, то компілятор сам вибирає відповідний формат їх представлення.

9. При встановленому прапорці unitbuf вміст буфера скидається на диск після кожної операції виведення даних.

10. Якщо встановлено прапорець boolalpha, то значення булевого типу можна вводити або виводити, використовуючи ключові слова true і false.

Оскільки часто доводиться звертатися до полів oct, dec і hex, то на них допускається колективне посилання ios::basefield. Аналогічно поля left, right і internal можна узагальнено назвати ios::adjustfield. Нарешті, поля scientific і fixed можна назвати ios::floatfield.

Щоб встановити прапорці форматування, необхідно звернутися до функції setf().

Для встановлення будь-якого прапорця використовується функція setf(), яка є членом класу ios. Ось як виглядає її формат:

fmtflags setf(fmtflags flags);

Ця функція повертає значення попередніх установок прапорців форматування і встановлює їх відповідно до значення, які задаються параметром flags. Наприклад, щоб встановити прапорець showbase, можна використовувати таку настанову: