- •1.1 Перші кроки
- •1.2 Змінні й арифметичні вирази
- •1.3 Твердження for
- •1.4 Символічні константи
- •1.5 Ввід і вивід знаків
- •1.5.1 Копіювання файла
- •1.5.2 Відлік символів
- •1.5.3 Відлік рядків
- •1.6 Масиви
- •1.7 Функції
- •1.8 Аргументи - виклик за значенням
- •1.9 Символьні масиви
- •1.10 Зовнішні змінні й область дії
- •2.1 Назви змінних
- •2.2 Типи даних і розміри
- •2.3 Константи
- •2.4 Оголошення
- •2.5 Арифметичні операції
- •2.6 Реляційні та логічні оператори
- •2.7 Перетворення типів
- •2.8 Оператори приросту та спаду
- •2.9 Розрядні оператори
- •2.10 Оператори та вирази присвоєння
- •2.11 Вирази умов
- •2.12 Пріоритет і послідовність обчислення
- •3.1 Вирази та блоки
- •3.3 Else if
- •3.4 Switch
- •3.5 Цикли while та for
- •3.6 Цикли do-while
- •3.7 Break і continue
- •3.8 Goto та мітки
- •4.1 Основні знання про функції
- •4.2 Функції, які не повертають цілих
- •4.3 Зовнішні змінні
- •4.4 Правила області дії
- •4.5 Файли заголовка
- •4.6 Статичні змінні
- •4.7 Регістрові змінні
- •4.8 Структура блоків
- •4.10 Рекурсія
- •4.11 Препроцесор c
- •4.11.1 Включення файлів
- •4.11.2 Заміна макросів
- •4.11.3 Обумовлене включення файлів
- •5.1 Покажчики й адреси
- •5.2 Покажчики й аргументи функцій
- •5.3 Покажчики та масиви
- •5.4 Арифметика адрес
- •5.5 Покажчики на символи та функції
- •5.6 Масив покажчиків; покажчики на покажчики
- •5.7 Багатовимірні масиви
- •5.8 Ініціалізація масиву покажчиків
- •5.9 Покажчики в порівнянні з багатовимірними масивами
- •5.10 Аргументи командного рядка
- •5.11 Покажчики на функції
- •5.12 Складні оголошення
- •6.1 Основні поняття про структури
- •6.2 Структури та функції
- •6.3 Масиви структур
- •6.4 Покажчики на структури
- •6.5 Структури зі зворотнім звертанням
- •6.6 Пошук по таблиці
- •6.7 Typedef
- •6.8 Сполуки
- •6.9 Розрядні поля
- •7.1 Стандартний ввід і вивід
- •7.2 Форматований вивід - printf
- •7.3 Списки аргументів довільної довжини
- •7.4 Форматований ввід - scanf
- •7.5 Доступ до файлів
- •7.6 Обробка помилок - stderr і exit
- •7.7 Ввід і вивід рядків
- •7.8 Додаткові функції
- •7.8.1 Операції з ланцюжками
- •7.8.2 Перевірка і перетворення класів символів
- •7.8.3 Ungetc
- •7.8.4 Виконання команд
- •7.8.5 Керування пам'яттю
- •7.8.6 Математичні функції
- •7.8.7 Генератор випадкових чисел
- •8.1 Дескриптори файлів
- •8.2 Низькорівневий ввід/вивід - read і write
- •8.3 Open, creat, close, unlink
- •8.4 Довільний доступ - lseek
- •8.5 Приклад: втілення fopen і getc
- •8.6 Приклад - перелік вмісту каталогів
- •8.7 Приклад - розподільник пам'яті
5.3 Покажчики та масиви
У C існує тісний зв'язок між покажчиками і масивами, настільки тісний, що покажчики і масиви повинні обговорюватись одночасно. Будь-яку дію, що можна здійснити через індексацію масиву, можна також через покажчики. Версія з покажчиками буде дещо швидшою, але, принаймні для непосвячених, також трохи важчою у розумінні.
Оголошення
int a[10];
означує масив розміром у 10 елементів, тобто блок з 10-и суміжних об'єктів з назвами a[0],a[1], ..., a[9].
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
a: | | | | | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
a[0] a[1] a[9]
Позначення a[i] означає i-ний елемент масиву. Якщо pa, це покажчик на ціле, оголошений як
int *pa;
тоді присвоєння
pa = &a[0];
змушує pa вказувати на елемент з індексом нуль масиву a, тобто pa міститиме адресу a[0].
pa:
+-----+
| |
+---|-+
v
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
a: | | | | | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
a[0] a[1] a[9]
Тепер присвоєння
x = *pa;
копіює вміст a[0] до x.
Якщо pa вказує на певний елемент масиву, тоді певна річ, що pa+1 вказуватиме на наступний елемент, тоді як pa+n вказуватиме на елемент n після pa, тоді як pa-n — на елемент n попереду. Таким чином, якщо pa вказує на a[0], тоді
*(pa+1)
посилається на вміст a[1], pa+n є адресою a[n], а *(pa+n) - вмістом a[n].
pa: pa+1: pa+2:
+-----+ | |
| | | |
+---|-+ | |
v v v
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
a: | | | | | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
a[0] a[1] a[9]
Ці зауваження дійсні, незалежно від типу або розміру змінних масиву a. Зміст виразу «додати 1 до покажчика», і взагалі вся арифметика покажчиків зводиться до того, що pa+1 вказує на наступний об'єкт, тоді як pa+n вказує на n-ний об'єкт поза pa.
Відповідність між індексацією й арифметикою покажчиків дуже близька. За визначенням, значення змінної або виразу типу "масив" - це адреса елемента нуль масиву. Таким чином, після присвоєння
pa = &a[0];
pa й a матимуть абсолютно однакові значення. Оскільки назва масиву являється лише синонімом місцезнаходження початкового елемента, присвоєння pa=&a[0] можна з таким самим успіхом написати як
pa = a;
Швиде несподіванішим, з першого погляду, може здатися той факт, що посилання на a[n]можна також записати як *(a+n). Обчислюючи a[n], C сама перетворює його в *(a+n) — ці дві форми еквівалентні. Застосовуючи оператор & до обох частин цієї еквівалентності, ми дійдемо висновку, що &a[n] й a+n також однакові: a+n є адресою n-ного елемента після a. З іншого боку, якщо pa є покажчиком, він міг би використовуватись з індексом: pa[n], що рівнозначно*(pa+n). Одним словом, вираз масив-та-індекс рівнозначний тому, що записано як покажчик і зміщення.
Існує одна відмінність між назвою масиву і покажчиком, яку слід пам'ятати. Справа в тім, що покажчик — це змінна, тож pa=a і pa++ дозволено. Проте назва масиву не є мінною, тож конструкції на зразок a=pa або a++ заборонено.
Коли функції передано назву масиву, що передається насправді, це місцезнаходження першого елемента. Всередині викликаної функції цей аргумент стає локальною змінною, тож назва масиву, як параметр, насправді являється покажчиком — тобто змінною, що містить адресу. Ми можемо використати цей факт для написання іншої версії strlen — функції, що обчислює довжину ланцюжка.
/* strlen: повертає довжину ланцюжка s */
int strlen(char *s)
{
int n;
for (n = 0; *s != '\0', s++)
n++;
return n;
}
Оскільки s — це покажчик, то приріст його дозволено; s++ не матиме жодного впливу на символьний ланцюжок функції, яка викликала strlen — лише здійснює приріст приватної копії покажчика в strlen. Це означає, що всі виклики на зразок
strlen("hello, world"); /* ланцюжкова стала */
strlen(array); /* char array[100]; */
strlen(ptr); /* char *ptr; */
працюватимуть.
Формально, при визначенні функції, параметри
char s[];
i
char *s;
рівнозначні. Ми надаємо перевагу останньому, оскільки він відвертіше вказує на те, що ця змінна є покажчиком. Коли назву масиву передано функції, остання може, залежно від того як їй зручніше, вважати, що їй передано або масив, або покажчик, і опрацьовувати його відповідно. Вона навіть може вживати обидві нотації, якщо це здається їй слушним і зрозумілим.
Можливо також передати функції лише частину масиву, вказавши покажчиком на початок частини масиву. Наприклад, якщо a — це масив, то
f(&a[2])
і
f(a+2)
обидва, передають функції f адресу частини масиву, яка починається з a[2]. У самій f, оголошення параметрів може звучати як
f(int arr[]) { ... }
або
f(int *arr) { ... }
Тож, що стосується f, сам факт того, що параметр посилається на частину більшого масиву не має значення.
Якщо впевнені, що елементи існують, можливо також вказати індекс у зворотньому напрямку у масиві: p[-1], p[-2] і так далі. Це синтаксично допустимо, і вказує на елементи попередуp[0]. Звичайно, забороняється посилатися на об'єкти поза межами масиву.
