Namespace CounterNameSpace 13 страница

Ім'я Значення
ios::goodbit Помилок немає
ios::eofbit 1 внаслідок виявлення кінця файлу; 0 – в іншому випадку
ios::failbit 1 під час виникнення поправної помилки введення-виведення; 0 – в іншому випадку
ios::badbit 1 під час виникнення непоправної помилки введення-виведення; 0 – в іншому випадку

Статусну інформацію про результат виконання операцій введення-виведення даних можна отримувати двома способами. По-перше, можна викликати функцію rdstate(), яка є членом класу ios. Вона має такий прототип:

iostate rdstate();

Функція rdstate() повертає поточний статус прапорців помилок. Неважко здогадатися, що, судячи з наведеного вище переліку прапорців, функція rdstate() поверне значення goodbit за відсутності будь-яких помилок. У іншому випадку вона повертає відповідний прапорець помилки.

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

● функція bool bad(); повертає значення ІСТИНА, якщо внаслідок виконання операції введення-виведення даних було встановлено прапорець badbit;

● функцію bool eof(); розглянуто вище;

● функція bool fail(); повертає значення ІСТИНА, якщо внаслідок виконання операції введення-виведення даних було встановлено прапорець failbit;

● функція bool good(); повертає значення ІСТИНА, якщо у процесі виконання операції введення-виведення даних помилок не відбулося.

У інших випадках ці функції повертають значення ФАЛЬШ.

Якщо у процесі виконання операції введення-виведення даних відбулася помилка, то, можливо, перш ніж продовжувати виконання програми, є сенс скинути прапорці помилок. Для цього використовуйте функцію clear() (член класу ios), прототип якої виглядає так:

void clear(iostate flags = ios::goodbit);

Якщо параметр flags дорівнює значенню goodbit(воно встановлюється за замовчуванням), то всі прапорці помилок очищаються. Інакше прапорці встановлюються відповідно до заданого Вами значення.

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

19.6. Використання файлів довільного доступу

Дотепер ми використовували файли, доступ до вмісту яких було організовано строго послідовно, байт за байтом. Але у мові програмування C++ також можна отримувати доступ до файлу у довільному порядку.

19.6.1. Функції довільного доступу

Для довільного доступу до доданих файлу необхідно використовувати функції seekg() і seekp(). Їх прототипи мають такий вигляд:

istream &seekg(off_type offset, seekdir origin);

 

ostream &seekp(off_type offset, seekdir origin);

Використовуваний тут цілочисельний тип off_type (він визначений у класі ios) дає змогу зберігати найбільше допустиме значення, яке може мати параметр offset. Тип seekdir визначено як перерахунок, який має наступні значення.

Значення Опис
ios::beg Початок файлу
ios::cur Поточна позиція файлу
ios::end Кінець файлу

Функція seekg() переміщає покажчик, що "відповідає" за введення даних, а функція seekp() – покажчик, що "відповідає" за виведення.

У С++-системі введення-виведення передбачено можливість керування двома покажчиками, пов'язаними з файлом. Ці так звані get- і put-покажчики визначають, у якому місці файлу повинна виконатися наступна операція введення та виведення відповідно. Під час кожного виконанні операції введення або виведення відповідний покажчик автоматично переміщається у вказану позицію. Використовуючи функції seekg() і seekp(), можна отримувати доступ до файлу у довільному порядку.

1. Функція seekg() переміщає поточний get-покажчик відповідного файлу на offset байт відносно позиції, які задаються параметром origin.

2. Функція seekp() переміщає поточний put-покажчик відповідного файлу на offset байт відносно позиції, які задаються параметром origin.

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

Поточну позицію кожного файлового покажчика можна визначити за допомогою таких двох функцій:

pos_type tellg(); повертає поточну позицію get-покажчика;

pos_type tellp(); повертає поточну позицію put-покажчика.

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

Існують перевантажені версії функцій seekg() і seekp(), які переміщають файлові покажчики у позиції файлу, що задаються значеннями, які повертаються функціями tellg() і tellp()відповідно. Ось як виглядають їх прототипи:

istream &seekg(pos_type position);

 

ostream &seekp(pos_type position);

19.6.2. Приклади використання довільного доступу до вмісту файлу

