Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Posibnyk_C.doc
Скачиваний:
23
Добавлен:
03.11.2018
Размер:
1.56 Mб
Скачать

9 Файли

Файлом називають іменовану область зовнiшньої (магнiтної) пам’яті, куди можна записати якусь iнформацiю. Кожний файл має своє ім’я, яке складається з двох частин: назви і розширення, розділених крапкою. Назва представляє собою ідентифікатор, сучасні операційні системи дозволяють мати назву довжиною до 64 та більше будь-яких (за невеликим винятком) символів. Давніші, такі, наприклад, як MS DOS, – лише до 8 літер або цифр та символа підкреслення, причому першим символом повинна бути буква. Досвід роботи з файлами підказує, що не варто зловживати можливостями сучасних операційних систем. По-перше, для абсолютної більшості задач назва довжиною 8 літер є достатньою, по-друге, виникають ті ж труднощі, що й з вищеописаною ідентифікацією змінних – зростає ймовірність помилки в назві, громіздкість тексту програми та ін., по-третє, що є найголовнішим, звужується область використання програми.

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

З метою забезпечення одночасного звернення до декількох файлів використовуються символи групових назв: * – для цілого слова та ? –для однієї букви, наприклад, назва *.mmm означає всі файли з розширенням mmm.

За виглядом розширення файли діляться на два типи: стандартні і користувача. В якості прикладу можна назвати такі стандартні розширення: *.doc – документ MS Word, *.c – початковий модуль мовою С для операційних систем MS DOS та Unix, *.cpp – початковий модуль мовою С++, *.h – файл заголовків функцій, *.exe – скомпільована програма в MS DOS, *.out – скомпільована програма в Unix та ін.

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

Один магнітний диск або інший фізичний носій зовнішньої пам’яті може містити сотні, тисячі і т.д. різних файлів. З метою якогось їх упорядкування, наприклад, за тематикою та скорочення часу на пошуки файли об’єднують у каталоги та підкаталоги, які інакше називаються директоріями або папками. Каталог, відкритий у даний момент часу, називається поточним. При зверненні до файлу, який знаходиться в поточному каталозі, достатньо вказати лише його ім’я. Якщо файл знаходиться в іншому каталозі, то можна задати повне ім’я – разом із назвою диска та іменами каталогів і підкаталогів, розділених символом \ – зворотній слеш. Наприклад: d:\program\c\robota.c.

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

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

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

Мова С не має спеціальних операторів для роботи з файлами, цю роботу виконують функції. Всі вони описані в уже відомому нам заголовковому файлі stdio.h. Різні компілятори можуть мати різну кількість функцій вводу-виводу. Загалом їх є декілька десятків, але ми розглянемо лише найголовніші.

При використаннi файлу його обов’язково необхідно оголосити, це робиться так:

FILE *f1,*f2, ... ,*fn;

Тут *f1,...,*fn – вказівники на файли, одночасно можна оголосити n вказівників. Кожному файлу вiдповiдає свiй вказівник, ця вiдповiднiсть встановлюється в момент вiдкриття файлiв. Він являє собою адресу пам’яті, де знаходяться відомості про буфер: адреса буфера, ступінь його заповнення та ім’я фізичного файлу, для якого він призначений.

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

f1=fopen("iф", "тип") – вiдкриття файлу з iменем iф, призначення йому вказівника f1. Для задання типу служать символи: r – зчитування з файлу, w – запис у файл, a – доточування файлу новими записами та інші. Функцiя fopen() повертає значення вказівника на файл (наприклад f1) або NULL, якщо при вiдкриттi сталася помилка, наприклад, відсутній магнітний диск. Ця функція встановлює зв’язок між фізичним і логічним файлом. Якщо відкривається новий файл для запису (з символом w), то він перед записом створюється. Якщо файл уже існує, то він заново перестворюється, а стара інформація пропадає.

fclose(f1, ... , fn) – закриття файлiв. Закривати файли не обов’язково, закінчуючи виконання, програма закриває їх сама, але це не вважається хорошим тоном програмування. По-перше, існує небезпека недописати файл, бо в момент закриття вiдбувається запис у файл залишку буферної пам’ятi, якщо вiн був вiдкритий для запису. По-друге, навіть у випадку відкриття файлу лише для читання, програміст повинен дбати про закриття всього, що він повідкривав, та бути впевненим у тому, що програма завершиться коректно. По-третє, функція закриття файлу підвищує “читабельність” програми.

