Варіанти індивідуальних завдань

Мета роботи

 

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

 

3.2 Організація самостійної роботи студентів

 

Під час підготовки до виконання лабораторної роботи необхідно вивчити індивідуальне завдання (п. 3.3), виконати розробку алгоритму вирішення задачі та підготовити текст програми щодо реалізації розробленого алгоритму, підготовити відповідні розділи звіту та вивчити відповідний теоретичний матеріал, який викладено у лекціях: „Керуючі конструкції мови С++”, „Технологія програмування на Сі: представлення матриць, робота з файлами та текстами”.

Спеціального типу даних матриця або багатомірний масив у Сі нема, однак, можна використовувати масив елементів типу масив. Наприклад, змінна a представляє матрицю розміру 3×3 з дійсними елементами: double a[3][3];

Елементи матриці розташовуються в пам'яті послідовно по рядках: спочатку йдуть елементи рядка з індексом 0, потім рядка з індексом 1, наприкінці рядка з індексом 2. При цьому вираз a[i], де i…і ціла змінна, є покажчиком на початковий елемент i-го рядка і має тип double*. Для звертання до елемента матриці треба записати його індекси у квадратних дужках, наприклад, вираз a[i][j] представляє собою елемент матриці a у рядку з індексом i та стовпці з індексом j. Елемент матриці можна використовувати в будь-якому виразі як звичайну змінну (наприклад, можна читати його значення або привласнювати нове).

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

Нехай потрібна матриця, розмір якої визначається під час роботи програми. Тоді простір під неї треба захоплювати в динамічній пам'яті за допомогою оператора new. При цьому в динамічній пам'яті захоплюється лінійний масив та повертається покажчик на нього. Розглянемо дійсну матрицю розміром m рядків на n стовпців. Захоплення пам'яті виконується за допомогою оператора new:

 

double *a;

int m, n;

. . .

a = new double[m * n];

 

При цьому елементи матриці будуть розташовуватися в масиві в такий спосіб: спочатку йдуть елементи рядка з індексом 0, потім елементи рядка з індексом 1 і т.д., останніми йдуть елементи рядка з індексом m - 1. Кожний рядок складається з n елементів, отже, індекс елемента рядка i та стовпця j у лінійному масиві дорівнює: i * n + j. Дійсно, оскільки індекси починаються з нуля, то i дорівнює кількості рядків, які потрібно пропустити, i × n – сумарна кількість елементів у рядках, які пропускаються, число j дорівнює зміщенню усередині останнього рядка. Таким чином, елементу матриці в рядку i та стовпці j відповідає вираз: a[i * n + j].

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

Багатомірні масиви реалізуються аналогічно матрицям. Наприклад, дійсний тривимірний масив розміру 4 × 4 × 2 описується як: double a[4][4][2];звертання до його елемента з індексами x, y, z здійснюється за допомогою виразу: a[x][y][z].

Для роботи з файлами у Сі використовується набір функцій стандартної бібліотека.

Для доступу до файлу застосовується тип даних FILE. Це структурний тип, ім'я якого задано за допомогою оператора typedef у стандартному заголовному файлі "stdio.h". Прототип функції відкриття файлу має такий вигляд:

FILE *fopen(const char *path, const char *mode);