У наведеному нижче коді програми продемонстровано механізм використання функції seekp(). Вона дає змогу задати ім'я файлу у командному рядку, а за ним – конкретний байт, який потрібно у ньому змінити. Програма потім записує у вказану позицію символ "X". Звернемо Вашу увагу на те, що оброблюваний файл повинен бути відкритим для виконання операцій зчитування-запису.

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

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

#include <fstream> // Для роботи з файлами

#include <cstdlib> // Для використання бібліотечних функцій

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

 

int main(int argc, char *argv[])

{

if(argc !=3) {

cout << "Застосування: ім'я_програми "

<< "<ім'я_файлу> <байт>\n";

return 1;

}

 

fstream out(argv[1], ios::in | ios::out | ios::binary);

if(!out) {

cout << "Не вдається відкрити файл.\n";

return 1;

}

 

out.seekp(atoi(argv[2]), ios::beg);

 

out.put('X');

out.close();

 

getch(); return 0;

}

У наведеному нижче коді програми показано приклад використання функції seekg(). Вона відображає вміст файлу, починаючи з позиції, що задається у командному рядку.

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

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

#include <fstream> // Для роботи з файлами

#include <cstdlib> // Для використання бібліотечних функцій

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

 

int main(int argc, char *argv[])

{

char ch;

if(argc !=3) {

cout << "Застосування: ім'я_програми "

<< "<ім'я_файлу> <стapтова_позиція>\n";

return 1;

}

 

ifstream in(argv[1], ios::in | ios::binary);

if(!in) {

cout << "Не вдається відкрити файл.\n";

return 1;

}

 

in.seekg(atoi(argv[2]), ios::beg);

while(in.get(ch)) cout << ch;

 

getch(); return 0;

}

19.7. Використання перевантажених операторів введення-виведення даних під час роботи з файлами

Вище у цьому розділі Ви дізналися, як перевантажувати оператори введення-виведення для власних класів, а також як створювати власні маніпулятори. У наведених вище прикладах програм виконувалися тільки операції консольного введення-виведення. Але, оскільки всі С++-потоки однакові, то одну і ту саму операторну функцію виведення даних, наприклад, можна використовувати для виведення інформації як на екран, так і у файл, не вносячи при цьому ніяких істотних змін. Саме у цьому і полягають основні переваги С++-системи введення-виведення.

У наведеному нижче коді програми використовується перевантажений (для класу kooClass оператор виведення даних для запису значень координат у файл threed.

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

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

#include <fstream> // Для роботи з файлами

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

 

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

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

public:

kooClass(int a, int b, int c) { x = а; 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);

ofstream out("threed");

 

if(!out) {

cout << "Не вдається відкрити файл.";

return 1;

}

 

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

out << A_ob << B_ob << C_ob;

 

out.close();

 

getch(); return 0;

}

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

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


Розділ 20. Динамічна ідентифікація типів і оператори приведення типу

У цьому розділі розглядаються два засоби мови програмування C++, які підтримують сучасне ООП: динамічна ідентифікація типів (run-time type identification – RTTI) і набір додаткових операторів приведення типу. Жоден з цих засобів не був частиною оригінальної специфікації мови програмування C++, але обидва вони були додані з метою посилення підтримки поліморфізму часу виконання. Під RTTI розуміють можливість проведення ідентифікації типу об'єкта у процесі виконання програми. Оператори приведення типу, що розглядаються у цьому розділі, пропонують програмісту безпечніші способи виконання цієї операції. Як буде показано далі, один з них dynamic_cast, безпосередньо пов'язаний з RTTI-іденти­фі­ка­цією, тому оператори приведення типу і RTTI є сенс розглядати в одному розділі.

20.1. Динамічна ідентифікація типів (RTTI)

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

20.1.1. Отримання типу об'єкта у процесі виконання програми

Для отримання типу об'єкта у процесі виконання програми використовують оператор typeid.

Для цього необхідно приєднати до програми заголовок <typeinfо>. Найпоширеніший формат використання оператора typeid такий:

typeid(object)

У цьому записі елемент object означає об'єкт, тип якого потрібно отримати. Можна запрошувати не тільки вбудований тип, але і тип класу, створеного програмістом. Оператор typeid повертає посилання на об'єкт типу type_info, який описує тип об'єкта object.