Функція fclose() повертає значення NULL, якщо закриття файлу було успішним, або EOF – у протилежному випадку.

fseek(f1, n, k) – прямий доступ до файлу, встановлення вказівника файлу, на який вказує f1. Слід розрізняти поняття вказівник на файл, який оголошується, як логічний файл, та вказівник файлу – адреса байта всередині файлу. При вводi-виводi вказівником файлу контролюється поточний номер байта, який саме вводиться чи виводиться. Параметр n визначає на скiльки байтiв слiд змiстити цей вказівник. Параметр k задає початкову позицiю, вiд якої потрiбно перемiстити вказівник, вiн може приймати значення: 0 - вiд початку файлу, 1 - вiд поточної позицiї, 2 - вiд кiнця файлу.

rewind(f1) – встановлення вказівника файлу на його початок, тобто те ж, що й функція fseek() з параметрами fseek(f1,0,0);

a=fgetc(f1) – читання одного символа з файлу, на який вказує f1, присвоєння його значення змiннiй a типу int або char.

fputc(a, f1) – запис у файл, на який вказує f1, одного символа.

fgets(*s, size, f1) – читання з файлу, на який вказує f1, рядка символiв довжиною size байт, який закiнчується нулем ('\0'), помiщення цього рядка в оперативну пам’ять, починаючи з адреси s типу char.

fputs(*s, f1) – занесення в файл, на який вказує f1, лiтерного рядка, який закiнчується нулем i який знаходиться в оперативнiй пам’ятi, починаючи з адреси s типу char.

fread(void *r, size, n, f1) – читання з файлу, на який вказує f1, n записiв – рядкiв фiксованої довжини, кожний з яких займає size байт, i занесення їх в оперативну пам’ять, починаючи з адреси r типу void. Тип void зумовлений тим, що записані в цю адресу дані – двійкові, вони не мають типу, а їх довжину та кількість задають змінні size і n. Завдяки йому у файл можна записувати дані різного типу: літерного або двійкового. Записи в файлi повиннi закiнчуватися символом новий рядок, кiнець рядка тощо (різні операційні системі: Windows, Unix, Macintosh – роблять це по-різному).

fwrite(void *r, size, n, f1) – занесення записiв у файл. Ця функція має ті ж параметри, що й функція fread().

fscanf(f1, "сф", саз) – форматоване введення; читання з файлу, на який вказує f1, даних, перетворення типiв яких відбувається вiдповiдно до списку форматiв сф i занесення в оперативну пам’ять, задану списком адрес змiнних саз.

fprintf(f1, "сф", сiз) – форматоване виведення; занесення в файл, на який вказує f1, змiнних, заданих списком імен змінних сiз.

Функції fscanf() та fprintf() подібні до вже розглянених вище scanf() та printf() і відрізняються від них лише наявністю вказівника на файл.

Всi функцiї читання файлу повертають значення EOF або NULL, якщо був досягнений кiнець файлу. Значення NULL дорівнює 0 (машинний нуль, але не код літери '0'), а EOF – різне на різних компіляторах, найчастіше -1.

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

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

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

Текстові файли. У першому прикладі запишемо ці дані в файл за допомогою функції форматованого виводу. Алгоритм розв’язку задачi являтиме собою цикл типу арифметичної прогресiї, параметр якого змiнюється вiд 0 до 1 – за кількістю записів. В циклi записуються в файл прiзвища студентів та вiдповiдні їм оцiнки.

Нижче подано текст програми Stwor_filu_abc, яка реалізовує цей алгоритм. В ній оголошений вказівник ff на файл, змiнна i типу int для органiзацiї циклу та масив двох структур s[2]. Елементами структури є лiтерний масив c[12] для прiзвища та масив n[3] типу int для оцiнок. Масив структур iнiцiалiзований пiд час оголошення.

Iм’я файлу abc складається з трьох букв латини, розширення вiдсутнє. Файл вiдкривається для записування (виводу) за допомогою функцiї fopen(), йому вiдповiдає вказівник ff.

# include <stdio.h>

int main(void) /* Stwor_filu_abc */

