Зубенко, Омельчук - Програмування. Поглиблений курс
.pdf
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++
Функції форматованого читання з файла й запису у файл:
int fscanf (FILE *stream, const char *format[,address, …]); int fprintf(FILE *stream, const char *format[,argument, …]);
Дані функції зі змінною кількістю параметрів є аналогами консоль- них функцій scanf та printf і працюють не зі стандартними, а з дові- льними відкритими потоками. Перша читає символи з потоку stream, відкритого для читання, перетворює їх згідно зі специфікаторами формату на двійкову форму й результати записує в поля, задані адре- сними аргументами. Друга здійснює виведення в потік stream, від- критий для запису, відформатованої інформації. Роль форматного рядка в обох функціях така сама, як і в їхніх аналогах.
Функції для роботи з буферним покажчиком:
long int ftell(FILE *stream);
int fseek(FILE *stream, long offset, int whence); int rewind(FILE *stream);
Функція ftell повертає поточну позицію буферного покажчика по- току stream, з якого починається наступна операція введення- виведення. Зокрема, це значення можна передати функції fseek. Ви- користовується також для текстових файлів, але тоді значення, що повертається, не обов'язково буде подавати байтове зміщення у фай- лі. Функція fseek установлює поточну позицію буферного покажчика потоку stream, з якої почнеться наступна операція читання чи запису. У випадку успіху повертає нуль, при виникненні помилки – ненульове значення. Тут long offset для двійкових файлів – кількість байтів, на яку потрібно зсунути файловий покажчик у напрямку, указаному whence. Для текстових файлів це значення має бути нулем чи значен- ням, що повертається функцією ftell. Щоб перемістити файловий покажчик в оберненому напрямку (до початку файла), потрібно вста- новити значення offset від'ємним. Для переміщення файлового по- кажчика на offset байтів від початку файла потрібно встановити whence рівним SEEK_SET (або 0) відносно поточної позиції – SEEK_CUR (або 1); на задану кількість байтів від кінця – SEEK_END (або 2).
Функція rewind установлює буферний покажчик на початок файла. Зазвичай використовується для повторного читання, починаючи з першого байта, чи для підготовки запису нових даних із початку іс- нуючого файла.
401
ПРОГРАМУВАННЯ
Запис буферів у файл:
int fflush(FILE *stream);
Функція записує у файл поточний зміст буфера для заданого пото- ку без закривання файла. Цю функцію слід періодично викликати в усіх програмах, що довго працюють із відкритими файлами.
Повідомлення про помилкові ситуації в потоці:
int ferror(FILE *stream);
Функція повертає істину (ненульове значення), якщо під час оброб- ки даного потоку виникали помилки. Якщо помилки не були виявлені, то повертається нуль (хибно).
Видалення файла:
int remove(const char *filename);
Функція видаляє файл filename з файлової системи за умови, що він існує. У випадку успіху повертає нуль, інакше повертає –1 і вста- новлює значення змінної errno рівним ENOENT (файл не знайдено) або EACCES (доступ заборонено). Зазвичай реалізується як макрос, що ви- кликає функцію unlink.
Функції для читання й запису даних із потоку:
size_t fread (void *ptr, size_t size, size_t n, FILE *stream); size_t fwrite(void *ptr, size_t size, size_t n, FILE *stream);
Реалізують введення-виведення нижнього рівня. Функція fread чи- тає дані з файлового потоку, відкритого у двійковому режимі, почи- наючи з поточного файлового покажчика. Після читання даних цією функцією файловий покажчик розміщується безпосередньо за остан- нім прочитаним байтом. Кількість прочитаних байтів дорівнює ре- зультату функції, помноженому на кількість байтів у елементі. У ви- падку невдачі або якщо файловий покажчик розташований за кінцем файла функція повертає нуль. Тут void *ptr – покажчик на прийма- ючий буфер, size_t size – розмір у байтах одного елемента, що чита- ється з файлового потоку, size_t n – кількість таких елементів.
Функція fwrite записує один чи кілька байтів у файловий потік, ві- дкритий у двійковому режимі, і повертає кількість записаних елемен- тів. Кількість записаних байтів дорівнює результату функції, помно-
402
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++
женому на значення аргументу size (розмір одного елемента). Тут void *ptr – покажчик на дані, що записуються, size_t size – розмір у байтах одного елемента, size_t n – кількість елементів.
Приклад 3.73. Для ілюстрації роботи деяких описаних вище фун- кцій розглянемо програму:
/*fopen, fgets, ftell, fseek, fread, fclose, rewind, ferror, fputs*/
#include <stdio.h> #include <stdlib.h> #define FNAME "TEST.C"
int main(int argc, char *argv[])
{
FILE *inf; /*вхідний файловий потік*/ long pos, pos1; /*позиція у файлі*/ char buffer[128];
char c;
/*відкрити текстовий файл для читання у двійковому режимі*/ inf=fopen(FNAME, "rb");
if(inf==NULL){
printf("Помилка при відкриванні файла", FNAME); exit(1);
}
/*читає перший рядок*/ fgets(buffer, 128, inf); printf("10-й рядок==%s\n", buffer); pos=ftell(inf);
printf("Позиція у файлі==%ld\n", pos); /*покажчик на початок файла*/ rewind(inf);
pos1=ftell(inf);
printf("Позиція після rewind==%ld\n", pos1);
/*Шукати pos байтів від початку файла*/ if(fseek(inf, pos-1, SEEK_SET)!=0)
printf("Помилка позиціонування");
else{
/*читання символу з файла*/ fread(&c, 1, 1, inf); printf("pos-й символ==%c", c);
}
/*намагаємося писати у відкритий для читання файл, тобто моделюємо помилку*/
403
ПРОГРАМУВАННЯ
puts("Намагаємося писати у відкритий для читання файл");
fputs("Моделюємо файлову помилку", inf); if(ferror(inf))
printf("Виникла помилка файлового потоку!");
/*закривання файла*/ fclose(inf);
return 0;
}
/*fopen, fwrite, rewind, fread, fclose*/ #include <stdio.h>
#include <stdlib.h>
int array[100]; /*масив цілих нулів*/ #define FNAME "TEMP.C"
int main(int int argc, char *argv[])
{
/*створити файл у режимі w+b*/ FILE *tmpf=fopen(FNAME, "w+b"); if(!tmpf){
perror("Не можна відкрити файл"); exit(1);
}
/*записати 100 цілих у файл*/ for(int i=0; i<100; i++)
fwrite(&i, sizeof(int), 1, tmpf); /*прочитати цілі числа в масив*/
/*установити файловий покажчик на початок файла*/ rewind(tmpf);
fread(&array, sizeof(int), 100, tmpf); puts("Масив після читання з диска:\n"); for(int i=0; i<100; i++)
printf("%8d", array[i]); /*закрити файл*/
fclose(tmpf); return 0;
}
■
3.8.4. ПЕРЕНАПРАВЛЕННЯ ПОТОКІВ
Пригадаємо, що на початку виконання програми автоматично від- криваються три потоки: stdіn (стандартний потік введення), stdout (стандартний потік виведення) і stderr (стандартний потік помилок).
404
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++
Зазвичай ці потоки зв'язані з консоллю. Проте в середовищах, що пі- дтримують перенаправлення введення-виведення, вони можуть бути перенаправлені операційною системою на інший пристрій.
Функції консольного введення-виведення насправді спрямовують результати своїх операцій на один із потоків – stdіn або stdout, і по суті, кожна з них є спеціальною версією відповідної файлової функції.
Оскільки імена стандартних потоків є покажчиками файлів типу FІLE*, то вони можуть використовуватися для застосування загальних файлових операцій при введенні-виведенні на консоль. Наприклад, функція putchar може бути визначена таким чином:
іnt putchar(char c)
{
return putc(c, stdout);
}
Узагалі, у ролі покажчиків файлів потоки stdіn, stdout та stderr можна застосовувати в будь-якій функції, де використовується змін- на типу FІLE*. Наприклад, для введення рядка з консолі можна засто- сувати такий виклик файлової функції fgets:
char str[255]; fgets(str, 80, stdіn);
Зазначимо, що stdіn, stdout і stderr – це не змінні у звичайному ро- зумінні та їм не можна присвоювати значення за допомогою функції fopen(). Через те, що на початку роботи програми покажчики файлів створюються автоматично, наприкінці роботи вони й закриваються автоматично. Тому не потрібно намагатися закрити їх самостійно.
Операції введення-виведення на дискових файлах можна викону- вати за допомогою функції консольного введення-виведення, напри- клад prіntf. Справа в тому, що всі функції консольного введення- виведення виконують свої операції з потоками stdіn та stdout, а в середовищах, що підтримують перенаправлення потоків введення- виведення, це рівносильне тому, що stdіn або stdout можуть бути пе- ренаправлені на інший пристрій. Коли С-програма завершується, то всі перенаправлені потоки повертаються в стани, які були встановле- ні за умовчанням. Таке перенаправлення здійснює функція
FІLE *freopen(const char *filename, const char *mode, FІLE *stream);
Вона закриває поточний відкритий файловий потік і пов'язує його з но- вим файлом. Зазвичай використовується для перенаправлення потоків
405
ПРОГРАМУВАННЯ
stdin, stdout, stderr. Тут filename – покажчик на рядок імені нового файла, пов'язаного з файловим потоком. Файл відкривається в режимі mode; цей параметр може набувати тих самих значень, що й відповідний параметр функції fopen(). Якщо функція freopen() виконана успішно, то вона повертає stream, а якщо зустрілися помилки – то NULL:
#include <stdio.h> #include <stdlib.h> #include <string.h>
int main()
{
FILE *outf;
//перенаправити stderr на тимчасовий вихідний файл outf=freopen("tmp.txt", "w", stderr); if(outf==NULL) {
puts("Помилка відкривання тимчасового файла"); exit(1);
}
fputs("Перевірка повідомлення про помилку,\n", stderr); fputs("записаного у файл за допомогою stderr\n", stderr); fclose(outf);
printf("Перегляньте файл tmp.txt");
return 0;
}
Перенаправлення стандартних потоків за допомогою freopen у де- яких випадках може бути корисним, наприклад, при налагодженні. Однак виконання дискових операцій введення-виведення на перена- правлених потоках stdіn та stdout не дуже ефективне порівняно з використанням таких функцій, як fread або fwrіte.
* Література для CР: обробка файлів – [32, 55, 102, 133].
Контрольні запитання та вправи
1.Що таке введення-виведення для консолі й портів?
2.Які функції є консольними функціями введення-виведення?
3.Як ввести символ із клавіатури?
4.Яка різниця в дії функцій getchar, getch та getchе?
5.Як вивести символ на екран?
6.Як прочитати рядок символів із клавіатури?
7.Як вивести на екран рядок "Hello, world!"?
8.Що таке форматовані введення й виведення?
406
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++
9.Описати форматний рядок функції printf.
10.Описати форматний рядок функції scanf.
11.Як діють функції sscanf та sprintf?
12.Що таке потік і файл?
13.Що таке потокове введення-виведення?
14.Що таке покажчик файла і для чого він використовується?
15.Як відкрити файловий потік?
16.Які є режими відкривання файла?
17.Як закрити файловий потік?
18.Як записати символ у потік?
19.Як отримати символ із заданого потоку?
20.Як записати рядок у потік?
21.Як отримати рядок із заданого потоку?
22.Як отримати файловий покажчик?
23.Як установити нову позицію покажчика файла?
24.Як установити файловий покажчик на початок файла?
25.Як здійснити форматоване текстове виведення чисел у файл? Яка роль при цьому форматного рядка?
26.Як прочитати з файла п'ять даних різних числових типів?
27.Як перевірити, чи досягнутий кінець файла?
28.Як перевірити, чи виникали помилки під час обробки фай- лового потоку?
29.Як видалити файл?
30.Описати функції для читання й запису даних fread та fwrіte.
31.Що таке перенаправлення потоків?
32.Написати програму-фільтр, яка малі латинські літери замі- нює на великі. Текст вводиться з клавіатури й виводиться на екран. Скористатися функціями islower(с) та
toupper(c). Перша перевіряє, чи є символ малою латинсь- кою літерою, друга повертає код відповідної літери на вер- хньому регістрі.
33. Підрахувати кількість прочитаних із клавіатури: а) симво- лів; б) символів табуляції (‘\t'), пробілів (‘ ‘) і рядків (‘\n'). Для завершення процесу читання ввести символ
‘Ctrl+Z'.
34.Написати програму-фільтр, яка копіює введені з клавіатури символи на екран і при цьому: а) прибирає зайві пробіли; б) розміщує по одному слову на рядок. В обох випадках підра- хувати кількість введених із клавіатури слів.
35.Не всі зовнішні пристрої вміють інтерпретувати символ та- буляції ‘\t'. Написати програми-фільтри для вилучення символу ‘\t' текстового файла й заміни його відповідною кількістю пробілів ‘ ' і навпаки – для вставки там це можли- во символ табуляції і вилученням відповідної кількості про-
407
ПРОГРАМУВАННЯ
білів. Позиції для табуляції задаються константою n у ко- мандному рядку. Стандартне значення константи – 8.
36.Із клавіатури читається послідовність рядків. Надрукувати
їїв лексикографічному порядку.
37.Написати програму з іменем tail, що друкує n останніх
введених рядків. За умовчанням n=5. Однак n може бути
задано й у командному рядку. Командний рядок > tail – n запускає друкування останніх n рядків. У кожний момент зберігати в пам'яті не більше n рядків.
38.Написати програму-фільтр для копіювання текстового фай- ла. Рядки тексту мають довжину не більше 80. Порожні ря- дки й рядки із символів-пробілів вилучаються. Вхідний файл: а) вводиться з клавіатури; б) міститься на диску.
39.Порівняти два файли й надрукувати перший рядок, в яко- му вони розрізняються.
40.Написати: а) довільну програму, б) * програму з лінійною часовою складністю пошуку текстових файлів, що містять входження даного слова. Слово та імена файлів подаються в командному рядку.
41.Написати програму для друкування кількох файлів. Кожний файл повинен розпочинатися з нової сторінки, містити за- головок і мати свою нумерацію сторінок. Розміри сторінки задаються у командному рядку.
42.Написати функцію для: а) копіювання файла в список його елементів у ОП; б) переписування у файл елементів задано- го списку.
43.Написати програму для пошуку спільних рядків двох упо- рядкованих лексикографічно файлів. Програма має виво- дити рядки у три колонки: у першу – ті, що є тільки в пер- шому файлі; у другу – ті, що є тільки в другому; у третю – спільні рядки обох файлів.
44.Написати програму, що будує дерево пошуку з чисел, пода- них у файлі sf.txt, і виводить його: а) у префіксному; б) постфіксному; в) інфіксному порядках у файл df.txt.
45.Прокоментувати й налагодити програму:
#include <stdio.h> #include <stdlib.h> #define N 5
int array[N];
#define FNAME "TEMP.C"
void main()
{
408
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++
FILE *tmpf=fopen(FNAME, "w+b"); |
/* … |
*/ |
|
if(!tmpf){perror("Не можна відкрити файл"); |
|||
exit(1); |
|
|
|
} |
|
|
|
for(int i=0; i<N; i++) |
/* … |
*/ |
|
fwrite(&i, sizeof(int), 1, tmpf); |
|||
rewind(tmpf); /* … |
*/ |
|
/* … */ |
fread(&array, sizeof(int), 100, tmpf); |
|||
puts("Масив після читання з диска:\n"); |
|
||
for(int i=0; i<100; i++)printf("%8d", array[i]); |
|||
fclose(tmpf); |
|
|
|
} |
|
|
|
46 * . Написати функції |
int fwritef(FILE* fp,char |
||
*format,…) та int freadf(FILE* fp, char *format,…)
для запису в потік і читання з потоку послідовностей різ- нотипних даних. Скористатися макросами з бібліотеки <stdarg.h> для роботи з неоголошеними аргументами функції (див. підрозд. 3.6.7). Функції повертають значен- ня так само, як їхні аналоги – функції fprintf та fscanf.
3.9. Препроцесор
¾Директиви препроцесора
¾Макропідстановки
¾Включення файлів
¾Умовна компіляція
¾Директиви #error, #line та #pragma
Ключові слова: препроцесор, директива препроцесора, простий і параметри- зований макроси, включення файлів, умовна компіляція, операції перетворення на рядок # і конкатенації ##, директиви: #define, #endif, #ifdef, #line, #elif,
#error, #ifndef, #pragma, #else, #if, #include, #undef.
Препроцесор є складовою системи програмування мови C. Він отримує на вхід текст С-програми з директивами, виконує їх і вилу- чає з тексту. Окрім цього, фаза препроцесування включає:
1) відкидання пари символів: \ та \n (склеювання сусідніх рядків програми);
409
ПРОГРАМУВАННЯ
2)розбиття програми на лексеми. Коментарі при цьому замінюють- ся одиничними пробілами;
3)заміну в символьних константах і літералах керівних послідовно- стей на їхні еквіваленти. Сусідні літерали конкатенуються.
Розглянемо приклад препроцесорної обробки C-програми, що не міс- тить директив препроцесора. Лексеми розміщуються в дужках <…>.
Приклад 3.74. Нехай на вхід препроцесора надходить програма:
int main(void)
{
printf("\n"); /*вихід із програми*/
}
Препроцесор поверне послідовність лексем:
<int> |
Службове слово |
< > |
Пробіл |
<main> |
Ідентифікатор |
<(> |
Роздільник |
<void> |
Ідентифікатор |
<)> |
Роздільник |
<{> |
Роздільник |
< > |
Пробіл (один замість трьох) |
<printf> |
Ідентифікатор |
<(> |
Роздільник |
<10> |
Код символу ‘\n' |
<)> |
Роздільник |
<; > |
Роздільник |
< > |
Пробіл замість коментаря |
<}> |
Роздільник |
■
3.9.1. ДИРЕКТИВИ ПРЕПРОЦЕСОРА
Директиви задають певні синтаксичні перетворення тексту про- грами й мають вигляд:
#<ім'я-директиви> <лексема-операнд>{<лексема-операнд>}
Вони розміщуються в окремих рядках і розпочинаються символом ‘#'. Область дії директив – від неї до кінця текстового файла. Якщо безпосередньо перед закінченням рядка поставити символ ‘\', то ди- ректива продовжиться на наступний рядок програми.
410