У класі type_info визначено такі public-члени:

bool operator==(const type_info &ob);

bool operator!=(const type_info &ob);

bool before(const type_info &ob);

const char *name();

Перевантажені оператори "==" і "!=" слугують для порівняння типів. Функція before() повертає значення true, якщо викликаючий об'єкт у порядку зіставлення знаходиться перед об'єктом (елементом ob|), що використовується як параметр[79]. Функція name() повертає покажчик на ім'я типу.

Розглянемо простий приклад використання оператора typeid.

Код програми 20.1. Демонстрація механізму використання оператора typeid

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

#include <typeinfo>

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

 

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

//. . .

};

 

int main()

{

int izm, jzm;

float fzm;

myClass ob;

 

cout << "Тип змінної izm: " << typeid(izm).name();

cout << endl;

cout << "Тип змінної fzm: " << typeid(fzm).name();

cout << endl;

cout << "Тип змінної ob: " << typeid(ob).name();

cout << "\n\n";

 

if(typeid(izm) == typeid(jzm))

cout << "Типи змінних izm та jzm однакові.\n";

 

if(typeid(izm) != typeid(fzm))

cout << "Типи змінних izm та fzm неоднакові.\n";

 

getch(); return 0;

}

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

Тип змінної izm: int

Тип змінної fzm: float

Тип змінної ob: class myClass

 

Типи змінних izm та jzm однакові.

Типи змінних izm та fzm неоднакові.

Якщо оператор typeid застосовується до покажчика на поліморфний базовий клас (пригадайте: поліморфний клас – це клас, який містить хоч би одну віртуальну функцію), він автоматично повертає тип реального об'єкта, на який той вказує: будь то об'єкт базового класу або об'єкт класу, виведеного з базового.

Отже, оператор typeid можна використовувати для динамічного визначення типу об'єкта, яка адресується покажчиком на базовий клас. Застосування цієї можливості продемонстровано в такій програмі.

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

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

#include <typeinfo>

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

 

// Оголошення базового класу

class Base {

virtual void fun() {}; // Робимо клас Base поліморфним

//. . .

};

 

class DerivedA: public Base {

//. . .

};

 

class DerivedB: public Base {

//. . .

};

 

int main()

{

Base *p, baseob;

DerivedA A_ob; // Створення об'єкта класу

DerivedB B_ob; // Створення об'єкта класу

 

p = &baseob;

cout << "Змінна p вказує на об'єкт типу ";

cout << typeid(*p).name() << endl;

 

p = &A_ob;

cout << "Змінна p вказує на об'єкт типу ";

cout << typeid(*p).name() << endl;

 

p = &B_ob;

cout << "Змінна p вказує на об'єкт типу ";

cout << typeid(*p).name() << endl;

 

getch(); return 0;

}

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

Змінна p вказує на об'єкт типу Base

Змінна p вказує на об'єкт типу DerivedA

Змінна p вказує на об'єкт типу DerivedB

Якщо оператор typeid застосовується до покажчика на базовий клас поліморфного типу, то тип об'єкта, що реально адресується, як підтверджують ці результати, буде визначений у процесі виконання програми.

У всіх випадках застосування оператора typeid до покажчика на неполіморфну ієрархію класів буде отримано покажчик на базовий тип, тобто те, на що цей покажчик реально вказує, визначити не можна. Як експеримент спробуйте перетворити на коментар віртуальну функцію fun() у класі Base і подивіться на результат. Ви побачите, що тип кожного об'єкта після внесення у програму цієї зміни буде визначений як Base, оскільки саме цей тип має покажчик р.

Оскільки оператор typeid зазвичай застосовується до перейменованого покажчика (тобто до покажчика, до якого вже застосовано оператор "*"), то для оброблення ситуації, коли цей перейменований| покажчик виявиться нульовим, створено спеціальний виняток. У цьому випадку оператор typeid генерує виняток типу bad_typeid.