{FILE *ff; int i;

struct {char c[12];

int n[3];}s[2]={"Селіна С.С.",{3,4,4},

"Дорока Д.Д.",{5,5,4}};

ff=fopen("abc", "w");

for(i=0; i<2; i++)fprintf(ff,"%s%d%d%d\n", s[i].c, s[i].n[0], s[i].n[1], s[i].n[2]);

fclose(ff);

return(0);

}

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

Результатом виконання програми буде файл abc, утворений в поточному каталозi, та записанi в нього два рядки, кожний з яких мiстить прiзвище i вiдповiднi йому оцiнки. В правильностi виконання програми пересвiдчуємось, переглянувши файл за допомогою команди F3 (читання файлу) в Total Commander. Вiн має вигляд:

Селіна С.С.344

Дорока Д.Д.554

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

Другий приклад демонструє читання файлу за допомогою двох функцiй: fgetc() i fgets(), тому алгоритм розв’язування задачi являє собою два послiдовнi цикли типу while, якi забезпечують дворазове читання файлу abc. Другий з них матиме два вкладені в нього послідовні цикли типу for. Алгоритм виконує програма Chyt_filu_2_sposobamy.

# include <stdio.h>

# include <stdlib.h>

# include <bios.h>

int main(void) /* Chyt_filu_2_sposobamy */

{FILE *ff;

int i=0, n[3];

char x, z[16], a[2], c[12];

if((ff=fopen("abc","r"))==NULL){printf("\nПомилка при відкр. файлу \n"); exit(1);}

printf(" \n\n Посимвольне читання файлу: \n");

while((x=fgetc(ff))!=EOF)printf("%c", x);

printf(" \n Читання рядків змінної довжини: \n");

fseek(ff, 0, 0); /* можна замінити на rewind(ff) */

while((fgets(z, sizeof(z), ff))!=NULL)

{for(i=0; i<11; i++)c[i]=z[i];

for(i=0; i<3; i++){a[0]=z[i+11]; a[1]='\0'; n[i]=atoi(a);}

printf("%s має оцінки: %d %d %d\n", c, n[0], n[1], n[2]);

}

if(fclose(ff)!=NULL)puts("Помилка при закритті файлу");

bioskey(0);

return(0);

}

Результати виконання програми Chyt_filu_2_sposobamy:

Посимвольне читання файлу:

Селіна С.С.344

Дорока Д.Д.554

Читання рядків змінної довжини:

Селіна С.С. має оцінки: 3 4 4

Дорока Д.Д. має оцінки: 5 5 4

Програма має три директиви препроцесору типу #include. Вони служать для пiд’єднання файлiв stdio.h, stdlib.h та bios.h з описами функцiй відповідно перший – вводу-виводу, другий – atoi(), перетворення масиву літер на ціле число, третій – bioskey(0).

На початку програми оголошена змінна i цілого типу – параметр циклу та масив n[3] цiлого типу для запам’ятовування трьох оцiнок. Змiнна x лiтерного типу (char) служить для запам’ятовування однієї лiтери при посимвольному читаннi файлу, масив z[16] – для чергового одного рядка лiтер при читанні рядків змінної довжини функцією fgets(). Лiтерний масив c[12] призначений для запам’ятовування одного прiзвища, a[2] – для чергової (однієї з 3-х) оцiнки в лiтерному представленнi. Оголошено також вказівник ff на файл. Звернемо увагу на те, що записувалися в файл abc елементи масиву структур s[2], а читати будемо масиви літер z[16]. Файл “не пам’ятає” в якому вигляді були записані дані, це справа програміста.

Тiло програми починається з вiдкриття файлу abc для читання за допомогою функцiї fopen(). Тут передбачена обробка можливої помилки при вiдкриттi файлу (файл зiпсований, вiдсутнiй, записаний в iншому каталозi, тощо): вивід на екран повiдомлення про те, що сталася помилка при вiдкриттi файлу, i закiнчення виконання програми за допомогою функцiї exit(1).

За своєю структурою тіло програми складається з двох циклiв типу while. В першому циклi вiдбувається читання чергового символа x з файлу поки не буде досягнений його кiнець i вивiд цього символа на екран. Читання файлу виконується за допомогою функцiї fgetc(), яка при досягненнi кiнця файлу видає значення EOF.