Тут path – шлях до файлу (наприклад, ім'я файлу або абсолютний шлях до файлу), mode – режим відкриття файлу.

Значення символів у рядку mode зведені в наступну таблицю:

r Відкрити існуючий файл для читання
w Відкрити файл для запису. Старий вміст файлу губиться, у випадку відсутності файлу він створюється.
a Відкрити файл для запису. Якщо файл існує, то запис виконується в його кінець.
t Відкрити текстовий файл.
b Відкрити бінарний файл.
+ Дозволити як читання, так і запис.

 

Кілька прикладів відкриття файлів:

FILE *f, *g, *h;. . .// 1. Відкрити текстовий файл "abcd.txt" для читанняf = fopen("abcd.txt", "rt");// 2. Відкрити бінарний файл "c:\Windows\Temp\tmp.dat"// для читання й записуg = fopen("c:/Windows/Temp/tmp.dat", "wb+");// 3. Відкрити текстовий файл //"c:\Windows\Temp\abcd.log"// для дописування в кінець файлуh = fopen("c:\\Windows\\Temp\\abcd.log", "at");

 

При удачі функція fopen повертає ненульовий покажчик на структуру типу FILE, що описує параметри відкритого файлу. Цей покажчик треба потім використовувати у всіх файлових операціях. У випадку невдачі (наприклад, при спробі відкрити на читання неіснуючий файл) вертається нульовий покажчик. При цьому глобальна системна змінна errno, описана в стандартному заголовному файлі "errno.h", містить чисельний код помилки. У випадку невдачі при відкритті файлу цей код можна роздрукувати, щоб одержати додаткову інформацію. Наприклад:

#include <stdio.h>#include <errno.h>. . .FILE *f = fopen("filnam.txt", "rt");if (f == NULL) { printf( "Помилка відкриття файлу з кодом %d\n", errno ); . . .}

У наведеному вище прикладі при відкритті файлу функція fopen у випадку помилки повертає нульовий покажчик на структуру FILE. Щоб перевірити, чи відбулася помилка, необхідно порівняти повернуте значення з нульовим покажчиком. Для наочності стандартний заголовний файл "stdio.h" визначає символічну константу NULL як нульовий покажчик на тип void:

#define NULL ((void *) 0)

 

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

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

#include <stdio.h>. . .FILE *f = fopen("filnam.txt", "rt");if (f == 0) { perror("Не можу відкрити файл на читання"); . . .}

Необхідно пам’ятати, що тільки після того як файл відкритий, можна читати інформацію з файлу або записувати інформацію у файл. Для виконання цих дій можуть використовуватися різні функції. Розглянемо спочатку функції бінарного читання та запису fread і fwrite. Вони називаються бінарними тому, що не виконують ніякого перетворення інформації при введенні або виведенні (з одним невеликим виключенням при роботі з текстовими файлами, що буде розглянута нижче): інформація зберігається у файлі як послідовність байтів у тому ж вигляді, у якому вона зберігається в пам'яті комп'ютера.

Функції читання fread має наступний прототип:

size_t fread( char *buffer, // Масив для читання даних size_t elemSize, // Розмір одного елемента size_t numElems, // Число елементів для читання FILE *f // Покажчик на структуру FILE);

 

Тут size_t визначений як беззнаковий цілий тип у системних заголовних файлах. Функція намагається прочитати numElems елементів з файлу, що задається покажчиком f на структуру FILE, розмір кожного елемента дорівнює elemSize. Функція повертає число прочитаних елементів, що може бути менше, ніж numElems, у випадку кінця файлу або помилки читання. Покажчик f повинен бути повернутий функцією fopen у результаті успішного відкриття файлу. Приклад використання функції fread:

FILE *f;double buff[100];size_t res;f = fopen("tmp.dat", "rb"); // Відкриваємо файлif (f == 0) { // При помилці відкриття файлу // надрукувати повідомлення про помилку perror("Не можу відкрити файл для читання"); exit(1); // завершити роботу з кодом 1}// Читаємо 100 дійсних чисел з файлуres = fread(buff, sizeof(double), 100, f);// res дорівнює реальній кількості прочитаних чисел

 

У цьому прикладі файл "tmp.dat" відкривається на читання як бінарний, з нього читається 100 дійсних чисел розміром 8 байт кожне. Функція fread повертає реальну кількість прочитаних чисел, що менше або дорівнює 100.

Функція fread читає інформацію у вигляді потоку байтів і в незмінному вигляді поміщає їх у пам'ять. Варто розрізняти текстове подання чисел і їхнє бінарне подання! У наведеному вище прикладі числа уфайлі повинні бути записані в бінарному виді, а не у вигляді тексту. Для текстового введення чисел треба використовувати функції введення по формату.

Функція бінарного запису у файл fwrite аналогічна функції читання fread. Вона має наступний прототип:

size_t fwrite( char *buffer, // Масив записуваних даних size_t elemSize, // Розмір одного елемента size_t numElems, // Число записуваних елементів FILE *f // Покажчик на структуру FILE);

 

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

FILE *f;double buff[100];size_t num;. . .f = fopen("tmp.res", "wb"); // Відкрити файл "tmp.res"if (f == 0) { // При помилці відкриття файлу // Надрукувати повідомлення про помилку perror("Не можу відкрити файл для запису"); exit(1); // завершити роботу програми з кодом 1}// Записуємо 100 дійсних чисел у файлres = fwrite(buff, sizeof(double), 100, f);// У випадку успіху res == 100

 

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

int fclose(FILE *f);

 

У випадку успіху функція fclose повертає нуль, при помилці негативне значення (точніше, константу кінець файлу EOF, визначену в системних заголовних файлах як мінус одиниця). При помилці можна скористатися функцією perror, наприклад:

FILE *f;f = fopen("tmp.res", "wb"); // Відкрити файл "tmp.res"if (f == 0) { // При помилці відкриття файлу // Надрукувати повідомлення про помилку perror("Не можу відкрити файл для запису"); exit(1); // завершити роботу програми з кодом 1}. . .// Закрити файлif (fclose(f) < 0) { // Надрукувати повідомлення про помилку perror("Помилка при закритті файлу");

 

На відміну від функції бінарного введення fread, що вводить байти з файлу без усякого перетворення безпосередньо в пам'ять комп'ютера, функція форматного введення fscanf призначена для введення інформації з перетворенням її з текстового виду в бінарне.

Функція fprintf використовується для форматного виведення в файл. Дані при виведенні перетворюються в їхній текстовий вигляд відповідно до форматного рядка.

Найбільше часто використовувані при введенні формати функції fread наведені в таблиці:

%d Ціле десяткове число типу int
%lf Дійсне число типу double
%c Один символ типу char
%s Введення рядка. Із вхідного потоку виділяється слово, що обмежено пробілами або символами переведення рядка '\n'. Слово розміщується в масиві символів. Кінець слова відзначається нульовим байтом.

 

Приклади використання функції fscanf:

int n, m; double a; char c; char str[256]; FILE *f; . . .fscanf(f, "%d", &n); //Введення цілого числаfscanf(f, "%lf", &a); //Введення дійсного числаfscanf(f, "%c", &c); //Введення одного символуfscanf(f, "%s", str); //Введення рядка (виділяється //чергове слово із вхідного потоку) fscanf(f, "%d%d", &n, &m); // Уведення двох цілих чисел

 

Деякі типові приклади форматів для висновку наведені у таблиці:

%d виведення цілого десяткового числа
%10d виведення цілого десяткового числа, для запису числа приділяється 10 позицій, запис при необхідності доповнюється пробілами ліворуч
%lf виведення дійсного числа типу double у формі з фіксованою десятковою крапкою
%.3lf виведення дійсного число типу double з друком трьох знаків після десяткової крапки
%12.3lf виведення дійсного число типу double із трьома знаками після десяткової крапки, під число виділяється 12 позицій
%c виведення одного символу
%s кінець рядка, тобто масиву символів. Кінець рядка задається нульовим байтом

 

Прототип функції fprintf має вигляд:

 

int fprinf(FILE *f, const char *format, ...);

 

В операційній системі Windows застосовується поняття потоку введення та виведення. Потік – це послідовність байтів. Розрізняють потоки введення та виведення. Програма може читати дані з потоку введення та направляти дані в потік виведення. Програми можна запускати в конвеєрі, коли потік виведення першої програми є потоком введення іншої програми й т.д. Для запуску двох програм у конвеєрі використовується символ вертикальної риси | між іменами програм у командному рядку. Наприклад:

ab | cd | ef

 

Тут потоком введення програми є клавіатура, потоком ви виведення – термінал (або, як говорять програмісти, консоль). Потоки можна переправляти у файл або з файлу, використовуючи символи більше > і менше <, які можна представляти як лійки. Наприклад, командний рядок:

abcd > tmp.res

 

перенаправляє вихідний потік програми abcd у файл "tmp.res", тобто дані будуть виводитися у файл замість виведення на екран термінала. Відповідно, командний рядок:

abcd < tmp.dat

 

змушує програму abcd читати вихідні дані з файлу "tmp.dat" замість введення із клавіатури. Командний рядок:

abcd < tmp.dat > tmp.res

 

перенаправляє як вхідний, так і вихідний потоки: вхідний призначається на файл "tmp.dat", вихідний – на файл "tmp.res".

У Сі робота з потоком не відрізняється від роботи з файлом. Доступ до потоку здійснюється за допомогою змінною типу FILE *. На момент початку роботи програми відкриті три потоки:

· stdin – стандартний вхідний потік. За замовчуванням він призначений на клавіатуру;

· stdout – стандартний вихідний потік. За замовчуванням він призначений на екран термінала;

· stderr – вихідний потік для печатки інформації про помилки. Він також призначений за замовчуванням на екран термінала.

Змінні stdin, stdout, stderr є глобальними, вони описані в стандартному заголовному файлі "stdio.h". Операції файлового введення-виведення можуть використовувати ці потоки, наприклад, за допомогою рядка:

fscanf(stdin, "%d", &n);

 

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

fprintf(stdout, "n = %d\n", n);

 

виконується виведення значення зміною n у вихідний потік. За допомогою рядка:

fprintf(stderr, "Помилка при відкритті файлу\n");

 

виконується виведення тексту у потік stderr, який, як правило, використовується для друку повідомлень про помилки. Функція perror також виводить повідомлення про помилки в потік stderr.

За замовчуванням, стандартний вихідний потік і вихідний потік для друку помилок призначені на екран термінала. Однак операція перенаправлення виведення у файл > діє тільки на стандартний вихідний потік. Наприклад, у результаті виконання командного рядка:

abcd > tmp.res

 

результат роботи програми abcd буде записуватися у файл "tmp.res", а повідомлення про помилки, як і раніше, будуть виводитися на екрані термінала. Для того щоб перенаправляти у файл "tmp.log" стандартний потік друку помилок, необхідно використовувати командний рядок

abcd 2> tmp.log

 

(між двійкою та символом > не повинно бути пробілів!). Двійка тут означає номер потоку, що перенаправляється. Стандартний вхідний потік має номер 0, стандартний вихідний потік – номер 1, стандартний потік друку помилок – номер 2. Дана команда перенаправляє тільки потік stderr, потік stdout, як і раніше, буде виводитися на термінал. Можна перенаправляти потоки в різні файли:

abcd 2> tmp.log > tmp.res

 

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

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

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

char txt[256] = "-135.76"; double x;sscanf(txt, "%lf", &x);

 

текстовий запис дійсного числа, що знаходиться в рядку txt, перетвориться у внутрішнє представлення дійсного числа, результат записується в змінну x. Навпаки, при виконання фрагмента:

char txt[256]; int x = 12345;sprintf(txt, "%d", x);

 

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

Стандартна бібліотека введення-виведення Сі містить ряд інших функцій вводу-виводу. До них відносяться:

Посимвольне введення-виведення
int fgetc(FILE *f); ввести символ з потоку f
int fputc(int c, FILE *f); вивести символ у потік f
Построковевведення-виведення
char *fgets(char *line,int size, FILE *f); ввести рядок з потоку f
char *fputs(char *line, FILE *f); вивести рядок у потік f

 

Позиціонування у файлі
int fseek(FILE *f, long offset, int whence); встановити поточну позицію у файлі f
long ftell(FILE *f); одержати поточну позицію у файлі f
int feof(FILE *f); перевірити, чи досягнуто кінець файлу f

 

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

Варіанти індивідуальних завдань

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

1. Виконати парне порівняння двох бінарних файлів, які містять масиви з 20 елементів. Більші з пар елементів записуються у третій файл.

 

2. Для двох файлів, які включають по 15 елементів знайти парні суми цих елементів та записати до третього файлу.

 

3. Для двох файлів, які включають по 21 елементу знайти парні різниці цих елементів та записати до третього файлу.

 

4. Для двох файлів А та В, які включають по 17 елементів: , необхідно виконати наступні дії та отримані значення записати до файлу .

 

5. Для двох файлів А та В, які включають по 20 елементів:

 

необхідно виконати наступні дії: та отримані значення записати до файлу

 

6. Для двох файлів А та В, які включають по 25 елементів: необхідно виконати наступні дії та отримані значення записати до файлу , .

 

7. Для двох файлів А та В, які включають по 20 елементів: необхідно виконати наступні дії: та отриманні значення записати до файлу .

 

8. Для двох файлів А та В, які включають по 17 елементів: необхідно виконати наступні дії із елементами та : та записати отримані значення до файлу .

 

9. Для двох файлів А та В, які складаються з 18 елементів: необхідно обчислити значення та записати їх до файлу . .

 

10. Для двох файлів А та В, які складаються з 20 елементів: необхідно обчислити значення та записати їх до файлу .

 

11. Виконати парне порівняння двох бінарних файлів, які містять масиви з 20 елементів. Більші з пар елементів записуються у кінець третього файлу. Менші - зводяться у квадрат, та виводяться на екран.

 

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

 

13. Для двох файлів, які включають по 30 елементу знайти парні різниці цих елементів. Якщо різниця більше 0, записати її до третього файлу, інакше – записати у кінець четвертого файлу.

 

14. Для двох файлів А та В, які включають по 17 елементів: , необхідно виконати наступні дії та отримані значення записати до файлу .

 

15. Для двох файлів А та В, які включають по 20 елементів:

 

необхідно виконати наступні дії: та отримані значення записати до файлу

 

16. Для двох файлів А та В, які включають по 25 елементів: необхідно виконати наступні дії та отримані значення записати до файлу , .

 

17. Для двох файлів А та В, які включають по 50 елементів: необхідно виконати наступні дії: та отриманні значення записати до файлу .

 

18. Для двох файлів А та В, які включають по 20 елементів: необхідно виконати наступні дії із елементами та : та записати отримані значення до файлу .

 

19. Для двох файлів А та В, які складаються з 18 елементів: необхідно обчислити значення та записати їх до файлу . .

 

20. Для двох файлів А та В, які складаються з 20 елементів: необхідно обчислити значення та записати їх до файлу .

 

21. Для двох файлів А та В, які включають по 20 елементів:

 

необхідно виконати наступні дії: та отримані значення записати до файлу - користувач вводить з клавіатури.

 

22. Для двох файлів А та В, які включають по 24 елементів: необхідно виконати наступні дії та отримані значення записати до файлу , .

 

23. Для двох файлів А та В, які включають по 20 елементів: необхідно виконати наступні дії: та отриманні значення записати до файлу .

 

 

24. Для двох файлів А та В, які включають по 50 елементів: необхідно виконати наступні дії: та отриманні значення записати до файлу .

 

25. Для двох файлів А та В, які включають по 20 елементів: необхідно виконати наступні дії із елементами та : та записати отримані значення до файлу .

 

26. Для двох файлів А та В, які складаються з 18 елементів: необхідно обчислити значення та записати їх до файлу . .