Посилання на об'єкти ієрархії поліморфних класів працюють подібно до покажчиків. Якщо оператор typeid застосовується до посилання на поліморфний клас, то він повертає тип об'єкта, на який вона реально посилається, і це може бути об'єкт не базового, а похідного типу. Описаний засіб найчастіше використовується під час передачі об'єктів функціям за посиланням. Наприклад, у наведеному нижче коді програми функція WhatType() оголошує посилальний параметр на об'єкти типу Base. Це означає, що функції WhatType() можна передавати посилання на об'єкти типу Base або посилання на об'єкти будь-яких класів, похідних від Base. Оператор typeid, що застосовується до такого параметра, поверне реальний тип об'єкта, що передається функції.

Код програми 20.3. Демонстрація механізму застосування оператора typeid до посилального параметра

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

#include <typeinfo>

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

 

// Оголошення базового класу

class Base {

virtual voidfun() {; // робимо клас Base поліморфним

//. . .

};

 

class DerivedA: public Base {

//. . .

};

 

class DerivedB: public Base {

//. . .

};

 

// Демонструємо застосування оператора typeid до посилального параметра.

void WhatType(Base &ob)

{

cout << "Параметр ob посилається на об'єкт типу ";

cout << typeid(ob).name() << endl;

}

 

int main()

{

int izm;

 

Base baseob;

DerivedA A_ob; // Створення об'єкта класу

DerivedB B_ob; // Створення об'єкта класу

 

WhatType(baseob);

WhatType(A_ob);

WhatType(B_ob);

 

getch(); return 0;

}

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

Параметр ob посилається на об'єкт типу Base

Параметр ob посилається на об'єкт типу DerivedA

Параметр ob посилається на об'єкт типу DerivedB

Існує ще одна версія застосування оператора typeid, яка як аргумент приймає ім'я типу. Формат його є таким:

typeid(ім'я_типа)

Наприклад, наступна настанова є абсолютно допустимою:

cout << typeid(int).name();

Призначення цієї версії оператора typeid – отримати об'єкт типу type_info (який описує заданий тип даних), щоб його можна було використовувати в настанові порівняння типів.

20.1.2. Приклад RTTI-застосування

У наведеному нижче коді програми показано, наскільки корисним може бути засіб динамічної ідентифікації типів (RTTI). Тут використовується модифікована версія ієрархії класів геометричних фігур з розд. 16, який обчислює площу круга, трикутника і прямокутника. У наведеному нижче коді програми визначена функція factory(), призначена для створення примірника круга, трикутника або прямокутника. Ця функція повертає покажчик на створений об'єкт[80]. Конкретний тип створюваного об'єкта визначається в результаті звернення до функції rand() С++-генератора випадкових чисел. Таким чином, ми не можемо знати наперед, об'єкт якого типу буде згенеровано. Програма створює десять об'єктів і підраховує кількість створених фігур кожного типу. Оскільки під час виклику функції factory() може бути згенерована фігура будь-якого типу, то для визначення типу реально створеного об'єкта у програмі використовують оператор typeid.

Код програми 20.4. Демонстрація ефективності використання засобу динамічної ідентифікації типів

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

#include <cstdlib> // Для використання бібліотечних функцій

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

 

class figUre {

protected:

double x, y;

public:

figUre(double izm, double jzm) { x = izm; y = jzm;}

virtual double area() = 0;

};

 

class triAngle: public figUre {

public:

triAngle(double izm, double jzm): figUre(izm, jzm) {}

double area() { return x * 0.5 * y;}

};

 

class rectAngle: public figUre {

public:

rectAngle(double izm, double jzm): figUre(izm, jzm) {}

double area() { return x * y;}

};

 

class cirCle: public figUre {

public:

cirCle(double izm, double jzm=0): figUre(izm, jzm) {}

double area() { return 3.14 * x * x;}

};

 

// Генератор об'єктів класу figUre.

figUre *factory() {

switch(rand() % 3) {

case 0: return new cirCle(10.0);

case 1: return new triAngle(10.1, 5.3);

case 2: return new rectAngle(4.3, 5.7);

}

return 0;

}

 

int main()

{

figUre *p; // Покажчик на базовий клас

int i;

int t = 0, r = 0, c = 0;

 

// Генеруємо і підраховуємо об'єкти

for(i=0; i<10; i++) {

p = factory(); // Генеруємо об'єкт

cout << "Об'єкт має тип " << typeid(*p).name();

cout << ". ";