У другому циклі почергово зчитується цiлий рядок лiтер, який заноситься в масив z[16]. Рядки з файлу читаються за допомогою функцiї fgets(), яка при досягненнi кiнця файлу видає значення NULL. Ця функція вимагає, щоб кожний рядок закiнчувався символом '\n' – новий рядок.

Цей цикл, у свою чергу, мiстить два вкладені в нього послiдовнi цикли типу for, за допомогою яких масив літер дiлиться на двi частини: масив с[12] для зберiгання прiзвища i масив n[3] – оцiнок. Перший служить для полiтерного переприсвоєння перших 11 лiтер (прiзвище) з масиву z[16] у масив c[12], його параметр – змiнна i приймає значення вiд 0 до 10. В другому циклi типу for, параметр якого змiнна i приймає значення вiд 0 до 2 (3 значення відповідно до кількості цифр), вiдбувається вибiр з масиву z[] i присвоєння елементу масиву a[0] чергової лiтери-оцiнки. Пiсля доповнення елементом '\0' - нуль (a[1]='\0';) масив a[2], як лiтерний, перетворюється на число типу int за допомогою функцiї atoi(), яке заноситься в масив n[3]. Параметром цiєї функцiї є вказівник на масив лiтер (адреса першого елемента масиву), тобто ім’я масиву a, останнiм елементом якого має бути нуль.

Перетворення символа в число можна було виконати й іншим способом, потрібно лише відняти від коду символа число 48 – ASCII-код числа 0. Застосувавши його в програмі Chyt_filu_2_sposobamy замість функції atoi(), можна суттєво її спростити і скоротити текст. Тоді другий цикл for мав би такий значно простіший вигляд:

for(i=0; i<3; i++)n[i]= z[i+11]-48;

Як бачимо, ні функція atoi(), ні допоміжний масив a[2] стали непотрібними. Зрозуміло, що цей прийом буде корисним лише при перетворенні одноцифрового символа на число. Якщо матимемо декількацифрове число та ще й довільної довжини, то програма вийде набагато складнішою.

Закiнчується другий цикл типу while виводом на екран масиву лiтер c[12] – прiзвища та цiлого масиву n[3] – трьох оцiнок.

Таким чином, програма Chyt_filu_2_sposobamy читає та виводить на екран вміст файлу abc двiчi. За першим разом для читання файлу використовується функцiя посимвольного вводу fgetc() і на екран кожний введений символ x виводиться на екран у лiтерному представленнi за допомогою функції printf() форматованого виводу зі специфікатором c. За другим – функція вводу рядкiв змiнної довжини, тобто теж символів, лише не по одному, а цілий рядок. Оцiнки програмно перетворюються на числа і виводяться на екран як числа.

Оскiльки пiсля першого прочитання файлу функцiєю fgetc() вказівник файлу (той, що перебирає байти всередині файлу) зупиниться на останньому його байтi, а нам потрiбно заново його читати функцiєю fgets() спочатку, то в програмi пiсля першого циклу типу while використана функцiя fseek(ff, 0, 0). Параметри цiєї функцiї означають, що в файлi, на який вказує ff, необхiдно встановити вказівник файлу на перший байт вiд початку файлу, тобто на початок файлу. Замість цієї функції можна було вжити функцію rewind(ff), яка виглядає простіше і спеціально призначена для встановлення вказівника файлу на його початок.

Програма завершується функцiєю закриття файлу та функцiєю bioskey(0), яка в даному випадку служить для затримки екрану з метою вiзуального спостереження результатiв виконання подібно до вище вже розгляненої функції getch().

Слід наголосити на тому, що програма Stwor_filu_abc записувала в файл abc оцінки як числа, а обидва способи програми Chyt_filu_2_sposobamy зчитали їх як символи. Це означає, що під час запису в файл числа автоматично були перетворені на символи і що в файлі abc вони знаходяться в символьному вигляді.

