
Константний метод
Щоб явно указати компілятору на недопустимість змін об’єкта, якому належить виклик методу, в кінці заголовку цього методу додають слово const.
void output() const;
bool eq(const Point& p2) const;
void Point :: output() const
{ cout<< '(' << x << ',' << y << ')'; }
bool Point :: eq(const Point& p2) const
{ return (x == p2.x && y == p2.y); }
Метод, якому заборонено змінювати атрибути об’єкта-власника, називається константним.
Всі методи, що викликаються в константному методі, теж мають бути константними.
Використання об’єктів одного класу як полів об’єктів іншого класу називається композицією. При цьому кажуть, що класи перебувають у відношенні композиції.
Статичні атрибути й методи
У класі можна оголосити атрибути, що належать не об’єктам класу, а самому класу – статичні атрибути. Статичні атрибути існують в одному екземплярі незалежно від кількості створених об’єктів цього класу. Кожен об’єкт класу за допомогою методів класу має доступ до статичних атрибутів класу, проте за межами методів статичні атрибути класу недоступні.
Статичний атрибут оголошується зі специфікатором static.
class Point {
private :
double x, y;
static unsigned int pntNumber;
public: ...
};
Конструктор копії
class Point {
...
public: ...
Point(const Point & p2); // конструктор копії
};
Конструктор копії будь-якого класу має тільки один незмінюваний параметр-посилання, типом якого є цей клас.
У реалізації конструктора копії ініціалізуємо поля нового об’єкта значеннями з уже існуючого й збільшимо pntNumber.
Point::Point(const Point & p2):x(p2.x),y(p2.y)
{ ++pntNumber; }
Деструктор
~Point(); // деструктор
Деструктор у класі може бути тільки один, причому без параметрів. Його заголовок – це знак ~, ім’я класу й дужки. Виконання деструктора – це не знищення об’єкта, а лише виконання дій, заданих у тілі деструктора. Деструктор неявно викликається перед знищенням об’єкта.
Статичний атрибут має бути статичною змінною, тому його необхідно означити в програмі за межами функцій, тобто як глобальний. Статичний атрибут належить класу, тому позначається з ім’ям класу й оператором розв’язання контексту :: попереду.
Статичний атрибут за межами методів класу можна ініціалізувати. У нашому прикладі ініціалізуємо атрибут pntNumber значенням 0.
unsigned int Point :: pntNumber = 0;
int main() {
...
}
Окрім ініціалізації, ніякий інший доступ до прихованого статичного атрибуту за межами методів класу неможливий.
У прикладі з точками бажано мати можливість вивести кількість утворених об’єктів-точок, тобто значення прихованого статичного атрибуту pntNumber. Щоб отримати його, до класу Point додамо відкритий метод, що повертає значення pntNumber й має такий заголовок.
unsigned int getPntNumber();
Метод, оголошений у цьому вигляді, застосовний до існуючих об’єктів, проте природно викликати його незалежно від наявних об’єктів у програмі.
Щоб викликати метод класу незалежно від об’єктів, його необхідно оголосити як статичний метод класу.
Файли й потоки
Під файлом (фізичним файлом) прийнято розуміти деяку іменовану область даних або послідовність байтів на зовнішньому носії даних, організовану в певний спосіб.
Роботу з файлами здійснюють бібліотечні засоби в складі систем програмування. Завдяки цим засобам у С++-програмі описується робота не з файлом, а його представником – файловою змінною.
У засобах введення-виведення мови С++ основним різновидом файлових змінних є потік – об’єкт деякого бібліотечного класу.
Засоби введення-виведення, представлені нижче, розглядають і обробляють вміст файлу як послідовність байтів.
У будь-який момент обробки файлу в ньому є тільки один байт, до якого може бути застосована наступна операція – доступний байт. Коли виконуються послідовні операції введення або виведення, доступними по черзі стають послідовні байти файлу, тому інколи кажуть про послідовний доступ до файлу.
Клас вхідних потоків ifstream призначено для введення даних з файлу, клас вихідних потоків ofstream – для виведення у файл (від input file stream і output file stream). Клас fstream дозволяє як вводити дані з файлу, так і виводити в нього. Щоб користуватися цими класами, потрібно включити файл заголовків <fstream>.
ifstream fi; fi.open("in.txt");
ofstream fo("D:\MyDir\out.txt");
Після обробки потік треба закрити методом close(), наприклад, fo.close();.
Потоки cin і cout використовуються в багатьох програмах і є потоками класів istream і ostream («потік уведення» й «потік виведення»). Ці класи використовують дані та методи, означені в класі «нижчого рівня» ios, і, у свою чергу, постачають засоби для класів ifstream та ofstream. Клас iostream представляє поняття «потік уведення-виведення» й є основою для класу fstream.
Потоки cin і cout є стандартними потоками введення й виведення. Їх імена оголошено в бібліотечному файлі заголовків <iostream> і зв’язано зі стандартними файлами введення й виведення – клавіатурою й екраном.
Перевірити, чи успішно відкрито потік, можна різними засобами. Метод is_open() повертає 1, якщо потік відкрито, інакше повертає 0. Метод fail() повертає ненульове значення, якщо в потоці трапилася помилка. Так само, вираз, що є іменем потоку, в разі помилки має значення 0. Отже, після зв’язування й відкривання потоку скажімо, f, ознакою помилки є ненульове значення виразу !f.is_open(), або f.fail() або !f.
Параметр, що є потоком, має бути параметром-посиланням, наприклад, як у заголовку int
inpFunc(ifstream & fi).
int outpFunc(char *fName)
{ ofstream fo(fName); ... /*робота з потоком fo*/ fo.close(); return 0;}
Серед атрибутів потоку є так звані прапорці, що мають значення 1 або 0 («прапорець встановлено» або «прапорець скинуто»). Від значень прапорців залежить, в який спосіб потік веде обмін даними з файлом.
Прапорці означено в бібліотечному класі ios (точніше, в класі ios_base, засоби якого
використовуються в ios). Керувати ними можна й за допомогою методів класів «вищих рівнів» ostream, ofstream, fstream і інших.
Є чотири типи прапорців: режиму обробки потоку (своїми значеннями визначають той або інший спосіб обробки даних, що передаються за допомогою потоку (уведення, виведення, інтерпретація або її відсутність тощо).), стану (вказують на відсутність або появу помилок під час передачі даних), форматування (визначають вигляд, до якого перетворюються дані, що виводяться у файл, або вигляд, в якому вони надходять з файлу в потік), режиму пошуку (задають місце у файлі, з якого має починатися пошук потрібного байта).
Значення прапорців режиму обробки потоку встановлюються тільки методом open або конструктором класу потоків.
Встановлений прапорець ios::in дозволяє добувати дані з потоку, ios::out – виводити дані в потік, обидва прапорці – виконувати обидві дії.
Якщо у вихідному потоці встановлено прапорець ios::trunc, доступним спочатку стає перший байт файлу, і подальше виведення в існуючий файл знищує його вміст. Якщо ж замість ios::trunc встановити ios::app, то доступним стає байт, наступний за останнім байтом існуючого файлу. За стандартних налаштувань встановлюється прапорець ios::trunc.
Виразом, який встановлює прапорець, є ім’я прапорця. Щоб встановити декілька прапорців, їх імена записують як операнди операції «побітового або» |: ios::in | ios::out. Вираз такого вигляду й є другим аргументом у виклику open або в означенні імені потоку. Наприклад, відкрити потік, щоб дописувати дані до вже існуючих у файлі, можна так. ofstream fo("out.txt", ios::out | ios::app);
Двійковий режим роботи потоку дозволяє передавати символи (байти) у файл або отримувати їх з файлу без будь-яких перетворень. У текстовому режимі потік інтерпретує отримані символи. Наприклад, потік отримує з файлу послідовність символів, що задають числову константу, а з потоку добувається значення числового типу. За стандартних налаштувань потік є текстовим. Для двійкового режиму необхідно встановити прапорець ios::binary.
Арифметичні значення й рядки можна вивести у файл, представлений потоком, за допомогою операції вставки (або виведення) <<, означеної в класі istream. Найпростіший за формою спосіб уведення даних – це операція введення (або добування) >>, означена в класі istream.
Виконання циклу while(cin>>n) закінчується, якщо, замість того, щоб набрати цілу константу, користувач натискає на клавіші Ctrl-Z, а потім на Enter. Введення також припиниться після того, як користувач введе послідовність символів, які не утворюють цілої константи.
Якщо кінець файлу, з яким зв’язано потік f, досягнуто після спроби введення з нього, виклик методу f.eof() повертає значення 1.
Неуспішна спроба введення призводить до появи помилки в потоці, про що можуть свідчити ненульові значення виразів f.fail() або !f. Звідси, істинне значення виразу f.fail()&&!f.eof() або !f&&!f.eof(), обчислене після спроби введення, може служити ознакою того, що з’явився недопустимий символ.
У потоці є декілька прапорців стану, які своїми значеннями сигналізують про відсутність або появу помилок. Наприклад, під час спроби ввести дані з потоку після того, як у ньому виявлено кінець файлу, встановлюється прапорець ios::eofbit, а будь-яка невдала спроба отримати дані з потоку призводить до встановлення ios::failbit. Якщо ж у потоці трапляється серйозна помилка, яка взагалі не дозволяє працювати з його даними, то встановлюється ios::badbit. І навпаки, коли всі вказані прапорці скинуто, встановлюється прапорець ios::goodbit. Значення цих прапорців повертають методи, відповідно, eof(), fail(), bad(), good().
Потік за наявності помилки не дозволяє виконувати операції введення-виведення. Проте, якщо «в стані помилки» виклик bad() повертає 0 (наприклад, виявлено кінець файлу або недопустимий символ), то з потоком можна продовжити роботу. Для цього достатньо викликати метод clear(), який скидає прапорці ios::eofbit, ios::failbit і встановлює ios::goodbit.
ПІд час виконання f>>ch пропускаються порожні символи (якщо є), найближчий значущий стає значенням змінної, а наступний за ним стає доступним. Винятком є символ (char)26 – він позначає кінець тексту й з потоку не зчитується. Значенням виразу f>>ch стає хибність, а ch не змінюється.
У виклику f.get(ch) з потоку добувається доступний символ, яким би він не був (окрім (char)26); доступним стає наступний за ним. Якщо досягнуто кінець файлу, з виклику повертається нульове значення.
Виклик f.peek() повертає значення типу int, молодший байт якого зображує доступний символ потоку, тому це значення можна присвоїти символьній змінній. Сам символ залишається доступним. Якщо досягнуто кінець файлу, виклик повертає цілу константу -1, іменовану EOF. При цьому помилка в потоці не виникає.
Операція введення у виразі f>>s, де s – адресний вираз типу char*, добуває з потоку послідовність символів і записує в послідовні байти, починаючи з того, на який вказує s. При цьому, за стандартних налаштувань, пропускаються порожні символи й уводиться послідовність непорожніх.
Якщо файл може містити порожні символи, отримувати з нього порожні й непорожні символи краще за допомогою кількох переозначених методів get і getline.
У виклику f.get(s,lim), де значенням lim є додатне ціле число, з потоку добувається lim–1 символ, а можливо, й менше – якщо раніше з’являється символ кінця рядка '\n' або кінець файлу. До символів, записаних у пам’ять за допомогою вказівника s, додається '\0'. Поява символу '\n' зупиняє введення, і цей символ залишається доступним.
Інший варіант цієї функції має додатковий параметр символьного типу. Виклик f.get(s,lim,delim), де delim – символ, також добуває з потоку не більше ніж lim–1 символ, але зупиняється не на '\n', а на символі, заданому аргументом delim.
Виклик f.getline(s,lim) відрізняється від f.get(s,lim) тим, що символ '\n' у потоці пропускається, і доступним стає символ, наступний за ним. Проте, якщо спочатку від доступного символу до найближчого '\n' більше ніж lim–1 символ, то перші lim–1 з них записуються в пам’ять, але виклик getline повертає нульове значення, а в потоці виникає помилка.
Інший варіант цієї функції має додатковий параметр символьного типу. Виклик f.getline(s,num,delim), де delim – символ, аналогічно попередньому варіанту добуває з потоку не більше ніж lim–1 символ, але обмежувачем добутих символів є не '\n', а символ, заданий аргументом delim.
Для того, щоб керувати виглядом послідовностей символів, які виводяться у файл або вводяться з нього, потоки надають три різновиди засобів:
– прапорці форматування й методи керування ними,
– методи форматування,
– маніпулятори, тобто спеціальні функції з інструкціями форматування, які компілятор додає до коду.
Вказані засоби означено в бібліотечних класах ios_base і ios. Ними можна користуватися в класах «вищих рівнів» – ostream, istream і інших.
Встановити прапорець, що позначає певний параметр виведення, можна методом setf.
f.setf(ios::right);
Дія встановленого прапорця поширюється на всі подальші виведення, доки його не буде скинуто за допомогою функції unsetf, наприклад, f.unsetf(ios::right);.
Дійсні значення виводяться у вигляді з фіксованою крапкою або в нормалізованій формі. Першу форму задає прапорець fixed, другу – scientific.
Прапорець showpoint встановлює обов’язкове виведення десяткової крапки й цифр після неї.
Наприклад, інструкції
cout.setf(ios_base::showpoint);
cout<<1.00000001;
виведуть на екран 1.00000. За дії noshowpoint дійсні значення, що зображують цілі числа, виводяться без десяткової крапки, наприклад, значення 2.0 буде виведено як 2.
Якщо встановлено прапорець fixed або scientific, то виклик методу precision з цілим аргументом задає кількість цифр у дробовій частині дійсних значень (cout.precision(2);). Якщо обидва прапорці fixed і scientific скинуто, цей метод встановлює загальну кількість цифр у записі числа. Значенням виразу cout.precision() є поточна точність виведення дробових чисел. Якщо в програмі явно не вказано інше, діє cout.precision(6).
Логічні значення виводяться у два способи: як 1 і 0 або як true й false. За стандартних налаштувань встановлено прапорець noboolalpha, який визначає перший спосіб виведення; для другого способу треба встановити boolalpha.
Метод width задає певну ширину поля для символів значення, яке виводиться наступним. Наприклад, інструкція cout.width(10); Встановлена ширина стосується лише одного елемента виведення.
Якщо задано ширину поля, вихідні символи «притискаються» до його правого краю – це визначає прапорець right, встановлений за стандартних налаштувань. Щоб притискати символи до лівого краю поля, потрібно встановити прапорець left.
Потоки мають безпечніший засіб керування форматом даних – маніпулятори, тобто спеціальні функції, що містять інструкції форматування. Деякі з них, хоча й не всі, встановлюють і скидають прапорці.
Виклики маніпуляторів виведення записуються у виразах виведення після знака операції <<. ( cout<<fixed;) Маніпулятор fixed встановлює прапорець fixed і скидає scientific. Аналогічно маніпуляторам fixed і scientific, маніпулятори showpos, showpoint, right, left, boolalpha встановлюють однойменні прапорці (та скидають «протилежні»). Дія маніпуляторів noshowpos і noboolalpha протилежна дії маніпуляторів showpos і boolalpha.
Виклики маніпуляторів уведення записуються у виразах уведення після знака операції >>.
Маніпулятор skipws (skip white space – пропускати порожні символи). Встановити або скинути його можна маніпулятором уведення, відповідно, skipws або noskipws. Якщо прапорець skipws скинуто, операція f>>ch рівносильна виклику f.get(ch).
Маніпулятор dec визначає, що цілі значення вводяться (виводяться) в десятковому записі, oct – у вісімковому, hex – у шістнадцятковому. За дії маніпулятора виведення showbase цілі значення у вісімковому або шістнадцятковому вигляді виводяться з префіксами 0 або 0x, за дії noshowbase – без префіксів.
Маніпулятор setiosflags встановлює прапорці, вказані виразом у його виклику. Наприклад, у наступній інструкції встановлюються прапорці, які задають виведення додатних значень зі знаком «+» попереду й виведення дійсних у нормалізованому вигляді з порядком.
cout << setiosflags(ios::showpos |
ios::scientific) <<
12 << " " << 12.34 << endl;
Вихід форматується згідно з цими установками: +12 +1.23e+0001.
Маніпулятори setprecision(int) і setw(int) встановлюють кількість цифр після десяткової крапки в дійсній константі й ширину поля виведення. Маніпулятор setfill(char) встановлює символ, яким заповнюється поле до встановленої ширини.
Маніпулятор уведення-виведення setbase(int) з цілим аргументом 8, 10 або 16 встановлює, в якому вигляді вводяться (виводяться) цілі значення. Дія setbase(8) рівносильна дії oct, setbase(10) – dec, setbase(16) – hex.
Щоб користуватися маніпуляторами форматування з параметрами, необхідно включити бібліотечний файл <iomanip>.
Означення маніпулятора без параметрів має такий загальний вигляд.
ім’я-класу-потоків &
ім’я(ім’я-класу-потоків & параметр)
{
... //
return параметр;
}
Означення маніпулятора без параметрів має такий загальний вигляд.
ім’я-класу-потоків &
ім’я(ім’я-класу-потоків & параметр)
{
... //
return параметр;
}
Відкрити потік у двійковому режимі можна, встановивши прапорець ios::binary, наприклад, так.
ifstream fi("infile.txt", ios::binary);
Уводити байти можна за допомогою методу get. Байт, що вводиться у виклику вигляду fi.get(ch), стає значенням символьної змінної ch. Якщо досягнуто кінець файла, то виклик вигляду fi.get(ch) повертає 0, а значення ch не змінюється, інакше виклик повертає ненульове значення.
Виклик fo.put(ch) виводить значення змінної в потік.
Послідовність байтів заданого розміру (блок) можна ввести з потоку в масив символів (буфер) за допомогою методу read. Наприклад, нехай buf – вказівник типу char*, num – вираз із цілим невід’ємним значенням. Тоді виклик f.read(buf,num) добуває з потоку num байтів і записує їх у пам’ять, починаючи з байта, на який вказує buf. Звісно, значення виразу num має бути не більше розміру буферу. Кількість байтів, добутих з потоку під час виконання останньої операції введення, повертається з виклику методу gcount().
Виклик fo.write(buf,num) записує в потік num байтів, починаючи з байта, на який вказує buf. Кількість байтів, щоразу добутих з потоку fi у буфер, повертається з виклику fi.gcount().
Байти з буферу в цій кількості записуються в потік fo.
Класи потоків дозволяють зробити доступним довільний байт файлу за його номером. Для цього потік має працювати в двійковому режимі.
Байт, доступний для подальшої операції введення, встановлюється методом seekg, а для операції виведення, – методом seekp.
У варіанті з одним параметром цілий аргумент у виклику задає номер байта у файлі, який стає доступним. У варіанті з двома параметрами перший задає зсув відносно місця у файлі, позначеного другим.
Другим аргументом у виклику є один з прапорців режиму пошуку ios::beg, ios::cur, ios::end, що позначає відповідно початок файлу, поточний доступний байт і кінець файлу (байт, наступний за останнім байтом файлу). Якщо другий аргумент у виклику не задано, його значенням насправді стає ios::beg.
Методи tellg() і tellp() повертають номер байта, доступного в момент їх виклику, відповідно, для введення й для виведення.
Буферизоване введення й виведення
Об’єкти класів, представлених у цій главі, містять спеціальний символьний масив – буфер. Байти з файлу надходять у буфер або навпаки, з буфера у файл, великими порціями. Коли потік виконує операції введення, він обробляє байти, розташовані не у файлі, а в буфері. Саме за символами, записаними в буфері, потік утворює арифметичні й інші значення, які присвоює змінним програми. Коли всі байти в буфері оброблено, а введення продовжується, буфер заповнюється наступною порцією байтів з файлу.
Аналогічно, виконуючи операцію виведення, потік спочатку накопичує символи у своєму буфері. Коли буфер заповнюється або в нього надходять певні символи, наприклад, '\n', його вміст однією великою порцією переписується у файл («буфер спорожнюється»).
Спорожненням буфера можна керувати. Якщо встановлено прапорець unitbuf, то буфер спорожнюється після кожної операції виведення. Якщо ж встановлено «протилежний» прапорець nounitbuf, спорожнення відбувається, тільки коли буфер заповнився або закривається потік. Маніпулятори unitbuf і nounitbuf встановлюють і скидають відповідні прапорці.
Примусове одноразове спорожнення буфера задає маніпулятор flush. Маніпулятор endl додає в потік символ '\n' і спорожнює буфер.