- •Передмова
- •Передмова до першого видання
- •Вступ
- •Вступний урок
- •Твердження for
- •Масиви
- •Аргументи - виклик за значенням
- •Типи, оператори та вирази
- •Константи
- •Оголошення
- •Оператори приросту та спаду
- •Оператори та вирази присвоєння
- •Вирази умов
- •Керування потоком
- •Вирази та блоки
- •If-else
- •Else if
- •Switch
- •Цикли while та for
- •Файли заголовка
- •Препроцесор C
- •Покажчики та масиви
- •Покажчики й адреси
- •Покажчики та масиви
- •Арифметика адрес
- •Аргументи командного рядка
- •Структури
- •Масиви структур
- •Покажчики на структури
- •Typedef
- •Сполуки
- •Ungetc
- •Виконання команд
- •Генератор випадкових чисел
- •Open, creat, close, unlink
110 |
РОЗДIЛ 5. ПОКАЖЧИКИ ТА МАСИВИ |
int getch(void); void ungetch(int);
/* getint: зберiгає наступне введене цiле у *pn */ int getint(int *pn)
{
int c, sign;
while (isspace(c = getch())) |
/* опустити пробiли */ |
; |
|
if (!isdigit(c) && c != EOF && c != ’+’ && c != ’-’) { ungetch(c); /* c не є числом */
return 0;
}
sign = (c == ’-’) ? -1 : 1; if (c == ’+’ || c == ’-’)
c = getch();
for (*pn = 0; isdigit(c), c = getch()) *pn = 10 * *pn + (c - ’0’);
*pn *= sign; if (c != EOF)
ungetch(c); return c;
}
Скрiзь у getint, *pn використовується як звичайна змiнна типу int. Ми також використали getch i ungetch (описанi у Роздiлi 4.3), тож один зайвий знак, який ми змушенi були прочитати, можна було вiдкинути назад на ввiд.
Вправа 5-1. Так як ми написали, функцiя getint сприймає + або - без числа як чинне числове значення. Виправте цю помилку, щоб таку послiдовнiсть було вiдкинуто.
Вправа 5-2. Напишiть getfloat, аналогiчну getint функцiю але для чисел з рухомою точкою. Який тип повертає getfloat?
5.3Покажчики та масиви
У C iснує тiсний зв’язок мiж покажчиками i масивами, настiльки тiсний, що покажчики i масиви повиннi обговорюватись одночасно. Будь-яку дiю, що можна здiйснити через iндексацiю масиву, можна також через покажчики. Версiя з покажчиками буде дещо швидшою, але, принаймнi для непосвячених, також трохи важчою у розумiннi.
Оголошення
5.3. ПОКАЖЧИКИ ТА МАСИВИ |
111 |
int a[10];
означує масив розмiром у 10 елементiв, тобто блок з 10-и сумiжних об’єктiв з назвами a[0], a[1], . . . , a[9].
Позначення a[i] означає i-ний елемент масиву. Якщо pa, це покажчик на цiле, оголошений як
int *pa;
тодi присвоєння
pa = &a[0];
змушує pa вказувати на елемент з iндексом нуль масиву a, тобто pa мiститиме адресу a[0].
Тепер присвоєння
x = *pa;
копiює вмiст a[0] до x.
Якщо pa вказує на певний елемент масиву, тодi певна рiч, що pa+1 вказуватиме на наступний елемент, тодi як pa+n вказуватиме на елемент n пiсля pa, тодi як pa-n — на елемент n попереду. Таким чином, якщо pa вказує на a[0], тодi
*(pa+1)
112 |
РОЗДIЛ 5. ПОКАЖЧИКИ ТА МАСИВИ |
посилається на вмiст a[1], pa+n є адресою a[n], а *(pa+n) — вмiстом a[n].
Цi зауваження дiйснi, незалежно вiд типу або розмiру змiнних масиву a. Змiст виразу додати 1 до покажчика , i взагалi вся арифметика покажчикiв зводиться до того, що pa+1 вказує на наступний об’єкт, тодi як pa+n вказує на n-ний об’єкт поза pa.
Вiдповiднiсть мiж iндексацiєю й арифметикою покажчикiв дуже близька. За визначенням, значення змiнної або виразу типу масив — це адреса елемента нуль масиву. Таким чином, пiсля присвоєння
pa = &a[0];
pa й a матимуть абсолютно однаковi значення. Оскiльки назва масиву — це лише синонiм мiсцезнаходження початкового елемента, присвоєння pa=&a[0] можна з таким самим успiхом написати як
pa = a;
Швидше несподiваним, з першого погляду, може здатися той факт, що посилання на a[n] можна також записати як *(a+n). Обчислюючи a[n], C сама перетворює його в *(a+n) — цi двi форми еквiвалентнi. Застосовуючи оператор & до обох частин цiєї еквiвалентностi, ми дiйдемо висновку, що &a[n] й a+n також однаковi: a+n є адресою n-ного елемента пiсля a. З iншого боку, якщо pa є покажчиком, вiн мiг би використовуватись з iндексом: pa[n], що рiвнозначно *(pa+n). Одним словом, вираз масив-та-iндекс рiвнозначний тому, що записано як покажчик i змiщення.
Iснує одна вiдмiннiсть мiж назвою масиву i покажчиком, яку слiд пам’ятати. Справа в тiм, що покажчик — це змiнна, тож pa=a i pa++ дозволено. Проте назва масиву не є мiнною, тож конструкцiї на зразок a=pa або a++ заборонено.
Коли функцiї передано назву масиву, що передається насправдi, це мiсцезнаходження першого елемента. Всерединi викликаної функцiї цей аргумент стає локальною змiнною, тож назва масиву, як параметр, насправдi є покажчиком — тобто змiнною, котра мiстить адресу. Ми можемо використати цей факт для написання iншої версiї strlen — функцiї, яка обчислює довжину ланцюжка.
/* strlen: повертає довжину ланцюжка s */ int strlen(char *s)
5.3. ПОКАЖЧИКИ ТА МАСИВИ |
113 |
{
int n;
for (n = 0; *s != ’\0’, s++) n++;
return n;
}
Оскiльки s — це покажчик, то прирiст його дозволено; s++ не матиме жодного впливу на символьний ланцюжок функцiї, яка викликала strlen — лише здiйснює прирiст приватної копiї покажчика в strlen. Це означає, що всi виклики на зразок
strlen("hello, world"); |
/* ланцюжкова стала */ |
strlen(array); |
/* char array[100]; */ |
strlen(ptr); |
/* char *ptr; */ |
працюватимуть.
Формально, при визначеннi функцiї, параметри
char s[];
i
char *s;
рiвнозначнi. Ми надаємо перевагу останньому, оскiльки вiн вiдвертiше вказує на те, що ця змiнна є покажчиком. Коли назву масиву передано функцiї, остання може, залежно вiд того як їй зручнiше, вважати, що їй передано або масив, або покажчик, i опрацьовувати його вiдповiдно. Вона навiть може вживати обидвi нотацiї, якщо це здається їй слушним i зрозумiлим.
Можливо також передати функцiї лише частину масиву, вказавши покажчиком на початок частини масиву. Наприклад, якщо a — це масив, то
f(&a[2])
i
f(a+2)
обидва, передають функцiї f адресу частини масиву, яка починається з a[2]. У самiй f, оголошення параметрiв може звучати як