Отже, обидві програми виконалися, дали необхідні результати, але проаналізуємо їх більш детально. В обох програмах для прізвища призначався літерний рядок c[12], який містив 12 літер. З них 11 – для самого прізвища, а 12-й – для символа '\0' закінчення масиву. Проте при читанні файлу abc цього останнього символа не виявлено. Не видно подібного символа ні всередині, ні наприкінці масиву z[16]. Коли його ділили на дві частини два цикли типу for програми Chyt_filu_2_sposobamy, то кількість символів дорівнювала 11+3+1=15 (прізвище + три оцінки + новий рядок – символ '\n'). Тим не менше, для масиву z під час оголошення було замовлено 16 букв. Тому в програміста мовою С який хоче контролювати кожний записаний у файл символ, виникають два запитання: куди подівся символ кінця рядка масиву c[12] та звідки взявся зайвий символ масиву z[16]?

Відповідь на перше запитання очевидна – функція виводу в файл проігнорувала символ закінчення масиву '\0', не додала його й операційна система в кінець кожного рядка z у файлі.

Для одержання відповіді на друге запитання слід прочитати файл за допомогою якоїсь підходящої утиліти операційної системи. Всі вони дозволяють читати коди символів, як правило, шістнадцятковими числами. Тоді дізнаємось, що код 15-го символа рядка дорівнює 0x0d – десяткове 13 (кінець рядка), а 16-го 0x0a – десяткове 10 (новий рядок), що означає відповідно '\r' і '\n'. Функція вводу об’єднала ці два символи і сприйняла їх як один, хоча під час оголошення масиву z вимагала для них два байти (по одному на символ) оперативної пам’яті.

Додамо лише, що обидві вищенаведені останні програми були виконані в середовищі операційної системи Windows, а компілятор С мав однобайтовий тип char і двобайтовий – int. Інші операційні системи можуть повестися по-іншому.

Двійкові файли, як уже вище говорилося, утворює функція fwrite(), а читає ­– fread(). Їх застосування демонструє програма Bit_fil.

#include<stdio.h>

#define k 12

#define n 3

int main(void) /* Bit_fil */

{

int i;

FILE *fp;

size_t siz1=k*sizeof(char), siz2=n*sizeof(int);

struct mmm{char c[12];

int d[3];

};

struct mmm s[2]={"Селіна С.С.", {3, 4, 4},

"Дорока Д.Д.", {5, 5, 4}

};

struct mmm b[2];

printf("\n");

if((fp=fopen("abcd", "w"))==NULL){puts("Немає місця для файлу"); exit(1);}

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

{

if(fwrite(s[i].c, siz1, 1, fp)!=1){puts("Не записано прізвище"); exit(1);}

if(fwrite(s[i].d, siz2, 1, fp)!=1){puts("Не записані оцінки ст."); exit(1);}

}

if(fclose(fp)!=NULL)puts("Помилка при закритті файлу");

if((fp=fopen("abcd", "r"))==NULL){puts("Файл не знайдено"); exit(1);}

puts("Читання файлу abcd:");

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

{

if(fread(b[i].c, 1 ,12, fp)!=12){puts("Не прочитано прізвище"); exit(1);}

if(fread(b[i].d, 2, 3, fp)!=3){puts("Не прочитані оцінки ст."); exit(1);}

}

if(fclose(fp)==EOF)puts("Помилка при закритті файлу");

for(i=0; i<2; i++)printf("%d %12s %d %d %d\n", i, b[i].c, b[i].d[0], b[i].d[1], b[i].d[2]);

getch();

return(0);

}

Результати виконання програми Bit_fil:

Читання файлу abcd:

0 Селіна С.С. 3 4 4

1 Дорока Д.Д. 5 5 4

Програма має дві директиви препроцесору #define для позначення двох іменованих констант: k=12 і n=3. Всередині програмного блоку оголошено змінну і цілого типу – параметр циклу та вказівник на файл *fp. Далі оголошено дві змінні: siz1 і siz2 типу size_t та тег mmm уже відомої з попередніх прикладів структури. За допомогою цього тега оголошено масиви двох структур: s[2], яка й ініціалізована, її дані будуть записані в файл, та масив b[2], у який будуть зчитані дані з файлу. Два масиви структур забезпечують чистоту експерименту.

Тип size_t не новий, це штучно створений тип на основі базових типів даних і призначений для обробки мовою С адрес та кількості байтів пам’яті. Різні компілятори утворюють його по-різному, найчастіше відомим з розділу 6 способом так:

typedef unsigned int size_t;

Змінні siz1 та siz2 ініціалізовані числами, підставивши в їхні формули відповідні значення (для однобайтового типу char і двобайтового int), одержимо: siz1=12 та siz2=6.

