Перевантаження оператора виводу
Якщо ми хочемо, щоб наш тип класу підтримував операції введення/виводу, необхідно перевантажити обидва відповідні оператори. У розділі ми розглянемо, як перевантажується оператор вывода. (Перевантаження оператора введення – тема наступного розділу.) Наприклад, для класу WordCount він виглядає так:
| > слово os " > " #include "WordCount.h" int main() < WordCount wd ("sadness", 12); cout sadness |
Оператор виведення – це бінарний оператор, який повертає посилання на об'єкт класу ostream. У випадку структура визначення перевантаженого оператора висновку виглядає так:
| // структура перевантаженого оператора виводу ostream& operator |
Нехай Location – це клас, у якому зберігаються номери рядка та колонки входження слова. Ось його визначення:
| #include class Location < friend ostream& operator os"; return os; |
Змінимо визначення класу WordCount, включивши до нього вектор occurList об'єктів Location і об'єкт word класу string:
| #include #include #include #include "Location.h" class WordCount < friend ostream& operator _occurList; |
У класах string і Location визначено оператора виводу operator " ::const_iterator first = wd._occurList.begin(); vector ::const_iterator last = wd._occurList.end(); for ( ; first != last; // os = onLine) <os rosebud
Отриманий результат збережено у файлі output. Далі ми визначимо оператор введення, за допомогою якого прочитаємо дані цього файлу.
Дано визначення класу Date:
| class Date < public: // . private: int month, day, year; |
Напишіть перевантажений оператор виведення дати у форматі:
// повна назва місяця
September 8th, 1997
(c) Який формат кращий? Поясніть.
(d) Чи повинен оператор виводу Date бути функцією-другом? Чому?
Визначте оператор виводу для наступного класу CheckoutRecord:
| class CheckoutRecord < // Запис про видачу public: // . private: double book_id; // Ідентифікатор книги string title; // Назва Date date_borrowed; // Дата видачі Date date_due; // дата повернення pair borrower; // кому видано vector pair wait_list; // черга на книгу |
Перевантаження оператора введення
Перевантаження оператора введення (>>) схоже на перевантаження оператора виводу, але, на жаль, можливостей для помилок набагато більше. Ось, наприклад, його реалізація для класу WordCount:
На цьому прикладі показано цілу низку проблем, що стосуються можливих помилкових станів вхідного потоку:
- потік, читання з якого неможливе через неправильний формат, переводиться в стан fail:
- операції вставки та вилучення з потоку, що знаходиться в помилковому стані, не працюють:
while (( ch = is.get() ) != lbrace)
Інструкція зациклиться, якщо об'єкт istream перебуватиме у помилковому стані. Тому перед кожним зверненням до get() перевіряється відсутність помилки:
| // Перевірити, чи потік "is" в "хорошому" стані |
while ( is & ( ch = is.get() ) != lbrace)
Якщо об'єкт istream над “хорошому” стані, його значення дорівнюватиме false. (Про стани потоку ми розповімо у розділі 20.7.)
Ця програма зчитує об'єкт класу WordCount, збережений оператором виведення з попереднього розділу:
| #include #include "WordCount.h" int main() <WordCount readIn; // operator>>( cin, readIn ) cin >> readIn; if ( !cin ) < cerr rosebud |
Оператор введення класу WordCount сам читає об'єкти класу Location. Винесіть цей код в окремий оператор класу Location.
Реалізуйте оператор введення для класу Date із вправи 20.7 у розділі 20.4.
Реалізуйте оператор введення для класу CheckoutRecord із вправи 20.8 у розділі 20.4.
Файлове введення/виведення
Якщо програмі необхідно працювати з файлом, слід включити в неї заголовний файл fstream (який у свою чергу включає iostream):
Якщо файл буде використовуватися лише для виведення, ми визначаємо об'єкт класу з stream. Наприклад:
Передані конструктору аргументи задають ім'я файлу і режим відкриття. Файл типу ofstream може бути відкритий або за промовчанням в режимі виведення (ios_base::out), або в режимі дозапису (ios_base::app). Таке визначення файлу outfile2 еквівалентно наведеному вище:
| // за замовчуванням відкривається як виведення |
Якщо в режимі виводу відкривається існуючий файл, всі дані, що зберігалися в ньому, пропадають. Якщо ж ми хочемо не замінити, а додати дані, слід відкривати файл в режимі дозапису: тоді нові дані поміщаються в кінець. Якщо вказаний файл немає, він створюється у будь-якому режимі.
Перш ніж намагатися прочитати або записати в файл, потрібно перевірити, що файл був успішно відкритий:
і вводимо слово "Borges", то cin переводиться в стан помилки після невдалої спроби привласнити рядковий літерал цілому числу. Якби ми ввели число 1024, читання пройшло б успішно і потік залишився б у нормальному стані.
Щоб з'ясувати, у якому стані перебуваєпотік, достатньо перевірити його значення на істину:
| if ( !cin ) |
// Операція читання не пройшла або зустрівся кінець файлу
Для читання наперед невідомої кількості елементів ми зазвичай пишемо цикл while:
| while (cin >> word) |
// Операція читання завершилася успішно.
Умова в циклі while дорівнює false, якщо досягнуто кінець файлу або сталася помилка при читанні. Найчастіше такої перевірки потокового об'єкта достатньо. Однак при реалізації оператора введення для класу WordCount з розділу 20.5 нам знадобився точніший аналіз стану.
Будь-який поток має набір прапорів, за допомогою яких можна стежити за станом потоку. Є чотири предикатні функції-члена:
- eof() повертає true, якщо досягнуто кінець файлу:
| if ( inOut.eof() ) |
- bad() повертає true при спробі виконання некоректної операції, наприклад, при встановленні позиції за кінцем файлу. Зазвичай це свідчить у тому, що потік перебуває у стані помилки;
- fail() повертає true, якщо операція завершилася невдало, наприклад, не вдалося відкрити файл або переданий некоректний формат введення:
| ifstream iFile(filename, ios_base::in); if ( iFile.fail() ) // не вдалося відкрити |
- good() повертає true, якщо всі перераховані вище умови помилкові:
Існує два способи явно змінити стан потоку iostream. За допомогою функції-члена clear() йому явно надається вказане значення. Функція setstate() не скидає стан, а встановлює один із прапорів, не змінюючи значення інших. Наприклад, у коді оператора введення для класу WordCount при виявленні невірногоформату ми використовуємо setstate() для встановлення прапора fail у стані об'єкта istream:
| if ((ch = is.get()) != ' |
Наприклад, наступна функція читає весь файл alice_emma в об'єкт buf класу ostringstream. Розмір buf збільшується при необхідності, щоб вмістити всі символи:
| #include #include #include string read_file_into_string() < ifstream ifile("alice_emma"); ostringstream buf; char ch; while ( buf & & ifile.get( ch )) buf.put( ch ); return buf.str(); |
Функція-член str() повертає рядок – об'єкт класу string, асоційований із рядковим потоком ostringstream. Цим рядком можна маніпулювати як і, як і “звичайним” об'єктом класу string. Наприклад, у наступній програмі text почленно ініціалізується рядком, асоційованим з buf:
| int main() < string text = read_file_into_string(); // Запам'ятати позиції кожного символу нового рядка vector lines_of_text; string::size_type pos = 0; while (pos! = string::npos) < pos = text.find('\n'pos); lines_of_text.push_back(pos); >//. |
Об'єкт класу ostringstream можна використовуватиме автоматичного форматування складеного рядка, тобто. рядки, складеної з різних типів даних. Так, наступний оператор виведення автоматично перетворює будь-який арифметичний тип у відповідне рядкове уявлення, тому піклуватися про виділення потрібної кількості пам'яті не потрібно:
| #include #include int main() < int ival = 1024; int *pival = &valival double dval = 3.14159; double *pdval = &valdval ostringstream format_message; // Перетворення значень в рядкове подання format_message * values); |
Програма може зберегти такірядки для подальшого відображення і навіть розсортувати їх за серйозністю. Узагальнити цю ідею допомагають класи Notify (повідомлення), Log (протокол) та Error (помилка).
Потік istringstream читає з об'єкта класу string, за допомогою якого було сконструйовано. Зокрема, він застосовується для перетворення рядкового подання числа на його арифметичне значення:
| #include #include #include int main() < int ival = 1024; int *pival = &valival double dval = 3.14159; double *pdval = &dval // створює рядок, в якому значення розділені пробілами ostringstream format_string; format_string >ival >> pival >> dval >> pdval; |
У мові Сі форматування вихідного повідомлення виконується з допомогою функцій сімейства printf(). Наприклад, наступний фрагмент
| int ival = 1024; double dval = 3.14159; char cval = 'a'; char * sval = "the end"; printf( "ival: %d\tdval% %g\tcval: %c\tsval: %s", |
ival, dval, cval, sval);
ival: 1024 dval: 3.14159 cval: a sval: the end
Першим аргументом printf() є форматний рядок. Кожен символ % показує, замість нього має бути підставлено значення аргументу, а наступний його символ визначає тип цього аргументу. Ось деякі з підтримуваних типів (повний опис див. [KERNIGHAN88]):
| %d ціле число %g число з плаваючою точкою %c char |
Додаткові аргументи printf() на позиційній основі порівнюються зі специфікаторами формату, що починаються зі знака %. Всі інші символи у форматному рядку розглядаються як літерали та виводяться буквально.
Основні недоліки сімейства функцій printf() такі: по-перше, форматний рядок не узагальнюється на певнікористувачем типи, і, по-друге, якщо типи чи кількість аргументів не відповідають форматному рядку, компілятор не помітить помилки, а висновок буде відформатовано неправильно. Однак у функцій printf() є і перевага компактність запису.
1. Отримайте також відформатований результат за допомогою об'єкта класу ostringstream.
2. Сформулюйте переваги та недоліки обох підходів.
Стан формату
Кожен об'єкт класу з бібліотеки iostream підтримує стан формату, який керує виконанням операцій форматування, наприклад основа системи числення для цілих значень або точність для значень з плаваючою точкою. Для модифікації стану формату об'єкта у розпорядженні програміста є набір маніпуляторів [O.A.6] .1 Маніпулятор застосовується до потокового об'єкта так само, як до даних. Однак, замість читання або запису даних маніпулятор модифікує внутрішній стан потоку. Наприклад, за умовчанням об'єкт типу bool, що має значення true (а також літеральна константа true), виводиться як ціла 1:
| #include int main() < bool illustrate=true; cout int main() < bool illustrate=true; cout int main() < bool illustrate=true; cout int main() < int ival = 16; double dval = 16.0; cout int main() < int ival = 16; double dval = 16.0; cout #include #include int main() < cout |
Крім описаних аспектів, setprecision() має ще два: на цілі значення він не має жодного впливу; значення з плаваючою точкою округляються, а чи не обрізаються. Таким чином, за точності 4 значення 3.14159 друкується як 3.142, а при точності 3 – як 3.14.
За замовчуванням десяткова точка не друкується, якщо дрібна частина значення дорівнює 0. Наприклад: