67374

Створення власних маніпуляторних функцій

Лекция

Информатика, кибернетика и программирование

Програміст може самостійно створювати власні маніпуляторні функції. Існує два типи маніпуляторних функцій – ті, що приймають (параметризовані) і не приймають аргументи (непараметризовані). Для створення параметризованих маніпуляторів використовуються підходи...

Украинкский

2014-09-07

532 KB

0 чел.

Лекція № 29

Тема: Створення власних маніпуляторних функцій

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

   Всі непараметризовані маніпуляторні функції виведення даних мають таку структуру:

ostream &manip_name(ostream &stream)

{

          // код маніпуляторної функції

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

}

   У цьому записі елемент manip_name вказує ім'я маніпулятора. Важливо розуміти особливості використання не параметризованих маніпуляторів. Незважаючи на те, що маніпулятор приймає як єдиний аргумент покажчик на потік, який він обробляє, проте при використанні маніпулятора в остаточному виразі введення-виведення аргументи не вказуються взагалі.

   У наведеному нижче коді програми створюється маніпулятор setup(), який встановлює опцію вирівнювання лівим краєм, ширину поля в 10 символів і задає як заповнювальний символ знак долара.

Приклад. Демонстрація механізму створення маніпулятора setup()

 

#include <iostream>

#include <iomanip> // Використання маніпуляторів введення-виведення

using namespace std;

ostream &setup(ostream &stream)

{

stream.setf(ios::left);          // Вирівнювання лівим краєм

   /* Встановлює ширину поля в 10 символів і задає як заповнювальний

      символ знак долара */

stream << setw(10) << setfill('$');

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

}

void main()

{

cout << 10 << " " << setup << 10;

}

   Створення власних маніпуляторів є корисним з двох причин:

  •  по-перше, іноді виникає потреба виконувати операції введення-виведення даних з використанням пристрою, до якого жоден з вбудованих маніпуляторів не застосовується (наприклад, плоттер). У цьому випадку створення власних маніпуляторів зробить виведення даних на цей пристрій зручнішим;
  •   по-друге, може так статися, що у створеній Вами програмі певна послідовність настанов повторюється декілька разів. І тоді ці операції можна об'єднати в один маніпулятор так, як це показано у попередній програмі.

   Всі непараметризовані маніпуляторні функції введення даних мають таку структуру:

istream &manip_name(istream &stream)

{

// код маніпуляторної функції

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

}

   Наприклад, у наведеному нижче коді програми створюється маніпулятор prompt(). Він налаштовує вхідний потік на прийняття даних у шістнадцятковому представленні та виводить для користувача відповідне повідомлення.

Приклад. Демонстрація механізму створення маніпулятора prompt()

 

istream &prompt(istream &stream)

{

cin >> hex;

cout << "Введіть число в шістнадцятковому форматі: ";

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

}

void main()

{

 int c;

cin >> prompt >> c;

cout << c; // Виведення числа в шістнадцятковому форматі

}

Тема: Файлове введення-виведення даних

План

  1.  Організація файлового введення-виведення даних
  2.  Відкриття та закриття файлу
  3.  Зчитування та запис текстових файлів
  4.  Неформатне введення-виведення даних у двійковому режимі
  5.  Зчитування та записування у файл блоків даних
  6.  Використання функції eof() для виявлення кінця файлу
  7.   Застосування C++ файлової системи для порівняння файлів
  8.  Використання інших функцій для двійкового введення-виведення даних
  9.  Перевірка статусу введення-виведення даних

  1.  Організація файлового введення-виведення даних

   У С++-системі введення-виведення також передбачено засоби для виконання відповідних операцій з використанням файлів. Файлові операції введення-виведення даних можна реалізувати після внесення у програму заголовка <fstream>, у якому визначено всі необхідні для цього класи і значення.

  1.  Відкриття та закриття файлу

   У мові програмування C++ файл відкривається шляхом зв'язування його з потоком. Як уже зазначалося вище, існують потоки трьох типів: введення, виведення і введення-виведення. Щоб відкрити вхідний потік, необхідно оголосити потік класу ifstream. Для відкриття вихідного потоку потрібно оголосити потік классу ofstream. Потік, який передбачається використовувати для операцій як введення, так і виведення, повинен бути оголошений як потік класу fstream. Наприклад, у процесі виконання такого фрагмента коду програми буде створено вхідний і ви-

хідний потоки, а також потік, що дає змогу виконувати операції в обох напрямах:

ifstream in;             // Вхідний потік

ofstream out;          // Вихідний потік

fstream both;         // Потік введення-виведення

Щоб відкрити файл використовується функція open().

   Створивши потік, його потрібно пов'язати з файлом. Це можна зробити за допомогою функції open(), причому у кожному з трьох потокових класів є своя функція-член open(). Їх прототипи мають такий вигляд:

void ifstream::open(const char *filename, ios::openmode mode = ios::in);

void ofstream::open(const char * filename, ios::openmode mode = ios::out | ios::trunc);

void fstream::open(const char *filename, ios::openmode mode = ios::in | ios::out);

   У цих записах елемент filename означає ім'я файлу, яке може містити специфікатор шляху, що вказує доступ до нього. Елемент mode називається специфікатором режиму, який визначає спосіб відкриття файлу. Він повинен приймати одне або декілька значень перерахунку openmode, який визначено у класі ios:

  •   ios::арр – приєднує до кінця файлу усі дані, що виводяться;
  •   ios::ate – пошук потрібних даних починатиметься з кінця файлу;
  •   ios::binary – відкриває файл у двійковому режимі;
  •   ios::in – забезпечує відкриття файлу для введення даних;
  •   ios::out – забезпечує відкриття файлу для виведення даних
  •   ios::trunc – призводить до руйнування вмісту файлу.

 

   Декілька значень перерахунку openmode можна об'єднувати за допомогою логічного додавання (АБО).

За замовчуванням усі файли відкриваються в текстовому режимі.

  1.  Внесення значення ios::арр у параметр mode забезпечить приєднання до кінця файлу всіх даних, які виводяться. Це значення можна застосовувати тільки до файлів, відкритих для виведення даних.
  2.  Під час відкриття файлу з використанням значення ios::ate пошук потрібних даних починатиметься з кінця файлу. Незважаючи на це, операції введення-виведення даних можуть, як і раніше, виконуватися по всьому файлу.
  3.  Значення ios::binary дає змогу відкрити файл у двійковому режимі. Як уже зазначалося вище, в текстовому режимі можуть відбуватися деякі перетворення символів (наприклад, послідовність, що складається з символів повернення каретки і переходу на новий рядок, може бути перетворена у символ нового рядка). Під час відкриття файлу у двійковому режимі ніякого перетворення символів не здійснюється.
  4.  Значення ios::in вказує на те, що цей файл відкривається для введення даних, а значення ios::out забезпечує відкриття файлу для виведення даних.
  5.  Використання значення ios::trunc призводить до руйнування вмісту файлу, ім'я якого збігається з параметром filename, а сам цей файл урізається до нульової довжини. При створенні вихідного потоку типу ofstream будь-який наявний файл з іменем filename автоматично урізається до нульової довжини.

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

   У процесі виконання такий фрагмент коду програми відкриває звичайний вихідний файл:

ofstream myStream;

out.open("тест");

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

   У результаті невдалого виконання функції open() не відкритому потоку при використанні булевого виразу встановлюється значення, яке дорівнює ФАЛЬШ. Такий механізм може слугувати для підтвердження успішного відкриття файлу, наприклад, за допомогою такої if-настанови:

if(!myStream)

{

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

    // оброблення помилки

}

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

   Рекомендується також перевірити факт успішного відкриття файлу за допомогою функції is_open(), яка є членом класів fstream, ifstream і ofstream. Її прототип має такий вигляд:

bool is_open();

   Ця функція повертає значення ІСТИНА, якщо потік пов'язаний з відкритим файлом, і ФАЛЬШ – в іншому випадку. Наприклад, використовуючи такий фрагмент коду програми, можна дізнатися про те, чи відкрито у цей момент потоковий об'єкт myStream:

if(!myStream.is_open())

{

     cout << "Файл не відкрито" << endl;

     //. . .

}

  Хоча цілком коректно використовувати функцію open() для відкриття файлу, проте здебільшого це робиться по-іншому, оскільки класи ifstream, ofstream і fstream містять конструктори, які автоматично відкривають заданий файл. Параметри у цих конструкторів і їх значення (що діють за замовчуванням) збігаються з параметрами і відповідними значеннями функції open(). Тому найчастіше файл відкривається так, як це показано в наведеному нижче прикладі:

ifstream myStream("myFile");      // Файл відкривається для введення

   Якщо з якоїсь причини файл відкрити неможливо, то потоковій змінній, що пов'язується з цим файлом, встановлюється значення, яке дорівнює ФАЛЬШ. Щоб закрити файл, використовується функція-член close(). Наприклад, щоб закрити файл, який пов'язано з потоковим об'єктом myStream, потрібно використати таку настанову:

myStream.close();

   Функція close() не має параметрів і не повертає ніякого значення.

  1.   Зчитування та запис текстових файлів

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

Приклад. Демонстрація механізму запису даних у файл

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

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

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

int main()

{

ofstream out("test");

 if(!out)

{

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

 return 1;

}

out << 10 << " " << 123.23 << endl;

out << "Це короткий текстовий файл.";

out.close();

}

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

Приклад. Демонстрація механізму зчитування даних з файлу

int main()

{         char ch;

 int c;

 float f;

 char str[80];

ifstream in("test");

 if(!in)

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

 return 1;

}

in >> c;

in >> f;

in >> ch;

in >> str;

cout << c << " " << f << " " << ch << endl;

cout << str;

in.close();

}

При використанні перевантаженого оператора “<<” для зчитування даних з текстових файлів відбувається перетворення деяких символів. Наприклад, «пропускні» символи опускаються. Якщо необхідно запобігти будь-яким перетворенням символів, то потрібно відкрити файл у двійковому режимі доступу до його даних.

  1.   Неформатне введення-виведення даних у двійковому режимі

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

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

Функція get() зчитує символ з файлу, а функція put() записує символ у файл.

   У загальному випадку існує два способи запису неформатних двійкових даних у файл і зчитування їх з файлу. Перший спосіб полягає у використанні функції-члена класу put() (для запису байта у файл) і функції-члена класу get() (для зчитування байта з файлу). Другий спосіб передбачає застосування "блокових" С++-функцій введення-виведення read() і write(). Розглянемо кожен спосіб окремо.

   Функції get() і put() мають багато форматів, але найчастіше використовуються такі їх версії:

istream &get(char &ch);

ostream &put(char ch);

  1.  Функція get() зчитує один символ з відповідного потоку і поміщає його значення у змінну ch. Вона повертає посилання на потік, що пов'язаний із заздалегідь відкритим файлом. Досягнувши кінця цього файлу, значення посилання дорівнюватиме нулю.
  2.  Функція put() записує символ ch у потік і повертає посилання на цей потік.

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

Приклад. Демонстрація механізму відображення вмісту файлу за допомогою функції get()

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

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

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

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

{

 char ch;

 if(argc !=2)

{

 cout << "Застосування: ім'я_програми <ім'я_файлу>" << endl;

 return 1;

}

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

 if(!in)

{

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

 return 1;

}

 while(in)

{  // Досягши кінця файлу потоковий об'єкт in прийме значення false.

 in.get(ch);

 if(in) cout << ch;

}

in.close();

}

   Досягши кінця файлу, потоковий об'єкт in прийме значення ФАЛЬШ, яке зупинить виконання циклу while. Проте існує дещо коротший варіант коду програми організації циклу, призначеного для зчитування і відображення вмісту файлу:

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

   Цей варіант організації циклу також має право на існування, оскільки функція get() повертає потоковий об'єкт in, який, досягши кінця файлу, прийме значення false. У наведеному нижче коді програми для запису рядка у файл використовується функція put().

Приклад. Демонстрація механізму використання функції put() для запису рядка у файл

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

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

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

int main()

{

 char *p = "Всім привіт!";

           ofstream out("test", ios::out | ios::binary);

 if(!out)

{

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

 return 1;

}

 while(*p) out.put(*p++);

}

  1.   Зчитування та записування у файл блоків даних

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

istream &read(char *buf, streamsize num);

ostream &write(const char *buf, int streamsize num);

  1.  Функція read() зчитує num байт даних з пов'язаного з файлом потоку і поміщає їх у буфер, який адресується покажчиком buf.
  2.   Функція write() записує num байт даних у пов'язаний з файлом потік з буфера, який адресується покажчиком buf.

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

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

Приклад. Демонстрація механізму використання функцій read() і write()

int main()

{

 int n[5] = {1, 2, 3, 4, 5};

 register int i;

ofstream out("test", ios::out | ios::binary);

 if(!out)

{

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

 return 1;

}

out.write((char *) &n, sizeof n);

out.close();

 for(i=0; i<5; i++)

 n[i] = 0; // Очищує масив

ifstream in("test", ios::in | ios::binary);

 if(!in)

{

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

 return 1;

}

in.read((char *) &n, sizeof n);

 for(i=0; i<5; i++) // Відображаємо значення, зчитані з файлу.

 cout << n[i] << " ";

in.close();

}

  Зверніть увагу на те, що в настановах звернення до функцій read() і write() виконуються операції приведення типу, які є обов'язковими при використанні буфера, що визначається у вигляді не символьного масиву.

Функція gcount() повертає кількість символів, зчитаних у процесі виконання останньої операції введення даних.

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

streamsize gcount();

   Функція gcount() повертає кількість символів, зчитаних у процесі виконання останньої операції введення даних.

  1.   Використання функції eof() для виявлення кінця файлу

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

bool eof();

   Ця функція повертає значення true у випадку досягнення кінця файлу; інакше вона повертає значення false.

   У наведеному нижче коді програми для виведення на екран вмісту файлу використовується функція eof().

Приклад. Демонстрація механізму виявлення кінця файлу за допомогою функції eof()

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

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

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

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

{

 char ch;

 if(argc != 2)

{

 cout << "Застосування: ім'я_програми <ім'я_файлу>" << endl;

 return 1;

}

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

 if(!in)

{

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

 return 1;

}

 while(!in.eof())

{ // Використання функції eof()

 in.get(ch);

 if(!in.eof()) cout << ch;

}

in.close();

}

  1.   Застосування C++ файлової системи для порівняння файлів

   Наведений нижче код програми ілюструє потужність і простоту застосування у мові програмування C++ файлової системи. Тут порівнюються два файли за допомогою функцій двійкового введення-виведення read(), eof() і gcount(). Програма спочатку відкриває порівнювані файли для виконання двійкових операцій (щоб не допустити перетворення символів). Потім з кожного файлу по черзі зчитуються блоки інформації у відповідні буфери і порівнюється їхній вміст. Оскільки об'єм зчитаних даних може бути меншим за розмір буфера, то у програмі використовується функція gcount(), яка точно визначає кількість зчитаних у буфер байтів.

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

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

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

{

 register int i;

 unsigned char buf1[1024], buf2[1024];

 if(argc !=3)

{

 cout << "Застосування: ім'я_програми <ім'я_файла1> " << "<ім'я_файла2>" <<               

                                      endl;

 return 1;

}

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

 if(!f1)

{

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

 return 1;

}

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

 if(!f2)

{

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

 return 1;

}

cout << "Порівняння файлів" << endl;

 do

{

 f1.read((char *) buf1, sizeof buf1);

 f2.read((char *) buf2, sizeof buf2);

 if(f1.gcount() != f2.gcount())

 {

  cout << "Файли мають різні розміри" << endl;

  f1.close();

  f2.close();

 }

      // Порівняння вмісту буферів.

 for(i=0; i<f1.gcount(); i++)

  if(buf1[i] != buf2[i])

  {

   cout << "Файли різні" << endl;

   f1.close();

   f2.close();

  }

}

 while(!f1.eof() && !f2.eof());

cout << "Файли однакові" << endl;

f1.close();

f2.close();

}

  1.   Використання інших функцій для двійкового введення-виведення даних

   Крім наведеного вище формату використання функції get() існують і інші її перевизначені версії. Наведемо прототипи для трьох з них, які використовуються найчастіше:

istream &get(char *buf, streamsize num);

istream &get(char *buf, streamsize num, char delim);

int get();

  1.  Перша версія функції get() дає змогу зчитувати символи і заносити їх у масив, який задається параметром buf, доти, доки не буде зчитано num-1 символів, або не трапиться символ нового рядка, або не буде досягнуто кінець файлу. Після завершення роботи функції get() масив, який адресується покажчиком buf, матиме завершальний нуль-символ. Символ нового рядка, якщо такий виявиться у вхідному потоці, не вилучається. Він залишається там доти, доки не виконається наступна операція введення-виведення.
  2.  Друга версія функції get() призначена для зчитування символів і занесення їх у масив, який адресується покажчиком buf, доти, доки не буде зчитано num-1 символів, або не виявиться символ, який задається параметром delim, або не буде досягнуто кінець файлу. Після завершення роботи функції get() масив, який адресується покажчиком buf, матиме завершальний нуль-символ. Символ-роздільник (заданий параметром delim) , якщо такий виявиться у вхідному потоці, не вилучається. Він залишається там доти, доки не виконається наступна операція введення-виведення.
  3.  Третя перевизначена версія функції get() повертає з потоку наступний символ. Він міститься в молодшому байті значення, що повертається функцією. Отже, значення, що повертається функцією get(), можна присвоїти змінній типу char. Досягши кінця файлу, ця функція повертає значення EOF, яке визначено у заголовку <iostream>.

   Функцію get() корисно використовувати для зчитування рядків, що містять пропуски. Як уже зазначалося вище, якщо для зчитування рядка використовують оператор ">>", то процес введення даних зупиняється внаслідок виявлення першого ж пропускного символу. Це робить оператор ">>" не придатним для зчитування рядків, що містять пропуски. Але цю проблему, як це показано в такому коді програми, можна обійти за допомогою функції get(buf, num).

Приклад. Демонстрація механізму використання функції get() для зчитування рядків, що

                 містять пропуски

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

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

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

void main()

{

 char str[80];

cout << "Введіть ім'я: ";

cin.get(str, 79);

cout << str << endl;

}

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

   Розглянемо ще одну функцію, яка дає змогу вводити дані. Йдеться про функцію getline(), яка є членом кожного потокового класу, призначеного для введення інформації. Ось як виглядають прототипи версій цієї функції:

istream &getline(char *buf, streamsize num);

istream &getline(char *buf, streamsize num, char delim);

  1.  При використанні першої версії функції getline()символи зчитуються і заносяться у масив, який адресується покажчиком buf, доти, доки не буде зчитано num-1 символів, або не трапиться символ нового рядка, або не буде досягнуто кінець файлу. Після завершення роботи функції getline() масив, який адресується покажчиком buf, матиме завершальний нуль-символ. Символ нового рядка, якщо такий виявиться у вхідному потоці, при цьому вилучається, але не заноситься у масив buf.
  2.  Друга версія функції getline()призначена для зчитування символів і занесення їх у масив, який адресується покажчиком buf, доти, доки не буде зчитано num-1 символів, або не виявиться символ, який задається параметром delim, або не буде досягнуто кінець файлу. Після завершення роботи функції getline() масив, який адресується покажчиком buf, матиме завершальний нуль-символ. Символ-роздільник (який задаються параметром delim) , якщо такий виявиться у вхідному потоці, вилучається, але не заноситься у масив buf.

   Як бачите, ці дві версії функцій getline() практично ідентичні версіям get(buf, num) і get(buf, num, delim) функції get(). Обидві зчитують символи з вхідного потоку і заносять їх у масив, який адресується покажчиком buf, доти, доки не буде зчитано num-1 символів, або не виявиться символ, який задається параметром delim. Відмінність між функціями get() і getline() полягає у тому, що функція getline() зчитує і видаляє символ-роздільник з вхідного потоку, а функція get() цього не робить.

   Наступний символ з вхідного потоку можна отримати і не видаляти його з потоку за допомогою функції peek(). Її прототип має такий вигляд:

int peek();

   Функція peek() повертає наступний символ потоку, або значення EOF, якщо досягнуто кінець файлу. Зчитаний символ повертається в молодшому байті значення, що повертається функцією. Тому значення, що повертається функцією peek(), можна присвоїти змінній типу char.

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

istream &putback(char сh);

   У цьому записі параметр сh містить символ, що зчитується з потоку останнім.

   Під час виведення даних за допомогою функції flush()не відбувається негайного їх запису на фізичний пристрій, що у даний момент пов'язаний з потоком. Інформація, яка підлягає виведенню, спочатку нагромаджується у внутрішньому буфері доти, доки він цілком не заповниться. І тільки тоді його вміст переписується на диск. Проте існує можливість негайного перезапису на диск даних, які зберігається в буфері функції flush(), не чекаючи його повного заповнення. Цей засіб полягає у виклику функції flush(). Її прототип має такий вигляд:

ostream &flush();

   До викликів функції flush() необхідно вдаватися у випадку, якщо программа призначена для роботи в несприятливих середовищах (для яких характерні часті відключення, наприклад, електрики).

  1.   Перевірка статусу введення-виведення даних

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

 

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

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