Решта тексту програми можна умовно поділити на дві частини. У першій утворюється і відкривається для запису файл abcd та записується в нього масив структур s[2] за допомогою функції fwrite(). Структура записується в циклі два рази – за кількістю елементів її масиву. Ця функція при кожному виконанні циклу викликається двічі, за першим разом вона має має такі 4 параметри: елемент структури s[i].c, довжина 1-го запису siz1=12 байт, кількість одночасно записаних порцій 1, та ім’я вказівника на файл fp. Вдруге вона викликається з параметрами: елемент структури s[i].d, довжина 1-го запису siz2=6 байт, кількість одночасно виведених записів 1, та ім’я вказівника на файл fp. Аналізуючи ці параметри, бачимо, що за першим викликом функція fwrite() записує в файл весь масив прізвища (12 букв), а за другим весь масив оцінок (3 числа), кількість записів у обох випадках дорівнює 1. Вище було вже сказано, що вона повертає кількість виконаних записів, ця властивість використана для перевірки коректності її роботи.

Друга частина програми майже не відрізняється від першої. В ній відкривається файл abcd, але для читання, а функція fread() заносить у масив b[2] дані, взяті з файлу.

Вище було сказано, що параметри обох функцій: і fwrite(), і fread() однакові, але в нашій програмі Bit_fil вони дещо різні – по-різному задані довжина однієї порції вводу-виводу та їх кількість. Це зроблено спеціально, щоб показати, по-перше, що не має значення довжина однієї порції та кількість вводів-виводів за один раз, головним є загальне число байтів. Літерний масив можна вводити чи виводити 12 разів по одному байту або 12 байтів за одним разом. Те ж стосується й масиву чисел, можна вводити 3 числа (6 байт) за 1 раз або 1 число (2 байти) за 3 рази. По-друге, що не обов’язково користуватися типом size_t, можна вибрати й тип int, якщо, звичайно, влаштовує його довжина.

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

Звернемо увагу на різне дворазове застосування функції fclose() в програмі Bit_fil, за першим разом видане нею значення перевіряється на NULL, а за другим – EOF. Це не суттєво, просто перше значення вона видає при успішному закритті файлу, а друге – у випадку помилки.

Наприкінці програми виводяться елементи структури b[2] за допомогою функції форматованого виводу. Прізвище виводиться за специфікатором %s, а числа мають специфікатор %d.

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

Всі зовнішні пристрої комп’ютера мовою С розглядаються як файли і мають власні системні імена: ввід даних з клавіатури називається stdin, вивід на екран – stdout, вивід на принтер – stdprn та ін. Наприклад, функції форматованого вводу цілого числа k і його виводу на екран можуть мати такий вигляд відповідно: fscanf(stdin,"%d",&k); та fprintf(stdout,"%d\n",k); Під час їх виконання їм теж призначається буфер, який інакше називають потоком. Він вигідний тим, що дозволяє користувачу програми візуально контролювати процес вводу та виправляти допущені помилки. Для роботи з стандартним потоком мова С має спеціальні варіанти функцій вводу-виводу.

Функція scanf() вживається порівняно найчастіше завдяки своїй універсальності. Вона може працювати і з одиночними літерами, і з рядками тексту, і з числами. Рядок літер (специфікатор %s) вона читає по одному слову за один раз – до першого пробіла та додає наприкінці його символ '\0'. При читанні слів та чисел пробільні символи пропускаються. Cимволи (специфікатор %c) читає і заносить на задану адресу по одному, включаючи пробіли. Якщо функція читає число зі специфікатором %d або %i для цілих десяткових чисел або %f – для дійсних та іншими, то розрізняє знаки + або - на його початку і десяткову крапку, якщо вона є, а цифри по одній додає до числа поки не зустріне пробіл.

Якщо ця функція вводить декілька якихось значень (наприклад, два цілі числа так: scanf("%d%d",&k,&n)) за один сеанс її виклику, то користувач програми може або набирати їх з клавіатури по одному (тобто після кожного з них натискати клавішу Enter), або заносити в потік обидва числа через пробіл.

Неврахування в програмі специфіки роботи функції scanf() може спричинити непорозуміння. Вона повертає кількість правильних вводів і ця її властивість була використана вище в програмах malloc розділу 6 та vizual_bit розділу 8 для виходу з циклу. Для припинення виконання зацикленого блоку цій функції пропонували букву замість числа. Але тоді повторне її використання для вводу числа стає неможливим.

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

# include <stdio.h>

int main(void) /* bukwa_no */

{int i, kil, k[3];

puts("Введіть 3 числа k ");

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

{

kil=scanf("%d", &k[i]);

printf("i=%d kil=%d k=%d\n", i, kil, k[i]);

}

printf("Введіть число i \n");

scanf("%d", &i);

printf("i=%d\n", i);

getch();

return(0);

}

Результати виконання програми bukwa_no (3 копії екрану):

запуск 1-го разу запуск 2-го разу запуск 3-го разу

Введіть 3 числа k Введіть 3 числа k Введіть 3 числа

123 234 345 123 234 3ab 123 234 abc

i=0 kil=1 k=123 i=0 kil=1 k=123 i=0 kil=1 k=123

i=1 kil=1 k=234 i=1 kil=1 k=234 i=1 kil=1 k=234

i=2 kil=1 k=345 i=2 kil=1 k=3 i=2 kil=0 k=12931

Введіть число i Введіть число i Введіть число i

12345

i=12345 i=3 i=3

Аналізуючи ці результати, бачимо, що в результаті першого запуску були правильно введені числа і ніяких проблем не виникло. Програма видала всі числа і кількість виконань функції scanf(), яка кожного разу дорівнювала одиниці. За другим разом замість числа 345 було введено комбінацію символів, складену з 1-ї цифри і 2-х букв (3ab), що спричинло відмову функції працювати. Однак, вона видала кількість виконаних вводів – теж одиницю, тобто повідомила, що операція вводу відбулася успішно. Вона не забезпечила ввід числа i, але прочитала з потоку випадкове значення i=3. Це говорить про ненадійність використання кількості вводів як інформацію про характер завершення процесу вводу за допомогою функції scanf(). У третьому випадку функція видала очікуване повідомлення про те, що процес вводу не відбувся – кількість вводів дорівнює нулю.

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

# include <stdio.h>

int main(void) /* bukwa_yes */

{int i, kil, k[3];

char d[9]="abcdefgh";

puts("Введіть 3 числа k");

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

{

kil=scanf("%d", &k[i]);

printf("i=%d kil=%d k=%d\n", i, kil, k[i]);

}

gets(d); puts(d);

printf("Введіть число i \n");

scanf("%d", &i);

printf("i=%d\n", i);

getch();

return(0);

}

Введіть 3 числа k

123 234 5pq

i=0 kil=1 k=123

i=1 kil=1 k=234

i=2 kil=1 k=5

pq

Введіть число i

12345

i=12345

Як бачимо, програма (функція gets(d)) зчитала залишені в потоці (у даному випадку непотрібні, зайві) букви pq, після чого функція scanf() змогла ввести число i (i=12345). Програма bukwa_yes має випробувальний характер, тому з метою візуального контролю її роботи букви pq були виведені на екран.

Покажемо ще один приклад неочікуваної поведінки функції scanf(), цього разу через невірне її застосування. Його демонструє програма slowa. В ній оголошено два літерні масиви: c[3][5] і d[9]="abcdefgh", масив c підлягає вводу. Програму випробуємо два рази, за першим разом занесемо в потік помилкове слово – з пробілом усередині, за другим –перевищимо довжину слів.

# include <stdio.h>

int main(void) /* slowa */

{int i;

char c[3][5], d[9]="abcdefgh";

puts("Введіть 3 слова");

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

{

scanf("%s", c[i]);

printf("i=%d c=%s\n", i, c[i]);

}

printf("d=%s\n", d);

getch();

return(0);

}

Результати виконання програми slowa:

запуск 1-го разу запуск 2-го разу

Введіть 3 слова Введіть 3 слова

koma nda spek elevator komanda spektr elevator

i=0 c=koma i=0 c=komanda

i=1 c=nda i=1 c=spektr

i=2 c=spek i=2 c=elevator

d=abcdefgh d=or

Аналізуючи одержані результати, бачимо, що слово koma nda, яке першого разу мало всередині пробіл, було сприйнято як два різні слова. Другого разу перевищення довжини слів (5 букв) масиву c ніяк не було попереджено функцією scanf(), через що він зайняв місце оголошеного після нього масиву d і стер його.

