- •Передмова
- •Передмова до першого видання
- •Вступ
- •Вступний урок
- •Твердження for
- •Масиви
- •Аргументи - виклик за значенням
- •Типи, оператори та вирази
- •Константи
- •Оголошення
- •Оператори приросту та спаду
- •Оператори та вирази присвоєння
- •Вирази умов
- •Керування потоком
- •Вирази та блоки
- •If-else
- •Else if
- •Switch
- •Цикли while та for
- •Файли заголовка
- •Препроцесор C
- •Покажчики та масиви
- •Покажчики й адреси
- •Покажчики та масиви
- •Арифметика адрес
- •Аргументи командного рядка
- •Структури
- •Масиви структур
- •Покажчики на структури
- •Typedef
- •Сполуки
- •Ungetc
- •Виконання команд
- •Генератор випадкових чисел
- •Open, creat, close, unlink
166 |
РОЗДIЛ 6. СТРУКТУРИ |
функцiя install використовує lookup, щоб визначити, чи назва, яка додається вже присутня; якщо так, то нове визначення витiснить старе. У протилежному випадку, буде створено новий запис. install поверне NULL, якщо з якоїсь причини не залишилося мiсця для нового запису.
struct nlist *lookup(char *); char *strdup(char *);
/* install: додає (name, defn) до hashtab */ struct nlist *install(char *name, char *defn)
{
struct nlist *np; unsigned hashval;
if ((np = lookup(name)) == NULL) { /* not found */ np = (struct nlist *) malloc(sizeof(*np));
|
if (np == NULL || (np->name = strdup(name)) == NULL) |
|
|
return NULL; |
|
hashval = hash(name); |
|
|
np->next = hashtab[hashval]; |
|
|
hashtab[hashval] = np; |
|
|
} else |
/* already there */ |
|
free((void *) np->defn); |
/*free previous defn */ |
|
if ((np->defn = strdup(defn)) == NULL) return NULL;
return np;
}
Вправа 6-5. Напишiть функцiю undef, яка би видаляла назву та визначення з таблицi, утворюваної функцiями lookup та install.
Вправа 6-6. Втiльте просту версiю (без аргументiв) оброблювача визначень #define (який можна би було використовувати iз C-програмами), основуючись на функцiях iз цього роздiлу. Вам можливо доведеться згадати функцiї getch i ungetch.
6.7Typedef
C передбачає засiб, що має назву typedef, який дозволяє створювати новi назви типiв даних. Так, наприклад, оголошення
typedef int Length;
6.7. TYPEDEF |
167 |
робить назву Length синонiмом int. Тепер тип Lengh можна вживати в оголошеннях, зведеннях тощо, так само як ми це робимо з int:
Length len, maxlen;
Length *lengths[];
Подiбно до цього, оголошення
typedef char *String;
перетворює String на синонiм char * (символьний покажчик), що пiзнiше можна використати в оголошеннях i зведеннях типiв:
String p, lineptr[MAXLINES], alloc(int); int strcmp(String, String);
p = (String) malloc(100);
Звернiть увагу, що новий тип, який було оголошено в typedef, з’являється на мiсцi назви змiнної, а не одразу пiсля слова typedef. Синтаксично, typedef подiбний до зберiгальних класiв extern, static тощо. Ми використали заголовнi першi лiтери нових типiв, щоб вони вирiзнялися.
Як складнiший приклад, ми могли би застосувати typedef для деревовидних вузлiв з попереднього роздiлу:
typedef struct tnode *Treeptr;
typedef struct tnode { /* the tree node: */
char *word; |
/* points to |
the text */ |
int count; |
/* number of |
occurrences */ |
struct tnode *left; |
/* left child */ |
|
struct tnode *right; |
/* right child */ |
|
} Treenode;
Це утворить двi новi назви типiв iз назвою Treenode (структура) i Treeptr (покажчик на структуру). Пiсля цього, функцiя talloc виглядатиме як
Treeptr talloc(void)
{
return (Treeptr) malloc(sizeof(Treenode));
}
168 |
РОЗДIЛ 6. СТРУКТУРИ |
Слiд однак пiдкреслити, що оголошення typedef не створює, власно кажучи, цiлком нового типу — воно просто додає нову назву для того, який вже iснує. Також, семантика не зазнає жодних змiн — змiннi, оголошенi в такий спосiб, матимуть тi самi властивостi, що й змiннi, чий тип вказано безпосередньо. Фактично, typedef аналогiчний #define за винятком того, що, через те, що його iнтерпретовано компiлятором, вiн може впоратися з текстовими замiнами, якi значно перевищують можливостi препроцесора. Наприклад,
typedef int (*PFI)(char *, char *);
утворює тип PFI, який є покажчиком на функцiю (з двома аргументами char *), i повертає цiле. Це можна використати в такому контекстi, як
PFI strcmp, numcmp;
скажiмо, програми sort з Роздiлу 5.
Попри чисто естетичнi мiркування, iснують двi вагомiшi причини використання typedef. Перша, це параметризувати програму вiд проблем з портабельнiстю. При використаннi typedef з типами даних, якi можуть виявитись машинозалежними, потрiбно тiльки помiняти typedef, коли програму перенесено на iншу машину. Поширеним випадком є використання typedef-назв для рiзноманiтних цiлих величин, пiсля чого встановлення вiдповiдних значень для short, int та long для кожної окремої машини. Типи на зразок size_t та ptrdiff_t зi стандартної бiблiотеки є хорошим прикладом.
Другою цiллю typedef є надання кращої документацiї програмi — тип iз назвою Treeptr легше зрозумiти нiж такий, що оголошено просто як покажчик на складну структуру.
6.8Сполуки
Сполука — це змiнна, яка може втримувати (в рiзний час) об’єкти рiзного типу та розмiру, компiлятор обчислюючи розмiри та умови вирiвнювання. Сполуки забезпечують можливiстю манiпулювання рiзного роду даними, збереженими в єдиному мiсцi, без потреби включення в програму якоїсь машинозалежної iнформацiї. Вони аналогiчнi варiйованим записам в Pascal.
Для прикладу, який можна вiднайти, скажiмо, в керiвниковi символiв компiлятора, припустiмо, що константа може бути int, float або покажчиком на символьний масив. Значення певної константи потрiбно зберегти у змiннiй вiдповiдного типу, однак найзручнiшим для керiвника символiв буде, щоб це значення займало ту саму кiлькiсть пам’ятi та зберiгалося в тому самому мiсцi, незалежно вiд типу. Саме для цiєї мети iснують сполуки — єдина змiнна, яка може легiтимно утримувати будь-який iз вказаних типiв. Синтаксис основується на тому, що притаманний структурам:
6.8. СПОЛУКИ |
169 |
union u_tag { int ival; float fval; char *sval;
} u;
Змiнна u буде достатньо великою, щоб утримати найбiльше з цих трьох типiв; певний розмiр залежить вiд реалiзацiї. Будь-який з цих типiв можна присвоїти u i потiм використати в якомусь виразi за умови, що використання буде несуперечливим: отриманий тип повинен збiгатися з типом, збереженим останнього разу. Саме на програмiста падає вiдповiдальнiсть за тим, щоб стежити за тим який тип в дану мить збережено в сполуцi; результат залежить вiд реалiзацiї, якщо щось збережено як один тип, а добуто як iнший.
Синтаксично доступ до членiв сполуки здiйснюється як
назва-сполуки.член
або
покажчик-на-сполуку->член
точно так, як це вiдбувається в структурах. Якщо для слiдкування за тим який тип у дану мить збережено в сполуцi u використати змiнну utype, тодi ви можете зустрiти код подiбний на наступне
if (utype == INT) printf("%d\n", u.ival);
if (utype == FLOAT) printf("%f\n", u.fval);
if (utype == STRING) printf("%s\n", u.sval);
else
printf("bad type %d in utype\n", utype);
Сполуки можна зустрiти всерединi структур i масивiв, а також навпаки. Синтаксис доступу до члена сполуки розмiщеного в структурi (i навпаки) тотожний гнiздованим структурам. Наприклад, в структурному масивi, означеному як
struct {
char *name; int flags; int utype;
170 |
РОЗДIЛ 6. СТРУКТУРИ |
union {
int ival; float fval; char *sval;
}u;
}symtab[NSYM];
до члена ival можна звернутися як
symtab[i].u.ival
а до першого символу ланцюжка sval одним iз наступних
*symtab[i].u.sval
symtab[i].u.sval[0]
Насправдi сполука — це структура, в якiй всi члени мають змiщення вiдносно бази рiвне нулю, структура достатньо велика, щоб утримати найбiльшого члена, i вирiвнювання придатне для всiх типiв у сполуцi. Стосовно сполук так само як структур застосовнi тi самi операцiї: присвоєння або копiювання як цiлого, здобуття адреси i доступ до одного з членiв. Сполуку можна iнiцiювати тiльки величиною того самого типу що її перший член; таким чином сполуцi u вище можна присвоїти початкове значення тiльки у виглядi int.
Розподiльник пам’ятi з Роздiлу 8 демонструє як можна використати сполуку, щоб змусити вирiвнювання змiнної з певною межею пам’ятi.
6.9Розряднi поля
Коли з пам’яттю справдi скрутно, то може з’явитися потреба помiстити декiлька об’- єктiв в єдине машинне слово; одним iз поширених використань, наприклад, є набiр однобiтних прапорцiв у додатках на зразок таблицi символiв компiлятора. Формати даних нав’язанi ззовнi, як у випадку iнтерфейсiв до апаратного устаткування, також часто вимагають здатностi одержати частини слова.
Уявiть фрагмент компiлятора, що манiпулює таблицею символiв. Кожний iдентифiкатор у програмi має певну iнформацiю, пов’язану з ним, наприклад чи це ключове слово чи нi, чи зовнiшнє чи/або статичне, i так далi. Найбiльш компактним способом закодувати таку iнформацiю, це встановити однобiтний прапорець на єдиному char або int.
Звичний спосiб за допомогою якого це можна зробити, це означити набiр маск , що вiдповiдатимуть слушним позицiям бiтiв, як наприклад
6.9. РОЗРЯДНI ПОЛЯ |
171 |
#define KEYWORD |
01 |
#define EXTRENAL |
02 |
#define STATIC |
04 |
або
enum { KEYWORD = 01, EXTERNAL = 02, STATIC = 04 };
Числа повиннi бути в степенi двiйки. Пiсля цього доступ до бiтiв складатиметься з їхнього перебирання шляхом зсуву, маскування, i доповнювальних операторiв описаних в Роздiлi 2.
Певнi iдiоми зустрiчаються досить часто:
flags |= EXTERNAL | STATIC;
вмикає бiти EXTERNAL i STATIC у flags, тодi як
flags &= ~(EXTERNAL | STATIC);
вимикає їх, i
if ((flags & (EXTERNAL | STATIC)) == 0) ...
є iстинним, якщо обидва бiти вимкнено.
Незважаючи на легкiсть засвоєння цих iдiом, як альтернатива C пропонує можливiсть безпосереднього означення та доступу полiв всерединi слова замiсть порозрядних логiчних операторiв. Розрядне поле, або просто поле для скорочення, є набором сумiжних бiтiв всерединi єдиної, визначеної реалiзацiєю пам’ятової одиницi, яке ми називаємо словом . Синтаксис означення полiв i доступу до них основується на притаманному структурам. Наприклад, означення #define вище можна замiнити означеннями трьох полiв:
struct {
unsigned int is_keyword : 1; unsigned int is_extern : 1; unsigned int is_static : 1;
} flags;
172 |
РОЗДIЛ 6. СТРУКТУРИ |
Це означує змiнну пiд назвою flags, яка мiстить три 1-бiтних поля. Число, яке слiдує за двокрапкою вiдображає ширину поля в бiтах. Поля оголошено як unsigned int, щоб упевнитися, що це беззнаковi величини.
Звертання до окремих полiв таке саме як до iнших членiв структур: flags.is_keyword, flags.is_extern тощо. Поля мiстять нiби маленькi цiлi i можуть брати участь в арифметичних операцiях так само як iншi цiлi числа. Таким чином, попереднiй приклад бiльш природньо написати як
flags.is_extern = flags.is_static = 1;
щоб увiмкнути бiти,
flags.is_extern = flags.is_static = 0;
щоб вимкнути їх, i
if (flags.is_extern == 0 && flags.is_static == 0)
...
щоб перевiрити.
Майже все, що пов’язано з полями залежить вiд втiлення. Чи може поле виходити за межi слова залежить вiд реалiзацiї. Поля не обов’язково повиннi мати назви; безiменнi поля (тiльки двокрапка та ширина) застосовуються як заповнювачi. Щоб добитися вирiвнювання з наступною межею слова, можна використати спецiальну ширину поля 0.
Присвоєння значення полям вiдбувається злiва направо на одних машинах, i з права на лiво на iнших. Це означає, що незважаючи на те, що поля кориснi для утримування внутрiшньо-означених структур даних, слiд уважно розглянути питання який кiнець йде першим, розбираючи зовнiшньо-означенi данi; програми, якi залежать вiд таких речей не є портабельними. Поля можна оголосити тiльки як int; для портабельностi явно вкажiть signed (знаковi) або unsigned (беззнаковi). Вони не є масивами i не мають адрес, оператор & застосувати неможливо.
Роздiл 7
Ввiд i вивiд
Ввiд i вивiд не є, власне, складовою частиною самої мови C, тож ми не досi наголошували на них у нашому викладi. Проте, програми взаємодiють зi своїм середовищем у набагато складнiший спосiб нiж тi, що ми показали. В цьому роздiлi ми опишемо стандартну бiблiотеку — набiр функцiй, якi забезпечують можливiстю вводу та виводу, оперування ланцюжками, керування пам’яттю, використання математичних функцiй та розмаїття iнших послуг для C-програм. Ми зосередимо нашу увагу на вводi i виводi.
Стандарт ANSI точно описує цi функцiї, тож вони можуть iснувати в сумiснiй формi на будь-якiй системi, де iснує C. Програми, якi обмежують свою взаємодiю iз системою до можливостей, наданих стандартною бiблiотекою, можна переносити з однiєї системи на iншу без якихось змiн.
Властивостi функцiй бiблiотеки зазначено в бiльш нiж двох десятках файлiв заголовка; ми вже зустрiчалися з деякими з них, включаючи <stdio.h>, <string.h> i <ctype.h>. Ми не зможемо представити цiлу бiблiотеку тут, оскiльки ми бiльш зацiкавленi в написаннi програм, що можуть її використовувати. Саму бiблiотеку детально описано в Додатку Б.
7.1Стандартний ввiд i вивiд
Як ми зазначили в Роздiлi 1, бiблiотека втiлює просту модель текстового вводу i виводу. Текстовий потiк складається з послiдовностi рядкiв, кожний рядок закiнчується знаком нового рядка. Якщо система не працює в такий спосiб, бiблiотека зробить все вiд неї залежне, щоб здавалося, що все саме так i є. Наприклад, бiблiотека може перетворювати повернення каретки i переведення рядка на символ нового рядка при вводi i в зворотньому напрямку при виводi.
Найпростiший механiзм вводу — це читати по одному символу за один раз зi стандартного вводу, як правило це клавiатура, за допомогою getchar:
int getchar(void)
173
174 |
РОЗДIЛ 7. ВВIД I ВИВIД |
getchar повертає наступний введений знак кожний раз як її викликано, або EOF, коли вона зiткнулася з кiнцем файла. Символiчну константу EOF визначено в <stdio.h>. Це значення, типово, дорiвнює -1, але краще вживати EOF, щоб не залежати вiд певного значення. В багатьох середовищах, клавiатуру можна замiнити на файл скориставшись з умовного знака <, який позначає перенаправлення вводу: якщо програма prog послуговується getchar, тодi команда
prog <infile
змушує prog читати символи з infile, натомiсть. Перемкнення вводу вiдбувається в такий спосiб, що сама програма prog знає про змiну; зокрема, ланцюжок <infile не включається як аргумент командного рядка в argv. Замiна вводу також залишається невидимою, якщо ввiд надходить з iншої програми через конвеєр: на деяких системах, команда
otherprog | prog
запускає двi програми, otherprog i prog, i передає через конвеєр стандартний вивiд otherprog стандартному вводу prog.
Функцiя
int putchar(int)
використовується для виводу: putchar(c) виводить символ c на стандартний вивiд, типово, це екран. putchar повертає виведений символ, або EOF, якщо сталася помилка. Знову ж таки, вивiд можна перенаправити у файл за допомогою >filename. Якщо програма використовує putchar, команда
prog >outfile
записує стандартний вивiд до outfile, натомiсть. Якщо пiдтримуються конвеєри,
prog | anotherprog
помiщає стандартний вивiд prog у стандартний ввiд anotherprog. Вивiд, спричинений printf, також знаходить свiй шлях до стандартного пристрою виводу. Виклики putchar i printf можуть чергуватися — вивiд вiдбувається в тiй послiдовностi, в якiй виклики вiдбуваються.
Кожний вихiдний файл, що згадує якусь з функцiй бiблiотеки вводу/виводу, повинен мiстити рядок
7.2. ФОРМАТОВАНИЙ ВИВIД - PRINTF |
175 |
#include <stdio.h>
перед тим як їх вживати. Коли назву файла заголовка включено в дужки < та >, пошук такого вiдбувається в стандартному наборi мiсць в системi (наприклад, на UNIX, типовим каталогом є /usr/include).
Багато програм читають тiльки один потiк вводу i записують тiльки один виводу; для таких програм буде цiлком достатньо здiйснення вводу i виводу за допомогою getchar, putchar i printf, або, принаймнi, цього вистачить для початку. Це особливо так, якщо перенаправлення використовується для пiд’єднання виводу однiєї програми до вводу iншої. Наприклад, розглянемо програму lower, яка переводить свiй ввiд у нижнiй регiстр:
#include <stdio.h> #include <ctype.h>
main() /* lower: переводить ввiд у нижнiй регiстр */
{
int c
while ((c = getchar()) != EOF) putchar(tolower(c));
return 0;
}
Функцiю tolower визначено в <ctype.h>, вона перетворює лiтеру верхнього регiстру у нижнiй i повертає решту символiв незмiнними. Як ми зазначили ранiше,
функцiї на зразок getchar i putchar з <stdio.h> i tolower з <ctype.h> часто бувають макросами, щоб запобiгти витратi ресурсiв на виклик функцiї для кожного символу. Ми покажемо як здiйснити це в Роздiлi 8.5. Незалежно вiд того, як втiлено функцiї <ctype.h> на окремих машинах, програми, що використовують їх, не вимушенi знати деталей про набiр символiв.
Вправа 7-1. Напишiть програму, яка би перетворювала лiтери верхнього регiстру на нижнiй або нижнього на верхнiй в залежностi вiд того, за яким iм’ям її було викликано, на що вказуватиме argv[0].
7.2Форматований вивiд - printf
Функцiя виводу, printf, обертає внутрiшнi значення на друковнi знаки. Ми неформально застосовували printf у попереднiх роздiлах. Опис, який ви знайдете тут, охоплює найтиповiшi випадки використання функцiї, але не є повним; для всiх подробиць звернiться до Додатка Б.
176 |
РОЗДIЛ 7. ВВIД I ВИВIД |
int printf(char *format, arg1, arg2, ...);
printf перетворює, форматує i виводить свої аргументи на стандартний пристрiй виводу згiдно iз форматом (format). Вона повертає кiлькiсть виведених символiв.
Ланцюжок формату мiстить два типи об’єктiв: звичайнi символи, якi буде скопiйовано до вивiдного потоку i вказiвники перетворення, кожен з яких призводить до, власне, перетворення i виводу кожного наступного аргументу printf. Кожний вказiвник перетворення починається з % i закiнчується знаком перетворення. Мiж % i цим знаком можуть також знаходитись, в данiй послiдовностi:
∙Знак мiнуса, що вказує вирiвнювання з лiвого боку перетвореного аргументу.
∙Число, яке вказує мiнiмальну ширину поля. Перетворений аргумент буде виведено у полi, щонайменше, цiєї ширини. Якщо треба, то простiр буде заповнено лiворуч (або праворуч, якщо вказано вирiвнювання злiва) для того, щоб добитися потрiбної ширини поля.
∙Крапка, яка вiдокремлює ширину поля вiд вказiвника точностi.
∙Число точностi, яке вказує максимальну кiлькiсть знакiв, що буде виведено з ланцюжка, або кiлькiсть цифр пiсля десяткової крапки у випадку числа з рухомою точкою, або мiнiмальну кiлькiсть цифр цiлого числа.
∙Знак h, якщо цiле буде виведене як коротке (short), або l (англiйська лiтерал ), якщо як довге (long).
Символи перетворення показано в Таблицi 7.1. Якщо знак пiсля % не є вказiвником перетворення, поводження залишиться невизначеним.
Ширина або точнiсть можуть бути вказаними як *, у такому разi, значення обчислюється шляхом перетворення наступного аргументу (який повинен бути типу int). Наприклад, щоб вивести, щонайбiльше, max знакiв ланцюжка s, ми можемо написати
printf("%.*s", max, s);
Бiльшiсть перетворень формату було проiлюстровано в попереднiх роздiлах. Одним виключенням є точнiсть, оскiльки вона стосується ланцюжкiв. Наступна таблиця демонструє ефект рiзних означень для виводу hello, world (12 знакiв). Ми додали двокрапки навколо кожного поля, щоб було видно їхнiй розмiр.
:%s: |
:hello, world: |
|
:%10s: |
:hello, world: |
|
:%.10s: |
:hello, wor: |
|
:%-10s: |
:hello, |
world: |
:%.15s: |
:hello, |
world: |
7.2. ФОРМАТОВАНИЙ ВИВIД - PRINTF |
|
177 |
|
:%-15s: |
:hello, world |
: |
|
:%15.10s: |
: |
hello, wor: |
|
:%-15.10s: |
:hello, wor |
: |
|
|
Табл. 7.1: Основнi перетворення printf |
|
|
|
|
Знак |
Тип аргументу; виводиться як |
d, i |
int; десяткове число. |
oint; беззнакове вiсiмкове число (без нуля попереду).
x, X int; беззнакове шiстнадцяткове число (без 0x або 0X попереду), використовуючи abcdef або ABCDEF замiсть 10, . . . , 15.
u int; беззнакове цiле число.
cint; один символ.
schar *; виводить знаки ланцюжка до ’ 0’ або тiєї кiлькостi знакiв, яку задано вказiвником точностi.
fdouble; [-]m.dddddd , де кiлькiсть d задано вказiвником точностi (без
задання — 6).
е, Е double; [-]m.dddddde +/-xx або [-]m.ddddddE +/-xx , де кiлькiсть d
задано вказiвником точностi (без задання — 6).
g, G double; застосовувати %e або %E, якщо показник степеня менший за -4 або бiльший за або рiвний точностi; у протилежному випадку використовувати %f. Хвостовi нулi та хвостова десяткова крапка не виводяться.
p void *; покажчик (представлення залежить вiд реалiзацiї).
%жодного аргументу не перетворено, вивести %.
Застереження: printf використовує свiй перший аргумент для того, щоб дiзнатися як багато аргументiв в цiлому i який їхнiй тип. Вона заплутається i ви отримаєте неправильний результат, якщо недостатньо аргументiв або вони неправильного типу. Вам також слiд знати про вiдмiннiсть цих двох викликiв:
printf(s); |
/* |
ЗАЗНАЄ НЕВДАЧI, якщо s мiстить % */ |
printf("%s", s); |
/* |
БЕЗПЕЧНИЙ для використання */ |
Функцiя sprintf здiйснює тi самi перетворення, що й printf, але зберiгає свiй вивiд у ланцюжку:
int sprintf(char *string, char *format, arg1, arg2, ...);
sprintf форматує аргументи arg1, arg2 i так далi, вiдповiдно до формату (format), як i ранiше, але результат в ланцюжку (string) замiсть стандартного виводу; string повинен бути досить великим, щоб втримати результат.
178 |
РОЗДIЛ 7. ВВIД I ВИВIД |
Вправа 7-1. Напишiть програму, яка виводитиме довiльний текст вводу у розумний спосiб. Як мiнiмум, вона повинна вiдображати неграфiчнi знаки як вiсiмковi або шiстнадцятковi числа, в залежностi вiд вашого бажання, окрiм цього розбивати довгi рядки тексту.
7.3Списки аргументiв довiльної довжини
Цей роздiл мiстить втiлення мiнiмальної версiї printf для демонстрацiї того, як написати функцiю, яка би обробляла список аргументiв довiльної довжини у машинонезалежний спосiб. Оскiльки ми, головним чином, зацiкавленi в опрацьовуваннi аргументiв, minprintf оброблятиме форматувальний ланцюжок i аргументи, але викликатиме дiйсний printf для перетворень формату. Чинним оголошенням printf
є
int printf(char *fmt, ...)
де ... означає, що кiлькiсть i типи цих аргументiв може вiдрiзнятися. Оголошення
... може тiльки з’являтися в кiнцi списку аргументiв. Нашу minprintf оголошено як
void minprintf(char *fmt, ...)
оскiльки ми не повертатимемо вiдлiк символiв, як це робить printf.
Делiкатний момент полягає в тому, як minprintf просувається через список аргументiв, в той час як сам список навiть не має iменi. Стандартний файл заголовка <stdarg.h> мiстить набiр макросiв, який визначає, як проходити список аргументiв. Втiлення цього файла заголовка може вiдрiзнятися на рiзних машинах, але iнтерфейс, який вiн представляє є однорiдним.
Тип va_list використовується для оголошення змiнної, яка посилатиметься почерзi до кожного аргументу; в minprintf ця змiнна називається ap, як скорочення вiд ¨argument pointer¨ (покажчик на аргумент). Макрос va_start iнiцiалiзує ap таким чином, щоб вона вказувала на перший безiменний аргумент. Макрос потрiбно викликати один раз до того, як використовувати ap. Необхiдно, щоб був щонайменше один аргумент з iменем; останнiй названий аргумент використовується va_start, щоб розпочати роботу.
Кожний виклик va_arg повертає один аргумент i переводить ap до наступного; va_arg використовує назву типу, щоб визначити, який тип повернути i наскiльки великим повинен бути крок. I, нарештi, va_end здiйснює очистку, якщо треба. Її потрiбно викликати до того, як програма поверне своє значення.
Цi властивостi складають базу для нашої спрощеної printf:
#include <stdarg.h>
7.3. СПИСКИ АРГУМЕНТIВ ДОВIЛЬНОЇ ДОВЖИНИ |
179 |
/* minprintf: |
мiнiмальна printf зi списком аргументiв * |
|
* змiнної довжини |
*/ |
|
void minprintf(char *fmt, ...) |
|
|
{ |
|
|
va_list ap; /* покажчик на кожний безiменний аргумент *
* по черзi |
*/ |
char *p, *sval; |
|
int ival; |
|
double dval; |
|
va_start(ap, fmt); /* заставляє ap бути покажчиком * * на 1-ий безiменний аргумент */
for (p = fmt; *p; p++) { if (*p != ’%’) {
putchar(*p);
continue;
}
switch (*++p) { case ’d’:
ival = va_arg(ap, int); printf("%d", ival); break;
case ’f’:
dval = va_arg(ap, double); printf("%f", dval); break;
case ’s’:
for (sval = va_arg(ap, char *); *sval; sval++) putchar(*sval);
break;
default:
putchar(*p);
break;
}
}
va_end(ap); /* очистка */
}
Вправа 7-3. Виправiть minprintf так, щоб вона втiлювала додатковi можливостi printf.
180 |
РОЗДIЛ 7. ВВIД I ВИВIД |
7.4Форматований ввiд - scanf
Функцiя scanf є аналогiчною printf, але тiльки для вводу. Вона надає багато з тих самих можливостей перетворення в зворотньому, правда, напрямку.
int scanf(char *format, ...)
scanf зчитує знаки зi стандартного вводу, iнтерпретуючи їх вiдповiдно до специфiкацiї, вказанiй форматом (format), i зберiгає результат за допомогою решти аргументiв. Аргумент формату описано нижче; решта аргументiв, кожен з яких повинен бути покажчиком, вказує на те, де вiдповiдний перетворений ввiд потрiбно зберегти. Так само як i з printf, цей роздiл є пiдсумком найкориснiших рис, а не вичерпним описом.
scanf зупиняється, якщо вона вичерпає свiй список формату, або коли ввiд не збiгається iз контрольною специфiкацiєю. Вона повертає як значення число елементiв вводу, що зiйшлися i яких було присвоєно. Це можна використати, щоб взнати, скiльки об’єктiв було знайдено. При завершеннi файла повертається EOF; зауважте, що не те саме що й 0, який означає, що наступний символ вводу не зiйшовся iз першим описом в ланцюжку форматування. Наступний виклик scanf вiдновить продовжить пошук, починаючи з мiсця, де було оброблено останнiй знак.
Iснує також функцiя sscanf, яка читає свiй ввiд з ланцюжка замiсть стандартного вводу:
int sscanf(char *string, char *format, arg1, arg2, ...)
Вона сканує ланцюжок, вiдповiдно до формату format i зберiгає отриманi значення в arg1, arg2 i так далi. Останнi мають бути покажчиками.
Ланцюжок формату, як правило, мiстить описи перетворення, що використовуються для керування перетворенням вводу. Ланцюжок формату може мiстити:
∙Пробiли i табуляцiю, якi не iгноруються.
∙Звичайнi знаки (не %), якi повиннi зiйтися з наступним символом, який не є пробiлом, з потоку вводу.
∙Описувачi перетворення, що складаються зi знака %, необов’язкового знака блокування присвоєння *, необов’язкового числа, яке вказує ширину поля, необо- в’язкових h, l або L, якi вказують ширину адресата та символ перетворення.
Описувач перетворення описує перетворення наступного поля вводу. Звичайно, результат розмiщено в змiннiй, на яку вказує вiдповiдний аргумент. Якщо ж за допомогою * вказано блокування присвоєння, ввiдне поле пропускається, присвоєння не вiдбувається. Ввiдним полем вважається ланцюжок знакiв, якi не є пробiлами; воно продовжиться або до наступного пробiлу, або доки ширину поля, якщо вказано,
7.4. ФОРМАТОВАНИЙ ВВIД - SCANF |
181 |
вичерпано. Це означає, що scanf читатиме крiзь границi для того, щоб знайти ввiд, оскiльки символи нового рядка також вважаються пробiлами. (Символами-пробiлами вважаються пробiл, табуляцiя, новий рядок, повернення каретки, вертикальна табуляцiя i змiна сторiнки.)
Символ перетворення визначає iнтерпретацiю ввiдного поля. Вiдповiдний аргумент повинен бути покажчиком, як вимагається семантикою виклику за за значенням C. Символи перетворень показано в Таблицi 7.2).
Табл. 7.2: Основнi перетворення scanf
Символ Вводимi данi; тип аргументу
dдесяткове цiле; int *.
iцiле; int *. Цiле може бути у вiсiмковiй (з 0 попереду) або шiстнадцятковiй (з 0x або 0X) формi.
o вiсiмкове цiле (iз або без переднього 0); int *.
uбеззнакове десяткове цiле; unsigned int *.
xшiстнадцяткове цiле (iз або без попереднього 0x або 0X); int *.
cсимволи; char *. Наступнi введенi знаки (без задання 1) розмiщено у вказане мiсце. Звичайний пробiл пригнiчено; щоб прочитати наступний не-пробiл, використайте %1s.
sсимвольний ланцюжок (не екранований); char *, вказуючи на масив символiв достатньо великий для ланцюжка i кiнцевого ’ 0’, який
буде додано.
e, f, g число з рухомою точкою з необов’язковим знаком, необов’язковою експонентою; float *.
%буквальний %; присвоєння не вiдбувається
Перед знаками перетворення d, i, o, u та x може стояти h, щоб вказати, що в списку аргументiв знаходиться покажчик на коротке цiле (short) а не цiле (int), або l (англiйська л ), щоб вказати на покажчик на довге цiле (long).
Як перший приклад, простенький калькулятор з Роздiлу 4 можна написати зi scanf, щоб здiйснювати перетворення вводу:
#include <stdio.h>
main() /* простий калькулятор */
{
double sum, v;
sum = 0;
while (scanf("%lf", &v) == 1) printf("\t%.2f\n", sum += v);
return 0;
182 |
РОЗДIЛ 7. ВВIД I ВИВIД |
}
Скажiмо, ми хочемо прочитати рядки вводу, якi мiстять дату у формi
25 Dec 1988
Твердження зi scanf у такому разi становитиме
int day, year;
char monthname[20];
scanf("%d %s %d", &day, monthname, &year);
Знак & не використовується зi змiнною, що зберiгає назву мiсяця monthname, оскiльки назва масиву i так є покажчиком. Буквальнi знаки також можуть з’являтися в ланцюжку формату scanf; вони повиннi зiйтися з такими самими знаками у вводi. Тож ми могли би читати дати, що мають форму мм/дд/рр за допомогою виразу зi scanf:
int day, month, year;
scanf("%d/%d/%d", &month, &day, &year);
scanf iгнорує пробiли i табуляцiю в ланцюжковi формату. Бiльше того, вона пропускає пропуски i пробiли (пробiли, табуляцiю, новi рядки тощо), розглядаючи ввiд. Для прочитання вводу, чий формат не є сталим, кращим буде читати по одному рядковi за раз, пiсля чого розбити його на окремi частини за допомогою scanf. Наприклад, скажiмо ми хотiли би прочитати рядки, якi можуть включати дату в однiй з, наведених вище, форм. В такому разi ми могли би написати
while (getline(line, sizeof(line)) > 0) {
if (sscanf(line, "%d %s %d", &day, monthname, &year) == 3) printf("valid: %s\n", line); /* форма 25 Dec 1988 */
else if (sscanf(line, "%d/%d/%d", &month, &day, &year) == 3) printf("valid: %s\n", line); /* форма мм/дд/рр */
else
printf("invalid: %s\n", line); /* недiйсна форма */
}
Виклики scanf можна змiшувати з викликами iнших функцiй вводу. Наступний виклик будь-якої ввiдної функцiї почнеться з прочитання першого знака, не прочитаного scanf.
7.5. ДОСТУП ДО ФАЙЛIВ |
183 |
Останнє попередження: аргументи scanf i sscanf повиннi бути покажчиками. Найчастiшою помилкою є написання
scanf("%d", n);
замiсть
scanf("%d", &n);
Цю помилку, як правило, не буде виявлено пiд час компiляцiї.
Вправа 7-4. Напишiть власну версiю scanf, аналогiчну minprintf з попереднього роздiлу.
Вправа 5-5. Перепишiть postfix-калькулятор з Роздiлу 4 так, щоб вiн використовував scanf i/або sscanf для вводу i перетворення чисел.
7.5Доступ до файлiв
Приклади, з якими ми досi стикалися, усi читали стандартний ввiд i записували до стандартного виводу, якi автоматично визначаються для програми операцiйною системою.
Наступним кроком буде написати програму, яка матиме доступ до файла. Одна з програм, що демонструє необхiднiсть таких операцiй, це cat, яка зчеплює набiр вказаних їй файлiв, виводячи їх на стандартний пристрiй виводу. cat використовується для видруку файлiв на екранi i як збирач вводу загального призначення для програм, якi не мають можливостi доступу до файлiв за iменем. Наприклад, команда
cat x.c y.c
виводить вмiст файлiв x.c та y.c (i нiчого бiльше) на стандартний пристрiй виводу. Питання в тому, як забезпечити прочитання даних файлiв, тобто, як пiд’єднати зовнiшнi назви, про якi думає користувач, до твердження, яке читатиме данi.
Правила — простi. Перед тим як його можна прочитати або здiйснити до нього запис, файл потрiбно вiдкрити за допомогою бiблiотечної функцiї fopen. fopen вiзьме зовнiшнi назви, такi як x.c або y.c, здiйснить певнi службовi дiї i переговори з операцiйною системою (деталi яких не повиннi нас хвилювати), i повертає покажчик, який використовуватиметься в наступних читаннях i записах до файла.
Цей покажчик, який називається покажчиком файла, вказує на структуру, яка мiстить iнформацiю про файл, таку як мiсцеперебування буфера, поточне положення знака в буферi, чи файл читається, чи до нього йде запис, i, чи мали мiсце помилки або вказiвник кiнця файла. Користувачi не повиннi знати подробиць, оскiльки визначення, що знаходяться в <stdio.h> включають оголошення структури пiд назвою FILE. Єдине, що потрiбно знати, це оголошення покажчика файла, яке спрощено виглядає як
184 |
РОЗДIЛ 7. ВВIД I ВИВIД |
FILE *fp;
FILE *fopen(char *name, char *mode);
Це означає, що fp є покажчиком на (структуру типу) FILE i fopen повертає покажчик на FILE. Замiтьте, що FILE, це назва типу, така сама як int, а не мiтка структури; її означено за допомогою typedef. (Деталi щодо того як можна втiлити fopen на UNIX надано в Роздiлi 8.5).) Виклик fopen у програмi має вигляд
fp = fopen(name, mode);
Першим аргументом fopen є символьний ланцюжок, що мiстить назву файла. Другий аргумент — це режим, також символьний ланцюжок, який вказує на те, як буде використовуватись файл. Допустимими режимами можуть бути режим читання ( r — read), запису ( w — write) i долучення ( a — append). Деякi системи розрiзняють текстовi i бiнарнi файли, для останнiх потрiбно додати b (binary) до лiтери режиму.
Якщо файл, якого не iснує, вiдкрито для запису або додання, його буде створено, якщо це можливо. Вiдкриття наявного файла для запису спричинить звiльнення старого змiсту, тодi як вiдкриття для дозапису (доточення) — збереже старий змiст. Спроба прочитання файла, якого не iснує, викличе помилку; iснують також iншi причини помилок, як скажiмо спроба прочитати файл, на який ви не маєте дозволу. Якщо сталася якась помилка, fopen повертає NULL. (Характер помилки можна визначити точнiше; подивiться обговорення функцiй з обробки помилок у Роздiлi 1 Додатка Б.)
Наступний крок — мати спосiб читання або запису до файла пiсля того, як його вiдкрито. getc повертає наступний знак з файла; вона вимагає покажчика на файл, щоб знати, який саме файл.
int getc(FILE *fp)
getc повертає наступний знак з потоку, на який вказує fp; вона повертає EOF у випадку кiнця файла або помилки.
putc — це функцiя виводу:
int putc(int c, FILE *fp)
putc запише символ з c до файла fp i поверне записаний знак або EOF у разi помилки. Так само як getchar i putchar, getc iз putc можуть бути макросами, а не функцiями. Пiд час запуску C-програми, середовище операцiйної системи бере на себе завдання вiдкриття трьох файлiв i надання покажчикiв до них. Цi файли — це стандартний ввiд, стандартний вивiд i стандартна помилка; вiдповiднi їм покажчики
7.5. ДОСТУП ДО ФАЙЛIВ |
185 |
називаються stdin, stdout i stderr, i оголошено в <stdio.h>. За звичайних обставин, stdin сполучено з клавiатурою, тодi як stdout iз stderr — з екраном, але stdin i stdout можна перенаправити в iншi файли або конвеєри, як описано в Роздiлi 7.1. getchar i putchar можна визначити через getc, putc, stdin i stdout наступним
чином:
#define |
getchar() |
getc(stdin) |
#define |
putchar(c) |
putc((c), stdout) |
Для форматованого вводу або виводу iз файлами, можна використати функцiї fscanf i fprintf. Вони тотожнi scanf iз printf за винятком того, що першим аргументом є покажчик на файл, який вказує на, власне, файл, який читатиметься; другим аргументом є ланцюжок формату.
int fscanf(FILE *fp, char *format, ...) int fprintf(FILE *fp, char *format, ...)
Маючи такий начерк, ми тепер можемо написати власну програму cat для зчеплення файлiв. Схема буде тiєю самою, що виявилась корисною в багатьох iнших програмах. Якщо iснують аргументи командного рядка, їх буде iнтерпретовано як назви файлiв i оброблено по-порядку. Якщо немає аргументiв, обробляється стандартний ввiд.
#include <stdio.h>
/* cat: зчеплює файли, 1-а версiя */ main(int argc, char *argv[])
{
FILE *fp;
void filecopy(FILE *, FILE *)
if (argc == 1) /* немає аргументiв; копiює *
* стандартний ввiд |
*/ |
filecopy(stdin, stdout); |
|
else |
|
while(--argc > 0) |
|
if ((fp = fopen(*++argv, "r")) == NULL) { printf("cat: can’t open %s\n, *argv); return 1;
} else {
filecopy(fp, stdout); fclose(fp);
186 |
РОЗДIЛ 7. ВВIД I ВИВIД |
}
return 0;
}
/* filecopy: копiює файл ifp до файла ofp */ void filecopy(FILE *ifp, FILE *ofp)
{
int c;
while ((c = getc(ifp)) != EOF) putc(c, ofp);
}
Покажчики на файл stdin i stdout є об’єктами типу FILE *. Незважаючи на це, вони є константами (сталими), а не змiнними, що внеможливлює надання їм нового значення.
Функцiя
int fclose(FILE *fp)
— це протилежнiсть fopen, вона розриває зв’язок мiж покажчиком на файл i зовнiшньою назвою, отриманою fopen, звiльняючи покажчик для iншого файла. Оскiльки бiльшiсть операцiйних систем мають певне обмеження кiлькостi файлiв, якi програма спроможна вiдкрити одночасно, хорошою iдеєю буде звiльнити покажчики на файли, якщо їх бiльше не потрiбно, як ми це зробили в cat. Iснує також iнша причина зробити fclose для файла виводу — це очищує буфер, в якому putc накопичує вивiд. fclose викликається автоматично для кожного вiдкритого файла при нормальному завершеннi програми. (Ви можете замкнути stdin i stdout, якщо вони непотрiбнi. Їх також можна переназначити за допомогою бiблiотечної функцiї freopen.)
7.6Обробка помилок - stderr i exit
Обробка помилок у cat не є iдеальною. Проблема в тому, що якщо неможливо дiстатися до одного з файлiв з якоїсь причини, дiагностичне повiдомлення буде виведене в кiнцi зчепленого виводу. Це, можливо, допустимо, якщо вивiд направляється на екран, але не тодi, коли вiн надходить у файл або iншу програму через конвеєр.
Щоб краще справлятися з подiбними ситуацiями, програмам окрiм stdin i stdout надається ще один потiк виводу пiд назвою stderr. Вивiд, записаний до stderr, як правило, з’являється на екранi навiть якщо стандартний вивiд перенаправлено.
Спробуймо виправити cat так, щоб писати її повiдомлення про помилки до стандартної помилки.
7.6. ОБРОБКА ПОМИЛОК - STDERR I EXIT |
187 |
#include <stdio.h>
/* cat: зчеплює файли, 2-а версiя */ main(int argc, char *argv[])
{
FILE *fp;
void filecopy(FILE *, FILE *);
char *prog = argv[0]; /* назва програми, для помилок */
if (argc == 1 ) /* немає аргументiв; копiює |
* |
* стандартний ввiд |
*/ |
filecopy(stdin, stdout); |
|
else |
|
while (--argc > 0)
if ((fp = fopen(*++argv, "r")) == NULL) { fprintf(stderr, "%s: can’t open %s\n",
prog, *argv); exit(1);
} else {
filecopy(fp, stdout); fclose(fp);
}
if (ferror(stdout)) {
fprintf(stderr, "%s: error writing stdout\n", prog); exit(2);
}
exit(0);
}
Програма сигналiзує про помилки в два способи. Перший: дiагностичний вивiд, спричинений fprintf надходить до stderr, тож вiн знаходить свiй шлях на екран замiсть того, щоб зникнути кудись через конвеєр або в файлi виводу. Ми включили в повiдомлення назву програми з argv[0], тож, якщо програма використовується разом з iншими, джерело помилки буде iдентифiковано.
Другий: програма використовує функцiю стандартної бiблiотеки exit, яка завершує виконання програми, якщо її викликано. Аргумент exit стане доступний будьякому процесовi, який викликав даний, тож успiх чи невдача програми може перевiрятись iншою програмою, яка використовує першу як дочiрнiй процес. Традицiйно, повернене значення 0 сигналiзує, що все успiшно; ненульовi значення, звично, означають анормальнi ситуацiї. exit викликає fclose для кожного вiдкритого файла виводу для того, щоб очистити будь-який буферований вивiд.
Всерединi main, return вираз еквiвалентне exit(вираз ). Перевага використання
188 |
РОЗДIЛ 7. ВВIД I ВИВIД |
exit полягає в тому, що її можна викликати з iнших функцiй i її виклики можна знаходити за допомогою програм пошуку за шаблоном як тi, якi ви знайдете у Роздiлi 5.
Функцiя ferror повертає ненульове значення, якщо сталася помилка при обробцi потоку fp.
int ferror(FILE *fp)
Хоча помилки виводу — рiдкiсть, вони теж стаються (наприклад, якщо диск заповнено до кiнця), тому виробнича програма повинна це перевiрити.
Функцiя feof(FILE *) є аналогiчною ferror; вона повертає ненульове значення, якщо досягнуто кiнця файла.
int feof(FILE *fp)
Загалом, нас не цiкавив статус виходу наших маленьких iлюстративних програм, але будь-яка серйозна програма повинна пiклуватися про повернення зрозумiлих i корисних значень статусу.
7.7Ввiд i вивiд рядкiв
Стандартна бiблiотека передбачає функцiю вводу i виводу fgets, аналогiчну getline, яку ми використовували в попереднiх роздiлах:
char *fgets(char *line, int maxline, FILE *fp)
fgets читає наступний рядок вводу (включаючи символ нового рядка) з файла fp у символьний масив line; щонайбiльше maxline-1 знакiв буде прочитано. Отриманий в результатi рядок буде завершено ’ 0’. Звично, fgets повертає рядок; у випадку кiнця файла або помилки, вона повертає NULL. (Наша getline повертає довжину рядка, — кориснiше значення; нуль означає кiнець файла.)
Для виводу, функцiя fputs записує ланцюжок (який не повинен включати символ нового рядка) до файла:
int fputs(char *line, FILE *fp)
Вона повертає EOF, якщо сталася помилка, i додатнє значення у протилежному випадку.
Функцiї бiблiотеки gets i puts тотожнi fgets i fputs, але оперують над stdin i stdout. Плутанину викликає те, що gets видаляє кiнцевий ’ n’, тодi як puts додає його.
Щоб продемонструвати, що немає нiчого особливого в таких функцiях як fgets i fputs, ось вони, будь ласка, скопiйованi зi стандартної бiблiотеки до нашої системи:
7.7. ВВIД I ВИВIД РЯДКIВ |
189 |
/* fgets: отримує щонайбiльше n символiв з iop */ char *fgets(char *s, int n, FILE *iop)
{
register int c; register char *cs;
cs = s;
while (--n > 0 && (c = getc(iop)) != EOF) if ((*cs++ = c) == ’\n’)
break; *cs = ’\0’;
return (c == EOF && cs == s) ? NULL : s;
}
/* fputs: додає ланцюжок s до файла iop */ int fputs(char *s, FILE *iop)
{
int c;
while (c = *s++) putc(c, iop);
return ferror(iop) ? EOF : 0;
}
Зневiдомих причин, стандарт вказує на вiдмiннi значення повернення для ferror i fputs. Досить легко втiлити нашу getline за допомогою fgets:
/* getline: читає рядок, повертає довжину */ int getline(char *line, int max)
{
if (fgets(line, max, stdin) == NULL) return 0;
else
return strlen(line);
}
Вправа 7-6. Напишiть програму по порiвнянню двох файлiв, виводячи перший рядок, там, де вони вiдрiзняються.
Вправа 7-7. Змiнiть програму знаходження по шаблону з Роздiлу 5 таким чином, щоб вона брала свiй ввiд з набору вказаних файлiв, або, якщо жодного файла не вказано як аргумент, зi стандартного вводу. Можливо, можна також виводити назву файла, в якому знайдено рядок, який збiгся.
190 |
РОЗДIЛ 7. ВВIД I ВИВIД |
Вправа 7-8. Напишiть програму видруку набору файлiв, починаючи кожний на новiй сторiнцi, з титулом i вiдлiком сторiнок для кожного файла.
7.8Додатковi функцiї
Стандартна бiблiотека надає широкий вибiр рiзноманiтних функцiй. Цей роздiл, це стислий перегляд найкориснiших з них. Додатковi деталi i багато iнших функцiй можна знайти в Додатку Б.
7.8.1Операцiї з ланцюжками
Ми вже згадали ланцюжковi функцiї strlen, strcpy, strcat i strcmp, якi ви знайдете
в<string.h>. В наступному оглядi s iз t — це char *, тодi як c та n — це int.
strcat(s,t) доточує t до кiнця s strncat(s,t,n) доточує n знакiв t до кiнця s
strcmp(s,t) повертає вiд’ємне, нуль або додатнє значення для s < t, s ==
|
t або s > t |
strncmp(s,t,n) |
те саме, що й strcmp, але тiльки для перших n знакiв |
strcpy(s,t) |
копiює t до s |
strncpy(s,t,n) |
копiює щонайбiльше n символiв t до s |
strlen(s) |
повертає довжину s |
strchr(s,c) |
повертає покажчик на перший знайдений символ c у s, або NULL, |
|
якщо жодного не знайдено |
strrchr(s,c) |
повертає покажчик на останнiй знайдений символ c у s, або |
|
NULL, якщо жодного не знайдено |
7.8.2Перевiрка та перетворення класiв символiв
Декiлька функцiй з <ctype.h> здiйснюють перевiрку символiв i перетворення. В наступному, c є типу int, який може бути представленим як unsigned char (беззнаковим символом) або EOF. Функцiї повертають int.