iostate rdstate();

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

  1.  По-друге, про наявність помилки можна дізнатися за допомогою однієї або

декількох наступних функцій-членів класу ios:

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

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

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

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

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

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

Тема: Використання файлів довільного доступу

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

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

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

istream &seekg(off_type offset, seekdir origin);

ostream &seekp(off_type offset, seekdir origin);

 

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

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

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

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

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

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

  •  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);

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

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

Приклад. Демонстрація механізму використання функції seekp() для довільного доступу

                 до вмісту файлу

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

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

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

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

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

{

 if(argc !=3)

{

 cout << "Застосування: ім'я_програми " << "<ім'я_файлу> <байт>" << endl;

 return 1;

}

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

 if(!out)

{

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

 return 1;

}

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

out.put('X');

out.close();

}

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

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

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

{

 char ch;

 if(argc !=3)

{

 cout << "Застосування: ім'я_програми " << "<ім'я_файлу> <стapтова_позиція>" << endl;

 return 1;

}

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

 if(!in)

{

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

 return 1;

}

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

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

}

Тема: Перевантаження операторів введення-виведення даних при роботі з

          файлами

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

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

Приклад. Демонстрація механізму використання перевизначеного оператора введення-

                 виведення даних для запису об'єктів класу у файл

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

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

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);

};

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

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

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

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

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

stream << obj.z << endl;

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

}

int main()

{          kooClass ObjA(1, 2, 3), ObjB(3, 4, 5), ObjC(5, 6, 7);

ofstream out("threed");

 if(!out)

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

 return 1;

}

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

out << ObjA << ObjB << ObjC;

out.close();

}

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


 

А также другие работы, которые могут Вас заинтересовать

5169. Система управления качеством продукции предприятия. Повышение качества 356 KB
  Система управления качеством продукции предприятия 1.1 Качество как экономическая категория. Сущность управления качеством Вопросы повышения качества продукции всегда были объектом пристального общественного внимания. Перенос центра внимания с колич...
5170. Содержание и социально-экономическая сущность маркетинга 150 KB
  Содержание и социально-экономическая сущность маркетинга Термин маркетинг происходит от английского market (рынок) и означает деятельность в сфере рынка сбыта. Подчинение всех функций управления задаче сбыта продукции явл...
5171. Современный подход к управлению качеством 79 KB
  Современный подход к управлению качеством Современная концепция управления качеством. Стандартизация и сертификация продукции и системы управления качеством. Всеобщее управление качеством и управление предприятием. 1 Современная ко...
5172. Реформирование и развитие предприятий промышленного комплекса 75.5 KB
  Реформирование и развитие предприятий промышленного комплекса Реформирование предприятий при трансформации экономики. Адаптация предприятий к новым условиям хозяйствования. Реабилитация промышленного предприятия при его рефор...
5173. Формы и особенности международной интеграции производства 77.5 KB
  Формы и особенности международной интеграции производства Базовые стратегии развития бизнеса. Межфирменная интеграция и диверсификация производства. Формы и уровни международной интеграции производства. 1 Базовые стратегии развития...
5174. Предмет, задачи и методы генетики. 756 KB
  Предмет, задачи и методы генетики. Генетика - наука об основных закономерностях наследственности и изменчивости. Наследственность - это свойство живых организмовприобретать в процессе онтогенеза признаки сходные с родительскими организмами и п...
5175. Понятия, ген, генотип и фенотип. Фенотипическая и генотипическая изменчивость, мутации 247.5 KB
  Понятия, ген, генотип и фенотип. Фенотипическая и генотипическая изменчивость, мутации. При изучении закономерностей наследования обычно скрещивают особи, отличающиеся друг от друга альтернативными признаками, например желтый и зеленый цвет, гладкая...
5176. Концепции клеточного строения и функционирования живой материи 699 KB
  Концепции клеточного строения и функционирования живой материи I. История открытия клеточного строения живых организмов Все живые организмы построены из клеток. Одноклеточные организмы (бактерии, простейшие, многие водоросли и грибы) состоят из одно...
5177. Закон гомологических рядов 37.5 KB
  Закон гомологических рядов Обработка обширного материала наблюдений и опытов, детальное исследование изменчивости многочисленных линнеевских видов (линнеонов), огромное количество новых фактов, полученных главным образом при изучении культурных раст...