Функція gets(). Функція gets() більш компактна, ніж scanf(), тому більш вживана для вводу рядків символів. Приклад її використання маємо в програмі bukwa_yes цього розділу. Вона повертає адресу введеного зі стандартного потоку stdin масиву літер, який закінчується символом нового рядка ('\n'). Цей символ вона не заносить у введений рядок, натомість ставить у кінець рядка символ нуль ('\0'). Але вона не контролює довжину рядка, тому може стерти “чужі” символи подібно до того, як це було з функцією scanf() програми slowa.

В цьому відношенні більш виграшною виглядає функція fgets(). Нехай маємо такий фрагмент програми:

char d[3], s[7]="Ракета";

fgets(d, 3, stdin);

fputs(d, stdout);

fputs(s, stdout);

gets(d);

puts(d);

puts(s);

Як бачимо, довжина масиву d складає 3 байти, включаючи нуль наприкінці рядка. Тому другим параметром функції fgets є число 3. Вслід за масивом d у пам’яті знаходиться масив s довжиною 7 символів. Він служить для перевірки, чи не буде стертим під час виконання фрагмента.

Під час запуску програми, яка містить цей фрагмент, (програма зупинилася і відкрила потік для вводу даних) було занесемо в потік слово Система – довше, ніж 3 символи. Результати виявилися такими:

СиРакетастема

а

Як бачимо, функція fgets(d, 3, stdin) ввела не цілий рядок Система, але лише 2 перші його сиволи (третім очевидно був нуль наприкінці рядка d, який ця функція вставила сама) та занесла їх у масив d. При цьому сусідній масив s="Ракета" залишився неушкодженим, його вивела функція fputs(s, stdout). Решта слова: стема залишилася в потоці. Паралельно було проведено дослідження поведінки функції gets(). Вона дочитала цей залишок рядка, а видала – puts(d). Оскільки довжина рядка d складає лише 3 символи, а решта слова, тобто стема, була довшою, то наступний рядок s виявився стертим (вище було сказано, що така поведінка функції gets() вважається нормальною) і в нього потрапила одна буква а – остання буква залишку стема, яку й вивела функція puts(s).

Функція getch() забирає по одному символу під час вводу даних зі стандартного потоку. Подібно до функції scanf() зі специфікатором %с вона не пропускає пробільні символи, а виводить їх.

Характеризуючи функції вводу зі стандартного потоку (stdin), можна зробити такі загальні далеко не вичерпні висновки:

  • всі функції працюють з одним і тим же потоком послідовно;

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

  • якщо функція читає з потоку рядок символів, то під час занесення його в пам’ять вона додає символ нуль ('\0') у кінець рядка.

Запитання для самоперевiрки

  1. Що таке файл, iм’я файлу, розширення iменi файлу?

  2. Назвiть засоби мови Сi, обов’язковi у програмі для обробки файлiв.

  3. Що таке буферна пам’ять? Як відбувається запис даних у файл з буферної пам’яті?

  4. Перелiчiть основнi функцiї вводу-виводу, назвiть та пояснiть їх параметри.

  5. Як оголошується файл мовою Ci, що таке вказівник на файл i яке вiн має вiдношення до iменi файлу?

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

  7. Чим вiдрiзняється вказівник файлу вiд вказівника на файл?

  8. Що таке прямий доступ до файлу i як вiн здiйснюється?

  9. Чи можна створений функцiєю посимвольного виводу файл, який мiстить данi лiтерного i числового типiв, читати за допомогою iнших функцiй? Чи потрiбнi будуть тут якiсь перетворення?

  10. Чи є необхiднiсть в додатковому застосуваннi якихось перетворень типiв даних при їх читанні з файлу, створеного шляхом занесення записiв фіксованої довжини?

  11. Чим відрізняються функції форматованого виводу від інших подібних функцій?

  12. Чим відрізняється файл, який містить одне число, але створений двома функціями: форматоваго або посимвольного виводу?

  13. Чи можна записати в файл число за допомогою функції виводу рядків змінної довжини?

  14. Чим відрізняються двійкові файли від текстових?

  15. Коротко охарактеризуйте роботу функцій вводу зі стандартного потоку stdin.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]